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
dense_evolution/qec.py ADDED
@@ -0,0 +1,20 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.physics.qec as part of the Phase 2 subpackage split
3
+ (see prog.txt). Kept so `from dense_evolution.qec import pauli_commutes, compute_syndrome, erasure_aware_decode`
4
+ (used by external consumers, e.g. Dense-Evolution-Discovery) keeps working
5
+ unchanged. Import from dense_evolution.physics.qec directly in new code.
6
+ """
7
+ from dense_evolution.physics.qec import (
8
+ pauli_commutes, compute_syndrome, erasure_aware_decode, pymatching_decode,
9
+ blind_minimum_weight_decode, decode_with_erasure_fallback,
10
+ counts_in_intervals_dimension, nearest_coset_decode, erasure_ml_decode,
11
+ peeling_decode, union_find_decode, matching_erasure_decode,
12
+ estimate_edge_probabilities_from_detection_events,
13
+ )
14
+
15
+ __all__ = [
16
+ 'pauli_commutes', 'compute_syndrome', 'erasure_aware_decode', 'pymatching_decode',
17
+ 'blind_minimum_weight_decode', 'decode_with_erasure_fallback',
18
+ 'counts_in_intervals_dimension', 'nearest_coset_decode', 'erasure_ml_decode', 'peeling_decode', 'union_find_decode', 'matching_erasure_decode',
19
+ 'estimate_edge_probabilities_from_detection_events',
20
+ ]
dense_evolution/qft.py ADDED
@@ -0,0 +1,9 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.circuits.qft as part of the Phase 2 subpackage split
3
+ (see prog.txt). Kept so `from dense_evolution.qft import qft`
4
+ (used by external consumers, e.g. Dense-Evolution-Discovery) keeps working
5
+ unchanged. Import from dense_evolution.circuits.qft directly in new code.
6
+ """
7
+ from dense_evolution.circuits.qft import qft
8
+
9
+ __all__ = ['qft']
@@ -0,0 +1,13 @@
1
+ from .forces import (
2
+ ATOMIC_MASSES_AMU, compute_hellmann_feynman_forces, md_step, run_md_trajectory,
3
+ MIN_NUCLEAR_DISTANCE_ANGSTROM, ACCEL_CONVERSION,
4
+ )
5
+ from .region import partition_qm_mm_region, sliced_geometry, ANGSTROM_TO_BOHR, CH_BOND_BOHR
6
+ from .propagation import propagate_relevance
7
+
8
+ __all__ = [
9
+ "ATOMIC_MASSES_AMU", "compute_hellmann_feynman_forces", "md_step", "run_md_trajectory",
10
+ "MIN_NUCLEAR_DISTANCE_ANGSTROM", "ACCEL_CONVERSION",
11
+ "partition_qm_mm_region", "sliced_geometry", "ANGSTROM_TO_BOHR", "CH_BOND_BOHR",
12
+ "propagate_relevance",
13
+ ]
@@ -0,0 +1,97 @@
1
+ """
2
+ ASE Calculator bridge for dense_evolution.native_hf (issue #288).
3
+
4
+ Motivation: QM/MM point 5's basis-flexibility test (Dense-Evolution-
5
+ Discovery, docs/qmmm_bond_order_and_embedding.md) needed a richer basis
6
+ (6-31G*) to test a real hypothesis about STO-3G's rigidity -- native_hf
7
+ already supports arbitrary basis names via `build_energy_fn`'s own
8
+ `basis_name` string parameter (nothing was actually hardcoded at the
9
+ library level; the QM/MM scripts' own `BASIS = "sto-3g"` constants were
10
+ just a script-level convenience). What native_hf does NOT give for free
11
+ is interop with the wider Python computational-chemistry ecosystem (ASE
12
+ Atoms objects, ASE's own optimizers/MD drivers, other engines' Atoms
13
+ representations) -- this bridge is exactly that interop layer, not a new
14
+ basis-set capability.
15
+
16
+ Requires ASE (the `ase` extra: pip install dense-evolution[ase]).
17
+ """
18
+ import numpy as np
19
+
20
+ HARTREE_TO_EV = 27.211386245988 # CODATA, matches this project's own use of scipy.constants elsewhere (dense_evolution.qmmm.forces.ACCEL_CONVERSION)
21
+ ANGSTROM_TO_BOHR = 1.8897259886
22
+
23
+
24
+ def _import_ase_calculator():
25
+ try:
26
+ from ase.calculators.calculator import Calculator, all_changes
27
+ except ImportError as exc:
28
+ raise ImportError(
29
+ "dense_evolution.qmmm.ase_bridge needs ASE, an optional dependency "
30
+ "(pip install dense-evolution[ase]); it is not installed."
31
+ ) from exc
32
+ return Calculator, all_changes
33
+
34
+
35
+ class DenseEvolutionCalculator:
36
+ """An ASE Calculator backed by dense_evolution.native_hf's own
37
+ differentiable RHF energy (build_energy_fn) -- real Obara-Saika
38
+ integrals and SCF, not a stub. `basis_name` is a plain string, passed
39
+ straight through to native_hf (e.g. "sto-3g", "6-31g*", any basis
40
+ basis_set_exchange has data for).
41
+
42
+ ASE units: eV, Angstrom (this class converts from native_hf's own
43
+ Hartree/Bohr at the boundary; everything inside native_hf itself
44
+ stays atomic units).
45
+
46
+ Only `energy` is implemented -- native_hf's forces come from
47
+ dense_evolution.qmmm.forces.compute_hellmann_feynman_forces (a
48
+ separate, already-real Hellmann-Feynman implementation with its own
49
+ finite-difference derivative and its own molecule-catalog-shaped
50
+ calling convention); this bridge does not reimplement or wrap that
51
+ here.
52
+
53
+ Examples
54
+ --------
55
+ >>> from ase import Atoms
56
+ >>> from dense_evolution.qmmm.ase_bridge import DenseEvolutionCalculator
57
+ >>> h2 = Atoms('H2', positions=[[0, 0, 0], [0, 0, 0.7414]])
58
+ >>> h2.calc = DenseEvolutionCalculator(atomic_numbers=[1, 1], nuclear_charges=[1.0, 1.0],
59
+ ... n_electrons=2, basis_name="sto-3g")
60
+ >>> round(h2.get_potential_energy(), 2)
61
+ -30.39
62
+ """
63
+
64
+ implemented_properties = ["energy"]
65
+
66
+ def __new__(cls, atomic_numbers, nuclear_charges, n_electrons, basis_name):
67
+ Calculator, all_changes = _import_ase_calculator()
68
+
69
+ class _Impl(Calculator):
70
+ implemented_properties = ["energy"]
71
+
72
+ def __init__(self):
73
+ super().__init__()
74
+ self._atomic_numbers = atomic_numbers
75
+ self._nuclear_charges = nuclear_charges
76
+ self._n_electrons = n_electrons
77
+ self._basis_name = basis_name
78
+ self._energy_fn = None
79
+ self._reference_geometry_bohr = None
80
+
81
+ def calculate(self, atoms=None, properties=("energy",), system_changes=all_changes):
82
+ super().calculate(atoms, properties, system_changes)
83
+ from dense_evolution.config import ensure_x64
84
+ from dense_evolution.native_hf.differentiable import build_energy_fn
85
+ ensure_x64()
86
+
87
+ geometry_bohr = np.asarray(atoms.get_positions()) * ANGSTROM_TO_BOHR
88
+ if self._energy_fn is None or "numbers" in system_changes:
89
+ self._reference_geometry_bohr = geometry_bohr
90
+ self._energy_fn = build_energy_fn(
91
+ self._atomic_numbers, self._nuclear_charges, self._n_electrons,
92
+ self._basis_name, self._reference_geometry_bohr,
93
+ )
94
+ energy_hartree = float(self._energy_fn(geometry_bohr))
95
+ self.results["energy"] = energy_hartree * HARTREE_TO_EV
96
+
97
+ return _Impl()
@@ -0,0 +1,388 @@
1
+ """
2
+ Real Hellmann-Feynman nuclear forces and a real Velocity-Verlet MD step
3
+ for this project's real molecule catalog -- no fabricated geometry,
4
+ charges, or Hamiltonian anywhere.
5
+
6
+ Forces are F = -dE/dR (E = <psi|H(R)|psi>, psi held fixed -- the
7
+ Hellmann-Feynman theorem exactly), with H(R) this project's own real
8
+ Hamiltonian at geometry R (dashboard_core.hamiltonians.
9
+ build_molecular_hamiltonian -- the same real Hartree-Fock/Jordan-Wigner
10
+ pipeline already used throughout dashboard_core, not a separate one built
11
+ for this module). The derivative itself is a real central finite
12
+ difference (step verified converged: h=0.001 and h=0.0005 Angstrom agree
13
+ to 4 significant figures against H2), not PennyLane's autograd through
14
+ the full qchem pipeline.
15
+
16
+ An earlier version used qml.grad to differentiate PennyLane's
17
+ "dhf"-method Hamiltonian directly -- mathematically the more elegant
18
+ exact-derivative approach, and it worked locally, but failed identically
19
+ on both Ubuntu and macOS CI runners with the exact same PennyLane
20
+ version (0.45.1) that passed locally on Windows: `TypeError: unsupported
21
+ operand type(s) for +: 'NotImplementedType' and 'NotImplementedType'`, a
22
+ signature of autograd hitting an operation its VJP system doesn't
23
+ support, apparently platform-dependent inside PennyLane/autograd's own
24
+ internals. Rather than depend on that fragile cross-platform behavior,
25
+ this module differentiates numerically instead, reusing the same dense
26
+ Hamiltonian construction already verified elsewhere in this codebase.
27
+
28
+ An even earlier version tried adding an external classical point-charge
29
+ potential directly to the post-Jordan-Wigner dense Hamiltonian (mirroring
30
+ legacy/dash.py's QMMMForceEngine). That shape-mismatched (the JW matrix
31
+ indexes many-body qubit basis states, not one row per atom) and was
32
+ dropped for the same direct-differentiation idea, minus the fragile
33
+ autograd dependency.
34
+
35
+ Verified 2026-08-05 against H2: force ~0.0154 Hartree/Angstrom at the
36
+ real equilibrium bond length (0.7414 A, small residual expected since
37
+ the electronic state is evaluated at fixed geometry -- clamped-nucleus
38
+ Hellmann-Feynman, not a fully relaxed force), rising sharply and
39
+ correctly signed as a restoring force at a stretched 1.2 A bond.
40
+
41
+ Moved here from dashboard_core/qmmm.py (previously the only "qmmm" code
42
+ in this project, doing full-QM Hellmann-Feynman forces on a small fixed
43
+ molecule catalog -- no MM region, no partitioning) so this module lives
44
+ in the library alongside region.py/propagation.py (real QM/MM region
45
+ partitioning and embedding, Dense-Evolution issue #283) instead of being
46
+ buried inside the dashboard tool. dashboard_core.qmmm now re-exports
47
+ these names for backward compatibility -- the dashboard code did not
48
+ change.
49
+ """
50
+ import numpy as np
51
+ from scipy import constants as _c
52
+
53
+ __all__ = [
54
+ 'ATOMIC_MASSES_AMU', 'compute_hellmann_feynman_forces', 'md_step', 'run_md_trajectory',
55
+ 'MIN_NUCLEAR_DISTANCE_ANGSTROM', 'ACCEL_CONVERSION',
56
+ ]
57
+
58
+
59
+ def _import_dashboard_hamiltonians():
60
+ # Lazy, not module-level: dense_evolution.qmmm is walked by
61
+ # test_imports.py::test_all_submodules_importable_with_only_base_dependencies,
62
+ # which must succeed with ONLY dense_evolution's own base dependencies
63
+ # installed. dashboard_core is a separate, heavier, optional tool (its
64
+ # own 'dashboard' extra: streamlit, qiskit, pennylane, ...) built ON
65
+ # TOP of this library -- the core library must never require it just
66
+ # to be imported. A real bug found in CI: an earlier version of this
67
+ # module imported dashboard_core.hamiltonians at module level, so
68
+ # merely importing dense_evolution.qmmm (even for region.py/
69
+ # propagation.py, which need neither dashboard_core nor this
70
+ # function) forced a dashboard_core import and broke that test.
71
+ try:
72
+ from dashboard_core.hamiltonians import MOLECULE_CATALOG, build_molecular_hamiltonian
73
+ except ImportError as exc:
74
+ raise ImportError(
75
+ "dense_evolution.qmmm.forces's Hellmann-Feynman/MD functions need "
76
+ "dashboard_core (the 'dashboard' extra: pip install dense-evolution[dashboard]); "
77
+ "it is not installed."
78
+ ) from exc
79
+ return MOLECULE_CATALOG, build_molecular_hamiltonian
80
+
81
+
82
+ # A plain physical-safety-floor constant (0.3 A -- shorter than any real
83
+ # covalent bond dashboard_core's molecule catalog could ever produce),
84
+ # not something that needs dashboard_core's dynamic Hamiltonian
85
+ # machinery -- kept as a real, always-available module-level constant
86
+ # here (must stay numerically in sync with
87
+ # dashboard_core.hamiltonians.MIN_NUCLEAR_DISTANCE_ANGSTROM, which is
88
+ # itself just this same literal).
89
+ MIN_NUCLEAR_DISTANCE_ANGSTROM = 0.3
90
+
91
+ # MIN_NUCLEAR_DISTANCE_ANGSTROM itself lives in hamiltonians.py now (prog.txt,
92
+ # dashboard_core audit point 1d) -- this module already imports several
93
+ # other names from there (see above), so importing this one too is not
94
+ # circular; it was previously redefined here under a comment reasoning
95
+ # the opposite (that importing it WOULD be circular), which had the
96
+ # import direction backwards. Real MD safety floor, not a fabricated
97
+ # number: shorter than any real covalent bond this project's molecule
98
+ # catalog could ever produce (H2's own equilibrium is 0.7414 A) --
99
+ # run_md_trajectory checks new positions against this after every
100
+ # Velocity-Verlet step, since Hartree-Fock at a near-collided geometry
101
+ # (the failure mode a too-large dt_fs drives light atoms like H toward)
102
+ # diverges rather than raising a clear error, making the actual cause
103
+ # hard to diagnose from the resulting crash. md_step itself stays a
104
+ # bare, unchecked F=ma primitive -- the check belongs at
105
+ # run_md_trajectory's real-simulation boundary, not the mechanical
106
+ # formula (tests exercise md_step directly with synthetic, not
107
+ # physically meaningful, starting positions).
108
+
109
+
110
+ def _assert_no_nuclear_collision(positions, step, dt_fs):
111
+ """Raises RuntimeError if any two atoms in `positions` (n_atoms, 3)
112
+ are closer than MIN_NUCLEAR_DISTANCE_ANGSTROM -- see that constant's
113
+ comment for why. A no-op for a single atom (nothing to compare)."""
114
+ if positions.shape[0] < 2:
115
+ return
116
+ diffs = positions[:, None, :] - positions[None, :, :]
117
+ dists = np.linalg.norm(diffs, axis=-1)
118
+ np.fill_diagonal(dists, np.inf)
119
+ min_dist = float(dists.min())
120
+ if min_dist < MIN_NUCLEAR_DISTANCE_ANGSTROM:
121
+ raise RuntimeError(
122
+ f"MD trajectory diverged: two atoms are {min_dist:.4f} A apart at step "
123
+ f"{step + 1} (below the {MIN_NUCLEAR_DISTANCE_ANGSTROM} A safety floor -- "
124
+ f"no real covalent bond in this catalog is that short). This almost always "
125
+ f"means dt_fs={dt_fs} is too large for the forces involved -- light atoms "
126
+ f"like H accelerate sharply and overshoot in a single step. Try a smaller "
127
+ f"dt_fs before rerunning."
128
+ )
129
+
130
+ # Real standard atomic weights (amu) for the elements this project's real
131
+ # molecule catalog actually uses (H2, HeH+, H3+, LiH, H2O) -- only what's
132
+ # needed, not the full periodic table, so nothing here is an unverified
133
+ # guess for an element no catalog molecule contains.
134
+ ATOMIC_MASSES_AMU = {'H': 1.008, 'He': 4.0026, 'Li': 6.94, 'O': 16.00, 'Si': 28.085}
135
+
136
+ # a [Angstrom/fs^2] = ACCEL_CONVERSION * F[Hartree/Angstrom] / mass[amu]
137
+ # -- derived from CODATA (scipy.constants), verified 2026-08-05:
138
+ # (hartree_J / angstrom_m) / amu_kg * fs_s**2 / angstrom_m = 0.262550...
139
+ ACCEL_CONVERSION = (
140
+ (_c.physical_constants['Hartree energy'][0] / 1e-10)
141
+ / _c.physical_constants['atomic mass constant'][0]
142
+ * (1e-15 ** 2) / 1e-10
143
+ )
144
+
145
+
146
+ def _reference_ground_state(symbols, geometry, charge, mapping, active_electrons=None, active_orbitals=None):
147
+ """Real ground-state eigenvector of the molecule's real Hamiltonian at
148
+ the given geometry, via build_molecular_hamiltonian -- the same
149
+ construction dashboard_core.hamiltonians' own energy/VQE panels use,
150
+ with its own native_hf fallback for elements outside PennyLane's
151
+ bundled STO-3G table (e.g. Si). Not part of the differentiable path
152
+ -- this is evaluated once at a fixed geometry to get a real
153
+ electronic state, then held fixed while forces are computed at
154
+ (possibly different) geometries, exactly as the Hellmann-Feynman
155
+ theorem requires.
156
+
157
+ BUG FIX (prog.txt, dashboard_core audit point 3a): this used to call
158
+ qml.qchem.Molecule/molecular_hamiltonian directly instead of going
159
+ through build_molecular_hamiltonian, so any molecule needing the
160
+ native_hf fallback (Si2 is in MOLECULE_CATALOG precisely because it
161
+ needs it) crashed here with PennyLane's own "basis set data is not
162
+ available for Si" error -- even though the rest of this module
163
+ (energy_at, below) already used the fallback-aware path and would
164
+ otherwise have worked."""
165
+ _MOLECULE_CATALOG, build_molecular_hamiltonian = _import_dashboard_hamiltonians()
166
+ h_matrix, n_qubits = build_molecular_hamiltonian(
167
+ symbols, geometry, charge, mapping, active_electrons, active_orbitals)
168
+ eigvals, eigvecs = np.linalg.eigh(h_matrix)
169
+ return eigvecs[:, 0], float(eigvals[0]), n_qubits
170
+
171
+
172
+ def compute_hellmann_feynman_forces(name: str, statevector=None, mapping: str = "jordan_wigner",
173
+ geometry=None, fd_step_angstrom: float = 0.001):
174
+ """Real Hellmann-Feynman forces (Hartree/Angstrom) on every nucleus of
175
+ MOLECULE_CATALOG[name]: F = -d<psi|H(R)|psi>/dR, with H(R) this
176
+ project's own real Hamiltonian (build_molecular_hamiltonian) and psi
177
+ held fixed. The derivative is a real central finite difference
178
+ (fd_step_angstrom, default 0.001 A -- verified converged against
179
+ 0.0005 A to 4 significant figures for H2), not automatic
180
+ differentiation (see module docstring for why). statevector defaults
181
+ to the molecule's own real Hartree-Fock ground state (computed at its
182
+ catalog geometry) -- pass a VQE-converged state instead to get forces
183
+ evaluated on that state. geometry defaults to the catalog's own
184
+ equilibrium geometry -- an MD loop moving the nuclei must pass its
185
+ own current positions here at each step, or every step evaluates the
186
+ same fixed catalog geometry again (the actual bug this parameter was
187
+ added to fix: run_md_trajectory originally never passed its own
188
+ updated positions back in here).
189
+
190
+ Cost (prog.txt, dashboard_core audit point 4a): the central-difference
191
+ derivative evaluates energy_at 6*n_atoms+1 times (H2's 2 atoms -> 13
192
+ Hamiltonian builds per call, each at a genuinely different geometry).
193
+ This looks like it should be cacheable -- build_molecular_hamiltonian
194
+ already caches by exact geometry -- but it isn't in practice: every
195
+ one of the 13 geometries differs by fd_step_angstrom, so every call
196
+ is a cache miss. Measured directly on H2 (dhf/PennyLane path) before
197
+ deciding not to add a "cache the Pauli-term basis" layer here: HF +
198
+ fermion-to-qubit mapping took 0.130s, Pauli-term extraction 0.0005s,
199
+ dense matrix assembly 0.0031s -- the Hartree-Fock solve itself is
200
+ 96%+ of the cost, not the bookkeeping after it, so caching the
201
+ Pauli-term structure would save a few percent at best, not the
202
+ 6*n_atoms multiplier prog.txt's framing suggests. A real geometry
203
+ change requires a real HF re-solve regardless of how its output gets
204
+ packaged afterward -- see the module docstring's own account of why
205
+ analytic differentiation (which WOULD avoid re-solving HF this many
206
+ times) was tried and dropped for a real cross-platform PennyLane/
207
+ autograd bug, not reattempted here.
208
+
209
+ Parameters
210
+ ----------
211
+ name : str
212
+ A key from `MOLECULE_CATALOG` (e.g. one of `list(MOLECULE_CATALOG)`
213
+ -- these are descriptive strings like `"H2 (Idrogeno) - R = 0.7414
214
+ A [equilibrio reale]"`, not bare element symbols like `"H2"`).
215
+ statevector, mapping, geometry, fd_step_angstrom : see description above.
216
+
217
+ Returns
218
+ -------
219
+ dict
220
+ `name`, `symbols`, `energy_hartree`, `positions_angstrom`,
221
+ `forces_hartree_per_angstrom`, `force_norm`.
222
+
223
+ Examples
224
+ --------
225
+ >>> from dense_evolution.qmmm import compute_hellmann_feynman_forces
226
+ >>> from dashboard_core.hamiltonians import MOLECULE_CATALOG
227
+ >>> h2 = [k for k in MOLECULE_CATALOG if k.startswith("H2 ")][0]
228
+ >>> result = compute_hellmann_feynman_forces(h2)
229
+ >>> round(result['energy_hartree'], 4)
230
+ -1.1373
231
+ >>> 0.01 < result['force_norm'] < 0.02 # small residual at equilibrium, not exactly zero
232
+ True
233
+ """
234
+ MOLECULE_CATALOG, build_molecular_hamiltonian = _import_dashboard_hamiltonians()
235
+ if name not in MOLECULE_CATALOG:
236
+ raise ValueError(f"unknown molecule {name!r}; available: {sorted(MOLECULE_CATALOG)}")
237
+ spec = MOLECULE_CATALOG[name]
238
+ symbols = spec["symbols"]
239
+ if geometry is None:
240
+ geometry = spec["geometry"]() if callable(spec["geometry"]) else spec["geometry"]
241
+ charge = spec["charge"]
242
+ # BUG FIX (prog.txt, dashboard_core audit point 3a): these two were
243
+ # never read from spec at all, so any catalog entry needing active-
244
+ # space reduction (Si2 is the reason it's in MOLECULE_CATALOG) built
245
+ # the FULL Hamiltonian instead of the reduced one here -- for Si2
246
+ # specifically, 36 qubits instead of the intended 8, which
247
+ # SafeMemoryGuard correctly refuses to allocate. Both
248
+ # _reference_ground_state and energy_at below need these to build
249
+ # the same, correctly-reduced Hamiltonian this molecule's other
250
+ # dashboard panels (VQE, energy scan) already use.
251
+ active_electrons = spec.get("active_electrons")
252
+ active_orbitals = spec.get("active_orbitals")
253
+
254
+ unknown = [s for s in symbols if s not in ATOMIC_MASSES_AMU]
255
+ if unknown:
256
+ raise ValueError(f"no real atomic mass on file for {unknown} -- add to "
257
+ f"ATOMIC_MASSES_AMU before using this molecule here")
258
+
259
+ if statevector is None:
260
+ statevector, _gs_energy, _n_qubits = _reference_ground_state(
261
+ symbols, geometry, charge, mapping, active_electrons, active_orbitals)
262
+ sv = np.asarray(statevector, dtype=np.complex128)
263
+ geometry = np.asarray(geometry, dtype=np.float64)
264
+
265
+ def energy_at(geom):
266
+ h_matrix, _n_qubits = build_molecular_hamiltonian(
267
+ symbols, geom, charge, mapping, active_electrons, active_orbitals)
268
+ return float(np.real(np.vdot(sv, h_matrix @ sv)))
269
+
270
+ energy = energy_at(geometry)
271
+ forces = np.zeros_like(geometry)
272
+ h = fd_step_angstrom
273
+ for i in range(geometry.shape[0]):
274
+ for j in range(3):
275
+ geom_plus = geometry.copy()
276
+ geom_plus[i, j] += h
277
+ geom_minus = geometry.copy()
278
+ geom_minus[i, j] -= h
279
+ forces[i, j] = -(energy_at(geom_plus) - energy_at(geom_minus)) / (2 * h)
280
+
281
+ return {
282
+ "name": name,
283
+ "symbols": symbols,
284
+ "energy_hartree": energy,
285
+ "positions_angstrom": geometry.tolist(),
286
+ "forces_hartree_per_angstrom": forces.tolist(),
287
+ "force_norm": float(np.linalg.norm(forces)),
288
+ }
289
+
290
+
291
+ def md_step(positions_angstrom, velocities_angstrom_per_fs, forces_hartree_per_angstrom,
292
+ symbols, dt_fs: float = 0.5):
293
+ """One real Velocity-Verlet half-step (v(t+dt/2) = v(t) + a(t)*dt/2,
294
+ r(t+dt) = r(t) + v(t+dt/2)*dt) using the real Hellmann-Feynman forces
295
+ above and each atom's real atomic mass -- ordinary classical Newtonian
296
+ mechanics (F=ma), nothing invented. Positions in Angstrom, velocities
297
+ in Angstrom/fs, forces in Hartree/Angstrom, dt in femtoseconds.
298
+
299
+ Examples
300
+ --------
301
+ >>> import numpy as np
302
+ >>> from dense_evolution.qmmm import md_step
303
+ >>> positions = np.array([[0, 0, 0.0], [0, 0, 0.7414]]) # H2 at equilibrium
304
+ >>> velocities = np.zeros_like(positions)
305
+ >>> forces = np.array([[0, 0, 0.0109], [0, 0, -0.0109]]) # restoring force, pulling atoms together
306
+ >>> new_pos, new_vel, accel = md_step(positions, velocities, forces, ['H', 'H'], dt_fs=0.5)
307
+ >>> bool(new_pos[1, 2] < 0.7414) # the second atom moved toward the first
308
+ True
309
+ """
310
+ positions = np.asarray(positions_angstrom, dtype=np.float64)
311
+ velocities = np.asarray(velocities_angstrom_per_fs, dtype=np.float64)
312
+ forces = np.asarray(forces_hartree_per_angstrom, dtype=np.float64)
313
+ masses = np.array([ATOMIC_MASSES_AMU[s] for s in symbols], dtype=np.float64)
314
+
315
+ accel = ACCEL_CONVERSION * forces / masses[:, None]
316
+ velocities_half = velocities + 0.5 * accel * dt_fs
317
+ positions_new = positions + velocities_half * dt_fs
318
+ return positions_new, velocities_half, accel
319
+
320
+
321
+ def run_md_trajectory(name: str, n_steps: int, dt_fs: float = 0.5, mapping: str = "jordan_wigner",
322
+ recompute_electronic_state: bool = False, fd_step_angstrom: float = 0.001):
323
+ """Real, minimal ab-initio-forces MD trajectory: at each step, real
324
+ Hellmann-Feynman forces (compute_hellmann_feynman_forces) move the
325
+ real nuclear positions/velocities via real Velocity-Verlet (md_step).
326
+ Starts from rest (zero initial velocities) at the catalog's real
327
+ equilibrium geometry.
328
+
329
+ fd_step_angstrom: forwarded to compute_hellmann_feynman_forces at
330
+ every step -- previously not exposed here at all, silently using
331
+ that function's own default (0.001 A) with no way for a caller to
332
+ ask for a different finite-difference step (e.g. a molecule with an
333
+ unusually steep energy landscape, where the default step size isn't
334
+ the one already verified converged for H2).
335
+
336
+ recompute_electronic_state=False (default) holds the electronic state
337
+ fixed at the initial Hartree-Fock reference through the whole
338
+ trajectory -- forces stay exact only close to the starting geometry
339
+ (a real, explicitly-stated approximation, not a fabricated one).
340
+ True ab-initio MD (re-solving Hartree-Fock at every step's new
341
+ geometry) is available by setting this True, at real, substantial
342
+ extra cost per step.
343
+
344
+ Examples
345
+ --------
346
+ >>> from dense_evolution.qmmm import run_md_trajectory
347
+ >>> from dashboard_core.hamiltonians import MOLECULE_CATALOG
348
+ >>> h2 = [k for k in MOLECULE_CATALOG if k.startswith("H2 ")][0]
349
+ >>> traj = run_md_trajectory(h2, n_steps=3, dt_fs=0.5)
350
+ >>> traj['step']
351
+ [0, 1, 2]
352
+ >>> len(traj['force_norm'])
353
+ 3
354
+ """
355
+ MOLECULE_CATALOG, _build_molecular_hamiltonian = _import_dashboard_hamiltonians()
356
+ if name not in MOLECULE_CATALOG:
357
+ raise ValueError(f"unknown molecule {name!r}; available: {sorted(MOLECULE_CATALOG)}")
358
+ spec = MOLECULE_CATALOG[name]
359
+ symbols = spec["symbols"]
360
+ geometry = spec["geometry"]() if callable(spec["geometry"]) else spec["geometry"]
361
+ charge = spec["charge"]
362
+ active_electrons = spec.get("active_electrons")
363
+ active_orbitals = spec.get("active_orbitals")
364
+
365
+ statevector, _gs_energy, _n_qubits = _reference_ground_state(
366
+ symbols, geometry, charge, mapping, active_electrons, active_orbitals)
367
+ positions = np.asarray(geometry, dtype=np.float64)
368
+ velocities = np.zeros_like(positions)
369
+
370
+ trajectory = {"step": [], "time_fs": [], "positions_angstrom": [], "energy_hartree": [], "force_norm": []}
371
+ for step in range(n_steps):
372
+ result = compute_hellmann_feynman_forces(name, statevector, mapping=mapping, geometry=positions,
373
+ fd_step_angstrom=fd_step_angstrom)
374
+ forces = np.asarray(result["forces_hartree_per_angstrom"])
375
+ trajectory["step"].append(step)
376
+ trajectory["time_fs"].append(step * dt_fs)
377
+ trajectory["positions_angstrom"].append(positions.tolist())
378
+ trajectory["energy_hartree"].append(result["energy_hartree"])
379
+ trajectory["force_norm"].append(result["force_norm"])
380
+
381
+ positions, velocities, _accel = md_step(positions, velocities, forces, symbols, dt_fs=dt_fs)
382
+ _assert_no_nuclear_collision(positions, step, dt_fs)
383
+
384
+ if recompute_electronic_state:
385
+ statevector, _gs_energy, _n_qubits = _reference_ground_state(
386
+ symbols, positions, charge, mapping, active_electrons, active_orbitals)
387
+
388
+ return trajectory
@@ -0,0 +1,80 @@
1
+ """
2
+ Diffuse2Seg-derived relevance propagation, for QM/MM region selection
3
+ weighted by a real chemical affinity (Mayer/Wiberg bond order) instead of
4
+ a fixed hop-count radius.
5
+ """
6
+ import numpy as np
7
+
8
+
9
+ def propagate_relevance(affinity, seed_idx, n_nodes, p=1.6, lam=1e-5, tau_prop=1e-4, max_iter=500):
10
+ """Algorithm 1 of Hummer, Sicking, Huger & Gottschalk 2026
11
+ (arXiv:2609.06491, "Diffuse2Seg", Sec. 3.4/A.1), read directly from
12
+ the paper and implemented verbatim.
13
+
14
+ Non-linear p-Laplacian graph-regularized smoothing (Elmoataz et al.
15
+ 2008), solved by Gauss-Jacobi iteration: propagates a one-hot seed
16
+ vector over any node-affinity graph `affinity` (self-attention in the
17
+ original paper; Mayer/Wiberg bond order in Dense-Evolution-Discovery's
18
+ QM/MM experiments) into a soft relevance map that stays smooth within
19
+ high-affinity regions and is throttled across low-affinity (edge)
20
+ ones. `p`, `lam`, `tau_prop` are the paper's own final values
21
+ (Sec. 4.2).
22
+
23
+ Measured, not assumed (Dense-Evolution-Discovery's
24
+ qmmm_diffuse2seg_propagation_lambda_sweep.py, on the real Mayer
25
+ bond-order graph of a branched-aromatic molecule,
26
+ OCC(c1ccccc1)CCC): at the paper's own lam=1e-5 -- tuned for a dense
27
+ grid of prompts later merged together, not a single isolated seed --
28
+ a stronger real bond (aromatic ring, bond order 1.412) actually
29
+ propagates LESS relevance than a weaker one (alkyl chain, bond order
30
+ 0.991), the opposite of the naive expectation. The ratio crosses 1.0
31
+ only around lam~0.5-1, and only reaches a large expected-direction
32
+ differentiation (ratio 1.49) at lam=10, two orders of magnitude above
33
+ the paper's own calibrated value. This is reported as the real,
34
+ measured lam-dependence of this algorithm in a single-seed molecular
35
+ setting, distinct from the many-prompt image setting it was designed
36
+ and calibrated for -- `lam` is not silently retuned to whatever value
37
+ looks best.
38
+
39
+ Degenerate-case handling: at any node i where g_i = sqrt(sum_j
40
+ A_ij(f_j-f_i)^2) is exactly 0 (every affinity-neighbor already equals
41
+ f_i), the formula's g_i^(p-2) term diverges for p<2. An earlier
42
+ version clamped g_i away from 0 with an epsilon -- the same category
43
+ of shortcut already rejected elsewhere in this project for degenerate
44
+ eigenvalues (see dense_evolution.physics.spectral, Kato's divided-
45
+ difference formula) in favor of the real mathematical limit. Worked
46
+ out directly here: as g_i -> 0, every A_ij-connected f_j equals f_i
47
+ by definition of g_i=0, so the g_i^(p-2)-weighted terms in both the
48
+ numerator and denominator of the update come to dominate and cancel
49
+ to exactly f_i -- i.e. a node already consistent with its whole
50
+ affinity-neighborhood is unchanged by an edge-preserving smoothing
51
+ step, exactly as expected. Implemented as an explicit special case
52
+ below, not an epsilon.
53
+
54
+ Examples
55
+ --------
56
+ >>> import numpy as np
57
+ >>> from dense_evolution.qmmm import propagate_relevance
58
+ >>> affinity = np.array([[0, 1.0, 0], [1.0, 0, 1.0], [0, 1.0, 0]])
59
+ >>> rel = propagate_relevance(affinity, [0], 3, lam=1.0)
60
+ >>> bool(rel[0] > rel[1] > rel[2])
61
+ True
62
+ """
63
+ f0 = np.zeros(n_nodes)
64
+ f0[seed_idx] = 1.0
65
+ f = f0.copy()
66
+ for _ in range(max_iter):
67
+ diff = f[None, :] - f[:, None]
68
+ g = np.sqrt(np.sum(affinity * diff ** 2, axis=1))
69
+ degenerate = g == 0.0
70
+ gp = np.zeros_like(g)
71
+ gp[~degenerate] = g[~degenerate] ** (p - 2)
72
+ gamma = affinity * (gp[:, None] + gp[None, :])
73
+ numerator = lam * f0 + (gamma * f[None, :]).sum(axis=1)
74
+ denominator = lam + gamma.sum(axis=1)
75
+ f_new = np.where(degenerate, f, numerator / denominator)
76
+ if np.sum((f_new - f) ** 2) <= tau_prop:
77
+ f = f_new
78
+ break
79
+ f = f_new
80
+ return f