# -*- coding: utf-8 -*-
# The turbomoleio package, a python interface to Turbomole
# for preparing inputs, parsing outputs and other related tools.
#
# Copyright (C) 2018-2022 BASF SE, Matgenix SRL.
#
# This file is part of turbomoleio.
#
# Turbomoleio is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Turbomoleio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with turbomoleio (see ~turbomoleio/COPYING). If not,
# see <https://www.gnu.org/licenses/>.
"""
Module with basic objects extracted from the stdout of Turbomole executables.
Rely on the Parser object to extract the data.
"""
import abc
import pprint
import numpy as np
import pandas as pd
from monty.json import MSONable
from pymatgen.core.structure import Molecule
from pymatgen.core.units import bohr_to_ang
from pymatgen.util.plotting import add_fig_kwargs
from turbomoleio.output.parser import Parser
[docs]
class BaseData(MSONable, abc.ABC):
"""Base class for the data extracted from the parser of the output files."""
[docs]
@classmethod
def from_file(cls, filepath):
"""
Generate an instance of the class from a file.
The file should contain the stdout of a Turbomole executable.
Args:
filepath (str): path to the file.
Returns:
An instance of the class.
"""
try:
with open(filepath, "r") as f:
return cls.from_string(f.read())
except UnicodeDecodeError:
with open(filepath, "r", errors="ignore") as f:
return cls.from_string(f.read())
[docs]
@classmethod
def from_string(cls, string):
"""
Generate an instance of the class from a string.
The string should contain the stdout of a Turbomole executable.
Args:
string (str): the string with output.
Returns:
An instance of the class.
"""
p = Parser(string)
return cls.from_parser(p)
[docs]
@classmethod
@abc.abstractmethod
def from_parser(cls, parser):
"""
Generate an instance of the class from a parser.
The parser is based on the stdout of a Turbomole executable.
Should return None if no data could be parsed.
Subclasses should use the parser to extract the relevant information.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
An instance of the class.
"""
pass # pragma: no cover
def __str__(self):
"""Get a string representation of the object with print of the as_dict()."""
string = "Content of the {} object".format(self.__class__.__name__)
string += pprint.pformat(self.as_dict())
return string
[docs]
class CosmoData(BaseData):
"""Information about the cosmo run. Inputs and outputs."""
def __init__(
self,
info=None,
parameters=None,
screening_charge=None,
energies=None,
element_radius=None,
):
"""Construct CosmoData object.
Args:
info (dict): initial info provided in the output by COSMO. Contains the
keys area and volume.
parameters (dict): COSMO parameters. Contains the keys nppa, nspa, nsph,
npspher, disex, disex2, rsolv, routf, phsran, ampran, cavity, epsilon,
refind, fepsi.
screening_charge (dict): values of the screening charge. Contains
the keys cosmo, correction, total.
energies (dict): total and dielectric energies.
Contains the keys total_energy, total_energy_oc_corr,
dielectric_energy and dielectric_energy_oc_corr.
element_radius (dict): to an Element object correspond values of the
radius and a list of sites of the corresponding element in the
molecule.
"""
self.info = info
self.parameters = parameters
self.screening_charge = screening_charge
self.energies = energies
self.element_radius = element_radius
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of CosmoData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Can be used for scf and escf/egrad.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
CosmoData.
"""
info = parser.cosmo_header
results = parser.cosmo_results
parameters = screening_charge = energies = element_radius = None
if results:
parameters = results["parameters"]
screening_charge = results["screening_charge"]
energies = results["energies"]
element_radius = {}
er_results = results["element_radius"] or {}
for el, el_data in er_results.items():
element_radius[el.capitalize()] = el_data
element_radius = element_radius or None
if all(
i is None
for i in [info, parameters, screening_charge, energies, element_radius]
):
return None
return cls(
info=info,
parameters=parameters,
screening_charge=screening_charge,
energies=energies,
element_radius=element_radius,
)
[docs]
class TurbomoleData(BaseData):
"""
Information about the Turbomole version and executable used.
Can be used for all Turbomole executables.
"""
def __init__(self, version=None, build=None, executable=None):
"""Construct TurbomoleData object.
Args:
version (str): number of the Turbomole version.
build (str): Turbomole build.
executable (str): name of the executable in the header of the output
"""
self.version = version
self.build = build
self.executable = executable
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of TurbomoleData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
TurbomoleData.
"""
header = parser.header
if not header:
return None
return cls(
version=header["tm_version"],
build=header["tm_build"],
executable=header["executable"],
)
[docs]
class RunData(BaseData):
"""
Information about where the calculation was executed and the timings.
Can be used for all Turbomole executables.
"""
def __init__(
self, host=None, start_time=None, end_time=None, cpu_time=None, wall_time=None
):
"""Construct RunData object.
Args:
host (str): name of the host.
start_time (datetime): initial time of the run.
end_time (datetime): end time of the run.
cpu_time (float): cpu time in seconds.
wall_time (float): wall time in seconds.
"""
self.host = host
self.start_time = start_time
self.end_time = end_time
self.cpu_time = cpu_time
self.wall_time = wall_time
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of RunData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
RunData.
"""
header = parser.header
time_data = parser.timings
if not header and not time_data:
return None
kwargs = {
"start_time": None,
"host": None,
"end_time": None,
"cpu_time": None,
"wall_time": None,
}
if header:
kwargs["start_time"] = header["start_time"]
kwargs["host"] = header["host"]
time_data = parser.timings
if time_data:
kwargs["end_time"] = time_data["end_time"]
kwargs["cpu_time"] = time_data["cpu_time"]
kwargs["wall_time"] = time_data["wall_time"]
return cls(**kwargs)
[docs]
class BasisData(BaseData):
"""
Information about the basis used for the calculation.
Can be used for most of the Turbomole executables (including the scf, escf, grad).
"""
def __init__(
self,
basis_per_specie=None,
aux_basis_per_specie=None,
number_scf_basis_func=None,
number_scf_aux_basis_func=None,
):
"""Construct BasisData object.
Args:
basis_per_specie (dict): dict with species as keys and name of the
basis as values.
aux_basis_per_specie (dict): dict with species as keys and name
of the auxiliary basis as values.
number_scf_basis_func (int): number of scf basis functions.
number_scf_aux_basis_func (int): number of auxialiary scf basis functions.
"""
self.basis_per_specie = basis_per_specie
self.aux_basis_per_specie = aux_basis_per_specie
self.number_scf_basis_func = number_scf_basis_func
self.number_scf_aux_basis_func = number_scf_aux_basis_func
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of BasisData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
BasisData.
"""
basis_data = parser.basis
if not basis_data:
return None
return cls(**basis_data)
[docs]
class SymmetryData(BaseData):
"""
Information on the symmetry of the molecule.
Can be used for all the TM executables (in some case only a part of the
information might be available).
"""
def __init__(self, symbol=None, n_reps=None, reps=None):
"""Construct SymmetryData object.
Args:
symbol (str): symbol of the symmetry of the molecule.
n_reps (int): number of representations
reps (list): the symbols of the representations.
"""
self.symbol = symbol
self.n_reps = n_reps
self.reps = reps
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of SymmetryData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
SymmetryData.
"""
sym_data = parser.symmetry
if not sym_data:
return None
return cls(**sym_data)
[docs]
class FunctionalData(BaseData):
"""
Information about the exchange-correlation functional.
Can be used for for scf, gradient and relax executables.
"""
def __init__(self, msg=None, name=None, func_type=None, xcfun=None):
"""Construct FunctionalData object.
Args:
msg (str): the full message in the output concerning the XC functional.
In case the parsing failed to identify the correct name it will provide
a way to figure out the information.
name (str): name of the XC functional.
func_type (str): type of the XC functional (e.g. GGA, LDA, ...).
xcfun (str): version of xcfun used, if present.
"""
self.msg = msg
self.name = name
self.func_type = func_type
self.xcfun = xcfun
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of FunctionalData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
FunctionalData.
"""
func_data = parser.density_functional_data
if not func_data:
return None
return cls(
msg=func_data["functional_msg"],
name=func_data["functional_name"],
func_type=func_data["functional_type"],
xcfun=func_data["xcfun"],
)
[docs]
class RiData(BaseData):
"""
Information about RI calculations.
Can be used for ridft and escf/egrad.
"""
def __init__(self, ricore=None, marij=None, rij_memory=None, rik=None):
"""Construct RiData object.
Args:
ricore (int): memory used in ricore.
marij (bool): True if marij approximation used.
rij_memory (int): memory for rij.
rik (bool): True if rik approximation used.
"""
self.ricore = ricore
self.marij = marij
self.rij_memory = rij_memory
self.rik = rik
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of RiData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
RiData.
"""
ri_data = parser.rij_info
if not ri_data:
return None
return cls(**ri_data)
[docs]
class DispersionCorrectionData(BaseData):
"""
Information about the dispersion correction used in the calculation.
Can be used for scf executables.
"""
def __init__(self, correction=None, en_corr=None):
"""Construct DispersionCorrectionData object.
Args:
correction (str): the name of the correction (e.g. D1, D2, ...).
en_corr (float): correction on the total energy.
"""
self.correction = correction
self.en_corr = en_corr
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of DispersionCorrectionData from a parser.
The paser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
DispersionCorrectionData.
"""
d = parser.dftd
if not d:
return None
return cls(**d)
[docs]
class DFTData(BaseData):
"""
The information about a dft calculation.
Can be used for scf, gradients and escf executables.
"""
def __init__(
self,
functional=None,
ri=None,
spherical_gridsize=None,
gridpoints=None,
dispersion_correction=None,
):
"""Construct a DFTData object.
Args:
functional (FunctionalData): information about the exchange-correlation
functional.
ri (RiData): information RI calculations.
spherical_gridsize (int): size of the grid for spherical integration.
gridpoints (int): number of points for spherical integration.
dispersion_correction (DispersionCorrectionData): information about
dispersion correction.
"""
self.functional = functional
self.ri = ri
self.spherical_gridsize = spherical_gridsize
self.gridpoints = gridpoints
self.dispersion_correction = dispersion_correction
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of DFTData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
DFTData.
"""
data = parser.density_functional_data
if not data:
spherical_gridsize = None
gridpoints = None
else:
spherical_gridsize = data["spherical_gridsize"]
gridpoints = data["gridpoints"]
func = FunctionalData.from_parser(parser)
ri = RiData.from_parser(parser)
disp = DispersionCorrectionData.from_parser(parser)
if all(i is None for i in [spherical_gridsize, gridpoints, func, ri, disp]):
return None
return cls(
functional=func,
ri=ri,
spherical_gridsize=spherical_gridsize,
gridpoints=gridpoints,
dispersion_correction=disp,
)
[docs]
class ScfIterationData(BaseData):
"""
Details about the iteration in a scf calculation.
It contains the value for each step as lists, but only keeps the initial
and final indices to avoid wasting space. The index may start from
values higher than 1 if it is a restart of a previous scf calculation.
"""
def __init__(
self,
energies=None,
first_index=None,
n_steps=None,
dampings=None,
converged=None,
):
"""Construct ScfIterationData object.
Args:
energies (list): values of the energies for each step of the scf loop.
first_index (int): first index of the loop.
n_steps (int): number of scf steps.
dampings (list): values of the dampings for each step of the scf loop.
converged (bool): True if the calculation converged.
"""
self.energies = energies
self.first_index = first_index
self.n_steps = n_steps
self.dampings = dampings
self.converged = converged
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of ScfIterationData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
ScfIterationData.
"""
iter_data = parser.scf_iterations
if not iter_data:
return None
return cls(
energies=iter_data["energies"],
first_index=iter_data["first_index"],
n_steps=iter_data["n_steps"],
dampings=iter_data["dampings"],
converged=iter_data["converged"],
)
[docs]
@add_fig_kwargs
def plot_energies(self, ax=None, **kwargs):
"""
Plot the evolution of the energies in the scf loop.
Args:
ax (Axes): a matplotlib Axes or None if a new figure should be created.
kwargs: arguments passed to matplotlib plot function.
Returns:
A matplotlib Figure.
"""
try:
from pymatgen.util.plotting import get_ax_fig_plt
ax, fig, plt = get_ax_fig_plt(ax=ax) # pragma: no cover (old pymatgen)
except ImportError:
from pymatgen.util.plotting import get_ax_fig
ax, fig = get_ax_fig(ax=ax)
ax.plot(range(len(self.energies)), self.energies, **kwargs)
ax.set_xlabel("Steps")
ax.set_ylabel("Energy")
return fig
[docs]
class ScfData(BaseData):
"""
Information about options and operations in an scf calculation.
Can be used for scf calculations.
"""
def __init__(
self,
iterations=None,
diis=None,
diis_error_vect=None,
conv_tot_en=None,
conv_one_e_en=None,
virtual_orbital_shift_on=None,
virtual_orbital_shift_limit=None,
orbital_characterization=None,
restart_file=None,
n_occupied_orbitals=None,
):
"""Construct an ScfData object.
Args:
iterations (ScfIterationData): details about the iteration in the
scf loop.
diis (bool): True if DIIS is switched on.
diis_error_vect (str): type of DIIS error vector.
conv_tot_en (float): criterion for scf convergence on increment
of total energy.
conv_one_e_en (float): criterion for scf convergence on increment of
one-electron energy.
virtual_orbital_shift_on (bool): True if automatic virtual orbital
shift is switched on.
virtual_orbital_shift_limit (float): automatic virtual orbital shift
switched when e(lumo)-e(homo) lower than this value.
orbital_characterization (str): type of orbital characterization.
restart_file (str): file to which restart information will be dumped.
n_occupied_orbitals (int): number of occupied orbitals.
"""
self.iterations = iterations
self.diis = diis
self.diis_error_vect = diis_error_vect
self.conv_tot_en = conv_tot_en
self.conv_one_e_en = conv_one_e_en
self.virtual_orbital_shift_on = virtual_orbital_shift_on
self.virtual_orbital_shift_limit = virtual_orbital_shift_limit
self.orbital_characterization = orbital_characterization
self.restart_file = restart_file
self.n_occupied_orbitals = n_occupied_orbitals
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of ScfData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
ScfData.
"""
iterations = ScfIterationData.from_parser(parser)
scf_data = parser.pre_scf_run
if not iterations and not scf_data:
return None
kwargs = {}
if iterations: # pragma: no branch (trivial)
kwargs["iterations"] = iterations
if scf_data: # pragma: no branch (trivial)
kwargs.update(scf_data)
return cls(**kwargs)
[docs]
class ScfEnergiesData(BaseData):
"""
Final energies and different contributions obtained from an scf calculation.
Can be used for scf calculations.
"""
def __init__(
self,
total_energy=None,
kinetic_energy=None,
potential_energy=None,
virial_theorem=None,
wavefunction_norm=None,
coulomb_energy=None,
xc_energy=None,
ts_energy=None,
free_energy=None,
sigma0_energy=None,
):
"""Construct ScfEnergiesData object.
Args:
total_energy (float): Total energy of the system.
kinetic_energy (float): Kinetic energy of the system.
potential_energy (float): Potential energy of the system.
virial_theorem (float): Value of the virial theorem.
wavefunction_norm (float): Norm of the wavefunction.
coulomb_energy (float): Coulomb energy of the system.
xc_energy (float): Exchange-correlation energy of the system.
ts_energy (float): Entropic energy of the system.
free_energy (float): Free energy of the system.
sigma0_energy (float): "Sigma0" energy of the system.
"""
self.total_energy = total_energy
self.kinetic_energy = kinetic_energy
self.potential_energy = potential_energy
self.virial_theorem = virial_theorem
self.wavefunction_norm = wavefunction_norm
self.coulomb_energy = coulomb_energy
self.xc_energy = xc_energy
self.ts_energy = ts_energy
self.free_energy = free_energy
self.sigma0_energy = sigma0_energy
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of ScfEnergiesData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
ScfEnergiesData.
"""
en_data = parser.scf_energies
riper_en_data = parser.riper_scf_energies
if en_data:
if riper_en_data:
raise RuntimeError(
"Found scf energy data from dscf/ridft as well as from riper."
)
return cls(**en_data)
elif riper_en_data:
return cls(**riper_en_data)
return None
[docs]
class ElectrostaticMomentsData(BaseData):
"""
The data of the electrostatic moments (charge, dipole and quadrupole).
Can be used for scf executables.
"""
def __init__(
self,
charge=None,
unrestricted_electrons=None,
dipole_vector=None,
dipole_norm=None,
quadrupole_tensor=None,
quadrupole_trace=None,
quadrupole_anisotropy=None,
):
"""Construct ElectrostaticMomentsData object.
Args:
charge (float): total charge.
unrestricted_electrons (float): number of unrestricted electrons.
dipole_vector (list): the 3 components of the dipole moment.
dipole_norm (float): norm of the dipole moment.
quadrupole_tensor (list): 3x3 matrix with the quadrupole tensor.
quadrupole_trace (float): trace of the quadrupole tensor.
quadrupole_anisotropy (float): anisotropy of the quadrupole tensor.
"""
self.charge = charge
self.unrestricted_electrons = unrestricted_electrons
self.dipole_vector = dipole_vector
self.dipole_norm = dipole_norm
self.quadrupole_tensor = quadrupole_tensor
self.quadrupole_trace = quadrupole_trace
self.quadrupole_anisotropy = quadrupole_anisotropy
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of ElectrostaticMomentsData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
ElectrostaticMomentsData.
"""
mom_data = parser.electrostatic_moments
if not mom_data:
return None
dip = mom_data["dipole"]
quad = mom_data["quadrupole"]
return cls(
charge=mom_data["charge"],
unrestricted_electrons=mom_data["unrestricted_electrons"],
dipole_vector=dip["moment"],
dipole_norm=dip["norm"],
quadrupole_tensor=quad["moment"],
quadrupole_trace=quad["trace"],
quadrupole_anisotropy=quad["anisotropy"],
)
[docs]
class GeometryData(BaseData):
"""
Data of the geometry of the system: molecule and centers.
Can be used for most of the Turbomole executables (including the scf, escf, grad).
"""
def __init__(self, center_of_mass=None, center_of_charge=None, molecule=None):
"""Construct GeometryData object.
Args:
center_of_mass (list): 3D vector with the position of the center of mass.
center_of_charge: 3D vector with the position of the center of charge.
molecule (Molecule): the molecule in the system.
"""
self.center_of_mass = center_of_mass
self.center_of_charge = center_of_charge
self.molecule = molecule
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of GeometryData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
GeometryData.
"""
centers_data = parser.centers
coords_data = parser.coordinates
if (
not centers_data or all(v is None for k, v in centers_data.items())
) and not coords_data:
return None
kwargs = dict(center_of_mass=None, center_of_charge=None, molecule=None)
if centers_data: # pragma: no branch (trivial)
kwargs.update(centers_data)
if coords_data: # pragma: no branch (trivial)
species = [s.capitalize() for s in coords_data["species"]]
coords = np.array(coords_data["coords"]) * bohr_to_ang
mol = Molecule(species, coords)
kwargs["molecule"] = mol
return cls(**kwargs)
[docs]
class SpinData(BaseData):
"""
Information about the spin in the calculation.
Can be used for scf, gradient and escf executables.
"""
def __init__(self, unrestricted=None, s2=None):
"""Construct SpinData object.
Args:
unrestricted (bool): True if is an uhf calculation.
s2 (float): S^2 value of the spin.
"""
self.unrestricted = unrestricted
self.s2 = s2
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of SpinData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
SpinData.
"""
return cls(unrestricted=parser.is_uhf, s2=parser.s2)
[docs]
class SmearingData(BaseData):
"""
Information about the smearing ($fermi datagroup).
Can be used for scf executables.
"""
def __init__(
self,
initial_elec_temp=None,
final_elec_temp=None,
annealing_factor=None,
annealing_homo_lumo_gap_limit=None,
smearing_de_limit=None,
):
"""Construct SmearingData object.
Args:
initial_elec_temp (float): initial electron temperature.
final_elec_temp (float): final electron temperature.
annealing_factor (float): annealing factor
annealing_homo_lumo_gap_limit (float): annealing if HOMO-LUMO gap lower
than this value.
smearing_de_limit (float): smearing switched off if DE lower than
this value.
"""
self.initial_elec_temp = initial_elec_temp
self.final_elec_temp = final_elec_temp
self.annealing_factor = annealing_factor
self.annealing_homo_lumo_gap_limit = annealing_homo_lumo_gap_limit
self.smearing_de_limit = smearing_de_limit
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of SmearingData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
SmearingData.
"""
fermi_data = parser.fermi
if not fermi_data:
return None
return cls(**fermi_data)
[docs]
class IntegralData(BaseData):
"""
Data about the thresholds for integrals.
Can be used for for scf, gradien and escf executables.
"""
def __init__(self, integral_neglect_threshold=None, thize=None, thime=None):
"""Construct IntegralData object.
Args:
integral_neglect_threshold (float): integral neglect threshold.
thize (float): integral storage threshold THIZE.
thime (float): integral storage threshold THIME.
"""
self.integral_neglect_threshold = integral_neglect_threshold
self.thize = thize
self.thime = thime
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of IntegralData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
IntegralData.
"""
integral_data = parser.integral
if not integral_data:
return None
return cls(**integral_data)
[docs]
class EscfIterationData(BaseData):
"""
Details about the iteration in an escf calculation.
It contains the data for a list of steps. For each one a sublist of the convergence
information converning each of the irreps treated as excited states.
"""
def __init__(self, steps=None, converged=None):
"""Construct EscfIterationData object.
Args:
steps (list): list of lists of lists (3 dimensions). One element of the
list for each escf step. One element of the sublist for each irrep
dealt with. The inner list contains the convergence information:
[name of the irrep, number of converged roots, euclidean residual norm]
converged (bool): True if converged.
"""
self.steps = steps
self.converged = converged
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of EscfIterationData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
EscfIterationData.
"""
iter_data = parser.escf_iterations
if not iter_data:
return None
return cls(**iter_data)
[docs]
class SingleExcitation(MSONable):
"""
Auxiliary object to store the information about an escf/egrad calculation.
It cannot be extracted directly from the string, being present several
times in the output of an escf/egrad calculation.
"""
def __init__(
self,
tot_en=None,
osc_stre=None,
rot_stre=None,
dominant_contributions=None,
moments_columns=None,
):
"""Construct SingleExcitation object.
Args:
tot_en (float): total energy.
osc_stre (float): oscillator strength, length representation.
rot_stre (float): rotatory strength, length representation.
dominant_contributions (list): list of all the dominant contributions.
Each element is a dictionary with "occ_orb" (a dictionary with index
of the occupied orbital, irrep, energy and spin), "virt_orb"
(same as "occ_orb") and "coeff" (the coefficient of the contribution).
moments_columns (list): a list of dictionaries containing the electric
and magnetic moments for each column. Each dictionary should contain
the following keys:
"electric_dipole" (3D vector with the electronic dipole as a list),
"magnetic_dipole" (3D vector with the magnetic dipole as a list) and
"electric_quadrupole" (description of the electronic quadrupole
with keys "moment", i.e. the 3x3 matrix of the quadrupole moment,
"trace" and "anisotropy").
"""
self.tot_en = tot_en
self.osc_stre = osc_stre
self.rot_stre = rot_stre
self.dominant_contributions = dominant_contributions
self.moments_columns = moments_columns
[docs]
class EscfData(BaseData):
"""
Output data of an escf calculation.
Can be used for escf and egrad.
"""
def __init__(
self,
calc_type=None,
iterations=None,
residuum_convergence_criterium=None,
n_occupied_orbitals=None,
orbital_characterization=None,
max_davidson_iter=None,
machine_precision=None,
max_core_mem=None,
max_cao_basis_vectors=None,
max_treated_vectors=None,
irrep_data=None,
gs_tot_en=None,
excitations=None,
):
"""Construct EscfData object.
Args:
calc_type (str): string describing the calculation type (e.g.
'RPA SINGLET-EXCITATION').
iterations (EscfIterationData): the data about the escf iterations.
residuum_convergence_criterium (float): residuum convergence criterium.
n_occupied_orbitals (int): number of occupied orbitals.
orbital_characterization (str): description of how the orbital were
converged in scf.
max_davidson_iter (int): maximum number of Davidson iterations.
machine_precision (float): machine precision.
max_core_mem (int): maximum core memory in MB.
max_cao_basis_vectors (int): number of maximum CAO vectors in memory.
max_treated_vectors (int): maximum number of simultaneously treated
vectors, including degeneracy.
irrep_data (dict): keys are strings with the name of the irrep, values
are tuples with [tensor space dimension, number of roots].
gs_tot_en (float): total energy of the ground state. Turbomole extracts
it from the output of the scf calculation. If that is missing
Turbomole sets this value to 0 in the output of escf.
excitations (dict): keys are the name of the irreps and values are lists
of SingleExcitation.
"""
self.calc_type = calc_type
self.iterations = iterations
self.residuum_convergence_criterium = residuum_convergence_criterium
self.n_occupied_orbitals = n_occupied_orbitals
self.orbital_characterization = orbital_characterization
self.max_davidson_iter = max_davidson_iter
self.machine_precision = machine_precision
self.max_core_mem = max_core_mem
self.max_cao_basis_vectors = max_cao_basis_vectors
self.max_treated_vectors = max_treated_vectors
self.irrep_data = irrep_data
self.gs_tot_en = gs_tot_en
self.excitations = excitations
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of EscfData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
EscfData.
"""
iterations = EscfIterationData.from_parser(parser)
excitations = parser.escf_excitations
escf_data = parser.pre_escf_run
if not iterations and not escf_data and not excitations:
return None
kwargs = dict(
calc_type=None,
residuum_convergence_criterium=None,
n_occupied_orbitals=None,
orbital_characterization=None,
max_davidson_iter=None,
machine_precision=None,
max_core_mem=None,
max_cao_basis_vectors=None,
max_treated_vectors=None,
irrep_data=None,
)
kwargs["iterations"] = iterations
converted_excitations = {}
for irrep, l in excitations.items():
converted_excitations[irrep] = [SingleExcitation(**d) for d in l]
kwargs["excitations"] = converted_excitations
kwargs["gs_tot_en"] = parser.escf_gs_total_en
if escf_data: # pragma: no branch (trivial)
kwargs.update(escf_data)
return cls(**kwargs)
[docs]
class StatptData(BaseData):
"""
Initial information provided in statpt.
Can be used only for statpt.
"""
def __init__(
self,
max_trust_radius=None,
min_trust_radius=None,
init_trust_radius=None,
min_grad_norm_for_gdiis=None,
prev_steps_for_gdiis=None,
hessian_update_method=None,
thr_energy_change=None,
thr_max_displ=None,
thr_max_grad=None,
thr_rms_displ=None,
thr_rms_grad=None,
use_defaults=None,
final_step_radius=None,
):
"""Construct StatptData object.
Args:
max_trust_radius (float): maximum allowed trust radius.
min_trust_radius (float): minimum allowed trust radius
init_trust_radius (float): initial trust radius.
min_grad_norm_for_gdiis (float): GDIIS used if gradient norm is lower
than this value.
prev_steps_for_gdiis (int): number of previous steps for GDIIS.
hessian_update_method (str): hessian update method.
thr_energy_change (float): threshold for energy change.
thr_max_displ (float): threshold for max displacement element.
thr_max_grad (float): threshold for max gradient element.
thr_rms_displ (float): threshold for RMS of displacement.
thr_rms_grad (float): threshold for RMS of gradient.
use_defaults (bool): True if $statpt was not defined and using
default options.
final_step_radius (float): final step radius.
"""
self.max_trust_radius = max_trust_radius
self.min_trust_radius = min_trust_radius
self.init_trust_radius = init_trust_radius
self.min_grad_norm_for_gdiis = min_grad_norm_for_gdiis
self.prev_steps_for_gdiis = prev_steps_for_gdiis
self.hessian_update_method = hessian_update_method
self.thr_energy_change = thr_energy_change
self.thr_max_displ = thr_max_displ
self.thr_max_grad = thr_max_grad
self.thr_rms_displ = thr_rms_displ
self.thr_rms_grad = thr_rms_grad
self.use_defaults = use_defaults
self.final_step_radius = final_step_radius
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of StatptData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
StatptData.
"""
statpt_data = parser.statpt_info
if not statpt_data:
return None
return cls(**statpt_data)
[docs]
class RelaxData(BaseData):
"""
Initial information provided in relax.
Can be used only for relax.
"""
def __init__(self, optimizations=None, thr_int_coord=None):
"""Construct RelaxData object.
Args:
optimizations (list): list of strings describing with respect to what the
optimization has been performed.
thr_int_coord (float): convergence criterion for internal coordinates.
"""
self.optimizations = optimizations
self.thr_int_coord = thr_int_coord
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of RelaxData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
RelaxData.
"""
relax_data = parser.relax_info
if not relax_data:
return None
return cls(**relax_data)
[docs]
class RelaxGradientsData(BaseData):
"""
Data about gradients in a relaxation calculation.
Gradient values are extracted from the relax/stapt output that take into account
the frozen coordinates.
Can be used for relax and statpt.
"""
def __init__(self, norm_cartesian=None, norm_internal=None, max_internal=None):
"""Construct RelaxGradientsData object.
Args:
norm_cartesian (float): norm of the cartesian gradient.
norm_internal (float): norm of the internal gradient.
max_internal (float): maximum norm of the internal gradient (available
only in relax).
"""
self.norm_cartesian = norm_cartesian
self.norm_internal = norm_internal
self.max_internal = max_internal
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of RelaxGradientsData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
RelaxGradientsData.
"""
grad_data = parser.relax_gradient_values
if not grad_data:
return None
return cls(**grad_data)
[docs]
class RelaxConvergenceData(BaseData):
"""
Final information about relaxation convergence.
This is obtained according to what is defined in the control file.
Can be used for relax and statpt.
"""
def __init__(
self,
energy_change=None,
rms_displ=None,
max_displ=None,
rms_grad=None,
max_grad=None,
):
"""Construct RelaxConvergenceData object.
Args:
energy_change (dict): information about the convergence of the
energy change. Contains "value" with the actual value, "thr"
with the specified threshold, and "conv" a bool specifying whether
the convergence for this threshold has been achieved or not.
rms_displ (dict): information about the convergence of the rms of the
displacements. Same structure as "energy_change".
max_displ (dict): information about the convergence of the maximum of
the displacements. Same structure as "energy_change".
rms_grad (dict): information about the convergence of the rms of the
gradient. Same structure as "energy_change".
max_grad (dict): information about the convergence of the maximum of the
gradient. Same structure as "energy_change".
"""
self.energy_change = energy_change
self.rms_displ = rms_displ
self.max_displ = max_displ
self.rms_grad = rms_grad
self.max_grad = max_grad
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of RelaxConvergenceData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
RelaxConvergenceData.
"""
conv_data = parser.relax_conv_info
if not conv_data:
return None
return cls(**conv_data)
[docs]
class AoforceNumericalIntegrationData(BaseData):
"""
Information about the numerical integration in aoforce.
Will be present only if a proper aoforce is run. Absent if running after numforce.
"""
def __init__(
self,
core_memory_dft=None,
memory_per_atom=None,
atoms_per_loop=None,
construction_timings=None,
):
"""Construct AoforceNumericalIntegrationData object.
Args:
core_memory_dft (int): remaining core memory for DFT in MB.
memory_per_atom (int): memory needed per atom in KB.
atoms_per_loop (int): atoms per loop corresponding to the memory.
construction_timings (list): a list of lists with construction timings.
For each element:
[timing_description, cpu_time, wall_time]
with timing_description being the description of the timing, cpu_time
being the CPU time in seconds and wall_time, the wall time in seconds.
"""
self.core_memory_dft = core_memory_dft
self.memory_per_atom = memory_per_atom
self.atoms_per_loop = atoms_per_loop
self.construction_timings = construction_timings
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of AoforceNumericalIntegrationData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
AoforceNumericalIntegrationData.
"""
data = parser.aoforce_numerical_integration
if not data:
return None
kwargs = {"construction_timings": data["construction_timings"]}
kwargs.update(data["memory"])
return cls(**kwargs)
[docs]
class AoforceRotationalData(BaseData):
"""Analysis of rotational states in aoforce."""
def __init__(self, b=None, intensities=None, m=None, dipole_moment=None):
"""Construct AoforceRotationalData object.
Args:
b (float): 3D vector with rotational constants b in cm^-1.
intensities (list): 3D vector with optical intensities in a.u.
m (list): 3x3 matrix.
dipole_moment (list): 3D vector with dipole moment in principle
axis system in a.u.
"""
self.b = b
self.intensities = intensities
self.m = m
self.dipole_moment = dipole_moment
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of AoforceRotationalData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
AoforceRotationalData.
"""
data = parser.aoforce_analysis
if not data or "rotational" not in data:
return None
return cls(**data["rotational"])
[docs]
class AoforceVibrationalData(BaseData):
"""
Analysis of vibrational states in aoforce.
Results are stored as lists, each value corresponds to the frequency
in the "frequencies" list with the same index.
"""
def __init__(
self,
frequencies=None,
symmetries=None,
ir=None,
dDIP_dQ=None,
intensities=None,
intensities_perc=None,
raman=None,
eigenvectors=None,
reduced_masses=None,
energies=None,
):
"""Construct AoforceVibrationalData object.
Args:
frequencies (list): vibrational frequencies in cm^-1.
symmetries (list): string with the name of the symmetry of the modes.
None if not specified, like for the first 6 modes.
ir (list): booleans stating whether if the mode is ir or not.
None if "-" in the output file.
dDIP_dQ (list): normal mode derivatives of the dipole moment in a.u.
intensities (list): intensity in km/mol.
intensities_perc (list): intensity percentage.
raman (list):booleans stating whether if the mode is raman or not.
None if "-" in the output file.
eigenvectors (list): matrix with shape (num frequencies, number of atoms, 3)
with the eigenvectors for each atom.
reduced_masses (list): reduced masses in g/mol.
energies (dict): the values of the vibrational energies.
A dictionary with "zpve", "scf" and "total" as keys.
"""
self.frequencies = frequencies
self.symmetries = symmetries
self.ir = ir
self.dDIP_dQ = dDIP_dQ
self.intensities = intensities
self.intensities_perc = intensities_perc
self.raman = raman
self.eigenvectors = eigenvectors
self.reduced_masses = reduced_masses
self.energies = energies
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of AoforceVibrationalData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
AoforceVibrationalData.
"""
data = parser.aoforce_analysis
if not data or "vibrational" not in data:
return None
return cls(**data["vibrational"])
[docs]
def get_freqs_df(self):
"""
Generate a pandas DataFrame with the frequencies and their properties.
Returns:
DataFrame
"""
cols = np.column_stack(
[
self.frequencies,
self.symmetries,
self.intensities,
self.intensities_perc,
self.ir,
self.raman,
self.dDIP_dQ,
self.reduced_masses,
]
)
labels = [
"frequency",
"symmetry",
"intensity",
"intensity %",
"IR",
"Raman",
"|dDIP/dQ|",
"red. mass",
]
return pd.DataFrame(cols, columns=labels)
[docs]
def n_negative_freqs(self, tol=0.1):
"""
Get the number of negative frequencies.
Frequencies are considered negative if they satisfy the condition
f < -abs(tol).
Args:
tol (float): tolerance for considering a frequency as negative.
Returns:
int: the number of negative frequencies.
"""
return np.count_nonzero(np.array(self.frequencies) < -np.abs(tol))
[docs]
def n_positive_freqs(self, tol=0.1):
"""
Get the number of positive frequencies.
Frequencies are considered positive if they satisfy the condition
f > abs(tol).
Args:
tol (float): tolerance for considering a frequency as positive.
Returns:
int: the number of positive frequencies.
"""
return np.count_nonzero(np.array(self.frequencies) > np.abs(tol))
[docs]
def n_zero_freqs(self, tol=0.1):
"""
Get the number of frequencies close to zero.
Frequencies are considered close to zero if they satisfy the condition
-abs(tol) > f > abs(tol).
Args:
tol (float): tolerance for considering a frequency as zero.
Returns:
int: the number of zero frequencies.
"""
f_arr = np.array(self.frequencies)
return np.count_nonzero(
np.logical_and(-np.abs(tol) <= f_arr, f_arr <= np.abs(tol))
)
[docs]
class MP2Data(BaseData):
"""Data object containing parameters used to run the MP2 calculation."""
def __init__(self, energy_only=None):
"""Construct MP2Data object.
Args:
energy_only (bool): whether this is an energy-only MP2 calculation.
"""
self.energy_only = energy_only
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of MP2Data from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
MP2Data.
"""
data = parser.mp2_data
if not data: # pragma: no branch
return None
return cls(**data) # pragma: no cover
[docs]
class MP2Results(BaseData):
"""Results from an MP2 calculation."""
def __init__(self, energy=None):
"""Construct MP2Results object.
Args:
energy (float): MP2 energy.
"""
self.energy = energy
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of MP2Results from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
MP2Results.
"""
data = parser.mp2_results
if not data or "energy" not in data or data["energy"] is None:
return None
return cls(**data)
[docs]
class PeriodicityData(BaseData):
"""Information about periodicity."""
def __init__(
self,
periodicity=None,
lattice_params=None,
shortest_interatomic_distance=None,
direct_space_vectors=None,
reciprocal_space_vectors=None,
):
"""Construct PeriodicityData object.
Args:
periodicity (int): Periodicity of the system (1, 2 or 3).
lattice_params: Lattice parameters of the system (in Angstroms).
For 1D systems, a single number: [a].
For 2D systems, three numbers: [a, b, gamma], i.e. the two lattice
parameters and the angle between them.
For 3D systems, three numbers: [a, b, c, alpha, beta, gamma], i.e.
three two lattice parameters and the angles between them.
shortest_interatomic_distance: Shortest interatomic distance in the
system (in Angstroms).
direct_space_vectors: Lattice vectors of the system (in Angstroms).
The first vector will always be aligned with the x cartesian direction.
For 2D and 3D, the second vector is always in the xy cartesian plane.
reciprocal_space_vectors: Reciprocal lattice vectors of the system (in
Angstroms^-1). The physics definition of the reciprocal lattice
vectors is used here:
.. code-block:: text
b1 = 2*pi/V * (a2 x a3)
b2 = 2*pi/V * (a3 x a1)
b3 = 2*pi/V * (a1 x a2)
The crystallographer's definition is easily recovered as:
.. code-block:: text
b'1 = b1 / (2*pi)
b'2 = b2 / (2*pi)
b'3 = b3 / (2*pi)
Returns:
PeriodicityData.
"""
self.periodicity = periodicity
self.lattice_params = lattice_params
self.shortest_interatomic_distance = shortest_interatomic_distance
self.direct_space_vectors = direct_space_vectors
self.reciprocal_space_vectors = reciprocal_space_vectors
[docs]
@classmethod
def from_parser(cls, parser):
"""
Generate an instance of PeriodicityData from a parser.
The parser is based on the stdout of a Turbomole executable.
Returns None if no data could be parsed.
Args:
parser (Parser): the parser to be used to extract the data.
Returns:
PeriodicityData.
"""
data = parser.periodicity_data
if not data:
return None
periodicity = data["periodicity"]
if periodicity == 1:
# a
lattice_params = [bohr_to_ang * data["tm_lattice_params"][0]]
elif periodicity == 2:
# a, b, gamma
lattice_params = [
bohr_to_ang * data["tm_lattice_params"][0],
bohr_to_ang * data["tm_lattice_params"][1],
data["tm_lattice_params"][2],
]
elif periodicity == 3: # pragma: no branch
# a, b, c, alpha, beta, gamma
lattice_params = [
bohr_to_ang * data["tm_lattice_params"][0],
bohr_to_ang * data["tm_lattice_params"][1],
bohr_to_ang * data["tm_lattice_params"][2],
data["tm_lattice_params"][3],
data["tm_lattice_params"][4],
data["tm_lattice_params"][5],
]
else: # pragma: no cover (should not occur...)
raise RuntimeError('"periodicity" should be 1, 2 or 3.')
direct_space_vectors = [
[bohr_to_ang * xx for xx in vect] for vect in data["direct_space_vectors"]
]
reciprocal_space_vectors = [
[xx / bohr_to_ang for xx in vect]
for vect in data["reciprocal_space_vectors"]
]
return cls(
periodicity=periodicity,
lattice_params=lattice_params,
shortest_interatomic_distance=bohr_to_ang
* data["shortest_interatomic_distance"],
direct_space_vectors=direct_space_vectors,
reciprocal_space_vectors=reciprocal_space_vectors,
)