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
|
@@ -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
|