# SPDX-FileCopyrightText: 2025-2026 Julian Peil <julian.peil@tuwien.ac.at>
# SPDX-License-Identifier: MIT
#
# DGAmore — Multi-Orbital Ladder Dynamical Vertex Approximation (LDGA) &
# Eliashberg Equation Solver for Strongly Correlated Electron Systems
"""
YAML configuration parsing. :class:`ConfigParser` reads the run's YAML config (rank 0), broadcasts it to all MPI
ranks, and populates the global :mod:`dgamore.config` singletons section by section, falling back to the
``*Config`` defaults for any missing key/section. The config file location is taken from the ``-p``/``-c``
command-line arguments.
"""
import argparse
import os
from mpi4py import MPI
from ruamel.yaml import YAML
import dgamore.config as config
from dgamore.config import *
from dgamore.dga_logger import DgaLogger
[docs]
class ConfigParser:
"""
Parses the config file and builds the global ``dgamore.config`` singletons (``box``, ``lattice``, ``dmft``, ...). The configuration is then broadcast to all
processes. The config file location can be specified with the path and/or name arguments when executing the main
Python file.
"""
def __init__(self):
"""
Initializes an empty parser; the YAML content is loaded later by :meth:`parse_config`.
"""
self._config_file = None
[docs]
def parse_config(self, comm: MPI.Comm = None, path: str = "./", name: str = "dga_config.yaml"):
"""
Parses the config file on rank 0, broadcasts it to all ranks, and populates the global config singletons. The
``-c``/``-p`` command-line arguments override the ``name``/``path`` defaults.
:param comm: The MPI communicator (rank 0 reads the file and broadcasts it).
:param path: Default directory of the config file.
:param name: Default config file name.
:return: ``self`` (for chaining).
"""
parser = argparse.ArgumentParser(
prog="DGApy", description="Multi-orbital dynamical vertex approximation solver."
)
parser.add_argument("-c", "--config", nargs="?", default=name, type=str, help=" Config file name. ")
parser.add_argument("-p", "--path", nargs="?", default=path, type=str, help=" Path to the config file. ")
if comm.rank == 0:
args = parser.parse_args()
self._config_file = YAML().load(open(os.path.join(args.path, args.config)))
config.logger = DgaLogger(comm)
self._config_file = comm.bcast(self._config_file, root=0)
self._build_config_from_file(self._config_file)
return self
[docs]
def save_config_file(self, path: str = "./", name: str = "dga_config.yaml") -> None:
"""
Dumps the loaded config back to a YAML file (typically into the output folder to record the used parameters).
:param path: Directory to write the config file to.
:param name: File name to write.
:return: None.
"""
with open(os.path.join(path, name), "w+") as file:
YAML().dump(self._config_file, file)
def _build_config_from_file(self, config_file):
"""
Populates every global config singleton from the parsed YAML content.
:param config_file: The parsed YAML mapping.
:return: None.
"""
config.dmft = self._build_dmft_config(config_file)
config.output = self._build_output_config(config_file)
config.self_consistency = self._build_self_consistency_config(config_file)
config.eliashberg = self._build_eliashberg_config(config_file)
config.lambda_correction = self._build_lambda_correction_config(config_file)
config.box = self._build_box_config(config_file)
config.lattice = self._build_lattice_config(config_file)
config.self_energy_interpolation = self._build_self_energy_interpolation_config(config_file)
config.sys = self._build_system_config(config_file)
config.memory = self._build_memory_config(config_file)
config.ana_cont = self._build_ana_cont_config(config_file)
def _build_box_config(self, config_file) -> BoxConfig:
"""
Builds the frequency-box config from the ``box_sizes`` section (defaults if absent), deriving ``niv_full``
from the core and shell sizes.
:param config_file: The parsed YAML mapping.
:return: The populated :class:`BoxConfig`.
"""
conf = BoxConfig()
try:
section = config_file["box_sizes"]
except KeyError:
config.logger.info(f"'box_sizes' section not found. Using default values.")
return conf
conf.niw_core = self._try_parse(section, "niw_core", conf.niw_core)
conf.niv_core = self._try_parse(section, "niv_core", conf.niv_core)
conf.niv_shell = self._try_parse(section, "niv_shell", conf.niv_shell)
if conf.niv_shell <= 0:
config.logger.info(f"'niv_shell' is set to {conf.niv_shell}. No asymptotics will be used.")
conf.niv_shell = 0
conf.niv_full = conf.niv_core + conf.niv_shell
return conf
def _build_lattice_config(self, config_file) -> LatticeConfig:
"""
Builds the lattice config from the ``lattice`` section (defaults if absent): grid sizes, symmetries, k/q grids,
and the lattice/interaction input.
:param config_file: The parsed YAML mapping.
:return: The populated :class:`LatticeConfig`.
"""
conf = LatticeConfig()
try:
section = config_file["lattice"]
except KeyError:
config.logger.info(f"'lattice' section not found. Using default values.")
return conf
conf.nk = self._try_parse(section, "nk", conf.nk)
if "nq" not in section:
config.logger.info("'nq' not set in config. Setting 'nq' = 'nk'.")
conf.nq = conf.nk
else:
conf.nq = self._try_parse(section, "nq", conf.nq)
symmetries = self._try_parse(section, "symmetries", "two_dimensional_square")
conf.symmetries = bz.get_lattice_symmetries_from_string(symmetries)
conf.k_grid = bz.KGrid(conf.nk, conf.symmetries)
conf.q_grid = bz.KGrid(conf.nq, conf.symmetries)
conf.type = self._try_parse(section, "type", conf.type)
conf.er_input = section["hr_input"] # can be multiple types
conf.interaction_type = self._try_parse(section, "interaction_type", conf.interaction_type)
conf.interaction_input = self._try_parse(section, "interaction_input", conf.interaction_input)
return conf
def _build_dmft_config(self, config_file) -> DmftConfig:
"""
Builds the DMFT input config from the ``dmft_input`` section (defaults if absent).
:param config_file: The parsed YAML mapping.
:return: The populated :class:`DmftConfig`.
"""
conf = DmftConfig()
try:
section = config_file["dmft_input"]
except KeyError:
config.logger.info(f"'dmft_input' section not found. Using default values.")
return conf
conf.type = self._try_parse(section, "type", conf.type)
conf.input_path = self._try_parse(section, "input_path", conf.input_path)
conf.fname_1p = self._try_parse(section, "fname_1p", conf.fname_1p)
conf.fname_2p = self._try_parse(section, "fname_2p", conf.fname_2p)
conf.do_sym_v_vp = self._try_parse(section, "do_sym_v_vp", conf.do_sym_v_vp)
conf.symmetrize_orbitals = self._try_parse(section, "symmetrize_orbitals", conf.symmetrize_orbitals)
conf.n_ineq = self._try_parse(section, "n_ineq", conf.n_ineq)
conf.ineq_ordering = self._try_parse(section, "ineq_ordering", conf.ineq_ordering)
return conf
def _build_system_config(self, _) -> SystemConfig:
"""
Returns an empty system config; its fields are filled later by the main routine from the DMFT input.
:param _: Unused (the parsed YAML mapping).
:return: A default :class:`SystemConfig`.
"""
return SystemConfig()
def _build_output_config(self, config_file) -> OutputConfig:
"""
Builds the output config from the ``output`` section (defaults if absent), defaulting ``output_path`` to the
DMFT input path when unset.
:param config_file: The parsed YAML mapping.
:return: The populated :class:`OutputConfig`.
"""
conf = OutputConfig()
try:
section = config_file["output"]
except KeyError:
config.logger.info(f"'output' section not found. Using default values.")
return conf
conf.do_plotting = self._try_parse(section, "do_plotting", conf.do_plotting)
conf.plotting_subfolder_name = self._try_parse(section, "plotting_subfolder_name", conf.plotting_subfolder_name)
conf.output_path = self._try_parse(section, "output_path", conf.output_path)
if not conf.output_path or conf.output_path == "":
config.logger.info(f"'output_path' not set in config. Setting 'output_path' = '{config.dmft.input_path}'.")
conf.output_path = config.dmft.input_path
return conf
def _build_self_consistency_config(self, config_file) -> SelfConsistencyConfig:
"""
Builds the self-consistency config from the ``self_consistency`` section (defaults if absent).
:param config_file: The parsed YAML mapping.
:return: The populated :class:`SelfConsistencyConfig`.
"""
conf = SelfConsistencyConfig()
try:
section = config_file["self_consistency"]
except KeyError:
config.logger.info(f"'self_consistency' section not found. Using default values.")
return conf
conf.max_iter = self._try_parse(section, "max_iter", conf.max_iter)
conf.epsilon = self._try_parse(section, "epsilon", conf.epsilon)
conf.mixing = self._try_parse(section, "mixing", conf.mixing)
conf.mixing_strategy = self._try_parse(section, "mixing_strategy", conf.mixing_strategy)
conf.mixing_history_length = self._try_parse(section, "mixing_history_length", conf.mixing_history_length)
conf.previous_sc_path = self._try_parse(section, "previous_sc_path", conf.previous_sc_path)
conf.use_interpolated_sigma = self._try_parse(section, "use_interpolated_sigma", conf.use_interpolated_sigma)
conf.use_lambda_correction = self._try_parse(section, "use_lambda_correction", conf.use_lambda_correction)
conf.restrict_chi_phys = self._try_parse(section, "restrict_chi_phys", conf.restrict_chi_phys)
return conf
def _build_eliashberg_config(self, config_file) -> EliashbergConfig:
"""
Builds the Eliashberg config from the ``eliashberg`` section (defaults if absent).
:param config_file: The parsed YAML mapping.
:return: The populated :class:`EliashbergConfig`.
"""
conf = EliashbergConfig()
try:
section = config_file["eliashberg"]
except KeyError:
config.logger.info(f"'eliashberg' section not found. Using default values.")
return conf
conf.perform_eliashberg = self._try_parse(section, "perform_eliashberg", conf.perform_eliashberg)
conf.save_pairing_vertex = self._try_parse(section, "save_pairing_vertex", conf.save_pairing_vertex)
conf.save_fq = self._try_parse(section, "save_fq", conf.save_fq)
conf.construct_fq_cheap = self._try_parse(section, "construct_fq_cheap", conf.construct_fq_cheap)
conf.n_eig = self._try_parse(section, "n_eig", conf.n_eig)
conf.epsilon = self._try_parse(section, "epsilon", conf.epsilon)
conf.symmetry = self._try_parse(section, "symmetry", conf.symmetry)
conf.include_local_part = self._try_parse(section, "include_local_part", conf.include_local_part)
conf.subfolder_name = self._try_parse(section, "subfolder_name", conf.subfolder_name)
return conf
def _build_lambda_correction_config(self, config_file):
"""
Builds the lambda-correction config from the ``lambda_correction`` section (defaults if absent).
:param config_file: The parsed YAML mapping.
:return: The populated :class:`LambdaCorrectionConfig`.
"""
conf = LambdaCorrectionConfig()
try:
section = config_file["lambda_correction"]
except KeyError:
config.logger.info(f"'lambda_correction' section not found. Using default values.")
return conf
conf.perform_lambda_correction = self._try_parse(
section, "perform_lambda_correction", conf.perform_lambda_correction
)
conf.type = self._try_parse(section, "type", conf.type)
return conf
def _build_self_energy_interpolation_config(self, config_file):
"""
Builds the self-energy interpolation config from the ``self_energy_interpolation`` section (defaults if absent).
:param config_file: The parsed YAML mapping.
:return: The populated :class:`SelfEnergyInterpolationConfig`.
"""
conf = SelfEnergyInterpolationConfig()
try:
section = config_file["self_energy_interpolation"]
except KeyError:
config.logger.info(f"'self_energy_interpolation' section not found. Using default values.")
return conf
conf.do_interpolation = self._try_parse(section, "do_interpolation", conf.do_interpolation)
conf.beta_target = self._try_parse(section, "target_beta", conf.beta_target)
conf.niv_target = self._try_parse(section, "target_niv", conf.niv_target)
return conf
def _build_memory_config(self, config_file):
"""
Builds the memory config from the ``memory`` section (defaults if absent).
:param config_file: The parsed YAML mapping.
:return: The populated :class:`MemoryConfig`.
"""
conf = MemoryConfig()
try:
section = config_file["memory"]
except KeyError:
config.logger.info(f"'memory' section not found. Using default values.")
return conf
conf.save_memory_for_chi0q = self._try_parse(section, "save_memory_for_chi0q", conf.save_memory_for_chi0q)
conf.save_memory_for_chiq_aux = self._try_parse(
section, "save_memory_for_chiq_aux", conf.save_memory_for_chiq_aux
)
conf.save_memory_for_sde = self._try_parse(section, "save_memory_for_sde", conf.save_memory_for_sde)
conf.save_memory_for_fq = self._try_parse(section, "save_memory_for_fq", conf.save_memory_for_fq)
conf.save_memory_for_lanczos = self._try_parse(section, "save_memory_for_lanczos", conf.save_memory_for_lanczos)
return conf
def _build_ana_cont_config(self, config_file):
"""
Builds the analytic-continuation config from the ``ana_cont`` section (defaults if absent).
:param config_file: The parsed YAML mapping.
:return: The populated :class:`AnaContConfig`.
"""
conf = AnaContConfig()
try:
section = config_file["ana_cont"]
except KeyError:
config.logger.info(f"'ana_cont' section not found. Using default values.")
return conf
conf.do_spectrum_dga = self._try_parse(section, "do_spectrum_dga", conf.do_spectrum_dga)
conf.do_spectrum_dmft = self._try_parse(section, "do_spectrum_dmft", conf.do_spectrum_dmft)
conf.w_count = self._try_parse(section, "w_count", conf.w_count)
conf.plot_spectrum = self._try_parse(section, "plot_spectrum", conf.plot_spectrum)
conf.k_path = self._try_parse(section, "k_path", conf.k_path)
conf.energy_window = self._try_parse(section, "energy_window", conf.energy_window)
return conf
def _try_parse(self, config_section, key: str, default_value):
"""
Reads ``key`` from a config section and coerces it to the type of ``default_value``, returning the default if
the key is missing or cannot be coerced.
:param config_section: The YAML sub-mapping to read from.
:param key: The key to look up.
:param default_value: The fallback value (its type also determines the coercion target).
:return: The parsed value, or ``default_value`` on a missing key or coercion failure.
"""
if key not in config_section:
return default_value
value_type = type(default_value)
try:
return value_type(config_section[key])
except ValueError:
config.logger.info(f"Could not parse value for {key}. Using default value: {default_value}.")
return default_value