"""Internal module for managing persistent parameter storage.
This module handles loading and saving of parameter data. Not for end-user use.
"""
import json
import os
import warnings
from pathlib import Path
from typing import Dict, List, Optional, Union
# Get the package's data directory
_PACKAGE_DIR = Path(__file__).parent
_PARAMS_FILE = _PACKAGE_DIR / "data" / "parameters.json"
# Default parameters that are always available
_DEFAULT_PARAMETERS = {
"N2_Hacc5_QS": {
"E_g": 0.5340041306217247,
"a0": 99999.99999998882,
"a1": 15.376194957191581,
"a2": 0.8790068909090262,
"a4": 2.770721571015505,
},
"H2_Hacc5_QS": {
"E_g": 0.5673724982911573,
"a0": 1937.088166540485,
"a1": 8.491546001681295,
"a2": 1.3543992171831474,
"a4": 2.011176745861679,
},
"CO_Hacc5_QS": {
"E_g": 0.5255150414899638,
"a0": 0.00233014720296687,
"a1": 2.8261859163927094,
"a2": 2.3475286210319757,
"a4": -3.998124088750205,
},
"Cs_QS": {
"E_g": 0.14309811329075314,
"a0": 1.101183623936075,
"a1": 10.10193123075726,
"a2": 5.999999850621686,
"a4": -2.7408334445567526,
},
"K_QS": {
"E_g": 0.15951770213954444,
"a0": 0.8282884118748779,
"a1": 8.143214583015666,
"a2": 5.999999999965908,
"a4": -2.767117023726227,
},
"Fr_QS": {
"E_g": 0.14966888877455772,
"a0": 1.0073373413965747,
"a1": 9.282909141254013,
"a2": 5.999999969726635,
"a4": -2.741824131101126,
},
"Rb_QS": {
"E_g": 0.15350551607047047,
"a0": 0.8382418045183027,
"a1": 8.549404330511935,
"a2": 5.999999962739765,
"a4": -2.797254328314558,
},
"H_diabatic": {
"E_g": 0.5,
"a0": 6.5145636716865285,
"a1": 3.5,
"a2": 3.412961064100051,
"a3": 3.1901975961759295,
"a4": -0.5,
},
"H_SFA": {
"E_g": 0.5,
"a0": 3.376633520215513,
"a1": 3.5,
"a2": 2.035789354025642,
"a3": 0.27574597131861167,
"a4": 0,
},
"H_QS": {
"E_g": 0.5,
"a1": 3.5,
"a2": 3.8752084444900223,
"a0": 2.8369675204664158,
"a4": -0.5,
},
"Ne_Hacc4_QS": {
"E_g": 0.792461982845425,
"a1": 1.9131207338962406,
"a2": 4.169719545489457,
"a0": 0.9316816153527308,
"a4": -0.02900758396067893,
},
"He_Hacc5_QS": {
"E_g": 0.9036653755411335,
"a1": 1.0195227787372818,
"a2": 2.2141763280400886,
"a0": 0.0195686829574937,
"a4": -2.913981621862887,
},
"He_Hacc5": {"E_g": 0.9035551, "a1": 2.0, "a2": 3.26, "a3": 0.38, "a0": 0.82},
"SiO2_Ulmic": {
"E_g": 0.347,
"a1": 1.5,
"m_eff": 0.235,
"a2": 0.8,
"a3": -5.68,
"a0_per_au3": 0.0194,
"lattice_constant_au": 1.0,
},
"Diamond_TBmBJ": {
"E_g": 0.235,
"a1": 14,
"m_eff": 0.115,
"a2": 1.7,
"a3": -3.0,
"a0_per_au3": 0.35,
"lattice_constant_au": 6.74,
},
"Ar_SAE": {"E_g": 0.5791543, "a1": 4.4, "a2": 2.8, "a3": 2.6, "a0": 16.4},
"Si_TBmBJ": {
"E_g": 0.108,
"a1": 25.7,
"m_eff": 0.080,
"a2": -0.32,
"a3": 7.7,
"a0_per_au3": 25.4,
"lattice_constant_au": 10.21,
},
}
_DEFAULT_PARAMETERS["Hydrogen_SFA"] = _DEFAULT_PARAMETERS["H_SFA"]
_DEFAULT_PARAMETERS["Hydrogen_diabatic"] = _DEFAULT_PARAMETERS["H_diabatic"]
_DEFAULT_PARAMETERS["Hydrogen_QS"] = _DEFAULT_PARAMETERS["H_QS"]
_DEFAULT_PARAMETERS["Hydrogen"] = _DEFAULT_PARAMETERS["H_diabatic"]
def _ensure_data_dir():
"""Ensure the data directory exists."""
data_dir = _PACKAGE_DIR / "data"
data_dir.mkdir(exist_ok=True)
return data_dir
def _load_parameters() -> Dict[str, Dict[str, float]]:
"""Load parameters from file, falling back to defaults if file doesn't exist."""
try:
with open(_PARAMS_FILE, "r") as f:
stored_params = json.load(f)
# Merge with defaults, stored params take precedence
params = _DEFAULT_PARAMETERS.copy()
params.update(stored_params)
return params
except (FileNotFoundError, json.JSONDecodeError):
return _DEFAULT_PARAMETERS.copy()
def _save_parameters_to_file(parameters: Dict[str, Dict[str, float]]) -> None:
"""Save parameters to file.
Only saves parameters that differ from defaults to keep the file clean.
"""
_ensure_data_dir()
# Only store parameters that differ from defaults
custom_params = {}
for medium, params in parameters.items():
if medium not in _DEFAULT_PARAMETERS or params != _DEFAULT_PARAMETERS[medium]:
custom_params[medium] = params
with open(_PARAMS_FILE, "w") as f:
json.dump(custom_params, f, indent=4)
def _save_parameters(medium_name: str, parameters: Dict[str, float]) -> None:
"""Persist a single medium's parameters to the on-disk store.
Args:
medium_name: Identifier string for the medium (e.g. ``"H_SFA"``).
parameters: Dict mapping parameter names to their float values.
"""
param_as_dict = {medium_name: parameters}
_write_parameters(param_as_dict)
# Global parameters dictionary, initialized on module import
_PARAMETERS = _load_parameters()
[docs]
def get_parameters(
medium_name: Optional[str] = None,
) -> Union[Dict[str, float], List[str]]:
"""Return parameters for a medium, or list all available medium names.
Args:
medium_name: Case-insensitive name of the medium (e.g. ``"Hydrogen_SFA"``).
Pass ``None`` to get a list of all available medium names.
Returns:
A copy of the parameter dict when *medium_name* is given, or a list of
all known medium name strings when *medium_name* is ``None``.
Raises:
ValueError: If *medium_name* is not found in the parameter store.
Example:
>>> params = get_parameters("Hydrogen_SFA")
>>> names = get_parameters() # list all available media
"""
if medium_name is None:
return list(_PARAMETERS.keys())
# Make key lookup case-insensitive
for key in _PARAMETERS:
if key.lower() == medium_name.lower():
return _PARAMETERS[key].copy()
available = list(_PARAMETERS.keys())
raise ValueError(
f"Unknown medium: {medium_name.lower()}. Available media are: {available}"
)
def _write_parameters(new_parameters: Dict[str, Dict[str, float]]) -> None:
"""Add new parameters to the store and save them to disk.
This is an internal function not meant for end-user use.
"""
required_params = {"E_g", "a0", "a1", "a2", "a4"}
diabatic_params = {"a3", "a5"}
internal_params = {
"internal_param_xi1",
"internal_param_pxi1",
"internal_param_pxi2",
}
for medium, params in new_parameters.items():
# Check if all required parameters are present
missing_params = required_params - set(params.keys())
if missing_params:
raise ValueError(
f"Parameter dictionary for {medium} is missing required parameters: {missing_params}"
)
# give a warning if diabatic parameters are not present
if not diabatic_params.issubset(params.keys()):
warnings.warn(
f"Diabatic parameters {diabatic_params} are not present for {medium}. "
"This may lead to incorrect results if using these parameters to compute diabatic rates."
)
# give a warning if internal parameters are being used
if internal_params.intersection(params.keys()):
warnings.warn(
f"Internal parameters {internal_params.intersection(params.keys())} are being used for {medium}. "
"These parameters are not meant for advanced users and may lead to unexpected results."
)
# Add or update parameters
_PARAMETERS[medium] = params.copy()
# Save to disk
_save_parameters_to_file(_PARAMETERS)
[docs]
def return_parameter_table():
r"""Return a formatted LaTeX ``tabularx`` string of all stored parameters.
Each row is ``medium_name & a0 & a1 & a2 & a3 & a4 & E_g & m_eff``.
The returned string is a complete ``tabularx`` environment ready to be
pasted into a LaTeX document.
"""
media = get_parameters()
table_str = "\\begin{tabularx}{\\linewidth}{@{} l *{5}{>{\\raggedleft\\arraybackslash}X} @{} }\n"
table_str += " \\toprule\n"
table_str += " & $a_0$ & $a_1$ & $a_2$ & $a_3$ & $a_4$ & E_g & m_eff \\\\\n"
table_str += " \\midrule\n"
for medium in media:
params = get_parameters(medium)
a0 = params.get("a0", "N/A")
a1 = params.get("a1", "N/A")
a2 = params.get("a2", "N/A")
a3 = params.get("a3", "N/A")
a4 = params.get("a4", "N/A")
E_g = params.get("E_g", "N/A")
m_eff = params.get("m_eff", "N/A")
# format medium name to be more presentable in latex, e.g. "H_SFA" -> "$H_\mathrm{SFA}$"
# the first part is subscript the second superscript, e.g. "H_SFA_diabatic" -> "$H_\mathrm{SFA}^\mathrm{diabatic}$"
replaced_medium = (
medium.replace("_diabatic", "^\\mathrm{diabatic}")
.replace("_QS", "^\\mathrm{QS}")
.replace("_SAE", "^\\mathrm{SAE}")
.replace("_Ulmic", "^\\mathrm{Ulmic}")
.replace("_TBmBJ", "^\\mathrm{TBmBJ}")
)
medium = medium.replace("_", "_\\mathrm{") + "}"
medium = f"${medium}$"
if isinstance(E_g, (int, float)):
E_g_eV = E_g * 27.21138
else:
E_g_eV = "N/A"
table_str += f" {medium} & {a0} & {a1} & {a2} & {a3} & {a4} & {E_g_eV} & {m_eff} \\\\\n"
table_str += " \\bottomrule\n"
table_str += " \\end{tabularx}"
return table_str
if __name__ == "__main__":
import sys
print(return_parameter_table())
if len(sys.argv) > 1:
output_path = sys.argv[1]
with open(output_path, "w") as f:
f.write(return_parameter_table())