Skip to content

Dashboard Core — Hamiltonians

Real molecular Hamiltonians, built on demand from actual atomic geometry — no fabricated or hand-picked coefficients. _get_hamiltonian dispatches per molecule: PennyLane's own qchem pipeline (Hartree-Fock + Jordan-Wigner) when every requested element is in PennyLane's bundled STO-3G table (H2/HeH+/H3+/LiH/H2O), or Dense-Evolution's own native Hartree-Fock engine otherwise — currently backing Si2 (real equilibrium R = 2.184 Å, Balamurugan & Prasad, arXiv:cond-mat/0108426), whose elements PennyLane's dhf can't reach at all (its table stops at Ne). Both paths hand off to the same PennyLane fermionic_observable + jordan_wigner step, so the qubit mapping is identical either way. Backs Composer's molecular-energy panel, dashboard_core.vqe, and dashboard_core.qmmm.

Honest caveat on Si2: with only 4 active electrons/orbitals (all 20 core electrons frozen across both atoms), this active space is too small to reproduce 2.184 Å as its own energy minimum — a direct 10-point bond-length scan found the minimum at the 1.9 Å edge of the scanned range, not an interior point. Stated in the catalog entry's own comment rather than silently picking a geometry that flatters the active-space choice.

hamiltonians

Real molecular Hamiltonians, built on demand from actual atomic geometry via PennyLane's qchem module (Hartree-Fock + Jordan-Wigner fermion-to- qubit mapping, method='dhf' -- native to PennyLane, no PySCF/OpenFermion dependency needed).

Ported from feature/streamlit-dashboard (git branch), where this was built and verified against known values before the dashboard rebuild. Only the real molecular catalog comes along here -- the old diagonal "toy model" library and the VQE optimization loop stay out for now (kept minimal on purpose, brought back separately if/when needed).

For elements PennyLane's own bundled STO-3G table doesn't cover (row 3+, e.g. Silicon), this falls back to dense_evolution.native_hf -- a from-scratch, jax-vmap-vectorized Hartree-Fock engine (Obara-Saika integrals, Roothaan-Hall SCF) that sources basis-set data from basis_set_exchange instead, so any element it has STO-3G parameters for works. Only the Hartree-Fock/integral stage is native; the resulting converged result is still handed to PennyLane's own fermionic_observable + jordan_wigner for the qubit mapping (see native_hf/bridge.py), since that stage is already fast and well-tested.

linear_chain_geometry

linear_chain_geometry(
    n_atoms: int, bond_length_angstrom: float
)

N atoms on a line, each bond_length_angstrom apart -- the real, general shape behind every diatomic entry in the catalog (H2, HeH+, LiH), extended to any atom count.

Source code in tools/dashboard_core/hamiltonians.py
def linear_chain_geometry(n_atoms: int, bond_length_angstrom: float):
    """N atoms on a line, each bond_length_angstrom apart -- the real,
    general shape behind every diatomic entry in the catalog (H2, HeH+,
    LiH), extended to any atom count."""
    if n_atoms < 1:
        raise ValueError("linear_chain_geometry needs at least 1 atom")
    return np.array([[0.0, 0.0, i * bond_length_angstrom] for i in range(n_atoms)])

ring_geometry

ring_geometry(n_atoms: int, bond_length_angstrom: float)

N atoms on a regular polygon (equal bond_length_angstrom between neighbors), circumradius R = bond_length / (2*sin(pi/n)) -- standard regular-polygon geometry. At n_atoms=3 this is exactly an equilateral triangle -- the same real D3h geometry H3+'s catalog entry uses, just generalized to any ring size (still only meaningful up to whatever qubit count this simulator's exact diagonalization / VQE range can handle -- this function itself has no such limit, the caller does).

Source code in tools/dashboard_core/hamiltonians.py
def ring_geometry(n_atoms: int, bond_length_angstrom: float):
    """N atoms on a regular polygon (equal bond_length_angstrom between
    neighbors), circumradius R = bond_length / (2*sin(pi/n)) -- standard
    regular-polygon geometry. At n_atoms=3 this is exactly an equilateral
    triangle -- the same real D3h geometry H3+'s catalog entry uses, just
    generalized to any ring size (still only meaningful up to whatever
    qubit count this simulator's exact diagonalization / VQE range can
    handle -- this function itself has no such limit, the caller does)."""
    if n_atoms < 3:
        raise ValueError("ring_geometry needs at least 3 atoms")
    R = bond_length_angstrom / (2 * np.sin(np.pi / n_atoms))
    angles = 2 * np.pi * np.arange(n_atoms) / n_atoms
    return np.array([[R * np.cos(a), R * np.sin(a), 0.0] for a in angles])

build_molecular_hamiltonian

build_molecular_hamiltonian(
    symbols,
    geometry,
    charge: int = 0,
    mapping: str = "jordan_wigner",
    active_electrons=None,
    active_orbitals=None,
)

Runs real Hartree-Fock + fermion-to-qubit mapping (PennyLane qchem) on the given geometry and returns (H_dense, n_qubits). The eigenvalue spectrum (and therefore the ground-state energy) is mapping-invariant -- Jordan-Wigner and Bravyi-Kitaev represent the identical physical Hamiltonian in a different qubit basis -- so this only changes which qubit operators appear, never the energies this function's callers report. Cached: Hartree-Fock isn't free, and the UI can re-request the same molecule repeatedly.

The dense matrix itself is built by this project's own dense_evolution.pauli_hamiltonian_to_matrix from PennyLane's real Pauli decomposition, not qml.matrix() -- verified to match qml.matrix exactly (same ground-state energy, same matrix, atol=1e-8) for every catalog molecule.

Source code in tools/dashboard_core/hamiltonians.py
def build_molecular_hamiltonian(symbols, geometry, charge: int = 0, mapping: str = "jordan_wigner",
                                 active_electrons=None, active_orbitals=None):
    """Runs real Hartree-Fock + fermion-to-qubit mapping (PennyLane
    qchem) on the given geometry and returns (H_dense, n_qubits). The
    eigenvalue spectrum (and therefore the ground-state energy) is
    mapping-invariant -- Jordan-Wigner and Bravyi-Kitaev represent the
    identical physical Hamiltonian in a different qubit basis -- so this
    only changes which qubit operators appear, never the energies this
    function's callers report. Cached: Hartree-Fock isn't free, and the
    UI can re-request the same molecule repeatedly.

    The dense matrix itself is built by this project's own
    dense_evolution.pauli_hamiltonian_to_matrix from PennyLane's real
    Pauli decomposition, not qml.matrix() -- verified to match qml.matrix
    exactly (same ground-state energy, same matrix, atol=1e-8) for every
    catalog molecule."""
    H, n_qubits = _get_hamiltonian(symbols, geometry, charge, mapping, active_electrons, active_orbitals)

    dense_key = (tuple(symbols), tuple(map(tuple, np.asarray(geometry).round(10))), charge, mapping,
                 active_electrons, active_orbitals)
    if dense_key in _dense_hamiltonian_cache:
        return _dense_hamiltonian_cache[dense_key]

    # H_dense is dim x dim (dim = 2**n_qubits), not just dim, and its only
    # consumer (ground_state_energy) runs a full dense np.linalg.eigvalsh
    # on it -- LAPACK's own eigh workspace needs comparable scratch memory
    # on top of the matrix itself, and the geometry generators in the
    # Composer's UI (linear_chain_geometry/ring_geometry) let a visitor
    # build an arbitrarily long chain, with no smaller natural ceiling than
    # whatever PennyLane's own Hartree-Fock step tolerates. Same real
    # anti-OOM guard as dashboard_core.engine.run_circuit_from_qasm and
    # mitigation.py's ZNE panels, sized for what this actually allocates
    # (the x3 covers the matrix + its eigh scratch space + the cached copy
    # this function stores in _dense_hamiltonian_cache).
    dim = 2 ** n_qubits
    required_mb = dim * dim * 16 / 1e6 * 3
    de.chunk.SafeMemoryGuard().check_allocation(required_mb, context=f"{n_qubits}-qubit molecular Hamiltonian")

    terms = _pennylane_hamiltonian_to_pauli_terms(H, n_qubits)
    H_dense = de.pauli_hamiltonian_to_matrix(terms, n_qubits)
    result = (H_dense, n_qubits)
    _dense_hamiltonian_cache[dense_key] = result
    return result

get_molecule_n_qubits

get_molecule_n_qubits(
    symbols,
    geometry,
    charge=0,
    mapping="jordan_wigner",
    active_electrons=None,
    active_orbitals=None,
)

The qubit count a molecule's real Hamiltonian needs, without paying for a dense matrix build -- cheap enough to call for every catalog entry when just listing what's available.

Source code in tools/dashboard_core/hamiltonians.py
def get_molecule_n_qubits(symbols, geometry, charge=0, mapping="jordan_wigner",
                           active_electrons=None, active_orbitals=None):
    """The qubit count a molecule's real Hamiltonian needs, without
    paying for a dense matrix build -- cheap enough to call for every
    catalog entry when just listing what's available."""
    _, n_qubits = _get_hamiltonian(symbols, geometry, charge, mapping, active_electrons, active_orbitals)
    return n_qubits

get_all_molecules

get_all_molecules(catalog=None, mapping='jordan_wigner')

Every catalog molecule, each annotated with its real qubit count under the given mapping -- unfiltered, so the UI can always show the whole catalog and let the molecule choice drive the circuit's qubit count (not the other way around).

Source code in tools/dashboard_core/hamiltonians.py
def get_all_molecules(catalog=None, mapping="jordan_wigner"):
    """Every catalog molecule, each annotated with its real qubit count
    under the given mapping -- unfiltered, so the UI can always show the
    whole catalog and let the molecule choice drive the circuit's qubit
    count (not the other way around)."""
    catalog = catalog if catalog is not None else MOLECULE_CATALOG
    out = {}
    for name, spec in catalog.items():
        geometry = spec["geometry"]() if callable(spec["geometry"]) else spec["geometry"]
        n_qubits = get_molecule_n_qubits(
            spec["symbols"], geometry, spec["charge"], mapping=mapping,
            active_electrons=spec.get("active_electrons"), active_orbitals=spec.get("active_orbitals"),
        )
        out[name] = {
            "symbols": spec["symbols"],
            "geometry": np.asarray(geometry).tolist(),
            "charge": spec["charge"],
            "n_qubits": n_qubits,
        }
    return out

get_compatible_molecules

get_compatible_molecules(
    n_qubits, catalog=None, mapping="jordan_wigner"
)

Filters MOLECULE_CATALOG down to molecules whose real Hamiltonian needs exactly n_qubits. Kept for callers that want a qubit-filtered view; the main catalog UI uses get_all_molecules instead so every molecule is always visible.

Source code in tools/dashboard_core/hamiltonians.py
def get_compatible_molecules(n_qubits, catalog=None, mapping="jordan_wigner"):
    """Filters MOLECULE_CATALOG down to molecules whose real Hamiltonian
    needs exactly n_qubits. Kept for callers that want a qubit-filtered
    view; the main catalog UI uses get_all_molecules instead so every
    molecule is always visible."""
    catalog = catalog if catalog is not None else MOLECULE_CATALOG
    if n_qubits is None or n_qubits <= 0:
        return {}
    all_molecules = get_all_molecules(catalog, mapping=mapping)
    return {name: catalog[name] for name, info in all_molecules.items() if info["n_qubits"] == n_qubits}

get_molecular_hamiltonian_matrix

get_molecular_hamiltonian_matrix(
    name, catalog=None, mapping="jordan_wigner"
)

Resolves a MOLECULE_CATALOG entry by name to its (cached) dense Hermitian Hamiltonian matrix, under the given fermion-to-qubit mapping (spectrum is identical either way, see build_molecular_hamiltonian).

Source code in tools/dashboard_core/hamiltonians.py
def get_molecular_hamiltonian_matrix(name, catalog=None, mapping="jordan_wigner"):
    """Resolves a MOLECULE_CATALOG entry by name to its (cached) dense
    Hermitian Hamiltonian matrix, under the given fermion-to-qubit
    mapping (spectrum is identical either way, see build_molecular_hamiltonian)."""
    catalog = catalog if catalog is not None else MOLECULE_CATALOG
    spec = catalog[name]
    geometry = spec["geometry"]() if callable(spec["geometry"]) else spec["geometry"]
    H_dense, _ = build_molecular_hamiltonian(
        spec["symbols"], geometry, spec["charge"], mapping=mapping,
        active_electrons=spec.get("active_electrons"), active_orbitals=spec.get("active_orbitals"),
    )
    return H_dense

ground_state_energy

ground_state_energy(H_dense) -> float

Exact ground-state energy via dense diagonalization -- a real, checkable number (Hartree) for a Hamiltonian this small (H2/HeH+/H3+ all fit well within exact diagonalization), not an estimate.

Source code in tools/dashboard_core/hamiltonians.py
def ground_state_energy(H_dense) -> float:
    """Exact ground-state energy via dense diagonalization -- a real,
    checkable number (Hartree) for a Hamiltonian this small (H2/HeH+/H3+
    all fit well within exact diagonalization), not an estimate."""
    eigvals = np.linalg.eigvalsh(H_dense)
    return float(eigvals.min())

mix_hamiltonians

mix_hamiltonians(
    H_a, H_b, weight_a: float = 0.5, weight_b: float = 0.5
)

Real weighted combination H_mix = weight_aH_a + weight_bH_b of two molecular Hamiltonians acting on the same qubit space (same electron/qubit count -- the only condition that makes the sum mean anything, mirroring the old dashboard's own "mix molecules that share an electron space" behavior). A real-weighted sum of two Hermitian matrices is itself Hermitian, so H_mix is a real, valid Hamiltonian with a real spectrum -- not a fabricated hybrid, just linear algebra applied to two already-real operators.

Source code in tools/dashboard_core/hamiltonians.py
def mix_hamiltonians(H_a, H_b, weight_a: float = 0.5, weight_b: float = 0.5):
    """Real weighted combination H_mix = weight_a*H_a + weight_b*H_b of
    two molecular Hamiltonians acting on the same qubit space (same
    electron/qubit count -- the only condition that makes the sum mean
    anything, mirroring the old dashboard's own "mix molecules that
    share an electron space" behavior). A real-weighted sum of two
    Hermitian matrices is itself Hermitian, so H_mix is a real, valid
    Hamiltonian with a real spectrum -- not a fabricated hybrid, just
    linear algebra applied to two already-real operators."""
    if H_a.shape != H_b.shape:
        dim_a, dim_b = H_a.shape[0], H_b.shape[0]
        raise ValueError(
            f"cannot mix: different qubit spaces ({int(np.log2(dim_a))} vs {int(np.log2(dim_b))} qubits)"
        )
    H_mix = weight_a * H_a + weight_b * H_b
    if not np.allclose(H_mix, H_mix.conj().T, atol=1e-9):
        raise ValueError("mixed Hamiltonian is not Hermitian -- this should be unreachable")
    return H_mix

See also: dashboard_core.vqe for the ansatz circuits optimized against these Hamiltonians, dashboard_core.qmmm for the Hellmann-Feynman forces derived from them, and Native Hartree-Fock for the engine backing Si2 and any other element outside PennyLane's own STO-3G table.