Source code for dgamore.dga_logger

# 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
"""
MPI-aware logging. :class:`DgaLogger` timestamps messages and, by default, only emits them on the root rank so
that parallel runs produce a single clean log stream.
"""

import logging
import os
from datetime import datetime

import mpi4py.MPI as MPI


[docs] class DgaLogger: """ Handles logging messages in a distributed environment using MPI. Currently, it logs messages to stdout. """ def __init__(self, comm: MPI.Comm, output_path: str = "./", filename: str = "dga.log"): """ Sets up the underlying stream logger and records the start time for elapsed-time reporting. :param comm: The MPI communicator; the rank determines which processes actually emit a message. :param output_path: Directory for the (currently disabled) log file. :param filename: Name of the (currently disabled) log file. """ self._comm = comm self._output_path = output_path self._filename = filename self._filepath = os.path.join(output_path, filename) self._logger = logging.getLogger("dga_logger") self._logger.setLevel(logging.DEBUG) self._logger.addHandler(logging.StreamHandler()) # self._logger.addHandler(logging.FileHandler(self._filepath)) self._start_time = datetime.now() self.info(" Current-T | Elapsed-T | Message") @property def is_root(self) -> bool: """ Whether the current process is the root rank. :return: True if the current MPI rank is the root rank (rank 0). """ return self._comm.rank == 0 @property def total_elapsed_time(self) -> str: """ Calculates the total elapsed time since the logger was initialized (i.e. almost the same as the runtime of the code). :return: The elapsed time formatted as ``"DD-HH:MM:SS.mmm"``. """ delta = datetime.now() - self._start_time total_seconds = int(delta.total_seconds()) days, remainder = divmod(total_seconds, 86400) hours, remainder = divmod(remainder, 3600) minutes, seconds = divmod(remainder, 60) milliseconds = delta.microseconds // 1000 return str(f"{days:02}-{hours:02}:{minutes:02}:{seconds:02}.{milliseconds:03}") @property def current_time(self) -> str: """ The current wall-clock time as a formatted string. :return: The current wall-clock time formatted as ``"YYYY-MM-DD HH:MM:SS.sss"``. """ return str(datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"))[:-3] def _log(self, message: str, level: int, allowed_ranks: tuple | int = (0,)): """ Logs a message with a specific logging level, but only if the current rank is in the allowed ranks. :param message: The message to log; ignored if None or empty. :param level: The :mod:`logging` level (e.g. ``logging.INFO``); a negative level suppresses the message. :param allowed_ranks: The MPI rank(s) permitted to emit this message. :return: None. """ if isinstance(allowed_ranks, int): allowed_ranks = (allowed_ranks,) if self._comm.rank not in allowed_ranks or message is None or message == "" or level < 0: return self._logger.log(level, f"{self.current_time} | {self.total_elapsed_time} | {message}")
[docs] def debug(self, message: str, allowed_ranks: tuple = (0,)): """ Logs a debug message. This is intended for detailed debugging information that is not usually needed in production. :param message: The message to log. :param allowed_ranks: The MPI rank(s) permitted to emit this message. :return: None. """ self._log("::DEBUG:: " + message, level=logging.DEBUG, allowed_ranks=allowed_ranks)
[docs] def info(self, message: str, allowed_ranks: tuple = (0,)): """ Logs an informational message. This is intended for general information about the program's execution. :param message: The message to log. :param allowed_ranks: The MPI rank(s) permitted to emit this message. :return: None. """ self._log(message, level=logging.INFO, allowed_ranks=allowed_ranks)
[docs] def warning(self, message: str, allowed_ranks: tuple = (0,)): """ Logs a warning message. This is intended for situations that are not errors but may require attention. :param message: The message to log. :param allowed_ranks: The MPI rank(s) permitted to emit this message. :return: None. """ self._log("::WARNING:: " + message, level=logging.WARNING, allowed_ranks=allowed_ranks)
[docs] def log_memory_usage(self, obj_name: str, obj, n_exists: int = 1, allowed_ranks: tuple = (0,)): """ Logs the memory usage of an object in gigabytes. This is useful for tracking memory consumption in distributed applications, especially when using MPI. :param obj_name: Human-readable name used in the log line. :param obj: An object exposing ``memory_usage_in_gb`` (e.g. any :class:`IHaveMat`); ignored if None. :param n_exists: Multiplicity factor, i.e. how many such objects exist (the reported size is scaled by it). :param allowed_ranks: The MPI rank(s) permitted to emit this message. :return: None. """ if obj is None: return self.info(f"{obj_name} use(s) (GB): {obj.memory_usage_in_gb * n_exists:.6f}.", allowed_ranks=allowed_ranks)