dense-evolution 8.3.0__py3-none-win_amd64.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 (165) hide show
  1. dashboard_core/__init__.py +115 -0
  2. dashboard_core/_gate_tables.py +30 -0
  3. dashboard_core/band_structure.py +71 -0
  4. dashboard_core/circuit_builder_component.py +232 -0
  5. dashboard_core/circuit_diagram.py +216 -0
  6. dashboard_core/crypto_protocols.py +77 -0
  7. dashboard_core/engine.py +326 -0
  8. dashboard_core/graphical_builder.py +114 -0
  9. dashboard_core/hamiltonians.py +593 -0
  10. dashboard_core/mass_decomposition_tool.py +47 -0
  11. dashboard_core/mitigation.py +343 -0
  12. dashboard_core/native_hf_diagnostics.py +62 -0
  13. dashboard_core/noise_tools.py +125 -0
  14. dashboard_core/qasm_library.py +233 -0
  15. dashboard_core/qmmm.py +16 -0
  16. dashboard_core/rag_tool.py +45 -0
  17. dashboard_core/state_visuals.py +288 -0
  18. dashboard_core/system_limits.py +60 -0
  19. dashboard_core/vector_healing.py +102 -0
  20. dashboard_core/visuals.py +158 -0
  21. dashboard_core/vqe.py +533 -0
  22. dashboard_core/wormhole.py +580 -0
  23. dense_evolution/__init__.py +114 -0
  24. dense_evolution/autodiff.py +10 -0
  25. dense_evolution/backends/__init__.py +5 -0
  26. dense_evolution/backends/chunk/__init__.py +37 -0
  27. dense_evolution/backends/chunk/_engine_imports.py +57 -0
  28. dense_evolution/backends/chunk/circuit_chunker.py +55 -0
  29. dense_evolution/backends/chunk/core.py +432 -0
  30. dense_evolution/backends/chunk/disk_overflow.py +232 -0
  31. dense_evolution/backends/chunk/geometry.py +95 -0
  32. dense_evolution/backends/chunk/guard.py +190 -0
  33. dense_evolution/backends/chunk/kernels.py +531 -0
  34. dense_evolution/backends/mps.py +1569 -0
  35. dense_evolution/backends/statevector.py +616 -0
  36. dense_evolution/chunk.py +25 -0
  37. dense_evolution/circuits/__init__.py +20 -0
  38. dense_evolution/circuits/compiler.py +488 -0
  39. dense_evolution/circuits/diagram.py +94 -0
  40. dense_evolution/circuits/gates.py +91 -0
  41. dense_evolution/circuits/parser.py +632 -0
  42. dense_evolution/circuits/qft.py +66 -0
  43. dense_evolution/circuits/random_circuit.py +85 -0
  44. dense_evolution/circuits/registry.py +74 -0
  45. dense_evolution/circuits/topology.py +79 -0
  46. dense_evolution/circuits/trotter.py +265 -0
  47. dense_evolution/circuits/uccsd.py +275 -0
  48. dense_evolution/cli.py +199 -0
  49. dense_evolution/compiler.py +9 -0
  50. dense_evolution/config.py +49 -0
  51. dense_evolution/drawing.py +10 -0
  52. dense_evolution/entropy.py +9 -0
  53. dense_evolution/fermions.py +9 -0
  54. dense_evolution/gates.py +9 -0
  55. dense_evolution/harrison_tb.py +16 -0
  56. dense_evolution/healing.py +18 -0
  57. dense_evolution/interop/__init__.py +18 -0
  58. dense_evolution/interop/qiskit_pennylane.py +406 -0
  59. dense_evolution/measurement.py +10 -0
  60. dense_evolution/mitigation/__init__.py +54 -0
  61. dense_evolution/mitigation/healing.py +215 -0
  62. dense_evolution/mitigation/kl_divergence.py +93 -0
  63. dense_evolution/mitigation/magic_entropy.py +163 -0
  64. dense_evolution/mitigation/magic_entropy_shadows.py +262 -0
  65. dense_evolution/mitigation/renyi.py +168 -0
  66. dense_evolution/mitigation/stabilizer_renyi_entropy.py +103 -0
  67. dense_evolution/mitigation/zne.py +990 -0
  68. dense_evolution/mps.py +9 -0
  69. dense_evolution/native_hf/__init__.py +26 -0
  70. dense_evolution/native_hf/_libcint/LICENSE-libcint +10 -0
  71. dense_evolution/native_hf/_libcint/libdecint.dll +0 -0
  72. dense_evolution/native_hf/assembly.py +304 -0
  73. dense_evolution/native_hf/basis.py +117 -0
  74. dense_evolution/native_hf/boys.py +35 -0
  75. dense_evolution/native_hf/bridge.py +112 -0
  76. dense_evolution/native_hf/cartesian.py +64 -0
  77. dense_evolution/native_hf/coulomb.py +196 -0
  78. dense_evolution/native_hf/differentiable.py +53 -0
  79. dense_evolution/native_hf/gaussians.py +79 -0
  80. dense_evolution/native_hf/kinetic.py +52 -0
  81. dense_evolution/native_hf/libcint_bridge.py +167 -0
  82. dense_evolution/native_hf/overlap.py +91 -0
  83. dense_evolution/native_hf/scf.py +404 -0
  84. dense_evolution/noise/__init__.py +79 -0
  85. dense_evolution/noise/coherent_attack.py +264 -0
  86. dense_evolution/noise/cosmic_ray.py +61 -0
  87. dense_evolution/noise/density_matrix_channels.py +78 -0
  88. dense_evolution/noise/differentiable.py +66 -0
  89. dense_evolution/noise/kraus/__init__.py +6 -0
  90. dense_evolution/noise/kraus/amplitude_damping.py +47 -0
  91. dense_evolution/noise/kraus/bitflip.py +22 -0
  92. dense_evolution/noise/kraus/combined.py +16 -0
  93. dense_evolution/noise/kraus/depolarizing.py +47 -0
  94. dense_evolution/noise/kraus/ideal.py +10 -0
  95. dense_evolution/noise/kraus/phaseflip.py +21 -0
  96. dense_evolution/noise/kraus_channels.py +285 -0
  97. dense_evolution/noise/oscillating.py +32 -0
  98. dense_evolution/noise/pink.py +80 -0
  99. dense_evolution/observables.py +11 -0
  100. dense_evolution/parser.py +9 -0
  101. dense_evolution/physics/__init__.py +27 -0
  102. dense_evolution/physics/entropy.py +161 -0
  103. dense_evolution/physics/fermions.py +322 -0
  104. dense_evolution/physics/observables.py +523 -0
  105. dense_evolution/physics/qec.py +1113 -0
  106. dense_evolution/physics/spectral.py +143 -0
  107. dense_evolution/physics/states.py +43 -0
  108. dense_evolution/protocols/__init__.py +27 -0
  109. dense_evolution/protocols/bb84.py +133 -0
  110. dense_evolution/protocols/di_qkd_ghz.py +199 -0
  111. dense_evolution/protocols/dicka_protocol2.py +124 -0
  112. dense_evolution/qec.py +20 -0
  113. dense_evolution/qft.py +9 -0
  114. dense_evolution/qmmm/__init__.py +13 -0
  115. dense_evolution/qmmm/ase_bridge.py +97 -0
  116. dense_evolution/qmmm/forces.py +388 -0
  117. dense_evolution/qmmm/propagation.py +80 -0
  118. dense_evolution/qmmm/region.py +137 -0
  119. dense_evolution/random_circuit.py +15 -0
  120. dense_evolution/registry.py +9 -0
  121. dense_evolution/simulator.py +10 -0
  122. dense_evolution/solvers/__init__.py +19 -0
  123. dense_evolution/solvers/autodiff.py +169 -0
  124. dense_evolution/solvers/harrison_tb.py +189 -0
  125. dense_evolution/solvers/vhd_tb.py +187 -0
  126. dense_evolution/states.py +9 -0
  127. dense_evolution/topology.py +9 -0
  128. dense_evolution/trotter.py +9 -0
  129. dense_evolution/utils/__init__.py +13 -0
  130. dense_evolution/utils/drawing.py +101 -0
  131. dense_evolution/utils/mass_decomposition.py +246 -0
  132. dense_evolution/utils/measurement.py +94 -0
  133. dense_evolution/vhd_tb.py +16 -0
  134. dense_evolution-8.3.0.dist-info/METADATA +366 -0
  135. dense_evolution-8.3.0.dist-info/RECORD +165 -0
  136. dense_evolution-8.3.0.dist-info/WHEEL +5 -0
  137. dense_evolution-8.3.0.dist-info/entry_points.txt +2 -0
  138. dense_evolution-8.3.0.dist-info/licenses/license.md +58 -0
  139. dense_evolution-8.3.0.dist-info/top_level.txt +5 -0
  140. ia_utils/__init__.py +0 -0
  141. ia_utils/adversarial_vector_attack.py +196 -0
  142. ia_utils/rag.py +288 -0
  143. ia_utils/vector_healing.py +399 -0
  144. local_site/__init__.py +0 -0
  145. local_site/app/__init__.py +0 -0
  146. local_site/app/server.py +1009 -0
  147. mcp_server/__init__.py +0 -0
  148. mcp_server/client.py +324 -0
  149. mcp_server/config.py +32 -0
  150. mcp_server/models.py +347 -0
  151. mcp_server/molecules.py +71 -0
  152. mcp_server/server.py +119 -0
  153. mcp_server/tools/__init__.py +0 -0
  154. mcp_server/tools/chemistry_tools.py +225 -0
  155. mcp_server/tools/circuit_tools.py +83 -0
  156. mcp_server/tools/crypto_tools.py +66 -0
  157. mcp_server/tools/mitigation_tools.py +81 -0
  158. mcp_server/tools/noise_tools.py +60 -0
  159. mcp_server/tools/retrieval_tools.py +44 -0
  160. mcp_server/tools/system_tools.py +149 -0
  161. mcp_server/tools/wormhole_tools.py +142 -0
  162. mcp_server/utils/__init__.py +0 -0
  163. mcp_server/utils/cache.py +55 -0
  164. mcp_server/utils/images.py +67 -0
  165. mcp_server/utils/truncation.py +38 -0
@@ -0,0 +1,225 @@
1
+ """Tools: molecular Hamiltonians, VQE, QM/MM forces, and molecular
2
+ dynamics."""
3
+ import asyncio
4
+ import json
5
+
6
+ from ..client import _request, catch_errors
7
+ from ..config import COMPUTE
8
+ from ..models import (
9
+ CustomMoleculeInput, EnergyScanInput, MassDecompositionInput, MdTrajectoryInput, MixMoleculesInput,
10
+ MoleculeEnergyInput, NativeHfDiagnosticsInput, QmmmForcesInput, RunVqeInput,
11
+ )
12
+ from ..molecules import _resolve_molecule_name
13
+ from ..server import mcp
14
+
15
+
16
+ @mcp.tool(name="dense_evolution_molecule_energy", annotations={"title": "Get catalog molecule ground-state energy", **COMPUTE})
17
+ @catch_errors
18
+ async def dense_evolution_molecule_energy(params: MoleculeEnergyInput) -> str:
19
+ """Compute the exact ground-state energy of a catalog molecule via real
20
+ Hartree-Fock + Jordan-Wigner/Bravyi-Kitaev Hamiltonian construction and
21
+ exact dense diagonalization.
22
+
23
+ Args:
24
+ params (MoleculeEnergyInput): name (short id or full catalog name), mapping.
25
+
26
+ Returns:
27
+ str: JSON with n_qubits, symbols, geometry, charge, ground_state_energy_hartree.
28
+ "Error: ..." with a 404-style message if `name` is not in the catalog
29
+ (call dense_evolution_list_molecules to see valid names/ids).
30
+ """
31
+ resolved = await _resolve_molecule_name(params.name)
32
+ payload = {**params.model_dump(), "name": resolved}
33
+ return json.dumps(await _request("POST", "/api/hamiltonian/molecule", timeout=60.0, json=payload), indent=2)
34
+
35
+
36
+ @mcp.tool(name="dense_evolution_mix_molecules", annotations={"title": "Mix two catalog Hamiltonians", **COMPUTE})
37
+ @catch_errors
38
+ async def dense_evolution_mix_molecules(params: MixMoleculesInput) -> str:
39
+ """Compute H_mix = weight_a*H_a + weight_b*H_b for two catalog molecules
40
+ that share the same qubit count (same electron space), and diagonalize
41
+ all three (H_a, H_b, H_mix) for their ground-state energies. Mixing
42
+ molecules with different qubit counts is physically meaningless and is
43
+ rejected with a clear error.
44
+
45
+ Args:
46
+ params (MixMoleculesInput): name_a, name_b (short id or full catalog name), weight_a, weight_b, mapping.
47
+
48
+ Returns:
49
+ str: JSON with n_qubits, energy_a, energy_b, energy_mixed (all in Hartree).
50
+ """
51
+ name_a = await _resolve_molecule_name(params.name_a)
52
+ name_b = await _resolve_molecule_name(params.name_b)
53
+ payload = {**params.model_dump(), "name_a": name_a, "name_b": name_b}
54
+ return json.dumps(await _request("POST", "/api/hamiltonian/mix", timeout=60.0, json=payload), indent=2)
55
+
56
+
57
+ @mcp.tool(name="dense_evolution_custom_molecule_energy", annotations={"title": "Get custom molecule ground-state energy", **COMPUTE})
58
+ @catch_errors
59
+ async def dense_evolution_custom_molecule_energy(params: CustomMoleculeInput) -> str:
60
+ """Compute the ground-state energy of an arbitrary molecule (not in the
61
+ catalog) from its atomic symbols and geometry, via the same Hartree-Fock
62
+ pipeline as the catalog. Small molecules only -- exact dense
63
+ diagonalization caps out at 12 qubits, rejected before PennyLane runs if
64
+ the electron/orbital count would exceed that.
65
+
66
+ Args:
67
+ params (CustomMoleculeInput): symbols, geometry, charge, mapping.
68
+ len(symbols) must equal len(geometry).
69
+
70
+ Returns:
71
+ str: JSON with n_qubits, ground_state_energy_hartree, or "Error: ..."
72
+ if the molecule needs more than 12 qubits.
73
+ """
74
+ return json.dumps(await _request("POST", "/api/hamiltonian/custom", timeout=60.0, json=params.model_dump()), indent=2)
75
+
76
+
77
+ @mcp.tool(name="dense_evolution_native_hf_diagnostics", annotations={"title": "Diagnose Hartree-Fock SCF convergence", **COMPUTE})
78
+ @catch_errors
79
+ async def dense_evolution_native_hf_diagnostics(params: NativeHfDiagnosticsInput) -> str:
80
+ """Run real Hartree-Fock SCF (dense_evolution.native_hf, the fallback
81
+ path for elements outside PennyLane's bundled STO-3G table, e.g.
82
+ Silicon) and run Dense-Armor's Hampel/Tukey anomaly filters over the
83
+ real per-iteration energy trace, instead of trusting the final
84
+ `converged` flag alone. Needs the `armor` extra (pip install
85
+ dense-evolution[armor]) -- returns an actionable "Error: ..." if it
86
+ isn't installed.
87
+
88
+ Args:
89
+ params (NativeHfDiagnosticsInput): symbols, geometry, charge,
90
+ active_electrons, active_orbitals. len(symbols) must equal len(geometry).
91
+
92
+ Returns:
93
+ str: JSON with n_qubits, converged, n_iterations,
94
+ electronic_energy_hartree, total_energy_hartree,
95
+ n_anomalies_hampel, n_anomalies_tukey, anomaly_fraction_hampel,
96
+ anomaly_fraction_tukey.
97
+ """
98
+ return json.dumps(await _request("POST", "/api/native_hf/diagnose", timeout=60.0, json=params.model_dump()), indent=2)
99
+
100
+
101
+ @mcp.tool(name="dense_evolution_mass_decomposition", annotations={"title": "Check a mass-spectrometry neutral loss", **COMPUTE})
102
+ @catch_errors
103
+ async def dense_evolution_mass_decomposition(params: MassDecompositionInput) -> str:
104
+ """Check whether a mass-spectrometry peak-pair difference is a
105
+ chemically valid, reachable sub-formula mass of a precursor formula
106
+ (dense_evolution.utils.mass_decomposition, promoted from CASMI26
107
+ spectral-identification work). `nearest_reachable_mass` is the exact,
108
+ RDBE-filtered answer; `density_at_target` is a plausibility density
109
+ from the same landscape via FFT, not a probability.
110
+
111
+ Args:
112
+ params (MassDecompositionInput): formula, target_mass, max_mass.
113
+
114
+ Returns:
115
+ str: JSON with formula_counts, rdbe, nearest_reachable_mass, density_at_target.
116
+ """
117
+ return json.dumps(await _request("POST", "/api/mass_decomposition", timeout=30.0, json=params.model_dump()), indent=2)
118
+
119
+
120
+ @mcp.tool(name="dense_evolution_energy_scan", annotations={"title": "Scan ground-state energy over several geometries", **COMPUTE})
121
+ @catch_errors
122
+ async def dense_evolution_energy_scan(params: EnergyScanInput) -> str:
123
+ """Compute the ground-state energy at each of several geometries in one
124
+ call -- e.g. a bond-length dissociation curve or a bond-angle sweep --
125
+ instead of calling dense_evolution_custom_molecule_energy once per
126
+ point. Points are evaluated concurrently against the kernel. A point
127
+ that fails (e.g. too many qubits for exact diagonalization) is reported
128
+ with its own error and does not abort the rest of the scan.
129
+
130
+ Args:
131
+ params (EnergyScanInput): symbols, geometries (list of points, max 50),
132
+ charge, mapping, labels (optional, one per point).
133
+
134
+ Returns:
135
+ str: JSON with:
136
+ {
137
+ "n_points": int,
138
+ "results": [{"label": ..., "n_qubits": int, "ground_state_energy_hartree": float} | {"label": ..., "error": str}, ...],
139
+ "minimum": {"label": ..., "ground_state_energy_hartree": float} | null # over successful points only
140
+ }
141
+ """
142
+ if params.labels is not None and len(params.labels) != len(params.geometries):
143
+ raise ValueError(f"{len(params.labels)} labels but {len(params.geometries)} geometries -- must match.")
144
+ labels = params.labels if params.labels is not None else list(range(len(params.geometries)))
145
+
146
+ async def _one_point(label, geometry):
147
+ if len(params.symbols) != len(geometry):
148
+ return {"label": label, "error": f"{len(params.symbols)} symbols but {len(geometry)} geometry rows"}
149
+ try:
150
+ data = await _request(
151
+ "POST", "/api/hamiltonian/custom", timeout=60.0,
152
+ json={"symbols": params.symbols, "geometry": geometry, "charge": params.charge, "mapping": params.mapping},
153
+ )
154
+ return {"label": label, "n_qubits": data["n_qubits"], "ground_state_energy_hartree": data["ground_state_energy_hartree"]}
155
+ except Exception as e:
156
+ return {"label": label, "error": str(e)}
157
+
158
+ results = await asyncio.gather(*(_one_point(l, g) for l, g in zip(labels, params.geometries)))
159
+ successful = [r for r in results if "ground_state_energy_hartree" in r]
160
+ minimum = min(successful, key=lambda r: r["ground_state_energy_hartree"]) if successful else None
161
+ return json.dumps({"n_points": len(results), "results": results, "minimum": minimum}, indent=2)
162
+
163
+
164
+ @mcp.tool(name="dense_evolution_run_vqe", annotations={"title": "Run VQE ground-state optimization", **COMPUTE})
165
+ @catch_errors
166
+ async def dense_evolution_run_vqe(params: RunVqeInput) -> str:
167
+ """Run real VQE (Adam gradient descent with adjoint differentiation)
168
+ against a molecule's Jordan-Wigner Hamiltonian, from a fresh random
169
+ start every call -- not a cached/precomputed result. Can take a while
170
+ for 'uccsd' ansatz or high maxiter; consider dense_evolution_health's
171
+ RAM figures and start with a small maxiter/n_layers to gauge cost first.
172
+
173
+ Args:
174
+ params (RunVqeInput): either `name` (short id or full catalog name)
175
+ or `symbols`+`geometry` (custom) must be given, plus
176
+ ansatz_type, n_layers, maxiter, step_size, beta1, beta2, seed.
177
+
178
+ Returns:
179
+ str: JSON with the optimized energy, convergence history, and
180
+ optimized parameters. "Error: ..." if neither name nor
181
+ symbols+geometry is provided, or the molecule is unknown.
182
+ """
183
+ payload = params.model_dump()
184
+ if params.name:
185
+ payload["name"] = await _resolve_molecule_name(params.name)
186
+ return json.dumps(await _request("POST", "/api/vqe", timeout=600.0, json=payload), indent=2)
187
+
188
+
189
+ @mcp.tool(name="dense_evolution_qmmm_forces", annotations={"title": "Compute Hellmann-Feynman nuclear forces", **COMPUTE})
190
+ @catch_errors
191
+ async def dense_evolution_qmmm_forces(params: QmmmForcesInput) -> str:
192
+ """Compute real Hellmann-Feynman nuclear forces (F = -d<psi|H(R)|psi>/dR
193
+ via PennyLane autodiff, not finite differences) on a catalog molecule's
194
+ real Hartree-Fock ground state.
195
+
196
+ Args:
197
+ params (QmmmForcesInput): name (short id or full catalog name), mapping.
198
+
199
+ Returns:
200
+ str: JSON with per-atom force vectors and related energetics.
201
+ """
202
+ resolved = await _resolve_molecule_name(params.name)
203
+ payload = {**params.model_dump(), "name": resolved}
204
+ return json.dumps(await _request("POST", "/api/qmmm_forces", timeout=120.0, json=payload), indent=2)
205
+
206
+
207
+ @mcp.tool(name="dense_evolution_md_trajectory", annotations={"title": "Run a molecular dynamics trajectory", **COMPUTE})
208
+ @catch_errors
209
+ async def dense_evolution_md_trajectory(params: MdTrajectoryInput) -> str:
210
+ """Run a real Velocity-Verlet MD trajectory driven by Hellmann-Feynman
211
+ forces at every step, for a catalog molecule.
212
+
213
+ Args:
214
+ params (MdTrajectoryInput): name (short id or full catalog name),
215
+ n_steps, dt_fs, mapping, recompute_electronic_state (true = true
216
+ ab-initio MD, capped at 30 steps; false = fixed electronic
217
+ state, capped at 200 steps).
218
+
219
+ Returns:
220
+ str: JSON with the trajectory (positions/energies per step).
221
+ "Error: ..." if n_steps is out of range for the chosen mode.
222
+ """
223
+ resolved = await _resolve_molecule_name(params.name)
224
+ payload = {**params.model_dump(), "name": resolved}
225
+ return json.dumps(await _request("POST", "/api/md_trajectory", timeout=600.0, json=payload), indent=2)
@@ -0,0 +1,83 @@
1
+ """Tools: circuit building and execution."""
2
+ import json
3
+
4
+ from ..client import _request, catch_errors
5
+ from ..config import COMPUTE, READ_ONLY_IDEMPOTENT
6
+ from ..models import BuildCircuitInput, RunCircuitInput
7
+ from ..server import mcp
8
+ from ..utils.images import _save_png
9
+ from ..utils.truncation import _truncate_probabilities, _truncate_statevector
10
+
11
+
12
+ @mcp.tool(name="dense_evolution_build_circuit", annotations={"title": "Build OpenQASM from gate operations", **READ_ONLY_IDEMPOTENT})
13
+ @catch_errors
14
+ async def dense_evolution_build_circuit(params: BuildCircuitInput) -> str:
15
+ """Convert a list of gate operations (as used by the graphical circuit
16
+ builder) into real OpenQASM text, ready to pass to `dense_evolution_run_circuit`.
17
+
18
+ Args:
19
+ params (BuildCircuitInput): n_qubits, ops (see dense_evolution_list_gates for valid gate names).
20
+
21
+ Returns:
22
+ str: JSON {"qasm": "..."} on success, or "Error: ..." if the op list is invalid.
23
+ """
24
+ data = await _request("POST", "/api/build_from_ops", timeout=30.0, json=params.model_dump())
25
+ return json.dumps(data, indent=2)
26
+
27
+
28
+ @mcp.tool(name="dense_evolution_run_circuit", annotations={"title": "Run an OpenQASM circuit", **COMPUTE})
29
+ @catch_errors
30
+ async def dense_evolution_run_circuit(params: RunCircuitInput) -> str:
31
+ """Run real OpenQASM on dense_evolution's DenseSVSimulator (or the MPS
32
+ backend for large circuits) and return measurement counts, probabilities,
33
+ and statevector amplitudes. Above the dense backend's safe qubit ceiling
34
+ (see dense_evolution_system_limits), automatically switches to an MPS
35
+ top-k-states approximation instead of failing.
36
+
37
+ Large statevectors/probability arrays are truncated to their most
38
+ significant entries (see 'shown' vs 'total_*' fields) to keep the
39
+ response usable in an agent's context -- the full histogram is always
40
+ returned in 'counts' since it's naturally bounded by `shots`.
41
+
42
+ Args:
43
+ params (RunCircuitInput): qasm, shots, seed, noise_model, noise_p, backend,
44
+ top_k, include_visualizations (see field descriptions).
45
+
46
+ Returns:
47
+ str: JSON with n_qubits, backend, counts, truncated probabilities/statevector,
48
+ fidelity_vs_ideal, and (if include_visualizations) paths to saved PNG files.
49
+ For circuits above the dense limit on the 'mps' backend, returns a
50
+ differently-shaped {"large_scale": true, "top_k_states": [...], ...} response.
51
+ """
52
+ payload = params.model_dump(exclude={"include_visualizations", "top_k"})
53
+ data = await _request("POST", "/api/run", timeout=60.0, json=payload)
54
+
55
+ image_metadata = {
56
+ "tool": "dense_evolution_run_circuit", "qasm": params.qasm, "shots": params.shots,
57
+ "seed": params.seed, "noise_model": params.noise_model, "noise_p": params.noise_p,
58
+ "backend": params.backend,
59
+ }
60
+
61
+ if data.get("large_scale"):
62
+ result = {k: v for k, v in data.items() if k != "circuit_png"}
63
+ if params.include_visualizations:
64
+ result["circuit_png_path"] = _save_png(data.get("circuit_png"), "circuit_large_scale", metadata=image_metadata)
65
+ return json.dumps(result, indent=2)
66
+
67
+ result = {
68
+ "n_qubits": data["n_qubits"],
69
+ "backend": data["backend"],
70
+ "counts": data["counts"],
71
+ "probabilities": _truncate_probabilities(data["probabilities"], top_k=params.top_k),
72
+ "statevector": _truncate_statevector(data["statevector"], top_k=params.top_k),
73
+ "fidelity_vs_ideal": data.get("fidelity_vs_ideal"),
74
+ "mps_max_bond_used": data.get("mps_max_bond_used"),
75
+ "mps_memory_mb": data.get("mps_memory_mb"),
76
+ "mps_avg_jsd": data.get("mps_avg_jsd"),
77
+ }
78
+ if params.include_visualizations:
79
+ result["circuit_png_path"] = _save_png(data.get("circuit_png"), "circuit", metadata=image_metadata)
80
+ result["histogram_png_path"] = _save_png(data.get("histogram_png"), "histogram", metadata=image_metadata)
81
+ result["qsphere_png_path"] = _save_png(data.get("qsphere_png"), "qsphere", metadata=image_metadata)
82
+ result["bloch_png_path"] = _save_png(data.get("bloch_png"), "bloch", metadata=image_metadata)
83
+ return json.dumps(result, indent=2)
@@ -0,0 +1,66 @@
1
+ """Tools: quantum cryptography protocols (crypto-q, promoted from
2
+ Dense-Evolution-Discovery issue #189). Registered against the shared
3
+ `mcp` instance created in server.py -- see that module's docstring for
4
+ why importing `mcp` back from there (rather than the other way around)
5
+ is safe despite looking circular."""
6
+ import json
7
+
8
+ from ..client import _request, catch_errors
9
+ from ..config import COMPUTE
10
+ from ..models import Bb84Input, DiQkdGhzInput, DickaInput
11
+ from ..server import mcp
12
+
13
+
14
+ @mcp.tool(name="dense_evolution_crypto_bb84", annotations={"title": "Run BB84 quantum key distribution", **COMPUTE})
15
+ @catch_errors
16
+ async def dense_evolution_crypto_bb84(params: Bb84Input) -> str:
17
+ """Run real BB84 quantum key distribution: prepare -> channel ->
18
+ measure -> sift -> QBER. Validated at N=5000 rounds, 5 independent
19
+ seeds: QBER=0 on a perfect channel, QBER=2*p_channel/3 under isotropic
20
+ depolarizing noise, QBER=0.25 under an intercept-resend attack
21
+ (eve=True) -- all within +-1sigma of theory.
22
+
23
+ Args:
24
+ params (Bb84Input): n_rounds, p_channel, eve, seed.
25
+
26
+ Returns:
27
+ str: JSON with n_rounds, p_channel, eve, qber, sifted_key_length.
28
+ """
29
+ return json.dumps(await _request("POST", "/api/crypto/bb84", timeout=60.0, json=params.model_dump()), indent=2)
30
+
31
+
32
+ @mcp.tool(name="dense_evolution_crypto_di_qkd_ghz", annotations={"title": "Run device-independent QKD via GHZ(3)", **COMPUTE})
33
+ @catch_errors
34
+ async def dense_evolution_crypto_di_qkd_ghz(params: DiQkdGhzInput) -> str:
35
+ """Run real three-party device-independent conference key agreement
36
+ via a GHZ(3) state (Ribeiro, Murta & Wehner 2018, arXiv:1708.00798),
37
+ using the paper's own Parity-CHSH inequality. win_rate reaches the
38
+ quantum maximum ~0.85355 to machine precision on an ideal channel,
39
+ comfortably clearing the classical bound of 0.75.
40
+
41
+ Args:
42
+ params (DiQkdGhzInput): n_rounds, p_dep, seed.
43
+
44
+ Returns:
45
+ str: JSON with n_rounds, p_dep, win_rate, expected_win_rate, qber_b1, qber_b2.
46
+ """
47
+ return json.dumps(await _request("POST", "/api/crypto/di_qkd_ghz", timeout=60.0, json=params.model_dump()), indent=2)
48
+
49
+
50
+ @mcp.tool(name="dense_evolution_crypto_dicka", annotations={"title": "Run the multi-round DICKA protocol", **COMPUTE})
51
+ @catch_errors
52
+ async def dense_evolution_crypto_dicka(params: DickaInput) -> str:
53
+ """Run the full multi-round DICKA structure (Appendix Protocol 2 of
54
+ Ribeiro, Murta & Wehner 2018) around the GHZ(3) primitives:
55
+ round selection, parameter estimation, and the abort decision.
56
+ Deliberately does not report a secure key length -- Theorem 4's exact
57
+ value depends on a numerical optimization the source paper never
58
+ reduces to closed form.
59
+
60
+ Args:
61
+ params (DickaInput): n_rounds, gamma, beta, p_dep, seed.
62
+
63
+ Returns:
64
+ str: JSON with n_rounds, n_test, n_key, p_hat, beta, aborted, qber_b1, qber_b2.
65
+ """
66
+ return json.dumps(await _request("POST", "/api/crypto/dicka", timeout=60.0, json=params.model_dump()), indent=2)
@@ -0,0 +1,81 @@
1
+ """Tools: error mitigation (ZNE, density-matrix ZNE, vector healing)."""
2
+ import json
3
+
4
+ from ..client import _request, catch_errors
5
+ from ..config import COMPUTE
6
+ from ..models import MitigateDensityMatrixInput, MitigateZneInput, VectorHealingInput
7
+ from ..server import mcp
8
+
9
+
10
+ @mcp.tool(name="dense_evolution_mitigate_zne", annotations={"title": "Zero-Noise Extrapolation on an expectation value", **COMPUTE})
11
+ @catch_errors
12
+ async def dense_evolution_mitigate_zne(params: MitigateZneInput) -> str:
13
+ """Run real Zero-Noise Extrapolation: measure a Pauli expectation value
14
+ at several noise scales under a real Kraus noise channel, then
15
+ extrapolate back to zero noise.
16
+
17
+ Args:
18
+ params (MitigateZneInput): qasm, pauli_string, noise_model, noise_p,
19
+ seed, extrapolation_method.
20
+
21
+ Returns:
22
+ str: JSON with n_qubits, ideal_expectation, noise_factors,
23
+ noisy_expectations, zne_extrapolated, extrapolation_method.
24
+ """
25
+ return json.dumps(await _request("POST", "/api/mitigate", timeout=120.0, json=params.model_dump()), indent=2)
26
+
27
+
28
+ @mcp.tool(name="dense_evolution_mitigate_density_matrix", annotations={"title": "Density-matrix Zero-Noise Extrapolation", **COMPUTE})
29
+ @catch_errors
30
+ async def dense_evolution_mitigate_density_matrix(params: MitigateDensityMatrixInput) -> str:
31
+ """Run real density-matrix ZNE: Monte-Carlo density-matrix estimate at
32
+ 1x/2x/3x noise_p, extrapolated and projected onto the nearest physical
33
+ state, graded by Uhlmann fidelity against the true ideal state.
34
+
35
+ Args:
36
+ params (MitigateDensityMatrixInput): qasm, noise_model, noise_p, seed.
37
+
38
+ Returns:
39
+ str: JSON with n_qubits, noise_factors, fidelity_raw, fidelity_corrected.
40
+ """
41
+ return json.dumps(await _request("POST", "/api/mitigate_matrix", timeout=120.0, json=params.model_dump()), indent=2)
42
+
43
+
44
+ @mcp.tool(name="dense_evolution_mitigate_coherence", annotations={"title": "Coherence-predictive density-matrix ZNE", **COMPUTE})
45
+ @catch_errors
46
+ async def dense_evolution_mitigate_coherence(params: MitigateDensityMatrixInput) -> str:
47
+ """Run real coherence-L1-predictive density-matrix ZNE: the same
48
+ Monte-Carlo density-matrix construction as
49
+ dense_evolution_mitigate_density_matrix, but extrapolated via a signal
50
+ that covers phase-type noise (phaseflip, dephasing) the classical-JSD
51
+ signal behind that other tool is structurally blind to -- it only ever
52
+ reads the density matrix's diagonal. Use this one for phase-type noise
53
+ models, the JSD one for amplitude/bitflip-type noise.
54
+
55
+ Args:
56
+ params (MitigateDensityMatrixInput): qasm, noise_model, noise_p, seed.
57
+
58
+ Returns:
59
+ str: JSON with n_qubits, noise_factors, fidelity_raw, fidelity_corrected.
60
+ """
61
+ return json.dumps(await _request("POST", "/api/mitigate_coherence", timeout=120.0, json=params.model_dump()), indent=2)
62
+
63
+
64
+ @mcp.tool(name="dense_evolution_vector_healing", annotations={"title": "Heal a noisy vector sequence", **COMPUTE})
65
+ @catch_errors
66
+ async def dense_evolution_vector_healing(params: VectorHealingInput) -> str:
67
+ """Run a real predictive-healing pass over a noisy (n_steps, dim)
68
+ vector sequence -- e.g. VQE convergence telemetry or an MD
69
+ trajectory. Per step, a Phi-Trigger (dense_evolution.healing) decides
70
+ whether the change from a local baseline looks like genuine dynamics
71
+ (kept as-is) or static noise (replaced by the local median). NaN/Inf
72
+ entries are sanitized first regardless of that decision.
73
+
74
+ Args:
75
+ params (VectorHealingInput): vectors, radius_baseline.
76
+
77
+ Returns:
78
+ str: JSON with healed_vectors, fallback_triggered,
79
+ adaptive_radius_used, reconstruction_error.
80
+ """
81
+ return json.dumps(await _request("POST", "/api/vector_healing", timeout=30.0, json=params.model_dump()), indent=2)
@@ -0,0 +1,60 @@
1
+ """Tools: standalone noise profiles and density-matrix channels."""
2
+ import json
3
+
4
+ from ..client import _request, catch_errors
5
+ from ..config import COMPUTE
6
+ from ..models import CosmicRayBurstInput, OscillatingNoiseInput, DensityMatrixChannelInput
7
+ from ..server import mcp
8
+
9
+
10
+ @mcp.tool(name="dense_evolution_cosmic_ray_burst", annotations={"title": "Cosmic-ray burst noise profile", **COMPUTE})
11
+ @catch_errors
12
+ async def dense_evolution_cosmic_ray_burst(params: CosmicRayBurstInput) -> str:
13
+ """Real time-dependent decay-probability profile for a cosmic-ray/
14
+ gamma-ray-induced quasiparticle burst, reproducing a real measured
15
+ event from arXiv:2104.05219 on a 26-qubit chip: a two-stage rise then
16
+ a single-exponential recovery.
17
+
18
+ Args:
19
+ params (CosmicRayBurstInput): baseline_gamma, times_us.
20
+
21
+ Returns:
22
+ str: JSON with times_us, baseline_gamma, decay_probabilities, peak_ratio.
23
+ """
24
+ return json.dumps(await _request("POST", "/api/cosmic_ray_burst", timeout=30.0, json=params.model_dump()), indent=2)
25
+
26
+
27
+ @mcp.tool(name="dense_evolution_oscillating_noise", annotations={"title": "Oscillating noise-scale profile", **COMPUTE})
28
+ @catch_errors
29
+ async def dense_evolution_oscillating_noise(params: OscillatingNoiseInput) -> str:
30
+ """A noise strength that oscillates instead of scaling smoothly with
31
+ a ZNE-style scale factor -- for checking whether a mitigation
32
+ technique that assumes smooth noise-vs-scale still works when that
33
+ assumption breaks down.
34
+
35
+ Args:
36
+ params (OscillatingNoiseInput): base_p, freq, amp, factors.
37
+
38
+ Returns:
39
+ str: JSON with base_p, freq, amp, factors, p_eff.
40
+ """
41
+ return json.dumps(await _request("POST", "/api/oscillating_noise", timeout=30.0, json=params.model_dump()), indent=2)
42
+
43
+
44
+ @mcp.tool(name="dense_evolution_density_matrix_channel", annotations={"title": "Apply a density-matrix noise channel", **COMPUTE})
45
+ @catch_errors
46
+ async def dense_evolution_density_matrix_channel(params: DensityMatrixChannelInput) -> str:
47
+ """Apply a density-matrix-level noise channel to the ideal density
48
+ matrix of a QASM circuit -- distinct from per-qubit statevector
49
+ noise (see dense_evolution_run_circuit's noise_model/noise_p):
50
+ 'global_depolarizing' mixes the whole register toward the fully
51
+ mixed state as one unit (a SPAM-style error), 'amplitude_damping' is
52
+ single-qubit asymmetric energy relaxation.
53
+
54
+ Args:
55
+ params (DensityMatrixChannelInput): qasm, channel, param.
56
+
57
+ Returns:
58
+ str: JSON with n_qubits, channel, param, ideal_diagonal, noisy_diagonal, trace.
59
+ """
60
+ return json.dumps(await _request("POST", "/api/density_matrix_channel", timeout=30.0, json=params.model_dump()), indent=2)
@@ -0,0 +1,44 @@
1
+ """Tools: document retrieval (ia_utils.rag, promoted from the quantumrag
2
+ literature-grounding tool). Registered against the shared `mcp` instance
3
+ created in server.py -- see that module's docstring for why importing
4
+ `mcp` back from there (rather than the other way around) is safe despite
5
+ looking circular."""
6
+ import json
7
+
8
+ from ..client import _request, catch_errors
9
+ from ..config import COMPUTE
10
+ from ..models import RagSearchInput
11
+ from ..server import mcp
12
+
13
+
14
+ @mcp.tool(name="dense_evolution_rag_search", annotations={"title": "Search documents with hybrid retrieval", **COMPUTE})
15
+ @catch_errors
16
+ async def dense_evolution_rag_search(params: RagSearchInput) -> str:
17
+ """Build an ephemeral index over the given documents and search it for
18
+ `query` in one call -- ground an agent's own answer in a document
19
+ collection, not a quantum measurement result. Nothing persists
20
+ server-side: each call builds and searches its own index from
21
+ scratch, so pass the same `documents` again for a second query rather
22
+ than expecting a prior index to still exist.
23
+
24
+ Two modes: semantic (default) pools TF-IDF and dense-bi-encoder
25
+ candidates then re-scores with a cross-encoder if rerank=True --
26
+ catches paraphrases plain TF-IDF misses. exact=True instead does a
27
+ substring/regex search over raw chunk text (no embedding, no
28
+ reranker) -- use this when you already know roughly what wording
29
+ you're looking for and semantic ranking buries it under
30
+ topically-similar-but-wrong chunks.
31
+
32
+ Needs the `rag` extra (pip install dense-evolution[rag]) -- returns an
33
+ actionable "Error: ..." if it isn't installed.
34
+
35
+ Args:
36
+ params (RagSearchInput): documents ([[text, source], ...]), query,
37
+ top, rerank, exact, regex, max_hits, context.
38
+
39
+ Returns:
40
+ str: JSON with `results` -- semantic mode: a list of {rank,
41
+ source, text, score, score_type, cosine}; exact mode: a list of
42
+ {source, chunk_index, match, snippet, start, end}.
43
+ """
44
+ return json.dumps(await _request("POST", "/api/rag_search", timeout=60.0, json=params.model_dump()), indent=2)