Skip to content

output.yml schema 1.3: export levels, provenance, atom maps, IRC mapping and correction switches - #1059

Open
calvinp0 wants to merge 8 commits into
mainfrom
feature_export_atom_corrections_applied
Open

calvinp0 wants to merge 8 commits into
mainfrom
feature_export_atom_corrections_applied

Conversation

@calvinp0

@calvinp0 calvinp0 commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

The defect

output.yml left a consumer to infer things ARC knew, or could have recorded, and in several places the inference was wrong.

Formation enthalpies. When Arkane has no atom-energy corrections (AEC) for the level of theory and data/AEC.yml has no entry either, ARC renders the Arkane input with useAtomCorrections = False (arc/statmech/arkane.py, render_arkane_input_template). RMG-Py then sets the atom correction to zero (arkane/statmech.py), so the species' h298_kj_mol, NASA a6/b6 and tabulated H/G are raw electronic energies, about −10⁵ kJ/mol per heavy atom, not formation enthalpies.

output.yml gave a consumer no reliable way to tell. An atom_energy record in energy_corrections looks like proof that AEC was applied, but its absence proves nothing:

  • The data/AEC.yml path applies AEC without writing a record.
  • Failed recomputations drop records silently.
  • Records were written from the key matched for arkane_level_of_theory, regardless of whether Arkane actually ran with corrections. With sp wb97xd/def2tzvp, a frequency level Arkane has no entry for and freq_scale_factor=None, get_arkane_model_chemistry returns None and Arkane runs uncorrected, yet output.yml still reported atom_energy and bond_additivity corrections as applied.

A downstream consumer (the TCKDB adapter) must label an enthalpy formation_298k only when it really is one, so it needs ARC to say so.

Everything else a consumer needs. The same gap exists elsewhere in the document:

  • The level of theory of the job behind each exported log was held nowhere. Under adaptive_levels, a reaction-wide override or troubleshooting it differs from the run-level setting, and a consumer had only that setting.
  • conformer_energies mixed units without saying so: a force-field energy is kcal/mol and a parsed electronic energy is kJ/mol. The docs also called them relative to the lowest conformer, which they are not.
  • The atom map did not state the species order it counts in, or where it came from, and nothing related a reactant or product atom to an atom of the TS geometry. No ARC TS method guarantees that the TS lists its atoms in reactant order.
  • reactant_labels and product_labels are sorted and de-duplicated, so HO2 + HO2 <=> H2O2 + O2 exported reactant_labels: [HO2] and a consumer could not balance the reaction.
  • The IRC check decided by graph isomorphism and then discarded which fragment of each endpoint geometry was which species.
  • TS frequencies were exported only in forms that drop the position of the reaction-coordinate mode, and a normal mode check forced to pass could not be told from a genuine pass.
  • No program was stated for conformer jobs, and thermo read from Arkane's output.py stated no standard-state pressure.
  • xtb_gsm staged its ograd script with a hard-coded --chrg 0 and no spin, so a charged or open-shell reaction was searched as a neutral closed-shell one.
  • The Gaussian adapter rewrote self.level.basis and self.level.method in place, so the level ARC recorded for a job no longer matched the level requested.

The change

OUTPUT_SCHEMA_VERSION goes from 1.1 to 1.3. The schema is closed and every key a consumer relies on is required but nullable, so a consumer can tell "ARC does not know" (null) from "not applicable" without probing for absent keys. ARC states only what it observed or what the run recorded, and never rebuilds a value from a level or from job.args. The new keys are required in records that disallow extra properties, so 1.1 and 1.2 validators reject 1.3 output. docs/output_yml_schema.md and arc/schemas/output_yml_schema.json describe every key.

Correction switches (the 1.2 keys). Each species' thermo gains three required keys:

thermo:
  atom_corrections_applied: true      # true | false | null
  bond_corrections_applied: false     # true | false | null
  atom_corrections_level:             # sp_level-shaped dict, or null
    method: bmk
    basis: cbsb7
    method_type: dft
    software: gaussian
  • The switches are the ones ARC actually rendered. The AEC/BAC decision is computed once in render_arkane_input_template and stored as use_aec / use_bac. The same values feed the Arkane template and the record, so they cannot disagree. parse_arkane_thermo_output stamps them only on species whose thermo this Arkane run wrote.
  • null means unknown, never guessed. This covers thermo loaded from an Arkane YAML (StatMechJob.load returns before any correction step), thermo not produced by this run, and older output.
  • true means only that the switch was on, not that H298 is a formation enthalpy. atom_corrections_level is the level the atom energies came from, so a consumer can compare it with the species' energy level. They differ when arkane_level_of_theory is a stand-in, as in examples/Stationary/bde/input.yml: bmk/cbsb7 corrections on apfd/def2svp energies.
  • Correction records follow the switches. atom_energy / bond_additivity records are dropped for a species whose switch was off. They are left unchanged when the switch is unknown.
  • The same switches travel with E0 and kinetics. They are stamped on the species' and the TS's E0 (statmech.e0_atom_corrections_applied, e0_bond_corrections_applied) and on the fitted kinetics (kinetics.atom_corrections_applied), since they decide what the E0 means. A species or reaction declared by an Arkane YAML is stamped null.
  • Startup warnings. ARC warns at startup when arkane_level_of_theory differs from the energy level: the composite method or sp level, and any adaptive sp/composite level. The comparison normalises method and basis with Arkane's own name handling, and folds dispersion into the method whether it is written in the method string (b3lyp-d3bj) or in the separate dispersion field (gd3bj, EmpiricalDispersion=GD3BJ). ARC also warns when solvated energy levels are paired with a gas-phase arkane_level_of_theory: gas-phase atom energies are then subtracted from solvated energies, so the results are not formation enthalpies.

The JSON schema enforces the couplings with if/then rules:

  • atom true requires a level;
  • atom false requires bond false and a null level;
  • atom null requires both others to be null.

docs/output_yml_schema.md states how a consumer should compare the two levels, and when a match still does not establish a formation enthalpy:

  • adaptive levels that vary the sp level;
  • a separate dispersion field;
  • solvation.

Kinetics are unaffected in the numbers: the same AEC decision applies to reactants, TS and products in one run, so AEC cancels in barriers, and kinetics never uses BAC.

Levels and programs. Each species and TS record carries levels (opt, freq, sp, composite, irc), the level of the job behind each exported log, as recorded by the Scheduler. The header carries adaptive_levels plus the requested scan, IRC, conformer opt, conformer sp, TS guess and GSM levels, each null unless a job of that type could have run at it. ess_software and ess_version are identified from the logs themselves, now for composite, IRC and scan jobs as well.

Conformers. Records export conformer_levels, conformer_energy_kind (force field kcal/mol or electronic kJ/mol), conformer_energy_level and conformer_force_field, and conformer_ess_software / conformer_ess_version, read from the optimization log that produced each conformer geometry (null for a force-field conformer). Conformer energies are absolute. A null energy means the conformer has no energy, and the energy kind, level and force field describe only the other entries.

Atom maps and IRC. A reaction exports atom_map with atom_map_reactant_labels and atom_map_product_labels (the order of the species it counts in), atom_map_source (declared or inferred) and atom_map_method; the map must be a permutation that conserves the elements or it is null. TS records export irc_participant_mapping (which atoms of each endpoint belong to which participant, whether the sides are distinguishable, and atom_order_matches_ts), irc_log_routes, irc_log_directions, irc_log_levels, and IRC endpoint species state irc_endpoint_of and irc_endpoint_direction. atom_order_matches_ts is true only when the chain and element order below are verified, false when one is contradicted, and null when unknown. TS frequencies are exported in ESS order as freq_frequencies_cm1_ess_order, and nmd_forced states whether the normal mode check was forced to pass. Reactions state their species once per occurrence, in get_reactants_and_products order, as reactant_species_labels and product_species_labels; both are null, not [], for a reaction that holds no species objects.

Reaction coordinate mode. reaction_coordinate_mode_index is the 1-based position, in freq_frequencies_cm1_ess_order, of the mode the normal mode check validated. It comes from the NMD record's mode_index, and is stated only when the record's n_modes equals the number of exported frequencies, the log the check parsed is the exported frequency log, and the check genuinely passed. A forced check leaves it null.

TS atom map. A reaction exports ts_atom_map, the TS atom of every reactant and product atom (method irc_endpoint_cgr_isomorphism), or ts_atom_map_unavailable_reason. ARC does not assume an order: it takes a label-preserving isomorphism between two condensed graphs of reaction, one over the reactant atoms with every bond of the reactants or products (labelled by which side it is on) and one over the TS atoms with the bonds of the two IRC endpoints. The isomorphism fixes the atoms whose bonds form or break and leaves only graph automorphisms free, so the reactant and product legs agree with atom_map by construction. The endpoint graphs come from the IRC verdict's own perceived fragments, tied to the endpoint geometry by exact coordinates; no distance limit is applied. The map is a constitutional (2D) correspondence: symmetry-equivalent atoms, diastereotopic ones included, follow a deterministic convention that prefers the identity. ts_atom_order_follows_reactants states whether the identity is a solution.

Taking endpoint atom i as TS atom i is verified from the logs, not assumed:

  • The first geometry of every IRC log (Gaussian's first input orientation, read by parse_irc_start_geometry) must be congruent with the TS geometry, atom for atom.
  • The optimization log of endpoint k must start from a geometry congruent with the last geometry of IRC log k, and the endpoint geometry the check receives must equal the final geometry of that optimization log.
  • Congruence is converter.kabsch with an RSSD of at most 1e-3 Å (print precision), proper rotations only, after the parsed geometry takes the isotopes of the reference. A mirror image is refused.
  • Both endpoints must have the element sequence of the TS.

What remains rests on the ESS keeping its input atom order within a log and on ARC's parsing and writing keeping it. The map is also null when a participant's exported geometry is not in the atom order of its molecule (same elements in each position and every bond within ARC's are_coords_compliant_with_graph limit), a check that can only turn a map into null. When no map is stated, the reason is one of: no_ts, irc_not_passed, irc_fallback_path, no_atom_map, endpoint_perception_mismatch, atom_map_contradicts_ts, species_atom_order_mismatch, irc_start_geometry_unavailable, irc_start_geometry_differs, irc_endpoint_geometry_differs, computation_failed or not_recorded.

Parsed properties and routes. The effective route keywords of each job are read from the log in preference to the input deck. A Gaussian composite job (CBS-QB3, G4) exports composite_step_routes, the route of each Link1 step in the log's order; composite_route stays the first entry. A TS exports neb_succeeded: true when the log of a recorded orca_neb guess prints THE NEB OPTIMIZATION HAS CONVERGED, false when such guesses were recorded with readable logs and none prints it, null otherwise, and never inferred from a geometry, neb_level or a success flag. sp_t1_diagnostic is the T1 that parse_t1 reads from the exported sp log, with no gate on the method name. The Gaussian dipole moment with its density and the Gaussian polarizability (read by fixed-width field) are exported.

rigid_rotor_kind. It is read from the exported point group alone, with no moment of inertia and no tolerance. Cubic groups are spherical tops. Groups with a proper C_n axis of n >= 3 are symmetric tops, and so are D2d and S_n with even n >= 4: an improper rotation S_n(θ) is the inversion times the proper rotation C(θ+π), and the inversion acts trivially on the inertia tensor, so S_n acts as a rotation of order 3 or more and forces two moments equal (allene is a prolate symmetric top). C1, Cs, Ci, C2, C2v, C2h, D2 and D2h are asymmetric tops, and C∞v and D∞h are linear. It is null without a point group, and with non-default isotopes, since the point group is that of the unlabelled geometry.

Isotopes. Isotope lists accompany a geometry only when the log of that geometry prints its masses (Gaussian thermochemistry jobs, including each conformer's own log); otherwise they are null. ARC's own geometry dicts are never read for them.

Kinetics and reactions. Kinetics carry Arkane's comment and a ts_validation marker for a rate from a TS that failed its IRC check. Reactions state reversible from the arrow; ARC writes every reaction with <=>, so it is always true.

Arkane identity and treatment. The header exports the identity of the tables Arkane loaded (rmg_database: path_kind, git_commit, version, quantum_corrections_sha256; a mismatch with the file under ARC's RMG_DB_PATH is logged, not exported). arc_aec_yml_sha256 is the SHA-256 of ARC's data/AEC.yml, recorded when an Arkane adapter renders atom energies from it, and carried with each E0 as e0_aec_yml_sha256, so the E0 computed by the TS check is covered. It is exported when exactly one distinct digest exists, and is null otherwise. Petersson components list the bonds Arkane skipped (skipped_components), and statmech states arkane_rotors_applied and arkane_treatment. A rotor's treatment is read from the mode Arkane holds and is null when that is not known. Thermo read from Arkane's output.py states a standard-state pressure of 101325 Pa (1 atm). Applied bond corrections require a bac_type in the schema.

Correction records. An atom_energy or bond_additivity record is kept only when its switch is true; a switch that is off or unknown states no row.

Removed keys. rmg_database.quantum_corrections_path, rmg_database.matches_arc_rmg_db_path and irc_participant_mapping.<side>.endpoint are not exported.

Commits

  1. 6337df90 level: record the level of a job as a plain dict. arc/level.py gains level_as_plain_dict, a deep-copied dictionary of plain types (the nested solvation scheme level is converted recursively, or dropped), so a recorded level survives a restart.yml round trip and a YAML dump. adaptive_levels_as_list converts the processed adaptive levels into the restart list form. set_recorded_level, set_recorded_irc_level and set_recorded_irc_log_level keep the per-species levels dictionary and the irc_levels list in step with the logs ARC stores; forward and reverse IRC logs are exported together, so a second log at a different level removes the record instead of stating either level.

  2. 10917979 species: track conformer provenance, atom map origin, NMD and IRC participant records. ARCSpecies keeps conformer_levels, conformer_energy_sources ({kind, level, force_field}) and conformer_logs in lockstep with conformers, padded with None for an older restart. conformers.py records which force field and backend produced the energies of a generation, and in which unit, so the name is stated only when exactly one force field, backend and unit did; RDKit's MMFF to UFF fallback is reported as UFF, and the UFF energy path no longer asks for MMFF properties. ARCReaction records the origin of its atom map (inferred with algorithm and family, declared, or None). ARCSpecies keeps e0_atom_corrections_applied, e0_bond_corrections_applied and arkane_rotor_modes next to e0, and copy_e0_from takes the switches with the E0. The NMD check records the position of the mode it analysed (mode_index), and a TS keeps ts_atom_map and its unavailable reason. A queue TS-guess job whose log yields no usable geometry records a failed, coordinate-less orca_neb guess, as the in-core job does.

  3. 09e2b03a ts: state IRC participants, endpoint atom order and the TS atom map, stage xtb_gsm. _assign_fragments_to_species replaces the boolean _match_fragments_to_species and returns the matching, which the IRC check uses to build irc_participant_mapping. The IRC check also builds ts_atom_map as described above and verifies the IRC start geometry and the endpoint chain; parse_irc_start_geometry is the new Gaussian parser, and the tolerance is IRC_START_GEOMETRY_TOLERANCE in arc/checks/common.py. A failure to build a record is logged and leaves the verdict, the participant mapping and the reaction's atom map untouched. The xtb_gsm ograd script becomes a template with @CHARGE@ and @UHF@ placeholders and an explicit --gfn 2, and the adapter refuses to stage when the charge or multiplicity is unknown. The Gaussian adapter applies its basis and method rewrites only to the values written to the input file and leaves the Level alone. The linear TS adapter uses the reaction's atom map state accessors.

  4. fb697a92 pipe: record the level of ingested tasks and the IRC direction. The ingest functions build a Level from the task spec and record it the way the local scheduler does: the conformer geometry level and its log, the conformer energy source (electronic, kJ/mol), the freq level, and the IRC level and direction of each IRC log. A conf_opt task whose geometry parses but whose energy does not sets the energy to None instead of keeping the force-field energy.

  5. 30dd1320 scheduler: record the level behind each job's log, and require a charge for TS search jobs. The Scheduler records the level of the job whose log it stores in output[label]['levels'] and, for IRC, paths['irc_levels'], as an independent plain dict that round-trips through restart.yml. The level comes from the job that ran, not the run-level setting. A composite job's freq level is left unknown, and the sp record is restored after a solvation scheme's extra sp jobs. IRC endpoint species record the direction of their IRC job, and the IRC check receives the IRC logs (irc_log_paths) and the endpoint opt logs (endpoint_log_paths). switch_ts clears the TS's E0 together with its correction switches and Arkane rotor modes. A conformer whose optimization returned no geometry no longer overwrites the stored one or its energy. TS search jobs are not spawned for a reaction whose charge is unknown.

  6. 842a96d9 arkane: record whether corrections were applied, and match them on dispersion and solvation. Records the atom- and bond-correction switches ARC rendered into the Arkane input, and the level the atom energies came from, on each species' thermo, E0 and kinetics. ARC now writes the species' bonds into the Arkane species file, records which rotor modes the conformer block of the Arkane output holds, and derives the statmech treatment from them. get_qm_corrections.py reports the quantum_corrections/data.py path Arkane loaded. The adapter records the SHA-256 of data/AEC.yml when it renders atom energies from it, and process_arc_project returns the distinct digests. Correction keys (AEC, PBAC, MBAC) and data/AEC.yml are matched on a level's dispersion and solvation_method as well as method, basis, software and year (BREAKING, see below). Thermo read from output.py records the 1 atm standard state.

  7. 82aeb4ab output: export levels, provenance, atom maps, IRC mapping and parsed properties (schema 1.3). Bumps the schema and exports everything listed under "The change". The main driver passes the requested levels to the writer, reports an NEB level only when the run has a TS or a reaction and orca_neb is among the adapters the scheduler uses, and emits the startup level warnings. The schema file, the docs, the parser evidence version and its golden files follow.

  8. e1c2126e ARC.py: write the tckdb_arc upload's log records to arc.log. See the section below.

Correction matching: dispersion and solvation (BREAKING)

ARC's Arkane energy-correction matching (AEC, PBAC, MBAC and data/AEC.yml) now reads a level's dispersion and solvation_method. No existing match changes; this was checked against the previous matcher on every key in Arkane's database, with software, year and spelling variants. Two forms that matched nothing now match Arkane's B2PLYP-D3 entry: the Gaussian-style b2plyp-gd3 and b2plyp with dispersion: gd3.

A dispersion correction added to a separately parametrized "-D" functional (B97, ωB97X, ωB97M) matches none of that functional's variants. That holds whether it is given as a separate field or in Gaussian's g-prefixed spelling (b97-gd3). The functionals' own names (b97-d3, wb97x-d3, wb97xd) match as before.

BREAKING: some levels now get no Arkane energy corrections. Previously they silently got the gas-phase, non-dispersion ones. This covers:

  • levels with a solvation_method;
  • levels with a separate dispersion field for which Arkane has no dispersion-corrected entry, e.g. m062x with gd3.

Arkane's b3lyp2023/def2tzvp set was fitted on B3LYP-D3(BJ) (Wu et al., J. Phys. Chem. A 2024, 128, 4335) but is keyed as plain B3LYP. With the current RMG-database, plain b3lyp matches it and b3lyp + gd3bj gets no corrections; RMG-database #747 relabels it b3lypd3bj2023, after which the two swap. The dispersion-matching tests use a synthetic corrections file, so they pass with either database.

What happens next depends on the run:

  • Arkane thermo adapter with compute_thermo on (the default): ARC stops at startup. This includes restarting an existing project that used such a level. The message names the remedies: compute_thermo: false, or an explicit gas-phase / non-dispersion arkane_level_of_theory, which ARC then warns mixes levels. docs/source/advanced.rst describes them.
  • compute_thermo: false, e.g. rates only: Arkane runs with atom corrections off. That cancels in rate coefficients, and output.yml carries no correction records for those levels.

Frequency-key matching is unchanged. A missing frequency key drops the whole Arkane model chemistry, and with it the corrections of a correct gas-phase sp level.

arc.log records the TCKDB upload

tckdb_arc logs to its own tckdb_arc logger and attaches no handler, so during the post-run upload its INFO records were dropped and its warnings reached only stderr. A run whose TCKDB server was unreachable therefore ended arc.log at "Uploading ARC results to TCKDB …" without saying the upload failed or giving the tckdb-arc-upload command that recovers it. arc/common.py gains route_logger_to_arc_log(name, level=INFO), a context manager that passes a named logger's records to ARC's console and arc.log handlers for one block. It turns off that logger's propagation for the block so nothing is written twice, and restores the logger's handlers, level and propagation on exit, including on error. Each ARC handler's own level still applies, so a quiet run keeps INFO out of both outputs. run_tckdb_upload wraps the config parsing, adapter and sweep in it.

Behaviour changes outside output.yml

  • The Gaussian adapter no longer mutates the level. The def2- to def2 and cbs-qb3-paraskevas to cbs-qb3 rewrites apply only to the values written to the input file, so the Level ARC records for a job is the one requested.
  • xtb_gsm is staged with charge and spin, and refuses without them. The script passes --gfn 2, --chrg with the reaction charge and --uhf with multiplicity minus one. Before, every search ran as neutral and closed-shell.
  • TS searches need a known charge. The Scheduler spawns no TS search job, for any adapter, when the reaction charge is unknown, as it already did not for an unknown multiplicity. It is no longer only xtb_gsm that refuses.
  • switch_ts clears the TS E0, with its correction switches and Arkane rotor modes. Before, compute_rxn_e0 skipped a TS whose e0 was set, so the E0 check of the next guess reused the E0 of the abandoned guess. Resetting a species' E0 also resets the switches and rotor modes recorded with it, and resetting its conformers resets their provenance.
  • The pipe conf_opt energy is None when it is not parsed, instead of keeping the force-field energy that was there.
  • parse_conformer. A finished conf_opt job with no parseable geometry keeps the stored geometry and the force-field energy. On main it set both to None.
  • Conformer force fields. RDKit's MMFF to UFF fallback is reported as UFF, and the UFF path no longer first asks RDKit for MMFF94 molecule properties it does not use.
  • Arkane species file. ARC writes the species' bonds = {...} into it, so Arkane's BAC and the exported Petersson components use one bond dictionary.
  • Queue-path orca_neb. A queue-path orca_neb job whose log gives no usable geometry is recorded as a failed TS guess without coordinates, as the in-core job does. Before, nothing was recorded.
  • IRC check. The IRC check now reads the IRC logs (irc_log_paths) and the endpoint opt logs (endpoint_log_paths), and parse_irc_start_geometry is added to the parsers.

Tests

Results (serial, at e1c2126e):

Module Result
arc/output_test.py 523 passed
arc/output_schema_test.py 140 passed
arc/job/adapters/ts/orca_neb_test.py 8 passed
arc/checks/nmd_test.py 56 passed
arc/species/species_test.py 111 passed
arc/reaction/reaction_test.py 52 passed
arc/statmech/arkane_test.py 91 passed, 1 skipped (needs rmgpy)
arc/processor_test.py 3 passed
arc/main_test.py 55 passed
arc/checks/ts_test.py 82 passed
arc/parser/parser_test.py 116 passed
arc/parser/adapters/hessian_test.py 15 passed
arc/job/pipe/pipe_run_test.py 76 passed
arc/parser_evidence_test.py 39 passed
arc/level_test.py 23 passed
arc/species/conformers_test.py 70 passed
arc/job/adapters/gaussian_test.py 25 passed
arc/job/adapters/ts/xtbgsm_test.py 15 passed
arc/job/adapters/ts/linear_test.py 137 passed
arc/scheduler_test.py 200 passed
arc/arc_cli_test.py 16 passed
arc/common_test.py 73 passed

arc/output_schema_test.py drives the real writer over real ARCSpecies and ARCReaction objects and validates the document against arc/schemas/output_yml_schema.json; its negative cases assert that the schema rejects a missing key and each inconsistent combination.

Consumers

The TCKDB adapter (tckdb-arc; its 0.9.x branches) reads schema 1.3. It must treat a null species-label key (reactant_species_labels, product_species_labels) as "not stated", and read neb_succeeded. The adapter turns ts_atom_map into TCKDB's per-participant atom_to_ts by slicing it with the participants' atom counts in atom_map_reactant_labels / atom_map_product_labels order; TCKDB's validate_reaction_atom_map accepts the result for H-abstraction, A + A and degenerate reactions.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.05%. Comparing base (133b8c1) to head (e1c2126).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1059      +/-   ##
==========================================
+ Coverage   67.09%   68.05%   +0.96%     
==========================================
  Files         123      123              
  Lines       43663    44981    +1318     
  Branches    11152    11399     +247     
==========================================
+ Hits        29295    30613    +1318     
+ Misses      11220    11186      -34     
- Partials     3148     3182      +34     
Flag Coverage Δ
functionaltests 68.05% <ø> (+0.96%) ⬆️
unittests 68.05% <ø> (+0.96%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@calvinp0
calvinp0 requested a balanced review from Copilot September 29, 2026 12:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread arc/common_test.py Fixed
@calvinp0
calvinp0 force-pushed the feature_export_atom_corrections_applied branch 3 times, most recently from 7fcbdcd to bc731fb Compare October 1, 2026 05:49
@calvinp0 calvinp0 changed the title Record whether Arkane applied atom and bond corrections to each species' thermo output.yml schema 1.3: export levels, provenance, atom maps, IRC mapping and correction switches Oct 1, 2026
Comment thread arc/scheduler_test.py Fixed
Comment thread arc/scheduler_test.py Fixed
calvinp0 added a commit that referenced this pull request Oct 1, 2026
…fcbdcd..bc731fb)

Applies the delta between the old and the force-pushed head of PR #1059 onto arcbench; the earlier commits of that PR are already integrated here.

The delta contains: per-record levels and the header levels of the adaptive, scan, IRC, conformer and TS-guess jobs with their provenance (software, versions, routes, isotopes); reaction atom maps and the IRC participant mapping; parsed properties (T1 diagnostic, dipole moment, polarizability); thermo and kinetics atom and bond correction switches and the energy-correction components; the RMG database and AEC identity; the arkane follow-ups (dispersion and solvation key matching, synthetic-key tests); scheduler, species, conformer, vector, reaction, level, TS-check, xtb_gsm, pipe and Gaussian changes the export relies on; and the schema, docs and golden files for schema 1.3.

Conflicts with arcbench-only code were resolved by keeping both sides: the cost-metrics completed_job_records argument beside adaptive_levels in main.py and output.py, the cost-metrics tests beside the new tests, and the NMD undetermined-bonds test beside the new frequency test.
@calvinp0
calvinp0 force-pushed the feature_export_atom_corrections_applied branch from bc731fb to bbff27f Compare October 1, 2026 06:16
calvinp0 added a commit that referenced this pull request Oct 1, 2026
…sary lambdas, unclosed descriptor)

Mirrors the test changes folded into PR #1059 at bbff27f: the raise inside
route_logger_to_arc_log's block moves into a local function passed to
assertRaises, job_factory is patched with functools.partial instead of a
lambda, and the mocked mkstemp returns the descriptor of a real mkstemp
call, which the code under test closes.
@calvinp0
calvinp0 force-pushed the feature_export_atom_corrections_applied branch from bbff27f to ebc88ec Compare October 1, 2026 10:10
calvinp0 added a commit that referenced this pull request Oct 1, 2026
…ff27f..ebc88ec)

Adds reactant_species_labels and product_species_labels to exported reactions,
the TS-only nmd_forced flag, ARCSpecies.conformer_logs with the exported
conformer_ess_software and conformer_ess_version, the 101325 Pa standard-state
pressure fallback in arkane.py, the ts_atom_map condensed-graph-of-reaction
isomorphism in checks/ts.py with its constants and helpers in checks/common.py,
the schema and docs changes, and the _get_exported_xyz helper in output.py.
@calvinp0
calvinp0 force-pushed the feature_export_atom_corrections_applied branch from ebc88ec to bbd3861 Compare October 1, 2026 15:37
Comment thread arc/checks/ts.py Fixed
Comment thread arc/checks/ts.py Fixed
@calvinp0
calvinp0 force-pushed the feature_export_atom_corrections_applied branch from bbd3861 to be7da53 Compare October 1, 2026 17:23
calvinp0 added a commit that referenced this pull request Oct 1, 2026
…c88ec..be7da53)

Applies the PR delta: the TS atom map redesign (IRC start and endpoint congruence, endpoint graphs from the verdict's
fragments), composite step routes and neb_succeeded, nullable species labels, isotopes read only from logs, the rigid
rotor kind from the point group, fail-closed correction rows, the AEC.yml digest stamped with each E0, T1 from the sp
log, the NMD mode index, and parse_irc_start_geometry.

On arcbench, compute_rxn_e0 and check_ts also reset, copy and carry e0_aec_yml_sha256 alongside the E0 correction
stamps, the condensed-graph fallback of the IRC check is kept, and the helper that only served the superseded atom
order comparison is dropped.
calvinp0 added a commit that referenced this pull request Oct 1, 2026
The Scheduler runs check_charge_balance() after check_attributes() once it
has attached a reaction's species, so a reaction built from labels alone is
charge checked too (PR #1064). The IRC endpoint-chain check returns
irc_start_geometry_unavailable directly when its logs cannot be read
(PR #1059).
@calvinp0
calvinp0 force-pushed the feature_export_atom_corrections_applied branch from be7da53 to 7d31795 Compare October 1, 2026 17:41
output.yml states the level of theory of the job behind each exported log,
and that level has to survive a restart.yml round trip and a YAML dump. A
Level object cannot be stored as is, and a shallow as_dict() shares its
mutable values (args) with the level and can emit YAML anchors. arc/level.py
gains level_as_plain_dict, a deep-copied dictionary of plain types in which
the nested solvation_scheme_level is converted recursively, or dropped, and
any other keys can be stripped at every depth. adaptive_levels_as_list
converts the processed adaptive levels into the list form written to
restart.yml; the output commit switches the main driver to it.

set_recorded_level, set_recorded_irc_level and set_recorded_irc_log_level
keep the per-species levels dictionary and the irc_levels list in step with
the logs ARC stores. The solvation scheme's own level is not recorded, since
the energies of the scheme's extra jobs are not the exported ones. Forward
and reverse IRC logs are exported together under one key, so the first log
records its level and a second log at a different level removes the record:
the level of the pair is then undeterminable, and stating either level would
be wrong. Levels are compared without the fields output.yml does not state
for a job (repr, compatible_ess, software, solvation_scheme_level).
…ticipant records

Several things a consumer of output.yml needs were held nowhere, or held in a
form that hid where they came from. This commit gives the species and
reaction objects the attributes, and keeps them through restart.

Conformers. conformer_energies mixed units without saying so: a force-field
energy is kcal/mol and a parsed electronic energy is kJ/mol. ARCSpecies now
keeps conformer_levels (the level of the optimization job that produced each
geometry) and conformer_energy_sources ({kind, level, force_field}) in
lockstep with conformers, padded with None for an older restart or a
conformer added without a record. conformers.py records which force field
and backend actually produced the energies of a generation, and in which
unit, through a context variable, so the name is only stated when exactly one
force field, backend and unit did. RDKit's MMFF to UFF fallback is reported
as UFF, and the UFF energy path no longer asks for MMFF properties it does
not use.

Reaction atom map. ARCReaction records where its atom map came from:
'inferred' when map_reaction computed it (with the algorithm and family),
'declared' when the reaction dict carries an atom_map_source: declared
entry, and None when it is unknown. The setter records no
origin, and the state is saved to and restored from the reaction dict. The
docstring now states the atom order the map counts in: the species of
get_reactants_and_products, once per occurrence.

Arkane and TS records. ARCSpecies keeps e0_atom_corrections_applied,
e0_bond_corrections_applied and arkane_rotor_modes next to e0, and
copy_e0_from takes the switches together with an E0 from another species, so
a borrowed E0 is never paired with switches from a different Arkane run. The
NMD check records the 0-based position of the mode it analysed in the
frequencies it parsed (nmd_record['mode_index']), and the IRC check's
participant mapping is stored on the TS species.

Conformer log provenance. ARCSpecies keeps conformer_logs, the path of the
conformer optimization log that produced each conformer geometry, in
lockstep with conformer_levels and conformer_energy_sources: padded with
None for an older restart, extended with None for force-field and user-
supplied conformers, reset with the rest of the provenance, and saved to and
restored from the species dict. record_conformer_geometry_level takes the
log path beside the level.

TS atom map. A TS species keeps ts_atom_map (the TS atom of every reactant
and product atom of its reaction) and ts_atom_map_unavailable_reason (why it
holds none), saved, restored and cleared with the other IRC check records.

A queue TS-guess job whose log yields no usable geometry now records a
failed, coordinate-less orca_neb guess, as the in-core job does, so that a
failed NEB reads the same on both paths.
…stage xtb_gsm

The IRC check decided by graph isomorphism but discarded which fragment of
each endpoint geometry was which species. _assign_fragments_to_species
replaces the boolean _match_fragments_to_species: it returns the matching
(for each fragment, the index of the expected species it matched, or None
when there is no one-to-one matching), and the check uses it to build
ts_species.irc_participant_mapping: for the reactant and product side, the
endpoint it matched and, per participant (repeated species included, one
entry per occurrence), the 0-based atom indices into the endpoint geometry.
The mapping also states whether the reactants and products are graph-
isomorphic to each other (when they are, which endpoint is called the
reactants is a convention) and whether the IRC geometries follow from the TS
geometry (see below), which is what makes atom i of an endpoint atom i of
the TS. A failure to build it is logged and leaves the verdict
untouched. A check that is forced to pass is marked by the forced flag of
nmd_record, and the record is cleared with the rest of the TS checks.

xtb_gsm staged its ograd script with a hard-coded --chrg 0 and no spin, so a
charged or open-shell reaction was searched as a neutral closed-shell one.
The script is now a template with @charge@ and @UHF@ placeholders (and an
explicit --gfn 2) that the adapter fills with the reaction charge and
multiplicity minus one, and it refuses to stage when either is unknown. The
script is executable, and the staged copy is made executable too.

The Gaussian adapter rewrote self.level.basis and self.level.method in
place (def2- to def2, cbs-qb3-paraskevas to cbs-qb3), so the level ARC
recorded for a job no longer matched the level requested. It now applies the
same rewrites only to the values written to the input file and leaves the
Level alone. The linear TS adapter uses the reaction's atom map state
accessors, so that restoring a snapshot also restores the map's source and
method.

The IRC check also states which TS atom each atom of the reaction is, as
ts_species.ts_atom_map. No ARC TS method guarantees that the TS lists its
atoms in reactant order, so the map comes from a label-preserving
isomorphism of two condensed graphs of reaction (a constitutional, 2D
correspondence): one over the concatenated reactant atoms, with an edge for
every bond of the reactants or the products, labelled by which of the two it
is in, and one over the TS atoms, whose bonds are the connectivity of the
two IRC endpoints taken from the molecule fragments the IRC verdict itself
perceived (element and connectivity only, and refused unless each fragment
atom carries the coordinates of the endpoint geometry exactly). The identity
is preferred when it is a solution, otherwise the first match found is
taken, so symmetry-equivalent atoms follow a deterministic convention that
is not canonical across networkx versions; the endpoint roles are swapped
when the reactants and products are isomorphic.

Taking endpoint atom i as TS atom i is verified from the logs, not assumed.
The first geometry of every IRC log must be the TS geometry atom for atom
(Gaussian's first input orientation, read by the new
parse_irc_start_geometry; no other program's IRC log is read): the same
elements in order and, with no permutation of atoms, the same coordinates
after a proper rotation and translation (kabsch, after giving the parsed
geometry the isotopes of the reference), within a root-summed-squared
deviation of 1e-3 Angstrom that covers print precision
(IRC_START_GEOMETRY_TOLERANCE in arc/checks/common.py). The chain continues
to the endpoints: the optimization log of endpoint k starts from a geometry
congruent with the last geometry of IRC log k (the endpoint species is made
from that geometry), and the endpoint geometry the check receives is exactly
the final geometry of that optimization log, which the scheduler now passes
by path. Both endpoints must also have the element sequence of the TS. What
remains rests on the ESS keeping its input atom order within a log and on
ARC's parsing and writing keeping it. No distance limits are applied. The
fragments of the IRC verdict are tied to the endpoint atoms by exact
coordinate equality, so a perception that reorders their atoms still maps.
Each way of not stating a map has its own reason, including an unreadable or
differing IRC start geometry (irc_start_geometry_unavailable and
irc_start_geometry_differs) and a differing endpoint chain
(irc_endpoint_geometry_differs), and a failure is logged and leaves the
verdict, the participant mapping and the reaction's atom map untouched.
A piped job left no trace of the level it ran at: conf_opt and conf_sp
ingestion overwrote the conformer geometry and energy and stated neither
level nor energy kind, and the freq and IRC ingestion recorded paths only.
The ingest functions now build a Level from the task spec and record it the
way the local scheduler does, through the helpers in arc/level.py: the
conformer geometry level, the conformer energy source (electronic, in
kJ/mol, with the task's level), the freq level, and the IRC level together
with the level and direction of each IRC log, kept in lockstep with the IRC
paths.

A conf_opt task whose optimized geometry parses but whose energy does not
now sets the energy to None with no recorded kind, instead of keeping the
force-field energy that was there, which would otherwise be reported as
belonging to the optimized geometry.

Ingesting a conf_opt task also records its output log, the conformer
optimization log that produced the geometry, beside the level.
@calvinp0
calvinp0 force-pushed the feature_export_atom_corrections_applied branch from 7d31795 to d31ab7a Compare October 1, 2026 21:34
calvinp0 added a commit that referenced this pull request Oct 1, 2026
A static method on Scheduler reached through the pipe coordinator test's
real-method stub was bound with the stub as an extra argument, so piped freq
finalization raised TypeError there. The helper does not use the Scheduler, so
it now lives at module level, as in PR #1059.
…ge for TS search jobs

output.yml states the level of theory of the job whose log it exports, so
the Scheduler now records it where it stores the log path, in
output[label]['levels'] (opt, freq, sp, composite, irc) and, for IRC,
paths['irc_levels'] in lockstep with paths['irc']. The record is an
independent plain dict that round-trips through restart.yml, and an older
restart without the keys gets them created. The level comes from the job that
ran, not from the run-level setting, since those differ under adaptive
levels and after troubleshooting. A composite job's frequencies are at the
composite method's own geometry level, which the job's level does not name,
so the freq level of a composite log is left unknown. opt_level of a species
is likewise taken from the job. The sp record is restored after a solvation
scheme's extra sp jobs, together with the original path, the T1 diagnostic
and the electronic energy. Conformer jobs record the conformer geometry level
and the electronic energy source, and a conformer whose optimization returned
no geometry no longer overwrites the stored one or its energy.

IRC endpoint species record the direction of the IRC job they came from and
the check is passed the endpoint labels. switch_ts now clears the TS's E0,
together with its correction switches and Arkane rotor modes. Before this,
compute_rxn_e0 skipped a TS whose e0 was already set, so the E0 check of the
next guess reused the E0 of the abandoned guess. Resetting a species' E0 also
resets the correction switches and Arkane rotor modes recorded with it, and
resetting its conformers resets their provenance.

TS search jobs are not spawned for a reaction whose charge is unknown, as they
already were not for an unknown multiplicity, since xtb_gsm now states both in
its input.

Conformer jobs also record the conformer optimization log that produced the
stored geometry, beside its level.

The IRC check receives the paths of the two IRC logs, so that it can verify
they start from the TS geometry.
…spersion and solvation

Record on each species' thermo the atom- and bond-correction switches ARC
actually rendered into the Arkane input, and the level the atom energies were
taken from. Without them a consumer cannot tell a formation enthalpy from the
raw electronic energy Arkane reports when no atom energies exist for the
level, because a missing atom_energy correction record proves nothing.

thermo.atom_corrections_applied and bond_corrections_applied are the switches
of the Arkane run that produced the thermo, and are None when unknown (thermo
loaded from an Arkane YAML, not produced by this run, or an older output).
True means the switch was on, not that H298 is a formation enthalpy.
thermo.atom_corrections_level is the level the atom energies came from, in
the sp_level shape, so it can be compared with the species' energy level; it
differs when arkane_level_of_theory is a stand-in, as in the BDE example. The
same switches are stamped on the species' and the TS's E0 and on the fitted
kinetics, since they decide what the E0 means, and a species or reaction
declared by an Arkane YAML is stamped None because Arkane loads it as is.

ARC now also renders the species' bonds into the Arkane species file, records
which rotor modes (HinderedRotor, FreeRotor, HinderedRotor2D,
HinderedRotorClassicalND, and Mode, which is how Arkane writes a
multi-dimensional rotor) the conformer block of the Arkane output holds, and
derives from them the statmech treatment Arkane applied. get_qm_corrections.py
reports the quantum_corrections/data.py path Arkane loaded. The adapter
records the SHA-256 of ARC's data/AEC.yml (aec_yml_sha256s) at the moment it
renders atomEnergies from it into an Arkane input, and process_arc_project
returns the distinct digests of its adapters, so a consumer can identify the
correction tables behind a number without a level being matched again at
output time.

Match Arkane correction keys (AEC, PBAC, MBAC) and data/AEC.yml on a level's
dispersion and solvation_method as well as its method string, basis, software
and year. A canonical dispersion is folded into the method, so a dispersion
field behaves exactly like the same dispersion written in the method string. A
dispersion added to a separately parametrized "-D" functional (B97, wB97X,
wB97M) matches none of that functional's variants. A solvated level matches
nothing, since Arkane has only gas-phase correction sets; such levels follow
ARC's existing no-correction path, and the startup error names the remedies,
which docs/source/advanced.rst now describes. Frequency-key matching is
unchanged: a missing frequency key drops the whole Arkane model chemistry, and
with it the corrections of a correct gas-phase sp level.

BREAKING: a level with a solvation_method, or with a separate dispersion field
for which Arkane has no dispersion-corrected entry, now gets no Arkane
corrections. With compute_thermo on (the default), ARC stops at startup for
such a level, which includes restarting an existing project that used one. The
error names the remedies: set compute_thermo to False, or set an
arkane_level_of_theory that Arkane has corrections for.

No existing match changes (checked against the previous matcher on every
Arkane key with software, year and spelling variants); b2plyp with a gd3
suffix or field now matches Arkane's b2plypd3 entry.

Thermo that is read from the Arkane output.py because no thermo.yaml is
available records a standard-state pressure of 101325 Pa (1 atm), the
constant RMG's translational partition function applies. A thermo that no
Arkane run produced still records none. parse_species_thermo reports whether
it parsed a thermo block.
…properties (schema 1.3)

Bump the output.yml schema to 1.3. The schema is closed and every key a
consumer relies on is required but nullable, so a consumer can tell "ARC
does not know" (null) from "not applicable" without probing for absent keys.
ARC states only what it observed or what the run recorded, and never
rebuilds a value from a level or from job.args.

Levels and programs. Each species and TS record carries levels, the level
of the job behind each exported log (opt, freq, sp, composite, irc), as
recorded by the Scheduler, and the header carries adaptive_levels plus the
requested scan, IRC, conformer opt, conformer sp, TS guess and GSM levels,
each null unless a job of that type could have run at it. The GSM level and
program are read from the archived xtb outputs, and ess_software and
ess_version are identified from the logs themselves, now for composite, IRC
and scan jobs as well. Conformers export their geometry levels, the energy
kind (force field kcal/mol or electronic kJ/mol), the level of an electronic
energy and the force field, and conformer energies are absolute.

Atom maps and IRC. A reaction exports its atom map with the order of the
species it counts in, its source (declared or inferred) and method; the map
must be a permutation that conserves the elements, and the exported geometry
of every participant must follow the atom order of its mol, or it is null. TS records
export the IRC participant mapping (which atoms of each endpoint belong to
which participant, whether the sides are distinguishable, whether the atom
order follows the TS), IRC log routes, directions and levels, and IRC
endpoint species state the TS they belong to and their direction. TS
frequencies are exported in ESS order with the index of the reaction
coordinate mode, which is the position the NMD check recorded for the mode it
analysed, given only when the number of modes the check indexed equals the
number of exported frequencies and the log it parsed is the exported
frequency log, and the NMD record states whether a check was forced.

Corrections. Thermo states whether atom and bond corrections were applied
and at which level, E0 and the kinetics state the same switches, and
an atom_energy or bond_additivity record is kept only for a switch
recorded as on: a switch that was off or is unknown states no row. The
identity of the tables Arkane loaded (git commit or package version, and the
SHA-256 of the file; a mismatch with ARC's RMG_DB_PATH is logged) is
exported, and so is the digest of ARC's own AEC.yml that an Arkane adapter
recorded when it rendered atom energies from it, collected from the final
processing and from the E0 of every exported species and TS (the TS check's
E0 carries the digest of its own adapter), which is null unless exactly one
distinct digest was recorded in the run. Petersson components
list the bonds Arkane skipped, and the Arkane treatment and rotor count
are stated. A rotor's treatment is read from the mode Arkane holds and is
null when that is not known.

Properties and routes. The rigid rotor kind is read from the exported point group
alone (cubic groups are spherical tops, groups with a proper C_n axis of
n >= 3 are symmetric tops, and so are D2d and S_n with even n >= 4, since an
improper rotation acts on the inertia tensor as the proper rotation by the
angle plus pi, the low-symmetry groups are
asymmetric tops, C-infinity-v and D-infinity-h are linear), with no moment of
inertia and no tolerance, and is null without a point group or for an
isotopically labelled species. Isotope lists are exported only from logs
that state the masses (the log of the exported geometry, and each conformer's
own log) and are null otherwise; ARC's own geometry dicts are never read for
them. The effective route keywords of each job are read from the log in
preference to the input deck, and the T1 diagnostic the exported sp log
prints for its correlated method, the Gaussian dipole moment with its density, and
the Gaussian polarizability (read by fixed-width field) are exported.
Kinetics carry Arkane's comment and a ts_validation marker for a rate from a
TS that failed its IRC check, and reactions state their reversibility from
the arrow.

The main driver warns at startup when arkane_level_of_theory differs from the
energy level (composite method or sp level, and any adaptive sp/composite
level), comparing method and basis with Arkane's own normalisation and with
dispersion folded into the method, whether it is written in the method string
or in the separate dispersion field. A gas-phase Arkane level set for solvated
energy levels is warned about too. The driver also passes the requested levels
to the writer, and reports an NEB level only when the run has a TS or a
reaction and the orca_neb adapter is among those the scheduler uses. The
schema file, the docs, the parser evidence version and its golden files
follow.

Reactions state their species once per occurrence in reactant_species_labels
and product_species_labels, since reactant_labels is sorted and de-
duplicated, and TS records state nmd_forced, whether a check was forced to
pass. Species with conformers state the program and the version banner of
each conformer's own optimization log in conformer_ess_software and
conformer_ess_version. A null conformer energy means the conformer has no
energy, and the energy kind, level and force field describe only the other
entries.

A reaction exports ts_atom_map, the TS atom of each reactant and product
atom, a constitutional (2D) correspondence from an isomorphism of condensed
graphs of reaction (method irc_endpoint_cgr_isomorphism) whose TS-side bonds
come from the fragments the IRC verdict perceived, or the reason it cannot.
It is stated only after the first geometry of every IRC log is verified to
be the TS geometry atom for atom, each endpoint is verified to follow from
its IRC log through its optimization log, and the record is checked against
the reaction, the TS elements and the atom order of each participant's
exported geometry. That last check is ARC's are_coords_compliant_with_graph:
the geometry has the element of the molecule's atom in every position and
every bond of the molecule is no longer than 1.2 times the single-bond length
of its two elements, so it can only turn a map into null. The reason
species_atom_order_mismatch appears only when the TS has a geometry, its IRC
check passed and it recorded a TS map; otherwise the reason is no_ts,
irc_not_passed or the one the IRC check recorded. The reasons
irc_start_geometry_unavailable, irc_start_geometry_differs and
irc_endpoint_geometry_differs state the geometry checks. The
irc_participant_mapping flag atom_order_matches_ts is true only when those
checks pass and both endpoints have the element sequence of the TS, false
when one is contradicted, and null when unknown. Applied bond corrections, in
the thermo block or in the E0 of a species or TS, require a bac_type in the
schema, and thermo read from output.py states its standard-state pressure.

Composite steps and NEB. A Gaussian composite job (CBS-QB3, G4) exports
composite_step_routes, the route of each Link1 step in the order the log
echoes them, read by the same reader as composite_route, which stays the
first entry; nothing more is stated or deduced from it. A TS exports
neb_succeeded: true when the log recorded for an orca_neb guess (its own or a
merged source) prints THE NEB OPTIMIZATION HAS CONVERGED, false when orca_neb
guesses were recorded with readable logs and none prints it, null otherwise
(a guess with no recorded log states nothing), and never inferred from a
geometry, neb_level, a success flag or the configured adapters. reactant_species_labels and product_species_labels are
null, not an empty array, for a reaction that holds no species objects.
tckdb_arc logs to its own "tckdb_arc" logger and, as a library, attaches
no handler. ARC never routed that logger, so during the post-run upload
its INFO records were dropped and its warnings reached only stderr through
Python's last-resort handler. A run whose TCKDB server was unreachable
ended arc.log at "Uploading ARC results to TCKDB ..." with no word that
the upload failed or of the tckdb-arc-upload command that recovers it.

arc/common.py gains route_logger_to_arc_log(name, level=INFO), a context
manager that hands a named logger's records to the ARC logger's handlers
(console and arc.log) for one block, stops it propagating for that block
so nothing is written twice, and restores its handlers, level and
propagation on exit, including on error. run_tckdb_upload wraps the
config parsing, adapter construction and sweep in it. Each ARC handler's
own level still applies, so a quiet run keeps INFO out of both outputs.

The sweep returns None and prints its per-kind summary to stdout, so ARC
has no outcome to summarise into arc.log beyond what is now routed.
@calvinp0
calvinp0 force-pushed the feature_export_atom_corrections_applied branch from d31ab7a to e1c2126 Compare October 1, 2026 22:36

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants