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.
Files changed (49) hide show
  1. qversus-0.1.0/LICENSE +21 -0
  2. qversus-0.1.0/PKG-INFO +194 -0
  3. qversus-0.1.0/README.md +129 -0
  4. qversus-0.1.0/pyproject.toml +72 -0
  5. qversus-0.1.0/qversus/__init__.py +17 -0
  6. qversus-0.1.0/qversus/core/__init__.py +22 -0
  7. qversus-0.1.0/qversus/core/circuit_trace.py +107 -0
  8. qversus-0.1.0/qversus/core/rng.py +38 -0
  9. qversus-0.1.0/qversus/core/trace.py +69 -0
  10. qversus-0.1.0/qversus/problems/__init__.py +30 -0
  11. qversus-0.1.0/qversus/problems/base.py +51 -0
  12. qversus-0.1.0/qversus/problems/bernstein_vazirani.py +70 -0
  13. qversus-0.1.0/qversus/problems/chsh.py +74 -0
  14. qversus-0.1.0/qversus/problems/deutsch_jozsa.py +75 -0
  15. qversus-0.1.0/qversus/problems/grover.py +65 -0
  16. qversus-0.1.0/qversus/problems/interference.py +79 -0
  17. qversus-0.1.0/qversus/problems/maxcut.py +80 -0
  18. qversus-0.1.0/qversus/problems/noise.py +72 -0
  19. qversus-0.1.0/qversus/problems/qec_repetition.py +67 -0
  20. qversus-0.1.0/qversus/problems/qec_surface.py +66 -0
  21. qversus-0.1.0/qversus/problems/qft.py +67 -0
  22. qversus-0.1.0/qversus/problems/qml_classifier.py +113 -0
  23. qversus-0.1.0/qversus/problems/qpe.py +70 -0
  24. qversus-0.1.0/qversus/problems/qrng.py +71 -0
  25. qversus-0.1.0/qversus/problems/shor.py +71 -0
  26. qversus-0.1.0/qversus/problems/simon.py +76 -0
  27. qversus-0.1.0/qversus/problems/single_qubit.py +73 -0
  28. qversus-0.1.0/qversus/problems/state_prep.py +72 -0
  29. qversus-0.1.0/qversus/problems/superdense.py +63 -0
  30. qversus-0.1.0/qversus/problems/teleportation.py +77 -0
  31. qversus-0.1.0/qversus/problems/vqe.py +69 -0
  32. qversus-0.1.0/qversus/registry.py +69 -0
  33. qversus-0.1.0/qversus/solvers/__init__.py +29 -0
  34. qversus-0.1.0/qversus/solvers/base.py +59 -0
  35. qversus-0.1.0/qversus/solvers/cirq_solvers.py +81 -0
  36. qversus-0.1.0/qversus/solvers/classical_solvers.py +701 -0
  37. qversus-0.1.0/qversus/solvers/hardware_solvers.py +119 -0
  38. qversus-0.1.0/qversus/solvers/pennylane_solvers.py +193 -0
  39. qversus-0.1.0/qversus/solvers/qiskit_solvers.py +1030 -0
  40. qversus-0.1.0/qversus/solvers/stim_solvers.py +66 -0
  41. qversus-0.1.0/qversus.egg-info/PKG-INFO +194 -0
  42. qversus-0.1.0/qversus.egg-info/SOURCES.txt +47 -0
  43. qversus-0.1.0/qversus.egg-info/dependency_links.txt +1 -0
  44. qversus-0.1.0/qversus.egg-info/requires.txt +46 -0
  45. qversus-0.1.0/qversus.egg-info/top_level.txt +1 -0
  46. qversus-0.1.0/setup.cfg +4 -0
  47. qversus-0.1.0/tests/test_core.py +64 -0
  48. qversus-0.1.0/tests/test_physics.py +309 -0
  49. 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
+ [![CI](https://github.com/fsantibanezleal/CAOS_QVersus/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fsantibanezleal/CAOS_QVersus/actions/workflows/ci.yml)
69
+ [![PyPI](https://img.shields.io/pypi/v/qversus)](https://pypi.org/project/qversus/)
70
+ [![License](https://img.shields.io/github/license/fsantibanezleal/CAOS_QVersus)](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).
@@ -0,0 +1,129 @@
1
+ # qversus
2
+
3
+ [![CI](https://github.com/fsantibanezleal/CAOS_QVersus/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fsantibanezleal/CAOS_QVersus/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/qversus)](https://pypi.org/project/qversus/)
5
+ [![License](https://img.shields.io/github/license/fsantibanezleal/CAOS_QVersus)](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()))