Skip to content
Draft
62 changes: 25 additions & 37 deletions qmp/models/fcidump.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
This file provides an interface to work with FCIDUMP files.
"""

import os
import typing
import logging
import dataclasses
Expand All @@ -20,30 +19,20 @@
from ..hamiltonian import Hamiltonian
from ..utility.model_dict import model_dict, ModelProto, NetworkProto, NetworkConfigProto

QMP_MODEL_PATH = "QMP_MODEL_PATH"


@dataclasses.dataclass
class ModelConfig:
"""
The configuration of the model.
"""

# The openfermion model name
model_name: str
# The path of models folder
model_path: pathlib.Path | None = None
# The complete path to the model file (can be relative or absolute)
model_path: pathlib.Path | str
# The ref energy of the model, leave empty to read from FCIDUMP.yaml
ref_energy: float | None = None

def __post_init__(self) -> None:
if self.model_path is not None:
self.model_path = pathlib.Path(self.model_path)
else:
if QMP_MODEL_PATH in os.environ:
self.model_path = pathlib.Path(os.environ[QMP_MODEL_PATH])
else:
self.model_path = pathlib.Path("models")
self.model_path = pathlib.Path(self.model_path)


def _read_fcidump(
Expand Down Expand Up @@ -134,58 +123,55 @@ class Model(ModelProto[ModelConfig]):

@classmethod
def default_group_name(cls, config: ModelConfig) -> str:
return config.model_name
# Use the filename as the group name, removing extensions
name = config.model_path.name
return name.removesuffix(".FCIDUMP.gz").removesuffix(".FCIDUMP")

def __init__(self, args: ModelConfig) -> None:
# pylint: disable=too-many-locals
logging.info("Input arguments successfully parsed")
logging.info("Model name: %s, Model path: %s", args.model_name, args.model_path)
logging.info("Model path: %s", args.model_path)

model_name = args.model_name
model_path = args.model_path
# model_path is now the complete path to the file (already converted to Path in __post_init__)
model_file_name = args.model_path
ref_energy = args.ref_energy
assert model_path is not None

model_file_name = model_path / f"{model_name}.FCIDUMP.gz"
model_file_name = model_file_name if model_file_name.exists() else model_path / model_name

checksum = hashlib.sha256(model_file_name.read_bytes()).hexdigest() + "v5"
cache_file = platformdirs.user_cache_path("qmp", "kclab") / checksum
if cache_file.exists():
logging.info("Loading FCIDUMP metadata '%s' from file: %s", model_name, model_file_name)
logging.info("Loading FCIDUMP metadata from file: %s", model_file_name)
(n_orbit, n_electron, n_spin), _ = _read_fcidump(model_file_name, cached=True)
logging.info("FCIDUMP metadata '%s' successfully loaded", model_name)
logging.info("FCIDUMP metadata successfully loaded")

logging.info("Loading FCIDUMP Hamiltonian '%s' from cache", model_name)
logging.info("Loading FCIDUMP Hamiltonian from cache")
openfermion_hamiltonian_data = torch.load(cache_file, map_location="cpu", weights_only=True)
logging.info("FCIDUMP Hamiltonian '%s' successfully loaded", model_name)
logging.info("FCIDUMP Hamiltonian successfully loaded")

logging.info("Recovering internal Hamiltonian representation for model '%s'", model_name)
logging.info("Recovering internal Hamiltonian representation")
self.hamiltonian = Hamiltonian(openfermion_hamiltonian_data, kind="fermi")
logging.info("Internal Hamiltonian representation for model '%s' successfully recovered", model_name)
logging.info("Internal Hamiltonian representation successfully recovered")
else:
logging.info("Loading FCIDUMP Hamiltonian '%s' from file: %s", model_name, model_file_name)
logging.info("Loading FCIDUMP Hamiltonian from file: %s", model_file_name)
(n_orbit, n_electron, n_spin), openfermion_hamiltonian_dict = _read_fcidump(model_file_name)
logging.info("FCIDUMP Hamiltonian '%s' successfully loaded", model_name)
logging.info("FCIDUMP Hamiltonian successfully loaded")

logging.info("Converting OpenFermion Hamiltonian to internal Hamiltonian representation")
self.hamiltonian = Hamiltonian(openfermion_hamiltonian_dict, kind="fermi")
logging.info("Internal Hamiltonian representation for model '%s' has been successfully created", model_name)
logging.info("Internal Hamiltonian representation has been successfully created")

logging.info("Caching OpenFermion Hamiltonian for model '%s'", model_name)
logging.info("Caching OpenFermion Hamiltonian")
cache_file.parent.mkdir(parents=True, exist_ok=True)
torch.save((self.hamiltonian.site, self.hamiltonian.kind, self.hamiltonian.coef), cache_file)
logging.info("OpenFermion Hamiltonian for model '%s' successfully cached", model_name)
logging.info("OpenFermion Hamiltonian successfully cached")

self.n_qubit: int = n_orbit * 2
self.n_electron: int = n_electron
self.n_spin: int = n_spin
logging.info(
"Identified %d qubits, %d electrons and %d spin for model '%s'",
"Identified %d qubits, %d electrons and %d spin",
self.n_qubit,
self.n_electron,
self.n_spin,
model_name,
)

self.ref_energy: float
Expand All @@ -196,10 +182,12 @@ def __init__(self, args: ModelConfig) -> None:
if fcidump_ref_energy_file.exists():
with open(fcidump_ref_energy_file, "rt", encoding="utf-8") as file:
fcidump_ref_energy_data = yaml.safe_load(file)
self.ref_energy = fcidump_ref_energy_data.get(pathlib.Path(model_name).name, 0)
# Extract filename without extensions for YAML lookup
yaml_key = model_file_name.name.removesuffix(".FCIDUMP.gz").removesuffix(".FCIDUMP")
self.ref_energy = fcidump_ref_energy_data.get(yaml_key, 0)
else:
self.ref_energy = 0
logging.info("Reference energy for model '%s' is %.10f", model_name, self.ref_energy)
logging.info("Reference energy is %.10f", self.ref_energy)

def apply_within(self, configs_i: torch.Tensor, psi_i: torch.Tensor, configs_j: torch.Tensor) -> torch.Tensor:
return self.hamiltonian.apply_within(configs_i, psi_i, configs_j)
Expand Down
40 changes: 13 additions & 27 deletions qmp/models/openfermion.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
This file provides an interface to work with openfermion models.
"""

import os
import typing
import logging
import dataclasses
Expand All @@ -15,28 +14,18 @@
from ..hamiltonian import Hamiltonian
from ..utility.model_dict import model_dict, ModelProto, NetworkProto, NetworkConfigProto

QMP_MODEL_PATH = "QMP_MODEL_PATH"


@dataclasses.dataclass
class ModelConfig:
"""
The configuration of the model.
"""

# The openfermion model name
model_name: str
# The path of models folder
model_path: pathlib.Path | None = None
# The complete path to the model file (can be relative or absolute)
model_path: pathlib.Path | str

def __post_init__(self) -> None:
if self.model_path is not None:
self.model_path = pathlib.Path(self.model_path)
else:
if QMP_MODEL_PATH in os.environ:
self.model_path = pathlib.Path(os.environ[QMP_MODEL_PATH])
else:
self.model_path = pathlib.Path("models")
self.model_path = pathlib.Path(self.model_path)


class Model(ModelProto[ModelConfig]):
Expand All @@ -50,36 +39,33 @@ class Model(ModelProto[ModelConfig]):

@classmethod
def default_group_name(cls, config: ModelConfig) -> str:
return config.model_name
# Use the filename as the group name, removing extension
return config.model_path.name.removesuffix(".hdf5")

def __init__(self, args: ModelConfig) -> None:
logging.info("Input arguments successfully parsed")
logging.info("Model name: %s, Model path: %s", args.model_name, args.model_path)
logging.info("Model path: %s", args.model_path)

model_name = args.model_name
model_path = args.model_path
assert model_path is not None
# model_path is now the complete path to the file (already converted to Path in __post_init__)
model_file_name = args.model_path

model_file_name = model_path / f"{model_name}.hdf5"
logging.info("Loading OpenFermion model '%s' from file: %s", model_name, model_file_name)
logging.info("Loading OpenFermion model from file: %s", model_file_name)
openfermion_model: openfermion.MolecularData = openfermion.MolecularData(filename=str(model_file_name)) # type: ignore[no-untyped-call]
logging.info("OpenFermion model '%s' successfully loaded", model_name)
logging.info("OpenFermion model successfully loaded")

self.n_qubits: int = int(openfermion_model.n_qubits) # type: ignore[arg-type]
self.n_electrons: int = int(openfermion_model.n_electrons) # type: ignore[arg-type]
logging.info(
"Identified %d qubits and %d electrons for model '%s'", self.n_qubits, self.n_electrons, model_name
)
logging.info("Identified %d qubits and %d electrons", self.n_qubits, self.n_electrons)

self.ref_energy: float = float(openfermion_model.fci_energy) # type: ignore[arg-type]
logging.info("Reference energy for model '%s' is %.10f", model_name, self.ref_energy)
logging.info("Reference energy is %.10f", self.ref_energy)

logging.info("Converting OpenFermion Hamiltonian to internal Hamiltonian representation")
self.hamiltonian: Hamiltonian = Hamiltonian(
openfermion.transforms.get_fermion_operator(openfermion_model.get_molecular_hamiltonian()).terms, # type: ignore[no-untyped-call]
kind="fermi",
)
logging.info("Internal Hamiltonian representation for model '%s' has been successfully created", model_name)
logging.info("Internal Hamiltonian representation has been successfully created")

def apply_within(self, configs_i: torch.Tensor, psi_i: torch.Tensor, configs_j: torch.Tensor) -> torch.Tensor:
return self.hamiltonian.apply_within(configs_i, psi_i, configs_j)
Expand Down