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,137 @@
1
+ """
2
+ Real QM/MM region-partitioning utilities (Dense-Evolution issue #283).
3
+ Validated on Dense-Evolution-Discovery on two molecules (1-hexanol,
4
+ OCC(c1ccccc1)CCC) before promotion here -- see that repo's
5
+ docs/qmmm_utils.md for the full experiment record.
6
+
7
+ Two real bugs already found and fixed:
8
+
9
+ 1. Hydrogens must always follow their own heavy atom, never be
10
+ independently BFS-expanded -- doing so spuriously cuts terminal C-H
11
+ bonds and leaves an empty MM fragment (found on 1-hexanol).
12
+
13
+ 2. A boundary bond that would cut INTO an aromatic ring must instead pull
14
+ the WHOLE ring into the QM region -- otherwise RDKit's FragmentOnBonds
15
+ leaves one aromatic atom outside its ring, capped with H, which is not
16
+ a valid molecule (`AtomKekulizeException: non-ring atom marked
17
+ aromatic`, found on OCC(c1ccccc1)CCC at radius=2).
18
+
19
+ Deliberately NOT included here, both real negative results with the full
20
+ record in Dense-Evolution-Discovery's docs/qmmm_bond_order_and_embedding.md:
21
+
22
+ - An MMFF94 ONIOM-style mechanical correction that originally looked like
23
+ it halved 1-hexanol's error, but that used a cruder geometry (each
24
+ fragment independently re-embedded by RDKit instead of sliced from one
25
+ shared conformer). Once corrected to use the same shared-conformer
26
+ geometry as everything here, plain truncation is already accurate to
27
+ <0.2 kcal/mol on every radius tested on two different molecules -- and
28
+ applying that correction on top makes it WORSE in every single case.
29
+ The correction was compensating for a geometry artifact of an older
30
+ embedding choice, not for truncation itself.
31
+
32
+ - Electrostatic embedding: five different treatments were tried (plain
33
+ point charge, charge-shifting, Gaussian-smeared over the whole MM
34
+ region, Gaussian-smeared on just the boundary atom, boundary-atom
35
+ charge deletion) and every one made the isodesmic-energy error worse
36
+ than no embedding at all, on the one molecule with a real MM charge to
37
+ embed. The sign convention was independently verified correct (a
38
+ minimal He-atom test: E(+1 charge nearby) < E(isolated) < E(-1 charge
39
+ nearby), exactly as physics requires) -- the failure is not a bug, just
40
+ genuinely unsolved. Plain truncation remains the right default.
41
+
42
+ Requires RDKit (the `qmmm` extra: `pip install dense-evolution[qmmm]`).
43
+ """
44
+ import numpy as np
45
+ from rdkit import Chem
46
+
47
+ ANGSTROM_TO_BOHR = 1.8897259886
48
+ CH_BOND_BOHR = 1.09 * ANGSTROM_TO_BOHR
49
+
50
+
51
+ def partition_qm_mm_region(mol, seed_heavy_atoms, radius):
52
+ """BFS outward from `seed_heavy_atoms` (a set/list of atom indices)
53
+ across `radius` heavy-atom hops. Hydrogens always follow their own
54
+ heavy atom afterward (never independently expanded -- see module
55
+ docstring). Any boundary bond that would cut into an aromatic ring
56
+ pulls that whole ring into the QM region first, iterating until
57
+ stable, before hydrogens are added and the boundary is finalized.
58
+
59
+ Returns (qm_atoms: set[int], boundary_pairs: list[(kept_idx, cut_idx)]),
60
+ one pair per bond crossing the QM/MM boundary, `kept_idx` on the QM
61
+ side.
62
+
63
+ Examples
64
+ --------
65
+ >>> from rdkit import Chem
66
+ >>> from dense_evolution.qmmm import partition_qm_mm_region
67
+ >>> mol = Chem.AddHs(Chem.MolFromSmiles("OCCCCCC"))
68
+ >>> qm_atoms, boundary_pairs = partition_qm_mm_region(mol, {0, 1}, radius=1)
69
+ >>> len(boundary_pairs)
70
+ 1
71
+ """
72
+ qm_heavy = set(seed_heavy_atoms)
73
+ frontier = set(seed_heavy_atoms)
74
+ for _ in range(radius):
75
+ new_frontier = set()
76
+ for idx in frontier:
77
+ for nbr in mol.GetAtomWithIdx(idx).GetNeighbors():
78
+ if nbr.GetAtomicNum() > 1 and nbr.GetIdx() not in qm_heavy:
79
+ new_frontier.add(nbr.GetIdx())
80
+ qm_heavy |= new_frontier
81
+ frontier = new_frontier
82
+
83
+ ring_info = mol.GetRingInfo()
84
+ changed = True
85
+ while changed:
86
+ changed = False
87
+ for bond in mol.GetBonds():
88
+ if not bond.GetIsAromatic():
89
+ continue
90
+ a, b = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()
91
+ if (a in qm_heavy) != (b in qm_heavy):
92
+ for ring in ring_info.AtomRings():
93
+ if a in ring or b in ring:
94
+ newly = set(ring) - qm_heavy
95
+ if newly:
96
+ qm_heavy |= newly
97
+ changed = True
98
+
99
+ qm_atoms = set(qm_heavy)
100
+ for idx in qm_heavy:
101
+ for nbr in mol.GetAtomWithIdx(idx).GetNeighbors():
102
+ if nbr.GetAtomicNum() == 1:
103
+ qm_atoms.add(nbr.GetIdx())
104
+
105
+ raw_boundary = [(b.GetBeginAtomIdx(), b.GetEndAtomIdx()) for b in mol.GetBonds()
106
+ if (b.GetBeginAtomIdx() in qm_atoms) != (b.GetEndAtomIdx() in qm_atoms)]
107
+ boundary_pairs = [(i, j) if i in qm_atoms else (j, i) for i, j in raw_boundary]
108
+ return qm_atoms, boundary_pairs
109
+
110
+
111
+ def sliced_geometry(atomic_numbers, geom_bohr, keep_idx, boundary_pairs):
112
+ """A coordinate SUBSET of one whole-molecule conformer (never an
113
+ independently re-embedded fragment -- that would give unrelated 3D
114
+ structures across fragments, and was the real cause of the MMFF94
115
+ correction's apparent benefit turning out to be a geometry artifact,
116
+ see module docstring). Boundary bonds get a capping H placed along
117
+ the kept->cut bond direction at a standard C-H bond length.
118
+
119
+ Examples
120
+ --------
121
+ >>> import numpy as np
122
+ >>> from dense_evolution.qmmm import sliced_geometry
123
+ >>> numbers = [8, 6, 6]
124
+ >>> geom = np.array([[0.0, 0.0, 0.0], [1.4, 0.0, 0.0], [2.8, 0.0, 0.0]])
125
+ >>> new_numbers, new_geom = sliced_geometry(numbers, geom, {0, 1}, [(1, 2)])
126
+ >>> new_numbers
127
+ [8, 6, 1]
128
+ """
129
+ keep_idx = sorted(keep_idx)
130
+ new_numbers = [atomic_numbers[i] for i in keep_idx]
131
+ new_geom = [geom_bohr[i] for i in keep_idx]
132
+ for kept, cut in boundary_pairs:
133
+ vec = geom_bohr[cut] - geom_bohr[kept]
134
+ vec = vec / np.linalg.norm(vec)
135
+ new_geom.append(geom_bohr[kept] + vec * CH_BOND_BOHR)
136
+ new_numbers.append(1)
137
+ return new_numbers, np.array(new_geom)
@@ -0,0 +1,15 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.circuits.random_circuit as part of fixing the module/
3
+ function name collision (dense_evolution.qft/random_circuit each had a
4
+ function sharing its own module's name -- any code importing the flat
5
+ submodule path directly clobbered the package's re-exported function
6
+ attribute with the module object, e.g. `TypeError: 'module' object is
7
+ not callable`; see prog.txt). Kept so
8
+ `from dense_evolution.random_circuit import random_circuit` (used by
9
+ external consumers, e.g. Dense-Evolution-Discovery) keeps working
10
+ unchanged. Import from dense_evolution.circuits.random_circuit directly
11
+ in new code.
12
+ """
13
+ from dense_evolution.circuits.random_circuit import random_circuit
14
+
15
+ __all__ = ['random_circuit']
@@ -0,0 +1,9 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.circuits.registry as part of the Phase 2 subpackage split
3
+ (see prog.txt). Kept so `from dense_evolution.registry import HAS_JAX`
4
+ (used by external consumers, e.g. Dense-Evolution-Discovery) keeps working
5
+ unchanged. Import from dense_evolution.circuits.registry directly in new code.
6
+ """
7
+ from dense_evolution.circuits.registry import HAS_JAX, NoiseModel, NoiseSpec, QuantumHardwareRegistry
8
+
9
+ __all__ = ['HAS_JAX', 'NoiseModel', 'NoiseSpec', 'QuantumHardwareRegistry']
@@ -0,0 +1,10 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.backends.statevector as part of the Phase 2 subpackage
3
+ split (see prog.txt). Kept so `from dense_evolution.simulator import
4
+ DenseSVSimulator` (used by external consumers, e.g. Dense-Evolution-
5
+ Discovery) keeps working unchanged. Import from
6
+ dense_evolution.backends.statevector directly in new code.
7
+ """
8
+ from dense_evolution.backends.statevector import DenseSVSimulator
9
+
10
+ __all__ = ["DenseSVSimulator"]
@@ -0,0 +1,19 @@
1
+ """Solvers subpackage: algorithms built on top of the backends
2
+ (VQE/autodiff gradients, tight-binding solvers)."""
3
+ from .autodiff import circuit_to_energy_fn
4
+ from .harrison_tb import (
5
+ ELEMENTS, ETA, HBAR2_OVER_M_EV_ANG2,
6
+ hopping_integral, sp3_bond_block, sp3_dimer_hamiltonian, zincblende_hamiltonian,
7
+ )
8
+ from .vhd_tb import (
9
+ Material, MATERIALS, sp3s_star_hamiltonian, direct_gap_at_gamma,
10
+ band_extrema_along_path,
11
+ )
12
+
13
+ __all__ = [
14
+ "circuit_to_energy_fn",
15
+ "ELEMENTS", "ETA", "HBAR2_OVER_M_EV_ANG2",
16
+ "hopping_integral", "sp3_bond_block", "sp3_dimer_hamiltonian", "zincblende_hamiltonian",
17
+ "Material", "MATERIALS", "sp3s_star_hamiltonian", "direct_gap_at_gamma",
18
+ "band_extrema_along_path",
19
+ ]
@@ -0,0 +1,169 @@
1
+ from typing import Callable, Optional, Tuple
2
+
3
+ from ..circuits.parser import QASMCircuit
4
+ from ..circuits.gates import GATE_IDS
5
+ from ..circuits.compiler import QuantumTranspiler
6
+ from ..circuits.registry import HAS_JAX, NoiseModel, NoiseSpec
7
+ from ..config import ensure_x64
8
+
9
+ if HAS_JAX:
10
+ import jax
11
+ import jax.numpy as jnp
12
+ from ..circuits.compiler import _compile_and_run_circuit_jit
13
+ else:
14
+ jnp = None
15
+
16
+
17
+ #: gates that receive a value from theta — must match the n_params count
18
+ #: below exactly, or theta's allocation order desyncs from the template's
19
+ #: injection order. Kept identical to dashboard_core's own list (same
20
+ #: engine, one source of truth).
21
+ _PARAMETRIC_GATES = ('rx', 'ry', 'rz', 'u1', 'p', 'cp', 'crz')
22
+ _TWO_QUBIT_GATES = ('cx', 'cy', 'cz', 'cp', 'crz', 'swap')
23
+
24
+
25
+ def _require_jax():
26
+ if not HAS_JAX:
27
+ raise ImportError(
28
+ "circuit_to_energy_fn requires JAX. "
29
+ "Install it with: pip install dense-evolution[jax]")
30
+ ensure_x64() # this template is always complex128 -- see config.py
31
+
32
+
33
+ def _build_template(circuit: QASMCircuit, n_qubits: int) -> "jnp.ndarray":
34
+ """Builds the (n_ops, 4) float64 [g_id, q1, q2, sentinel] template that
35
+ the energy function injects theta into (-1.0 in the param slot for
36
+ gates whose value comes from theta, patched in via jnp.where inside a
37
+ jax.lax.scan, never a Python float() — that would sever the JAX trace).
38
+
39
+ Structural pass only: build (name, *qubits) tuples (no param values —
40
+ QuantumTranspiler.transpile only inspects gate name/qubit-count, for
41
+ ccx/swap decomposition), transpile once, then look up g_id per gate and
42
+ mark parametric slots with the sentinel. ccx/toffoli decomposes into
43
+ non-parametric gates only, so this never desyncs theta's order.
44
+
45
+ circuit.ops qubits are always plain ints here (QASMCircuit is the
46
+ package's own interchange type, produced by QASMParser.parse and by
47
+ the Qiskit/PennyLane interop bridge alike) — no defensive unwrapping
48
+ of framework-specific qubit/wire objects needed.
49
+ """
50
+ tuples = []
51
+ for op in circuit.ops:
52
+ name = str(op['name']).lower().strip()
53
+ qubits = [int(q) for q in op.get('qubits', [])]
54
+ if not qubits or any(q >= n_qubits for q in qubits):
55
+ continue
56
+ tuples.append((name, *qubits))
57
+
58
+ target = QuantumTranspiler.transpile(tuples)
59
+
60
+ rows = []
61
+ for cmd in target:
62
+ name = cmd[0].lower()
63
+ if name not in GATE_IDS:
64
+ # BUG FIX: used to `continue` here, silently dropping the
65
+ # gate from the traced circuit -- a typo'd or unsupported
66
+ # gate name (e.g. 'u2'/'u3', multi-parameter gates this
67
+ # template's single-param-per-row [g_id, q1, q2, sentinel]
68
+ # shape can't represent -- see gates.py's PARAMETRIC_GATES)
69
+ # gave a silently WRONG energy/gradient instead of an error.
70
+ raise ValueError(
71
+ f"circuit_to_energy_fn: gate '{name}' has no GATE_IDS "
72
+ f"entry, so it can't be represented in this JIT template "
73
+ f"and would otherwise be silently dropped from the "
74
+ f"traced circuit. Multi-parameter gates like 'u2'/'u3' "
75
+ f"aren't supported here (this template carries one "
76
+ f"parameter per row) -- decompose them into rx/ry/rz/cx "
77
+ f"first, or use DenseSVSimulator.run_circuit (the eager "
78
+ f"path) instead, which does support them directly.")
79
+ g_id = float(GATE_IDS[name])
80
+ qubits = cmd[1:]
81
+ sentinel = -1.0 if name in _PARAMETRIC_GATES else 0.0
82
+ if name in _TWO_QUBIT_GATES and len(qubits) >= 2:
83
+ rows.append([g_id, float(qubits[0]), float(qubits[1]), sentinel])
84
+ elif qubits:
85
+ rows.append([g_id, float(qubits[0]), 0.0, sentinel])
86
+
87
+ if not rows:
88
+ return jnp.empty((0, 4), dtype=jnp.float64)
89
+ return jnp.array(rows, dtype=jnp.float64)
90
+
91
+
92
+ def circuit_to_energy_fn(
93
+ circuit: QASMCircuit, n_qubits: int
94
+ ) -> Tuple[Callable, int]:
95
+ """
96
+ Convert a QASMCircuit into a JAX-differentiable energy function.
97
+
98
+ circuit : QASMCircuit — from QASMParser.parse(qasm), or from the
99
+ Qiskit/PennyLane interop bridge (from_qiskit/from_pennylane).
100
+
101
+ Returns (energy_fn, n_params):
102
+ energy_fn(theta, h_matrix, stato_zero=None, noise=None) ->
103
+ (energy, statevector) is a pure JAX function, differentiable w.r.t.
104
+ theta via jax.grad / jax.value_and_grad(energy_fn, argnums=0,
105
+ has_aux=True). stato_zero defaults to |0...0> if not given.
106
+ n_params is the number of parametric gates in the circuit, in the
107
+ same order theta is injected — build theta as an array of that
108
+ length.
109
+
110
+ noise, when given, is a registry.NoiseSpec (a JAX PyTree) applied
111
+ to the statevector right after the circuit and before the energy
112
+ expectation value is computed — natively inside the same traced
113
+ computation as theta, not as an external step the caller has to
114
+ splice in around energy_fn themselves. Because NoiseSpec carries
115
+ its own jax_key as a pytree leaf, the whole thing stays
116
+ jit/grad/vmap-composable with no OS-entropy fallback and no
117
+ external key-management workaround:
118
+
119
+ noise = NoiseSpec(model='depolarizing', p=0.05,
120
+ jax_key=jax.random.PRNGKey(0))
121
+ energy, sv = energy_fn(theta, h_matrix, noise=noise)
122
+
123
+ This is the same engine dashboard_core.py's real VQE gradient uses
124
+ internally (verified against finite differences, ~1e-11 agreement) —
125
+ exposed here as public API so it's reachable without reading
126
+ dashboard_core.py, and so circuits imported via from_qiskit/
127
+ from_pennylane (which are NOT differentiable on their own — see
128
+ run_pennylane_circuit's docstring) have a real way to become
129
+ differentiable instead of just a documented dead end.
130
+ """
131
+ _require_jax()
132
+ template = _build_template(circuit, n_qubits)
133
+ n_params = sum(1 for op in circuit.ops
134
+ if str(op['name']).lower().strip() in _PARAMETRIC_GATES)
135
+
136
+ def energy_fn(theta, h_matrix, stato_zero: Optional["jnp.ndarray"] = None,
137
+ noise: Optional["NoiseSpec"] = None):
138
+ if stato_zero is None:
139
+ stato_zero = jnp.zeros(2 ** n_qubits, dtype=jnp.complex128).at[0].set(1.0)
140
+
141
+ if n_params == 0:
142
+ # No parametric gates -> no sentinel (-1.0) rows in template, so
143
+ # patch_and_apply below would never take its is_param branch.
144
+ # Skip the scan entirely rather than index into an empty theta
145
+ # array during tracing (n_params is a static Python int, fixed
146
+ # at circuit_to_energy_fn() call time, so this branch is
147
+ # resolved before any tracing happens — not a jax.lax.cond).
148
+ sv = _compile_and_run_circuit_jit(stato_zero, template)
149
+ else:
150
+ def patch_and_apply(carry, op):
151
+ idx = carry
152
+ is_param = op[3] == -1.0
153
+ final_p = jnp.where(is_param, theta[idx], op[3])
154
+ next_idx = jnp.where(is_param, idx + jnp.int32(1), idx)
155
+ return next_idx, jnp.array([op[0], op[1], op[2], final_p], dtype=jnp.float64)
156
+
157
+ _, patched_ops = jax.lax.scan(patch_and_apply, jnp.int32(0), template)
158
+ sv = _compile_and_run_circuit_jit(stato_zero, patched_ops)
159
+
160
+ if noise is not None:
161
+ sv = NoiseModel.apply_to_sv(
162
+ sv, n_qubits, model=noise.model, p=noise.p,
163
+ jax_key=noise.jax_key, qubits=list(noise.qubits) if noise.qubits is not None else None,
164
+ )
165
+
166
+ energy = jnp.real(jnp.vdot(sv, h_matrix @ sv))
167
+ return energy, sv
168
+
169
+ return energy_fn, n_params
@@ -0,0 +1,189 @@
1
+ """
2
+ Harrison empirical tight-binding parameters -- builds an sp3 tight-binding
3
+ Hamiltonian for a cluster of real atoms directly from published atomic term
4
+ values and the universal bond-scaling law, with no SCF/DFT and no external
5
+ quantum-chemistry dependency (PySCF, OpenFermion).
6
+
7
+ Source: Walter A. Harrison, "Electronic Structure and the Properties of
8
+ Solids" (Dover reprint). Atomic term values (ELEMENTS) and the universal
9
+ eta coefficients (ETA) below are transcribed from that book's Solid State
10
+ Table, cross-checked against the numbers in jarvist/HarrisonSolidStateTable.jl
11
+ (github.com/jarvist/HarrisonSolidStateTable.jl), a Julia implementation of
12
+ the same table. Only elements with both an s and a p term value in that
13
+ table are included here (the "simple atom" sp3 entries Harrison uses for
14
+ tetrahedral semiconductors); d-block entries are left out since Harrison's
15
+ own table flags them "not well checked".
16
+
17
+ Sign convention: ELEMENTS stores true orbital energies (negative, eV --
18
+ bound states below vacuum), i.e. the negative of the magnitudes printed in
19
+ Harrison's table.
20
+
21
+ Two-center bond integrals follow Harrison's universal scaling law:
22
+ V_ll'm = eta_ll'm * hbar^2 / (m_e * d^2)
23
+ with d the bond length in Angstrom and hbar^2/m_e = 7.62 eV*Angstrom^2 (the
24
+ standard constant quoted alongside this law). eta_ssσ/spσ/ppσ/ppπ are
25
+ dimensionless and materials-independent -- the same four numbers apply to
26
+ every element pair.
27
+
28
+ Slater-Koster sp3 matrix elements (sp3_bond_block) follow the standard
29
+ 1954 Slater-Koster table for an (s, px, py, pz) basis.
30
+
31
+ zincblende_hamiltonian builds the periodic Bloch Hamiltonian for a
32
+ two-atom zinc-blende basis (nearest-neighbor sp3, 4 bonds per atom) --
33
+ validated against real GaAs (a=5.6533 Angstrom): computed direct gap
34
+ at Gamma is 2.91 eV vs. the experimental 1.42 eV, roughly 2x too
35
+ large. This is a known, documented limitation of Harrison's universal
36
+ (materials-independent) parameter set on polar/ionic compound
37
+ semiconductors -- not a bug here -- since it uses no per-material
38
+ fitting and omits d-orbitals. Useful as a fast, dependency-free
39
+ qualitative estimate; not a substitute for this project's DFT-derived
40
+ GaAs parameters where quantitative accuracy matters.
41
+ """
42
+ import numpy as np
43
+
44
+ __all__ = [
45
+ 'ELEMENTS', 'ETA', 'HBAR2_OVER_M_EV_ANG2',
46
+ 'hopping_integral', 'sp3_bond_block', 'sp3_dimer_hamiltonian',
47
+ 'zincblende_hamiltonian',
48
+ ]
49
+
50
+ # name -> (Z, eps_s [eV], eps_p [eV], atomic mass [amu])
51
+ # Values transcribed from Harrison's Solid State Table (magnitudes in the
52
+ # book are positive "term values"; stored here as negative orbital energies).
53
+ ELEMENTS = {
54
+ 'Be': dict(Z=4, eps_s=-8.17, eps_p=-4.14, mass=9.01),
55
+ 'B': dict(Z=5, eps_s=-12.54, eps_p=-6.64, mass=10.81),
56
+ 'C': dict(Z=6, eps_s=-17.52, eps_p=-8.97, mass=12.01),
57
+ 'N': dict(Z=7, eps_s=-23.04, eps_p=-11.47, mass=14.01),
58
+ 'O': dict(Z=8, eps_s=-29.14, eps_p=-14.13, mass=16.00),
59
+ 'Mg': dict(Z=12, eps_s=-6.86, eps_p=-2.99, mass=24.31),
60
+ 'Si': dict(Z=14, eps_s=-13.55, eps_p=-6.52, mass=28.09),
61
+ 'P': dict(Z=15, eps_s=-17.10, eps_p=-8.33, mass=30.97),
62
+ 'S': dict(Z=16, eps_s=-20.80, eps_p=-10.27, mass=32.06),
63
+ 'Cu': dict(Z=29, eps_s=-6.92, eps_p=-1.83, mass=63.54),
64
+ 'Zn': dict(Z=30, eps_s=-8.40, eps_p=-3.38, mass=65.37),
65
+ 'Ga': dict(Z=31, eps_s=-11.37, eps_p=-4.90, mass=69.82),
66
+ 'Ge': dict(Z=32, eps_s=-14.38, eps_p=-6.36, mass=72.59),
67
+ 'As': dict(Z=33, eps_s=-17.33, eps_p=-7.91, mass=74.92),
68
+ 'Se': dict(Z=34, eps_s=-20.32, eps_p=-9.53, mass=78.96),
69
+ 'Sn': dict(Z=50, eps_s=-12.50, eps_p=-5.94, mass=118.7),
70
+ 'I': dict(Z=53, eps_s=-19.42, eps_p=-9.97, mass=126.9),
71
+ 'Pb': dict(Z=82, eps_s=-12.07, eps_p=-5.77, mass=207.2),
72
+ }
73
+
74
+ # Universal Harrison interatomic matrix element coefficients (dimensionless),
75
+ # from the Dover reprint's "top right table". Materials-independent.
76
+ ETA = dict(ss_sigma=-1.40, sp_sigma=1.84, pp_sigma=3.24, pp_pi=-0.81)
77
+
78
+ # hbar^2 / m_e in eV*Angstrom^2, the constant in Harrison's d^-2 scaling law.
79
+ HBAR2_OVER_M_EV_ANG2 = 7.62
80
+
81
+
82
+ def hopping_integral(eta, d_angstrom):
83
+ """V_ll'm = eta * hbar^2/(m_e d^2) [eV], for bond length d in Angstrom."""
84
+ if d_angstrom <= 0:
85
+ raise ValueError(f"bond length must be positive, got {d_angstrom}")
86
+ return eta * HBAR2_OVER_M_EV_ANG2 / d_angstrom ** 2
87
+
88
+
89
+ def sp3_bond_block(l, m, n, d_angstrom, eta=ETA):
90
+ """
91
+ 4x4 Slater-Koster hopping block <A, {s,px,py,pz}| H |B, {s,px,py,pz}>
92
+ for a bond from atom A to atom B along direction cosines (l, m, n)
93
+ (unit vector, l^2+m^2+n^2 = 1) and bond length d_angstrom.
94
+
95
+ Basis order: s, px, py, pz. Standard Slater-Koster (1954) table.
96
+ """
97
+ norm = l * l + m * m + n * n
98
+ if not np.isclose(norm, 1.0, atol=1e-6):
99
+ raise ValueError(f"(l, m, n) must be a unit vector, got norm={norm}")
100
+
101
+ Vssσ = hopping_integral(eta['ss_sigma'], d_angstrom)
102
+ Vspσ = hopping_integral(eta['sp_sigma'], d_angstrom)
103
+ Vppσ = hopping_integral(eta['pp_sigma'], d_angstrom)
104
+ Vppπ = hopping_integral(eta['pp_pi'], d_angstrom)
105
+
106
+ block = np.zeros((4, 4), dtype=np.complex128)
107
+ block[0, 0] = Vssσ
108
+ block[0, 1], block[0, 2], block[0, 3] = l * Vspσ, m * Vspσ, n * Vspσ
109
+ block[1, 0], block[2, 0], block[3, 0] = -l * Vspσ, -m * Vspσ, -n * Vspσ
110
+
111
+ block[1, 1] = l * l * Vppσ + (1 - l * l) * Vppπ
112
+ block[2, 2] = m * m * Vppσ + (1 - m * m) * Vppπ
113
+ block[3, 3] = n * n * Vppσ + (1 - n * n) * Vppπ
114
+
115
+ block[1, 2] = block[2, 1] = l * m * (Vppσ - Vppπ)
116
+ block[2, 3] = block[3, 2] = m * n * (Vppσ - Vppπ)
117
+ block[1, 3] = block[3, 1] = l * n * (Vppσ - Vppπ)
118
+ return block
119
+
120
+
121
+ def sp3_dimer_hamiltonian(element_a, element_b, bond_length_angstrom,
122
+ direction=(0.0, 0.0, 1.0), eta=ETA):
123
+ """
124
+ 8x8 sp3 tight-binding Hamiltonian for a 2-atom A-B cluster (one bond),
125
+ basis order [A:s,px,py,pz, B:s,px,py,pz]. On-site blocks are each
126
+ atom's diagonal (eps_s, eps_p, eps_p, eps_p); the A-B off-diagonal
127
+ block is sp3_bond_block along `direction` (unit vector, default: bond
128
+ along z), Hermitian-conjugated into the B-A block.
129
+
130
+ This is a minimal, directly checkable sanity case (bonding/antibonding
131
+ sp3 splitting), not a periodic-solid band structure.
132
+ """
133
+ for name in (element_a, element_b):
134
+ if name not in ELEMENTS:
135
+ raise ValueError(f"no Harrison sp term values for element {name!r}; "
136
+ f"available: {sorted(ELEMENTS)}")
137
+ l, m, n = direction
138
+ a, b = ELEMENTS[element_a], ELEMENTS[element_b]
139
+
140
+ H = np.zeros((8, 8), dtype=np.complex128)
141
+ H[0, 0] = a['eps_s']
142
+ H[1, 1] = H[2, 2] = H[3, 3] = a['eps_p']
143
+ H[4, 4] = b['eps_s']
144
+ H[5, 5] = H[6, 6] = H[7, 7] = b['eps_p']
145
+
146
+ hop = sp3_bond_block(l, m, n, bond_length_angstrom, eta=eta)
147
+ H[0:4, 4:8] = hop
148
+ H[4:8, 0:4] = hop.conj().T
149
+ return H
150
+
151
+
152
+ def zincblende_hamiltonian(k, cation, anion, lattice_constant_angstrom, eta=ETA):
153
+ """
154
+ 8x8 Bloch Hamiltonian for a zinc-blende crystal's two-atom basis
155
+ (cation at (0,0,0), anion at (1/4,1/4,1/4) of the conventional cubic
156
+ cell), sp3 nearest-neighbor tight-binding, at crystal momentum k
157
+ (Cartesian, 1/Angstrom -- e.g. Gamma=(0,0,0)).
158
+
159
+ Basis order [cation:s,px,py,pz, anion:s,px,py,pz]. The four
160
+ cation->anion nearest-neighbor bonds are the standard zinc-blende
161
+ tetrahedral set (lattice_constant/4)*(1,1,1), (1,-1,-1), (-1,1,-1),
162
+ (-1,-1,1); each contributes sp3_bond_block(...) weighted by its
163
+ Bloch phase exp(i k . d), summed into the off-diagonal block.
164
+ """
165
+ for name in (cation, anion):
166
+ if name not in ELEMENTS:
167
+ raise ValueError(f"no Harrison sp term values for element {name!r}; "
168
+ f"available: {sorted(ELEMENTS)}")
169
+ k = np.asarray(k, dtype=float)
170
+ d_vectors = (lattice_constant_angstrom / 4) * np.array([
171
+ [1, 1, 1], [1, -1, -1], [-1, 1, -1], [-1, -1, 1],
172
+ ], dtype=float)
173
+ bond_length = np.linalg.norm(d_vectors[0])
174
+
175
+ T = np.zeros((4, 4), dtype=np.complex128)
176
+ for d in d_vectors:
177
+ l, m, n = d / bond_length
178
+ phase = np.exp(1j * np.dot(k, d))
179
+ T += phase * sp3_bond_block(l, m, n, bond_length, eta=eta)
180
+
181
+ c, a = ELEMENTS[cation], ELEMENTS[anion]
182
+ H = np.zeros((8, 8), dtype=np.complex128)
183
+ H[0, 0] = c['eps_s']
184
+ H[1, 1] = H[2, 2] = H[3, 3] = c['eps_p']
185
+ H[4, 4] = a['eps_s']
186
+ H[5, 5] = H[6, 6] = H[7, 7] = a['eps_p']
187
+ H[0:4, 4:8] = T
188
+ H[4:8, 0:4] = T.conj().T
189
+ return H