Metadata-Version: 2.4
Name: xqsa
Version: 0.3.1
Summary: Solver adapters for XQMX models (dwave-samplers; pluggable solver interface).
License: AGPL-3.0-or-later
Requires-Python: >=3.13
Requires-Dist: dwave-samplers>=1.0
Requires-Dist: xqvm-py
Provides-Extra: cuda
Requires-Dist: cupy-cuda12x>=13.0; extra == 'cuda'
Requires-Dist: nvidia-cuda-nvrtc-cu12; extra == 'cuda'
Requires-Dist: nvidia-cuda-runtime-cu12; extra == 'cuda'
Provides-Extra: dwave
Requires-Dist: dwave-system>=1.0; extra == 'dwave'
Provides-Extra: metal
Requires-Dist: pyobjc-framework-metal>=11.0; (sys_platform == 'darwin') and extra == 'metal'
Provides-Extra: quip
Requires-Dist: quip-signer>=0.2.2; extra == 'quip'
Requires-Dist: substrate-interface<2,>=1.7.4; extra == 'quip'
Description-Content-Type: text/markdown

# xqsa -- Solver adapters for XQMX models

Pluggable solvers for quadratic optimisation models produced by the XQuad toolchain.

| Solver | Class | Transport | Install |
|---|---|---|---|
| DWave CPU simulated annealing | `SolverDWaveCPU` | local | `pip install xqsa` |
| D-Wave Advantage QPU | `SolverDWaveQPU` | D-Wave Leap cloud | `pip install xqsa[dwave]` |
| CUDA GPU simulated annealing | `SolverCudaGPU` | local (NVIDIA GPU) | `pip install xqsa[cuda]` |
| Metal GPU SA / Gibbs | `SolverMetalGPU` | local (Apple GPU, macOS) | `pip install xqsa[metal]` |

## Install

```sh
# CPU simulated annealing only
pip install xqsa

# Add D-Wave QPU support
pip install xqsa[dwave]

# Add CUDA GPU support (requires NVIDIA GPU + CUDA driver)
pip install xqsa[cuda]

# Add Metal GPU support (requires macOS + Apple Silicon GPU)
pip install xqsa[metal]
```

Already have xqsa installed? Add an extra on top:

```sh
pip install "xqsa[cuda]"          # adds CUDA support to an existing install
```

### Driver prerequisites

| Extra | Hardware | Prerequisite | Verify |
|-------|----------|-------------|--------|
| `[cuda]` | NVIDIA GPU | CUDA 12.x driver | `nvidia-smi` |
| `[metal]` | Apple Silicon | macOS 13+ (Ventura) | built-in on supported Macs |
| `[dwave]` | -- | D-Wave Leap account + `DWAVE_API_TOKEN` env var | `dwave ping` |

After installing an extra, verify the solver class imports:

```sh
python -c "from xqsa import SolverCudaGPU; print('ok')"
python -c "from xqsa import SolverMetalGPU; print('ok')"
python -c "from xqsa import SolverDWaveQPU; print('ok')"
```

### uv workspace caveat

`uv sync` installs only base dependencies, not optional extras. To test GPU/QPU solvers locally, install the extra explicitly:

```sh
uv pip install "xqsa[cuda]"       # or [metal] / [dwave]
```

Note that a bare `uv run pytest` re-syncs the environment and drops both the extra and the maturin-built `xqffi` extension. Use `uv run --no-sync pytest` to preserve them, or re-run `maturin develop` + `uv pip install "xqsa[...]"` after each sync.

## Quick start -- CPU simulated annealing

```python
from xqsa import SolverDWaveCPU
from xqvm_py import XQMX, XQMXDomain

model = XQMX.binary_model(size=4)
model.set_linear(0, -1)
model.set_quadratic(0, 1, 2)

solver = SolverDWaveCPU()
result = solver.solve(model)
# result.sample: XQMX   -- the best assignment found
# result.energy: int     -- authoritative Hamiltonian energy
# result.timing: float   -- wall-clock seconds spent solving
# result.metadata: dict  -- seed, reads, solver-specific params
```

## Quick start -- D-Wave Advantage QPU

Requires a [D-Wave Leap](https://cloud.dwavesys.com/leap/) account and
`pip install xqsa[dwave]`.

```python
import os
os.environ["DWAVE_API_TOKEN"] = "your-leap-token"  # or pass token= directly

from xqsa import SolverDWaveQPU
from xqvm_py.xqmx import XQMX

model = XQMX.binary_model(size=4)
model.set_linear(0, -1)
model.set_quadratic(0, 1, 2)

solver = SolverDWaveQPU()              # auto-selects best Advantage system
result = solver.solve(model)
print(result.metadata["solver"])       # e.g. "Advantage_system5.4"
print(result.metadata["qpu_timing"])   # QPU timing breakdown from Leap
```

Credential resolution order: `token=` constructor argument ->
`DWAVE_API_TOKEN` env var -> `ValueError`.

For a specific solver: `SolverDWaveQPU(solver="Advantage_system5.4")`.
For custom annealing: `solver.solve(model, annealing_time=100, chain_strength=2.0)`.

## Quick start -- CUDA GPU simulated annealing

Requires an NVIDIA GPU with CUDA support and `pip install xqsa[cuda]`.

```python
from xqsa import SolverCudaGPU
from xqvm_py.xqmx import XQMX

model = XQMX.binary_model(size=4)
model.set_linear(0, -1)
model.set_quadratic(0, 1, 2)

solver = SolverCudaGPU()                # uses all defaults
result = solver.solve(model)
print(result.energy, result.timing)

# Custom parameters
solver = SolverCudaGPU(num_reads=200, num_sweeps=2000, seed=42)
result = solver.solve(model, beta_range=(0.1, 5.0))
```

Algorithm selection via the `strategy` parameter (currently only `"sa"`
is supported; `"gibbs"` and `"metropolis"` are planned).

## Quick start -- Metal GPU (Apple Silicon)

Requires macOS with an Apple Metal GPU and `pip install xqsa[metal]`.

```python
from xqsa import SolverMetalGPU
from xqvm_py.xqmx import XQMX

model = XQMX.binary_model(size=4)
model.set_linear(0, -1)
model.set_quadratic(0, 1, 2)

# Simulated annealing (default), or strategy="gibbs" for block Gibbs sampling.
solver = SolverMetalGPU(strategy="sa", num_reads=200, num_sweeps=2000, seed=42)
result = solver.solve(model)
print(result.energy, result.timing)
```

The `strategy` parameter selects `"sa"` (simulated annealing) or `"gibbs"`
(block Gibbs sampling over a greedy graph colouring). `beta_schedule_type`
selects `"geometric"` (default) or `"linear"`. Coefficients are computed in
float32 on the GPU; `result.energy` is recomputed authoritatively in
integer arithmetic.

## Selecting a solver by name

`build_solver` constructs any backend from a short, CLI-friendly name, so
tools and scripts can stay backend-agnostic:

```python
from xqsa import SOLVERS, build_solver

print(sorted(SOLVERS))          # ['cuda-gpu', 'dwave-cpu', 'dwave-qpu', 'metal-gpu']
solver = build_solver("dwave-cpu", seed=42)
result = solver.solve(model)
```

`seed` is forwarded to the classical SA backends (`dwave-cpu`, `cuda-gpu`,
`metal-gpu`) and ignored for `dwave-qpu`, which is physical hardware. Every
example runner exposes this as a `--solver` flag:

```sh
uv run python examples/maxcut/runner.py --solver cuda-gpu
```

## Solver protocol

Any class conforming to `xqsa.Solver` can drop in:

```python
class Solver(ABC):
    @abstractmethod
    def solve(self, model: XQMX, **kwargs: Any) -> SolverResult: ...
```

`SolverResult` is a frozen dataclass of `(sample: XQMX, energy: int, timing: float, metadata: dict)`. Solvers return the best solution found for the model.

## Specification

Authoritative specification: [`../spec/xqsa/SPEC.md`](../spec/xqsa/SPEC.md). The spec is the source of truth; any divergence in this reference implementation is a bug.

## Also see

- [`xqvm_py`](../xqvm_py/) -- pure-Python reference VM.
- [`xqffi`](../xqffi/) -- pyo3 FFI bindings to the Rust runtime.
- [`xqcp`](../xqcp/) -- constraint-programming DSL that compiles to models this package can sample.
- [`xquad`](../xquad/) -- umbrella meta-package.
- [`docs/python-api-walkthrough.md`](../docs/python-api-walkthrough.md) -- end-to-end tour.

## License

AGPL-3.0-or-later.
