Migrating from v4.x to v5.0

GoodVibes v5.0 cleans up the programmatic surface that started landing in v4.2. The CLI and .dat output are unchanged — if you only use goodvibes from the shell, no migration is needed. This page is for users embedding GoodVibes in scripts, notebooks, or libraries.

TL;DR

Old

New

calc_bbe(file, QS, QH, cutoff, H_FREQ_CUTOFF, temp, conc, scale_fac, ...)

compute_thermo(file, **kwargs) (returns ThermoResult)

Same, but you need the underlying calc_bbe instance

calc_bbe.from_options(file_or_qcdata, ThermoOptions(...))

15 positional args

One ThermoOptions dataclass

The legacy calc_bbe(file, QS, QH, ...) constructor still works in v5.0 but emits a DeprecationWarning. It will be removed in v6.0.

The high-level path: compute_thermo

For most uses, replace direct calc_bbe calls with compute_thermo:

# Before (v4.x)
from goodvibes.thermo import calc_bbe

bbe = calc_bbe(
    "file.log", "grimme", True, 100.0, 100.0, 298.15, None, 0.99,
    None, "TZ", None, False, None, "global"
)
print(bbe.qh_gibbs_free_energy)

# After (v5.0)
from goodvibes import compute_thermo

r = compute_thermo("file.log", QH=True, freq_scale_factor=0.99, spc="TZ")
print(r.qh_gibbs_free_energy)

compute_thermo returns a frozen ThermoResult dataclass. Every attribute on the old calc_bbe instance is still accessible via r.bbe.<attr> if you need it.

For batches:

from goodvibes import compute_batch
results = compute_batch(["a.log", "b.log", "c.log"], jobs=8)

The medium path: from_options

If you specifically need a calc_bbe instance (e.g. you’re embedding the legacy pes.get_pes workflow), use the new classmethod:

# Before
from goodvibes.thermo import calc_bbe
bbe = calc_bbe(
    "file.log", "grimme", True, 100.0, 100.0, 298.15, None, 0.99,
    None, "TZ", None, False, None, "global"
)

# After
from goodvibes.thermo import calc_bbe, ThermoOptions

opts = ThermoOptions(QH=True, freq_scale_factor=0.99, spc="TZ")
bbe = calc_bbe.from_options("file.log", opts)

from_options accepts either a path (parses internally) or a pre-parsed QCData (skips re-parsing):

from goodvibes.io import parse_qcdata
qc = parse_qcdata("file.log")
bbe = calc_bbe.from_options(qc, opts)

Both forms emit no DeprecationWarning.

ThermoOptions field reference

@dataclass(frozen=True)
class ThermoOptions:
    QS: str = "grimme"               # 'grimme' or 'truhlar' entropy scheme
    QH: bool = False                 # Head-Gordon enthalpy correction
    s_freq_cutoff: float = 100.0     # entropy cutoff (cm⁻¹)
    h_freq_cutoff: float = 100.0     # enthalpy cutoff (cm⁻¹)
    temperature: float = 298.15      # K
    concentration: float | None = None        # mol/L; None = gas-phase 1 atm
    freq_scale_factor: float | None = None    # None = auto-lookup harm_fac
    zpe_scale_factor: float | None = None     # None = auto-lookup zpe_fac
    solv: str | None = None
    spc: str | None = None
    invert: float | None = None
    symm: bool = False
    mm_freq_scale_factor: float | None = None
    inertia: str = "global"

The frozen dataclass means it’s safe to share across worker processes (parallel parsing) and across multiple from_options calls without accidental mutation.

Frequency-scaling defaults

freq_scale_factor and zpe_scale_factor are auto-resolved from the file’s level of theory via the Truhlar database when None. Three precedence cases (matches the CLI --vscal / --zpe-vscal flags):

freq_scale_factor

zpe_scale_factor

Result

None

None

both auto-looked-up (harm_fac / zpe_fac)

X

None

both = X (back-compat: --vscal alone scales everything)

None

Y

freq auto-looked-up; ZPE uses Y

X

Y

both explicit

Level-of-theory metadata

ThermoResult.level_of_theory is now populated automatically (in v4.x it was on a separate read_initial() scan). For advanced users:

r = compute_thermo("file.log")
print(r.level_of_theory)   # 'B3LYP/6-31G(d)' or None if unrecognised
print(r.program)           # 'Gaussian', 'Orca', ...

CLI changes in v5.0

Most flags are unchanged. Two cache flags are renamed:

v4.x

v5.0+

Notes

--cache-save FILE

--export FILE

Now writes the v1.0 unified JSON (richer than the legacy cache envelope; same file works as a re-import source). Old flag still works with a DeprecationWarning.

--cache-read FILE

--import FILE

Auto-detects v1.0 unified JSON OR legacy cache envelope, so existing on-disk caches keep working. Old flag deprecated.

--export PATH is a synonym for --json PATH — both write the same v1.0 schema. Use whichever name reads better in your script.

Caveat: in cache-only mode (--import FILE with no positional .log files), the level-of-theory info is unavailable, so the frequency scale factor falls back to 1.0 unless you pass --vscal / --zpe-vscal explicitly. Auto-restoring scale factors from the cached options block is a planned follow-up.

SPC results are cached too

When you --export a run that used --spc SUFFIX, the parsed single-point energy (and the SPC’s solvation, charge, dispersion, multiplicity, version-program metadata) is now written into the qcdata block alongside the parent’s parse output. On a subsequent --import FILE --spc SUFFIX run with the same suffix, the SPC file isn’t touched — the cached numbers drive the thermo. This means you can move or delete the SPC outputs after exporting, and re-runs still work:

# First pass: parse + persist.
goodvibes job.log --spc TZ --export job.json

# Later: SPC files can be archived; --import is enough.
goodvibes --import job.json --spc TZ

The cache is suffix-aware: if you re-run with --spc QZ (a different suffix), GoodVibes re-resolves the SPC file from disk and refreshes the cached numbers. Cache entries that don’t match the current --spc value are bypassed transparently.

JSON output schema is now stable (v1.0)

The JSON written by --json / --export was a preview through v4.x (schema_version: "0.4") and is now stable from v1.0. Use goodvibes.schema for runtime validation:

import json
from goodvibes import schema

payload = json.load(open("results.json"))
schema.validate(payload)             # raises ValueError on shape/version mismatch
print(schema.SCHEMA_VERSION)         # '1.0'

Pre-v1.0 payloads are rejected by validate() — regenerate with a v5.0+ build. The v1.0 contract is additive: v1.x minor versions introduce new fields without breaking old readers, and v2.0 is the next allowed-to-break point.

Parquet export

goodvibes *.log --parquet thermo.parquet

Same column set as --csv; same goodvibes[full] extras (adds pandas + pyarrow).

from goodvibes import compute_batch, to_parquet
results = compute_batch(glob.glob("*.log"))
to_parquet(results, "thermo.parquet")

What’s NOT changing in v5.0

  • Most CLI flags (--vscal, --zpe-vscal, --csv, --jobs, --label, --selectivity, etc.).

  • The .dat output format — back-compat goldens in tests/compatibility/ will guard the existing format.

  • pes.get_pes and its parallel-list attributes (still works as a back-compat shim around the v4.2 PES model; will be removed in v5.1 with a one-cycle deprecation window).

When to actually migrate

Situation

What to do

You only use goodvibes from the shell

Nothing — CLI is unchanged.

You import compute_thermo already

Nothing — that’s the v4.2 façade and is the v5.0 path too.

You import calc_bbe and call it directly

Switch to compute_thermo for ergonomics, or calc_bbe.from_options if you specifically want the calc_bbe instance.

You depend on cited paper values produced with v4.x.0 (uniform harm_fac)

Pin to goodvibes==4.2.0 until you’re ready to update. v5.0 uses Truhlar’s separate zpe_fac / harm_fac (the correct split per Alecu et al., JCTC 2010).

See the v4.x → v5.0 ROADMAP entry on GitHub for the full list of v5.0 changes.