Phase 6 notes
=============
2026-10-04 -- design note (draft for Paul and the design session)
-----------------------------------------------------------------
Phase 6 adds a ``parallel`` option to the Loop step: its iterations run at the same
time, each in its own evaluator with its own job database, and the parent merges
what they did back into the job. Nothing is coded yet. This note records the survey,
the proposed design and the decisions Paul needs to make, marked **D1** to **D8**.
Inputs: the design doc's "The parallel Loop" section (decided 2026-10-02: iterations
are evaluator tasks with a declared independence contract; the snapshot holds the
selected configurations by default, the whole database as an option, Q5); phase 4's
change journal "for the phase 6 merge"; phase 5's checkpoint (frames, frozen items,
``run_id``, restore hooks); and the design session's ten points and five follow-ups
(2026-10-04), each answered below.
The idea: an iteration is a resume
----------------------------------
A child evaluator runs **the parent's own flowchart file**, resumed from a checkpoint
the parent synthesizes: the Loop's frame holds the frozen items and iteration *k*,
the next node is the body's first, and the frame is marked "this iteration only". The
child is started like any resume (``SEAMM_RESUME=1``). When iteration *k* ends, the
Loop sees the mark, leaves without advancing, and the evaluator stops there instead
of running the rest of the flowchart.
Everything phase 5 built then applies unchanged: the variables at loop entry (the
codec, the restore hooks keyed by step id, ``Unrestorable`` markers), the current
system, the Loop's frozen item list and directory name, nested loops, and the
fingerprint, which matches by construction because it is the same flowchart. Step
numbering is the parent's (``3.iter_0017.2``), so step directories and headers are
what a serial loop gives.
*The alternative* is to cut the loop body out as a standalone flowchart
(``format3.tree`` → the Loop's item → ``_steps_data`` → ``from_data``) and start it
with injected state. The survey found it workable but worse: there is no way today
to inject variables or the current system at start (a new mechanism), the child
numbers its steps from 1 (directories and headers shift), nested loops and restore
hooks would need the same treatment again, and it is a second code path for running
a body. Resume reuses tested machinery; this note goes with it.
Survey (2026-10-04)
-------------------
- **No cross-database copy or merge exists in molsystem.** Only same-database
``copy_configuration``/``create_combined_system``; ``attach`` and ``_Table.copy``
(whole SQL tables) are the tools. Primary keys are plain ``INTEGER PRIMARY KEY``,
so every child allocates the same new ids: the merge must remap every new object
and its foreign keys (system → configuration → atomset/bondset/cell/symmetry →
atoms, bonds, coordinates, velocities, gradients; subsets, templates). Properties
are rows keyed by a per-database property id: map by name.
- **The table journal has no values** (op, table, row, column only), does not record
``current_row``/metadata/index changes, and appended rowids collide across
children (``append_rows`` starts at the copy's own maximum).
- **Job-level paths follow the root.** ``Node.directory`` and ``Node.job_path`` are
both ``flowchart.root_directory``; ``/name`` and ``job:NAME`` paths (Write Structure,
Read Structure, LAMMPS, MBE's labels file "so a loop gathers every configuration
into one file", ORCA checkpoints, Properties' ``
.csv``) resolve against it.
- **No "run_flowchart" program for the task layer**: a program needs a ``config``,
an ini, or a resolver (only orca, mopac, vasp register one).
- ``safe_filename`` de-duplicates by looking at the disk, so directory names are fixed
by the parent before dispatch (the frozen frame already holds them).
``BreakLoop``/``ContinueLoop``/``SkipIteration`` are exceptions inside a process.
- Each child writes its own ``references.db`` and ``final_structure.*``.
The design
----------
Where a child runs (design follow-up 2)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The flowchart gets a ``job_directory`` beside ``root_directory`` (defaulting to it,
so nothing changes for a normal job): ``Node.directory`` stays under
``root_directory``, ``Node.job_path`` (``/name``, ``job:``) and the evaluator's own
files move to ``job_directory``. A child runs with
- ``root_directory`` = the parent's job directory, so its step directories are the
serial loop's ``//`` -- distinct per iteration, so children
never share one;
- ``job_directory`` = ``//_evaluator/``: its ``job.out``,
``job_data.json``, ``seamm.db`` (the snapshot it writes), ``baseline.db`` (the
untouched snapshot, read-only), ``checkpoint.json``, ``references.db``, and any
job-level file the body writes (``/name``).
So no two evaluators ever write one database (the one-writer rule), and nothing the
child writes collides with the parent or a sibling. On a target without a shared
filesystem the iteration's directory is staged out and back as a unit.
The snapshot (design 1, follow-up 4)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Built by the parent, committed state only, into ``_evaluator/seamm.db``:
- **default** (Q5): the iteration's selected configurations -- the current
configuration (a loop over systems), plus any configurations the frozen frame names
-- each with its system, atoms, bonds, cell, symmetry, coordinates, velocities,
gradients and properties, copied with **their parent ids**; all user tables with
their rows, registry and rowids (copying only "the tables the body reads" is not
knowable in general; tables are small next to structures); the property
definitions; an empty journal. A new molsystem ``SystemDB.snapshot(path,
configurations=..., whole=False)`` does it with ``ATTACH`` and ``INSERT … SELECT``,
following the foreign keys. Keeping parent ids makes "unchanged" mean "same id,
same rows", and only new objects need remapping on merge.
- **whole database** (Loop option): ``sqlite3`` backup API, a consistent copy of the
committed parent.
Then ``baseline.db`` = a copy of the snapshot, kept read-only: the merge diffs against
it. The checkpoint row is written into the snapshot last (``run_id`` new for the child
-- its ``appended_files`` records are its own).
What comes back: the merge (design 2, 3)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The parent merges iterations **in iteration order**, each in one transaction together
with its checkpoint (the merged prefix in the Loop frame), so a kill mid-merge leaves
either nothing or the whole iteration merged. A finished iteration waits for the ones
before it; merging is cheap next to running.
- **Tables** (authority: the child's journal). Replay in journal order: ``create`` and
``add_column`` if missing; ``append`` → a new parent row (renumbered: never promise
rowid stability; the user-visible key is the index column or position); ``set`` on a
baseline row → the same parent row; ``set`` on an appended row → its renumbered row.
Values are read from the child's database by (table, row, column) at merge time: the
final value is the one that matters, and the journal stays small (decision: no value
column). Two iterations setting the same cell of a baseline row: **D3**.
- **Properties**: values the child added or changed on configurations it had from the
parent go onto the same parent configurations (property ids mapped by name).
- **Structures**: **D1** -- what to bring back.
- **Files**: the step directories are already in place. Job-level files in
``_evaluator/``: a file Write Structure *appended* (its ``appended_files.json``
record says from which size) has the appended bytes appended to the parent's file,
in iteration order; any other job-level file is copied to the parent's job
directory, later iterations replacing earlier ones (what a serial loop leaves);
tables a child exported (``Save as``) are re-exported from the merged table at the
end of the loop. The evaluator's own files stay in ``_evaluator/``.
- **Citations**: the child's ``references.db`` entries are cited in the parent's.
- **Variables**: not merged. A variable set in the body is not visible after the loop
(the contract); the loop variables are removed as now.
- A body that *reads* a table or structure another iteration writes sees it as it was
at loop entry (snapshot semantics), unlike a serial loop.
The contract (design 3)
~~~~~~~~~~~~~~~~~~~~~~~
Documented in the Loop's help and user guide, declared by choosing ``parallel``, not
verified: iterations are independent; each sees the job as at loop entry; what comes
back is what D1 decides plus table rows, properties and files; variables set in the
body are not visible after the loop; ``break`` / ``continue`` / ``skip`` work per
iteration (a ``break`` in iteration *k*: iterations after *k* are cancelled or not
merged, so the result is the serial one).
Running children: placement and resources (design 5, 7; follow-up 5)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Each iteration is a ``Task`` in the Loop step's ``TaskSet`` (manifest under the loop's
``tasks/``), program ``seamm`` -- a new resolver in seamm_exec gives the evaluator
command for the target: the parent's own interpreter locally, the target's
``remote_python`` (``-m seamm_exec.exec_flowchart``) on a cluster -- with
``SEAMM_RESUME=1`` in the task's environment. Resources per iteration come from the
Loop (cores, memory, walltime estimate).
- **Placement** (design doc): *inline* (default) -- the body's codes run in the
iteration's own allocation through the child's ``LocalPool``; *separate tasks* --
the child submits its codes to the target like any job.
- **Shapes**: on the laptop the parent's ``LocalPool`` runs ``cores / cores per
iteration`` children at once; with a queue target the iteration evaluators are
bundled several per allocation (the task layer's bundling); on ARC alone the same
from a queued parent.
- **No deadlock** (the rule): a child's own codes and a nested parallel loop inside it
use the *child's* pool and the *target*, never the parent's pool; the parent's pool
slots hold only iteration evaluators, and the parent waits on nothing else. A nested
parallel loop is a child spawning children against the target.
- A child's lost or timed-out allocation is retried by the task layer's rules
(``max_attempts``; the ``2**n`` time for timeouts); the retried child resumes from its
own checkpoint.
Errors (design 6)
~~~~~~~~~~~~~~~~~
The ``errors`` parameter applies per iteration: *continue* -- the failed iteration is
reported (in ``job.out`` and the Loop frame's ``failed`` list) and **never merged**,
unlike a serial loop, which keeps a failed iteration's partial writes (phase 5 D1);
*exit* -- no new iterations start, running ones are cancelled, and the iterations
before the failed one are merged; *raise* -- as *exit*, then the job fails.
Checkpoint and restart (design 4)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The parent's Loop frame keeps the frozen items, ``parallel: true``, the merged prefix
and the failed list; the per-iteration states (pending, running, done, failed) are the
``TaskSet`` manifest's, as for any task. A resumed parent re-plans from them: done
and unmerged → merged; running → reattached (queue) or rerun (local, the child
resumes itself); pending → submitted. A child uses the same ``_checkpoint`` row and
``checkpoint.json`` as any job.
Dashboard (design 8)
~~~~~~~~~~~~~~~~~~~~
First release: the Loop's ``job.out`` reports iterations started, finished, failed and
merged; the task counts are in the manifest. Child iterations as rows of their own
(``parent_id``) later.
Decisions for Paul
------------------
- **D1 -- structures.** Recommended: bring back everything an iteration did to
structures -- new systems and configurations are added to the parent (ids
remapped), and configurations from the parent that the iteration changed (e.g.
optimized with "overwrite the current configuration") replace the parent's. This
keeps the common serial loops ("create a structure from SMILES, optimize it, store
results") giving the same database. Two iterations changing the same configuration
(possible only with the whole-database snapshot) is an error. *The design doc's
stricter contract -- table rows, properties and files only -- is the alternative;
it is less work but silently drops the structures such loops make.*
- **D2 -- snapshot content by default**: the selected configurations and all user
tables (recommended), not only "the tables the body reads", which cannot be known.
- **D3 -- the same cell written by two iterations**: an error by default, a Loop option
to let the later iteration win with a warning (design session's recommendation).
- **D4 -- failed iterations** under *continue* are not merged (design doc), unlike a
serial loop, which keeps their partial writes.
- **D5 -- job-level files**: appended files gathered in iteration order, other files
last-iteration-wins, exported tables re-exported after the merge (recommended).
- **D6 -- the Loop's new parameters**: ``parallel`` (no / yes), ``iterations at once``
(default: as many as the cores allow), ``cores per iteration`` (default 1),
``memory per iteration``, ``snapshot`` (selected structures / whole database),
``placement`` (inline / separate tasks), ``same-cell writes`` (error / later wins).
Grayed or hidden when ``parallel`` is no (the GUI rule).
- **D7 -- scope of the first release**: all four loop types; ``break`` handled as
above; Dashboard child rows later.
- **D8 -- validation**: an A/B harness comparing serial and parallel runs of the same
flowcharts (tables, properties, structures, files; identical, row order aside where
the table has an index); the kill soak extended to kill a child, the parent, and
both; a live run on TinkerCliffs with iterations bundled as queue jobs. Local runs
stay small (MOPAC, a few iterations at once; the 2-4 GB rule).
Packages and order
------------------
molsystem (``snapshot``, structure import with id remapping, table journal replay
helpers) → seamm (``job_directory``, the "iteration only" frame, the snapshot
checkpoint) → seamm_exec (the ``seamm`` resolver, child mode in ``ExecFlowchart``,
merge driver) → loop_step (the ``parallel`` option, dispatch, merge in order, GUI)
and the six steps that build job-level paths from ``root_directory`` (read_structure,
table, properties, lammps, orca, geometry_analysis; review point 6).
seamm_scheduler only if bundling evaluators needs it. Opt-in, default off: no soak
switch; pins to the phase 5 versions.
Review (design session, 2026-10-04)
-----------------------------------
The design session reviewed the draft and approved the design (child as a resume,
``job_directory`` beside ``root_directory``). Folded in:
1. *Must:* **no snapshot or merge through the parent's deferred connection.** An
``ATTACH`` + ``INSERT … SELECT`` on the parent's connection would be part of its
open transaction (not visible to the child until the parent commits), and with
``detach`` a no-op while deferring, one attachment per iteration would hit
SQLite's limit of 10. So: the Loop writes the iteration's frame and commits
(checkpoint) **before** dispatch; ``SystemDB.snapshot()`` reads committed state
through its **own short-lived connection** (or the backup API for the whole
database). The merge runs in the parent's per-iteration transaction but reads the
child's database through a **separate read-only connection**, never an ``ATTACH``
on the parent's.
2. *Must:* **what a child needs on a target without a shared filesystem.** Stage in:
the flowchart, ``//`` (with ``_evaluator/``), and the job
directory's top-level *files* (inputs read by ``/name`` or ``job:``: structure,
forcefield, control-parameter files) -- not other iterations' or steps'
directories; a Loop option adds paths. Stage out: ``//`` only. So a
500-iteration loop does not copy the job 500 times.
3. **Current row after the loop**: as a serial loop leaves it -- for each table an
iteration moved, the current row of the last merged iteration (mapped to the
parent's row); otherwise unchanged.
4. **New property definitions** a child created (``store_results``) are created in the
parent before values are mapped by name, as a child's new tables are; a child's
``add_column`` whose type or default differs from the parent's column is an error.
5. **Iterations at once**, memory-aware: ``min(cores // cores per iteration,
memory // memory per iteration)``, memory per iteration defaulting to 2 GB -- on a
16 GB laptop 4-8 MOPAC children but 1-2 ORCA (the machine-wide 2-4 GB rule).
6. **Everything job-level keys off ``job_directory``** in a child: ``plan_start``,
``archive_previous_database``, ``checkpoint.json``, ``job_data.json``, ``job:`` and
``/name``. Direct uses of ``flowchart.root_directory`` for job-level paths, which
must move to ``Node.job_path`` (else a child writes into the parent's job
directory): read_structure_step ``write_structure.py:180`` and
``read_structure.py:204,209,213`` (``213`` derives other jobs' directories for
``job://`` and must use the *parent* job's directory); table_step
``table.py:305`` (``Save as /name``); properties_step ``properties.py:286``;
lammps_step ``nve.py:485``; orca_step ``energy.py:655`` (checkpoints);
geometry_analysis_step ``geometry_analysis.py:230``. The code steps' uses that pass
the root to their subflowcharts stay (step directories), and gaussian_step's are
its own install root. So phase 6 also touches these six step packages.
7. **Errors**, completed: under *exit*/*raise*, iterations after the failed one that
had already finished are not merged (as for ``break``) and their directories are
kept and reported; under *continue*, a failed iteration's step directories and
``_evaluator/`` stay for debugging.
Nits taken: D3's same-cell rule covers the index column; citations merge by key
(no duplicates); each child's iteration gets the usual banner in the parent's
``iteration.out``; the parent's ``job_data.json`` records iterations run, failed and
merged.
The design session's recommendations on D1-D8 match the ones above, with D6's
memory-aware default (5).
Decisions (Paul, 2026-10-04)
----------------------------
D1-D8 approved as recommended (with the review's memory-aware D6): structures come
back with ids remapped; the default snapshot is the selected structures and all
tables; the same cell from two iterations is an error unless the Loop allows the
later to win; failed iterations are never merged; job-level files as in D5; the new
Loop parameters; all four loop types with inline and separate placement; validation
by serial-vs-parallel comparison, kill soak on parent and children, and a live
TinkerCliffs run.
Implementation (2026-10-04)
---------------------------
Local commits on ``dev``, not pushed; all ten packages installed editable in
``~/SEAMM_DEV/venvs/phase6-B``.
- **molsystem** ``molsystem/snapshot.py``: ``snapshot`` (own connection; selected
configurations with their ids, or the whole database by the backup API),
``baseline`` (read-only copy) and ``merge`` (property definitions by name, id
tables remapped, row tables, property data, the table journal replayed with
values read from the child, ``MergeConflict``). Found in testing: an iteration
adds *columns* -- per-atom attributes of the structure tables (MOPAC's charges)
and table columns after rows it appended (MOPAC results) -- so the merge adds
new columns first and reads the baseline with NULL for them.
``System.copy_configuration`` fixed on the way (it copied from the new
configuration, not the previous one).
- **seamm**: ``Flowchart.job_directory`` (defaults to ``root_directory``);
``Node.job_path`` follows it, ``Node.job_file(name)`` reads the iteration's own
file else the job's (inputs read by ``/name`` or ``job:NAME``); citations go to
``job_directory``; ``IterationDone(broke, skipped)``; ``Checkpointer.write_child``
(the child's checkpoint, written into its snapshot), ``parallel_loop`` (the
parallel frame, committed), and ``iteration`` kept in the final checkpoint.
- **seamm_exec** ``seamm_exec/iteration.py``: the ``seamm`` resolver (entry point;
``run_flowchart`` beside the interpreter; the child's share of the machine as
``SEAMM_CE``, which ``computational_environment`` honours), ``iteration_task``
(shell command with relative flowchart and parent paths, ``SEAMM_RESUME=1``,
``SEAMM_PARENT_JOB``, ``SEAMM_TARGET=""`` for inline placement, ``target.json``
copied for separate), ``iteration_outcome``, ``merge_database``,
``plan_files``/``merge_files`` (appends from Write Structure's records, by run id;
other files copied; redone exactly from the plan), ``merge_citations`` (not the
SEAMM citation every evaluator adds). Child mode in ``exec_flowchart``: forced
standalone (no datastore job of its own); resumes only its parent's checkpoint,
never from the top; a rerun after the iteration finished reports it from the
checkpoint. ``Task.keep`` (``"."`` for an iteration) so an in-situ task's
cleanup leaves the evaluator's files; ``TaskSet`` looks for ``target.json`` in
``job_directory``.
- **loop_step**: the D6 parameters, shown only when parallel is yes. ``run()``
refactored around ``_prepare`` / ``_setup_iteration(k)`` / ``_end_loop`` shared
by the serial loop, the child (``only``) and the parent. The parent:
``parallel_loop`` frame (frozen items, directories given out, ``next`` to merge,
failed, merge state, exports, pending file merge); ``loop_entry.db`` (the
committed database at loop entry, rollback journal) from which every snapshot
is taken; per iteration its set-up, directory name committed before the
directory exists, snapshot + ``write_child`` + baseline, then a task; the
``TaskSet`` with a ``LocalPool`` of ``iterations at once``; merge in order (one
transaction + checkpoint each, then files and citations from a committed plan);
for a loop over rows the iteration's row is made current before the merged
current rows are applied (the merge cannot see a move back to the entry's
current row). An evaluator that stopped before its iteration ended (its
checkpoint still 'running': killed, out of memory) is run again, resuming, up
to twice; one that ended in error is a failed iteration. Errors: *continue*
records and goes on; *exit* records and stops; *stop the job* raises without
recording, so a resume runs the iteration again. ``break`` stops dispatching
and merging after the iteration; skipped iterations are merged and their
directory removed, as serially.
- **The six steps** (read_structure, table, properties, lammps, orca,
geometry_analysis): job-level writes to ``job_path``, reads through
``job_file``; ``job://`` keeps ``root_directory`` (the parent job in a child);
ORCA's read-only checkpoint lookup falls back to the job's checkpoints.
Validation so far (``Testing/phase6``: ``ab_parallel.py`` builds each spec serial
and parallel with ``seamm-flowchart`` and compares databases and files;
``kill_parallel.py`` kills the parent, a child, or both, resumes, and compares
with the serial run):
============================ ==============================================
case serial vs parallel
============================ ==============================================
p1 rows + MOPAC + files identical
p2 systems, optimize in place identical (D1)
p3 parallel outer, serial in identical
p4 serial outer, parallel in identical
p5 break, skip, failure identical database; the dispatched iteration
after the break kept, not merged (by design)
============================ ==============================================
Differences that are the contract, not bugs: variables set in the body (and the
row variables of a rows loop) are not visible after a parallel loop; the order of
the table journal (merge bookkeeping). A loop whose iterations are *not*
independent (Read Structure overwriting the current system that iteration 1
created) differs, as documented in the Loop's user guide.
Timing: each iteration pays ~5 s of evaluator start-up, so the tiny MOPAC test
loops run slower in parallel (p1: 11 s serial, 21 s parallel with 2 at once).
Kill soak (``kill_parallel.py``, local, 2026-10-04): every trial resumed and matched
the serial run -- p1 rows: parent 3/3, child 4/4, both 6/6 (rerun after the fix
below); p2 systems: parent 3/3, both 3/3; p3 parallel outer: parent 3/3, both 3/3;
p4 parallel inner: parent 3/3, both 3/3. Killed iterations were run again and
resumed from their own checkpoints; orphaned evaluators of a killed parent were
killed by the resumed parent's ``TaskSet`` before it reran them. Found: an evaluator
killed *as it exited*, after its iteration had finished, was counted a failed
iteration (return code -9) and dropped under *continue* -- now the iteration's own
final checkpoint decides, whatever the return code (loop_step 92d9ac8).
Live TinkerCliffs (2026-10-04), flowchart ``p8_tc`` (a parallel Foreach over SMILES:
From SMILES as a new system, a row, MOPAC PM7 optimization into the row, Write
Structure appending ``/all.sdf``), private venv ``/projects/seamm/psaxe/phase6``:
============================================ ========= ======================
run elapsed vs serial reference
============================================ ========= ======================
ref: serial, 1 core (SLURM 7855609) 1:48 --
inline: 4 at once in a 4-core job (7855610) 1:50 identical
queue: 1-core parent, iterations bundled 2 2:27 identical
per SLURM job (7855611 + bundles
7855614-17)
SEAMM_DEV job 4013 via the JobServer, 0:58 identical
4-core, 1-minute walltime (finished as the
walltime hit; JobServer trusted job_data)
SEAMM_DEV job 4014, 24 molecules, 1-minute 3 attempts identical to ref24
walltime: TIMEOUT, resubmit, resume at 1/24; (SLURM (7855648, 1:59) but a
TIMEOUT, resume at 19/24; COMPLETED 7855649, ``mopac.end`` of a
(``resubmit_count`` 2) -56, -58) killed MOPAC run
============================================ ========= ======================
The iterations are short (about 10 s of MOPAC each), so evaluator start-up hides
any speed-up; the runs validate correctness, placement and the queue bundling,
not performance. **The live validation (D8) is complete** for inline placement,
iterations bundled as queue jobs from a parent on a compute node, and the
JobServer's resubmit-and-resume of a parallel loop. Separate placement (the
children's codes as tasks on the target) is not yet tested live.
The two remaining A/B cases (2026-10-04): **p6, whole-database snapshot** -- a loop
over systems with each iteration given the whole database (each baseline holds all
six systems): identical to serial. **p7, two iterations setting the same cell** of a
row that existed before the loop: by default the parallel job stops with
``MergeConflict`` at iteration 2 (iteration 1 merged and committed, iteration 2's
merge rolled back; the message now names the Loop setting, loop_step b7423f1);
with "the later iteration wins" it is identical to serial, with a warning per
overwritten cell (in the log, not ``job.out`` -- a review point).
Left to clean up after the review: ``[tinkercliffs_phase6]`` in
``~/SEAMM_DEV/PaulVT.local.ini`` (backup ``PaulVT.local.ini.bak-2026-10-04-phase6``;
restart the services after); TinkerCliffs ``/projects/seamm/psaxe/phase6``;
SEAMM_DEV jobs 4013 and 4014 (project ``test``); local ``Testing/phase6/runs`` and
``~/SEAMM_DEV/venvs/phase6-B``.
Code review (design session, 2026-10-04) and fixes
--------------------------------------------------
The review verified the design and the driver's kill/retry/break/exit semantics,
and found merge defects the A/B and live runs had not reached (none looped over
several configurations of one system, or changed the shared current structure in
two iterations). Fixed:
- **M1** (molsystem): an unchanged row referring to a row outside the snapshot (a
system's default configuration) was rewritten to NULL. Now a reference to a row
the iteration did not create keeps its id, deferred updates are only for
changed rows, and the snapshot includes each selected system's default
configuration (so the system is usable in the child and a change of default is
visible; rewriting the default in the snapshot instead hid the iteration's
choice).
- **M2** (molsystem, loop_step): a loop over several configurations of one system
raised a spurious conflict (each iteration makes its configuration current).
``system.default_configuration`` is merged without a conflict, the last merged
change left; the Loop makes the iteration's configuration current before
merging it (the case the merge cannot see: back to the entry's default).
- **M3** (molsystem): two iterations changing the coordinates (velocities,
gradients, attributes, links) of one configuration were not detected; now a
conflict on the configuration. The note's claim that only whole-database
snapshots share structures was wrong: the current configuration is in every
snapshot.
- **M4** (seamm): ``Node.job_path`` falls back to ``root_directory`` for a
flowchart without ``job_directory`` (13 orca_step tests used such fakes).
- **S1** appends replayed as appends, as serially (two rows with one index
value), superseding the index rule; **S2** property definitions must agree in
type and units; **S3** ``Node.job_file`` walks the enclosing iterations
(``Flowchart.job_read_directories``, passed as ``SEAMM_JOB_READ``) and the
guide says a job-level file is read live; **S4** citations planned with the
job's counts in the committed plan, merged by setting counts, so a redo does not
double them; **S5** an iteration's ``OMP_NUM_THREADS`` is its share of cores,
not the pool's one per core asked for; **S6** tables re-exported and
``loop_entry.db`` removed after a break or *exit* too (kept after *stop the
job*, whose resume reruns the failed iteration); **S7** tables made in the body
are variables after the merge; **S8** ``loop_step/tests/test_parallel.py``: a
serial-vs-parallel run, black-box, with a fake step plug-in installed on
``PYTHONPATH`` (no external codes; ~40 s).
- Nits: URIs quoted; no NOT NULL without a default; later-wins warnings in
``job.out``; the user guide marks separate placement experimental and states the
start-up and whole-snapshot disk costs.
Pins, at release preparation: seamm_exec ``molsystem>=`` and ``seamm>=`` the phase
6 versions; loop_step now requires ``molsystem`` and ``seamm-exec`` (floors raised
to the phase 6 versions at release, with ``seamm``); read_structure_step
``seamm>=`` phase 6 (``job_file``); the other five steps bumped for consistency.
Answers to the review's questions: start-up cost documented, a warm evaluator per
bundle is phase 7 material; whole-database snapshots stay up front, the disk cost
documented.
Release (2026-10-05)
--------------------
All ten released as 2026.10.5, each PR reviewed by ``seamm`` and merged by Paul,
Releases cut in order, ``make update`` after each, and a clean PyPI-only venv
test before each PR: molsystem #123, seamm #224, seamm_exec #44, loop_step #40,
read_structure_step #84, table_step #99, properties_step #5, lammps_step #115,
orca_step #43, geometry_analysis_step #12. Snags: loop_step's PR CI failed twice
on macOS because the runner's restored uv cache still held PyPI's index page from
before seamm-exec 2026.10.5 (passed after ~10 minutes); properties_step's Release
run failed in the docs push to ``gh-pages`` (two pushes colliding), which skipped
the PyPI job -- rerunning the failed jobs published it. The last three steps
depend only on seamm, so their PRs went together (Paul). Phase 6 is opt-in; no
rollout until the next planned one.
Cleanup (2026-10-05, Paul OK): ``[tinkercliffs_phase6]`` removed from
``~/SEAMM_DEV/PaulVT.local.ini`` (restored from its backup) and SEAMM_DEV's services
restarted; TinkerCliffs ``/projects/seamm/psaxe/phase6`` (1.7 GB), local
``Testing/phase6/runs`` and ``~/SEAMM_DEV/venvs/phase6-B`` deleted. SEAMM_DEV jobs
4013 and 4014 are kept as the record of the live test; the specs and harnesses stay
in ``Testing/phase6``.