qversus 0.1.0__py3-none-any.whl

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 (41) hide show
  1. qversus/__init__.py +17 -0
  2. qversus/core/__init__.py +22 -0
  3. qversus/core/circuit_trace.py +107 -0
  4. qversus/core/rng.py +38 -0
  5. qversus/core/trace.py +69 -0
  6. qversus/problems/__init__.py +30 -0
  7. qversus/problems/base.py +51 -0
  8. qversus/problems/bernstein_vazirani.py +70 -0
  9. qversus/problems/chsh.py +74 -0
  10. qversus/problems/deutsch_jozsa.py +75 -0
  11. qversus/problems/grover.py +65 -0
  12. qversus/problems/interference.py +79 -0
  13. qversus/problems/maxcut.py +80 -0
  14. qversus/problems/noise.py +72 -0
  15. qversus/problems/qec_repetition.py +67 -0
  16. qversus/problems/qec_surface.py +66 -0
  17. qversus/problems/qft.py +67 -0
  18. qversus/problems/qml_classifier.py +113 -0
  19. qversus/problems/qpe.py +70 -0
  20. qversus/problems/qrng.py +71 -0
  21. qversus/problems/shor.py +71 -0
  22. qversus/problems/simon.py +76 -0
  23. qversus/problems/single_qubit.py +73 -0
  24. qversus/problems/state_prep.py +72 -0
  25. qversus/problems/superdense.py +63 -0
  26. qversus/problems/teleportation.py +77 -0
  27. qversus/problems/vqe.py +69 -0
  28. qversus/registry.py +69 -0
  29. qversus/solvers/__init__.py +29 -0
  30. qversus/solvers/base.py +59 -0
  31. qversus/solvers/cirq_solvers.py +81 -0
  32. qversus/solvers/classical_solvers.py +701 -0
  33. qversus/solvers/hardware_solvers.py +119 -0
  34. qversus/solvers/pennylane_solvers.py +193 -0
  35. qversus/solvers/qiskit_solvers.py +1030 -0
  36. qversus/solvers/stim_solvers.py +66 -0
  37. qversus-0.1.0.dist-info/METADATA +194 -0
  38. qversus-0.1.0.dist-info/RECORD +41 -0
  39. qversus-0.1.0.dist-info/WHEEL +5 -0
  40. qversus-0.1.0.dist-info/licenses/LICENSE +21 -0
  41. qversus-0.1.0.dist-info/top_level.txt +1 -0
qversus/__init__.py ADDED
@@ -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}
qversus/core/rng.py ADDED
@@ -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()))
qversus/core/trace.py ADDED
@@ -0,0 +1,69 @@
1
+ """The quantum trace schema: the artifact contract between the engine and whatever consumes its runs.
2
+
3
+ A trace is a *replayable recording* of one circuit run: for every step (one gate / a barrier / a
4
+ measurement) we store the full statevector, the per-qubit Bloch vector, and the basis-state
5
+ probabilities, plus the final measurement histogram. A consumer (a notebook, a report, a static web app)
6
+ only reads or animates it: replay is the truth, nothing is recomputed. The schema is JSON-first, compact,
7
+ and free of any framework type, so a reader never needs Python or a quantum SDK to use it.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from dataclasses import asdict, dataclass, field
14
+ from pathlib import Path
15
+
16
+ SCHEMA_VERSION = "qversus-trace/1"
17
+
18
+ # Amplitudes/Bloch coords are rounded to keep traces small and gzip-friendly; 6 decimals is well below
19
+ # any didactic precision need and keeps |state| ~ 1 to float tolerance.
20
+ ROUND = 6
21
+
22
+
23
+ def amp(z: complex) -> dict[str, float]:
24
+ """Serialize a complex amplitude as {"re", "im"} rounded for compactness."""
25
+ return {"re": round(float(z.real), ROUND), "im": round(float(z.imag), ROUND)}
26
+
27
+
28
+ @dataclass
29
+ class Step:
30
+ """One animation frame: the state immediately AFTER applying `gate`."""
31
+
32
+ index: int
33
+ gate: str # e.g. "H", "CX", "RY", "barrier", "measure"
34
+ targets: list[int] # qubit indices the gate acts on
35
+ label: dict[str, str] # bilingual short caption {"en":..., "es":...}
36
+ statevector: list[dict[str, float]] # 2**n amplitudes as {"re","im"}
37
+ bloch: list[list[float]] # per-qubit reduced Bloch vector [x, y, z]
38
+ probabilities: list[float] # 2**n basis-state probabilities
39
+ params: list[float] = field(default_factory=list)
40
+
41
+
42
+ @dataclass
43
+ class Trace:
44
+ """A complete, replayable case run."""
45
+
46
+ case_id: str
47
+ title: dict[str, str] # bilingual {"en","es"}
48
+ concept: dict[str, str] # bilingual one-paragraph "what this teaches"
49
+ qubits: int
50
+ steps: list[Step]
51
+ measurements: dict # {"counts": {bitstring: int}, "shots": int}
52
+ circuit_ops: list[dict] # flat op list for the diagram renderer
53
+ provenance: dict # {engine, engine_version, seed, lane, ran_on}
54
+ references: list[dict] = field(default_factory=list) # [{"label","doi"|"url"}]
55
+ extra: dict = field(default_factory=dict) # optional noisy/mitigated/curves blocks
56
+ schema_version: str = SCHEMA_VERSION
57
+
58
+ def to_dict(self) -> dict:
59
+ return asdict(self)
60
+
61
+ def write_json(self, path: str | Path) -> Path:
62
+ path = Path(path)
63
+ path.parent.mkdir(parents=True, exist_ok=True)
64
+ # Bytes, not text: write_text turns "\n" into CRLF on Windows, so a run's bytes would depend on the OS.
65
+ path.write_bytes(json.dumps(self.to_dict(), indent=1, ensure_ascii=False).encode("utf-8"))
66
+ return path
67
+
68
+ def nbytes(self) -> int:
69
+ return len(json.dumps(self.to_dict(), ensure_ascii=False).encode("utf-8"))
@@ -0,0 +1,30 @@
1
+ """Problem catalog. Importing this package registers every problem (one import line per module).
2
+ Adding a problem = add its module here; nothing else changes.
3
+ """
4
+
5
+ from qversus.problems import ( # noqa: F401 (import = registration)
6
+ bernstein_vazirani,
7
+ chsh,
8
+ deutsch_jozsa,
9
+ grover,
10
+ interference,
11
+ maxcut,
12
+ noise,
13
+ qec_repetition,
14
+ qec_surface,
15
+ qft,
16
+ qml_classifier,
17
+ qpe,
18
+ qrng,
19
+ shor,
20
+ simon,
21
+ single_qubit,
22
+ state_prep,
23
+ superdense,
24
+ teleportation,
25
+ vqe,
26
+ )
27
+
28
+ __all__ = ["state_prep", "maxcut", "bernstein_vazirani", "deutsch_jozsa", "simon", "grover", "qft", "qpe",
29
+ "shor", "vqe", "qml_classifier", "noise", "qec_repetition", "qec_surface", "chsh", "teleportation",
30
+ "superdense", "single_qubit", "qrng", "interference"]
@@ -0,0 +1,51 @@
1
+ """Problem formulations, what to compute, independent of the method that computes it.
2
+
3
+ A `Problem` declares its didactic identity (bilingual title/concept, category) and its set of
4
+ `Instance`s (the variant regimes a consumer can select between; aim for ≥6 where a meaningful
5
+ parametric family exists). It is solver-agnostic: the same problem is attacked by many `Solver`s
6
+ (quantum + classical), each a thin adapter over a real framework. Adding a problem never touches a solver.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from abc import ABC, abstractmethod
12
+ from dataclasses import dataclass, field
13
+
14
+
15
+ @dataclass
16
+ class Instance:
17
+ """One variant regime of a problem (a full parameter vector a consumer can select)."""
18
+
19
+ id: str
20
+ title: dict[str, str] # bilingual short label for the variant chip
21
+ params: dict # problem-specific (graph edges, geometry, marked item, N, …)
22
+ note: dict[str, str] = field(default_factory=dict) # bilingual "what this regime shows"
23
+
24
+
25
+ class Problem(ABC):
26
+ """A formulation. Concrete problems set the class attributes and implement `instances`."""
27
+
28
+ id: str = "problem"
29
+ title: dict[str, str] = {"en": "Problem", "es": "Problema"}
30
+ concept: dict[str, str] = {"en": "", "es": ""}
31
+ category: str = "fundamentals"
32
+ # The bilingual name of the quantity the solvers compute (energy, max cut, found item, …).
33
+ metric: dict[str, str] = {"en": "result", "es": "resultado"}
34
+ references: list[dict] = [] # [{label, doi|url}] shared by the case
35
+ # Is the circuit small and clean enough to re-simulate interactively (e.g. in a browser)? False when the case needs an offline-only
36
+ # feature (optimization loop, realistic noise, mid-circuit feed-forward, >12 qubits). The gate still
37
+ # re-checks the measured numbers; this is the case author's honest hint.
38
+ live_capable: bool = True
39
+
40
+ @abstractmethod
41
+ def instances(self) -> list[Instance]:
42
+ """The variant regimes (≥6 where a parametric family exists; a single honest benchmark otherwise)."""
43
+
44
+ def instance(self, instance_id: str | None) -> Instance:
45
+ items = self.instances()
46
+ if instance_id is None:
47
+ return items[0]
48
+ for it in items:
49
+ if it.id == instance_id:
50
+ return it
51
+ raise KeyError(f"{self.id}: no instance {instance_id!r} (have {[i.id for i in items]})")
@@ -0,0 +1,70 @@
1
+ """Bernstein–Vazirani, recover a hidden bit-string from an oracle.
2
+
3
+ Given an oracle f(x) = s·x (mod 2) for a hidden string s, the Bernstein–Vazirani algorithm recovers s with
4
+ a SINGLE quantum oracle query via phase kickback + interference, where any classical algorithm needs n
5
+ queries (one per bit). It is the cleanest demonstration of a genuine, if oracle-model, quantum query
6
+ advantage. The honest nuance (the comparison panel makes it explicit): the advantage is in *query count*,
7
+ not wall-clock; at these sizes a classical computer answers instantly.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from qversus.problems.base import Instance, Problem
13
+ from qversus.registry import register_problem
14
+
15
+
16
+ @register_problem
17
+ class BernsteinVazirani(Problem):
18
+ id = "bernstein-vazirani"
19
+ category = "oracle-algorithms"
20
+ live_capable = True
21
+ title = {"en": "Bernstein–Vazirani, 1 query vs n", "es": "Bernstein–Vazirani, 1 consulta vs n"}
22
+ concept = {
23
+ "en": (
24
+ "A hidden string s is encoded in an oracle f(x) = s·x mod 2. Bernstein–Vazirani puts the input "
25
+ "register in uniform superposition, the answer qubit in |−⟩, queries the oracle once, phase "
26
+ "kickback stamps (−1)^{s·x} onto each branch, and a final layer of Hadamards interferes the "
27
+ "branches so that measuring the input register yields s exactly. Classically you must query the "
28
+ "oracle n times (once per bit). One quantum query vs n classical queries."
29
+ ),
30
+ "es": (
31
+ "Una cadena oculta s se codifica en un oráculo f(x) = s·x mod 2. Bernstein–Vazirani pone el "
32
+ "registro de entrada en superposición uniforme, el qubit de respuesta en |−⟩, consulta el "
33
+ "oráculo una vez, el phase kickback marca (−1)^{s·x} en cada rama, y una capa final de "
34
+ "Hadamards interfiere las ramas de modo que medir el registro de entrada da s exactamente. "
35
+ "Clásicamente hay que consultar el oráculo n veces (una por bit). Una consulta cuántica vs n."
36
+ ),
37
+ }
38
+ metric = {"en": "recovered hidden string", "es": "cadena oculta recuperada"}
39
+ references = [
40
+ {"label": "Bernstein & Vazirani, Quantum complexity theory, SIAM J. Comput. 26(5) (1997)",
41
+ "doi": "10.1137/S0097539796300921"},
42
+ {"label": "Nielsen & Chuang, Quantum Computation and Quantum Information (2010)",
43
+ "url": "https://doi.org/10.1017/CBO9780511976667"},
44
+ ]
45
+
46
+ @staticmethod
47
+ def oracle(secret: str, x: int) -> int:
48
+ """f(x) = parity of (secret AND x), the classically queryable oracle. `secret[i]` is bit of qubit i."""
49
+ n = len(secret)
50
+ bits = [(x >> i) & 1 for i in range(n)] # bit i of x ↔ qubit i
51
+ return sum(int(secret[i]) & bits[i] for i in range(n)) % 2
52
+
53
+ def instances(self) -> list[Instance]:
54
+ secrets = [
55
+ ("101", "bv-101"),
56
+ ("0111", "bv-0111"),
57
+ ("1101", "bv-1101"),
58
+ ("11010", "bv-11010"),
59
+ ("101101", "bv-101101"),
60
+ ("111111", "bv-111111"),
61
+ ]
62
+ out = []
63
+ for s, iid in secrets:
64
+ out.append(Instance(
65
+ iid, {"en": f"s = {s}", "es": f"s = {s}"},
66
+ {"n": len(s), "secret": s},
67
+ {"en": f"Hidden string {s} ({len(s)} bits), recovered in 1 query vs {len(s)} classical.",
68
+ "es": f"Cadena oculta {s} ({len(s)} bits), recuperada en 1 consulta vs {len(s)} clásicas."},
69
+ ))
70
+ return out
@@ -0,0 +1,74 @@
1
+ """CHSH / Bell inequality, where quantum GENUINELY beats classical (and where it doesn't).
2
+
3
+ Two parties share an entangled pair and each measures in one of two settings. The CHSH correlator
4
+ S = E(a₀,b₀) + E(a₀,b₁) + E(a₁,b₀) − E(a₁,b₁) is bounded by |S| ≤ 2 for ANY local-hidden-variable
5
+ (classical) theory, but quantum mechanics reaches the Tsirelson bound |S| = 2√2 ≈ 2.83 with the right
6
+ angles on a Bell state. qversus computes S from the real quantum state across several angle settings. This is
7
+ the rare, honest case where quantum *does* beat classical, but it is a **nonlocality** advantage (ruling
8
+ out local realism; the basis of the 2022 Nobel Prize), NOT a computational speedup. It also shows the
9
+ necessary ingredient: a separable (unentangled) state cannot violate the bound.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import math
15
+
16
+ from qversus.problems.base import Instance, Problem
17
+ from qversus.registry import register_problem
18
+
19
+ PI = math.pi
20
+
21
+
22
+ @register_problem
23
+ class CHSH(Problem):
24
+ id = "chsh"
25
+ category = "entanglement"
26
+ live_capable = True
27
+ title = {"en": "CHSH / Bell inequality", "es": "Desigualdad CHSH / Bell"}
28
+ concept = {
29
+ "en": (
30
+ "Share a Bell pair; Alice measures at angle a₀ or a₁, Bob at b₀ or b₁. The CHSH quantity "
31
+ "S = E(a₀,b₀)+E(a₀,b₁)+E(a₁,b₀)−E(a₁,b₁) cannot exceed 2 for any classical (local-hidden-"
32
+ "variable) theory, yet a Bell state with optimal angles reaches the Tsirelson bound 2√2 ≈ 2.83. "
33
+ "Violating |S|≤2 rules out local realism, a real, experimentally confirmed quantum effect (2022 "
34
+ "Nobel). But it is a foundational/nonlocality advantage, not a faster computation; and a "
35
+ "separable state cannot violate the bound at all, so entanglement is the necessary ingredient."
36
+ ),
37
+ "es": (
38
+ "Comparte un par de Bell; Alice mide en ángulo a₀ o a₁, Bob en b₀ o b₁. La cantidad CHSH "
39
+ "S = E(a₀,b₀)+E(a₀,b₁)+E(a₁,b₀)−E(a₁,b₁) no puede superar 2 en ninguna teoría clásica (de "
40
+ "variables ocultas locales), pero un estado de Bell con ángulos óptimos alcanza la cota de "
41
+ "Tsirelson 2√2 ≈ 2.83. Violar |S|≤2 descarta el realismo local, un efecto cuántico real y "
42
+ "confirmado experimentalmente (Nobel 2022). Pero es una ventaja fundacional/de no-localidad, no "
43
+ "un cálculo más rápido; y un estado separable no puede violar la cota, así que el entrelazamiento "
44
+ "es el ingrediente necesario."
45
+ ),
46
+ }
47
+ metric = {"en": "CHSH value S (classical ≤ 2, quantum ≤ 2√2)", "es": "valor CHSH S (clásico ≤ 2, cuántico ≤ 2√2)"}
48
+ references = [
49
+ {"label": "Clauser, Horne, Shimony & Holt, Proposed experiment to test local hidden-variable theories (1969)",
50
+ "doi": "10.1103/PhysRevLett.23.880"},
51
+ {"label": "Aspect, Nobel Prize in Physics 2022, Bell inequality experiments",
52
+ "url": "https://www.nobelprize.org/prizes/physics/2022/summary/"},
53
+ ]
54
+
55
+ def instances(self) -> list[Instance]:
56
+ # angles (a0, a1, b0, b1) in radians; `entangled` toggles Bell vs product state.
57
+ opt = (0.0, PI / 2, PI / 4, -PI / 4)
58
+ defs = [
59
+ ("chsh-optimal", "Bell, optimal angles → 2√2", opt, True),
60
+ ("chsh-suboptimal", "Bell, sub-optimal angles", (0.0, PI / 2, PI / 6, -PI / 6), True),
61
+ ("chsh-weak", "Bell, weak angles", (0.0, PI / 3, PI / 8, -PI / 8), True),
62
+ ("chsh-aligned", "Bell, aligned bases → S=2", (0.0, 0.0, 0.0, PI / 2), True),
63
+ ("chsh-rotated", "Bell, rotated optimal (still 2√2)", (PI / 8, PI / 8 + PI / 2, PI / 8 + PI / 4, PI / 8 - PI / 4), True),
64
+ ("chsh-product", "Separable state (no entanglement)", opt, False),
65
+ ]
66
+ out = []
67
+ for iid, label, angles, ent in defs:
68
+ out.append(Instance(
69
+ iid, {"en": label, "es": label},
70
+ {"n": 2, "a0": angles[0], "a1": angles[1], "b0": angles[2], "b1": angles[3], "entangled": ent},
71
+ {"en": f"{'Bell pair' if ent else 'product state'}, see whether S exceeds the classical 2.",
72
+ "es": f"{'par de Bell' if ent else 'estado producto'}, muestra si S supera el clásico 2."},
73
+ ))
74
+ return out
@@ -0,0 +1,75 @@
1
+ """Deutsch–Jozsa, decide constant vs balanced in one query.
2
+
3
+ Promised that f:{0,1}^n→{0,1} is either constant (same output for all inputs) or balanced (0 on exactly
4
+ half the inputs), Deutsch–Jozsa decides which with a SINGLE quantum oracle query. Deterministically,
5
+ classically you may need 2^{n-1}+1 queries (just over half the inputs) to be certain, an exponential
6
+ query-complexity gap. The honest nuance (the comparison panel says it): it is an oracle-model, query-count
7
+ advantage; at these sizes the classical decision is still instant.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from qversus.problems.base import Instance, Problem
13
+ from qversus.registry import register_problem
14
+
15
+
16
+ @register_problem
17
+ class DeutschJozsa(Problem):
18
+ id = "deutsch-jozsa"
19
+ category = "oracle-algorithms"
20
+ live_capable = True
21
+ title = {"en": "Deutsch–Jozsa, constant vs balanced", "es": "Deutsch–Jozsa, constante vs balanceada"}
22
+ concept = {
23
+ "en": (
24
+ "An oracle hides a function f that is promised constant or balanced. Deutsch–Jozsa puts the "
25
+ "input register in uniform superposition with the answer qubit in |−⟩, queries the oracle once "
26
+ "(phase kickback stamps (−1)^{f(x)}), and a final layer of Hadamards interferes the branches: "
27
+ "the input register collapses to all-zeros iff f is constant, and to something non-zero iff f "
28
+ "is balanced. One quantum query decides it; a deterministic classical algorithm may need "
29
+ "2^{n-1}+1."
30
+ ),
31
+ "es": (
32
+ "Un oráculo oculta una función f que se promete constante o balanceada. Deutsch–Jozsa pone el "
33
+ "registro de entrada en superposición uniforme con el qubit de respuesta en |−⟩, consulta el "
34
+ "oráculo una vez (el phase kickback marca (−1)^{f(x)}), y una capa final de Hadamards interfiere "
35
+ "las ramas: el registro de entrada colapsa a todo-ceros si f es constante, y a algo no nulo si f "
36
+ "es balanceada. Una consulta cuántica lo decide; un algoritmo clásico determinista puede "
37
+ "necesitar 2^{n-1}+1."
38
+ ),
39
+ }
40
+ metric = {"en": "constant-or-balanced verdict", "es": "veredicto constante-o-balanceada"}
41
+ references = [
42
+ {"label": "Deutsch & Jozsa, Rapid solution of problems by quantum computation, Proc. R. Soc. A 439 (1992)",
43
+ "doi": "10.1098/rspa.1992.0167"},
44
+ {"label": "Nielsen & Chuang, Quantum Computation and Quantum Information (2010)",
45
+ "url": "https://doi.org/10.1017/CBO9780511976667"},
46
+ ]
47
+
48
+ @staticmethod
49
+ def evaluate(params: dict, x: int) -> int:
50
+ """The oracle f(x). constant → fixed value; balanced → parity of (secret AND x), secret ≠ 0."""
51
+ if params["kind"] == "constant":
52
+ return params["value"]
53
+ n, s = params["n"], params["secret"]
54
+ bits = [(x >> i) & 1 for i in range(n)]
55
+ return sum(int(s[i]) & bits[i] for i in range(n)) % 2
56
+
57
+ def instances(self) -> list[Instance]:
58
+ defs = [
59
+ ("dj-const0-3", "constant f=0 (n=3)", {"n": 3, "kind": "constant", "value": 0}),
60
+ ("dj-const1-3", "constant f=1 (n=3)", {"n": 3, "kind": "constant", "value": 1}),
61
+ ("dj-bal-101", "balanced s=101 (n=3)", {"n": 3, "kind": "balanced", "secret": "101"}),
62
+ ("dj-bal-parity", "balanced full parity (n=3)", {"n": 3, "kind": "balanced", "secret": "111"}),
63
+ ("dj-const0-4", "constant f=0 (n=4)", {"n": 4, "kind": "constant", "value": 0}),
64
+ ("dj-bal-1011", "balanced s=1011 (n=4)", {"n": 4, "kind": "balanced", "secret": "1011"}),
65
+ ]
66
+ out = []
67
+ for iid, label, params in defs:
68
+ kind = "constant" if params["kind"] == "constant" else "balanced"
69
+ worst = 2 ** (params["n"] - 1) + 1
70
+ out.append(Instance(
71
+ iid, {"en": label, "es": label}, params,
72
+ {"en": f"f is {kind}, 1 quantum query vs up to {worst} classical (worst case).",
73
+ "es": f"f es {kind}, 1 consulta cuántica vs hasta {worst} clásicas (peor caso)."},
74
+ ))
75
+ return out
@@ -0,0 +1,65 @@
1
+ """Grover's search, amplitude amplification over an unstructured database.
2
+
3
+ Find a marked item in an unstructured set of N = 2ⁿ items. Grover needs ~(π/4)√(N/M) oracle queries
4
+ (M = number of marked items) vs the classical ~N/2. The famous quadratic speedup, but honestly: it is
5
+ *quadratic, asymptotic*, and at the tiny n a laptop simulates exactly, the classical scan still wins on
6
+ wall-time. What it teaches beautifully is amplitude amplification: each iteration tips probability toward
7
+ the marked state, and over-rotating (too many iterations) tips it back.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from qversus.problems.base import Instance, Problem
13
+ from qversus.registry import register_problem
14
+
15
+
16
+ @register_problem
17
+ class Grover(Problem):
18
+ id = "grover"
19
+ category = "flagship-algorithms"
20
+ live_capable = True
21
+ title = {"en": "Grover, unstructured search", "es": "Grover, búsqueda no estructurada"}
22
+ concept = {
23
+ "en": (
24
+ "Grover searches N = 2ⁿ unstructured items for the M marked ones. Start in uniform "
25
+ "superposition; each Grover iteration applies the oracle (a phase flip on the marked states) "
26
+ "then the diffuser (inversion about the mean), rotating amplitude toward the marked subspace. "
27
+ "After ~(π/4)√(N/M) iterations a measurement returns a marked item with high probability, a "
28
+ "quadratic speedup over the classical ~N/2 scan. Run too many iterations and you *over-rotate* "
29
+ "past the target."
30
+ ),
31
+ "es": (
32
+ "Grover busca entre N = 2ⁿ ítems no estructurados los M marcados. Parte en superposición "
33
+ "uniforme; cada iteración de Grover aplica el oráculo (un cambio de fase en los estados "
34
+ "marcados) y luego el difusor (inversión respecto a la media), rotando la amplitud hacia el "
35
+ "subespacio marcado. Tras ~(π/4)√(N/M) iteraciones una medición devuelve un ítem marcado con "
36
+ "alta probabilidad, un speedup cuadrático sobre el barrido clásico ~N/2. Con demasiadas "
37
+ "iteraciones te *pasas* del objetivo (sobre-rotación)."
38
+ ),
39
+ }
40
+ metric = {"en": "marked item found", "es": "ítem marcado encontrado"}
41
+ references = [
42
+ {"label": "Grover, A fast quantum mechanical algorithm for database search, STOC '96 (1996)",
43
+ "doi": "10.1145/237814.237866"},
44
+ {"label": "Nielsen & Chuang, Quantum Computation and Quantum Information (2010)",
45
+ "url": "https://doi.org/10.1017/CBO9780511976667"},
46
+ ]
47
+
48
+ def instances(self) -> list[Instance]:
49
+ defs = [
50
+ ("grover-2-3", "n=2, mark |11⟩", {"n": 2, "marked": [3]}),
51
+ ("grover-3-5", "n=3, mark |101⟩", {"n": 3, "marked": [5]}),
52
+ ("grover-3-2", "n=3, mark |010⟩", {"n": 3, "marked": [2]}),
53
+ ("grover-3-2marked", "n=3, mark |011⟩,|101⟩", {"n": 3, "marked": [3, 5]}),
54
+ ("grover-4-10", "n=4, mark |1010⟩", {"n": 4, "marked": [10]}),
55
+ ("grover-4-0", "n=4, mark |0000⟩", {"n": 4, "marked": [0]}),
56
+ ]
57
+ out = []
58
+ for iid, label, params in defs:
59
+ n, m = params["n"], len(params["marked"])
60
+ out.append(Instance(
61
+ iid, {"en": label, "es": label}, params,
62
+ {"en": f"N={2 ** n}, M={m} marked, quantum ~(π/4)√(N/M) queries vs classical ~N/2.",
63
+ "es": f"N={2 ** n}, M={m} marcados, cuántico ~(π/4)√(N/M) consultas vs clásico ~N/2."},
64
+ ))
65
+ return out