CREST Adapter#807
Conversation
d6a83b4 to
c231a43
Compare
3e88c36 to
7674f5f
Compare
9c25f3d to
51368be
Compare
9be935e to
eab7647
Compare
…by enum)
The backend hoists parameters_json.tckdb_origin.origin_kind into the validated
CalculationWithResultsPayload.origin_kind enum {executed, reused_result, imported,
derived} — proven by reused_result round-tripping while screened_conformer 422s
under identical nesting. So parameters_json is not opaque for this key; the
"free-form" assumption was wrong.
_screened_conformer_origin (alt/screened conformer opt rows: converged geometry +
relative energy from the conformer screen, no independently executed ESS job) emitted
origin_kind='screened_conformer', which is not an enum member, so every computed-species
bundle containing an alt conformer 422'd. Map it to 'derived' (a screened conformer's
geometry is derived from the conformer screen, not independently executed) and preserve
the ARC-specific 'screened_conformer' distinction verbatim under a new origin_detail key
(backend-opaque, like reason/producer). The selected conf0 is unmarked (real executed
opt) and unaffected; reused_result rows unchanged.
Add VALID_TCKDB_ORIGIN_KINDS + schema-conformance guard tests that walk each generated
payload and assert every origin_kind (at any depth) is a valid enum member, so this
class of 422 cannot recur silently.
…s_gcn hook Unpinned 'pip install torch-geometric' pulled 2.6.x (needs torch>=2.0, imports torch.utils._pytree absent in the pinned torch 1.7.1) -> TS-GCN ModuleNotFoundError. Pin to the torch-1.7.1-compatible 1.7.2. Also export LD_LIBRARY_PATH=$CONDA_PREFIX/lib in the activation hook so scipy finds the conda libstdc++ (GLIBCXX_3.4.29) instead of an older system /lib64 one (surfaced on zeus once the torch-geometric import was fixed).
…stion A transient TCKDB server not-ready state during a long ARC run made the in-run upload fail its one-shot /readyz preflight, dropping the reaction upload (payload was written; a manual CLI re-run succeeded once the server was up). _ensure_ready now retries the probe up to PREFLIGHT_MAX_ATTEMPTS (5 = 1 initial + 4 retries) with exponential backoff (PREFLIGHT_BASE_DELAY_SECONDS * 2**i, capped at PREFLIGHT_MAX_DELAY_SECONDS -> 1,2,4,8s, ~15s worst case), retrying both failure modes (a TCKDBError request exception and a 200 body that reports not-ready), and returns on the first ready response. The single-check-per-adapter-instance contract is preserved (the loop runs once; the final error is cached in _preflight_error). Backoff sleeps route through _preflight_sleep so tests patch them out. On genuine exhaustion, _record_failure now calls _log_readiness_recovery for a TCKDBReadinessError, logging that payloads were written and the exact copy-pasteable re-run command (python -m arc.tckdb.cli <input.yml> -p <project> --upload-mode <mode>) using the real project dir / upload mode in scope.
Fills the "TS-specific adapter not yet implemented" gap: converged transition states are no longer deferred — a new computed_ts upload mode composes a TransitionStateUploadRequest and POSTs each converged TS to /uploads/transition-states. - adapter.py: submit_computed_ts_from_output + _compose_transition_state_request + _ts_calc_to_standalone + _build_ts_reaction_upload. Reuses _build_ts_block (results stay wrapped as opt_result/freq_result/... = the CalculationWithResultsPayload shape), the geometry/identity fragments, correction schemes, origin markers, and idempotency. primary_opt is the type=opt calc (backend validator); additional_calculations carry freq/sp/irc/path_search. Bundle-only keys stripped for the extra=forbid fragments. - sweep.py: _run_ts_sweep uploads each transition_states entry whose own `converged` flag is true (same gate as species), maps TS->reaction via ts_label, skips non-converged and TSs with no referencing reaction (never fabricates a reaction). - config.py: UPLOAD_MODE_COMPUTED_TS; CLI picks it up from VALID_UPLOAD_MODES. Review (Fable) validated a REAL reaction_05 payload against the REAL backend pydantic schema on the Pi (construction only, no POST): primary_opt=opt, freq/sp/irc attached, origin_kind valid, no leaked keys. Sub-critical fixes applied: standalone path never builds/inlines artifacts (backend has no slot) and warns once if artifacts.upload is set; applied_energy_corrections omission documented + debug-logged; whitespace-only reaction_family gated out (mirrors backend normalization) to avoid a 422. Follow-up (documented): replace the test's reconstructed TransitionStateUploadRequest with a real tckdb_schemas import once the package publishes it.
The GSM stringfile has no per-frame energies (comment lines are all 0.000000,
verified across every benchmark stringfile), so path points were geometry-only.
Rather than fabricate a flat-zero energy profile, this populates path_coordinate
correctly and adds a guarded energy fallback for GSM builds that DO emit energies.
- parser.py: parse_gsm_stringfile_energies() — reads the per-frame comment energy
(relative kcal/mol) or None; parse_trajectory untouched (its callers unaffected).
- adapter.py _build_path_search_result_payload:
* path_coordinate = cumulative Kabsch-ALIGNED inter-node displacement in Angstrom
(the analog of ORCA NEB's Dist.(Ang.) the backend column stores). GSM does not
keep every node in a common frame — the reactant endpoint is often translated/
rotated vs the interior, which would inflate a raw displacement (~1.56 A vs
~0.15 A aligned for node 0->1) — so each node is aligned onto its predecessor
(converter.kabsch) before summing. Node 0 = 0.0; null everywhere if alignment
fails. This replaces an earlier index/(N-1) fraction, which mislabeled a shared
column whose ORCA rows hold Angstroms.
* guarded stringfile relative-energy fallback: only when NO per-node Hartree was
preserved AND the comment column spans > 1e-6 kcal/mol (not the all-zero
sentinel), avoiding a fabricated flat profile and source-mixing with partial
Hartrees; flags the peak is_climbing_image.
The real per-node energy gap is producer-side (GSM stdout / gsm_node_outputs not
preserved on the run) and tracked separately. Parser=stays / adapter=extractable.
The running_jobs.yml status snapshot previously listed only bare job_names
(e.g. 'tsg3'), forcing a cross-reference against arc.log to find the job on
the cluster by its qstat name (e.g. 'a3129'). Add
Scheduler._running_job_snapshot_entry, which resolves each running job_name
back to its Job object in job_dict and emits a self-describing entry
{name, server_name (qstat job name), job_id, adapter, server}. Unresolvable
entries fall back to {'name': job_name}. report_running_jobs_snapshot now
builds running_jobs as {label: [entry, ...]}.
GSM's driver invokes the copied ograd gradient wrapper as ./ograd, which
requires the executable bit. write_input_file (incore) and set_files (queue)
copied ograd without +x (safe_copy_file/copyfile drops mode, tracked template
was 100644), so ograd landed 644 and GSM aborted with permission denied.
- incore: add change_mode('+x') on self.ograd_path
- queue: pass make_x=True on the ograd upload dict
- git: make the tracked ograd template 100755 (defense-in-depth)
- test: assert incore ograd is X_OK; flip set_files ograd expectation to make_x=True
When ARC's TS-guess deduplication (ARCSpecies.cluster_tsgs) merges several guesses with the same geometry into one, it keeps a single primary `method` plus a merged `method_sources` list. Path-search provenance (GSM/NEB per-node energies and points) keyed off the SINGLE primary method, so when a geometry-only method (e.g. gcn) won dedup over an absorbed path-search source (xtb-gsm / orca_neb), the GSM/NEB log was dropped: `paths['gsm']` was never set in the scheduler and the TCKDB `path_search` calc was skipped. Additive fix (does NOT change which guess/method wins selection): - TSGuess gains `method_source_paths` (method -> log path). cluster_tsgs seeds it from each clustered guess's live method/log_path — the xtb_gsm/orca_neb/ qst2 adapters assign log_path *after* construction, so the dict is empty at clustering time and must be seeded there, not only in __init__. Persisted through as_dict/from_dict for restarts. - scheduler._ts_guess_path_provenance: primary method first, else the first path-search method in method_sources with a preserved log. Used at both output[label]['paths'] write sites. - output.py restart-fallback recovers a merged source's log into gsm_log/ neb_log, single-slot (first source wins) to match the scheduler. - tckdb adapter._resolve_ts_guess_path_search_for_record gates emission off the chosen guess's method_sources, preferring the method whose log field is populated so the gate and log lookup agree for multi-source merges.
…ints The xtb-GSM TS search writes per-node xTB outputs into gsm_node_outputs/ (0000.NN.xtbout), each carrying a real total energy, but those energies never reached the TCKDB path_search_result.points: the reader globbed *.energy files, while the patched ograd wrapper reliably preserves only *.xtbout (the .energy/.gradient copies depend on a tm2orca.py rename that usually doesn't land). So electronic_energy_hartree / relative_energy_kj_mol came out null despite the data existing on disk. - Add _parse_xtb_xtbout_energy: robustly parse the xTB total energy (Hartree) from an .xtbout file (accepts either the ':: total energy .. Eh' block or the boxed '| TOTAL ENERGY .. Eh |' banner, case-insensitive, last-match-wins; UTF-8 with errors='replace' so the non-ASCII 'Eh/α' glyph can't raise UnicodeDecodeError and abort the bundle build). - _read_gsm_node_outputs now prefers the cleaner Turbomole .energy file (with gradient), then falls back to .xtbout (energy only) for any node still missing. - Relax the relative-energy rule from strict all-or-none to: reference every point that has an absolute energy to min(known); points without one stay explicit nulls. Real GSM stringfiles have N+1 frames but only N node-energy files (the fixed reactant-anchor frame 0 has no ograd energy), so strict all-or-none suppressed the whole profile on all real data. Node label NN maps to stringfile frame index NN; frame 0 is the orphan. Confirmed on reaction_06: 14 energies parse, the -9.0206 Eh peak (node 8) lands exactly on the selected TS-guess frame, relatives reference the reactant well. Tests: .xtbout parsing (formats, garbled/missing -> None, non-UTF-8 bytes, Eh-unit gating, last-match), reader xtbout fallback + .energy precedence + mixed sources, and the real N+1-frame orphan-endpoint end-to-end shape.
…ilure compare_thermo ran the RMG/Arkane thermo script via execute_command, which returns stderr as a list of lines and does not surface the return code. RMG/Arkane write their normal INFO:root/WARNING:root startup logging to stderr even on a fully successful run, so `if len(stderr): logger.error(...)` emitted a false "Error while running RMG thermo script" on every reaction, despite output/RMG_thermo.yml being produced and consumed. It was a logger.error only (never raised), so the impact was cosmetic noise in arc.log. Fix, kept local to compare_thermo (execute_command's signature is unchanged): - Filter stderr, dropping benign `^(INFO|WARNING|DEBUG):root` lines and blanks; demote that chatter to logger.debug. - Only logger.error if real (non-log) error content remains OR the deliverable wasn't produced (gate on RMG_thermo.yml yielding species with h298/s298, since the return code isn't available here). A genuine failure (real traceback, or an unpopulated/missing deliverable) still logs an error. - Early-return when there are no species to compare (avoids a pointless RMG database load and a spurious error report). Adds focused unit tests: benign INFO/WARNING stderr + populated deliverable logs no error; a real traceback, and separately an unpopulated deliverable, each still log an error.
The backend StatmechInBundle / BundleStatmechIn now accept optical_isomers (int, ge=1); ARC already carries the value in output.yml (species[*].statmech.optical_isomers, from Arkane's conformer.opticalIsomers) but _build_statmech_block_for_species dropped it, copying only external_symmetry. Add the field with the same int>=1 guard so absent/non-int/0 are omitted. The shared builder covers both computed-species and computed-reaction bundle modes.
PayloadWriter.write atomically overwrote the canonical <label>.payload.json and its .meta.json sidecar on a re-run for the same label, destroying the prior payload plus the sidecar that held the last upload's status/idempotency_key/server IDs. The TCKDB backend appends on re-upload (old + new bundles resolved in review), so destroying the local prior bundle broke the trail mapping which local run produced which DB record. Before overwriting, move the existing payload+sidecar pair into an archive/ subdir under a self-describing name keyed off the prior sidecar's identity: uploaded_at if present (the accepted-record moment), else idempotency_key (the server's dedupe key), else the payload mtime, else a monotonic counter. The key is made filesystem-safe (colons stripped). Colliding archive names are disambiguated with a numeric suffix. Move-then-write ordering means a crash can never lose the prior payload (it is either canonical or archived, never gone). First writes archive nothing. Default-on via archive_previous=True; existing callers untouched. The canonical current path is unchanged, so upload/replay/discovery tooling is unaffected (no in-repo tooling globs the payload dirs; archive/ is a nested subdir external discovery must skip).
Open-shell/unrestricted single points carry an <S**2> spin-contamination
value that is needed to replicate/validate Arkane thermo for radicals and
biradicals. ARC parsed no such diagnostic and output.yml carried none, so
the TCKDB backend's ready spin_diagnostic slot went unfilled.
Parser: add parse_s_squared to the ESS adapter interface (default None on
the base class) with implementations for Gaussian (converged post-SCF
<S**2>=, plus "S**2 before annihilation ... after" -> s_squared_annihilated,
expected S(S+1) from the log's multiplicity), ORCA ("Expectation value of
<S**2>" + "Ideal value S*(S+1)" -> expected; no annihilation), and QChem
(<S^2> =; expected from the echoed $molecule multiplicity). Registered as
parser.parse_s_squared; s_squared_expected_from_multiplicity helper added.
Restricted/closed-shell logs print no <S**2> -> None.
output.yml: _parse_spin_diagnostic parses the sp log (falling back to
freq/opt when the sp energy reused the opt output), recomputing the ideal
S(S+1) from ARC's authoritative multiplicity; _spc_to_dict writes it as
sp_spin_diagnostic (None for closed-shell).
TCKDB emit: _spin_diagnostic_payload builds SpinDiagnosticPayload and
attaches it as the sp calc's spin_diagnostic in _calculation_payload (the
single funnel for all sp construction sites), only when s_squared was
parsed; closed-shell/absent -> block omitted (never all-null). ge=0 and the
required s_squared field are respected; optional companions omitted when
absent. Coupled-cluster wavefunction_diagnostic (T1/D1) deferred.
Tests: parser unit tests (Gaussian doublet/triplet, ORCA, QChem, closed-
shell None, malformed None) against real repo fixtures; output.yml plumbing
tests; TCKDB emit tests incl. full CalculationWithResultsPayload
model_validate against installed tckdb-schemas.
…ions) For an A+A reaction (two reactants that are the same species, e.g. OH + OH), Arkane's own save_thermo_lib raises DatabaseError on the R1==R2 duplicate at the very end of its run -- after output.py is written but before RMG_libraries/thermo.py is. ARC's save_arkane_thermo.py then returned early (it guards on that missing library file), so thermo.yaml was never written and parse_arkane_thermo_output left spc.thermo null: null thermo in output.yml and an empty project thermo library, despite Arkane having computed full NASA polynomials. Two independent fixes: 1. Robust reload (primary): when RMG_libraries/thermo.py is absent, save_arkane_thermo.py now reconstructs each species' NASA thermo directly from output.py (evaluated with the same rmgpy classes Arkane uses to read these files back) and produces thermo.yaml exactly as the library path would. This recovers thermo from ANY save_thermo_lib failure, not just A+A. Happy path unchanged: the library file is used when present. 2. Dedup at the source (secondary): _dedup_thermo_species_list collapses species with identical adjacency list + multiplicity into a single Arkane thermo block (full-thermo run only; kinetics and e0-only keep every block), so save_thermo_lib never sees a duplicate and never crashes. The collapsed duplicate label's thermo (and E0/symmetry) is copied from the representative in parse_arkane_thermo_output. The computed_reaction TCKDB payload was unaffected (it carries no NASA). Tests: A+A dedup + propagation and an all-distinct regression (arc_env); output.py reconstruction and library-precedence (rmg_env, run as a script).
# Conflicts: # arc/settings/settings.py
| if len(tokens) >= 2: | ||
| try: | ||
| multiplicity = int(tokens[1]) | ||
| except ValueError: |
Check notice
Code scanning / CodeQL
Empty except Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
To fix this without changing functionality, replace the empty except ValueError: pass with a non-empty handler that explicitly records intent. The safest minimal change is to assign multiplicity = None (no behavioral change since it is already None) and add a clarifying comment that malformed multiplicity lines are ignored intentionally.
Edit only arc/parser/adapters/qchem.py, in parse_s_squared(), around lines 163–166.
No new imports, methods, or dependencies are required.
| @@ -163,7 +163,9 @@ | ||
| try: | ||
| multiplicity = int(tokens[1]) | ||
| except ValueError: | ||
| pass | ||
| # Leave multiplicity as None if the echoed $molecule line is malformed. | ||
| # This keeps parsing robust while allowing s_squared parsing to proceed. | ||
| multiplicity = None | ||
| in_molecule = False | ||
| if s_squared is None: | ||
| return None |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # for 'common' and the module under test | ||
|
|
||
| try: | ||
| from rmgpy.thermo import NASA # noqa: F401 |
Check notice
Code scanning / CodeQL
Unused import Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
Use a module-level import check instead of importing an unused class.
Best fix: in arc/scripts/save_arkane_thermo_test.py, replace:
from rmgpy.thermo import NASA # noqa: F401
with:
import rmgpy.thermo
This preserves behavior (HAS_RMG reflects whether rmgpy.thermo is importable), removes the unused symbol, and avoids changing functionality.
| @@ -23,7 +23,7 @@ | ||
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # for 'common' and the module under test | ||
|
|
||
| try: | ||
| from rmgpy.thermo import NASA # noqa: F401 | ||
| import rmgpy.thermo | ||
| HAS_RMG = True | ||
| except ImportError: | ||
| HAS_RMG = False |
| # adapters support (see ts_adapters_by_rmg_family) but that RMG does not list as recommended | ||
| # (e.g. Intra_RH_Add_Endocyclic, XY_Addition_MultipleBond). Useful when running specific | ||
| # reactions across many families (e.g. a benchmark) rather than generating a mechanism. | ||
| rmg_family_set = 'default' |
Check notice
Code scanning / CodeQL
Unused global variable Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
To fix this without changing behavior, explicitly export rmg_family_set via __all__. This tells linters/static analyzers that the variable is intentionally part of the module’s public API.
Best change in arc/settings/settings.py:
- Add a module-level
__all__definition that includes'rmg_family_set'. - Place it near the
rmg_family_setassignment for clarity. - Do not rename or delete the variable, since it may be imported elsewhere and used as a documented setting.
No new imports, methods, or dependencies are needed.
| @@ -378,6 +378,7 @@ | ||
| # (e.g. Intra_RH_Add_Endocyclic, XY_Addition_MultipleBond). Useful when running specific | ||
| # reactions across many families (e.g. a benchmark) rather than generating a mechanism. | ||
| rmg_family_set = 'default' | ||
| __all__ = ['rmg_family_set'] | ||
|
|
||
| # Default environment names for sister repos | ||
| TS_GCN_PYTHON, TANI_PYTHON, UMA_PYTHON, AUTOTST_PYTHON, KINBOT_PYTHON, ARC_PYTHON, XTB, XTB_PYTHON, OB_PYTHON, RMG_PYTHON, RMG_PATH, RMG_DB_PATH = \ |
…ings)
- arc/statmech/arkane_test.py: TestRunArkaneOutputPySignal asserted a
'non-cosmetic lines' substring that run_arkane never emits. The advisory
WARNING it actually logs is 'Arkane emitted errors but still produced
output.py (proceeding): ...'. The code's output.py-existence success
signal and its logging are correct; assert the real substring.
- arc/main_test.py: TestARC::test_as_dict expected_dict omitted the qst2
ESS entry that arcbench's settings enable ('qst2': 'local'). Add
'qst2': ['local'] in alphabetical slot to match produced ess_settings.
A coordinate-less TS guess (e.g. a failed kinbot/queue guess, or a queue TS-search job whose .log produced no parseable geometry) reached almost_equal_coords(None, None) during cluster_tsgs, raising a TypeError that aborted the entire scheduler at init (benchmark reaction_08). - almost_equal_tsgs: if either guess's get_xyz() is None, return False instead of comparing coordinates. A guess with no geometry can never be a geometric duplicate. - process_completed_tsg_queue_jobs: when a queue .log yields no usable geometry, mark the guess failed and log a warning instead of silently dropping it, so it is never kept as a clusterable 'successful' guess. Adds regression tests covering both paths.
Register 'Disproportionation' in both TS-adapter gates for autotst: the adapter's own supported_families and ts_adapters_by_rmg_family. intra_H_migration was already enabled and is untouched. Note: Disproportionation requires the AutoTST env on a branch whose SUPPORTED_FAMILIES includes it (e.g. fix/disproportionation-support), not AutoTST main. Adds a focused test asserting both gates admit it.
| shutil.rmtree(path, ignore_errors=True) | ||
| try: | ||
| os.rmdir(self.project_dir) | ||
| except OSError: |
Check notice
Code scanning / CodeQL
Empty except Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 14 hours ago
The best fix is to replace the empty except with explicit handling that preserves current functionality (best-effort cleanup) while making intent clear and avoiding silent swallowing.
In arc/job/adapters/ts/autotst_ts_test.py, update _remove_test_dir so that:
os.rmdir(self.project_dir)remains in atry.except OSErrorbecomesexcept OSError as e.- Add an explanatory comment and only suppress expected race/cleanup conditions (
directory not empty/not found), re-raising unexpected OS errors.
This keeps tests robust under parallel execution and satisfies the static-analysis concern by avoiding an empty handler.
| @@ -44,8 +44,12 @@ | ||
| shutil.rmtree(path, ignore_errors=True) | ||
| try: | ||
| os.rmdir(self.project_dir) | ||
| except OSError: | ||
| pass | ||
| except OSError as e: | ||
| # Best-effort cleanup: under parallel execution the shared parent directory | ||
| # may already be removed or still contain other tests' subdirectories. | ||
| if not os.path.isdir(self.project_dir): | ||
| return | ||
| raise e | ||
|
|
||
| def get_adapter(self, dir_name: str) -> AutoTSTAdapter: | ||
| """A helper function to instantiate an AutoTSTAdapter instance.""" |
An impossible spin multiplicity (e.g. an even multiplicity for a species
with an even electron count) was forwarded all the way to the ESS, where
it surfaced as a cryptic non-retryable Gaussian GL301 InputError and
aborted the species after wasting a full run.
Enforce the parity relation
(total_electrons - net_charge) % 2 == (multiplicity - 1) % 2
in ARCSpecies once charge and multiplicity are finalized, raising a clear
SpeciesError that names the label, electron count, net charge, requested
multiplicity, and the nearest valid multiplicities. This is an inviolable
parity relation, so the guard has zero false positives. It is skipped
gracefully when the electron count is not yet known (e.g. a TS or an
xyz-less species with no perceived Molecule).
A fine transition-state optimization that dead-ends on the Gaussian l9999 "Optimization stopped" / "Number of steps exceeded" oscillation (classified as ['MaxOptCycles', 'GL9999']) was previously escalated only through the Hessian/step ladder (maxcycle=200 -> recalcfc=5 -> calcall -> RFO) and then abandoned via 'all_attempted'. opt=(cartesian) - wired until now only to the GL103/InternalCoordinateError path - was never tried, even though the failure is a redundant-internal-coordinate oscillation that Cartesian coordinates sidestep. This lost chemically-sensible TS guesses (e.g. arcbench reaction_16 C=C + HCl, heuristics-xy conformers 0/1). Insert opt=(cartesian) as an early rung in the MaxOptCycles ladder, right after the cheap maxcycle bump and before the expensive Hessian/algorithm escalation, gated tightly to the fine TS opt (is_ts and fine). Ground-state opts (is_ts=False) and coarse TS opts (fine=False) are unchanged, as is the GL103 path. Cartesian is recorded once (bare 'cartesian' marker, consistent with trsh_keyword_cartesian) and folded into the merged opt route so it is carried through the remaining rungs; the ladder still terminates via 'all_attempted' if Cartesian also fails. Also fixes a latent cartesian-loss in mixed GL103+MaxOptCycles histories, where the opt-route rewrite would previously clobber a standalone opt=(cartesian).
| seed = {'xyz': xyz, 'family': rxn.family} | ||
| constraints = get_wrapper_constraints(wrapper='crest', reaction=rxn, seed=seed) | ||
| self.assertIsInstance(constraints, dict) | ||
| self.assertTrue({'A', 'H', 'B', 'atoms', 'distance_pairs', 'angle_atoms'} <= set(constraints)) |
Check notice
Code scanning / CodeQL
Imprecise assert Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 9 hours ago
Replace the imprecise boolean assertion with a specific unittest assertion that compares sets directly.
Best fix in this file/region:
- In
arc/job/adapters/ts/heuristics_test.py, intest_get_wrapper_constraints_crest(around line 2328), change:self.assertTrue(expected_keys <= set(constraints))
toself.assertLessEqual(expected_keys, set(constraints))
This preserves test intent (expected keys must be a subset of actual keys) while yielding more informative failure messages that show the compared sets.
No new imports, helper methods, or dependencies are needed.
| @@ -2325,7 +2325,7 @@ | ||
| seed = {'xyz': xyz, 'family': rxn.family} | ||
| constraints = get_wrapper_constraints(wrapper='crest', reaction=rxn, seed=seed) | ||
| self.assertIsInstance(constraints, dict) | ||
| self.assertTrue({'A', 'H', 'B', 'atoms', 'distance_pairs', 'angle_atoms'} <= set(constraints)) | ||
| self.assertLessEqual({'A', 'H', 'B', 'atoms', 'distance_pairs', 'angle_atoms'}, set(constraints)) | ||
| self.assertEqual( | ||
| (constraints['A'], constraints['H'], constraints['B']), | ||
| constraints['angle_atoms'], |
…ny cores On small double-radical PESs (e.g. OH + OH -> H2O + O) the CREST H-abstraction $constrain block pinned only the A-H and H-B distances plus a weak positional restraint, leaving the A...B heavy-heavy separation and the A-H-B angle free. GFN2-xTB metadynamics then collapsed the linear A...H...B seed into a bent A-B minimum, which the post-CREST angle guard correctly rejected. Part 1: for the three-center H-abstraction path, also emit 'distance: A, B, auto' and 'angle: A, H, B, auto' in the $constrain block (xtb supports the angle keyword; see xtb_adapter.py). The XY four-center path (no angle_atoms) is left unchanged. Part 2: skip the CREST guess when the reactive core spans essentially the whole molecule (H_Abstraction with <=1 spectator atom, i.e. <=4 atoms total), where CREST has no conformational DOF to sample; defer to the other TS-search methods.
Addition of CREST Adapter that complements the heuristic adapter.
This pull request adds support for the CREST conformer and transition state (TS) search method to the ARC project, along with several related improvements and code cleanups. The most important changes include integrating CREST as a TS search adapter, updating configuration and constants, and enhancing the heuristics TS search logic for better provenance tracking and code clarity.
CREST Integration:
JobEnum(arc/job/adapter.py), included CREST in the list of adapters and RMG family mapping, and registered it as a default incore adapter (arc/job/adapters/common.py,arc/job/adapters/ts/__init__.py). [1] [2] [3] [4]arc/job/adapters/ts/crest_test.py).Makefile). [1] [2]Constants and Configuration:
angstrom_to_bohrconversion constant to both Cython and Python constants modules (arc/constants.pxd,arc/constants.py). [1] [2] [3]Heuristics TS Search Enhancements and Refactoring:
arc/job/adapters/ts/heuristics.py). [1] [2] [3] [4]arc/job/adapters/ts/heuristics.py). [1] [2] [3] [4] [5] [6] [7].