Files
lucidia-core/quantum_engine/backends.py
Alexa Louise 6afdb4b148 Initial extraction from blackroad-prism-console
Lucidia Core - AI reasoning engines for specialized domains:
- Physicist (867 lines) - energy modeling, force calculations
- Mathematician (760 lines) - symbolic computation, proofs
- Geologist (654 lines) - terrain modeling, stratigraphy
- Engineer (599 lines) - structural analysis, optimization
- Painter (583 lines) - visual generation, graphics
- Chemist (569 lines) - molecular analysis, reactions
- Analyst (505 lines) - pattern recognition, insights
- Plus: architect, researcher, mediator, speaker, poet, navigator

Features:
- FastAPI wrapper with REST endpoints for each agent
- CLI with `lucidia list`, `lucidia run`, `lucidia api`
- Codex YAML configurations for agent personalities
- Quantum engine extensions

12,512 lines of Python across 91 files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 08:00:53 -06:00

208 lines
7.2 KiB
Python

"""Runtime selection of quantum simulation backends."""
from __future__ import annotations
from dataclasses import dataclass
from functools import lru_cache
import importlib
import importlib.util
from typing import Callable, Dict, List, Optional
@dataclass(frozen=True)
class QuantumBackend:
"""Metadata describing an available quantum simulation backend."""
name: str
description: str
create_device: Callable[[int, int, str], object]
qasm_exporter: Optional[Callable[[object], str]] = None
def summary(self) -> str:
"""Return a compact human readable description."""
return f"{self.name}: {self.description}"
def _register_torchquantum(registry: Dict[str, QuantumBackend]) -> None:
from third_party import torchquantum as tq
version = getattr(tq, "__version__", "vendored")
def _create_device(n_wires: int, bsz: int = 1, device: str = "cpu") -> object:
return tq.QuantumDevice(n_wires=n_wires, bsz=bsz, device=device)
def _export_qasm(device: object) -> str:
if hasattr(device, "qasm"):
return device.qasm() # type: ignore[no-any-return]
return ""
registry["torchquantum"] = QuantumBackend(
name="torchquantum",
description=f"TorchQuantum ({version}) batch-friendly simulator",
create_device=_create_device,
qasm_exporter=_export_qasm,
)
def _register_pennylane(registry: Dict[str, QuantumBackend]) -> None:
if importlib.util.find_spec("pennylane") is None:
return
import pennylane as qml
import numpy as _np
import torch
version = getattr(qml, "__version__", "unknown")
gate_map = {"rx": qml.RX, "ry": qml.RY, "rz": qml.RZ}
class _PennyLaneDevice:
"""Lightweight adapter exposing a TorchQuantum-like interface."""
def __init__(self, n_wires: int, bsz: int = 1, device: str = "cpu") -> None:
if bsz != 1:
raise ValueError("Pennylane backend currently supports batch size 1")
self.n_wires = n_wires
self._qml = qml
self._device = qml.device("default.qubit", wires=n_wires)
self._ops: List[tuple[str, tuple[int, ...], Optional[float]]] = []
def apply(self, name: str, mat, wire, params=None) -> None: # pragma: no cover - thin adapter
wires: tuple[int, ...]
if isinstance(wire, (list, tuple)):
wires = tuple(int(w) for w in wire)
else:
wires = (int(wire),)
theta: Optional[float] = None
if params:
theta = float(params[0])
self._ops.append((name.lower(), wires, theta))
def measure_all(self): # pragma: no cover - thin adapter
qml = self._qml
ops = list(self._ops)
@qml.qnode(self._device)
def circuit():
for opname, wires, theta in ops:
gate = gate_map.get(opname)
if gate is None:
raise ValueError(f"Unsupported gate {opname!r} for pennylane backend")
kwargs = {"wires": wires if len(wires) > 1 else wires[0]}
if theta is None:
gate(**kwargs)
else:
gate(theta, **kwargs)
return [qml.expval(qml.PauliZ(w)) for w in range(self.n_wires)]
values = circuit()
array = _np.asarray(values, dtype=_np.float32)
return torch.from_numpy(array).reshape(1, -1)
def qasm(self) -> str: # pragma: no cover - string formatting helper
lines: List[str] = []
for opname, wires, theta in self._ops:
targets = ",".join(f"q[{w}]" for w in wires)
gate = opname.upper()
if theta is None:
lines.append(f"{gate} {targets}")
else:
lines.append(f"{gate}({theta}) {targets}")
return "\n".join(lines)
registry["pennylane"] = QuantumBackend(
name="pennylane",
description=f"Pennylane ({version}) analytic simulator",
create_device=lambda n_wires, bsz=1, device="cpu": _PennyLaneDevice(n_wires, bsz, device),
qasm_exporter=lambda dev: dev.qasm(),
)
def _register_qiskit(registry: Dict[str, QuantumBackend]) -> None:
if importlib.util.find_spec("qiskit") is None:
return
try: # pragma: no cover - optional dependency subject to policy guard
from qiskit import QuantumCircuit
import torch
version = importlib.import_module("qiskit").__version__
except Exception:
return
class _QiskitDevice:
def __init__(self, n_wires: int, bsz: int = 1, device: str = "cpu") -> None:
if bsz != 1:
raise ValueError("Qiskit backend currently supports batch size 1")
self.n_wires = n_wires
self._circuit = QuantumCircuit(n_wires)
def apply(self, name: str, mat, wire, params=None) -> None: # pragma: no cover - thin adapter
gate = getattr(self._circuit, name.lower(), None)
if gate is None:
raise ValueError(f"Unsupported gate {name!r} for qiskit backend")
if params:
gate(float(params[0]), int(wire))
else:
gate(int(wire))
def measure_all(self): # pragma: no cover - thin adapter
# qasm export is the primary goal; provide zeroed expectations as a placeholder.
return torch.zeros(1, self.n_wires, dtype=torch.float32)
def qasm(self) -> str: # pragma: no cover - thin adapter
return self._circuit.qasm()
registry["qiskit"] = QuantumBackend(
name="qiskit",
description=f"Qiskit ({version}) circuit builder",
create_device=lambda n_wires, bsz=1, device="cpu": _QiskitDevice(n_wires, bsz, device),
qasm_exporter=lambda dev: dev.qasm(),
)
@lru_cache()
def _registry() -> Dict[str, QuantumBackend]:
registry: Dict[str, QuantumBackend] = {}
_register_torchquantum(registry)
_register_pennylane(registry)
_register_qiskit(registry)
return registry
def available_backends() -> List[QuantumBackend]:
"""Return the discovered quantum backends."""
return list(_registry().values())
def backend_names() -> List[str]:
"""Return the names of registered backends in preference order."""
preferred_order = ["torchquantum", "pennylane", "qiskit"]
names = {backend.name for backend in available_backends()}
ordered = [name for name in preferred_order if name in names]
ordered.extend(sorted(names - set(ordered)))
return ordered
def get_backend(name: Optional[str] = None) -> QuantumBackend:
"""Return the requested backend, defaulting to the first available one."""
registry = _registry()
if name is None:
for candidate in backend_names():
if candidate in registry:
return registry[candidate]
raise RuntimeError("No quantum backends registered")
if name not in registry:
raise ValueError(f"Unknown quantum backend {name!r}")
return registry[name]
def backend_summaries() -> List[str]:
"""Return human readable summaries for CLI display."""
return [backend.summary() for backend in available_backends()]