Phase 3 notes#
2026-10-03 – plan and decisions#
Phase 3 is the Model Chemistry batch contract (get_task/analyze_task) for
ORCA and MOPAC, the facade that chooses MDI or batch, and the consumers on it.
Paul’s decisions:
Fix the MDI basis bug first, as separate bugfix releases (below).
MOPAC’s batch “energy” is the heat of formation, as its MDI engine reports it, so the facade’s choice of path never changes results.
Convert four consumers: the Energy step, ORCA’s counterpoise (BSSE, as a fan-out with ghost atoms), the Dimer Builder, and the finite-difference Hessian of Normal Mode Sampling.
The design session’s decisions:
The facade lives in
seamm_exec, notseamm.seamm_execalready depends onseamm, and how things run is its job.seamm_mdiis imported only on the MDI path.A provider may set
prefers_batchin its options. ORCA sets it: its MDI engine runs a subprocess per evaluation, so the pool’s concurrency beats a sequential warm engine. MLFFs, MOPAC and xTB stay on MDI locally.analyze_taskraises on a missing property; it never returns partial numbers.A test checks that the batch path equals the MDI path for ORCA and MOPAC water; it also protects the heat-of-formation decision.
The local pool applies resolvers only to tasks without
config. The command must be exactly today’s; the SEAMM_DEV A/B ontest.flowandbsse.flowis the proof, beforeorca_stepis released.options.ghost_atomsmust be enough forseamm_bsse’s N-fragment SSFC inputs (ghosts plus per-fragment charge and multiplicity), which is also the MBE step’s counterpoise path.Release order:
seamm_exec, thenorca_stepandmopac_stepwith pins, then the consumers.
2026-10-03 – the MDI basis bug (bugfix releases)#
Found by the phase 3 survey and reproduced. ORCA, driven as an MDI engine by the Energy step, ran def2-SVP whatever basis was chosen.
Cause:
match_model_chemistrybuilt the published_model_chemistryfrom the first matching offering, user’s basis substituted. But it kept the offering’soptions– andoptions["mdi_basis_arg"]was the advertised example basis, which the Energy step and the Dimer Builder pass to the engine ahead ofmc["basis"].Normal Mode Sampling and LAMMPS passed
method=mc["method"]alone: no basis (so ORCA’s default def2-SVP), and the model-chemistry spelling rather than ORCA’s keyword.The ORCA MDI engine could not take a
bse:NAMEbasis at all.
Fixes, each with a test:
model_chemistry_step: the match copies the options and puts the user’s basis inmdi_basis_arg.orca_step: the MDI engine fetches abse:basis from the Basis Set Exchange intobasis.bas(%basis GTOName), as the ORCA step does. With the real binary, the energy matches ORCA’s own def2-SVP to 1e-6.normal_mode_sampling_stepandlammps_step:mdi_method_argandmdi_basis_arg, like the Energy step.energy_step(test only): a regression test through the real chain (match, method/basis rule, engine command, MDI engine, ORCA). It asserts the generated input’s basis, fordef2-TZVPandbse:def2-TZVP, and that the energy equals ORCA run directly (-76.4258395903 Eh, to 1e-8).dimer_builder_step(test only): its method/basis rule takes the user’s basis. Its code was already right once the match was fixed.
PRs: model_chemistry_step #7, orca_step #37, normal_mode_sampling_step #1 (CI moved to uv) and lammps_step #114, all 2026.10.3, independent of each other.
Lesson#
An advertised offering’s options must never override a user’s explicit choice. A program advertises a few example bases so the list stays short. Anything carried along with an example must be re-derived from the user’s selection when it is matched, not copied. The bug was silent: no error, just the wrong basis, found only by reading the code.
2026-10-03 – the MOPAC MDI engine’s forces (bugfix release)#
Found by the first run of the MOPAC batch == MDI test. The heats of formation agreed to 2e-7 kJ/mol, but the MDI gradients were 3.57 times the batch ones.
A central difference of the MOPAC binary’s heat of formation (O z of water: 6.951 kcal/mol/Å) matched the batch path (6.959).
The factor is (bohr/Å)².
mopac_mdi.pyconverted kcal/mol/Å to hartree/bohr with* BOHR_PER_ANGinstead of* ANG_PER_BOHR, so every MOPAC force over MDI has been 3.57 times too large since 2026-06-23 (992909f). Energies were right.Affected: the Energy step’s MOPAC gradients, LAMMPS QM/MD with a MOPAC model chemistry, and Normal Mode Sampling’s finite-difference Hessian with MOPAC (frequencies about 1.89 times too high). xTB’s engine works in atomic units throughout and is unaffected; ORCA’s agrees with the batch path.
Fixed in
mopac_step2026.10.3 (PR #158). A regression test compares the engine’s forces with a finite difference of its own energies.
After the fix, MOPAC’s batch and MDI gradients differ by about 0.1%: the
single-SCF gradient depends on where the SCF stops. On water’s O z it is 29.116
(binary default), 29.082 (SCFCRT=1e-12), 29.072 (mopactools) and 29.084
kJ/mol/Å (a finite difference). The invariant test states 0.3% + 0.02 kJ/mol/Å
for MOPAC gradients and 1e-4 kJ/mol for heats.
Also found: a conda run -n <env> python engine command runs whichever
python is first on the PATH. With a SEAMM venv’s bin ahead of conda’s,
the MOPAC engine started the wrong Python and could not import mopactools.
This is a pre-existing fragility of the MOPAC (and other conda run) engine
launchers. It showed up here only because of how the tests were started.
Recorded, not fixed.
Lesson#
Two independent paths to the same number are a test. The batch == MDI invariant was written to protect a design decision. Its first run found a units bug that had been in production for three months, because nothing had compared the MOPAC engine’s forces with anything else.
2026-10-03 – what was built#
Committed locally on dev in each package; nothing pushed except the
bugfix releases above.
seamm_execevaluator.pyholdsEvaluator,choose_path(),Geometry,structure_data(),check_properties()andmdi_method_and_basis().submit(configuration, key=None, options=None)andresults()yieldEvaluatorResultobjects:ok,energy(kJ/mol),gradients((n, 3) kJ/mol/Å),stress,reason,restored,pathandelapsed.The MDI path is the Energy step’s old loop, moved: one engine per (elements, charge, multiplicity, periodicity), stress only where the engine supports it.
seamm_mdiis imported only there.The batch path is a
TaskSetof the provider’s tasks, read back by itsanalyze_task; a failed or partial task givesok=Falsewith the reason.The local pool now configures every task without
configfrom that machine’s ini file and the program’s resolver (_configure). Tasks withconfigare untouched;resolve_programsis kept but no longer changes anything.
orca_stepbatch.py:get_taskwrites the MDI engine’s own input: the helpers are loaded fromdata/orca_mdi.py. That covers the keyword plus AutoAux, thebse:basis file, the DLPNO block andEnGrad.optionsgivesatom_indices,ghost_atoms(writtenO:),chargeandmultiplicity. Defaults: 1 rank,%maxcore 2000;resourcesoverrides them.analyze_taskuses the engine’s parsers, with the same pint conversions asseamm_mdi. Methods without an MDI engine (CCSD(T) and the like) get their real keyword and can run energy-only as tasks.resolver.py: theorcaresolver (full path,mpi_env,{orca_2aim}, PATH fallback), registered inorg.molssi.seamm.exec.resolvers._mpi_envbecame the module functionmpi_env.The ORCA step’s own tasks no longer carry
config, so ORCA steps can now go to remote targets.run_orca_jobis split:orca_job_taskbuilds the task.BSSE builds all its sub-jobs and runs them as one
TaskSet.Every ORCA offering declares
prefers_batch.
mopac_stepbatch.py: a single SCF with gradients andAUX(PRECISION=9). The energy is the heat of formation (Paul’s decision). Only the lowest spin state, and molecules only; open-shell and periodic MOPAC stay on MDI, where equality has not been shown.resolver.py: themopacresolver (ini, elsewhich mopac; it never writes files, unlike the MOPAC step).The MOPAC step’s own runs keep their
configthis phase (its configuration code writes default ini files). Consider each code separately.
energy_steprun()is on the Evaluator, with keysc<configuration id>. Properties,store_resultsandenergies.csvare unchanged. The MDI-capable requirement is dropped. Failures are listed and raised after the successful structures are stored. The report says “N engine sessions” (MDI) or “N separate calculations, M finished in an earlier run” (tasks).dimer_builder_step_open_energy_enginereturns_MDIEnergies(the engine plusenergies) or_TaskEnergies(the Evaluator). Grids – the 11-point minimum search, the outward profile, the interpolation points – are evaluated together. The inward wall walk stays one point at a time, since it is sequential by nature. The van der Waals radii are now cached, and mendeleev’s SQLAlchemy sessions are collected on the main thread. Left to the garbage collector inside a pool worker thread, SQLite refused to close them and logged frightening, harmless errors.normal_mode_sampling_stepThe Hessian is analytic over MDI when the engine offers
<HESSIAN. Otherwise it is the finite difference: as tasks (_fd_hessian_tasks, 6N displaced structures, keysc<id>-fd<j><p|m>) when the Evaluator chooses the batch path, else over the warm engine as before. A model chemistry without an MDI engine now works through the tasks.
Validation#
Unit and integration tests:
Package
Tests
New
seamm_exec98
the Evaluator: path rule, batch path, failures, restart, helpers
orca_step178
resolver equivalence (4 cases), batch == MDI, ghost atoms, partial results, BSSE on a TaskSet
mopac_step39
batch == MDI (heat of formation), refusals,
.auxparsing, forces vs finite differenceenergy_step21
dimer_builder_step59
normal_mode_sampling42
FD-by-tasks assembly on a quadratic potential
The resolver reproduces the old command, environment and configuration: serial and parallel, with and without
orca_2aim.ORCA batch == MDI for water at def2-SVP and
bse:def2-SVP: exact (1e-6 kJ/mol) on the first geometry; within the SCF convergence on later ones, where the engine reuses the previous orbitals (3e-3 kJ/mol, 1e-2 kJ/mol/Å; observed 2.5e-4).MOPAC batch == MDI: heats to 1e-4 kJ/mol (observed 2e-7), gradients to 0.3% (see the force bug above).
Live flowcharts on this Mac (
Testing/phase3_*.flow):the Energy step over six molecules with ORCA (tasks, 5.9 s; a rerun restored them all) and MOPAC (MDI);
bsse.flowwith its sub-jobs on one TaskSet;the Dimer Builder with energy contacts: ORCA as tasks, 69 evaluations in 76 s, and MOPAC over MDI.
The NMS Hessian of HF/def2-SVP water by finite difference as tasks against ORCA’s analytic one: frequencies 1790.2 / 3972.5 / 4062.3 against 1790.9 / 3972.9 / 4062.4 cm⁻¹; the largest Hessian element difference is 1.1e-4 Eh/bohr².
Remote ORCA through the resolver, Mac -> TinkerCliffs.
The Energy step over six molecules with
SEAMM_TARGET=tcandinline_below = 0sent config-less ORCA tasks in two bundles. Without that setting, the inline rule rightly kept water’s 1 s estimates local.TinkerCliffs’
orca.ini(installation = modules) resolved them through theorcaresolver, in a development venv in/projects/seamm/psaxe/phase3.Energies are identical to the local run to all printed digits; forces agree to 1e-4 kJ/mol/Å.
The SEAMM_DEV A/B comparison (
venvs/phase3-A: today’s releases;phase3-B: plus the six checkouts) ontest,bsse,harness_water,builder_loop,phase3_energy_mopacandphase3_energy_orca:ORCA’s final energies, the BSSE energies and the MOPAC results are identical.
The ORCA Energy step over MDI (A) and as tasks (B) agrees to every printed digit.
The only differences are versions, pids and timings, plus the new
tasks/layouts (BSSE’s sub-jobs share one manifest in the step directory; the ORCA Energy step runs as tasks).
Review (2026-10-03)#
An independent review of the six diffs. Fixed, each with a test unless noted:
ORCA’s batch path accepted periodic structures. With
prefers_batchthe Energy step would have stored a gas-phase cluster energy for a periodic configuration; the MDI path had refused it.Programs now offer
can_run_task(configuration, model_chemistry, options). A structure for which it is False goes to the program’s MDI engine, if it has one, even when the rest run as tasks; otherwise it gets a failed result.ORCA’s
get_taskrefuses periodic structures and initial guesses (not supported yet), andcan_run_taskrefuses open-shell DLPNO.
A queue target forced MOPAC’s periodic and open-shell structures onto tasks it cannot run. Same mechanism: MOPAC’s
can_run_taskkeeps them on MDI. Aget_taskthat raises now fails that structure alone, instead of stopping the wholeresults().Pins.
orca_stepandmopac_steprequireseamm-exec>=2026.10.3. The released 2026.10.2.1 never resolves config-less tasks in the evaluator’s pool, so a neworca_stepon it would lose ORCA’s full path, the OpenMPI paths and{orca_2aim}.The three consumers now list
seamm-exec>=2026.10.3.The pool’s error names the missing resolver when a plug-in is not installed where a task runs (no test).
Lone atoms.
get_tasklacked the ORCA step’s one-centerNoCOSXguard, soseamm_bsse/MBE jobs for a bare Li⁺ or Na⁺ would get the ~5 kJ/mol RIJCOSX artifact. It is added to bothget_taskand the MDI engine (single_center_method), so the two paths still agree. Real ORCA, Na⁺: batch == MDI exactly.This is a behaviour change of the ORCA MDI engine for single atoms (a fix), and goes in orca_step’s HISTORY.
NMS imported ``seamm_mdi`` unconditionally, and failed when no engine could start, e.g. on a queue target with ORCA installed only on the cluster. It now imports lazily and falls back to the finite difference as tasks.
The Dimer Builder’s single points are slow on the task path. Deferred.
Each point of the inward wall walk is its own one-task
TaskSet(the manifest is re-read and re-written per point). On a queue target, a point aboveinline_belowwould be its own batch job.Small ORCA dimers stay local by the inline rule. The rest needs speculative blocks of inward points, or one long-lived
TaskSetper_TaskEnergies.
MOPAC’s ``.aux`` gradients stop at the next
KEY=line and must be exactly 3n values (AnalysisErrorotherwise).BSSE sub-jobs were treated as “concurrent”, so the pool set
OMP_NUM_THREADS=1and bindingnoneon them, under SLURM too. They run one at a time anyway, each taking every core. A task that takes the whole pool is no longer “concurrent”. Old per-sub-job restart records are not reused: one recompute after upgrading.Minor:
a configuration selected twice is evaluated once by the Energy step;
the
mopacresolver keeps a conda or modules installation whosecodeis empty;DLPNO open-shell refused energy-only too, matching MDI (unchanged).
Design session’s review (2026-10-03)#
A second independent read, at the heads with the fixes above. Fixed:
A refused structure on a queue target, with the code only on the cluster, went to a local MDI engine that could not start, and the error stopped every other structure. Such a structure now gets a failed result, “cannot run here: …”, and the rest finish (
_mdi_results(fallback=True)).NMS fell back to the finite difference on any engine failure, so a transient failure (a port, the environment) silently turned ORCA’s analytic Hessian into 6N tasks. It now falls back only when the code is not installed here, says so through the step’s printer, and closes the engine.
Tests. A task with
confignever meets a registered resolver, in the pool and inbundle_runner; a config-less one does. The Energy step’sc<id>keys are stable on rerun, a duplicate selection is skipped, failures are stored then raised, and the report says how the work ran. The Dimer Builder maps out-of-order results back by key.The stress is required for a periodic structure (
check_properties(..., periodic=True)), so a future periodic batch program cannot return none.This note and both design copies now carry the contract as coded: classmethods
(configuration, model_chemistry, *, ...), no citations, the stress as the program gives it, andcan_run_task.One ``mdi_method_and_basis``: ORCA’s and MOPAC’s
get_taskcallseamm_exec’s, so the basis-wins rule lives in one place.The resolver registry is built under a lock; pool threads asked for it at the same time and a second build dropped a
register().Wording: MOPAC’s refusal says the structure “runs on MOPAC’s MDI engine where one is available”; “Where ORCA runs” says
codemust be ORCA’s full path on module sites.
Addendum, also fixed:
NMS rule: on a queue target, the target wins. The finite difference runs as tasks on the cluster, and no local engine is started, even to ask for
<HESSIAN. Otherwise the analytic Hessian goes over MDI when the program has one: ORCA now says so in its model-chemistry options (analytic_hessian, frommethod_has_analytic_hessian), so no live probe is needed; other programs are still asked. Failing that, the finite difference runs as tasks if the evaluator chose them, or over the warm engine.NMS closes the engine on the fallback, catches only the “no code here” case, and prints the reason.
_can_run_tasklogs a provider’s exception before treating it as False, so a buggy hook does not reroute silently.orca_step’s HISTORY records the MDI engine’s behaviour change: a single centre now runs without COSX, which moves lone-ion energies by about 5 kJ/mol from before.
The Energy step’s report says how a mixed run went: “X over MDI in N engine session(s) and Y as separate calculations”.
The MOPAC batch == MDI test has a doublet, OH. Heats agree to 1e-3 kJ/mol; the gradients to 0.06 kJ/mol/Å, because the MDI path (mopactools) gives spurious components of about 0.03 kJ/mol/Å perpendicular to the bond.
Released (2026-10-03)#
Paul merged the PRs and the design session created the releases, in order:
Package |
Version |
PR |
|---|---|---|
|
2026.10.3 |
#35 |
|
2026.10.3.1 |
#38 |
|
2026.10.3.1 |
#159 |
|
2026.10.3 |
#1 |
|
2026.10.3 |
#9 |
|
2026.10.3.1 |
#2 |
energy_step and dimer_builder_step are 2026.10.3, not .1: neither had
released earlier that day. Both moved to the uv CI (test_env.yaml removed)
in the same release. Nothing is rolled out to an installation yet.
Lessons#
A CI rerun can race PyPI. The plug-ins’ CI failed on the
seamm-exec>=2026.10.3pin untilseamm_execwas published. A rerun started right after the publish can still fail: the index (and its JSON API, which lagged the uploads by minutes here) need not serve the new version yet. Rerun again rather than look for a problem. Check a version withhttps://pypi.org/pypi/<package>/<version>/json, not the package’s latest.energy_step’s Makefile predates theupdatetarget (a 2026-09 scaffold), so its checkout was synced by hand. Copy the current cookiecutter Makefile targets into it,xnn_stepand the other 2026-09 scaffolds at their next change.
Still open: the Dimer Builder’s inward wall walk on the task path (one
TaskSet per point). The phase 3 test environments
(~/SEAMM_DEV/venvs/phase3-{A,B}, tinkercliffs:/projects/seamm/psaxe/phase3)
were deleted on 2026-10-03.