Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 3 additions & 15 deletions analysis/config_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import tomlkit
from tomlkit.toml_file import TOMLFile

from qhat.common.git_utils import get_git_hash

logger = logging.getLogger(__name__)

# -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -342,26 +344,12 @@ def _generate_TOML_table(self):
# internal types and support functions
# -------------------------------------------------------------------------------------------------

def _get_git_hash():
file_path = os.path.realpath(__file__)
dirpath = os.path.dirname(file_path)
commands = ";".join([
f"pushd {dirpath} > /dev/null",
"if [[ $(git diff --stat) != '' ]]",
"then echo $(git rev-parse HEAD)-dirty",
"else git rev-parse HEAD",
"fi",
"popd > /dev/null"
])
output = subprocess.run(commands, shell=True, capture_output=True)
return output.stdout.decode("utf-8")[:-1]

class GeneralConfiguration:
def __init__(self, user_config: GeneralConfigurationUser):
self.logfile = user_config.logfile
self.loglevel = user_config._loglevel
self.output_directory = user_config.output_directory
self.git_hash = _get_git_hash()
self.git_hash = get_git_hash(reference_file=__file__)

def get_output_path(self, filename):
"""
Expand Down
38 changes: 10 additions & 28 deletions analysis/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
State,
UnitaryConfiguration,
)
from qhat.common.config_utils import (
parse_key_value_params,
get_standard_exec_namespace,
)

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -37,17 +41,7 @@ def load_configuration() -> tuple[State, str]:
args = parser.parse_args()

# Parse the key=value parameters
config_params = {}
for param in args.params:
if '=' not in param:
raise ValueError(f"Parameter must be in KEY=VALUE format, got: {param}")
key, value = param.split('=', 1)
# Try to evaluate as Python literal (numbers, lists, etc.)
try:
config_params[key] = eval(value)
except:
# If evaluation fails, treat as string
config_params[key] = value
config_params = parse_key_value_params(args.params)

# Read the configuration file
with open(args.configuration_file, 'r') as fin:
Expand All @@ -61,19 +55,10 @@ def load_configuration() -> tuple[State, str]:
unitary = UnitaryConfiguration()
algorithm = AlgorithmConfiguration()
analysis = AnalysisConfiguration()
def meV_to_Hartree(meV):
return 3.67493221757e-5 * meV
def string_to_seed(s):
import hashlib
"""Convert a string to a deterministic integer seed."""
# Use SHA-256 hash and convert to integer
hash_bytes = hashlib.sha256(s.encode('utf-8')).digest()
# Take first 8 bytes and convert to integer (fits in 64-bit)
seed = int.from_bytes(hash_bytes[:8], byteorder='big')
return seed

# Create namespace with config objects and params dictionary
exec_namespace = {

# Create namespace with config objects, params, and standard utilities
exec_namespace = get_standard_exec_namespace()
exec_namespace.update({
# configuration objects
'general': general,
'hamiltonian': hamiltonian,
Expand All @@ -82,10 +67,7 @@ def string_to_seed(s):
'analysis': analysis,
# command-line parameters
'params': config_params,
# utility functions
'meV_to_Hartree': meV_to_Hartree,
'string_to_seed': string_to_seed,
}
})
exec(config_script, exec_namespace)

# Build the state (does some post-processing of user configuration)
Expand Down
2 changes: 1 addition & 1 deletion common/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__all__ = ["dense_paul_exp", "trotter"]
__all__ = ["dense_paul_exp", "trotter", "git_utils", "config_utils"]
114 changes: 114 additions & 0 deletions common/config_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Configuration utilities shared across QHAT tools."""

import hashlib


def parse_key_value_params(param_strings):
"""
Parse KEY=VALUE parameter strings from command line.

Parameters
----------
param_strings : list of str
List of "KEY=VALUE" strings

Returns
-------
dict
Dictionary mapping keys to values, with type conversion applied

Raises
------
ValueError
If a parameter string is not in KEY=VALUE format

Notes
-----
- Attempts to evaluate values as Python literals (int, float, list, etc.)
- Falls back to string if evaluation fails
- Example: "distance=1.5" → {"distance": 1.5}
- Example: "name=hello" → {"name": "hello"}
"""
config_params = {}
for param in param_strings:
if '=' not in param:
raise ValueError(f"Parameter must be in KEY=VALUE format, got: {param}")
key, value = param.split('=', 1)
# Try to evaluate as Python literal (numbers, lists, etc.)
try:
config_params[key] = eval(value)
except:
# If evaluation fails, treat as string
config_params[key] = value
return config_params


def meV_to_Hartree(meV):
"""
Convert millielectronvolts (meV) to Hartree atomic units.

Parameters
----------
meV : float
Energy in millielectronvolts

Returns
-------
float
Energy in Hartree atomic units

Notes
-----
Conversion factor: 1 meV = 3.67493221757e-5 Hartree
"""
return 3.67493221757e-5 * meV


def string_to_seed(s):
"""
Convert a string to a deterministic integer seed for random number generators.

Parameters
----------
s : str
Input string

Returns
-------
int
Deterministic seed derived from the string (fits in 64-bit integer)

Notes
-----
- Uses SHA-256 hash for deterministic, uniform distribution
- Same string always produces same seed
- Different strings produce very different seeds
"""
hash_bytes = hashlib.sha256(s.encode('utf-8')).digest()
# Take first 8 bytes and convert to integer (fits in 64-bit)
seed = int.from_bytes(hash_bytes[:8], byteorder='big')
return seed


def get_standard_exec_namespace():
"""
Get a dictionary of standard utility functions for config file execution.

Returns
-------
dict
Dictionary of utility function names to functions

Notes
-----
This provides the standard utilities available in config files:
- meV_to_Hartree: Energy unit conversion
- string_to_seed: Deterministic seed generation

Tools should add their own config objects (general, hamiltonian, etc.)
and params dictionary to this namespace before executing config files.
"""
return {
'meV_to_Hartree': meV_to_Hartree,
'string_to_seed': string_to_seed,
}
35 changes: 35 additions & 0 deletions common/git_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Git utilities for QHAT tools."""
import os
import subprocess


def get_git_hash(reference_file=None):
"""
Get current git commit hash with dirty flag if uncommitted changes exist.

Parameters
----------
reference_file : str, optional
Path to a file in the repository. If provided, uses that file's directory
as the repository root. If None, uses current working directory.

Returns
-------
str
Git hash, with "-dirty" suffix if uncommitted changes exist
"""
if reference_file:
dirpath = os.path.dirname(os.path.realpath(reference_file))
else:
dirpath = os.getcwd()

commands = ";".join([
f"pushd {dirpath} > /dev/null",
"if [[ $(git diff --stat) != '' ]]",
"then echo $(git rev-parse HEAD)-dirty",
"else git rev-parse HEAD",
"fi",
"popd > /dev/null"
])
output = subprocess.run(commands, shell=True, capture_output=True)
return output.stdout.decode("utf-8")[:-1]
24 changes: 17 additions & 7 deletions hamiltonian_generator/config.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
# _________________________________________________________________________________________________
# General configuration

general.print_verbose() # Additional information printed out
general.file_stub = "diatomic_lithium" # Base name that all filenames are built from
general.file_format = "default" # Use default Pauli string style (not HamLib style)
general.print_verbose() # Additional information printed out
general.file_stub = "diatomic_lithium" # Base name that all filenames are built from
general.file_format = "default" # Default Pauli string style (not HamLib style)

# Output directory - keeps all generated files organized
# Override with: -p output_directory=custom_dir
# Defaults to current directory (old behavior)
general.output_directory = params.get("output_directory", "")

# Cache directory - where to look for reusable intermediate files (ham1, ham2)
# If not set, falls back to output_directory
# Override with: -p cache_directory=previous_run
general.cache_directory = params.get("cache_directory", "")

# _________________________________________________________________________________________________
# Describe the Hamiltonian

L = 2.0
L = params.get("L", 2.0)
for i in range(2):
hamiltonian.add_atom("Li", i * L, 0, 0) # Add two lithium atoms at (0,0,0) and (L,0,0)
hamiltonian.add_atom("Li", i * L, 0, 0) # Add two lithium atoms at (0,0,0) and (L,0,0)

hamiltonian.basis = "sto-3g" # Select the atomic basis functions
hamiltonian.basis = params.get("basis", "sto-3g") # Select the atomic basis functions

hamiltonian.num_active_occupied = 4 # Specify the active space
hamiltonian.num_active_occupied = 4 # Specify the active space
hamiltonian.num_active_vacant = 6

Loading