Tutorial: from a first run to your own chemistry¶
Q2MM fits an inexpensive molecular mechanics (MM) energy model against quantum-mechanical (QM) reference observations. Start here after the CPU first-run installation: first use the packaged CH3F inputs, then replace them with your own molecule or a multi-structure fitting set. A completed run is not evidence of force-field accuracy; the observations and independent checks define what you can claim.
The same example from Python¶
The source-only runner is the canonical small example implementation, not a
second tutorial algorithm. Set JAX_PLATFORMS=cpu before launching Python.
From the checkout, the following executes it with native Pydantic settings:
from pathlib import Path
import runpy
from q2mm.settings import GroundStateOptimizationSettings
example = runpy.run_path(str(Path("examples") / "ch3f" / "run.py"))
settings = GroundStateOptimizationSettings(
optimizer={"maxiter": 2, "maxls": 5},
n_evals=0,
)
restored = GroundStateOptimizationSettings.model_validate_json(settings.model_dump_json())
assert restored.model_dump() == settings.model_dump()
result = example["run"](settings=restored, output_root=Path("results") / "ch3f")
assert result["execution_mode"] == "budget_limited_native"
assert result["optimization"]["convergence_claim"] is False
print(result["saved"])
For an arbitrary working directory or installed wheel, replace the relative
Path("examples") / "ch3f" / "run.py" with the absolute source-script path.
The local-wheel instructions keep the
source script separate from the installed package.
GroundStateOptimizationSettings defaults to a JAX analytical objective and
SciPy L-BFGS-B. The partial dictionary updates the existing typed optimizer
default; it does not discard its other controls. JSON reconstruction validates
the same values. n_evals=0 disables optional endpoint sampling, not the
optimizer's evaluations. The source command exposes the same controls as
--optimizer.maxiter 2 --optimizer.maxls 5 --n-evals 0.
The example defaults to a two-iteration, five-line-search-step, single-stage budget even when no settings are supplied. An iteration cap is not an energy evaluation count or wall-time guarantee. All these onboarding runs report no convergence claim, regardless of the solver flag.
What happens inside?¶
Read examples/_small_runner.py
alongside the canonical lifecycle:
- Load the packaged geometry and explicitly attach the packaged Hessian with units and source provenance.
q2mm.preparecreates an immutable problem: declared ground-state/TS cases, starting force field, parameter layout, active/frozen partition and reference observations. It launches no QM calculation.- Build the backend, optimizer and workflow from actual settings; capture their configuration before execution.
- Create an objective evaluator and evaluate the baseline.
q2mm.optimizeuses the supplied optimizer, workflow and evaluator factory, returning anOptimizationResult, not a force field. OptimizationRun.from_resultassembles the final field and provenance; explicitq2mm.savewrites the field and manifest.
The default observations compare geometry and the Hessian in a reference
eigenvector basis ("eigenmatrix"). This is not frequency matching and not the
same as every publication objective. See
fitting objectives.
When deliberately fitting matched frequencies, prepare with an empty
ObservationSet(), then call the explicit runtime q2mm.match_frequencies
step with a loaded MM backend and FrequencyMatchingSettings.
Bring your own FCHK¶
A Gaussian formatted checkpoint (.fchk) must contain element identities,
coordinates, and Cartesian force constants (the Hessian). An XYZ file is
not a substitute. Decide whether the calculation describes a minimum
(ground_state) or a transition state (transition_state); Q2MM does not
infer this from the filename or Hessian eigenvalues.
For a declared minimum, use the existing source runner, still with CPU selected:
python examples\ch3f\run.py --fchk inputs\minimum.fchk --stationary-point ground_state --optimizer.maxiter 2 --output-root results\my-molecule
The example's filenames remain ch3f.frcmod and
ch3f.frcmod.manifest.json in your chosen directory even when the molecule is
caller-supplied. The JSON identifies input.kind="caller_fchk" and the input
name; the manifest fingerprints the actual input. Do not interpret these
filenames as a scientific identity.
For a declared TS, use examples\ch3f-sn2\run.py and
--stationary-point transition_state; its filenames are ch3f-sn2.frcmod
and ch3f-sn2.frcmod.manifest.json. This selects the TS settings and
curvature inversion, not the GS settings. The approved packaged SN2 Hessian
can emit a multiple-negative-mode diagnostic; that warning is not suppressed
or repaired for a demo, and this is not a TS scientific validation.
Missing files, missing force constants, or conflicting stationary-point
options fail explicitly. Do not supply both --fchk and --input-root.
The small scripts intentionally support only a fresh harmonic field.
For direct preparation only (no backend, optimizer or output):
import q2mm
from q2mm.io import load_fchk_molecule
from q2mm.settings import PreparationSettings
molecule = load_fchk_molecule("inputs/minimum.fchk", bond_tolerance=1.5)
problem = q2mm.prepare(
molecule,
settings=PreparationSettings(stationary_point="ground_state", functional_form="harmonic"),
)
The prepare API describes input
validation. To execute your own prepared problem without the source runner,
use the canonical lifecycle
with an explicitly budgeted GS or TS settings model and a chosen output path.
Do not reintroduce an execution wrapper or settings resolver.
XYZ plus an explicit Hessian¶
XYZ provides element labels and coordinates, not charge, stationary-point kind or a Hessian. The following preparation-only snippet expects your optimized geometry and a separately obtained Hessian in canonical Hartree/Bohr² units:
import numpy as np
import q2mm
from q2mm.io.xyz import load_xyz
from q2mm.models.hessian import HessianProvenance, HessianUnits
from q2mm.settings import PreparationSettings
molecule = load_xyz("inputs/minimum.xyz", charge=0, bond_tolerance=1.5)
molecule = molecule.with_hessian(
np.load("inputs/minimum-hessian.npy", allow_pickle=False),
HessianProvenance(
units=HessianUnits.ATOMIC,
source="caller-quantum-calculation",
path="inputs/minimum-hessian.npy",
),
)
problem = q2mm.prepare(
molecule,
settings=PreparationSettings(stationary_point="ground_state", functional_form="harmonic"),
)
For N atoms the matrix must be 3N by 3N. Labeling a matrix with these units does not convert values from other units. Supply a real Hessian, not a frequency list or a guessed identity matrix. Preparation without a Hessian fails; it does not silently generate one.
Reference-property generation is a separate explicit
q2mm.evaluate_reference(molecule, backend=..., property="hessian") action.
Optional QCEngine and
ASE integrations require their extras and an actual
supported program/calculator. See reference services;
they are not prerequisites for onboarding.
Many structures: complete field plus OPT/custom subset¶
Several molecules must share an explicit template topology. Supply a complete MM3 field for evaluation, and a smaller OPT/custom field that identifies which scalar parameters are allowed to change. It is a selection mask, not a second independent force field for evaluation.
This preparation-only example expects your own ordered QM logs and field files. It does not launch a full publication optimization:
import q2mm
from q2mm.io import load_gaussian_molecules, load_mm3_fld
from q2mm.settings import PreparationSettings
case_ids = ("TS1", "TS2")
molecules = load_gaussian_molecules(
["inputs/TS1.log", "inputs/TS2.log"],
structure_index=-1,
require_hessian=True,
bond_tolerance=1.4,
)
full_ff = load_mm3_fld("inputs/complete.fld")
opt_ff = load_mm3_fld("inputs/custom-opt.fld", include_standard=False)
problem = q2mm.prepare(
molecules,
settings=PreparationSettings(stationary_point="transition_state", initialize="qfuerza"),
force_field=full_ff,
active_parameters=opt_ff,
case_ids=case_ids,
)
case_ids define identity and order explicitly. structure_index=-1 selects
the last structure; Gaussian/Jaguar/MacroModel bridges require you to choose
an index instead of silently taking one. Batch loading is all-or-nothing.
With initialize="qfuerza", Hessian-informed initialization re-estimates
supported active bond/angle scalars; frozen values stay equal to the baseline.
With initialize="provided", supplied values are retained and the native
qfuerza default becomes None. Both settings JSON-round-trip without a
resolver. See the initialization methodology.
Declared TS preparation inverts negative QM curvature so the target TS
geometry can be an MM minimum, not an MM saddle.
Pass explicit observations= when your scientific target set differs from
the generic recipe. Neither this preparation nor a successful optimizer
flag establishes completeness of charges, energy differences, torsions or
other scientific targets. See
observation categories.
Assemble and save an MM3 run to .fld, not harmonic .frcmod.
Publication workflows are a separate branch¶
Current system/profile/start availability and blockers are generated on the publication coverage page, not maintained as another tutorial table. Follow its per-system source links before provisioning files or making a reproduction claim.
Rh-enamide source inputs at examples/publication/rh-enamide are
source-tracked but excluded from wheel/sdist;
recovered dissertation archives and the MM3 base must be supplied by the
caller. Publication examples never download them. Canonical relaxed-geometry
publication optimization remains methodology-blocked: limit work to
preparation/evaluation and bounded optimizer-entry/save proofs, not full
convergence runs.
First full case: Rh-enamide¶
Here "full case" means a multi-structure template problem, not exact
publication reproduction or a full convergence run. The named Rh-enamide
loader prepares the ordered nine-case, frozen
repository-geometry-eigenmatrix-v1 compatibility objective. That objective
does not supply all targets in the
governing source; consult the
source/status details before using it.
Preparation only, with the source checkout explicitly selected:
from pathlib import Path
from q2mm import ExternalDataRoots
from q2mm.benchmarks.systems import load_system
case = load_system(
"rh-enamide",
data_roots=ExternalDataRoots(rh_enamide=Path("examples") / "publication" / "rh-enamide"),
starting_point="qfuerza",
objective_profile="repository-geometry-eigenmatrix-v1",
functional_form="mm3",
)
problem = case.problem
print(problem.case_ids)
print(problem.active_space.n_active, problem.active_space.n_full)
starting_point="published" retains the supplied OPT values instead of
re-estimating them; it does not change the named objective. No engine is
loaded here. Use the publication script's explicitly labeled --bounded-ci
only for a software-entry/save proof, never to claim a native fit.
The source-only
publication examples
document required roots and their software-only --bounded-ci mode. That
mode echoes references and updates no parameters, exactly as in the small
software proof; it is not a physical MM engine.
The benchmark CLI records candidates under your selected
<output>/candidates/. Only accepted candidates are promoted to
<output>/accepted/ and <output>/forcefields/; a rejected/skipped candidate
does not promise a saved force field. The small example instead calls
q2mm.save explicitly, including when its numerical budget expires, without
changing benchmark acceptance policy. See CLI persistence.
Historical numerical claims require tracked evidence in
ericchansen/q2mm-data.
Keep generated example outputs in your chosen results directory, not in a
code commit.
For custom observation blocks and mixed stationary points, construct explicit
TrainingCase and OptimizationProblem values using the
data model guide.
For an independent backend, reuse the source-only
backend plugin example
and backend API v1 authoring guide.