Skip to content

Commit

Permalink
Merge pull request #55 from BoothGroup/logging
Browse files Browse the repository at this point in the history
Logging improvements
  • Loading branch information
obackhouse authored Mar 9, 2024
2 parents ec7ac82 + d6788f3 commit 0aa3aae
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 172 deletions.
85 changes: 5 additions & 80 deletions ebcc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,101 +36,25 @@

__version__ = "1.4.3"

import logging
import os
import subprocess
import sys

# --- Logging:


def output(self, msg, *args, **kwargs):
"""Output a message at the `"OUTPUT"` level."""
if self.isEnabledFor(25):
self._log(25, msg, args, **kwargs)


default_log = logging.getLogger(__name__)
default_log.setLevel(logging.INFO)
default_log.addHandler(logging.StreamHandler(sys.stderr))
logging.addLevelName(25, "OUTPUT")
logging.Logger.output = output


class NullLogger(logging.Logger):
"""A logger that does nothing."""

def __init__(self, *args, **kwargs):
super().__init__("null")

def _log(self, level, msg, args, **kwargs):
pass


HEADER = """ _
| |
___ | |__ ___ ___
/ _ \| '_ \ / __| / __|
| __/| |_) || (__ | (__
\___||_.__/ \___| \___|
%s"""


def init_logging(log):
"""Initialise the logging with a header."""

if globals().get("_EBCC_LOG_INITIALISED", False):
return

# Print header
header_size = max([len(line) for line in HEADER.split("\n")])
log.info(HEADER % (" " * (header_size - len(__version__)) + __version__))

# Print versions of dependencies and ebcc
def get_git_hash(directory):
git_directory = os.path.join(directory, ".git")
cmd = ["git", "--git-dir=%s" % git_directory, "rev-parse", "--short", "HEAD"]
try:
git_hash = subprocess.check_output(
cmd, universal_newlines=True, stderr=subprocess.STDOUT
).rstrip()
except subprocess.CalledProcessError:
git_hash = "N/A"
return git_hash

import numpy
import pyscf

log.info("numpy:")
log.info(" > Version: %s" % numpy.__version__)
log.info(" > Git hash: %s" % get_git_hash(os.path.join(os.path.dirname(numpy.__file__), "..")))

log.info("pyscf:")
log.info(" > Version: %s" % pyscf.__version__)
log.info(" > Git hash: %s" % get_git_hash(os.path.join(os.path.dirname(pyscf.__file__), "..")))
# --- Import NumPy here to allow drop-in replacements

log.info("ebcc:")
log.info(" > Version: %s" % __version__)
log.info(" > Git hash: %s" % get_git_hash(os.path.join(os.path.dirname(__file__), "..")))
import numpy

# Environment variables
log.info("OMP_NUM_THREADS = %s" % os.environ.get("OMP_NUM_THREADS", ""))

log.info("")
# --- Logging:

globals()["_EBCC_LOG_INITIALISED"] = True
from ebcc.logging import default_log, init_logging, NullLogger


# --- Types of ansatz supporting by the EBCC solvers:

METHOD_TYPES = ["MP", "CC", "LCC", "QCI", "QCC", "DC"]


# --- Import NumPy here to allow drop-in replacements

import numpy


# --- General constructor:

from ebcc.gebcc import GEBCC
Expand Down Expand Up @@ -189,6 +113,7 @@ def constructor(mf, *args, **kwargs):
from ebcc.brueckner import BruecknerGEBCC, BruecknerREBCC, BruecknerUEBCC
from ebcc.space import Space


# --- List available methods:


Expand Down
60 changes: 33 additions & 27 deletions ebcc/brueckner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
import scipy.linalg
from pyscf import lib

from ebcc import NullLogger
from ebcc import NullLogger, init_logging
from ebcc import numpy as np
from ebcc import util
from ebcc.damping import DIIS
from ebcc.logging import ANSI
from ebcc.precision import types


Expand Down Expand Up @@ -78,12 +79,14 @@ def __init__(self, cc, log=None, options=None, **kwargs):
self.converged = False

# Logging:
cc.log.info("Brueckner options:")
cc.log.info(" > e_tol: %s", options.e_tol)
cc.log.info(" > t_tol: %s", options.t_tol)
cc.log.info(" > max_iter: %s", options.max_iter)
cc.log.info(" > diis_space: %s", options.diis_space)
cc.log.info(" > damping: %s", options.damping)
init_logging(cc.log)
cc.log.info(f"\n{ANSI.B}{ANSI.U}{self.name}{ANSI.R}")
cc.log.info(f"{ANSI.B}Options{ANSI.R}:")
cc.log.info(f" > e_tol: {ANSI.y}{self.options.e_tol}{ANSI.R}")
cc.log.info(f" > t_tol: {ANSI.y}{self.options.t_tol}{ANSI.R}")
cc.log.info(f" > max_iter: {ANSI.y}{self.options.max_iter}{ANSI.R}")
cc.log.info(f" > diis_space: {ANSI.y}{self.options.diis_space}{ANSI.R}")
cc.log.info(f" > damping: {ANSI.y}{self.options.damping}{ANSI.R}")
cc.log.debug("")

def get_rotation_matrix(self, u_tot=None, diis=None, t1=None):
Expand Down Expand Up @@ -292,17 +295,14 @@ def kernel(self):
u_tot = None

self.cc.log.output("Solving for Brueckner orbitals.")
self.cc.log.info(
"%4s %16s %18s %8s %13s %13s",
"Iter",
"Energy (corr.)",
"Energy (tot.)",
"Conv.",
"Δ(Energy)",
"|T1|",
self.cc.log.debug("")
self.log.info(
f"{ANSI.B}{'Iter':>4s} {'Energy (corr.)':>16s} {'Energy (tot.)':>18s} "
f"{'Conv.':>8s} {'Δ(Energy)':>13s} {'|T1|':>13s}{ANSI.R}"
)
self.log.info(
"%4d %16.10f %18.10f %8s", 0, self.cc.e_corr, self.cc.e_tot, self.cc.converged
f"{0:4d} {self.cc.e_corr:16.10f} {self.cc.e_tot:18.10f} "
f"{[ANSI.r, ANSI.g][self.cc.converged]}{self.cc.converged!r:>8}{ANSI.R}"
)

converged = False
Expand Down Expand Up @@ -336,34 +336,40 @@ def kernel(self):
de = abs(e_prev - self.cc.e_tot)
dt = self.get_t1_norm()

# Log the iteration:
converged_e = de < self.options.e_tol
converged_t = dt < self.options.t_tol
self.log.info(
"%4d %16.10f %18.10f %8s %13.3e %13.3e",
niter,
self.cc.e_corr,
self.cc.e_tot,
self.cc.converged,
de,
dt,
f"{niter:4d} {self.cc.e_corr:16.10f} {self.cc.e_tot:18.10f}"
f" {[ANSI.r, ANSI.g][self.cc.converged]}{self.cc.converged!r:>8}{ANSI.R}"
f" {[ANSI.r, ANSI.g][converged_e]}{de:13.3e}{ANSI.R}"
f" {[ANSI.r, ANSI.g][converged_t]}{dt:13.3e}{ANSI.R}"
)

# Check for convergence:
converged = de < self.options.e_tol and dt < self.options.t_tol
converged = converged_e and converged_t
if converged:
self.cc.log.output("Converged.")
self.log.debug("")
self.log.output(f"{ANSI.g}Converged.{ANSI.R}")
break
else:
self.cc.log.warning("Failed to converge.")
self.log.debug("")
self.log.warning(f"{ANSI.r}Failed to converge.{ANSI.R}")

self.cc.log.debug("")
self.cc.log.output("E(corr) = %.10f", self.cc.e_corr)
self.cc.log.output("E(tot) = %.10f", self.cc.e_tot)
self.cc.log.debug("")
self.cc.log.debug("Time elapsed: %s", timer.format_time(timer()))
self.cc.log.debug("")
self.cc.log.debug("")

return self.cc.e_corr

@property
def name(self):
"""Get a string representation of the method name."""
return self.spin_type + "B" + self.cc.ansatz.name

@property
def spin_type(self):
"""Return the spin type."""
Expand Down
118 changes: 118 additions & 0 deletions ebcc/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"""Logging."""

import logging
import os
import subprocess
import sys

from ebcc import __version__
from ebcc.util import Namespace

HEADER = """ _
| |
___ | |__ ___ ___
/ _ \| '_ \ / __| / __|
| __/| |_) || (__ | (__
\___||_.__/ \___| \___|
%s""" # noqa: W605


def output(self, msg, *args, **kwargs):
"""Output a message at the `"OUTPUT"` level."""
if self.isEnabledFor(25):
self._log(25, msg, args, **kwargs)


default_log = logging.getLogger(__name__)
default_log.setLevel(logging.INFO)
default_log.addHandler(logging.StreamHandler(sys.stderr))
logging.addLevelName(25, "OUTPUT")
logging.Logger.output = output


class NullLogger(logging.Logger):
"""A logger that does nothing."""

def __init__(self, *args, **kwargs):
super().__init__("null")

def _log(self, level, msg, args, **kwargs):
pass


def init_logging(log):
"""Initialise the logging with a header."""

if globals().get("_EBCC_LOG_INITIALISED", False):
return

# Print header
header_size = max([len(line) for line in HEADER.split("\n")])
space = " " * (header_size - len(__version__))
log.info(f"{ANSI.B}{HEADER}{ANSI.R}" % f"{space}{ANSI.B}{__version__}{ANSI.R}")

# Print versions of dependencies and ebcc
def get_git_hash(directory):
git_directory = os.path.join(directory, ".git")
cmd = ["git", "--git-dir=%s" % git_directory, "rev-parse", "--short", "HEAD"]
try:
git_hash = subprocess.check_output(
cmd, universal_newlines=True, stderr=subprocess.STDOUT
).rstrip()
except subprocess.CalledProcessError:
git_hash = "N/A"
return git_hash

import numpy
import pyscf

log.info("numpy:")
log.info(" > Version: %s" % numpy.__version__)
log.info(" > Git hash: %s" % get_git_hash(os.path.join(os.path.dirname(numpy.__file__), "..")))

log.info("pyscf:")
log.info(" > Version: %s" % pyscf.__version__)
log.info(" > Git hash: %s" % get_git_hash(os.path.join(os.path.dirname(pyscf.__file__), "..")))

log.info("ebcc:")
log.info(" > Version: %s" % __version__)
log.info(" > Git hash: %s" % get_git_hash(os.path.join(os.path.dirname(__file__), "..")))

# Environment variables
log.info("OMP_NUM_THREADS = %s" % os.environ.get("OMP_NUM_THREADS", ""))

log.debug("")

globals()["_EBCC_LOG_INITIALISED"] = True


def _check_output(*args, **kwargs):
"""
Call a command. If the return code is non-zero, an empty `bytes`
object is returned.
"""
try:
return subprocess.check_output(*args, **kwargs)
except subprocess.CalledProcessError:
return bytes()


ANSI = Namespace(
B="\x1b[1m",
H="\x1b[3m",
R="\x1b[m\x0f",
U="\x1b[4m",
b="\x1b[34m",
c="\x1b[36m",
g="\x1b[32m",
k="\x1b[30m",
m="\x1b[35m",
r="\x1b[31m",
w="\x1b[37m",
y="\x1b[33m",
)


def colour(text, *cs):
"""Colour a string."""
return f"{''.join([ANSI[c] for c in cs])}{text}{ANSI[None]}"
Loading

0 comments on commit 0aa3aae

Please sign in to comment.