Module reference

The GoodVibes Python package is organised into the modules listed below. Most users will only need goodvibes.api (the v4.2 façade); the lower-level modules are documented for advanced/embedded use.

Public API (v4.2)

Programmatic API façade for GoodVibes (v4.2 item 5).

A small import-friendly entry point for notebooks and scripts. Replaces the 15-positional-arg calc_bbe constructor with kwargs that have the same defaults as the CLI, and returns a structured ThermoResult rather than the raw calc_bbe instance.

from goodvibes import compute_thermo, compute_batch r = compute_thermo(“file.log”, QH=True, spc=”TZ”) print(r.qh_gibbs_free_energy)

rs = compute_batch(glob.glob(”*.log”))

Internally just calls calc_bbe; no behavior change relative to the CLI. The underlying calc_bbe and QCData instances stay accessible via result.bbe / result.qcdata for advanced use (e.g. PES analysis, direct attribute reads not yet promoted to the result dataclass).

class goodvibes.api.ThermoResult(file: str, name: str, scf_energy: float | None, sp_energy: float | None, zpe: float | None, enthalpy: float | None, qh_enthalpy: float | None, entropy: float | None, qh_entropy: float | None, gibbs_free_energy: float | None, qh_gibbs_free_energy: float | None, frequency_wn: List[float] | None, im_frequency_wn: List[float] | None, inverted_freqs: List[float] | None, point_group: str | None, symmno: int | None, linear_mol: bool, multiplicity: int | None, job_type: str | None, level_of_theory: str | None, program: str | None, bbe: Any, qcdata: Any, spc_applied: bool = False, temperature: float | None = None, options: Any = None, freq_scale_factor: float | None = None, zpe_scale_factor: float | None = None, scale_factor_source: str | None = None, symmetry_source: str | None = None, n_imag: int | None = None)[source]

Bases: object

Bundle of thermochemistry for one structure.

All numeric fields are in atomic units (Hartree for energies, Hartree/K for entropies); frequencies are cm⁻¹. Fields that aren’t available for the given file (e.g. qh_enthalpy when QH=False, sp_energy when spc=None) are reported as None rather than a sentinel.

bbe: Any
enthalpy: float | None
entropy: float | None
file: str
freq_scale_factor: float | None = None
frequency_wn: List[float] | None
gibbs_free_energy: float | None
property has_thermo: bool

True when calc_bbe found enough information to compute G(T). False for SP-only outputs (no frequency block).

im_frequency_wn: List[float] | None
inverted_freqs: List[float] | None
job_type: str | None
level_of_theory: str | None
linear_mol: bool
multiplicity: int | None
n_imag: int | None = None
name: str
options: Any = None
point_group: str | None
program: str | None
qcdata: Any
qh_enthalpy: float | None
qh_entropy: float | None
qh_gibbs_free_energy: float | None
scale_factor_source: str | None = None
scf_energy: float | None
sp_energy: float | None
spc_applied: bool = False
symmetry_source: str | None = None
symmno: int | None
temperature: float | None = None
zpe: float | None
zpe_scale_factor: float | None = None
goodvibes.api.bbe_to_result(bbe: Any, path: str | None = None, *, level_of_theory: str | None = None) → ThermoResult[source]

Project a calc_bbe instance into a ThermoResult.

Useful for adapting CLI internals (which keep thermo_data as a {path: calc_bbe} dict) to the structured API without re-parsing. level_of_theory is read from the file via read_initial() if not supplied; pass it explicitly to avoid the extra file scan.

goodvibes.api.compute_batch(paths: Sequence[Any], *, jobs: int = 1, **kwargs: Any) → List[ThermoResult][source]

Compute thermochemistry for a list of files or QCData objects.

Parameters:
  • paths – QC output file paths and/or parsed QCData instances (e.g. from QCData.from_atoms in an MLIP workflow).

  • jobs – parallelism level. 1 (default) is sequential — no process-pool overhead. > 1 spawns that many worker processes via concurrent.futures.ProcessPoolExecutor. 0 or negative uses os.cpu_count() (or 1 if unknown).

  • **kwargs – forwarded unchanged to every compute_thermo call.

Returns:

Results in input order (matches executor.map’s contract). Workers can’t write to the orchestrator’s logger, so per-file log.info from inside calc_bbe is silenced under jobs > 1; warnings that surface through ThermoResult (e.g. missing-frequency files) still come through unchanged.

goodvibes.api.compute_thermo(path: str | None = None, *, qcdata: Any = None, QS: str = 'grimme', QH: bool = False, s_freq_cutoff: float = 100.0, h_freq_cutoff: float = 100.0, temperature: float = 298.15, concentration: float | None = None, freq_scale_factor: float | None = None, zpe_scale_factor: float | None = None, solv: str | None = None, spc: str | None = None, invert: float | None = None, symm: bool = False, inertia: str = 'global', strict_spc: bool = False) → ThermoResult[source]

Compute thermochemistry for one QC output file.

Pass either path (a filesystem location) or a pre-parsed qcdata (skips re-parsing). When concentration is None the gas-phase reference at 1 atm is used (P / RT).

Parameters mirror the CLI:

QS ‘grimme’ (default) or ‘truhlar’ quasi-harmonic entropy QH apply Head-Gordon quasi-harmonic enthalpy correction s_freq_cutoff entropy cutoff (cm⁻¹) h_freq_cutoff enthalpy cutoff (cm⁻¹) — only used when QH=True temperature K concentration mol/L. None → gas phase 1 atm. freq_scale_factor None → auto-lookup harm_fac from level of theory.

Applied to the partition-function frequencies (used in H_vib and S_vib).

zpe_scale_factor None → auto-lookup zpe_fac from level of theory.

Applied to ZPE only. If freq_scale_factor is explicitly set but zpe_scale_factor is None, ZPE inherits freq_scale_factor (back-compat).

solv ‘none’, or solvent name for free-space correction spc None, ‘link’, or filename suffix for SPC files invert None, or threshold for converting small imag → real symm apply pymsym symmetry-number correction strict_spc raise MissingSinglePointError when spc is set but

the single-point energy is missing or unparseable (default: RuntimeWarning and the frequency-level energy is used)

goodvibes.api.to_dataframe(results: Sequence[ThermoResult])[source]

Convert a list of ThermoResults into a pandas DataFrame.

Pandas is an optional dependency; raises ImportError with an install hint if it isn’t available. The DataFrame has one row per result and columns for every public scalar field on ThermoResult (omits the bbe and qcdata references and the frequency lists). The imaginary frequencies are kept as im_frequency_wn: a space-separated string in cm⁻¹, as --imag prints them, or None when the structure has none.

goodvibes.api.to_parquet(results: Sequence[ThermoResult], path: str) → None[source]

Write a list of ThermoResults to a Parquet file at path.

Same column set as to_dataframe. Requires pandas + a Parquet engine (pyarrow or fastparquet); install with pip install goodvibes[full] or pip install pyarrow.

CLI orchestrator

Quasi-harmonic thermochemical corrections for electronic structure calculations.

goodvibes.GoodVibes.compute_thermochem(files, options, qcdata_cache=None)[source]

Run calc_bbe for each file and collect results.

For files matching the –media solvent name, the neat solvent concentration is used instead of options.conc. Parallelized across options.jobs worker processes when >1; sequential otherwise.

Parameters:
  • files (list) – output file paths.

  • options (Namespace) – parsed CLI options. Uses: QS, QH, S_freq_cutoff, H_freq_cutoff, temperature, conc, freq_scale_factor, freespace, spc, invert, symm, inertia, media, jobs.

  • qcdata_cache (dict, optional) – pre-parsed QCData keyed by basename.

Returns:

file path → calc_bbe mapping (thermo_data).

Return type:

dict

goodvibes.GoodVibes.main()[source]

CLI entry point: parse arguments, compute thermochemistry, and print results.

goodvibes.GoodVibes.parse_arguments()[source]

Parse command-line arguments and return (options, args).

goodvibes.GoodVibes.resolve_scaling_factor(files, options, level_of_theory)[source]

Look up or validate the vibrational frequency scaling factor.

If the user provided –freq_scale_factor, log it. Otherwise, attempt automatic lookup from the Truhlar database based on level of theory. Warns if multiple levels of theory are found.

Parameters:
  • files (list) – output file paths.

  • options (Namespace) – parsed CLI options. Uses: freq_scale_factor, zpe_scale_factor, boltz, ee.

  • level_of_theory (list) – level of theory strings, one per file.

goodvibes.GoodVibes.validate_and_configure(options, solvation_model)[source]

Validate solvent, print QH/QS configuration, and return the symmetry option.

goodvibes.GoodVibes.warn_orca_prescaled(files)[source]

Warn about ORCA files where pre-scaled frequencies were un-scaled.

Thermochemistry engine

exception goodvibes.thermo.MissingSinglePointError[source]

Bases: ValueError

Raised under strict_spc when a requested single-point energy is missing or unparseable instead of silently using the frequency-level energy.

goodvibes.thermo.SCALE_FACTOR_SOURCES = ('user', 'truhlar', 'mlip-unscaled', 'none-found')

Where calc_bbe.scale_factor_source says the harmonic frequency scale factor came from.

exception goodvibes.thermo.ScaleFactorWarning[source]

Bases: UserWarning

No vibrational scaling factor was found for a level of theory, so its frequencies were used unscaled (factor 1.0).

class goodvibes.thermo.ThermoOptions(QS: str = 'grimme', QH: bool = False, s_freq_cutoff: float = 100.0, h_freq_cutoff: float = 100.0, temperature: float = 298.15, concentration: float | None = None, freq_scale_factor: float | None = None, zpe_scale_factor: float | None = None, solv: str | None = None, spc: str | None = None, invert: float | None = None, symm: bool = False, inertia: str = 'global', strict_spc: bool = False, scale_factor_source: str | None = None)[source]

Bases: object

Bundle of thermochemistry options for calc_bbe.from_options.

Frozen so it’s safe to share across worker processes (parallel parsing) and across multiple calc_bbe calls without accidental mutation. Field names match the v4.2 façade compute_thermo kwargs; the older calc_bbe.__init__ parameter names are mapped internally.

QH: bool = False
QS: str = 'grimme'
concentration: float | None = None
freq_scale_factor: float | None = None
h_freq_cutoff: float = 100.0
inertia: str = 'global'
invert: float | None = None
s_freq_cutoff: float = 100.0
scale_factor_source: str | None = None
solv: str | None = None
spc: str | None = None
strict_spc: bool = False
symm: bool = False
temperature: float = 298.15
zpe_scale_factor: float | None = None
goodvibes.thermo.calc_avg_moment_of_inertia(roconst)[source]

Average moment of inertia (Grimme’s Bav for the free-rotor interpolation, –bav conf) from the rotational constants: the mean over the principal axes of I_i = h / (8 pi^2 B_i).

Averaging the moments (not the constants) is Grimme’s definition and is what ORCA and xtb use; for prolate tops (A >> B ~ C) the two averages differ by an order of magnitude. Earlier versions returned h / <B>, i.e. without the 8 pi^2 and averaged over constants (issue #113). The effect on qh-G is small (Bav only damps the lowest modes) but was measurable against ORCA / xtb reference values (up to ~30 uEh).

Parameters:

roconst (list[float]) – Rotational constants in gigahertz (GHz). Zero entries (the missing axis of a linear molecule) are ignored.

Returns:

Average moment of inertia in kilogram square meters (kg·m^2).

Return type:

float

Raises:

ValueError – If roconst is empty, contains a negative constant, or has no positive entry.

class goodvibes.thermo.calc_bbe(file, QS='grimme', QH=False, cutoff=100.0, H_FREQ_CUTOFF=100.0, temp=298.15, conc=None, scale_fac=None, solv=None, spc=None, invert=None, symm=False, inertia='global', qcdata=None, zpe_scale_fac=None, strict_spc=False, _from_options=False)[source]

Bases: object

Compute “black box” entropy and enthalpy values along with all other thermochemical quantities.

Computes H, S from partition functions, applying quasi-harmonic corrections

xyz

contains Cartesian coordinates and atom data.

Type:

QCData

job_type

contains information on the type of Gaussian job such as ground or transition state optimization, frequency.

Type:

str

roconst

list of parsed rotational constants from Gaussian calculations.

Type:

list

program

program used in chemical computation.

Type:

str

version_program

program version used in chemical computation.

Type:

str

solvation_model

solvation model used in chemical computation.

Type:

str

file

input chemical computation output file.

Type:

str

charge

overall charge of molecule.

Type:

int

empirical_dispersion

empirical dispersion model used in computation.

Type:

str

multiplicity

multiplicity of molecule or chemical system.

Type:

int

mult

multiplicity of molecule or chemical system.

Type:

int

point_group

point group of molecule or chemical system used for symmetry corrections.

Type:

str

sp_energy

single-point energy parsed from output file.

Type:

float

sp_program

program used for single-point energy calculation.

Type:

str

sp_version_program

version of program used for single-point energy calculation.

Type:

str

sp_solvation_model

solvation model used for single-point energy calculation.

Type:

str

sp_file

single-point energy calculation output file.

Type:

str

sp_charge

overall charge of molecule in single-point energy calculation.

Type:

int

sp_empirical_dispersion

empirical dispersion model used in single-point energy computation.

Type:

str

sp_multiplicity

multiplicity of molecule or chemical system in single-point energy computation.

Type:

int

cpu

days, hours, mins, secs, msecs of computation time.

Type:

list

scf_energy

self-consistent field energy.

Type:

float

frequency_wn

frequencies parsed from chemical computation output file.

Type:

list

im_freq

imaginary frequencies parsed from chemical computation output file.

Type:

list

inverted_freqs

frequencies inverted from imaginary to real numbers.

Type:

list

zero_point_corr

thermal corrections for zero-point energy parsed from file.

Type:

float

zpe

vibrational zero point energy computed from frequencies.

Type:

float

enthalpy

enthalpy computed from partition functions.

Type:

float

qh_enthalpy

enthalpy computed from partition functions, quasi-harmonic corrections applied.

Type:

float

entropy

entropy of chemical system computed from partition functions.

Type:

float

qh_entropy

entropy of chemical system computed from partition functions, quasi-harmonic corrections applied.

Type:

float

gibbs_free_energy

Gibbs free energy of chemical system computed from enthalpy and entropy.

Type:

float

qh_gibbs_free_energy

Gibbs free energy of chemical system computed from quasi-harmonic enthalpy and/or entropy.

Type:

float

linear_warning

flag for linear molecules, may be missing a rotational constant.

Type:

bool

ex_sym(file)[source]

Determine the molecular point group and symmetry number using pymsym.

Parameters:

file (str) – Ignored by this method; present for API compatibility.

Returns:

(symmetry_number, point_group) where symmetry_number is an int and point_group is a string.

Return type:

tuple

Raises:

RuntimeError – If the pymsym package is not installed/importable.

classmethod from_options(qcdata_or_path, options, *, scale_factor_source=None)[source]

Construct a calc_bbe from a ThermoOptions bundle.

Recommended v5.0+ entry point — replaces the legacy 15-argument positional constructor (which still works but emits a DeprecationWarning). Accepts either a parsed QCData instance (skips re-parsing) or a filesystem path (parses internally, like the old constructor).

When options.freq_scale_factor and/or options.zpe_scale_factor are None, this method auto-looks them up from the file’s level of theory via the Truhlar database (mirroring the CLI’s behavior). Pass explicit floats to skip the lookup.

The result’s scale_factor_source records where the harmonic factor came from: ‘user’ (passed in), ‘truhlar’ (database lookup), ‘mlip-unscaled’ (an ASE input whose level names no basis set, i.e. an MLIP, with no database entry: 1.0 by design) or ‘none-found’ (1.0 because the level of theory is not in the database; a ScaleFactorWarning says so). A caller that resolved the factors itself (the CLI) passes scale_factor_source.

sym_correction(file)[source]

Compute and return the symmetry entropy correction and detected point group for the structure identified by file.

Parameters:

file (str) – Filename or identifier for the structure passed to ex_sym to determine symmetry number and point group.

Returns:

sym_correction (float): Symmetry entropy correction converted to internal energy units (J/mol divided by J_TO_AU, i.e., same units used elsewhere in the module). pgroup (str): Detected molecular point-group symbol.

Return type:

tuple

goodvibes.thermo.calc_damp(frequency_wn, freq_cutoff)[source]

Compute per-mode damping factors for quasi-harmonic interpolation between RRHO and free-rotor entropy regimes.

Parameters:
  • frequency_wn (list[float]) – Vibrational frequencies in cm^-1.

  • freq_cutoff (float) – Cutoff frequency in cm^-1 at which the damping factor is approximately 0.5.

Returns:

Damping factors (0 to 1) for each input frequency; values near 1 indicate RRHO behavior, values near 0 indicate free-rotor behavior.

Return type:

list[float]

goodvibes.thermo.calc_electronic_entropy(multiplicity)[source]

Compute the electronic entropy contribution from spin multiplicity.

Parameters:

multiplicity (int) – Spin multiplicity (number of degenerate electronic states).

Returns:

Electronic entropy in J/(mol*K), equal to R * ln(multiplicity).

Return type:

float

goodvibes.thermo.calc_freerot_entropy(temperature, frequency_wn, bav=1e-44, freq_scale_factor=1.0)[source]

Compute per-mode free-rotor vibrational entropies for a list of vibrational modes.

Parameters:
  • temperature (float) – Temperature in kelvin.

  • frequency_wn (Sequence[float]) – Vibrational frequencies in cm^-1.

  • bav (float) – Reference average moment of inertia in kg·m^2 (defaults to GRIMME_BAV).

  • freq_scale_factor (float) – Frequency scale factor applied to all modes.

Returns:

Per-mode free-rotor entropies in J/(mol·K).

Return type:

list[float]

goodvibes.thermo.calc_qRRHO_energy(temperature, frequency_wn, freq_scale_factor=1.0)[source]

Compute per-mode quasi-rigid-rotor harmonic-oscillator (qRRHO) vibrational energy terms.

Parameters:
  • temperature (float) – Temperature in kelvin used for thermal factors.

  • frequency_wn (list[float]) – Vibrational frequencies in cm⁻¹.

  • freq_scale_factor (float | list[float], optional) – Global frequency scaling factor (or list of per-mode factors) applied to each frequency.

Returns:

Per-mode qRRHO vibrational energy terms in J/mol.

Return type:

list[float]

goodvibes.thermo.calc_rotational_energy(temperature, monatomic=False, linear=False)[source]

Compute the rotational energy for a species at a given temperature.

Returns 0 for monatomic species, R*T for linear molecules, and 3/2*R*T for non-linear molecules.

Parameters:
  • temperature (float) – Temperature in kelvin.

  • monatomic (bool) – If True, treat as a single atom (zero rotational energy).

  • linear (bool) – If True, treat as a linear molecule (use R*T).

Returns:

Rotational energy in joules per mole.

Return type:

float

goodvibes.thermo.calc_rotational_entropy(temperature, rotemp, symmno=1, monatomic=False, linear=False)[source]

Calculate the rotational entropy of a species at a given temperature.

Parameters:
  • temperature (float) – Temperature in kelvin.

  • rotemp (Sequence[float]) – Rotational temperatures (K). For linear molecules provide at least one value; for non-linear provide three principal rotational temperatures.

  • symmno (int) – Molecular symmetry number (default 1).

  • monatomic (bool) – If True, treat as a single atom (entropy = 0).

  • linear (bool) – If True, treat as a linear molecule.

Returns:

Rotational entropy in J/(mol·K).

Return type:

float

goodvibes.thermo.calc_rrho_entropy(temperature, frequency_wn, freq_scale_factor=1.0)[source]

Compute per-mode vibrational entropies using the rigid-rotor harmonic-oscillator (RRHO) model.

Parameters:
  • temperature (float) – Temperature in kelvin.

  • frequency_wn (iterable) – Vibrational wavenumbers in cm⁻¹.

  • freq_scale_factor (float) – Frequency scaling factor applied to each mode.

Returns:

List of per-mode vibrational entropies in J/(mol*K).

goodvibes.thermo.calc_translational_energy(temperature)[source]

Compute the ideal-gas translational molar energy at a specified temperature.

Parameters:

temperature (float) – Temperature in kelvin (K).

Returns:

Translational energy in joules per mole (J/mol), equal to 3/2 · R · T.

Return type:

float

goodvibes.thermo.calc_translational_entropy(molecular_mass, conc, temperature, solvent=None)[source]

Calculate the translational entropy for a species at a given temperature and concentration.

Parameters:
  • molecular_mass (float) – Molecular mass in atomic mass units (amu).

  • conc (float) – Concentration in moles per liter (mol/L).

  • temperature (float) – Temperature in kelvin (K).

  • solvent (str, optional) – If provided, adjusts the effective free volume using the solvent name via get_free_space; if None, assumes ideal-gas volume.

Returns:

Translational entropy in joules per mole per kelvin (J/(mol·K)).

Return type:

float

goodvibes.thermo.calc_vibrational_energy(temperature, frequency_wn, freq_scale_factor=1.0)[source]

Compute the vibrational energy (zero-point + thermal contributions) in joules per mole at a given temperature.

Parameters:
  • temperature (float) – Temperature in kelvin; must be greater than 0.

  • frequency_wn (list[float]) – Vibrational mode wavenumbers in cm^-1.

  • freq_scale_factor (float, optional) – Frequency scaling factor applied to all modes.

Returns:

Total vibrational energy (J/mol), including zero-point energy and thermal excitations.

Return type:

float

Raises:

ValueError – If temperature is not greater than 0, or if mode energies produce numerical overflow (indicative of too-low temperature).

goodvibes.thermo.calc_zeropoint_energy(frequency_wn, freq_scale_factor=1.0)[source]

Compute the vibrational zero-point energy for a set of vibrational modes.

Parameters:
  • frequency_wn (list) – Vibrational wavenumbers (cm^-1).

  • freq_scale_factor (float) – Global scale factor applied to all modes.

Returns:

Vibrational zero-point energy (J/mol), computed as the sum over modes of 0.5 * h * nu.

Return type:

float

goodvibes.thermo.get_free_space(solv)[source]

Estimate the accessible free volume in a bulk solvent (mL per L).

Computes the volume fraction of a litre of solvent that is not occupied by solvent molecules using literature molarities and molecular volumes (Shakhnovich & Whitesides). This function is deprecated and experimental.

Parameters:

solv (str) – Solvent name (case-sensitive) expected in the supported set.

Returns:

Estimated accessible free volume in milliliters per liter.

Return type:

float

Notes

  • If solv is not recognized a UserWarning is emitted and the function returns 1000.0 (gas-phase fallback).

  • The function emits a DeprecationWarning on use.

Native parsers and QCData

class goodvibes.io.HessianData(hessian: ndarray, masses: List[float], program: str, source: str)[source]

Bases: object

Cartesian Hessian and the masses needed to mass-weight it.

Produced by parse_hessian(). Units follow the QC programs’ native conventions: the Hessian is in Hartree/Bohr^2 and masses are in amu. Row/column order is 3N Cartesian components (x1, y1, z1, x2, …) matching the atom order of the parsed geometry.

hessian: ndarray
masses: List[float]
program: str
source: str
class goodvibes.io.QCData(file: str = '', program: str = '', version_program: str = '', job_type: str = '', scf_energy: float | None = None, charge: int | None = None, multiplicity: int = 1, solvation_model: str = '', empirical_dispersion: str = '', level_of_theory: str = '', molecular_mass: float = 0.0, symmno: int = 1, linear_mol: bool = False, point_group: str = '', roconst: List[float] = <factory>, rotemp: List[float] = <factory>, linear_warning: bool = False, frequency_wn: List[float] = <factory>, im_frequency_wn: List[float] = <factory>, zero_point_corr: float | None = None, cpu: List[int] = <factory>, nprocs: int = 1, has_oniom: bool = False, atom_nums: List[int] = <factory>, atom_types: List[str] = <factory>, cartesians: List[List[float]] = <factory>, per_atom_masses: List[float] = <factory>, applied_freq_scale_factor: float = 1.0, sp_energy: float | None = None, sp_version_program: str = '', sp_solvation_model: str = '', sp_charge: int | None = None, sp_empirical_dispersion: str = '', sp_multiplicity: int | None = None, sp_suffix: str = '', sp_file: str = '', sp_level_of_theory: str = '')[source]

Bases: object

Program-agnostic container for parsed quantum chemistry data.

Populated by parse_qcdata() in io.py. Consumed by calc_bbe in thermo.py.

applied_freq_scale_factor: float = 1.0
atom_nums: List[int]
atom_types: List[str]
cartesians: List[List[float]]
charge: int | None = None
cpu: List[int]
empirical_dispersion: str = ''
file: str = ''
frequency_wn: List[float]
classmethod from_atoms(atoms, energy, *, frequencies=None, energy_units='eV', frequency_units='cm-1', name=None, method=None, charge=None, multiplicity=None, symm='auto', masses='isotopic', job_type=None, solvation_model='gas phase', empirical_dispersion='', linear_mol=None, zpe=None)[source]

Build a QCData from an ASE Atoms (or anything with get_chemical_symbols() and get_positions()), an electronic energy and, optionally, vibrational frequencies; no file involved.

Parameters:
  • atoms – geometry; atoms.info may supply charge, multiplicity, name and level_of_theory defaults.

  • energy – electronic energy in energy_units (‘eV’ default, also ‘hartree’, ‘kcal/mol’, ‘kJ/mol’).

  • frequencies – vibrational wavenumbers in frequency_units (‘cm-1’ default; ‘eV’ / ‘meV’ are converted). A negative or complex value is an imaginary mode. Translational and rotational modes must already be removed (see from_vibrations(), which does that for an ASE VibrationsData).

  • name – display name (ThermoResult.name); default atoms.info['name'] or ‘atoms’.

  • method – model chemistry label, e.g. ‘MACE-OFF23’ or ‘B3LYP/6-31G(d)’. Stored as level_of_theory; when it matches an entry of the Truhlar scaling database the same scale factors as for a file are applied, otherwise 1.0 (by design for an MLIP, a method naming no basis set; with a ScaleFactorWarning for a QM level).

  • charge – default atoms.info values, else 0 / 1.

  • multiplicity – default atoms.info values, else 0 / 1.

  • symm – ‘auto’ (default) detects the point group and symmetry number with pymsym when it is installed, an int sets the symmetry number directly, None leaves it at 1 (C1).

  • masses – ‘isotopic’ (most-abundant-isotope masses, the QC-program convention), ‘atoms’ (atoms.get_masses(), standard atomic weights) or a sequence of per-atom masses in amu.

  • job_type – ‘Freq’, ‘TSFreq’ (‘TS’), ‘SP’; inferred from the frequencies when None (an imaginary mode → ‘TSFreq’).

  • linear_mol – force linearity; detected from the geometry when None.

  • zpe – zero-point energy in energy_units to record as parsed metadata (GoodVibes recomputes the ZPE it reports from the frequencies, as for every program).

classmethod from_vibrations(atoms, vibdata, energy, *, energy_units='eV', drop_tr_modes=True, imag_threshold_cm1=-15.0, **kw)[source]

Build a QCData from an ASE vibrational analysis.

vibdata is an ase.vibrations.VibrationsData (or a Vibrations object, or any sequence of wavenumbers in cm⁻¹; complex or negative values are imaginary). The 3N modes of a finite-difference Hessian include the 6 (5 for a linear molecule) translations and rotations, which drop_tr_modes removes as the modes of smallest magnitude. An imaginary mode smaller in magnitude than |imag_threshold_cm1| is numerical noise on a slightly rough (MLIP) surface and is taken as real at |ν| with a RuntimeWarning; larger ones stay imaginary. A RuntimeWarning also reports a transition state (job_type='TS') without exactly one imaginary mode, a minimum with any, or more than one imaginary mode when no job_type was given. Other keywords go to from_atoms().

has_oniom: bool = False
im_frequency_wn: List[float]
job_type: str = ''
level_of_theory: str = ''
linear_mol: bool = False
linear_warning: bool = False
molecular_mass: float = 0.0
multiplicity: int = 1
nprocs: int = 1
per_atom_masses: List[float]
point_group: str = ''
program: str = ''
roconst: List[float]
rotemp: List[float]
scf_energy: float | None = None
solvation_model: str = ''
sp_charge: int | None = None
sp_empirical_dispersion: str = ''
sp_energy: float | None = None
sp_file: str = ''
sp_level_of_theory: str = ''
sp_multiplicity: int | None = None
sp_solvation_model: str = ''
sp_suffix: str = ''
sp_version_program: str = ''
symmno: int = 1
version_program: str = ''
with_single_point(energy, units, method=None, *, solvation_model=None, charge=None, multiplicity=None)[source]

A copy of this QCData whose enthalpies and free energies use energy, a single point at another level, in place of the electronic energy of the frequency calculation: a composite such as DFT//MLIP (DFT energy, MLIP geometry and frequencies) with no single-point output file.

The single point is applied the way spc applies one read from a file (ThermoResult.spc_applied is True and sp_energy is energy) without passing spc; an explicit spc still wins. The copy keeps the attached energy through compute_thermo, re-evaluation at other temperatures, --export caches and embedded conformers. The vibrational scale factor is still looked up for the level of the frequencies.

Parameters:
  • energy – the single-point electronic energy.

  • units – its units, ‘hartree’, ‘eV’, ‘kcal/mol’ or ‘kJ/mol’ (required, so an energy is never read in the wrong units).

  • method – level of theory of the single point, e.g. ‘DLPNO-CCSD(T)/def2-TZVP’; recorded as sp_level_of_theory.

  • solvation_model – of the single point; default those of this QCData.

  • charge – of the single point; default those of this QCData.

  • multiplicity – of the single point; default those of this QCData.

zero_point_corr: float | None = None
goodvibes.io.compute_connectivity(atom_types, cartesians, tolerance=0.2)[source]

Compute molecular connectivity based on covalent radii.

goodvibes.io.dict_to_qcdata(d)[source]

Reconstruct a QCData instance from a dictionary.

Keys that are not QCData fields are ignored so that payloads written by other GoodVibes versions (e.g. the retired fract_modelsys field) still load.

goodvibes.io.element_id(massno, num=False)[source]

Get element symbol from mass number.

Used in parsing output files to determine elements present in file.

Parameter: massno (int): mass of element.

Returns: str: element symbol, or ‘XX’ if not found in periodic table.

goodvibes.io.find_spc_file(name, spc)[source]

Locate the single-point-correction output paired with name.

Pattern matched: {name}_{spc}.{log,out} — exact match. --spc TZVP finds filename_TZVP.log only; it will NOT match filename_def2_TZVP.log. To pair with a file like filename_def2_TZVP.log, pass the full suffix (--spc def2_TZVP).

Parameters:
  • name (str) – Path stem of the parent output file (no extension).

  • spc (str) – Suffix passed via --spc.

Returns:

Path to the matching file, or None if nothing matches.

Return type:

str or None

goodvibes.io.gaussian_jobtype(filename)[source]

Read the jobtype from a Gaussian archive string.

goodvibes.io.level_of_theory(file)[source]

Read output for the level of theory and basis set used.

goodvibes.io.load_cache(path)[source]

Load a QCData cache from a JSON file.

Auto-detects the format. v1.0+ payloads (the unified schema written by –export / –json) are read by extracting each result’s qcdata block; the legacy cache envelope ({_cache_version, entries}, written by pre-v5.0 –cache-save) is also accepted for back-compat.

Parameters:

path (str) – Path to JSON file (either v1.0 unified schema or legacy cache).

Returns:

Mapping of {basename_key: QCData}.

Return type:

dict

goodvibes.io.parse_ase_thermo(file)[source]

Parse a GoodVibes ASE thermo extxyz file.

The format is standard extended XYZ with thermochemistry metadata in the second line (key=value, ASE Atoms.info convention). Required keys: program=ase, scf_energy. Frequencies are space-separated in cm-1 (negatives are imaginary). See tests/ase/README.md for the full spec.

goodvibes.io.parse_data(file)[source]

Read computational chemistry output file.

Attempt to obtain single point energy, program type, program version, solvation_model, charge, empirical_dispersion, and multiplicity from file.

Parameter: file (str): name of file to be parsed.

Returns: float: single point energy. str: program used to run calculation. str: version of program used to run calculation. str: solvation model used in chemical calculation (if any). str: original filename parsed. int: overall charge of molecule or chemical system. str: empirical dispersion used in chemical calculation (if any). int: multiplicity of molecule or chemical system.

goodvibes.io.parse_gaussian_thermo(file)[source]

Parse Gaussian output for all thermochemistry-relevant data.

Returns QCData with raw frequencies (negative = imaginary, no inversion applied). Frequency inversion is a user policy decision handled in thermo.py.

Parameters:

file (str) – Path to Gaussian output file.

goodvibes.io.parse_hessian(file)[source]

Parse the Cartesian Hessian for a frequency job into HessianData.

Unlike parse_qcdata(), which extracts the program’s printed frequencies, this returns the raw second-derivative matrix – needed by consumers that re-mass-weight it, e.g. isotopologue analysis in Kinisot.

Supported sources:
  • Gaussian: the archive force-constant block of a freq .log/.out.

  • ORCA: the companion .hess file (same stub as the .out), or a .hess path given directly.

Parameters:

file (str) – QC output file (.log/.out) or an ORCA .hess file.

Returns:

hessian (Hartree/Bohr^2), masses (amu), program, source.

Return type:

HessianData

Raises:

FileNotFoundError, ValueError – If no file, no Hessian data, or an unsupported program.

goodvibes.io.parse_nwchem_thermo(file)[source]

Parse NWChem output for all thermochemistry-relevant data.

Returns QCData with raw frequencies (negative = imaginary, no inversion applied).

Parameters:

file (str) – Path to NWChem output file.

goodvibes.io.parse_orca_thermo(file)[source]

Parse ORCA output for all thermochemistry-relevant data.

Uses native line-by-line parsing.

Parameters:

file (str) – Path to ORCA output file.

goodvibes.io.parse_qcdata(file)[source]

Parse any supported output file into a QCData object.

Detects program from file content and delegates to the correct parser.

Parameters:

file (str) – Path to quantum chemistry output file.

goodvibes.io.parse_qchem_thermo(file)[source]

Parse a Q-Chem 6 output file for all thermochemistry-relevant data.

Handles single-job and multi-job (@@@-separated opt + freq + sp) inputs: the LAST occurrence wins for SCF energy, geometry, and frequencies, so the converged opt geometry and post-correlation energies are picked up correctly. Recognises native HF/DFT total energy, MP2 total energy, CCSD total energy, and CCSD(T) total energy via the precedence CCSD(T) > CCSD > MP2 > Total energy.

Parameters:

file (str) – Path to a Q-Chem 6 output file.

goodvibes.io.parse_xtb_thermo(file)[source]

Parse xtb output for all thermochemistry-relevant data.

xtb takes a .xyz coordinate file plus CLI flags rather than an input deck. The parser handles single-point (xtb file.xyz), Hessian-only (--hess), and optimization+Hessian (--ohess) jobs, plus implicit solvation (GBSA via -g, ALPB via --alpb, CPCM-X via --cpcmx).

For runs without --opt/--ohess (no optimized-geometry block in the output), Cartesians fall back to the paired .xyz input file.

Parameters:

file (str) – Path to xtb output file.

goodvibes.io.qcdata_to_dict(qcdata)[source]

Convert a QCData instance to a JSON-serializable dictionary.

goodvibes.io.read_initial(file)[source]

At beginning of procedure, read level of theory, solvation model, and check for normal termination

goodvibes.io.read_xyz_frames(path, *, energy_units=None, energy_key=None, method=None, charge=None, multiplicity=None)[source]

Read every frame of a multi-frame .xyz or .extxyz file as an energy-only QCData: a geometry and an electronic energy, no frequencies.

For conformer ensembles (CREST crest_conformers.xyz, xtb trajectories) and MLIP sweeps (ase.io.write of many frames). compute_batch evaluates the list, and a ConformerSet of the results weighted by 'electronic' gives Boltzmann populations and the ensemble energy; free energies need frequencies, so the thermochemical quantities are None.

The energy of a frame comes from its comment line: in an extxyz one (key=value pairs) from energy_key or, by default, the first of energy, free_energy, total_energy (eV, the ASE convention), scf_energy (hartree) and E (eV), a named key other than these being in eV; in a plain one from energy: <value> (xtb) or a bare number (CREST), in hartree. An energy_units (or scf_energy_units) key, or the energy_units argument, overrides the units. A frame without an energy is an error.

Parameters:
  • path – the .xyz / .extxyz file.

  • energy_units – units of every frame’s energy (‘hartree’, ‘eV’, ‘kcal/mol’, ‘kJ/mol’); default as above.

  • energy_key – the extxyz key holding the energy.

  • method – level of theory of the energies (level_of_theory), e.g. ‘GFN2-xTB’ or ‘MACE-OFF23’; default the frame’s level_of_theory key.

  • charge – default the frame’s keys, else 0 / 1.

  • multiplicity – default the frame’s keys, else 0 / 1.

Returns:

A list of QCData, one per frame, named <stem>_<n> (n from 1, zero-padded) unless a frame has a name key; job_type ‘SP’.

goodvibes.io.resolve_output_file(file, extensions=('.log', '.out', '.extxyz'))[source]

Return the on-disk path to parse for file.

The path the caller actually gave is always preferred when it exists. Only when it does not (e.g. an extension-less stem, or --spc twins named by stem) are sibling files with the same stem and one of extensions tried, in order. Returns None if nothing exists.

Earlier versions tried stem.log before the given path, so asking for x.out silently parsed x.log when both were present.

goodvibes.io.save_cache(cache_dict, path)[source]

Write QCData cache to a JSON file.

Parameters:
  • cache_dict (dict) – Mapping of {basename_key: qcdata_dict} where each value is from qcdata_to_dict().

  • path (str) – Output JSON file path.

goodvibes.io.sp_cpu(file)[source]

Read single-point output for CPU time.

Delegates to parse_qcdata(file).cpu so the SPC CPU is identical to what a standalone parse of the same file would report. This guarantees that goodvibes <parents> --spc <suffix> totals correctly reflect the SPC files’ CPU — including program-specific accumulation (Gaussian sums multi-step Job cpu time lines) and nprocs scaling (ORCA reports wall time only; the parser scales by parallel-MPI process count to estimate effective CPU).

goodvibes.io.write_xyz(filepath, files, thermo_data)[source]

Write optimized Cartesian coordinates to a multi-structure .xyz file.

Each structure block contains the atom count, a comment line with the filename and SCF energy, and the Cartesian coordinates.

Parameters:
  • filepath (str) – output .xyz file path.

  • files (list) – output file paths whose coordinates to write.

  • thermo_data (dict) – file path → calc_bbe mapping.

ASE (extended XYZ) bridge

Optional helper for emitting GoodVibes ASE thermo extxyz files from an ASE-driven calculation.

ASE is not a runtime dependency of GoodVibes. This module imports it lazily so the rest of the package works without ASE installed; only direct callers of write_thermo_extxyz need it. See tests/ase/README.md for the format spec.

goodvibes.ase_helper.write_thermo_extxyz(path, atoms, energy, frequencies=None, charge=0, multiplicity=1, level_of_theory=None, solvation_model='gas phase', empirical_dispersion='', point_group=None, symmno=None, linear_mol=None, zpe=None, job_type=None, energy_units='Hartree')[source]

Write a GoodVibes-compatible ASE thermo .extxyz file for the supplied geometry and metadata.

Parameters:
  • path (str) – Output file path (typically ending with .extxyz).

  • atoms (ase.Atoms) – Geometry; element symbols and Cartesian positions are written.

  • energy (float) – Electronic (SCF) energy.

  • frequencies (iterable[float], optional) – Vibrational frequencies in cm^-1; negative values indicate imaginary modes.

  • charge (int, optional) – Total molecular charge. Default: 0.

  • multiplicity (int, optional) – Spin multiplicity. Default: 1.

  • level_of_theory (str, optional) – Free-form method/basis description (e.g., “B3LYP/6-31G*”) used for GoodVibes scaling lookup.

  • solvation_model (str or None, optional) – Description of solvation model (defaults to ‘gas phase’); set to None to omit.

  • empirical_dispersion (str, optional) – Empirical dispersion tag (included when non-empty).

  • point_group (str, optional) – Molecular point group symbol.

  • symmno (int, optional) – Symmetry number.

  • linear_mol (bool, optional) – Whether the molecule is linear.

  • zpe (float, optional) – Zero-point energy in the same units as energy.

  • job_type (str, optional) – Job type hint (e.g., ‘Freq’, ‘GSFreq’, ‘TS’, ‘SP’); parser may infer if omitted.

  • energy_units (str, optional) – Units for energy (default: ‘Hartree’).

Raises:

ImportError – If ASE is not installed (suggests installing via pip install ase).

Output rendering (Rich tables, JSON)

Output formatting and printing functions for GoodVibes.

goodvibes.output.apply_cli_pes_options(result, options)[source]

Sync run-time CLI flags into result.options.

The PES file only carries presentation options (units, decimals); --nogconf, --lowest-only, -q and --spc come from the command line and must be applied to the model before any consumer (Rich tables, JSON pes block, --pes-plot) reads it. Call this once right after load_pes; it is idempotent.

Parameters:
  • result – PESResult instance (from goodvibes.pes_loader.load_pes).

  • options – argparse Namespace; reads .gconf, .QH, .spc, .lowest_only.

Returns:

The same result, for chaining.

goodvibes.output.pes_tables(result, temperature=None, conc=None)[source]

Rich tables for a PESResult: one rich.table.Table per pathway.

Usable from a notebook or script without the CLI’s logging set-up:

from rich import print
for table in pes_tables(result):
    print(table)
Parameters:
  • result – PESResult (goodvibes.load_pes or built by hand). Its options decide units, decimals, rollup and columns; the CLI syncs them with apply_cli_pes_options.

  • temperature –

    1. Defaults to the result’s first temperature.

  • conc – user concentration in mol/L for the title; None → 1 atm.

goodvibes.output.print_cpu_time(thermo_data, exclude=None)[source]

Aggregate and log total CPU time across the provided thermochemistry results.

Parameters:
  • thermo_data (dict) – Mapping of file path to calc_bbe-like objects whose cpu and optional sp_cpu attributes contribute to the total.

  • exclude (str, optional) – Glob pattern; files matching this pattern are omitted from the summed total.

goodvibes.output.print_pes_results(thermo_data, options, dup_list, boltz_facs=None, interval_bbe_data=None, interval=None, file_list=None)[source]

Print relative PES energies from a YAML-defined reaction pathway.

Reads the PES definition, computes relative energies with optional Boltzmann weighting and conformational corrections, and prints formatted tables. Optionally computes enantioselectivity and generates reaction profile graphs.

Parameters:
  • thermo_data (dict) – file path → calc_bbe mapping.

  • options (Namespace) – parsed CLI options. Uses: dp, QH, spc, gconf, temperature_interval, pes, temperature, ee, graph.

  • dup_list (list) – pairs of duplicate/enantiomer file paths.

  • boltz_facs (dict, optional) – Boltzmann factors keyed by file path. Computed by get_boltz() in the orchestrator. None when –ee is off.

  • interval_bbe_data (list, optional) – per-file, per-temperature calc_bbe data.

  • interval (range, optional) – temperature steps for variable-T PES.

  • file_list (list, optional) – file list from temperature interval analysis.

goodvibes.output.print_pes_tables(result, options, temperature=None)[source]

Render PES results as Rich tables (one per pathway).

Parameters:
  • result – PESResult instance (from goodvibes.pes_loader.load_pes).

  • options – argparse Namespace; reads .QH, .spc, .gconf.

  • temperature –

    1. Defaults to result.temperatures[0].

goodvibes.output.print_profile_selectivity(results, units='kcal/mol')[source]

The selectivities of a reaction-profile document’s selectivity blocks (Profile.evaluate_selectivity): a summary line, a table of branch barriers and populations, and any Curtin–Hammett warning.

goodvibes.output.print_results(thermo_data, options, media_conc=None, dup_list=None, boltz_facs=None)[source]

Print the single-temperature thermochemistry results table.

Outputs energies, enthalpies, entropies and free energies for each file. Optionally includes Boltzmann weighting, imaginary frequencies, symmetry point groups, CPU times, and media concentration annotations.

Parameters:
  • thermo_data (dict) – file path → calc_bbe mapping.

  • options (Namespace) – parsed CLI options. Uses: dp, QH, invert, spc, imag_freq, boltz, symm, duplicate, temperature, cputime, media.

  • media_conc (float, optional) – neat solvent concentration for display.

  • dup_list (list, optional) – pairs of duplicate/enantiomer file paths. Computed by deduplicate() in the orchestrator. Defaults to [].

  • boltz_facs (dict, optional) – Boltzmann factors keyed by file path. Computed by get_boltz() in the orchestrator. None when –boltz is off.

goodvibes.output.print_selectivity_results(results_by_method)[source]

Print one or more selectivity tables, one block per method.

Parameters:

results_by_method (dict[str, list[SelectivityResult]]) – ordered mapping of method name (e.g. “Boltzmann-averaged”, “Lowest conformer only”) to its list of SelectivityResults. Single-temperature mode passes a one-element list per method; scan mode passes one per temperature.

goodvibes.output.print_temperature_interval(thermo_data, options, media_conc=None, qcdata_cache=None)[source]

Recompute thermochemical properties over a temperature interval, log formatted results for each structure and return the computed per-temperature data.

For each file in thermo_data this function evaluates thermochemistry at every temperature in the interval (derived from options.temperature_interval), logs a human-readable table of H, T·S, G(T) and their quasi-harmonic variants when requested, and collects the resulting computed objects.

Parameters:
  • thermo_data (dict) – Mapping of file path → previously computed thermochemistry object (used to determine file order).

  • options (Namespace) – Parsed CLI/options object. Uses these attributes: temperature_interval, dp, QH, QS, S_freq_cutoff, H_freq_cutoff, spc, conc, freq_scale_factor, freespace, invert, inertia, media.

  • media_conc (float, optional) – Solvent concentration (M) to annotate solvent-matching structures when media is enabled.

  • qcdata_cache (dict, optional) – Optional mapping from file basename → pre-parsed QCData to pass through to the recomputation routine.

Returns:

(interval_bbe_data, interval, file_list)
  • interval_bbe_data (list[list]): Outer list indexed by file; each inner list contains the recomputed thermochemistry objects (one per temperature).

  • interval (list[float]): the temperatures iterated (see utils.parse_temperature_interval).

  • file_list (list): List of file paths in the order processed.

Return type:

tuple

goodvibes.output.write_json_results(thermo_data, options, path, media_conc_per_file=None, boltz_facs=None, selectivity_results=None, selectivity_lowest_results=None, pes_result=None, profile=None)[source]

Write structured run results to path as JSON.

Captures every parsed QCData field plus computed thermo per file, the schema version, the goodvibes version, and the run options. Schema is preview/v0.x — fields may shift before v5.0 stabilizes it.

Parameters:
  • thermo_data (dict) – file path -> calc_bbe.

  • options (Namespace) – the parsed CLI options.

  • path (str) – output JSON file path.

  • media_conc_per_file (dict, optional) – file path -> neat solvent concentration applied to that specific file (None for files where –media didn’t match).

  • boltz_facs (dict, optional) – file path -> Boltzmann factor.

  • selectivity_results (list[SelectivityResult], optional) – one entry per temperature; included as a top-level “selectivity” block.

  • pes_result (PESResult, optional) – written as the “pes” block.

  • profile (goodvibes.profile.Profile, optional) – the evaluated reaction-profile document, written as the “profile” block (payload 1.1; without embedded conformers).

Selectivity (v4.1 redesign)

Boltzmann weighting and selectivity calculations for GoodVibes.

class goodvibes.selectivity.SelectivityResult(temperature: float, key: str, labels: List[str], files_per_label: Dict[str, List[str]], populations: Dict[str, float], raw_boltzmann: Dict[str, float], preferred: str, ee: float | None = None, ddG: float | None = None, major: str | None = None, ee_signed: float | None = None, ratio: float | None = None, ensemble_energies: Dict[str, float] | None = None, quantity: str | None = None, name: str | None = None, series: str | None = None, reference: str | None = None, barriers: Dict[str, float] | None = None, curtin_hammett: str | None = None, warnings: Tuple[str, ...] = ())[source]

Bases: object

N-species selectivity outcome at one temperature.

Numeric data only — formatting strings (e.g. “60:40”, “1.5:1”) are derived in the print layer from populations.

Conventions (v2, GoodVibes 5.1):

  • major is the label with the largest population; on an exact tie, the first listed. preferred is the same label (kept from v1).

  • ee is the excess of the major over the minor for two labels, |p1 - p2| * 100, always ≥ 0; ee_signed is (p1 - p2) * 100 with the labels in the order given, so it is positive when the first label is the major one. Both are None for more than two labels.

  • ddG (two labels) and ratio (any number) compare the major with the runner-up: ddG = RT ln(p_major / p_runner_up) in Hartree, ≥ 0, and ratio = p_major / p_runner_up.

  • ensemble_energies is each label’s ensemble energy -RT ln Σ exp(-E_i / RT) over its conformers, in Hartree: absolute when computed from structures, relative to the reference point when computed from a reaction-profile document (then equal to barriers).

barriers: Dict[str, float] | None = None
curtin_hammett: str | None = None
ddG: float | None = None
ee: float | None = None
ee_signed: float | None = None
ensemble_energies: Dict[str, float] | None = None
files_per_label: Dict[str, List[str]]
key: str
labels: List[str]
major: str | None = None
name: str | None = None
populations: Dict[str, float]
preferred: str
quantity: str | None = None
ratio: float | None = None
raw_boltzmann: Dict[str, float]
reference: str | None = None
series: str | None = None
temperature: float
warnings: Tuple[str, ...] = ()
exception goodvibes.selectivity.SelectivityWarning[source]

Bases: UserWarning

A selectivity rests on an assumption the data do not support (a Curtin–Hammett precondition that fails, a branch that is not a transition state).

goodvibes.selectivity.assign_files_to_labels(files, label_patterns)[source]

Group file paths by fnmatch against each label’s pattern.

Each pattern is tested against TWO candidates per file, in order:

  1. the file’s basename (e.g. DA_exo_12_i.out) — matches when species are encoded in the filename, the v4.x default.

  2. the basename of the file’s immediate parent directory (e.g. exo) — matches when species are organized into per-species subdirectories.

A file is assigned to the first label whose pattern matches either candidate. So a layout like exo/DA_*.out endo/DA_*.out works with --label exo=exo --label endo=endo (parent-dir match), and the original --label exo='*_exo_*' (basename match) keeps working unchanged.

Parameters:
  • files (Iterable[str]) – file paths to species.

  • label_patterns (dict) – ordered label -> fnmatch glob pattern.

Returns:

label -> list of matching file paths.

Return type:

dict[str, list[str]]

goodvibes.selectivity.compute_selectivity(thermo_data, files_per_label, temperature, dup_list=None, key='gibbs')[source]

Compute populations and (for N=2) ee + ΔΔG‡ for a labeled species set.

Parameters:
  • thermo_data (dict) – file path -> calc_bbe.

  • files_per_label (dict) – ordered label -> list of file paths.

  • temperature (float) – Kelvin.

  • dup_list (list, optional) – pairs of duplicate files to exclude from sums.

  • key (str) – ‘gibbs’ (qh_gibbs_free_energy) or ‘energy’ (scf_energy).

Returns:

SelectivityResult.

Raises:

ValueError – if any label has no files, or if no files have a usable energy attribute.

goodvibes.selectivity.compute_selectivity_lowest_only(thermo_data, files_per_label, temperature, dup_list=None, key='gibbs')[source]

Selectivity using only the most stable conformer per species.

For each label, picks the file with the lowest key (default qh_gibbs_free_energy), drops the rest, and runs the standard Boltzmann calc on the resulting 1-conformer-per-species set. The result complements compute_selectivity by showing how much of the selectivity comes from conformer mixing versus the gap between the lowest TSs.

goodvibes.selectivity.compute_selectivity_lowest_only_scan(thermo_data, files_per_label, temperatures, dup_list=None, key='gibbs', thermo_by_temperature=None)[source]

Lowest-only selectivity at each temperature in temperatures.

The lowest conformer per species is chosen at each temperature from that temperature’s free energies when thermo_by_temperature is given (see compute_selectivity_scan).

goodvibes.selectivity.compute_selectivity_scan(thermo_data, files_per_label, temperatures, dup_list=None, key='gibbs', thermo_by_temperature=None)[source]

Compute a SelectivityResult at each temperature in temperatures.

Pairs with –ti temperature intervals; the species grouping is fixed across temperatures. Free energies are temperature dependent, so pass thermo_by_temperature ({T: thermo_data recomputed at T}) to use the correct G(T) at each point of the scan; the CLI does this. With only thermo_data the same free energies are reused at every T and only the Boltzmann factor’s RT changes, which is what this function did before 4.5 and is still the right thing when key='energy'.

goodvibes.selectivity.describe_selectivity(result, units='kcal/mol', decimals=1)[source]

A one-line summary: the major branch, the signed ee (two branches) or the ratio over the runner-up, ΔΔG‡ and the Curtin–Hammett status, e.g. er (G, 298.15 K): major TS_R, ee +76.6 % (TS_R vs TS_S), ΔΔG‡ 1.20 kcal/mol, Curtin–Hammett satisfied.

goodvibes.selectivity.get_boltz(thermo_data, temperature, dup_list, key='gibbs')[source]

Produce normalized Boltzmann populations across all files.

Used by –boltz for the per-file population display and by the legacy –ee selectivity flow (which still calls get_selectivity).

Parameters:
  • thermo_data (dict) – file path -> calc_bbe.

  • temperature (float) – Kelvin.

  • dup_list (list) – pairs [file_i, file_j]; entries whose path is the FIRST member of any pair are excluded (legacy behavior).

  • key (str) – ‘gibbs’ or ‘energy’.

Returns:

file path -> normalized population (Σ = 1.0).

Return type:

dict[str, float]

goodvibes.selectivity.get_selectivity(pattern, files, boltz_facs, temperature, dup_list)[source]

DEPRECATED — legacy ‘a:b’ colon-pattern selectivity.

Forwards to compute_selectivity with a 2-label spec built from the glob pattern. Emits a DeprecationWarning. Use –label / –selectivity going forward.

Returns the legacy 6-tuple: (ee, er, ratio, dd_free_energy, failed, pref) so the existing CLI print path keeps working unchanged.

goodvibes.selectivity.load_label_yaml(path)[source]

Load a selectivity YAML file.

Supports two shapes (mutually exclusive):

labels: { R: ‘P_R_’, S: ‘P_S_’ } # fnmatch patterns files: { R: [a.log, b.log], S: [c.log, …] } # explicit file lists

Returns:

mode is ‘patterns’ or ‘files’; dict maps label to its spec (string pattern, or list of file paths).

Return type:

(mode, dict)

goodvibes.selectivity.parse_label_args(label_args)[source]

Parse repeatable –label NAME=PATTERN args into an ordered dict.

Parameters:

label_args (list[str] or None) – values from argparse for –label.

Returns:

ordered mapping label -> fnmatch glob pattern.

Return type:

dict[str, str]

goodvibes.selectivity.selectivity_from_energies(energies, temperature, *, key=None, quantity=None, files_per_label=None, **extra)[source]

A SelectivityResult from each label’s conformer energies.

Parameters:
  • energies (Mapping[str, Sequence[float]]) – ordered label -> the energies (Hartree) of its conformers; one value per label for a single structure or an already rolled-up level.

  • temperature (float) – Kelvin.

  • key (str) – recorded on the result (quantity is the registry id; key defaults to it).

  • quantity (str) – recorded on the result (quantity is the registry id; key defaults to it).

  • files_per_label (dict, optional) – label -> file paths, recorded.

  • extra – further SelectivityResult fields (name, series, reference, barriers, curtin_hammett, warnings).

A label with no energy gets population 0.

Raises:

ValueError – fewer than two labels, or no energy at all.

goodvibes.selectivity.selectivity_rows(results, units='kcal/mol')[source]

One row per label of each SelectivityResult, for tables and CSV: selectivity (the block id), series, temperature, branch, barrier (units; document selectivities only), population (%), major (bool), ee (the signed ee of the result), ddG (units) and curtin_hammett.

goodvibes.selectivity.selectivity_to_dict(result, units=None)[source]

A SelectivityResult as a plain dict (JSON-ready); energies stay in Hartree unless units is given.

PES — data model (v4.2)

PES / reaction-profile data model.

Pure data + arithmetic; no I/O, no parsing, no global state. The model is what the legacy parser and the YAML parser produce, and what the output layer (Rich tables, JSON), plot_profile and the selectivity code consume.

Layers:

ThermoVector the thermo quantities of one structure or sum at one T ComputedEntry one parsed structure (QCData + ThermoOptions), evaluable

at any temperature (memoised)

ConformerSet one species, ≥1 conformers, with the ensemble rollups Point stoichiometric sum of species at one node of a pathway,

with a role (reactant | minimum | ts | product) and a display label

Edge a connection between two points (step | barrierless | none) Pathway ordered points + edges + a designated zero Series one line set on the axes / one column set in a table:

a quantity at a temperature, computed from the model or declared (typed-in levels that are never re-evaluated)

PESResult pathways + options + temperatures + series

Pathway.relative / Pathway.levels are the only places where relative values are formed: every table, JSON block and figure reads them, so they cannot disagree.

class goodvibes.pes_model.ComputedEntry(qcdata: Any, options: Any, file: str = '', _cache: Any, ~typing.Any]=<factory>)[source]

Bases: object

One parsed structure plus the options it is evaluated with.

thermo(T) returns the ThermoVector at temperature T and is memoised on the (temperature, options) pair, so a multi-temperature profile never re-parses a file and never evaluates the same temperature twice. When the options carry no explicit concentration the gas-phase standard state follows the temperature (P/RT), exactly as the CLI’s --ti scan does.

property base_temperature: float
bbe(T: float | None = None, options: Any = None) → Any[source]

The calc_bbe at temperature T (default: the base T).

file: str = ''
classmethod from_bbe(bbe: Any, file: str | None = None) → ComputedEntry | None[source]

Wrap a calc_bbe built by calc_bbe.from_options (or compute_thermo). Returns None when the object does not carry the parsed input and its options (stubs, or the legacy 15-argument constructor), in which case it can only be used at the temperature it was computed at.

classmethod from_result(result: Any) → ComputedEntry[source]

Wrap a ThermoResult from compute_thermo.

options: Any
qcdata: Any
thermo(T: float | None = None, options: Any = None) → ThermoVector[source]

ThermoVector at temperature T (default: the base T).

class goodvibes.pes_model.ConformerSet(name: str, files: List[str], bbes: List[Any], entries: List[ComputedEntry] | None = None, weight_by: str = 'qh_gibbs')[source]

Bases: object

A named species + ≥1 conformers.

bbes are the calc_bbe (or calc_bbe-shaped) objects at the base temperature, parallel to files. When every one of them carries its parsed input and options (anything built through calc_bbe.from_options / compute_thermo), entries is filled automatically and the set can be evaluated at any temperature; otherwise the rollups use the base-temperature values whatever T is passed (the pre-4.6 behaviour).

Encapsulates the rollup math: pure Boltzmann average, lowest-only, or gconf-corrected (lowest + Boltzmann adjustment + mixing entropy), weighting by weight_by (a registry quantity id; qh_gibbs by default).

property base_temperature: float | None
bbes: List[Any]
boltzmann_weighted(T: float) → ThermoVector[source]

Pure Boltzmann-weighted average over conformers, weights from weight_by (qh-G by default).

dedup(e_cutoff: float = 0.05, ro_cutoff: float = 0.01, rmsd_cutoff: float | None = None) → ConformerSet[source]

A new set without duplicate / enantiomeric conformers, using the same gates as goodvibes.sort.deduplicate (energy in kcal/mol, rotational constants as a fraction, optional Cartesian RMSD) and the same convention as the CLI’s --dedup: of each flagged pair the later file is the redundant copy and is dropped. A single-conformer set is returned unchanged.

ensemble_free_energy(T: float, quantity: str | None = None) → float[source]

−RT ln Σᵢ exp(−Gᵢ/RT) in Hartree over the conformers, i.e. the conformationally averaged value of quantity (default: weight_by) including the mixing entropy.

For quantity='qh_gibbs' this equals gconf_corrected(T).qh_gibbs when weight_by is qh_gibbs.

entries: List[ComputedEntry] | None = None
files: List[str]
classmethod from_results(name: str, results: Sequence[Any], weight_by: str = 'qh_gibbs') → ConformerSet[source]

Build a species from ThermoResult objects (compute_thermo).

gconf_corrected(T: float, QH: bool = True) → ThermoVector[source]

Lowest conformer + Boltzmann adjustment + mixing entropy −R Σ pᵢ ln pᵢ.

Mirrors the legacy pes.get_pes algorithm:

H_tot = H_min + (Σ pᵢ Hᵢ − H_min) S_tot = S_min + (Σ pᵢ Sᵢ + Σ −R pᵢ ln pᵢ − S_min) G(T) = H_tot − T·S_tot (or qh-H/qh-S if QH)

ZPE/SCF/SPC follow the plain Boltzmann sum (no mixing-entropy correction applies to those).

property is_single: bool
lowest_conformer(T: float | None = None) → ThermoVector[source]

Vector for the conformer with the lowest weight_by value (qh-G by default) at T.

lowest_index(T: float | None = None, quantity: str | None = None) → int[source]

Index of the conformer with the lowest quantity at T.

name: str
populations(T: float, quantity: str | None = None) → List[float][source]

Boltzmann populations of the conformers at T (sum to 1), weighted by quantity (default: weight_by).

property recomputable: bool

True when the set can be evaluated at temperatures other than the one its conformers were computed at.

rollup(T: float, gconf: bool = True, QH: bool = True, lowest_only: bool = False) → ThermoVector[source]

The species-level vector under the given rollup mode: lowest_only → lowest conformer; else gconf → gconf_corrected; else the pure Boltzmann average.

s_conf(T: float, quantity: str | None = None) → float[source]

Conformational (mixing) entropy −R Σ pᵢ ln pᵢ in Hartree/K.

vectors(T: float | None = None) → List[ThermoVector][source]

Per-conformer ThermoVectors at T (base temperature when None or when the set is not recomputable).

weight_by: str = 'qh_gibbs'
goodvibes.pes_model.EDGE_KINDS = ('step', 'barrierless', 'none')

Edge kinds. step is an ordinary connector, barrierless a dotted one (association / dissociation without a located TS), none no line.

class goodvibes.pes_model.Edge(src: str, dst: str, kind: str = 'step')[source]

Bases: object

A connection between two points of a pathway (by point label).

dst: str
kind: str = 'step'
src: str
class goodvibes.pes_model.PESOptions(units: 'str' = 'kcal/mol', decimals: 'int' = 2, gconf: 'bool' = True, QH: 'bool' = True, spc_used: 'bool' = False, lowest_only: 'bool' = False)[source]

Bases: object

QH: bool = True
decimals: int = 2
gconf: bool = True
lowest_only: bool = False
property rollup_kw: Dict[str, bool]

Keyword arguments for Point.thermo / Pathway.relative.

spc_used: bool = False
to_user_units(hartree: float | None) → float | None[source]
units: str = 'kcal/mol'
class goodvibes.pes_model.PESResult(pathways: ~typing.List[~goodvibes.pes_model.Pathway], options: ~goodvibes.pes_model.PESOptions, temperatures: ~typing.List[float] = <factory>, series: ~typing.List[~goodvibes.pes_model.Series] = <factory>, order: ~typing.List[str] | None = None, source: ~typing.Any = None)[source]

Bases: object

Top-level container: pathways + options + temperatures + series.

default_series(quantity: str = 'qh_gibbs', temperatures: Sequence[float] | None = None) → List[Series][source]

One computed series per temperature (the implicit series of a result that declares none).

levels(series: Sequence[Series] | None = None) → Dict[str, Dict[str, Dict[str, float | None]]][source]

{series id: {pathway name: {point label: value}}} in user units, for the given series (default: self.series or the implicit qh-G series per temperature).

merged_order() → List[str][source]

Point labels in x order: order when set, else the union of the pathways’ point sequences, each pathway’s order preserved.

options: PESOptions
order: List[str] | None = None
pathway(name) → Pathway[source]
pathways: List[Pathway]
property recomputable: bool

True when every species can be evaluated at other temperatures.

series: List[Series]
source: Any = None
property temperature: float

The first (base) temperature.

temperatures: List[float]
goodvibes.pes_model.POINT_ROLES = ('reactant', 'minimum', 'ts', 'product')

Point roles. A transition state is drawn with its label above the bar and takes part in barrier annotations; minima/reactants/products are labelled below.

class goodvibes.pes_model.Pathway(name: str, points: List[Point], zero: Point = None, edges: List[Edge] = None)[source]

Bases: object

Ordered points + edges + a designated zero (defaults to points[0]).

edges default to one step edge between each pair of consecutive points; give an explicit list to mark barrierless steps or to omit a connector. Edges name points by label.

property computable: bool

True when every point (and the zero) has species, so the full thermochemistry table can be formed for this pathway.

edge_kind(src: str, dst: str) → str | None[source]

Kind of the edge src -> dst, or None when there is none.

edges: List[Edge] = None
property labels: List[str]
levels(T: float, quantity: str = 'qh_gibbs', gconf: bool = True, QH: bool = True, lowest_only: bool = False) → Dict[str, float | None][source]

{point label: Δquantity relative to the zero, in Hartree} at T.

quantity is a registry id or alias (goodvibes.quantities); entropies are returned as T·ΔS. A value is None when the quantity is unavailable (single-point energy without –spc). A point without species (one that only carries declared values in a reaction-profile document) is absent from the result, and a pathway whose zero has no species gives an empty mapping.

name: str
point(label: str) → Point[source]
points: List[Point]
relative(T: float, gconf: bool = True, QH: bool = True, lowest_only: bool = False) → List[ThermoVector][source]

Per-point ΔThermo relative to self.zero, in Hartree.

This and levels() are the only places relative values are formed; tables, JSON and figures all read them.

with_edges(edges: Iterable) → Pathway[source]

Copy with explicit edges (tuples (src, dst[, kind]) or Edge).

zero: Point = None
class goodvibes.pes_model.Point(label: str, species: List[Tuple[int, ConformerSet]], role: str = 'minimum', display: str | None = None)[source]

Bases: object

One node of a pathway: a stoichiometric sum of ConformerSets.

label is the point’s identity (the string written in the PES file, e.g. “2*A + B”); display is what a figure prints for it (defaults to the label); role is one of POINT_ROLES.

display: str | None = None
property display_label: str
classmethod from_label(label: str, species_map: dict, role: str = 'minimum', display: str | None = None) → Point[source]

Build a Point from a label string and {name: ConformerSet} map.

property id: str
property is_ts: bool
label: str
role: str = 'minimum'
species: List[Tuple[int, ConformerSet]]
thermo(T: float, gconf: bool = True, QH: bool = True, lowest_only: bool = False) → ThermoVector[source]

Total thermo at this point: Σ coeff_i × ConformerSet_i.thermo(T).

Per-species rollup precedence:

lowest_only=True → the species’ lowest qh-G conformer only else gconf=True → lowest + Boltzmann adjustment + mixing entropy else → pure Boltzmann-weighted average

class goodvibes.pes_model.Series(id: str, label: str, quantity: str = 'qh_gibbs', temperature: float | None = None, method: str | None = None, style: Dict[str, ~typing.Any]=<factory>, levels: Dict[str, ~typing.Dict[str, float | None]] | None=None, declared: bool = False, units: str | None = None, uncertainty: Dict[str, ~typing.Dict[str, float]] | None=None, standard_state: Dict[str, ~typing.Any] | None=None, extensions: Dict[str, ~typing.Any]=<factory>)[source]

Bases: object

One quantity at one temperature: a line set on the axes, a column set in a table.

A computed series (declared=False) is evaluated from the model through Pathway.levels(temperature, quantity); temperature None means the result’s first temperature. Its levels, when set, are a stored evaluation (a reaction-profile document written by Profile.evaluate) and are used as they are. A declared series (declared=True) carries typed-in levels — literature values, a hand-entered table — as {pathway name: {point label: value}} in units (default: the result’s units) and is never re-evaluated; a point missing from its levels is simply absent from the figure. style holds matplotlib overrides (linestyle, color, …).

declared: bool = False
classmethod declared_from(id: str, label: str, levels: Mapping[str, Mapping[str, float | None]], *, quantity: str = 'gibbs', temperature: float | None = None, method: str | None = None, units: str | None = None, style: Dict[str, Any] | None = None) → Series[source]

A declared series from {pathway: {point label: value}}.

evaluate(result: PESResult, pathway: Pathway) → Dict[str, float | None][source]

{point label: value in the result’s units} for one pathway.

Stored levels (every declared series, and a computed series read from an evaluated document) are returned converted to the result’s units; otherwise a computed series goes through Pathway.levels.

evaluate_uncertainty(result: PESResult, pathway: Pathway) → Dict[str, float][source]

{point label: uncertainty in the result’s units} for one pathway (empty when the series carries none).

extensions: Dict[str, Any]
id: str
label: str
levels: Dict[str, Dict[str, float | None]] | None = None
method: str | None = None
quantity: str = 'qh_gibbs'
standard_state: Dict[str, Any] | None = None
style: Dict[str, Any]
temperature: float | None = None
uncertainty: Dict[str, Dict[str, float]] | None = None
units: str | None = None
class goodvibes.pes_model.ThermoVector(scf_energy: float, zpe: float, enthalpy: float, qh_enthalpy: float, entropy: float, qh_entropy: float, gibbs: float, qh_gibbs: float, sp_energy: float | None = None)[source]

Bases: object

Bundle of thermo quantities for one species at one temperature.

Energy fields are in Hartree; entropy fields are in Hartree/K (matching calc_bbe). sp_energy is None when –spc was not used, and every field but scf_energy is None for an energy-only structure (no frequencies); arithmetic propagates None (if either operand has a None field, the result does).

enthalpy: float
entropy: float
get(quantity, T: float | None = None) → float | None[source]

Value of a registry quantity in Hartree (see goodvibes.quantities).

Entropy quantities are returned as T·S and therefore need T. e_zpe is (sp_energy if present else scf_energy) + zpe, matching the table convention that H and G are SPC-substituted when –spc is used. Returns None where the underlying value is None (no SPC).

gibbs: float
qh_enthalpy: float
qh_entropy: float
qh_gibbs: float
scf_energy: float
sp_energy: float | None = None
classmethod zero(with_sp: bool = False) → ThermoVector[source]

Identity element for addition.

zpe: float
goodvibes.pes_model.merge_point_order(sequences: Sequence[Sequence[str]]) → List[str][source]

Merge several ordered label sequences into one order that keeps every sequence’s own order. A label new to the merged list is inserted just before the earliest already-placed label that follows it in its own sequence, or appended when none does; so [R, Int1, TS1, P] and [R, TS1, P] merge to the first, and two branches [R, TS_R, P_R] / [R, TS_S, P_S] keep each branch contiguous.

goodvibes.pes_model.parse_point_label(label: str) → List[Tuple[int, str]][source]

Parse a point label like “2*A + B + C” into [(2,’A’), (1,’B’), (1,’C’)].

Whitespace-tolerant. Coefficients must be positive integers. Returns the terms in source order; duplicate species are allowed (they’ll be summed).

PES — loader and dispatcher (v4.2)

Format-agnostic PES loading pipeline.

Three stages, each independently testable:

text ──parse_legacy()/parse_yaml()──▶ PESSpec PESSpec + thermo_data ──build_pes_result()──▶ PESResult

PESSpec is the intermediate that both parsers produce: a description of pathways, species (as file patterns), zero overrides, and options — but no references to actual calc_bbe instances. The builder resolves patterns against thermo_data, constructs ConformerSet`s, and emits a fully-realized `PESResult.

The format dispatcher load_pes() sniffs the file content (presence of — # PES/# SPECIES/# FORMAT markers) to choose the parser; the legacy path emits a DeprecationWarning.

class goodvibes.pes_loader.PESSpec(pathways: ~typing.Dict[str, ~typing.List[str]], species: ~typing.Dict[str, str | ~typing.List[str]], zero: ~typing.Dict[str, str] = <factory>, options: ~goodvibes.pes_model.PESOptions = <factory>, format_extras: ~typing.Dict[str, str] = <factory>)[source]

Bases: object

Format-agnostic intermediate. Both parsers emit this.

pathways

pathway name -> ordered list of point labels.

Type:

Dict[str, List[str]]

species

species name -> file pattern (glob or literal) or list of patterns.

Type:

Dict[str, str | List[str]]

zero

pathway name -> point label to use as zero. Optional; pathways missing from this dict default to their first point.

Type:

Dict[str, str]

options

PESOptions parsed from the FORMAT block.

Type:

goodvibes.pes_model.PESOptions

format_extras

any FORMAT keys not consumed by PESOptions (kept verbatim so graph_reaction_profile can read them).

Type:

Dict[str, str]

format_extras: Dict[str, str]
options: PESOptions
pathways: Dict[str, List[str]]
species: Dict[str, str | List[str]]
zero: Dict[str, str]
goodvibes.pes_loader.build_pes_result(spec: PESSpec, thermo_data: dict, temperatures: List[float] | None = None) → PESResult[source]

Resolve patterns, build ConformerSets, parse point labels, return PESResult.

All species referenced (transitively) by the pathways’ points must resolve to at least one file in thermo_data; unresolved names raise KeyError.

goodvibes.pes_loader.is_legacy_format(text: str) → bool[source]

Sniff for the — # PES / # SPECIES / # FORMAT markers.

Presence of any one of these markers anywhere in the file means legacy. Otherwise the file is treated as true YAML.

goodvibes.pes_loader.load_pes(path: str, thermo_data: dict, temperatures: List[float] | None = None) → PESResult[source]

Read a PES definition file, parse it, and return a PESResult.

Three formats are accepted: a reaction-profile document (schema: reaction-profile/1.x, YAML or JSON; see goodvibes.profile), the v2 PES YAML, and the legacy --- # PES text (deprecated; emits a DeprecationWarning). The returned result carries the document it was read from as result.source (a goodvibes.profile.Profile; the two older formats are upgraded).

goodvibes.pes_loader.resolve_pattern(pattern: str, thermo_data: dict) → List[str][source]

Resolve a species pattern against the keys of thermo_data.

Two pattern flavors:

  • File patterns (default) — matched against the basename of each key with the file extension stripped. Supports fnmatch globs; a pattern with no glob characters must equal the stem exactly.

  • Directory patterns — strings prefixed with @dir: (set automatically by the YAML loader when the user writes {dir: “X”} / {dirs: […]}). Matched against the basename of each key’s parent directory. Supports fnmatch globs.

Returns the matched thermo_data keys in their original (insertion) order.

PES — legacy line-based parser (deprecated)

Parser for the legacy line-based PES format.

The format predates v4.2; despite the .yaml extension it isn’t valid YAML (parens in species names like Cu(III)-S and the [a, b, c] PES point lists with bare identifiers would either fail or parse strangely).

Sections are introduced by — # NAME comment markers:

— # PES

Reaction: [Int-I + TolS, Int-II, Int-III]

— # SPECIES

Int-I: Int-I_*

— # FORMAT

units: kcal/mol dec: 1 zero: Int-I + TolS

The parser produces a PESSpec; format keys it doesn’t consume (graph styling — dpi, color, title, etc.) are preserved in format_extras so graph_reaction_profile can keep reading them.

goodvibes.pes_legacy.parse_legacy(text: str) → PESSpec[source]

Parse the legacy line-based PES format into a PESSpec.

PES — true-YAML parser (v4.2)

Parser for the new (true YAML) PES format.

Schema:

pathways:

Reaction: [“Int-I + TolS + TolSH”, “Int-II + TolSH”, “Int-III”]

species:

Int-I: {files: “Int-I_*.log”} TolS: {files: [“TolS.log”]} Int-II: {files: “Int-II_*.log”}

zero:

Reaction: “Int-I + TolS + TolSH” # optional; defaults to points[0]

format:

units: kcal/mol decimals: 1

pathways is a dict of pathway-name → ordered list of point label strings. species is a dict of species-name → {files: <glob string OR list>} (the dict shape leaves room for per-species options later — scaling factors, symmetry numbers — without breaking the schema). zero is optional and applies per pathway. format parses into PESOptions.

goodvibes.pes_yaml.parse_yaml(text: str) → PESSpec[source]

Parse the YAML text into a PESSpec. PyYAML is required.

goodvibes.pes_yaml.parse_yaml_data(data: dict) → PESSpec[source]

Parse an already-loaded v2 PES mapping into a PESSpec.

PES — back-compat surface and reaction-profile plot

PES driver: legacy get_pes class re-implemented on top of the v4.2 model.

The class signature is unchanged; everything that printed/plotted off get_pes instances in v4.0/v4.1 still does. Internally we delegate parsing and rollup to pes_loader + pes_model, then project the resulting PESResult into the legacy parallel-list attribute layout that output.print_pes_results and pes.graph_reaction_profile already consume.

This shim ships through the v4.x line. v5.1 removes get_pes entirely in favor of the structured API (goodvibes.profile, plot_profile).

class goodvibes.pes.get_pes(file, thermo_data, temperature, gconf, QH)[source]

Bases: object

Back-compat surface around pes_loader.load_pes.

Reads a PES definition file (legacy line-based or modern YAML), computes Boltzmann-weighted thermo at each pathway point with optional gconf correction, and exposes the same parallel-list attribute layout as the pre-v4.2 implementation: path, species, *_abs, *_zero, plus g_qhgvals / g_species_qhgzero / g_rel_val for the reaction-profile plot.

goodvibes.pes.graph_reaction_profile(graph_data, options, plt)[source]

Render a reaction energy profile plot using quasi-harmonic Gibbs free energies.

Parameters:
  • graph_data (get_pes) – Populated PES object providing pathway labels and thermodynamic arrays used for plotting (e.g., path, qhg_abs, qhg_zero, e_abs, species, g_qhgvals, g_species_qhgzero, g_rel_val, and units).

  • options (object) – Options container with a graph attribute pointing to a formatting file that controls plot appearance (ylim, colors, title, dpi, etc.). If dpi is set in that file, an image file named “Rxn_profile_<options.graph stem>.png” will be written.

  • plt (module) – The matplotlib.pyplot module (used to create and display the figure).

goodvibes.pes.jitter(datasets, color, ax, nx, marker, edgecol='black')[source]

Randomly offsets and plots vertically overlapping points to reduce marker overlap on a matplotlib Axes.

Parameters:
  • datasets (iterable) – Iterable of numeric y-values (each element plotted as a point).

  • color (str or tuple) – Marker color.

  • ax (matplotlib.axes.Axes) – Axes on which to draw the markers.

  • nx (float) – Center x-coordinate around which points are jittered.

  • marker (str) – Marker style string.

  • edgecol (str, optional) – Marker edge color. Defaults to ‘black’.

Plots (matplotlib, goodvibes[plot])

Visualization for GoodVibes.

Pure plotting layer that renders the v4.2+ structured types (PESResult, SelectivityResult, ThermoResult) to matplotlib axes. matplotlib is an optional dependency; install with pip install goodvibes[plot] (or pip install matplotlib directly).

All functions accept an optional ax=None; when None, they create a fresh plt.subplots() figure and return the axes. The plt import is deferred so just import goodvibes.plot doesn’t fail when matplotlib is missing — only the call-site fails, with a clear message.

Public API:

plot_profile(pes_result, series=…, …) — reaction profile (ProfileAxes) STYLE_PRESETS / resolve_preset(name) — figure presets for plot_profile plot_pes(pes_result, ax=None, **kw) — 4.2-4.5 shim over plot_profile plot_selectivity_strip(selectivity,

thermo_lookup, ax=None) — per-species scatter

plot_boltzmann_histogram(conformers, …) — population bars plot_temperature_scan(conformers or profile, …) — thermo or levels vs T

class goodvibes.plot.ProfileAxes(figure: ~typing.Any, axes: ~typing.List[~typing.Any], levels: ~typing.Dict[str, ~typing.Dict[str, ~typing.Dict[str, float | None]]], units: str, order: ~typing.List[str], x: ~typing.Dict[str, float], series: ~typing.List[~typing.Any], pathways: ~typing.List[~typing.Any], colors: ~typing.Dict[str, ~typing.Any], linestyles: ~typing.Dict[str, str], layout: str = 'overlay', uncertainty: ~typing.Dict[str, ~typing.Dict[str, ~typing.Dict[str, float]]] = <factory>, preset: str | None = None, rc: ~typing.Dict[str, ~typing.Any] = <factory>, label_size: ~typing.Any = 'x-small', pes_result: ~typing.Any = None, _ids: ~typing.Any = <factory>)[source]

Bases: object

What plot_profile drew, with the numbers behind it.

levels is {series id: {pathway name: {point label: value}}} in units: the same evaluation the bars were drawn from, so a table written from it cannot disagree with the figure; uncertainty has the same shape for the series that carry one. order is the merged x order (point labels) and x maps a label to its position. element_ids maps each drawn element’s SVG id to what it shows.

annotate_barrier(pathway, src: str, dst: str, series=None, *, fmt: str = '{:+.1f}', offset: float = 0.3, color=None, ax=None)[source]

Mark the difference dst − src on one pathway with a vertical double-headed arrow beside dst and the value in units. Returns the matplotlib annotation.

property ax

The (first) matplotlib Axes.

axes: List[Any]
axes_for(pathway) → Any[source]

The Axes a pathway (name or object) was drawn on.

close() → None[source]
colors: Dict[str, Any]
property element_ids: Dict[str, Dict[str, str]]
figure: Any
label_size: Any = 'x-small'
layout: str = 'overlay'
level(pathway, point: str, series=None) → float | None[source]

A drawn level in units. series defaults to the first.

levels: Dict[str, Dict[str, Dict[str, float | None]]]
linestyles: Dict[str, str]
order: List[str]
pathways: List[Any]
pes_result: Any = None
preset: str | None = None
rc: Dict[str, Any]
save(*paths: str, dpi: int = 200, bbox_inches: str = 'tight', embed: bool = True, **kw) → None[source]

Write the figure to each path (format by extension).

An SVG keeps its text as text (svg.fonttype = 'none'), gives every bar, connector, label and error bar an id (see element_ids) and, unless embed=False, carries the reaction-profile document of what was drawn in a <metadata> element, so goodvibes.load_profile("figure.svg") reads the numbers back. Output is deterministic (no date, fixed id salt).

series: List[Any]
to_document() → dict[source]

The reaction-profile document of what was drawn: the drawn series with the drawn levels (and uncertainties), in units. Needs the PESResult the figure came from (plot_profile keeps it).

uncertainty: Dict[str, Dict[str, Dict[str, float]]]
units: str
x: Dict[str, float]
goodvibes.plot.STYLE_PRESETS: Dict[str, StylePreset | None] = {'double-column': StylePreset(name='double-column', figsize=(7.0, 3.2), panel_height=2.4, font_size=8, label_size=7, bar_width=1.5, connector_width=0.9, axes_width=0.8, marker_size=4), 'none': None, 'single-column': StylePreset(name='single-column', figsize=(3.35, 2.6), panel_height=1.9, font_size=7, label_size=6, bar_width=1.2, connector_width=0.7, axes_width=0.6, marker_size=3), 'slide': StylePreset(name='slide', figsize=(10.0, 5.6), panel_height=3.2, font_size=16, label_size=14, bar_width=3.0, connector_width=1.8, axes_width=1.2, marker_size=7)}

Named styles for style.preset (reaction-profile documents), plot_profile(preset=...) and goodvibes-profile plot --preset. ‘none’ keeps matplotlib’s defaults and a figure sized to the profile.

goodvibes.plot.SVG_METADATA_ID = 'goodvibes-reaction-profile'

id of the <metadata> element that carries a figure’s document.

class goodvibes.plot.StylePreset(name: str, figsize: Tuple[float, float], panel_height: float, font_size: float, label_size: float, bar_width: float, connector_width: float, axes_width: float, marker_size: float)[source]

Bases: object

Figure size, font sizes (pt) and line widths (pt) for one target.

Applied inside a matplotlib.rc_context for the figure only, never set globally. figsize is the overlay figure in inches; with layout='panels' each panel is panel_height tall.

axes_width: float
bar_width: float
connector_width: float
figsize: Tuple[float, float]
font_size: float
label_size: float
marker_size: float
name: str
panel_height: float
property rc: Dict[str, Any]
goodvibes.plot.embed_svg_metadata(svg: str, payload: Mapping[str, Any]) → str[source]

Insert payload (JSON) as a <metadata id="goodvibes-reaction-profile"> element right after the opening <svg> tag.

goodvibes.plot.plot_boltzmann_histogram(source: Any, *, temperature: float = 298.15, quantity: str | None = None, top: int | None = None, sort: bool = True, ax=None, title: str | None = None)[source]

Bar chart of conformer Boltzmann populations.

Parameters:
  • source – a ConformerSet, a sequence of ThermoResult (one species’ conformers), or a mapping {group: ConformerSet or results}. With a mapping the populations are taken over all conformers together (for example the transition states of competing pathways, so the bars show where the selectivity comes from); bars are coloured by group and the legend gives each group’s total population.

  • temperature – K; each conformer is re-evaluated at it when the set carries its parsed data.

  • quantity – the registry quantity weighted (default the set’s weight_by, qh_gibbs; electronic for energy-only ensembles).

  • top – draw only the top most populated conformers, plus one “other” bar for the rest.

  • sort – most populated first (default); False keeps input order.

  • ax – optional matplotlib Axes.

  • title – figure title; generated when None.

Returns:

The matplotlib Axes. Each bar’s gid is pop-<group>-<name>.

goodvibes.plot.plot_pes(pes_result: Any, *, ax=None, pathway_index: int | Sequence[int] | None = None, connector_style: str = 'bezier', colors: Sequence[str] | None = None, show_conformers: bool = False, thermo_lookup: Mapping[str, float] | Callable[[str], float] | None = None, title: str | None = None, label_points: bool = False, quantity: str = 'qh_gibbs')[source]

Plot one or more pathways from a PESResult as a reaction profile.

A thin wrapper over plot_profile() kept for the 4.2–4.5 API; it returns the matplotlib Axes. New code should call plot_profile, which also returns the drawn levels and supports several series (temperatures, quantities, declared values) on one axes.

Parameters:
  • pes_result – a PESResult from goodvibes.pes_loader.load_pes.

  • ax – optional matplotlib Axes; new figure if None.

  • pathway_index – which pathway(s) to draw (None → all, an int, or a list of ints).

  • connector_style – ‘bezier’ (default) or ‘linear’.

  • colors – per-pathway colors in pathway order.

  • show_conformers – scatter individual conformer values around each level.

  • thermo_lookup – deprecated and ignored (conformer values are read from the PESResult); accepted for backwards compatibility.

  • title – figure title; defaults to the pathway names + temperature.

  • label_points – annotate each point’s value next to its bar.

  • quantity – which relative quantity to draw, by registry id or alias (see goodvibes.quantities); the y-label follows the choice.

Returns:

The matplotlib Axes the profile(s) were drawn on.

goodvibes.plot.plot_profile(pes_result: Any, *, series=None, quantity: str | None = None, temperatures: Sequence[float] | None = None, pathways=None, layout: str = 'overlay', ax=None, style: Mapping[str, Any] | None = None, colors=None, show_conformers: bool = False, label_points: bool = False, title: str | None = None, order: Sequence[str] | None = None, preset: str | None = None, uncertainty: bool = True) → ProfileAxes[source]

Draw a reaction profile from a PESResult.

Every pathway is drawn on a shared x axis whose order is the merge of the pathways’ point sequences (PESResult.merged_order), so pathways of different lengths, or branches sharing a reactant, line up by point label. Colour encodes the pathway; linestyle encodes the series (a quantity at a temperature, computed from the model or declared). Transition-state points (role='ts') carry their value label above the bar, other points below; a barrierless edge is a dotted connector; an edge of kind none draws no connector.

Parameters:
  • pes_result – a PESResult (goodvibes.load_pes or built by hand).

  • series – what to draw. A Series / list of Series (computed or declared), or series ids from pes_result.series. Default: pes_result.series when it declares any, else one computed series of quantity per temperature.

  • quantity – registry id or alias for the implicit series (default ‘qh_gibbs’); the y-label follows it.

  • temperatures – temperatures of the implicit series (default: the result’s); several give a temperature overlay.

  • pathways – which pathways to draw (names, indices or objects); default all.

  • layout – ‘overlay’ (all pathways on one axes) or ‘panels’ (one axes per pathway, shared y).

  • ax – an Axes to draw on (overlay only); a new figure otherwise.

  • style – {‘connector’: ‘bezier’ | ‘linear’ | ‘step’, ‘bar_half’: float, ‘decimals’: int, ‘figsize’: (w, h), ‘linestyles’: […], ‘preset’: name}.

  • colors – per-pathway colours, a sequence in pathway order or a {name: colour} mapping. Default: black for one pathway, the matplotlib cycle otherwise.

  • show_conformers – scatter each conformer at level + (conformer − species rollup) in the plotted quantity (computed series only).

  • label_points – print each level’s value next to its bar.

  • title – figure title (default: pathway names, and the temperature when there is one).

  • order – explicit x order of point labels (overrides the result’s).

  • preset – a STYLE_PRESETS name (‘single-column’, ‘double-column’, ‘slide’; default style['preset'] or ‘none’): figure size, fonts and line widths for that target, applied to this figure only. An explicit style['figsize'] still wins.

  • uncertainty – draw each series’ uncertainty as an error bar (± the value) on its levels.

Returns:

A ProfileAxes with the figure, axes and the drawn levels.

goodvibes.plot.plot_selectivity_strip(selectivity: Any, thermo_lookup: Mapping[str, float] | Callable[[str], float], *, ax=None, units: str = 'kcal/mol', title: str | None = None, jitter: float = 0.04, seed: int = 0)[source]

Per-species strip plot of conformer ΔG values.

Each species is one column; conformer ΔG values (relative to the lowest across all species) are scattered as dots.

Parameters:
  • selectivity – a SelectivityResult (from goodvibes.selectivity.compute_selectivity) — provides the label order, populations, and files_per_label.

  • thermo_lookup – either a {file_path: qh_gibbs_free_energy} mapping (Hartree) or a callable that takes a path and returns the same. Used to read the per-conformer ΔG.

  • ax – optional matplotlib Axes. New figure created if None.

  • units – ‘kcal/mol’ (default), ‘kJ/mol’, ‘eV’ or ‘hartree’.

  • title – figure title; auto-generated from the result’s temperature and key when None.

  • jitter – horizontal spread of conformer dots within each species column (fraction of column width).

  • seed – RNG seed for the jitter (deterministic plots).

Returns:

The matplotlib Axes the strip plot was drawn on.

goodvibes.plot.plot_temperature_scan(source: Any, temperatures: Sequence[float] | None = None, *, quantities: Sequence[str] | None = None, points: Sequence[str] | None = None, pathway: str | None = None, units: str = 'kcal/mol', ax=None, title: str | None = None)[source]

Thermochemistry against temperature.

Two kinds of source:

  • a ConformerSet (or a sequence of ThermoResult, one species’ conformers) and temperatures: one line per quantity (default Δqh-G, Δqh-H and T·Δqh-S) of the conformer ensemble (the gconf rollup), relative to its value at the first temperature;

  • a reaction-profile Profile: one line per point (points, default every point of pathway but its zero; pathway defaults to the first) giving its level against temperature over the document’s series of one quantity (quantities[0], default that of the first series with a temperature). A point is read on pathway when it is there, else on the first pathway that holds it (each relative to that pathway’s zero). With temperatures the document is evaluated at them first (it needs embedded conformers).

Returns:

The matplotlib Axes; each line’s gid is scan-<quantity> or scan-<point>.

goodvibes.plot.read_svg_metadata(svg: str) → dict | None[source]

The payload ProfileAxes.save embedded in an SVG, or None.

goodvibes.plot.resolve_preset(name: str | None) → StylePreset | None[source]

The StylePreset called name (None or ‘none’ for the defaults).

Structured-output schema

Stable schema definitions for GoodVibes structured output.

The shape of the JSON written by –json / goodvibes.api.write_json_results is now documented and stable. This module is the single source of truth for that schema:

  • SCHEMA_VERSION — the current shipping version.

  • SCHEMA_MIN_COMPATIBLE — readers tolerate any version ≥ this.

  • validate(payload) — runtime sanity check; raises ValueError

    with a clear message on shape/version mismatches.

  • TypedDicts (Payload, ResultEntry, ThermoBlock, …) — give

    static checkers and editors completion when working with parsed payloads.

The JSON file is also accepted by –import as a cache (skips re-parsing the underlying QC outputs); the same schema therefore plays two roles.

## Version policy (v1.0+)

Major bump (X.0) breaking change — fields removed or renamed,

or value types changed.

Minor bump (1.X) backwards-compatible addition — new fields,

new optional blocks, new enum values.

Patch bump (1.0.X) no-op (we don’t bump for typo fixes etc.).

Readers SHOULD accept any minor version ≥ their min and ignore unknown fields. Writers SHOULD emit the version they were built against.

class goodvibes.schema.PESBlock[source]

Bases: TypedDict

pathways: List[PESPathway]
class goodvibes.schema.PESPathway[source]

Bases: TypedDict

name: str
points: List[PESPoint]
temperature: float
units: str
zero_label: str
class goodvibes.schema.PESPoint[source]

Bases: TypedDict

label: str
relative: dict
species: List[dict]
class goodvibes.schema.Payload[source]

Bases: TypedDict

The top-level JSON document GoodVibes writes.

generated_at: str
goodvibes_version: str
options: dict
pes: PESBlock
profile: dict
results: List[ResultEntry]
schema_version: str
selectivity: SelectivityBlock
selectivity_lowest: SelectivityBlock
class goodvibes.schema.ResultEntry[source]

Bases: TypedDict

One per input QC file.

boltzmann_factor: float | None
file: str
media_conc: float | None
name: str
qcdata: dict | None
thermo: ThermoBlock
goodvibes.schema.SCHEMA_MIN_COMPATIBLE = '1.0'

The oldest schema a reader in this build will accept. Bump only on a true breaking change (e.g. v2.0).

goodvibes.schema.SCHEMA_VERSION = '1.1'

The schema version this build of GoodVibes writes. 1.1 adds the optional top-level profile block (a reaction-profile document, see goodvibes.profile); a 1.0 reader ignores it.

class goodvibes.schema.SelectivityBlock[source]

Bases: TypedDict

key: str
labels: List[str]
results: List[SelectivityResult]
class goodvibes.schema.SelectivityResult[source]

Bases: TypedDict

One per temperature inside selectivity/selectivity_lowest.

ddG: float | None
ee: float | None
ee_signed: float | None
ensemble_energies: dict
files_per_label: dict
major: str
populations: dict
preferred: str
ratio: float | None
raw_boltzmann: dict
temperature: float
class goodvibes.schema.ThermoBlock[source]

Bases: TypedDict

Per-file thermochemical numbers in atomic units (Hartree, Hartree/K).

Entries are Optional[float] — None when the QC output didn’t expose that quantity (e.g. sp_energy is None unless –spc was used, frequencies are absent for SP-only outputs).

applied_freq_scale_factor: float | None
enthalpy: float | None
entropy: float | None
frequency_wn: List[float] | None
gibbs_free_energy: float | None
im_frequency_wn: List[float] | None
inverted_freqs: List[float] | None
job_type: str | None
linear_mol: bool | None
multiplicity: int | None
point_group: str | None
qh_enthalpy: float | None
qh_entropy: float | None
qh_gibbs_free_energy: float | None
scf_energy: float | None
sp_energy: float | None
symmno: int | None
zero_point_corr: float | None
zpe: float | None
goodvibes.schema.validate(payload: Any) → None[source]

Light runtime sanity check on a loaded payload.

Verifies the document has a schema_version that this build can read and that the required top-level fields are present. Does NOT deeply type-check every field — that’s the consumer’s responsibility via the TypedDicts above.

Raises:

ValueError – with a one-line, actionable message on any failure.

Sorting and duplicate detection

Structure sorting and duplicate detection for GoodVibes.

goodvibes.sort.deduplicate(thermo_data, *, e_cutoff=0.05, ro_cutoff=0.01, rmsd_cutoff=None, groups=None)[source]

Identify duplicate or enantiomeric structures by comparing energies, rotational constants, and optionally Cartesian RMSD.

All active criteria must pass for a pair to be flagged as duplicate.

The energy and rotational-constant gates cannot tell enantiomers apart, so when structures belong to labelled species (--label R=... S=...) pass groups and only pairs inside the same group are compared: the R and S transition states of a selectivity calculation are then never collapsed into one. Files in no group are compared only with each other.

Parameters:
  • thermo_data (dict) – file path → calc_bbe mapping.

  • e_cutoff (float) – max absolute SCF energy difference in kcal/mol (default 0.05).

  • ro_cutoff (float) – max relative difference in rotational constants as a fraction, e.g. 0.01 = 1% (default 0.01).

  • rmsd_cutoff (float or None) – max Cartesian RMSD in Angstrom. None (default) disables RMSD comparison; 0.125 matches CREST.

  • groups (Mapping[str, Iterable[str]] or None) – label → files. None (default) compares every pair.

Returns:

pairs [file_i, file_j] flagged as duplicates.

Return type:

list

goodvibes.sort.kabsch_rmsd(coords_a, coords_b)[source]

Compute the RMSD between two Nx3 Cartesian coordinate sets after optimal rigid alignment using the Kabsch algorithm.

Parameters:
  • coords_a (array-like) – Reference coordinates with shape (N, 3).

  • coords_b (array-like) – Mobile coordinates with shape (N, 3).

Returns:

Root-mean-square deviation between the aligned coordinates, in the same units as the inputs.

Return type:

float

goodvibes.sort.sort_thermo(thermo_data, key)[source]

Return thermo_data reordered by the given energy attribute (lowest first).

Entries with linear_warning, missing the attribute, or with a None value are placed at the end.

Parameters:
  • thermo_data (dict) – file path → calc_bbe mapping.

  • key (str) – sort mode — ‘energy’ (scf_energy) or ‘gibbs’ (qh_gibbs_free_energy).

Returns:

new dict with the same items in sorted order.

Return type:

dict

File validation

File validation and consistency checks for GoodVibes.

goodvibes.validation.check_files(thermo_data, options, level_of_theory)[source]

Run consistency checks across all calculation output files.

Checks: program version, solvation model, level of theory, charge/multiplicity, standard concentration, linear molecule frequencies, TS imaginary frequencies, empirical dispersion, and (if –spc) single-point correction consistency.

Parameters:
  • thermo_data (dict) – file path → calc_bbe mapping.

  • options (Namespace) – parsed CLI options. Uses: conc, spc, duplicate.

  • level_of_theory (list) – level of theory strings, one per file.

goodvibes.validation.collect_and_validate_files(files, options)[source]

Read initial metadata for each output file, remove files that terminated with ‘Error’ or ‘Incomplete’, and verify SPC file termination when SPC mode is enabled.

When a file’s initial read reports progress ‘Error’ or ‘Incomplete’, that file is removed from the returned lists and a warning is logged. If SPC mode (options.spc) is set and not ‘link’, the function attempts to locate corresponding SPC files; if any discovered SPC file reports ‘Error’ or ‘Incomplete’ the program exits. Returned lists remain aligned: the returned level_of_theory and solvation_model correspond to the returned files order.

Parameters:
  • files (list) – Paths to output files to validate.

  • options (Namespace) – Parsed CLI options. Uses the spc attribute to control SPC lookup behavior.

Returns:

(files, level_of_theory, solvation_model) — the filtered file paths, and parallel lists of each file’s level of theory and solvation model.

Return type:

tuple

goodvibes.validation.print_check_fails(check_attribute, file, attribute, option2=None)[source]

Report groups of files that share differing attribute values.

Groups files by the values in check_attribute (or by (value, option2_value) when option2 is provided) and logs each distinct group. For each group the doc prints the attribute value(s) and up to the first three filenames; if more files are present the remaining count is reported.

Parameters:
  • check_attribute (list) – Attribute values aligned with the files list (one value per file).

  • file (list) – File identifiers aligned with check_attribute.

  • attribute (str) – Human-readable name of the attribute being checked (e.g., “levels of theory”).

  • option2 (list, optional) – Secondary attribute values aligned with the files list; when provided groups are keyed by (check_attribute[i], option2[i]).

Solvent database

Solvent database for media concentration corrections.

Solvent molecular weights [g/mol] and densities [g/mL] at 20 °C, loaded from solvents.json alongside this module. Each solvent entry defines a canonical name and one or more lookup aliases (abbreviations, full names).

Public API:

solvents – dict mapping alias (lowercase str) -> (mw, density) tuple canonical_solvent(name) – the canonical name of a solvent alias, or None

goodvibes.media.canonical_solvent(name)[source]

The canonical name of the solvent that name is an alias of (case-insensitive), or None when it is not a known alias.

goodvibes.media.compute_media_conc(media, file)[source]

Compute the neat-solvent molar concentration when the output file corresponds to the specified solvent.

The file’s name (without extension) and media match when they are aliases of the same solvent, so --media h2o applies to water.log as well as to H2O.log.

Parameters:
  • media (str) – Solvent name as provided (e.g., from –media).

  • file (str) – Path to the output file used to infer the solvent name.

Returns:

Neat-solvent concentration in mol/L if the file’s solvent matches media, None otherwise.

Return type:

float or None

goodvibes.media.lookup_solvent(name)[source]

Look up a solvent by alias and return its (mw, density).

Raises ValueError with a “did you mean …” hint and a list of common aliases if name is unknown. Use this to validate user input (e.g. –media / –freespace) up-front; compute_media_conc and other call sites can then assume the alias is valid.

Frequency scaling factors (Truhlar v5)

Vibrational frequency scaling factors from the Truhlar group database.

Frequency scaling factors taken from version 5 of the Truhlar group database (https://comp.chem.umn.edu/freqscale/).

Alecu, I. M.; Zheng, J.; Zhao, Y.; Truhlar, D. G. J. Chem. Theory Comput. 2010, 6, 2872-2887.

The data is stored in scaling_factors.json alongside this module. Each entry contains scaling factors for ZPEs, harmonic frequencies, and fundamentals.

goodvibes.vib_scale_factors.D()

Directly obtained from the ZPVE15/10 or F38/10 databases (Ref. 1).

goodvibes.vib_scale_factors.C()

Obtained by applying a systematic correction of -0.0025 to preexisting scale factor.

goodvibes.vib_scale_factors.R()

Obtained via the Reduced Scale Factor Optimization Model (Ref. 1).

Public API:

scaling_data_dict – dict mapping canonicalized level/basis -> ScalingData scaling_refs – list of reference citation strings ScalingData – namedtuple for scaling factor data canonicalize_level – normalize level/basis strings for lookup FUNCTIONAL_ALIASES – dict of known functional name aliases

class goodvibes.vib_scale_factors.ScalingData(level_basis, zpe_fac, zpe_ref, zpe_meth, harm_fac, harm_ref, harm_meth, fund_fac, fund_ref, fund_meth)

Bases: tuple

fund_fac

Alias for field number 7

fund_meth

Alias for field number 9

fund_ref

Alias for field number 8

harm_fac

Alias for field number 4

harm_meth

Alias for field number 6

harm_ref

Alias for field number 5

level_basis

Alias for field number 0

zpe_fac

Alias for field number 1

zpe_meth

Alias for field number 3

zpe_ref

Alias for field number 2

goodvibes.vib_scale_factors.canonicalize_level(level_basis)[source]

Normalize a level/basis string to a canonical form suitable for dictionary lookup.

The functional name has aliases resolved and case normalized. The basis set is normalized for the two spelling variations seen across programs: Pople star shorthand is expanded (“6-31+G**” == “6-31+G(d,p)”, “6-31G*” == “6-31G(d)”) and hyphens are dropped (“def2-TZVP” == “def2TZVP”, “ma-TZVP” == “maTZVP”). Database keys pass through the same normalization, so lookups are insensitive to these variations.

Parameters:

level_basis (str) – Level and optional basis separated by ‘/’, e.g. “b3lyp/6-31g*”.

Returns:

Canonicalized “FUNCTIONAL” or “FUNCTIONAL/BASIS” string.

Return type:

str

Constants

Constants and literature references for GoodVibes.

goodvibes.constants.canonical_units(units)[source]

Return the canonical spelling of an energy unit, or raise ValueError.

canonical_units('kJ/mol') == 'kJ/mol', canonical_units('ev') == 'eV'.

goodvibes.constants.hartree_factor(units)[source]

Multiplier that converts a value in Hartree to units.

Utilities

Utility classes and functions for GoodVibes.

goodvibes.utils.add_time(tm, cpu)[source]

Create a datetime representing tm’s day/time advanced by an elapsed CPU-style interval.

Parameters:
  • tm (datetime) – Source datetime whose day, hour, minute, second, and microsecond are used.

  • cpu (Sequence[int]) – Elapsed time as [days, hrs, mins, secs, msecs].

Returns:

A new datetime with year set to 100 and month set to 1, using tm’s day/time plus the interval from cpu (milliseconds interpreted as 1/1000 second).

Return type:

datetime

goodvibes.utils.all_same(items)[source]

Determine whether every element of items equals the first element.

Parameters:

items (Sequence) – A non-empty sequence of comparable elements.

Returns:

True if every element equals the first element, False otherwise.

Raises:

IndexError – If items is empty.

goodvibes.utils.display_name(file)[source]

Get the basename of a file path without its extension for display.

Parameters:

file (str) – Path or filename from which to extract the display name.

Returns:

The filename portion of file with the final extension removed.

Return type:

display_name (str)

goodvibes.utils.fatal(message)[source]

Log a critical error message and terminate the process.

Shuts down the logging subsystem and exits the process with status code 1.

Parameters:

message (str) – The message to emit at the critical level.

goodvibes.utils.get_console_dat() → Console[source]

Return the Rich Console for the .dat file (no color, box-drawing chars).

goodvibes.utils.get_console_stdout() → Console[source]

Get the Rich Console configured for colored stdout output.

Returns:

The Rich Console instance used for stdout.

Return type:

Console

Raises:

RuntimeError – If setup_logging() has not been called and the console is not initialized.

goodvibes.utils.natural_key(path)[source]

Sort key that orders conf_2 before conf_10 (and before conf_a).

Splits on digit runs and treats them as integers so ordinary string comparison won’t put conf_10 between conf_1 and conf_2. Comparison uses the basename so files from different directories with the same name don’t separate solely by directory path.

goodvibes.utils.parse_temperature_interval(spec)[source]

Turn a --ti specification into the list of temperatures to scan.

spec is "start,end" or "start,end,step" (kelvin). With two values the range is divided into ten steps. Values are floats: a step of 0.5 K or a start of 298.15 K is honoured rather than truncated to integers (which used to turn --ti 200,201,0.5 into a range() error and --ti 298.15,398.15,50 into 298, 348, 398). The end temperature is included when it lies on the grid (within 1e-9 K).

Returns:

temperatures in ascending order.

Return type:

list[float]

Raises:

ValueError – on a malformed spec, a non-positive step or end < start.

goodvibes.utils.setup_logging(filein, append)[source]

Configure the ‘goodvibes’ logger to write to both stdout and a .dat file and initialize module-level Rich consoles.

Initializes the logger named ‘goodvibes’ to emit messages to standard output and to a file at “{filein}_{append}.dat”. Opens the .dat file for writing and, if Rich is available, assigns module-level Console instances for stdout and the .dat file for later use.

Parameters:
  • filein (str) – Prefix for the output file (e.g., “GoodVibes”).

  • append (str) – Suffix for the output file (e.g., “output”).

Top-level package

GoodVibes: quasi-harmonic thermochemistry and reaction profiles from QC and MLIP outputs.

Programmatic API:

from goodvibes import compute_thermo, compute_batch, ThermoResult r = compute_thermo(“file.log”, QH=True, spc=”TZ”) print(r.qh_gibbs_free_energy)

# file-free (ASE / MLIP) from goodvibes import QCData qc = QCData.from_vibrations(atoms, vib.get_vibrations(), atoms.get_potential_energy()) r = compute_thermo(qcdata=qc)

# reaction profiles from goodvibes import load_pes, plot_profile pes = load_pes(“profile.yaml”, {r.file: r.bbe for r in results}) plot_profile(pes, temperatures=[298.15, 373.15]).save(“profile.svg”)

# reaction-profile documents (reaction-profile/1.x) from goodvibes import load_profile doc = load_profile(“profile.yaml”).evaluate(results, with_conformers=True) doc.dump(“profile.json”); doc.plot().save(“profile.svg”)

The calc_bbe class remains the canonical engine; compute_thermo is just a kwargs façade that returns a structured ThermoResult.

class goodvibes.ComputedEntry(qcdata: Any, options: Any, file: str = '', _cache: Any, ~typing.Any]=<factory>)[source]

Bases: object

One parsed structure plus the options it is evaluated with.

thermo(T) returns the ThermoVector at temperature T and is memoised on the (temperature, options) pair, so a multi-temperature profile never re-parses a file and never evaluates the same temperature twice. When the options carry no explicit concentration the gas-phase standard state follows the temperature (P/RT), exactly as the CLI’s --ti scan does.

property base_temperature: float
bbe(T: float | None = None, options: Any = None) → Any[source]

The calc_bbe at temperature T (default: the base T).

file: str = ''
classmethod from_bbe(bbe: Any, file: str | None = None) → ComputedEntry | None[source]

Wrap a calc_bbe built by calc_bbe.from_options (or compute_thermo). Returns None when the object does not carry the parsed input and its options (stubs, or the legacy 15-argument constructor), in which case it can only be used at the temperature it was computed at.

classmethod from_result(result: Any) → ComputedEntry[source]

Wrap a ThermoResult from compute_thermo.

options: Any
qcdata: Any
thermo(T: float | None = None, options: Any = None) → ThermoVector[source]

ThermoVector at temperature T (default: the base T).

class goodvibes.ConformerSet(name: str, files: List[str], bbes: List[Any], entries: List[ComputedEntry] | None = None, weight_by: str = 'qh_gibbs')[source]

Bases: object

A named species + ≥1 conformers.

bbes are the calc_bbe (or calc_bbe-shaped) objects at the base temperature, parallel to files. When every one of them carries its parsed input and options (anything built through calc_bbe.from_options / compute_thermo), entries is filled automatically and the set can be evaluated at any temperature; otherwise the rollups use the base-temperature values whatever T is passed (the pre-4.6 behaviour).

Encapsulates the rollup math: pure Boltzmann average, lowest-only, or gconf-corrected (lowest + Boltzmann adjustment + mixing entropy), weighting by weight_by (a registry quantity id; qh_gibbs by default).

property base_temperature: float | None
bbes: List[Any]
boltzmann_weighted(T: float) → ThermoVector[source]

Pure Boltzmann-weighted average over conformers, weights from weight_by (qh-G by default).

dedup(e_cutoff: float = 0.05, ro_cutoff: float = 0.01, rmsd_cutoff: float | None = None) → ConformerSet[source]

A new set without duplicate / enantiomeric conformers, using the same gates as goodvibes.sort.deduplicate (energy in kcal/mol, rotational constants as a fraction, optional Cartesian RMSD) and the same convention as the CLI’s --dedup: of each flagged pair the later file is the redundant copy and is dropped. A single-conformer set is returned unchanged.

ensemble_free_energy(T: float, quantity: str | None = None) → float[source]

−RT ln Σᵢ exp(−Gᵢ/RT) in Hartree over the conformers, i.e. the conformationally averaged value of quantity (default: weight_by) including the mixing entropy.

For quantity='qh_gibbs' this equals gconf_corrected(T).qh_gibbs when weight_by is qh_gibbs.

entries: List[ComputedEntry] | None = None
files: List[str]
classmethod from_results(name: str, results: Sequence[Any], weight_by: str = 'qh_gibbs') → ConformerSet[source]

Build a species from ThermoResult objects (compute_thermo).

gconf_corrected(T: float, QH: bool = True) → ThermoVector[source]

Lowest conformer + Boltzmann adjustment + mixing entropy −R Σ pᵢ ln pᵢ.

Mirrors the legacy pes.get_pes algorithm:

H_tot = H_min + (Σ pᵢ Hᵢ − H_min) S_tot = S_min + (Σ pᵢ Sᵢ + Σ −R pᵢ ln pᵢ − S_min) G(T) = H_tot − T·S_tot (or qh-H/qh-S if QH)

ZPE/SCF/SPC follow the plain Boltzmann sum (no mixing-entropy correction applies to those).

property is_single: bool
lowest_conformer(T: float | None = None) → ThermoVector[source]

Vector for the conformer with the lowest weight_by value (qh-G by default) at T.

lowest_index(T: float | None = None, quantity: str | None = None) → int[source]

Index of the conformer with the lowest quantity at T.

name: str
populations(T: float, quantity: str | None = None) → List[float][source]

Boltzmann populations of the conformers at T (sum to 1), weighted by quantity (default: weight_by).

property recomputable: bool

True when the set can be evaluated at temperatures other than the one its conformers were computed at.

rollup(T: float, gconf: bool = True, QH: bool = True, lowest_only: bool = False) → ThermoVector[source]

The species-level vector under the given rollup mode: lowest_only → lowest conformer; else gconf → gconf_corrected; else the pure Boltzmann average.

s_conf(T: float, quantity: str | None = None) → float[source]

Conformational (mixing) entropy −R Σ pᵢ ln pᵢ in Hartree/K.

vectors(T: float | None = None) → List[ThermoVector][source]

Per-conformer ThermoVectors at T (base temperature when None or when the set is not recomputable).

weight_by: str = 'qh_gibbs'
class goodvibes.Edge(src: str, dst: str, kind: str = 'step')[source]

Bases: object

A connection between two points of a pathway (by point label).

dst: str
kind: str = 'step'
src: str
class goodvibes.EnergySpan(tdts: str, tdi: str, span: float, reaction_energy: float, tof: float, tof_span: float, temperature: float, units: str, tdts_after_tdi: bool, control: Dict[str, float]=<factory>)[source]

Bases: object

The energy-span analysis of one catalytic cycle.

span (δE) and reaction_energy (ΔG_r) are in units; tof is the exact energy-span TOF and tof_span its approximation kB T / h exp(-δE / RT) (both s⁻¹; zero or negative when the cycle is not exergonic). control maps every TS and intermediate to its degree of TOF control (the TSs sum to 1, and so do the intermediates).

control: Dict[str, float]
reaction_energy: float
span: float
tdi: str
tdts: str
tdts_after_tdi: bool
temperature: float
tof: float
tof_span: float
units: str
exception goodvibes.MissingSinglePointError[source]

Bases: ValueError

Raised under strict_spc when a requested single-point energy is missing or unparseable instead of silently using the frequency-level energy.

class goodvibes.PESOptions(units: 'str' = 'kcal/mol', decimals: 'int' = 2, gconf: 'bool' = True, QH: 'bool' = True, spc_used: 'bool' = False, lowest_only: 'bool' = False)[source]

Bases: object

QH: bool = True
decimals: int = 2
gconf: bool = True
lowest_only: bool = False
property rollup_kw: Dict[str, bool]

Keyword arguments for Point.thermo / Pathway.relative.

spc_used: bool = False
to_user_units(hartree: float | None) → float | None[source]
units: str = 'kcal/mol'
class goodvibes.PESResult(pathways: ~typing.List[~goodvibes.pes_model.Pathway], options: ~goodvibes.pes_model.PESOptions, temperatures: ~typing.List[float] = <factory>, series: ~typing.List[~goodvibes.pes_model.Series] = <factory>, order: ~typing.List[str] | None = None, source: ~typing.Any = None)[source]

Bases: object

Top-level container: pathways + options + temperatures + series.

default_series(quantity: str = 'qh_gibbs', temperatures: Sequence[float] | None = None) → List[Series][source]

One computed series per temperature (the implicit series of a result that declares none).

levels(series: Sequence[Series] | None = None) → Dict[str, Dict[str, Dict[str, float | None]]][source]

{series id: {pathway name: {point label: value}}} in user units, for the given series (default: self.series or the implicit qh-G series per temperature).

merged_order() → List[str][source]

Point labels in x order: order when set, else the union of the pathways’ point sequences, each pathway’s order preserved.

options: PESOptions
order: List[str] | None = None
pathway(name) → Pathway[source]
pathways: List[Pathway]
property recomputable: bool

True when every species can be evaluated at other temperatures.

series: List[Series]
source: Any = None
property temperature: float

The first (base) temperature.

temperatures: List[float]
class goodvibes.Pathway(name: str, points: List[Point], zero: Point = None, edges: List[Edge] = None)[source]

Bases: object

Ordered points + edges + a designated zero (defaults to points[0]).

edges default to one step edge between each pair of consecutive points; give an explicit list to mark barrierless steps or to omit a connector. Edges name points by label.

property computable: bool

True when every point (and the zero) has species, so the full thermochemistry table can be formed for this pathway.

edge_kind(src: str, dst: str) → str | None[source]

Kind of the edge src -> dst, or None when there is none.

edges: List[Edge] = None
property labels: List[str]
levels(T: float, quantity: str = 'qh_gibbs', gconf: bool = True, QH: bool = True, lowest_only: bool = False) → Dict[str, float | None][source]

{point label: Δquantity relative to the zero, in Hartree} at T.

quantity is a registry id or alias (goodvibes.quantities); entropies are returned as T·ΔS. A value is None when the quantity is unavailable (single-point energy without –spc). A point without species (one that only carries declared values in a reaction-profile document) is absent from the result, and a pathway whose zero has no species gives an empty mapping.

name: str
point(label: str) → Point[source]
points: List[Point]
relative(T: float, gconf: bool = True, QH: bool = True, lowest_only: bool = False) → List[ThermoVector][source]

Per-point ΔThermo relative to self.zero, in Hartree.

This and levels() are the only places relative values are formed; tables, JSON and figures all read them.

with_edges(edges: Iterable) → Pathway[source]

Copy with explicit edges (tuples (src, dst[, kind]) or Edge).

zero: Point = None
class goodvibes.Point(label: str, species: List[Tuple[int, ConformerSet]], role: str = 'minimum', display: str | None = None)[source]

Bases: object

One node of a pathway: a stoichiometric sum of ConformerSets.

label is the point’s identity (the string written in the PES file, e.g. “2*A + B”); display is what a figure prints for it (defaults to the label); role is one of POINT_ROLES.

display: str | None = None
property display_label: str
classmethod from_label(label: str, species_map: dict, role: str = 'minimum', display: str | None = None) → Point[source]

Build a Point from a label string and {name: ConformerSet} map.

property id: str
property is_ts: bool
label: str
role: str = 'minimum'
species: List[Tuple[int, ConformerSet]]
thermo(T: float, gconf: bool = True, QH: bool = True, lowest_only: bool = False) → ThermoVector[source]

Total thermo at this point: Σ coeff_i × ConformerSet_i.thermo(T).

Per-species rollup precedence:

lowest_only=True → the species’ lowest qh-G conformer only else gconf=True → lowest + Boltzmann adjustment + mixing entropy else → pure Boltzmann-weighted average

class goodvibes.Profile(*, title=None, description=None, units='kcal/mol', ensemble='ideal-gas', default_temperature=298.15, species=None, points=None, pathways=None, order=None, methods=None, series=None, annotations=None, style=None, provenance=None, namespace=None, extensions=None, selectivity=None)[source]

Bases: object

A reaction-profile document (see the module docstring).

Build one with load_profile(), from_dict(), from_table() (CSV of relative energies) or from_pes_result(); evaluate its computed series with evaluate(); write it with dump(); draw it with plot(); tabulate it with to_rows() / to_dataframe().

copy() → Profile[source]
default_method() → str | None[source]
diff(other: Profile, *, tolerance: float = 0.01, units: str | None = None, series: Sequence[str] | None = None) → ProfileDiff[source]

How other differs from this document.

Compares the document fields (title, units, ensemble, default_temperature), points, pathways, series (matched by id: added, removed, changed metadata, and every level and uncertainty, which differ when they are more than tolerance apart, in units, default this document’s; the other document’s values are converted), selectivity blocks and annotations. Provenance, style and the GoodVibes namespace (sources, embedded conformers) are not compared. series restricts the series compared.

dump(path, *, include_conformers: bool = True, indent: int | None = 2) → None[source]

Write the explicit form to path (.json, .yaml or .yml). indent=None writes compact JSON (a document with embedded conformers is several times smaller).

energy_span(pathway: str | None = None, series: str | None = None, *, reaction_energy: float | None = None)[source]

goodvibes.kinetics.energy_span of a pathway read as one catalytic turnover: its points in order, the last being the regenerated catalyst with the products (unless reaction_energy is given), TSs by their role.

evaluate(thermo_data=None, *, temperatures: Sequence[float] | None = None, options: PESOptions | None = None, default_series: Sequence[Series] | None = None, with_conformers: bool = False, invocation: str | None = None, base_temperature: float | None = None) → Profile[source]

A new document with every computed series evaluated.

The data is thermo_data ({file: calc_bbe} or ThermoResult list) resolved through goodvibes.sources, or, when it is None, the embedded goodvibes.conformers. temperatures turns each computed series into one series per temperature (ids suffixed @<T>K). A document without computed series gets default_series (default: Δqh-G(T) at default_temperature). options overrides the rollup (the CLI passes its flags this way). base_temperature replaces default_temperature for computed series that give no temperature (the CLI passes its run temperature, so the document matches the tables and the pes block of the same run). with_conformers embeds every structure’s parsed data and thermochemistry options so the document can be re-evaluated, at any temperature, with no output files. Declared series are copied unchanged. Adds a provenance block.

evaluate_selectivity(block: str | None = None, *, series: str | None = None, warn: bool = True) → List[SelectivityResult][source]

The selectivity of each selectivity block, from series levels.

For every block (or only block, an id) and every series that has levels for its reference and branches (or only the block’s series, or series here), each branch’s barrier is level(branch) - level(reference) on a pathway holding both, and its population exp(-barrier / RT) normalised over the branches, at the series’ temperature (default_temperature when it has none). Declared series work as well as computed ones; computed ones need levels (see evaluate()).

The result follows the SelectivityResult conventions (labels are the branch point ids in the order listed, so ee_signed is positive when the first branch is the major one) and records barriers and ensemble_energies relative to the reference, in Hartree. curtin_hammett is ‘violated’ when a branch lies at or below the reference, when a point between the reference and a branch lies below the reference (a deeper resting state), or when the interconversion point’s barrier is not below the lowest branch barrier; ‘satisfied’ when an interconversion barrier is given and lower; ‘assumed’ otherwise. Each violation, and a branch that is not a transition state, is listed in warnings and raised as a SelectivityWarning when warn.

Raises ProfileError for an unknown block or series, or when no series has levels for a block.

classmethod from_dict(data: Mapping, *, strict: bool = False) → Profile[source]

Parse and validate a document mapping (explicit form or the GoodVibes v2 PES YAML shorthand). Raises ProfileError listing every problem; warnings are kept on .warnings and emitted as ProfileWarning.

classmethod from_figure(figure) → Profile[source]

The document of what a ProfileAxes drew: its points and pathways, and the drawn series with the drawn levels (and uncertainties) in the figure’s units. Embedded conformers are dropped; a computed series keeps its temperature and method.

classmethod from_pes_result(pes_result: PESResult, *, quantity: str = 'qh_gibbs', title: str | None = None, with_conformers: bool = False, invocation: str | None = None) → Profile[source]

An evaluated document for a PESResult built in Python (or loaded with load_pes): points, pathways, the files of every species (as goodvibes.sources) and one computed series of quantity per temperature of the result (or the result’s own series).

classmethod from_spec(spec, kind: str = 'legacy') → Profile[source]

Upgrade a PESSpec (kind ‘legacy’ for --- # PES text, ‘v2’ for the GoodVibes v2 PES YAML).

classmethod from_table(source, *, layout: str = 'auto', units: str = 'kcal/mol', quantity: str = 'gibbs', temperature: float | None = 298.15, method: str | None = None, series_id: str = 'table', label: str | None = None, title: str | None = None) → Profile[source]

A declared-only profile from a CSV of relative energies.

source is a path, a file object, CSV text or a list of row mappings. layout='wide': a point column, optional role and display columns, then one column per pathway (a column named pathway:series gives several series); a point belongs to a pathway when its cell is not empty, in row order, and the first such point is the pathway’s zero. layout='long': columns pathway, point, value and optionally series, label, quantity, temperature, units, method, role, display, uncertainty. 'auto' picks long when the header has pathway and value. Values are in units (or a long row’s own units) and are relative to the pathway zero; an empty cell means the point is not on that pathway, null / none / — an unknown value.

get_series(sid: str) → Series[source]
levels() → Dict[str, Dict[str, Dict[str, float | None]]][source]

{series id: {pathway: {point: value in the document units}}} for every series with levels (declared, or evaluated).

merged_order() → List[str][source]
method_thermo(method: str | None) → Dict[str, Any][source]

The thermochemistry options method sets for itself under goodvibes.thermo.by_method (empty when it sets none). They replace the options each of its structures was computed with, so a DFT and an MLIP method can use different scaling and qRRHO settings in one evaluation.

pes_options() → PESOptions[source]
plot(*, series=None, pathways=None, layout: str | None = None, ax=None, label_points: bool | None = None, connector: str | None = None, title: str | None = None, annotations: bool = True, preset: str | None = None, **kw)[source]

Draw the document with goodvibes.plot.plot_profile.

series (ids) defaults to every series; the document’s style gives the preset, layout, connector, decimals, figure size and whether to label points unless overridden here; annotations draws the document’s barrier / span annotations. A computed series needs levels (an evaluated document) or embedded conformers. Saving the result as SVG embeds the drawn document.

selectivity: List[dict]

reaction-profile 1.1 selectivity blocks (see evaluate_selectivity())

source_methods() → List[str][source]

Methods that GoodVibes can evaluate: those with goodvibes.sources or embedded goodvibes.conformers.

step_table(pathway: str | None = None, series: str | None = None) → List[dict][source]

goodvibes.kinetics.step_table for a pathway (default the first) and series (default the first with levels on it), at the series’ temperature, in the document’s units; each row also names the pathway and series.

to_dataframe(layout: str = 'long')[source]

to_rows as a pandas DataFrame (pandas is optional).

to_dict(*, include_conformers: bool = True) → dict[source]

The explicit form, ready for JSON or YAML (plain Python types only: NumPy scalars and arrays from parsed data are converted).

to_json(*, include_conformers: bool = True, indent: int | None = 2) → str[source]
to_pes_result(thermo_data=None, *, method: str | None = None, temperatures: Sequence[float] | None = None, options: PESOptions | None = None) → PESResult[source]

The PESResult model of this document for one method.

With thermo_data ({file: calc_bbe} or a list of ThermoResult) each species’ conformers are the files its goodvibes.sources entry matches; without it, the embedded goodvibes.conformers are used when present; otherwise points have no species and only stored levels can be drawn. Computed series keep their stored levels only when no data is used (the data would recompute them).

to_rows(layout: str = 'long') → List[dict][source]

The levels as table rows. long: one row per pathway × point × series (pathway, point, display, role, series, label, quantity, temperature, method, value, units, uncertainty). wide: one row per point (in the merged x order) with a column per pathway, or per pathway:series when there are several series; from_table reads either back.

to_yaml(*, include_conformers: bool = True) → str[source]
validate(strict: bool = False) → List[str][source]

Re-validate the explicit form; returns the warnings, raises ProfileError on an error.

write_mikimo(path, pathways: Sequence[str] | None = None, series: str | None = None) → Dict[str, str][source]

Write mikimo’s reaction_data.csv with one row per pathway (default all), levels in kcal/mol; returns the point-to-state-name map (INT0, TS1, ..., Prod). See goodvibes.kinetics.

write_table(path, layout: str = 'wide', decimals: int | None = None) → None[source]

Write to_rows to a .csv (full precision) or .md (rounded to decimals, default style.decimals or 1) file.

class goodvibes.ProfileAxes(figure: ~typing.Any, axes: ~typing.List[~typing.Any], levels: ~typing.Dict[str, ~typing.Dict[str, ~typing.Dict[str, float | None]]], units: str, order: ~typing.List[str], x: ~typing.Dict[str, float], series: ~typing.List[~typing.Any], pathways: ~typing.List[~typing.Any], colors: ~typing.Dict[str, ~typing.Any], linestyles: ~typing.Dict[str, str], layout: str = 'overlay', uncertainty: ~typing.Dict[str, ~typing.Dict[str, ~typing.Dict[str, float]]] = <factory>, preset: str | None = None, rc: ~typing.Dict[str, ~typing.Any] = <factory>, label_size: ~typing.Any = 'x-small', pes_result: ~typing.Any = None, _ids: ~typing.Any = <factory>)[source]

Bases: object

What plot_profile drew, with the numbers behind it.

levels is {series id: {pathway name: {point label: value}}} in units: the same evaluation the bars were drawn from, so a table written from it cannot disagree with the figure; uncertainty has the same shape for the series that carry one. order is the merged x order (point labels) and x maps a label to its position. element_ids maps each drawn element’s SVG id to what it shows.

annotate_barrier(pathway, src: str, dst: str, series=None, *, fmt: str = '{:+.1f}', offset: float = 0.3, color=None, ax=None)[source]

Mark the difference dst − src on one pathway with a vertical double-headed arrow beside dst and the value in units. Returns the matplotlib annotation.

property ax

The (first) matplotlib Axes.

axes: List[Any]
axes_for(pathway) → Any[source]

The Axes a pathway (name or object) was drawn on.

close() → None[source]
colors: Dict[str, Any]
property element_ids: Dict[str, Dict[str, str]]
figure: Any
label_size: Any = 'x-small'
layout: str = 'overlay'
level(pathway, point: str, series=None) → float | None[source]

A drawn level in units. series defaults to the first.

levels: Dict[str, Dict[str, Dict[str, float | None]]]
linestyles: Dict[str, str]
order: List[str]
pathways: List[Any]
pes_result: Any = None
preset: str | None = None
rc: Dict[str, Any]
save(*paths: str, dpi: int = 200, bbox_inches: str = 'tight', embed: bool = True, **kw) → None[source]

Write the figure to each path (format by extension).

An SVG keeps its text as text (svg.fonttype = 'none'), gives every bar, connector, label and error bar an id (see element_ids) and, unless embed=False, carries the reaction-profile document of what was drawn in a <metadata> element, so goodvibes.load_profile("figure.svg") reads the numbers back. Output is deterministic (no date, fixed id salt).

series: List[Any]
to_document() → dict[source]

The reaction-profile document of what was drawn: the drawn series with the drawn levels (and uncertainties), in units. Needs the PESResult the figure came from (plot_profile keeps it).

uncertainty: Dict[str, Dict[str, Dict[str, float]]]
units: str
x: Dict[str, float]
exception goodvibes.ProfileError(errors: Sequence[str], warnings_: Sequence[str] = ())[source]

Bases: ValueError

A reaction-profile document is invalid. errors lists every problem found (path: message); warnings the non-fatal ones.

exception goodvibes.ProfileWarning[source]

Bases: UserWarning

A reaction-profile document was accepted with a caveat (an unknown key, a quantity alias, a v2 shorthand, …).

class goodvibes.QCData(file: str = '', program: str = '', version_program: str = '', job_type: str = '', scf_energy: float | None = None, charge: int | None = None, multiplicity: int = 1, solvation_model: str = '', empirical_dispersion: str = '', level_of_theory: str = '', molecular_mass: float = 0.0, symmno: int = 1, linear_mol: bool = False, point_group: str = '', roconst: List[float] = <factory>, rotemp: List[float] = <factory>, linear_warning: bool = False, frequency_wn: List[float] = <factory>, im_frequency_wn: List[float] = <factory>, zero_point_corr: float | None = None, cpu: List[int] = <factory>, nprocs: int = 1, has_oniom: bool = False, atom_nums: List[int] = <factory>, atom_types: List[str] = <factory>, cartesians: List[List[float]] = <factory>, per_atom_masses: List[float] = <factory>, applied_freq_scale_factor: float = 1.0, sp_energy: float | None = None, sp_version_program: str = '', sp_solvation_model: str = '', sp_charge: int | None = None, sp_empirical_dispersion: str = '', sp_multiplicity: int | None = None, sp_suffix: str = '', sp_file: str = '', sp_level_of_theory: str = '')[source]

Bases: object

Program-agnostic container for parsed quantum chemistry data.

Populated by parse_qcdata() in io.py. Consumed by calc_bbe in thermo.py.

applied_freq_scale_factor: float = 1.0
atom_nums: List[int]
atom_types: List[str]
cartesians: List[List[float]]
charge: int | None = None
cpu: List[int]
empirical_dispersion: str = ''
file: str = ''
frequency_wn: List[float]
classmethod from_atoms(atoms, energy, *, frequencies=None, energy_units='eV', frequency_units='cm-1', name=None, method=None, charge=None, multiplicity=None, symm='auto', masses='isotopic', job_type=None, solvation_model='gas phase', empirical_dispersion='', linear_mol=None, zpe=None)[source]

Build a QCData from an ASE Atoms (or anything with get_chemical_symbols() and get_positions()), an electronic energy and, optionally, vibrational frequencies; no file involved.

Parameters:
  • atoms – geometry; atoms.info may supply charge, multiplicity, name and level_of_theory defaults.

  • energy – electronic energy in energy_units (‘eV’ default, also ‘hartree’, ‘kcal/mol’, ‘kJ/mol’).

  • frequencies – vibrational wavenumbers in frequency_units (‘cm-1’ default; ‘eV’ / ‘meV’ are converted). A negative or complex value is an imaginary mode. Translational and rotational modes must already be removed (see from_vibrations(), which does that for an ASE VibrationsData).

  • name – display name (ThermoResult.name); default atoms.info['name'] or ‘atoms’.

  • method – model chemistry label, e.g. ‘MACE-OFF23’ or ‘B3LYP/6-31G(d)’. Stored as level_of_theory; when it matches an entry of the Truhlar scaling database the same scale factors as for a file are applied, otherwise 1.0 (by design for an MLIP, a method naming no basis set; with a ScaleFactorWarning for a QM level).

  • charge – default atoms.info values, else 0 / 1.

  • multiplicity – default atoms.info values, else 0 / 1.

  • symm – ‘auto’ (default) detects the point group and symmetry number with pymsym when it is installed, an int sets the symmetry number directly, None leaves it at 1 (C1).

  • masses – ‘isotopic’ (most-abundant-isotope masses, the QC-program convention), ‘atoms’ (atoms.get_masses(), standard atomic weights) or a sequence of per-atom masses in amu.

  • job_type – ‘Freq’, ‘TSFreq’ (‘TS’), ‘SP’; inferred from the frequencies when None (an imaginary mode → ‘TSFreq’).

  • linear_mol – force linearity; detected from the geometry when None.

  • zpe – zero-point energy in energy_units to record as parsed metadata (GoodVibes recomputes the ZPE it reports from the frequencies, as for every program).

classmethod from_vibrations(atoms, vibdata, energy, *, energy_units='eV', drop_tr_modes=True, imag_threshold_cm1=-15.0, **kw)[source]

Build a QCData from an ASE vibrational analysis.

vibdata is an ase.vibrations.VibrationsData (or a Vibrations object, or any sequence of wavenumbers in cm⁻¹; complex or negative values are imaginary). The 3N modes of a finite-difference Hessian include the 6 (5 for a linear molecule) translations and rotations, which drop_tr_modes removes as the modes of smallest magnitude. An imaginary mode smaller in magnitude than |imag_threshold_cm1| is numerical noise on a slightly rough (MLIP) surface and is taken as real at |ν| with a RuntimeWarning; larger ones stay imaginary. A RuntimeWarning also reports a transition state (job_type='TS') without exactly one imaginary mode, a minimum with any, or more than one imaginary mode when no job_type was given. Other keywords go to from_atoms().

has_oniom: bool = False
im_frequency_wn: List[float]
job_type: str = ''
level_of_theory: str = ''
linear_mol: bool = False
linear_warning: bool = False
molecular_mass: float = 0.0
multiplicity: int = 1
nprocs: int = 1
per_atom_masses: List[float]
point_group: str = ''
program: str = ''
roconst: List[float]
rotemp: List[float]
scf_energy: float | None = None
solvation_model: str = ''
sp_charge: int | None = None
sp_empirical_dispersion: str = ''
sp_energy: float | None = None
sp_file: str = ''
sp_level_of_theory: str = ''
sp_multiplicity: int | None = None
sp_solvation_model: str = ''
sp_suffix: str = ''
sp_version_program: str = ''
symmno: int = 1
version_program: str = ''
with_single_point(energy, units, method=None, *, solvation_model=None, charge=None, multiplicity=None)[source]

A copy of this QCData whose enthalpies and free energies use energy, a single point at another level, in place of the electronic energy of the frequency calculation: a composite such as DFT//MLIP (DFT energy, MLIP geometry and frequencies) with no single-point output file.

The single point is applied the way spc applies one read from a file (ThermoResult.spc_applied is True and sp_energy is energy) without passing spc; an explicit spc still wins. The copy keeps the attached energy through compute_thermo, re-evaluation at other temperatures, --export caches and embedded conformers. The vibrational scale factor is still looked up for the level of the frequencies.

Parameters:
  • energy – the single-point electronic energy.

  • units – its units, ‘hartree’, ‘eV’, ‘kcal/mol’ or ‘kJ/mol’ (required, so an energy is never read in the wrong units).

  • method – level of theory of the single point, e.g. ‘DLPNO-CCSD(T)/def2-TZVP’; recorded as sp_level_of_theory.

  • solvation_model – of the single point; default those of this QCData.

  • charge – of the single point; default those of this QCData.

  • multiplicity – of the single point; default those of this QCData.

zero_point_corr: float | None = None
class goodvibes.SelectivityResult(temperature: float, key: str, labels: List[str], files_per_label: Dict[str, List[str]], populations: Dict[str, float], raw_boltzmann: Dict[str, float], preferred: str, ee: float | None = None, ddG: float | None = None, major: str | None = None, ee_signed: float | None = None, ratio: float | None = None, ensemble_energies: Dict[str, float] | None = None, quantity: str | None = None, name: str | None = None, series: str | None = None, reference: str | None = None, barriers: Dict[str, float] | None = None, curtin_hammett: str | None = None, warnings: Tuple[str, ...] = ())[source]

Bases: object

N-species selectivity outcome at one temperature.

Numeric data only — formatting strings (e.g. “60:40”, “1.5:1”) are derived in the print layer from populations.

Conventions (v2, GoodVibes 5.1):

  • major is the label with the largest population; on an exact tie, the first listed. preferred is the same label (kept from v1).

  • ee is the excess of the major over the minor for two labels, |p1 - p2| * 100, always ≥ 0; ee_signed is (p1 - p2) * 100 with the labels in the order given, so it is positive when the first label is the major one. Both are None for more than two labels.

  • ddG (two labels) and ratio (any number) compare the major with the runner-up: ddG = RT ln(p_major / p_runner_up) in Hartree, ≥ 0, and ratio = p_major / p_runner_up.

  • ensemble_energies is each label’s ensemble energy -RT ln Σ exp(-E_i / RT) over its conformers, in Hartree: absolute when computed from structures, relative to the reference point when computed from a reaction-profile document (then equal to barriers).

barriers: Dict[str, float] | None = None
curtin_hammett: str | None = None
ddG: float | None = None
ee: float | None = None
ee_signed: float | None = None
ensemble_energies: Dict[str, float] | None = None
files_per_label: Dict[str, List[str]]
key: str
labels: List[str]
major: str | None = None
name: str | None = None
populations: Dict[str, float]
preferred: str
quantity: str | None = None
ratio: float | None = None
raw_boltzmann: Dict[str, float]
reference: str | None = None
series: str | None = None
temperature: float
warnings: Tuple[str, ...] = ()
exception goodvibes.SelectivityWarning[source]

Bases: UserWarning

A selectivity rests on an assumption the data do not support (a Curtin–Hammett precondition that fails, a branch that is not a transition state).

class goodvibes.Series(id: str, label: str, quantity: str = 'qh_gibbs', temperature: float | None = None, method: str | None = None, style: Dict[str, ~typing.Any]=<factory>, levels: Dict[str, ~typing.Dict[str, float | None]] | None=None, declared: bool = False, units: str | None = None, uncertainty: Dict[str, ~typing.Dict[str, float]] | None=None, standard_state: Dict[str, ~typing.Any] | None=None, extensions: Dict[str, ~typing.Any]=<factory>)[source]

Bases: object

One quantity at one temperature: a line set on the axes, a column set in a table.

A computed series (declared=False) is evaluated from the model through Pathway.levels(temperature, quantity); temperature None means the result’s first temperature. Its levels, when set, are a stored evaluation (a reaction-profile document written by Profile.evaluate) and are used as they are. A declared series (declared=True) carries typed-in levels — literature values, a hand-entered table — as {pathway name: {point label: value}} in units (default: the result’s units) and is never re-evaluated; a point missing from its levels is simply absent from the figure. style holds matplotlib overrides (linestyle, color, …).

declared: bool = False
classmethod declared_from(id: str, label: str, levels: Mapping[str, Mapping[str, float | None]], *, quantity: str = 'gibbs', temperature: float | None = None, method: str | None = None, units: str | None = None, style: Dict[str, Any] | None = None) → Series[source]

A declared series from {pathway: {point label: value}}.

evaluate(result: PESResult, pathway: Pathway) → Dict[str, float | None][source]

{point label: value in the result’s units} for one pathway.

Stored levels (every declared series, and a computed series read from an evaluated document) are returned converted to the result’s units; otherwise a computed series goes through Pathway.levels.

evaluate_uncertainty(result: PESResult, pathway: Pathway) → Dict[str, float][source]

{point label: uncertainty in the result’s units} for one pathway (empty when the series carries none).

extensions: Dict[str, Any]
id: str
label: str
levels: Dict[str, Dict[str, float | None]] | None = None
method: str | None = None
quantity: str = 'qh_gibbs'
standard_state: Dict[str, Any] | None = None
style: Dict[str, Any]
temperature: float | None = None
uncertainty: Dict[str, Dict[str, float]] | None = None
units: str | None = None
class goodvibes.ThermoOptions(QS: str = 'grimme', QH: bool = False, s_freq_cutoff: float = 100.0, h_freq_cutoff: float = 100.0, temperature: float = 298.15, concentration: float | None = None, freq_scale_factor: float | None = None, zpe_scale_factor: float | None = None, solv: str | None = None, spc: str | None = None, invert: float | None = None, symm: bool = False, inertia: str = 'global', strict_spc: bool = False, scale_factor_source: str | None = None)[source]

Bases: object

Bundle of thermochemistry options for calc_bbe.from_options.

Frozen so it’s safe to share across worker processes (parallel parsing) and across multiple calc_bbe calls without accidental mutation. Field names match the v4.2 façade compute_thermo kwargs; the older calc_bbe.__init__ parameter names are mapped internally.

QH: bool = False
QS: str = 'grimme'
concentration: float | None = None
freq_scale_factor: float | None = None
h_freq_cutoff: float = 100.0
inertia: str = 'global'
invert: float | None = None
s_freq_cutoff: float = 100.0
scale_factor_source: str | None = None
solv: str | None = None
spc: str | None = None
strict_spc: bool = False
symm: bool = False
temperature: float = 298.15
zpe_scale_factor: float | None = None
class goodvibes.ThermoResult(file: str, name: str, scf_energy: float | None, sp_energy: float | None, zpe: float | None, enthalpy: float | None, qh_enthalpy: float | None, entropy: float | None, qh_entropy: float | None, gibbs_free_energy: float | None, qh_gibbs_free_energy: float | None, frequency_wn: List[float] | None, im_frequency_wn: List[float] | None, inverted_freqs: List[float] | None, point_group: str | None, symmno: int | None, linear_mol: bool, multiplicity: int | None, job_type: str | None, level_of_theory: str | None, program: str | None, bbe: Any, qcdata: Any, spc_applied: bool = False, temperature: float | None = None, options: Any = None, freq_scale_factor: float | None = None, zpe_scale_factor: float | None = None, scale_factor_source: str | None = None, symmetry_source: str | None = None, n_imag: int | None = None)[source]

Bases: object

Bundle of thermochemistry for one structure.

All numeric fields are in atomic units (Hartree for energies, Hartree/K for entropies); frequencies are cm⁻¹. Fields that aren’t available for the given file (e.g. qh_enthalpy when QH=False, sp_energy when spc=None) are reported as None rather than a sentinel.

bbe: Any
enthalpy: float | None
entropy: float | None
file: str
freq_scale_factor: float | None = None
frequency_wn: List[float] | None
gibbs_free_energy: float | None
property has_thermo: bool

True when calc_bbe found enough information to compute G(T). False for SP-only outputs (no frequency block).

im_frequency_wn: List[float] | None
inverted_freqs: List[float] | None
job_type: str | None
level_of_theory: str | None
linear_mol: bool
multiplicity: int | None
n_imag: int | None = None
name: str
options: Any = None
point_group: str | None
program: str | None
qcdata: Any
qh_enthalpy: float | None
qh_entropy: float | None
qh_gibbs_free_energy: float | None
scale_factor_source: str | None = None
scf_energy: float | None
sp_energy: float | None
spc_applied: bool = False
symmetry_source: str | None = None
symmno: int | None
temperature: float | None = None
zpe: float | None
zpe_scale_factor: float | None = None
class goodvibes.ThermoVector(scf_energy: float, zpe: float, enthalpy: float, qh_enthalpy: float, entropy: float, qh_entropy: float, gibbs: float, qh_gibbs: float, sp_energy: float | None = None)[source]

Bases: object

Bundle of thermo quantities for one species at one temperature.

Energy fields are in Hartree; entropy fields are in Hartree/K (matching calc_bbe). sp_energy is None when –spc was not used, and every field but scf_energy is None for an energy-only structure (no frequencies); arithmetic propagates None (if either operand has a None field, the result does).

enthalpy: float
entropy: float
get(quantity, T: float | None = None) → float | None[source]

Value of a registry quantity in Hartree (see goodvibes.quantities).

Entropy quantities are returned as T·S and therefore need T. e_zpe is (sp_energy if present else scf_energy) + zpe, matching the table convention that H and G are SPC-substituted when –spc is used. Returns None where the underlying value is None (no SPC).

gibbs: float
qh_enthalpy: float
qh_entropy: float
qh_gibbs: float
scf_energy: float
sp_energy: float | None = None
classmethod zero(with_sp: bool = False) → ThermoVector[source]

Identity element for addition.

zpe: float
goodvibes.bbe_to_result(bbe: Any, path: str | None = None, *, level_of_theory: str | None = None) → ThermoResult[source]

Project a calc_bbe instance into a ThermoResult.

Useful for adapting CLI internals (which keep thermo_data as a {path: calc_bbe} dict) to the structured API without re-parsing. level_of_theory is read from the file via read_initial() if not supplied; pass it explicitly to avoid the extra file scan.

goodvibes.build_pes_result(spec: PESSpec, thermo_data: dict, temperatures: List[float] | None = None) → PESResult[source]

Resolve patterns, build ConformerSets, parse point labels, return PESResult.

All species referenced (transitively) by the pathways’ points must resolve to at least one file in thermo_data; unresolved names raise KeyError.

class goodvibes.calc_bbe(file, QS='grimme', QH=False, cutoff=100.0, H_FREQ_CUTOFF=100.0, temp=298.15, conc=None, scale_fac=None, solv=None, spc=None, invert=None, symm=False, inertia='global', qcdata=None, zpe_scale_fac=None, strict_spc=False, _from_options=False)[source]

Bases: object

Compute “black box” entropy and enthalpy values along with all other thermochemical quantities.

Computes H, S from partition functions, applying quasi-harmonic corrections

xyz

contains Cartesian coordinates and atom data.

Type:

QCData

job_type

contains information on the type of Gaussian job such as ground or transition state optimization, frequency.

Type:

str

roconst

list of parsed rotational constants from Gaussian calculations.

Type:

list

program

program used in chemical computation.

Type:

str

version_program

program version used in chemical computation.

Type:

str

solvation_model

solvation model used in chemical computation.

Type:

str

file

input chemical computation output file.

Type:

str

charge

overall charge of molecule.

Type:

int

empirical_dispersion

empirical dispersion model used in computation.

Type:

str

multiplicity

multiplicity of molecule or chemical system.

Type:

int

mult

multiplicity of molecule or chemical system.

Type:

int

point_group

point group of molecule or chemical system used for symmetry corrections.

Type:

str

sp_energy

single-point energy parsed from output file.

Type:

float

sp_program

program used for single-point energy calculation.

Type:

str

sp_version_program

version of program used for single-point energy calculation.

Type:

str

sp_solvation_model

solvation model used for single-point energy calculation.

Type:

str

sp_file

single-point energy calculation output file.

Type:

str

sp_charge

overall charge of molecule in single-point energy calculation.

Type:

int

sp_empirical_dispersion

empirical dispersion model used in single-point energy computation.

Type:

str

sp_multiplicity

multiplicity of molecule or chemical system in single-point energy computation.

Type:

int

cpu

days, hours, mins, secs, msecs of computation time.

Type:

list

scf_energy

self-consistent field energy.

Type:

float

frequency_wn

frequencies parsed from chemical computation output file.

Type:

list

im_freq

imaginary frequencies parsed from chemical computation output file.

Type:

list

inverted_freqs

frequencies inverted from imaginary to real numbers.

Type:

list

zero_point_corr

thermal corrections for zero-point energy parsed from file.

Type:

float

zpe

vibrational zero point energy computed from frequencies.

Type:

float

enthalpy

enthalpy computed from partition functions.

Type:

float

qh_enthalpy

enthalpy computed from partition functions, quasi-harmonic corrections applied.

Type:

float

entropy

entropy of chemical system computed from partition functions.

Type:

float

qh_entropy

entropy of chemical system computed from partition functions, quasi-harmonic corrections applied.

Type:

float

gibbs_free_energy

Gibbs free energy of chemical system computed from enthalpy and entropy.

Type:

float

qh_gibbs_free_energy

Gibbs free energy of chemical system computed from quasi-harmonic enthalpy and/or entropy.

Type:

float

linear_warning

flag for linear molecules, may be missing a rotational constant.

Type:

bool

ex_sym(file)[source]

Determine the molecular point group and symmetry number using pymsym.

Parameters:

file (str) – Ignored by this method; present for API compatibility.

Returns:

(symmetry_number, point_group) where symmetry_number is an int and point_group is a string.

Return type:

tuple

Raises:

RuntimeError – If the pymsym package is not installed/importable.

classmethod from_options(qcdata_or_path, options, *, scale_factor_source=None)[source]

Construct a calc_bbe from a ThermoOptions bundle.

Recommended v5.0+ entry point — replaces the legacy 15-argument positional constructor (which still works but emits a DeprecationWarning). Accepts either a parsed QCData instance (skips re-parsing) or a filesystem path (parses internally, like the old constructor).

When options.freq_scale_factor and/or options.zpe_scale_factor are None, this method auto-looks them up from the file’s level of theory via the Truhlar database (mirroring the CLI’s behavior). Pass explicit floats to skip the lookup.

The result’s scale_factor_source records where the harmonic factor came from: ‘user’ (passed in), ‘truhlar’ (database lookup), ‘mlip-unscaled’ (an ASE input whose level names no basis set, i.e. an MLIP, with no database entry: 1.0 by design) or ‘none-found’ (1.0 because the level of theory is not in the database; a ScaleFactorWarning says so). A caller that resolved the factors itself (the CLI) passes scale_factor_source.

sym_correction(file)[source]

Compute and return the symmetry entropy correction and detected point group for the structure identified by file.

Parameters:

file (str) – Filename or identifier for the structure passed to ex_sym to determine symmetry number and point group.

Returns:

sym_correction (float): Symmetry entropy correction converted to internal energy units (J/mol divided by J_TO_AU, i.e., same units used elsewhere in the module). pgroup (str): Detected molecular point-group symbol.

Return type:

tuple

goodvibes.compute_batch(paths: Sequence[Any], *, jobs: int = 1, **kwargs: Any) → List[ThermoResult][source]

Compute thermochemistry for a list of files or QCData objects.

Parameters:
  • paths – QC output file paths and/or parsed QCData instances (e.g. from QCData.from_atoms in an MLIP workflow).

  • jobs – parallelism level. 1 (default) is sequential — no process-pool overhead. > 1 spawns that many worker processes via concurrent.futures.ProcessPoolExecutor. 0 or negative uses os.cpu_count() (or 1 if unknown).

  • **kwargs – forwarded unchanged to every compute_thermo call.

Returns:

Results in input order (matches executor.map’s contract). Workers can’t write to the orchestrator’s logger, so per-file log.info from inside calc_bbe is silenced under jobs > 1; warnings that surface through ThermoResult (e.g. missing-frequency files) still come through unchanged.

goodvibes.compute_selectivity(thermo_data, files_per_label, temperature, dup_list=None, key='gibbs')[source]

Compute populations and (for N=2) ee + ΔΔG‡ for a labeled species set.

Parameters:
  • thermo_data (dict) – file path -> calc_bbe.

  • files_per_label (dict) – ordered label -> list of file paths.

  • temperature (float) – Kelvin.

  • dup_list (list, optional) – pairs of duplicate files to exclude from sums.

  • key (str) – ‘gibbs’ (qh_gibbs_free_energy) or ‘energy’ (scf_energy).

Returns:

SelectivityResult.

Raises:

ValueError – if any label has no files, or if no files have a usable energy attribute.

goodvibes.compute_selectivity_batch(jobs: Mapping[str, Mapping[str, Any]], temperatures: Sequence[float] = (298.15,), *, quantity: str = 'qh_gibbs', s_freq_cutoffs: Sequence[float] | None = None, conformer_windows: Sequence[float] | None = None, records: bool = False, workers: int = 1, **thermo_options: Any)[source]

Selectivities of many jobs over temperatures, entropy cutoffs and conformer windows.

Parameters:
  • jobs – {job: {label: structures}}, the labels of each job in the order that sets the sign of ee (positive when the first label is major). structures is a glob pattern or path, a ConformerSet, or a list of paths, patterns, ThermoResult, QCData or ComputedEntry objects.

  • temperatures –

  • quantity – the registry quantity the conformers are weighted by ('qh_gibbs'; 'electronic' for energy-only ensembles such as read_xyz_frames frames).

  • s_freq_cutoffs – quasi-harmonic entropy cutoffs (cm⁻¹) to sweep; the nominal cutoff (s_freq_cutoff in thermo_options, 100 by default) is always included.

  • conformer_windows – energy windows (kcal/mol) to sweep: a label keeps the conformers within the window of its lowest one at that condition (0 keeps only the lowest). All conformers (window None, the nominal) are always included.

  • records – return a list of dicts instead of a pandas DataFrame.

  • workers – processes used to parse the files (compute_batch’s jobs).

  • thermo_options – compute_thermo keywords (QS, QH, spc, freq_scale_factor, …), applied to every file.

Returns:

job, temperature, s_freq_cutoff, conformer_window, nominal (the nominal cutoff and all conformers), quantity, labels (comma-joined), major, ee (signed, two labels), ddG (kcal/mol, major over runner-up), ratio, and per label population[<label>] (0–1) and n[<label>] (conformers kept). Rows follow the job order, then temperature, cutoff and window (None first).

Return type:

One row per job, temperature, cutoff and window

goodvibes.compute_thermo(path: str | None = None, *, qcdata: Any = None, QS: str = 'grimme', QH: bool = False, s_freq_cutoff: float = 100.0, h_freq_cutoff: float = 100.0, temperature: float = 298.15, concentration: float | None = None, freq_scale_factor: float | None = None, zpe_scale_factor: float | None = None, solv: str | None = None, spc: str | None = None, invert: float | None = None, symm: bool = False, inertia: str = 'global', strict_spc: bool = False) → ThermoResult[source]

Compute thermochemistry for one QC output file.

Pass either path (a filesystem location) or a pre-parsed qcdata (skips re-parsing). When concentration is None the gas-phase reference at 1 atm is used (P / RT).

Parameters mirror the CLI:

QS ‘grimme’ (default) or ‘truhlar’ quasi-harmonic entropy QH apply Head-Gordon quasi-harmonic enthalpy correction s_freq_cutoff entropy cutoff (cm⁻¹) h_freq_cutoff enthalpy cutoff (cm⁻¹) — only used when QH=True temperature K concentration mol/L. None → gas phase 1 atm. freq_scale_factor None → auto-lookup harm_fac from level of theory.

Applied to the partition-function frequencies (used in H_vib and S_vib).

zpe_scale_factor None → auto-lookup zpe_fac from level of theory.

Applied to ZPE only. If freq_scale_factor is explicitly set but zpe_scale_factor is None, ZPE inherits freq_scale_factor (back-compat).

solv ‘none’, or solvent name for free-space correction spc None, ‘link’, or filename suffix for SPC files invert None, or threshold for converting small imag → real symm apply pymsym symmetry-number correction strict_spc raise MissingSinglePointError when spc is set but

the single-point energy is missing or unparseable (default: RuntimeWarning and the frequency-level energy is used)

goodvibes.energy_span(levels: Mapping[str, float], transition_states: Iterable[str], *, reaction_energy: float | None = None, temperature: float = 298.15, units: str = 'kcal/mol') → EnergySpan[source]

The energy span of a catalytic cycle.

Parameters:
  • levels – the states of one turnover in order, {point: level}. Without reaction_energy, the last state is the first one regenerated after the turnover (catalyst plus products): it sets ΔG_r = level(last) - level(first) and is not a state of the cycle.

  • transition_states – the points of levels that are transition states; the others are intermediates.

  • reaction_energy – ΔG_r of one turnover, when every point of levels is a state of the cycle.

  • temperature –

  • units – of the levels and of the results.

goodvibes.eyring_rate(dG: float, temperature: float = 298.15, *, units: str = 'kcal/mol', kappa: float = 1.0) → float[source]

The Eyring rate constant κ kB T / h exp(-ΔG‡ / RT) for a free energy of activation dG in units (s⁻¹ for a unimolecular step).

goodvibes.load_pes(path: str, thermo_data: dict, temperatures: List[float] | None = None) → PESResult[source]

Read a PES definition file, parse it, and return a PESResult.

Three formats are accepted: a reaction-profile document (schema: reaction-profile/1.x, YAML or JSON; see goodvibes.profile), the v2 PES YAML, and the legacy --- # PES text (deprecated; emits a DeprecationWarning). The returned result carries the document it was read from as result.source (a goodvibes.profile.Profile; the two older formats are upgraded).

goodvibes.load_profile(source, *, strict: bool = False, **table_options) → Profile[source]

Read a reaction-profile document.

source is a Profile (returned as is), a mapping, or a path to: a reaction-profile document (.yaml / .yml / .json), an SVG figure saved by GoodVibes (it embeds the drawn document), a GoodVibes --json / --export payload with a profile block, a CSV/TSV table of relative energies (table_options go to Profile.from_table()), a GoodVibes v2 PES YAML or a legacy --- # PES text file (both upgraded to the explicit form).

goodvibes.merge_point_order(sequences: Sequence[Sequence[str]]) → List[str][source]

Merge several ordered label sequences into one order that keeps every sequence’s own order. A label new to the merged list is inserted just before the earliest already-placed label that follows it in its own sequence, or appended when none does; so [R, Int1, TS1, P] and [R, TS1, P] merge to the first, and two branches [R, TS_R, P_R] / [R, TS_S, P_S] keep each branch contiguous.

goodvibes.plot_pes(pes_result: Any, *, ax=None, pathway_index: int | Sequence[int] | None = None, connector_style: str = 'bezier', colors: Sequence[str] | None = None, show_conformers: bool = False, thermo_lookup: Mapping[str, float] | Callable[[str], float] | None = None, title: str | None = None, label_points: bool = False, quantity: str = 'qh_gibbs')[source]

Plot one or more pathways from a PESResult as a reaction profile.

A thin wrapper over plot_profile() kept for the 4.2–4.5 API; it returns the matplotlib Axes. New code should call plot_profile, which also returns the drawn levels and supports several series (temperatures, quantities, declared values) on one axes.

Parameters:
  • pes_result – a PESResult from goodvibes.pes_loader.load_pes.

  • ax – optional matplotlib Axes; new figure if None.

  • pathway_index – which pathway(s) to draw (None → all, an int, or a list of ints).

  • connector_style – ‘bezier’ (default) or ‘linear’.

  • colors – per-pathway colors in pathway order.

  • show_conformers – scatter individual conformer values around each level.

  • thermo_lookup – deprecated and ignored (conformer values are read from the PESResult); accepted for backwards compatibility.

  • title – figure title; defaults to the pathway names + temperature.

  • label_points – annotate each point’s value next to its bar.

  • quantity – which relative quantity to draw, by registry id or alias (see goodvibes.quantities); the y-label follows the choice.

Returns:

The matplotlib Axes the profile(s) were drawn on.

goodvibes.plot_profile(pes_result: Any, *, series=None, quantity: str | None = None, temperatures: Sequence[float] | None = None, pathways=None, layout: str = 'overlay', ax=None, style: Mapping[str, Any] | None = None, colors=None, show_conformers: bool = False, label_points: bool = False, title: str | None = None, order: Sequence[str] | None = None, preset: str | None = None, uncertainty: bool = True) → ProfileAxes[source]

Draw a reaction profile from a PESResult.

Every pathway is drawn on a shared x axis whose order is the merge of the pathways’ point sequences (PESResult.merged_order), so pathways of different lengths, or branches sharing a reactant, line up by point label. Colour encodes the pathway; linestyle encodes the series (a quantity at a temperature, computed from the model or declared). Transition-state points (role='ts') carry their value label above the bar, other points below; a barrierless edge is a dotted connector; an edge of kind none draws no connector.

Parameters:
  • pes_result – a PESResult (goodvibes.load_pes or built by hand).

  • series – what to draw. A Series / list of Series (computed or declared), or series ids from pes_result.series. Default: pes_result.series when it declares any, else one computed series of quantity per temperature.

  • quantity – registry id or alias for the implicit series (default ‘qh_gibbs’); the y-label follows it.

  • temperatures – temperatures of the implicit series (default: the result’s); several give a temperature overlay.

  • pathways – which pathways to draw (names, indices or objects); default all.

  • layout – ‘overlay’ (all pathways on one axes) or ‘panels’ (one axes per pathway, shared y).

  • ax – an Axes to draw on (overlay only); a new figure otherwise.

  • style – {‘connector’: ‘bezier’ | ‘linear’ | ‘step’, ‘bar_half’: float, ‘decimals’: int, ‘figsize’: (w, h), ‘linestyles’: […], ‘preset’: name}.

  • colors – per-pathway colours, a sequence in pathway order or a {name: colour} mapping. Default: black for one pathway, the matplotlib cycle otherwise.

  • show_conformers – scatter each conformer at level + (conformer − species rollup) in the plotted quantity (computed series only).

  • label_points – print each level’s value next to its bar.

  • title – figure title (default: pathway names, and the temperature when there is one).

  • order – explicit x order of point labels (overrides the result’s).

  • preset – a STYLE_PRESETS name (‘single-column’, ‘double-column’, ‘slide’; default style['preset'] or ‘none’): figure size, fonts and line widths for that target, applied to this figure only. An explicit style['figsize'] still wins.

  • uncertainty – draw each series’ uncertainty as an error bar (± the value) on its levels.

Returns:

A ProfileAxes with the figure, axes and the drawn levels.

goodvibes.rate_ratio(dG_a: float, dG_b: float, temperature: float = 298.15, *, units: str = 'kcal/mol') → float[source]

k_a / k_b = exp(-(ΔG‡_a - ΔG‡_b) / RT).

goodvibes.read_xyz_frames(path, *, energy_units=None, energy_key=None, method=None, charge=None, multiplicity=None)[source]

Read every frame of a multi-frame .xyz or .extxyz file as an energy-only QCData: a geometry and an electronic energy, no frequencies.

For conformer ensembles (CREST crest_conformers.xyz, xtb trajectories) and MLIP sweeps (ase.io.write of many frames). compute_batch evaluates the list, and a ConformerSet of the results weighted by 'electronic' gives Boltzmann populations and the ensemble energy; free energies need frequencies, so the thermochemical quantities are None.

The energy of a frame comes from its comment line: in an extxyz one (key=value pairs) from energy_key or, by default, the first of energy, free_energy, total_energy (eV, the ASE convention), scf_energy (hartree) and E (eV), a named key other than these being in eV; in a plain one from energy: <value> (xtb) or a bare number (CREST), in hartree. An energy_units (or scf_energy_units) key, or the energy_units argument, overrides the units. A frame without an energy is an error.

Parameters:
  • path – the .xyz / .extxyz file.

  • energy_units – units of every frame’s energy (‘hartree’, ‘eV’, ‘kcal/mol’, ‘kJ/mol’); default as above.

  • energy_key – the extxyz key holding the energy.

  • method – level of theory of the energies (level_of_theory), e.g. ‘GFN2-xTB’ or ‘MACE-OFF23’; default the frame’s level_of_theory key.

  • charge – default the frame’s keys, else 0 / 1.

  • multiplicity – default the frame’s keys, else 0 / 1.

Returns:

A list of QCData, one per frame, named <stem>_<n> (n from 1, zero-padded) unless a frame has a name key; job_type ‘SP’.

goodvibes.resolve_quantity(name) → Quantity[source]

Return the Quantity for an id or alias (case-insensitive), or raise ValueError.

goodvibes.selectivity_from_energies(energies, temperature, *, key=None, quantity=None, files_per_label=None, **extra)[source]

A SelectivityResult from each label’s conformer energies.

Parameters:
  • energies (Mapping[str, Sequence[float]]) – ordered label -> the energies (Hartree) of its conformers; one value per label for a single structure or an already rolled-up level.

  • temperature (float) – Kelvin.

  • key (str) – recorded on the result (quantity is the registry id; key defaults to it).

  • quantity (str) – recorded on the result (quantity is the registry id; key defaults to it).

  • files_per_label (dict, optional) – label -> file paths, recorded.

  • extra – further SelectivityResult fields (name, series, reference, barriers, curtin_hammett, warnings).

A label with no energy gets population 0.

Raises:

ValueError – fewer than two labels, or no energy at all.

goodvibes.si_rows(results: Sequence[Any], *, units: str = 'hartree', n_lowest: int = 3) → List[Dict[str, Any]][source]

One row per result (ThermoResult from compute_thermo / compute_batch); columns SI_COLUMNS, energies in units (absolute, Hartree by default), T·S and T·qh-S at the result’s temperature, imaginary and lowest (the n_lowest lowest real modes) as comma-separated cm⁻¹. A structure without frequencies has None for the thermal quantities.

goodvibes.step_table(levels: Mapping[str, float], transition_states: Iterable[str], *, temperature: float = 298.15, units: str = 'kcal/mol') → List[dict][source]

One row per transition state along a pathway (levels in order):

  • ts, from (the last intermediate before it), to (the first intermediate after it, or None);

  • barrier: level(ts) - level(from), in units;

  • barrier_from_lowest: from the lowest point before the TS (the effective barrier when an earlier state is deeper);

  • step_energy: level(to) - level(from);

  • k (s⁻¹) and half_life (s) of the Eyring rate over barrier.

goodvibes.summarize_selectivity(table, value: str = 'ee', *, decimals: int = 0) → List[Dict[str, Any]][source]

The nominal value of each job at each temperature and its range over the sweep: {job, temperature, value, nominal, low, high, text}, with text such as "ee +92 % (88 to 94 % over s_freq_cutoff 50–150 cm⁻¹, conformer window 0–2 kcal/mol)".

table is what compute_selectivity_batch returned (DataFrame or records); value is a numeric column (ee, ddG, ratio or a population[<label>]).

goodvibes.to_dataframe(results: Sequence[ThermoResult])[source]

Convert a list of ThermoResults into a pandas DataFrame.

Pandas is an optional dependency; raises ImportError with an install hint if it isn’t available. The DataFrame has one row per result and columns for every public scalar field on ThermoResult (omits the bbe and qcdata references and the frequency lists). The imaginary frequencies are kept as im_frequency_wn: a space-separated string in cm⁻¹, as --imag prints them, or None when the structure has none.

goodvibes.to_parquet(results: Sequence[ThermoResult], path: str) → None[source]

Write a list of ThermoResults to a Parquet file at path.

Same column set as to_dataframe. Requires pandas + a Parquet engine (pyarrow or fastparquet); install with pip install goodvibes[full] or pip install pyarrow.

goodvibes.validate_document(data, *, strict: bool = False, use_jsonschema: bool = True) → Tuple[List[str], List[str]][source]

(errors, warnings) for a document mapping.

Runs the reference Python validator and, for a document in the explicit form (one with a schema key) when jsonschema is installed, the JSON Schema too; errors from the latter are prefixed schema:. Never raises for an invalid document.

goodvibes.write_si(results: Sequence[Any], path, *, units: str = 'hartree', decimals: int = 6, n_lowest: int = 3, coordinates: bool = True) → List[str][source]

Write the SI table for results and return the paths written.

The format follows the extension: .md (a Markdown table, then a “Cartesian coordinates” section with one block per structure), .tex (a booktabs tabular, then the coordinates in a verbatim block), .csv (the table, all columns; the coordinates go to <stem>_coordinates.xyz) or .xyz (the coordinates only). coordinates=False leaves them out.