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.
- dashboard_core/__init__.py +115 -0
- dashboard_core/_gate_tables.py +30 -0
- dashboard_core/band_structure.py +71 -0
- dashboard_core/circuit_builder_component.py +232 -0
- dashboard_core/circuit_diagram.py +216 -0
- dashboard_core/crypto_protocols.py +77 -0
- dashboard_core/engine.py +326 -0
- dashboard_core/graphical_builder.py +114 -0
- dashboard_core/hamiltonians.py +593 -0
- dashboard_core/mass_decomposition_tool.py +47 -0
- dashboard_core/mitigation.py +343 -0
- dashboard_core/native_hf_diagnostics.py +62 -0
- dashboard_core/noise_tools.py +125 -0
- dashboard_core/qasm_library.py +233 -0
- dashboard_core/qmmm.py +16 -0
- dashboard_core/rag_tool.py +45 -0
- dashboard_core/state_visuals.py +288 -0
- dashboard_core/system_limits.py +60 -0
- dashboard_core/vector_healing.py +102 -0
- dashboard_core/visuals.py +158 -0
- dashboard_core/vqe.py +533 -0
- dashboard_core/wormhole.py +580 -0
- dense_evolution/__init__.py +114 -0
- dense_evolution/autodiff.py +10 -0
- dense_evolution/backends/__init__.py +5 -0
- dense_evolution/backends/chunk/__init__.py +37 -0
- dense_evolution/backends/chunk/_engine_imports.py +57 -0
- dense_evolution/backends/chunk/circuit_chunker.py +55 -0
- dense_evolution/backends/chunk/core.py +432 -0
- dense_evolution/backends/chunk/disk_overflow.py +232 -0
- dense_evolution/backends/chunk/geometry.py +95 -0
- dense_evolution/backends/chunk/guard.py +190 -0
- dense_evolution/backends/chunk/kernels.py +531 -0
- dense_evolution/backends/mps.py +1569 -0
- dense_evolution/backends/statevector.py +616 -0
- dense_evolution/chunk.py +25 -0
- dense_evolution/circuits/__init__.py +20 -0
- dense_evolution/circuits/compiler.py +488 -0
- dense_evolution/circuits/diagram.py +94 -0
- dense_evolution/circuits/gates.py +91 -0
- dense_evolution/circuits/parser.py +632 -0
- dense_evolution/circuits/qft.py +66 -0
- dense_evolution/circuits/random_circuit.py +85 -0
- dense_evolution/circuits/registry.py +74 -0
- dense_evolution/circuits/topology.py +79 -0
- dense_evolution/circuits/trotter.py +265 -0
- dense_evolution/circuits/uccsd.py +275 -0
- dense_evolution/cli.py +199 -0
- dense_evolution/compiler.py +9 -0
- dense_evolution/config.py +49 -0
- dense_evolution/drawing.py +10 -0
- dense_evolution/entropy.py +9 -0
- dense_evolution/fermions.py +9 -0
- dense_evolution/gates.py +9 -0
- dense_evolution/harrison_tb.py +16 -0
- dense_evolution/healing.py +18 -0
- dense_evolution/interop/__init__.py +18 -0
- dense_evolution/interop/qiskit_pennylane.py +406 -0
- dense_evolution/measurement.py +10 -0
- dense_evolution/mitigation/__init__.py +54 -0
- dense_evolution/mitigation/healing.py +215 -0
- dense_evolution/mitigation/kl_divergence.py +93 -0
- dense_evolution/mitigation/magic_entropy.py +163 -0
- dense_evolution/mitigation/magic_entropy_shadows.py +262 -0
- dense_evolution/mitigation/renyi.py +168 -0
- dense_evolution/mitigation/stabilizer_renyi_entropy.py +103 -0
- dense_evolution/mitigation/zne.py +990 -0
- dense_evolution/mps.py +9 -0
- dense_evolution/native_hf/__init__.py +26 -0
- dense_evolution/native_hf/_libcint/LICENSE-libcint +10 -0
- dense_evolution/native_hf/_libcint/libdecint.dll +0 -0
- dense_evolution/native_hf/assembly.py +304 -0
- dense_evolution/native_hf/basis.py +117 -0
- dense_evolution/native_hf/boys.py +35 -0
- dense_evolution/native_hf/bridge.py +112 -0
- dense_evolution/native_hf/cartesian.py +64 -0
- dense_evolution/native_hf/coulomb.py +196 -0
- dense_evolution/native_hf/differentiable.py +53 -0
- dense_evolution/native_hf/gaussians.py +79 -0
- dense_evolution/native_hf/kinetic.py +52 -0
- dense_evolution/native_hf/libcint_bridge.py +167 -0
- dense_evolution/native_hf/overlap.py +91 -0
- dense_evolution/native_hf/scf.py +404 -0
- dense_evolution/noise/__init__.py +79 -0
- dense_evolution/noise/coherent_attack.py +264 -0
- dense_evolution/noise/cosmic_ray.py +61 -0
- dense_evolution/noise/density_matrix_channels.py +78 -0
- dense_evolution/noise/differentiable.py +66 -0
- dense_evolution/noise/kraus/__init__.py +6 -0
- dense_evolution/noise/kraus/amplitude_damping.py +47 -0
- dense_evolution/noise/kraus/bitflip.py +22 -0
- dense_evolution/noise/kraus/combined.py +16 -0
- dense_evolution/noise/kraus/depolarizing.py +47 -0
- dense_evolution/noise/kraus/ideal.py +10 -0
- dense_evolution/noise/kraus/phaseflip.py +21 -0
- dense_evolution/noise/kraus_channels.py +285 -0
- dense_evolution/noise/oscillating.py +32 -0
- dense_evolution/noise/pink.py +80 -0
- dense_evolution/observables.py +11 -0
- dense_evolution/parser.py +9 -0
- dense_evolution/physics/__init__.py +27 -0
- dense_evolution/physics/entropy.py +161 -0
- dense_evolution/physics/fermions.py +322 -0
- dense_evolution/physics/observables.py +523 -0
- dense_evolution/physics/qec.py +1113 -0
- dense_evolution/physics/spectral.py +143 -0
- dense_evolution/physics/states.py +43 -0
- dense_evolution/protocols/__init__.py +27 -0
- dense_evolution/protocols/bb84.py +133 -0
- dense_evolution/protocols/di_qkd_ghz.py +199 -0
- dense_evolution/protocols/dicka_protocol2.py +124 -0
- dense_evolution/qec.py +20 -0
- dense_evolution/qft.py +9 -0
- dense_evolution/qmmm/__init__.py +13 -0
- dense_evolution/qmmm/ase_bridge.py +97 -0
- dense_evolution/qmmm/forces.py +388 -0
- dense_evolution/qmmm/propagation.py +80 -0
- dense_evolution/qmmm/region.py +137 -0
- dense_evolution/random_circuit.py +15 -0
- dense_evolution/registry.py +9 -0
- dense_evolution/simulator.py +10 -0
- dense_evolution/solvers/__init__.py +19 -0
- dense_evolution/solvers/autodiff.py +169 -0
- dense_evolution/solvers/harrison_tb.py +189 -0
- dense_evolution/solvers/vhd_tb.py +187 -0
- dense_evolution/states.py +9 -0
- dense_evolution/topology.py +9 -0
- dense_evolution/trotter.py +9 -0
- dense_evolution/utils/__init__.py +13 -0
- dense_evolution/utils/drawing.py +101 -0
- dense_evolution/utils/mass_decomposition.py +246 -0
- dense_evolution/utils/measurement.py +94 -0
- dense_evolution/vhd_tb.py +16 -0
- dense_evolution-8.3.0.dist-info/METADATA +366 -0
- dense_evolution-8.3.0.dist-info/RECORD +165 -0
- dense_evolution-8.3.0.dist-info/WHEEL +5 -0
- dense_evolution-8.3.0.dist-info/entry_points.txt +2 -0
- dense_evolution-8.3.0.dist-info/licenses/license.md +58 -0
- dense_evolution-8.3.0.dist-info/top_level.txt +5 -0
- ia_utils/__init__.py +0 -0
- ia_utils/adversarial_vector_attack.py +196 -0
- ia_utils/rag.py +288 -0
- ia_utils/vector_healing.py +399 -0
- local_site/__init__.py +0 -0
- local_site/app/__init__.py +0 -0
- local_site/app/server.py +1009 -0
- mcp_server/__init__.py +0 -0
- mcp_server/client.py +324 -0
- mcp_server/config.py +32 -0
- mcp_server/models.py +347 -0
- mcp_server/molecules.py +71 -0
- mcp_server/server.py +119 -0
- mcp_server/tools/__init__.py +0 -0
- mcp_server/tools/chemistry_tools.py +225 -0
- mcp_server/tools/circuit_tools.py +83 -0
- mcp_server/tools/crypto_tools.py +66 -0
- mcp_server/tools/mitigation_tools.py +81 -0
- mcp_server/tools/noise_tools.py +60 -0
- mcp_server/tools/retrieval_tools.py +44 -0
- mcp_server/tools/system_tools.py +149 -0
- mcp_server/tools/wormhole_tools.py +142 -0
- mcp_server/utils/__init__.py +0 -0
- mcp_server/utils/cache.py +55 -0
- mcp_server/utils/images.py +67 -0
- 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
|