qversus 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qversus-0.1.0/LICENSE +21 -0
- qversus-0.1.0/PKG-INFO +194 -0
- qversus-0.1.0/README.md +129 -0
- qversus-0.1.0/pyproject.toml +72 -0
- qversus-0.1.0/qversus/__init__.py +17 -0
- qversus-0.1.0/qversus/core/__init__.py +22 -0
- qversus-0.1.0/qversus/core/circuit_trace.py +107 -0
- qversus-0.1.0/qversus/core/rng.py +38 -0
- qversus-0.1.0/qversus/core/trace.py +69 -0
- qversus-0.1.0/qversus/problems/__init__.py +30 -0
- qversus-0.1.0/qversus/problems/base.py +51 -0
- qversus-0.1.0/qversus/problems/bernstein_vazirani.py +70 -0
- qversus-0.1.0/qversus/problems/chsh.py +74 -0
- qversus-0.1.0/qversus/problems/deutsch_jozsa.py +75 -0
- qversus-0.1.0/qversus/problems/grover.py +65 -0
- qversus-0.1.0/qversus/problems/interference.py +79 -0
- qversus-0.1.0/qversus/problems/maxcut.py +80 -0
- qversus-0.1.0/qversus/problems/noise.py +72 -0
- qversus-0.1.0/qversus/problems/qec_repetition.py +67 -0
- qversus-0.1.0/qversus/problems/qec_surface.py +66 -0
- qversus-0.1.0/qversus/problems/qft.py +67 -0
- qversus-0.1.0/qversus/problems/qml_classifier.py +113 -0
- qversus-0.1.0/qversus/problems/qpe.py +70 -0
- qversus-0.1.0/qversus/problems/qrng.py +71 -0
- qversus-0.1.0/qversus/problems/shor.py +71 -0
- qversus-0.1.0/qversus/problems/simon.py +76 -0
- qversus-0.1.0/qversus/problems/single_qubit.py +73 -0
- qversus-0.1.0/qversus/problems/state_prep.py +72 -0
- qversus-0.1.0/qversus/problems/superdense.py +63 -0
- qversus-0.1.0/qversus/problems/teleportation.py +77 -0
- qversus-0.1.0/qversus/problems/vqe.py +69 -0
- qversus-0.1.0/qversus/registry.py +69 -0
- qversus-0.1.0/qversus/solvers/__init__.py +29 -0
- qversus-0.1.0/qversus/solvers/base.py +59 -0
- qversus-0.1.0/qversus/solvers/cirq_solvers.py +81 -0
- qversus-0.1.0/qversus/solvers/classical_solvers.py +701 -0
- qversus-0.1.0/qversus/solvers/hardware_solvers.py +119 -0
- qversus-0.1.0/qversus/solvers/pennylane_solvers.py +193 -0
- qversus-0.1.0/qversus/solvers/qiskit_solvers.py +1030 -0
- qversus-0.1.0/qversus/solvers/stim_solvers.py +66 -0
- qversus-0.1.0/qversus.egg-info/PKG-INFO +194 -0
- qversus-0.1.0/qversus.egg-info/SOURCES.txt +47 -0
- qversus-0.1.0/qversus.egg-info/dependency_links.txt +1 -0
- qversus-0.1.0/qversus.egg-info/requires.txt +46 -0
- qversus-0.1.0/qversus.egg-info/top_level.txt +1 -0
- qversus-0.1.0/setup.cfg +4 -0
- qversus-0.1.0/tests/test_core.py +64 -0
- qversus-0.1.0/tests/test_physics.py +309 -0
- qversus-0.1.0/tests/test_registry.py +102 -0
qversus-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Felipe Santibáñez-Leal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
qversus-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qversus
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Canonical quantum-computing problems solved by real frameworks (Qiskit, PennyLane, Cirq, Stim) next to classical baselines, with replayable, seeded traces.
|
|
5
|
+
Author: Felipe Santibanez-Leal
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/fsantibanezleal/CAOS_QVersus
|
|
8
|
+
Project-URL: Documentation, https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/README.md
|
|
9
|
+
Project-URL: Issues, https://github.com/fsantibanezleal/CAOS_QVersus/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: quantum computing,quantum algorithms,benchmark,classical baseline,qiskit,pennylane,cirq,stim,grover,qaoa,vqe,quantum error correction,surface code,education
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Intended Audience :: Education
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
21
|
+
Classifier: Topic :: Education
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy>=1.26
|
|
27
|
+
Provides-Extra: qiskit
|
|
28
|
+
Requires-Dist: qiskit<3,>=2.4; extra == "qiskit"
|
|
29
|
+
Requires-Dist: qiskit-aer>=0.17; extra == "qiskit"
|
|
30
|
+
Provides-Extra: pennylane
|
|
31
|
+
Requires-Dist: pennylane>=0.45; extra == "pennylane"
|
|
32
|
+
Requires-Dist: networkx>=3.2; extra == "pennylane"
|
|
33
|
+
Provides-Extra: cirq
|
|
34
|
+
Requires-Dist: cirq-core>=1.6; extra == "cirq"
|
|
35
|
+
Provides-Extra: stim
|
|
36
|
+
Requires-Dist: stim>=1.16; extra == "stim"
|
|
37
|
+
Requires-Dist: pymatching>=2.0; extra == "stim"
|
|
38
|
+
Provides-Extra: learn
|
|
39
|
+
Requires-Dist: scikit-learn>=1.5; extra == "learn"
|
|
40
|
+
Provides-Extra: hardware
|
|
41
|
+
Requires-Dist: qiskit<3,>=2.4; extra == "hardware"
|
|
42
|
+
Requires-Dist: qiskit-ibm-runtime>=0.30; extra == "hardware"
|
|
43
|
+
Provides-Extra: all
|
|
44
|
+
Requires-Dist: qiskit<3,>=2.4; extra == "all"
|
|
45
|
+
Requires-Dist: qiskit-aer>=0.17; extra == "all"
|
|
46
|
+
Requires-Dist: pennylane>=0.45; extra == "all"
|
|
47
|
+
Requires-Dist: networkx>=3.2; extra == "all"
|
|
48
|
+
Requires-Dist: cirq-core>=1.6; extra == "all"
|
|
49
|
+
Requires-Dist: stim>=1.16; extra == "all"
|
|
50
|
+
Requires-Dist: pymatching>=2.0; extra == "all"
|
|
51
|
+
Requires-Dist: scikit-learn>=1.5; extra == "all"
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
54
|
+
Requires-Dist: ruff==0.15.18; extra == "dev"
|
|
55
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
56
|
+
Requires-Dist: qiskit<3,>=2.4; extra == "dev"
|
|
57
|
+
Requires-Dist: qiskit-aer>=0.17; extra == "dev"
|
|
58
|
+
Requires-Dist: pennylane>=0.45; extra == "dev"
|
|
59
|
+
Requires-Dist: networkx>=3.2; extra == "dev"
|
|
60
|
+
Requires-Dist: cirq-core>=1.6; extra == "dev"
|
|
61
|
+
Requires-Dist: stim>=1.16; extra == "dev"
|
|
62
|
+
Requires-Dist: pymatching>=2.0; extra == "dev"
|
|
63
|
+
Requires-Dist: scikit-learn>=1.5; extra == "dev"
|
|
64
|
+
Dynamic: license-file
|
|
65
|
+
|
|
66
|
+
# qversus
|
|
67
|
+
|
|
68
|
+
[](https://github.com/fsantibanezleal/CAOS_QVersus/actions/workflows/ci.yml)
|
|
69
|
+
[](https://pypi.org/project/qversus/)
|
|
70
|
+
[](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/LICENSE)
|
|
71
|
+
|
|
72
|
+
**Canonical quantum-computing problems, solved by the real frameworks next to the classical baseline that is
|
|
73
|
+
usually still more practical, with every run recorded as a replayable, seeded trace.**
|
|
74
|
+
|
|
75
|
+
`qversus` separates *what* to compute from *how*. A `Problem` is a formulation (Grover search, MaxCut, the H2
|
|
76
|
+
ground state, a surface-code memory, ...) with a set of parameter regimes (`Instance`s). A `Solver` is a thin
|
|
77
|
+
adapter that attacks a problem with **one** real framework (Qiskit + Aer, PennyLane, Cirq, Stim + PyMatching)
|
|
78
|
+
or with a classical method, and returns the same `SolverResult` shape whatever the framework. A registry pairs
|
|
79
|
+
them, so running a quantum method and the classical baseline side by side is one loop, not one script per
|
|
80
|
+
framework.
|
|
81
|
+
|
|
82
|
+
Circuit-model solvers also return a `Trace`: the state after every gate (amplitudes, per-qubit Bloch vectors,
|
|
83
|
+
probabilities), the circuit as a flat op list, and a seeded shot histogram. A run is a pure function of
|
|
84
|
+
`(params, seed)`: with the same framework versions, the same inputs give the same bytes.
|
|
85
|
+
|
|
86
|
+
The package makes no claim of quantum advantage. Every problem ships a classical baseline, and on these
|
|
87
|
+
textbook-scale instances the classical method wins on wall time; what the quantum methods show are real
|
|
88
|
+
phenomena (interference, entanglement, query separations, error-correction thresholds) at a scale a laptop
|
|
89
|
+
simulates exactly.
|
|
90
|
+
|
|
91
|
+
## Install
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pip install qversus # core: NumPy only (formulations, registry, trace schema, RNG)
|
|
95
|
+
pip install "qversus[all]" # every simulator-based adapter
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| Extra | Brings | Enables |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `qiskit` | `qiskit>=2.4,<3`, `qiskit-aer>=0.17` | the circuit adapters (16 problems), the step tracer, the Aer noise model |
|
|
101
|
+
| `pennylane` | `pennylane>=0.45`, `networkx` | QAOA cross-check, VQE on H2, the quantum-kernel classifier, the H2 Hamiltonian for the exact baseline |
|
|
102
|
+
| `cirq` | `cirq-core>=1.6` | the third QAOA implementation |
|
|
103
|
+
| `stim` | `stim>=1.16`, `pymatching>=2.0` | repetition and surface-code memories decoded by minimum-weight matching |
|
|
104
|
+
| `learn` | `scikit-learn>=1.5` | the classical RBF-SVM baseline and the precomputed-kernel SVM |
|
|
105
|
+
| `hardware` | `qiskit-ibm-runtime>=0.30` | the opt-in IBM Quantum adapter (real QPU, needs a token) |
|
|
106
|
+
| `all` | every extra except `hardware` | |
|
|
107
|
+
|
|
108
|
+
A missing framework disables only its own adapters (a warning names it); the rest of the registry works.
|
|
109
|
+
|
|
110
|
+
## Quick start
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from qversus.registry import get_problem, solvers_for
|
|
114
|
+
|
|
115
|
+
problem = get_problem("maxcut")
|
|
116
|
+
instance = problem.instance("square") # the 4-cycle; optimum cut = 4
|
|
117
|
+
|
|
118
|
+
for solver in solvers_for(problem): # QAOA on Qiskit, PennyLane and Cirq + two classical methods
|
|
119
|
+
result = solver.run(problem, instance, seed=42, shots=2048)
|
|
120
|
+
print(f"{result.paradigm:12} {result.solver:18} cut={result.value['cut']} cost={result.cost}")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from qversus.registry import get_problem, solvers_for
|
|
125
|
+
|
|
126
|
+
problem = get_problem("grover")
|
|
127
|
+
instance = problem.instance("grover-4-10") # N = 16 items, item 10 marked
|
|
128
|
+
qiskit = next(s for s in solvers_for(problem) if s.framework == "qiskit")
|
|
129
|
+
result = qiskit.run(problem, instance, seed=42, shots=2048)
|
|
130
|
+
|
|
131
|
+
trace = result.trace.to_dict() # JSON-ready: steps, circuit_ops, measurements, provenance
|
|
132
|
+
print(result.value, trace["measurements"]["counts"])
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
More: [quick start guide](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/guides/01_quickstart.md).
|
|
136
|
+
|
|
137
|
+
## The catalogue
|
|
138
|
+
|
|
139
|
+
Twenty problems in six families, 119 instances. Each has at least one quantum method and one classical baseline.
|
|
140
|
+
|
|
141
|
+
| Problem | Family | Quantum solvers | Classical baseline |
|
|
142
|
+
|---|---|---|---|
|
|
143
|
+
| `single-qubit`, `qrng`, `interference` | fundamentals | Qiskit | Bloch/bit model, PRNG, wave optics |
|
|
144
|
+
| `state-prep`, `chsh`, `teleportation`, `superdense` | entanglement | Qiskit | the amplitudes written down directly, local hidden variables, measure-and-resend, one bit per carrier |
|
|
145
|
+
| `deutsch-jozsa`, `bernstein-vazirani`, `simon` | oracle algorithms | Qiskit | query-counting classical algorithms |
|
|
146
|
+
| `grover`, `qft`, `qpe`, `shor` | flagship algorithms | Qiskit | linear scan, FFT, exact phase, trial division |
|
|
147
|
+
| `maxcut`, `vqe`, `qml` | variational | Qiskit, PennyLane, Cirq | brute force + greedy, exact diagonalisation, RBF-SVM |
|
|
148
|
+
| `noise`, `qec-repetition`, `qec-surface` | noise and QEC | Qiskit-Aer, Stim + PyMatching | the exact answer, the unprotected qubit |
|
|
149
|
+
|
|
150
|
+
Per-problem parameters, result fields and the closed-form checks the tests enforce:
|
|
151
|
+
[docs/problems](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/problems.md).
|
|
152
|
+
|
|
153
|
+
## Contracts
|
|
154
|
+
|
|
155
|
+
- **`Problem` / `Instance`**: identity (id, bilingual EN/ES title and concept, category, metric, references),
|
|
156
|
+
the instances, and a `live_capable` hint (small and noise-free enough to re-simulate interactively).
|
|
157
|
+
- **`Solver` / `SolverResult`**: `run(problem, instance, seed, shots) -> SolverResult` with `value`, `cost`,
|
|
158
|
+
bilingual `notes`, an optional `trace`, `optimal` (an exact classical baseline) and `extra` (landscapes,
|
|
159
|
+
curves, kernel matrices). Paradigm is one of `quantum-sim`, `quantum-hardware`, `classical`.
|
|
160
|
+
- **`Trace`** (schema `qversus-trace/1`): JSON-first and free of framework types, so a reader needs neither
|
|
161
|
+
Python nor a quantum SDK.
|
|
162
|
+
- **Bit order**: amplitude and probability arrays use Qiskit's little-endian index (qubit 0 is the least
|
|
163
|
+
significant bit); count keys are that index in binary, highest qubit leftmost.
|
|
164
|
+
|
|
165
|
+
Details: [docs/architecture](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/architecture.md).
|
|
166
|
+
|
|
167
|
+
## Real hardware (opt-in)
|
|
168
|
+
|
|
169
|
+
The `ibm-hardware` adapter submits small circuits to IBM Quantum (Open Plan, free tier) and returns the counts
|
|
170
|
+
with the backend named in the provenance. It is never part of the default solver set: it is offered only when
|
|
171
|
+
named explicitly **and** `QISKIT_IBM_TOKEN` is set. See
|
|
172
|
+
[docs/solvers/06_ibm-hardware.md](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/solvers/06_ibm-hardware.md).
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
python -m venv .venv && .venv/Scripts/python -m pip install -e ".[dev]" # bash: .venv/bin/python
|
|
178
|
+
python -m pytest # core, registry and physics tests
|
|
179
|
+
python -m ruff check qversus tests
|
|
180
|
+
python scripts/check_content_standards.py
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
CI runs lint, the tests, a core-only install check, and the repository guards on `develop` and `main`.
|
|
184
|
+
Releases are tagged `vX.XX.XXX` and published to PyPI by trusted publishing
|
|
185
|
+
([docs/guides/04_releasing.md](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/guides/04_releasing.md)).
|
|
186
|
+
|
|
187
|
+
## Used by
|
|
188
|
+
|
|
189
|
+
- **QLab** ([qlab.fasl-work.com](https://qlab.fasl-work.com)): a public, didactic quantum-computing lab whose
|
|
190
|
+
committed traces are produced by this engine.
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT. See [LICENSE](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/LICENSE).
|
qversus-0.1.0/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# qversus
|
|
2
|
+
|
|
3
|
+
[](https://github.com/fsantibanezleal/CAOS_QVersus/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/qversus/)
|
|
5
|
+
[](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
**Canonical quantum-computing problems, solved by the real frameworks next to the classical baseline that is
|
|
8
|
+
usually still more practical, with every run recorded as a replayable, seeded trace.**
|
|
9
|
+
|
|
10
|
+
`qversus` separates *what* to compute from *how*. A `Problem` is a formulation (Grover search, MaxCut, the H2
|
|
11
|
+
ground state, a surface-code memory, ...) with a set of parameter regimes (`Instance`s). A `Solver` is a thin
|
|
12
|
+
adapter that attacks a problem with **one** real framework (Qiskit + Aer, PennyLane, Cirq, Stim + PyMatching)
|
|
13
|
+
or with a classical method, and returns the same `SolverResult` shape whatever the framework. A registry pairs
|
|
14
|
+
them, so running a quantum method and the classical baseline side by side is one loop, not one script per
|
|
15
|
+
framework.
|
|
16
|
+
|
|
17
|
+
Circuit-model solvers also return a `Trace`: the state after every gate (amplitudes, per-qubit Bloch vectors,
|
|
18
|
+
probabilities), the circuit as a flat op list, and a seeded shot histogram. A run is a pure function of
|
|
19
|
+
`(params, seed)`: with the same framework versions, the same inputs give the same bytes.
|
|
20
|
+
|
|
21
|
+
The package makes no claim of quantum advantage. Every problem ships a classical baseline, and on these
|
|
22
|
+
textbook-scale instances the classical method wins on wall time; what the quantum methods show are real
|
|
23
|
+
phenomena (interference, entanglement, query separations, error-correction thresholds) at a scale a laptop
|
|
24
|
+
simulates exactly.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install qversus # core: NumPy only (formulations, registry, trace schema, RNG)
|
|
30
|
+
pip install "qversus[all]" # every simulator-based adapter
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Extra | Brings | Enables |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `qiskit` | `qiskit>=2.4,<3`, `qiskit-aer>=0.17` | the circuit adapters (16 problems), the step tracer, the Aer noise model |
|
|
36
|
+
| `pennylane` | `pennylane>=0.45`, `networkx` | QAOA cross-check, VQE on H2, the quantum-kernel classifier, the H2 Hamiltonian for the exact baseline |
|
|
37
|
+
| `cirq` | `cirq-core>=1.6` | the third QAOA implementation |
|
|
38
|
+
| `stim` | `stim>=1.16`, `pymatching>=2.0` | repetition and surface-code memories decoded by minimum-weight matching |
|
|
39
|
+
| `learn` | `scikit-learn>=1.5` | the classical RBF-SVM baseline and the precomputed-kernel SVM |
|
|
40
|
+
| `hardware` | `qiskit-ibm-runtime>=0.30` | the opt-in IBM Quantum adapter (real QPU, needs a token) |
|
|
41
|
+
| `all` | every extra except `hardware` | |
|
|
42
|
+
|
|
43
|
+
A missing framework disables only its own adapters (a warning names it); the rest of the registry works.
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from qversus.registry import get_problem, solvers_for
|
|
49
|
+
|
|
50
|
+
problem = get_problem("maxcut")
|
|
51
|
+
instance = problem.instance("square") # the 4-cycle; optimum cut = 4
|
|
52
|
+
|
|
53
|
+
for solver in solvers_for(problem): # QAOA on Qiskit, PennyLane and Cirq + two classical methods
|
|
54
|
+
result = solver.run(problem, instance, seed=42, shots=2048)
|
|
55
|
+
print(f"{result.paradigm:12} {result.solver:18} cut={result.value['cut']} cost={result.cost}")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from qversus.registry import get_problem, solvers_for
|
|
60
|
+
|
|
61
|
+
problem = get_problem("grover")
|
|
62
|
+
instance = problem.instance("grover-4-10") # N = 16 items, item 10 marked
|
|
63
|
+
qiskit = next(s for s in solvers_for(problem) if s.framework == "qiskit")
|
|
64
|
+
result = qiskit.run(problem, instance, seed=42, shots=2048)
|
|
65
|
+
|
|
66
|
+
trace = result.trace.to_dict() # JSON-ready: steps, circuit_ops, measurements, provenance
|
|
67
|
+
print(result.value, trace["measurements"]["counts"])
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
More: [quick start guide](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/guides/01_quickstart.md).
|
|
71
|
+
|
|
72
|
+
## The catalogue
|
|
73
|
+
|
|
74
|
+
Twenty problems in six families, 119 instances. Each has at least one quantum method and one classical baseline.
|
|
75
|
+
|
|
76
|
+
| Problem | Family | Quantum solvers | Classical baseline |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| `single-qubit`, `qrng`, `interference` | fundamentals | Qiskit | Bloch/bit model, PRNG, wave optics |
|
|
79
|
+
| `state-prep`, `chsh`, `teleportation`, `superdense` | entanglement | Qiskit | the amplitudes written down directly, local hidden variables, measure-and-resend, one bit per carrier |
|
|
80
|
+
| `deutsch-jozsa`, `bernstein-vazirani`, `simon` | oracle algorithms | Qiskit | query-counting classical algorithms |
|
|
81
|
+
| `grover`, `qft`, `qpe`, `shor` | flagship algorithms | Qiskit | linear scan, FFT, exact phase, trial division |
|
|
82
|
+
| `maxcut`, `vqe`, `qml` | variational | Qiskit, PennyLane, Cirq | brute force + greedy, exact diagonalisation, RBF-SVM |
|
|
83
|
+
| `noise`, `qec-repetition`, `qec-surface` | noise and QEC | Qiskit-Aer, Stim + PyMatching | the exact answer, the unprotected qubit |
|
|
84
|
+
|
|
85
|
+
Per-problem parameters, result fields and the closed-form checks the tests enforce:
|
|
86
|
+
[docs/problems](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/problems.md).
|
|
87
|
+
|
|
88
|
+
## Contracts
|
|
89
|
+
|
|
90
|
+
- **`Problem` / `Instance`**: identity (id, bilingual EN/ES title and concept, category, metric, references),
|
|
91
|
+
the instances, and a `live_capable` hint (small and noise-free enough to re-simulate interactively).
|
|
92
|
+
- **`Solver` / `SolverResult`**: `run(problem, instance, seed, shots) -> SolverResult` with `value`, `cost`,
|
|
93
|
+
bilingual `notes`, an optional `trace`, `optimal` (an exact classical baseline) and `extra` (landscapes,
|
|
94
|
+
curves, kernel matrices). Paradigm is one of `quantum-sim`, `quantum-hardware`, `classical`.
|
|
95
|
+
- **`Trace`** (schema `qversus-trace/1`): JSON-first and free of framework types, so a reader needs neither
|
|
96
|
+
Python nor a quantum SDK.
|
|
97
|
+
- **Bit order**: amplitude and probability arrays use Qiskit's little-endian index (qubit 0 is the least
|
|
98
|
+
significant bit); count keys are that index in binary, highest qubit leftmost.
|
|
99
|
+
|
|
100
|
+
Details: [docs/architecture](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/architecture.md).
|
|
101
|
+
|
|
102
|
+
## Real hardware (opt-in)
|
|
103
|
+
|
|
104
|
+
The `ibm-hardware` adapter submits small circuits to IBM Quantum (Open Plan, free tier) and returns the counts
|
|
105
|
+
with the backend named in the provenance. It is never part of the default solver set: it is offered only when
|
|
106
|
+
named explicitly **and** `QISKIT_IBM_TOKEN` is set. See
|
|
107
|
+
[docs/solvers/06_ibm-hardware.md](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/solvers/06_ibm-hardware.md).
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
python -m venv .venv && .venv/Scripts/python -m pip install -e ".[dev]" # bash: .venv/bin/python
|
|
113
|
+
python -m pytest # core, registry and physics tests
|
|
114
|
+
python -m ruff check qversus tests
|
|
115
|
+
python scripts/check_content_standards.py
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
CI runs lint, the tests, a core-only install check, and the repository guards on `develop` and `main`.
|
|
119
|
+
Releases are tagged `vX.XX.XXX` and published to PyPI by trusted publishing
|
|
120
|
+
([docs/guides/04_releasing.md](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/guides/04_releasing.md)).
|
|
121
|
+
|
|
122
|
+
## Used by
|
|
123
|
+
|
|
124
|
+
- **QLab** ([qlab.fasl-work.com](https://qlab.fasl-work.com)): a public, didactic quantum-computing lab whose
|
|
125
|
+
committed traces are produced by this engine.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT. See [LICENSE](https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/LICENSE).
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "qversus"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Canonical quantum-computing problems solved by real frameworks (Qiskit, PennyLane, Cirq, Stim) next to classical baselines, with replayable, seeded traces."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Felipe Santibanez-Leal" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"quantum computing", "quantum algorithms", "benchmark", "classical baseline", "qiskit", "pennylane",
|
|
15
|
+
"cirq", "stim", "grover", "qaoa", "vqe", "quantum error correction", "surface code", "education",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"Intended Audience :: Education",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
27
|
+
"Topic :: Education",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
]
|
|
30
|
+
# The core (RNG, trace schema, problem formulations, the registry and the classical adapters' module) needs
|
|
31
|
+
# NumPy only. Each framework is an extra, imported where it is used, so a missing framework disables only
|
|
32
|
+
# its own adapters and `import qversus` stays light.
|
|
33
|
+
dependencies = ["numpy>=1.26"]
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
qiskit = ["qiskit>=2.4,<3", "qiskit-aer>=0.17"]
|
|
37
|
+
pennylane = ["pennylane>=0.45", "networkx>=3.2"]
|
|
38
|
+
cirq = ["cirq-core>=1.6"]
|
|
39
|
+
stim = ["stim>=1.16", "pymatching>=2.0"]
|
|
40
|
+
learn = ["scikit-learn>=1.5"]
|
|
41
|
+
hardware = ["qiskit>=2.4,<3", "qiskit-ibm-runtime>=0.30"]
|
|
42
|
+
all = [
|
|
43
|
+
"qiskit>=2.4,<3", "qiskit-aer>=0.17", "pennylane>=0.45", "networkx>=3.2", "cirq-core>=1.6",
|
|
44
|
+
"stim>=1.16", "pymatching>=2.0", "scikit-learn>=1.5",
|
|
45
|
+
]
|
|
46
|
+
dev = [
|
|
47
|
+
"pytest>=8.0", "ruff==0.15.18", "build>=1.0",
|
|
48
|
+
"qiskit>=2.4,<3", "qiskit-aer>=0.17", "pennylane>=0.45", "networkx>=3.2", "cirq-core>=1.6",
|
|
49
|
+
"stim>=1.16", "pymatching>=2.0", "scikit-learn>=1.5",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[project.urls]
|
|
53
|
+
Homepage = "https://github.com/fsantibanezleal/CAOS_QVersus"
|
|
54
|
+
Documentation = "https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/docs/README.md"
|
|
55
|
+
Issues = "https://github.com/fsantibanezleal/CAOS_QVersus/issues"
|
|
56
|
+
Changelog = "https://github.com/fsantibanezleal/CAOS_QVersus/blob/main/CHANGELOG.md"
|
|
57
|
+
|
|
58
|
+
[tool.setuptools.packages.find]
|
|
59
|
+
include = ["qversus*"]
|
|
60
|
+
|
|
61
|
+
[tool.pytest.ini_options]
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
addopts = "-q"
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
line-length = 110
|
|
67
|
+
target-version = "py311"
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
# The classic rule set, stated explicitly so a new ruff release cannot redden trunk CI.
|
|
71
|
+
select = ["E4", "E7", "E9", "F"]
|
|
72
|
+
ignore = ["E741"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""qversus: canonical quantum-computing problems, solved by real frameworks next to classical baselines.
|
|
2
|
+
|
|
3
|
+
A `Problem` is a formulation (Grover search, MaxCut, H2 ground state, a surface-code memory, ...) with a
|
|
4
|
+
set of `Instance`s. A `Solver` is a thin adapter that attacks a problem with ONE real framework (Qiskit +
|
|
5
|
+
Aer, PennyLane, Cirq, Stim + PyMatching) or with a classical method, and returns the same `SolverResult`
|
|
6
|
+
shape whatever the framework. The registry pairs them, so putting a quantum method next to the classical
|
|
7
|
+
baseline that is usually still more practical is one loop, not a per-framework script.
|
|
8
|
+
|
|
9
|
+
Circuit-model solvers also return a `Trace`: a replayable, JSON-first recording of the run (the state after
|
|
10
|
+
every gate, per-qubit Bloch vectors, probabilities, a seeded shot histogram). A run is a pure function of
|
|
11
|
+
`(params, seed)`.
|
|
12
|
+
|
|
13
|
+
The core needs NumPy only. Each framework is an optional extra; a missing framework disables only its own
|
|
14
|
+
adapters. See the README and docs/ for the contracts.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__version__ = "0.01.000" # display form X.XX.XXX; pyproject.toml carries the PEP 440 form 0.1.0
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""qversus.core, the framework-free substrate (no quantum SDK imported here).
|
|
2
|
+
|
|
3
|
+
- rng: seeded RNG and shot sampling, so a run is a pure function of (params, seed).
|
|
4
|
+
- trace: the quantum trace schema (the artifact contract every circuit-model solver emits).
|
|
5
|
+
|
|
6
|
+
`qversus.core.circuit_trace` (the Qiskit step tracer) is deliberately NOT imported here, so importing the
|
|
7
|
+
core never requires Qiskit.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from qversus.core.rng import DEFAULT_SEED, make_rng, sample_counts
|
|
11
|
+
from qversus.core.trace import ROUND, SCHEMA_VERSION, Step, Trace, amp
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"DEFAULT_SEED",
|
|
15
|
+
"make_rng",
|
|
16
|
+
"sample_counts",
|
|
17
|
+
"ROUND",
|
|
18
|
+
"SCHEMA_VERSION",
|
|
19
|
+
"Step",
|
|
20
|
+
"Trace",
|
|
21
|
+
"amp",
|
|
22
|
+
]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Shared circuit→trace tracer (Qiskit). NOT imported by qversus.core.__init__, so `import qversus.core`
|
|
2
|
+
stays Qiskit-free (live-thin); only the precompute solvers import this.
|
|
3
|
+
|
|
4
|
+
Given a Qiskit circuit built gate-by-gate, `evolve` replays it one instruction at a time on a
|
|
5
|
+
`Statevector`, recording, after every step, the full statevector, the per-qubit reduced Bloch vector,
|
|
6
|
+
and the basis-state probabilities. That sequence of `Step`s IS the animation a consumer replays. Any
|
|
7
|
+
circuit-model solver (Qiskit, Cirq via QASM, …) funnels through here so every framework yields the same
|
|
8
|
+
trace shape, the adapter boundary the registry depends on.
|
|
9
|
+
|
|
10
|
+
Qubit/index convention (documented once, used everywhere): Qiskit's native little-endian, basis index
|
|
11
|
+
`i` has qubit 0 as its least-significant bit. The web renderer uses the same arrays, never reversed.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
from qiskit import QuantumCircuit
|
|
18
|
+
from qiskit.quantum_info import Pauli, Statevector, partial_trace
|
|
19
|
+
|
|
20
|
+
from qversus.core.rng import sample_counts
|
|
21
|
+
from qversus.core.trace import ROUND, Step, amp
|
|
22
|
+
|
|
23
|
+
_PAULIS = ("X", "Y", "Z")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def bloch(sv: Statevector, n: int) -> list[list[float]]:
|
|
27
|
+
"""Per-qubit reduced Bloch vector [⟨X⟩, ⟨Y⟩, ⟨Z⟩] via the reduced density matrix."""
|
|
28
|
+
out: list[list[float]] = []
|
|
29
|
+
for q in range(n):
|
|
30
|
+
others = [j for j in range(n) if j != q]
|
|
31
|
+
rho = partial_trace(sv, others)
|
|
32
|
+
out.append([round(float(rho.expectation_value(Pauli(p)).real), ROUND) for p in _PAULIS])
|
|
33
|
+
return out
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _scalar_params(params) -> list[float]:
|
|
37
|
+
"""Keep only real scalar gate params (a UnitaryGate carries a matrix, not floats, skip those)."""
|
|
38
|
+
out: list[float] = []
|
|
39
|
+
for x in params or []:
|
|
40
|
+
try:
|
|
41
|
+
out.append(round(float(x), ROUND))
|
|
42
|
+
except (TypeError, ValueError):
|
|
43
|
+
pass
|
|
44
|
+
return out
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def step_of(index: int, gate: str, targets, label: dict, sv: Statevector, n: int, params=None) -> Step:
|
|
48
|
+
return Step(
|
|
49
|
+
index=index,
|
|
50
|
+
gate=gate,
|
|
51
|
+
targets=[int(t) for t in targets],
|
|
52
|
+
label=label,
|
|
53
|
+
statevector=[amp(z) for z in sv.data],
|
|
54
|
+
bloch=bloch(sv, n),
|
|
55
|
+
probabilities=[round(float(p), ROUND) for p in sv.probabilities()],
|
|
56
|
+
params=_scalar_params(params),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def evolve(qc: QuantumCircuit, captions: list[dict] | None = None) -> list[Step]:
|
|
61
|
+
"""Step-replay a circuit, returning the per-step trace (incl. an initial |0…0⟩ frame).
|
|
62
|
+
|
|
63
|
+
`captions[k]` (optional) is the bilingual label for the k-th unitary instruction (0-based).
|
|
64
|
+
"""
|
|
65
|
+
n = qc.num_qubits
|
|
66
|
+
sv = Statevector.from_int(0, 2**n)
|
|
67
|
+
steps = [step_of(0, "init", [], {"en": "Initial state |0…0⟩", "es": "Estado inicial |0…0⟩"}, sv, n)]
|
|
68
|
+
k = 0
|
|
69
|
+
for inst in qc.data:
|
|
70
|
+
op = inst.operation
|
|
71
|
+
if op.name in ("measure", "barrier"):
|
|
72
|
+
continue
|
|
73
|
+
qargs = [qc.find_bit(q).index for q in inst.qubits]
|
|
74
|
+
sub = QuantumCircuit(n)
|
|
75
|
+
sub.append(op, qargs)
|
|
76
|
+
sv = sv.evolve(sub)
|
|
77
|
+
label = captions[k] if (captions and k < len(captions) and captions[k]) else {
|
|
78
|
+
"en": f"{op.name.upper()} q{qargs}",
|
|
79
|
+
"es": f"{op.name.upper()} q{qargs}",
|
|
80
|
+
}
|
|
81
|
+
steps.append(step_of(k + 1, op.name.upper(), qargs, label, sv, n, list(op.params)))
|
|
82
|
+
k += 1
|
|
83
|
+
return steps
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def circuit_ops(qc: QuantumCircuit) -> list[dict]:
|
|
87
|
+
"""A flat, JSON-able op list for the diagram renderer (excludes barriers)."""
|
|
88
|
+
ops: list[dict] = []
|
|
89
|
+
for inst in qc.data:
|
|
90
|
+
op = inst.operation
|
|
91
|
+
if op.name == "barrier":
|
|
92
|
+
continue
|
|
93
|
+
ops.append(
|
|
94
|
+
{
|
|
95
|
+
"gate": op.name,
|
|
96
|
+
"targets": [qc.find_bit(q).index for q in inst.qubits],
|
|
97
|
+
"params": _scalar_params(op.params),
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
return ops
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def measure_counts(qc: QuantumCircuit, shots: int, seed: int) -> dict:
|
|
104
|
+
"""Final measurement histogram, sampled deterministically from the exact final statevector."""
|
|
105
|
+
sv = Statevector.from_int(0, 2**qc.num_qubits).evolve(qc)
|
|
106
|
+
counts = sample_counts(np.asarray(sv.probabilities()), shots=shots, seed=seed)
|
|
107
|
+
return {"counts": counts, "shots": shots}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Seeded RNG. A qversus run is a pure function of (params, seed): same seed → byte-identical trace.
|
|
2
|
+
|
|
3
|
+
Measurement sampling (shot histograms) is the only stochastic step in the pipeline; everything else
|
|
4
|
+
(statevector evolution) is deterministic. We route all sampling through one seeded NumPy Generator so the
|
|
5
|
+
committed counts reproduce exactly.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
DEFAULT_SEED = 42
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def make_rng(seed: int = DEFAULT_SEED) -> np.random.Generator:
|
|
16
|
+
"""Return a NumPy Generator seeded deterministically (PCG64)."""
|
|
17
|
+
return np.random.default_rng(seed)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def sample_counts(probabilities: np.ndarray, shots: int, seed: int = DEFAULT_SEED) -> dict[str, int]:
|
|
21
|
+
"""Sample `shots` measurements from a probability vector over 2**n basis states.
|
|
22
|
+
|
|
23
|
+
Returns a {bitstring: count} dict, omitting zero counts. Each key is the basis index written in binary,
|
|
24
|
+
most significant bit first: the HIGHEST qubit is leftmost and qubit 0 is the rightmost character (Qiskit's
|
|
25
|
+
little-endian convention, the same order as `format(index, f"0{n}b")`).
|
|
26
|
+
"""
|
|
27
|
+
n_states = len(probabilities)
|
|
28
|
+
n_qubits = int(np.log2(n_states))
|
|
29
|
+
rng = make_rng(seed)
|
|
30
|
+
# Guard against tiny negative/round-off so np.random.choice accepts the vector.
|
|
31
|
+
p = np.clip(np.asarray(probabilities, dtype=float), 0.0, None)
|
|
32
|
+
p = p / p.sum()
|
|
33
|
+
draws = rng.choice(n_states, size=shots, p=p)
|
|
34
|
+
counts: dict[str, int] = {}
|
|
35
|
+
for idx, c in zip(*np.unique(draws, return_counts=True)):
|
|
36
|
+
bitstring = format(int(idx), f"0{n_qubits}b")
|
|
37
|
+
counts[bitstring] = int(c)
|
|
38
|
+
return dict(sorted(counts.items()))
|