feat: Add Research Lab pack with paralleled math modules

Create comprehensive research-lab pack structure with mathematical
and quantum computing modules from blackroad-prism-console:

Math Modules:
- hilbert_core.py: Hilbert space symbolic reasoning
- collatz/: Distributed Collatz conjecture verification
- linmath/: Linear mathematics C library
- lucidia_math_forge/: Symbolic proof engine
- lucidia_math_lab/: Experimental mathematics

Quantum Modules:
- lucidia_quantum/: Quantum core
- quantum_engine/: Circuit simulation

Experiments:
- br_math/: Gödel gap, quantum experiments

Includes pack.yaml manifest and comprehensive README.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Alexa Louise
2025-11-28 23:49:03 -06:00
parent 5830335df9
commit 0108860bff
71 changed files with 3024 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
"""Quantum ML module for Lucidia.
This package is optional and guarded by the ``LUCIDIA_QML`` environment
variable. Only local simulators are used; remote providers are disabled
when ``LUCIDIA_QML_REMOTE`` is unset or false.
"""
from __future__ import annotations
import os
from typing import Dict, Type
from .backends import AerCPUBackend, QuantumBackend
_QML_ENABLED = os.getenv("LUCIDIA_QML", "off").lower() in {"1", "true", "on"}
_REMOTE_OK = os.getenv("LUCIDIA_QML_REMOTE", "false").lower() in {"1", "true", "on"}
# Registry of available backends
_BACKENDS: Dict[str, Type[QuantumBackend]] = {"aer_cpu": AerCPUBackend}
def is_enabled() -> bool:
"""Return True if the Quantum ML feature flag is on."""
return _QML_ENABLED
def get_backend(name: str = "aer_cpu") -> QuantumBackend:
"""Instantiate and return a backend by name.
Parameters
----------
name:
Registered backend key. Defaults to ``aer_cpu``.
"""
if not _QML_ENABLED:
raise RuntimeError("Quantum ML disabled")
if not _REMOTE_OK and name not in _BACKENDS:
raise RuntimeError("Remote backends are disabled")
backend_cls = _BACKENDS.get(name)
if backend_cls is None:
raise ValueError(f"Unknown backend '{name}'")
return backend_cls()

View File

@@ -0,0 +1,50 @@
"""Backend interfaces for Quantum ML.
Currently only the local Aer CPU simulator is implemented. GPU support is
stubbed out and disabled unless ``LUCIDIA_QML_GPU`` is set.
"""
from __future__ import annotations
import os
from abc import ABC, abstractmethod
from typing import Any
from qiskit import QuantumCircuit
class QuantumBackend(ABC):
"""Abstract quantum backend."""
@abstractmethod
def run(self, circuit: QuantumCircuit, shots: int = 1024, seed: int | None = None) -> Any:
"""Execute a circuit and return the raw result."""
class AerCPUBackend(QuantumBackend):
"""Qiskit Aer CPU simulator backend."""
def __init__(self) -> None:
from qiskit_aer import AerSimulator
self.simulator = AerSimulator(method="automatic")
def run(self, circuit: QuantumCircuit, shots: int = 1024, seed: int | None = None) -> Any:
if seed is not None:
circuit = circuit.copy()
circuit.seed_simulator(seed)
job = self.simulator.run(circuit, shots=shots)
return job.result()
class AerGPUBackend(AerCPUBackend):
"""Stub GPU backend. Requires CUDA and qiskit-aer-gpu."""
def __init__(self) -> None:
if os.getenv("LUCIDIA_QML_GPU", "off").lower() not in {"1", "true", "on"}:
raise RuntimeError("GPU backend disabled")
super().__init__()
try:
self.simulator.set_options(device="GPU") # type: ignore[attr-defined]
except Exception as exc: # pragma: no cover - fallback path
raise RuntimeError("GPU backend not available") from exc

View File

@@ -0,0 +1,26 @@
"""Quantum kernel utilities."""
from __future__ import annotations
from typing import Any, Optional
import numpy as np
from qiskit_machine_learning.algorithms import PegasosQSVC
from qiskit_machine_learning.kernels import QuantumKernel
from .backends import AerCPUBackend, QuantumBackend
def fit_qsvc(
x: np.ndarray,
y: np.ndarray,
kernel_opts: Optional[dict[str, Any]] = None,
backend: Optional[QuantumBackend] = None,
) -> PegasosQSVC:
"""Train a PegasosQSVC on the given data using a local quantum kernel."""
backend = backend or AerCPUBackend()
kernel = QuantumKernel(quantum_instance=backend.simulator, **(kernel_opts or {}))
model = PegasosQSVC(quantum_kernel=kernel)
model.fit(x, y)
return model

View File

@@ -0,0 +1,18 @@
"""Resource policies for quantum execution."""
from __future__ import annotations
from qiskit import QuantumCircuit
MAX_QUBITS = 8
MAX_DEPTH = 40
MAX_SHOTS = 1024
def validate_circuit(circuit: QuantumCircuit) -> None:
"""Raise ``ValueError`` if the circuit exceeds policy limits."""
if circuit.num_qubits > MAX_QUBITS:
raise ValueError("too many qubits")
if circuit.depth() > MAX_DEPTH:
raise ValueError("circuit too deep")

View File

@@ -0,0 +1,58 @@
"""Helper builders for Qiskit EstimatorQNN and SamplerQNN."""
from __future__ import annotations
from typing import Optional
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
from qiskit_machine_learning.neural_networks import EstimatorQNN, SamplerQNN
from .backends import AerCPUBackend, QuantumBackend
def build_estimator_qnn(
feature_map: QuantumCircuit,
ansatz: QuantumCircuit,
observable: QuantumCircuit | None,
input_size: int,
weight_size: int,
backend: Optional[QuantumBackend] = None,
) -> EstimatorQNN:
"""Construct an :class:`EstimatorQNN` with gradients enabled."""
backend = backend or AerCPUBackend()
input_params = ParameterVector("x", length=input_size)
weight_params = ParameterVector("w", length=weight_size)
return EstimatorQNN(
feature_map=feature_map,
ansatz=ansatz,
observable=observable,
input_params=input_params,
weight_params=weight_params,
backend=backend.simulator,
input_gradients=True,
)
def build_sampler_qnn(
feature_map: QuantumCircuit,
ansatz: QuantumCircuit,
input_size: int,
weight_size: int,
num_classes: int,
backend: Optional[QuantumBackend] = None,
) -> SamplerQNN:
"""Construct a probabilistic :class:`SamplerQNN`."""
backend = backend or AerCPUBackend()
input_params = ParameterVector("x", length=input_size)
weight_params = ParameterVector("w", length=weight_size)
return SamplerQNN(
feature_map=feature_map,
ansatz=ansatz,
input_params=input_params,
weight_params=weight_params,
output_shape=num_classes,
backend=backend.simulator,
)

View File

@@ -0,0 +1,51 @@
"""Basic tests for the Quantum ML module."""
from __future__ import annotations
import importlib
import numpy as np
import pytest
pytest.importorskip("torch")
pytest.importorskip("qiskit")
pytest.importorskip("qiskit_machine_learning")
import torch
from qiskit.circuit.library import RealAmplitudes, ZZFeatureMap
import lucidia.quantum as qml
from lucidia.quantum.kernels import fit_qsvc
from lucidia.quantum.qnn import build_sampler_qnn
from lucidia.quantum.torch_bridge import QModule
def test_feature_flag_off(monkeypatch):
monkeypatch.setenv("LUCIDIA_QML", "off")
importlib.reload(qml)
assert not qml.is_enabled()
with pytest.raises(RuntimeError):
qml.get_backend()
def test_sampler_qnn_gradients(monkeypatch):
monkeypatch.setenv("LUCIDIA_QML", "on")
importlib.reload(qml)
feature_map = ZZFeatureMap(2)
ansatz = RealAmplitudes(2, reps=1)
qnn = build_sampler_qnn(feature_map, ansatz, input_size=2, weight_size=ansatz.num_parameters, num_classes=2)
module = QModule(qnn, seed=1)
x = torch.zeros((1, 2), requires_grad=True)
out = module(x)
out.backward(torch.ones_like(out))
assert torch.all(torch.isfinite(x.grad))
def test_qsvc_training(monkeypatch):
monkeypatch.setenv("LUCIDIA_QML", "on")
importlib.reload(qml)
x = np.array([[0, 0], [1, 1]])
y = np.array([0, 1])
model = fit_qsvc(x, y)
preds = model.predict(x)
assert preds.shape == (2,)

View File

@@ -0,0 +1,37 @@
"""PyTorch bridge for Quantum Neural Networks."""
from __future__ import annotations
import os
import random
from typing import Any
import numpy as np
import torch
from qiskit_machine_learning.connectors import TorchConnector
from qiskit_machine_learning.neural_networks import NeuralNetwork
class QModule(torch.nn.Module):
"""Wrap a Qiskit ``NeuralNetwork`` as a ``torch.nn.Module``."""
def __init__(self, qnn: NeuralNetwork, seed: int | None = 42) -> None:
super().__init__()
if seed is not None:
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
self.qnn = qnn
self.connector = TorchConnector(neural_network=self.qnn)
def forward(self, x: torch.Tensor) -> torch.Tensor:
if not isinstance(x, torch.Tensor): # pragma: no cover - defensive
raise TypeError("expected torch.Tensor")
return self.connector(x)
def to(self, device: Any) -> "QModule": # type: ignore[override]
if device not in {"cpu", torch.device("cpu")}: # pragma: no cover - gpu path
if os.getenv("LUCIDIA_QML_GPU", "off").lower() not in {"1", "true", "on"}:
raise RuntimeError("GPU disabled")
self.connector.to(device)
return super().to(device)

View File

@@ -0,0 +1,9 @@
"""Lucidia Quantum Engine."""
from .policy import enforce_import_block, guard_env, set_seed
enforce_import_block()
__all__ = [
'enforce_import_block',
'guard_env',
'set_seed',
]

View File

@@ -0,0 +1,64 @@
"""CLI entrypoint for the quantum engine."""
from __future__ import annotations
import argparse
import torch
from .policy import guard_env, set_seed
from .models import PQCClassifier, QAOAModel, VQEModel
from .device import Device
def _run(args: argparse.Namespace) -> None:
model_map = {
'vqe': VQEModel,
'qaoa': QAOAModel,
'qkernel': PQCClassifier,
}
model = model_map[args.example](n_wires=args.wires)
x = torch.zeros(args.shots, 1, device=args.device)
out = model(x)
print(out.mean().item())
def _bench(args: argparse.Namespace) -> None:
print(f"running {args.suite} bench")
def _qasm(args: argparse.Namespace) -> None:
dev = Device(n_wires=2)
with open(args.outfile, 'w', encoding='utf-8') as fh:
fh.write(dev.qasm())
def main() -> None:
guard_env()
parser = argparse.ArgumentParser(prog='lucidia-quantum')
parser.add_argument('--seed', type=int, default=0)
sub = parser.add_subparsers(dest='cmd', required=True)
runp = sub.add_parser('run')
runp.add_argument('--example', choices=['vqe', 'qaoa', 'qkernel'], required=True)
runp.add_argument('--wires', type=int, default=4)
runp.add_argument('--shots', type=int, default=1024)
runp.add_argument('--device', type=str, default='cpu')
benchp = sub.add_parser('bench')
benchp.add_argument('--suite', choices=['smoke', 'full'], default='smoke')
qasmp = sub.add_parser('qasm')
qasmp.add_argument('--in', dest='infile', required=True)
qasmp.add_argument('--out', dest='outfile', required=True)
args = parser.parse_args()
set_seed(args.seed)
if args.cmd == 'run':
_run(args)
elif args.cmd == 'bench':
_bench(args)
elif args.cmd == 'qasm':
_qasm(args)
if __name__ == '__main__': # pragma: no cover
main()

View File

@@ -0,0 +1,19 @@
"""Device wrapper around torchquantum.QuantumDevice."""
from __future__ import annotations
from third_party import torchquantum as tq
from .policy import guard_env, set_seed
class Device:
"""Lightweight wrapper providing deterministic setup and QASM export."""
def __init__(self, n_wires: int, bsz: int = 1, device: str = 'cpu', seed: int | None = None):
guard_env()
set_seed(seed)
self.qdev = tq.QuantumDevice(n_wires=n_wires, bsz=bsz, device=device)
def qasm(self) -> str:
"""Return a QASM-like string of the operations."""
return self.qdev.qasm()

View File

@@ -0,0 +1,21 @@
"""Layer helpers wrapping TorchQuantum primitives."""
from __future__ import annotations
from torch import nn
from third_party import torchquantum as tq
class RandomLayer(tq.RandomLayer):
"""Expose torchquantum.RandomLayer."""
class QFTLayer(tq.QFTLayer):
"""Expose torchquantum.QFTLayer."""
class QutritEmulator(nn.Module):
"""Encode a trit with two qubits and penalise the forbidden state."""
def forward(self, qdev, wires):
# Probability of reaching the forbidden |11> state acts as a penalty
return qdev.prob_11(wires)

View File

@@ -0,0 +1,26 @@
"""Utility metrics for quantum circuits."""
from __future__ import annotations
import torch
def expval(qdev) -> torch.Tensor:
return qdev.measure_all()
def kl_div(p: torch.Tensor, q: torch.Tensor) -> torch.Tensor:
p = p + 1e-9
q = q + 1e-9
return torch.sum(p * (torch.log(p) - torch.log(q)), dim=-1)
def energy(values: torch.Tensor) -> torch.Tensor:
return values.sum(dim=-1)
def depth(qdev) -> int:
return len(qdev.ops)
def two_qubit_count(qdev) -> int:
return 0

View File

@@ -0,0 +1,44 @@
"""Example quantum models."""
from __future__ import annotations
import torch
import torch.nn.functional as F
from torch import nn
from third_party import torchquantum as tq
class PQCClassifier(nn.Module):
def __init__(self, n_wires: int = 4):
super().__init__()
self.n_wires = n_wires
self.measure = tq.MeasureAll(tq.PauliZ)
self.rx0 = tq.RX(True, True)
self.ry0 = tq.RY(True, True)
self.rz0 = tq.RZ(True, True)
def forward(self, x: torch.Tensor) -> torch.Tensor:
bsz = x.shape[0]
qdev = tq.QuantumDevice(n_wires=self.n_wires, bsz=bsz, device=x.device)
self.rx0(qdev, wires=0)
self.ry0(qdev, wires=1)
self.rz0(qdev, wires=2)
meas = self.measure(qdev)
if meas.shape[-1] < 4:
pad = torch.zeros(bsz, 4 - meas.shape[-1], device=meas.device, dtype=meas.dtype)
meas = torch.cat([meas, pad], dim=-1)
logits = meas[..., :4].reshape(bsz, 2, 2).sum(-1)
return F.log_softmax(logits, dim=1)
class VQEModel(nn.Module):
"""Placeholder VQE model."""
def forward(self, *args, **kwargs):
return torch.tensor(0.0)
class QAOAModel(nn.Module):
"""Placeholder QAOA model."""
def forward(self, *args, **kwargs):
return torch.tensor(0.0)

View File

@@ -0,0 +1,56 @@
"""Runtime guardrails for the quantum engine."""
from __future__ import annotations
import builtins
import os
import random
import socket
from typing import Optional
MAX_WIRES = 8
MAX_SHOTS = 2048
TIMEOUT = 60
DENYLIST = (
'torchquantum.plugins',
'qiskit',
'qiskit_ibm_runtime',
)
def enforce_import_block() -> None:
"""Block imports of disallowed modules."""
real_import = builtins.__import__
def guarded(name, *args, **kwargs):
for bad in DENYLIST:
if name.startswith(bad):
raise ImportError(f'{bad} is disabled')
return real_import(name, *args, **kwargs)
builtins.__import__ = guarded
def guard_env() -> None:
"""Fail closed on hardware flags and block outbound sockets."""
if os.getenv('LUCIDIA_QHW') == '1':
raise RuntimeError('Hardware backends are disabled')
class _BlockedSocket(socket.socket):
def __init__(self, *args, **kwargs):
raise RuntimeError('Network access disabled')
socket.socket = _BlockedSocket
def set_seed(seed: Optional[int] = None) -> int:
if seed is None:
seed = 0
random.seed(seed)
try:
import torch
torch.manual_seed(seed)
except Exception:
pass
return seed

View File

@@ -0,0 +1,20 @@
"""Circuit search scaffolding."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List
from third_party import torchquantum as tq
@dataclass
class SearchSpace:
layers: List[tq.RandomLayer] = field(default_factory=list)
def add_layer(self, layer: tq.RandomLayer) -> None:
self.layers.append(layer)
def noise_aware_score(circuit: SearchSpace, noise: float | None = None) -> float:
"""Placeholder for future noise-aware scoring."""
return float(len(circuit.layers))