Skip to content

Fix guess keyword consistency for restricted TS routes - #973

Closed
calvinp0 wants to merge 1 commit into
mainfrom
fix_guess_mix_restricted_reference
Closed

Fix guess keyword consistency for restricted TS routes#973
calvinp0 wants to merge 1 commit into
mainfrom
fix_guess_mix_restricted_reference

Conversation

@calvinp0

Copy link
Copy Markdown
Member

This pull request refines the logic for adding the guess=mix keyword in Gaussian input files, ensuring it is only included for jobs that require an unrestricted reference. It also improves the transparency and test coverage for this behavior, particularly for transition state (TS) calculations. The changes include both code updates and expanded tests to verify correct handling of guess=mix and related logging.

Gaussian input file generation improvements:

  • Updated write_input_file in gaussian.py to only add guess=mix for jobs that require an unrestricted reference, and to log a message when it is omitted for restricted TS jobs. The logic now checks for existing checkfiles and job types more robustly, ensuring guess=mix is not added inappropriately.
  • Refactored the construction of the restricted_list to use the new restricted_flags variable for clarity and correctness.

Testing enhancements:

  • Added comprehensive tests in gaussian_test.py to verify that guess=mix is only present when appropriate, and that omission is logged exactly once for restricted TS jobs. This includes new test cases for singlet and doublet TS jobs, jobs with and without checkfiles, and standard species. [1] [2]
  • Updated expected input file outputs in multiple test cases to reflect the new logic, removing guess=mix from cases where it should not appear. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13]

Documentation and clarity:

  • Updated test docstrings and comments to clarify the new intended behavior for guess=mix handling, especially in troubleshooting scenarios.

These changes ensure that the Gaussian adapter generates input files with the correct guess keyword usage, improving both correctness and maintainability.

@calvinp0
calvinp0 force-pushed the fix_guess_mix_restricted_reference branch from ed3e5f4 to 0b3a26b Compare August 12, 2026 14:28
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.52%. Comparing base (7ae26d3) to head (958390f).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #973      +/-   ##
==========================================
+ Coverage   64.46%   64.52%   +0.06%     
==========================================
  Files         119      119              
  Lines       39636    39645       +9     
  Branches    10276    10280       +4     
==========================================
+ Hits        25550    25581      +31     
+ Misses      11102    11083      -19     
+ Partials     2984     2981       -3     
Flag Coverage Δ
functionaltests 64.52% <ø> (+0.06%) ⬆️
unittests 64.52% <ø> (+0.06%) ⬆️

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 added a commit that referenced this pull request Aug 19, 2026
Supersedes the predicate introduced by c2972c7 on this branch and the one
proposed in draft PR #973 (branch fix_guess_mix_restricted_reference). Three
versions of this chain existed: main emits guess=mix for every polyatomic
species with no checkfile; c2972c7 gated it on
`spc.is_ts or (multiplicity == 1 and number_of_radicals > 1)`; #973 hoisted
`is_restricted(self)` and gated on `not all(restricted_flags)`. This commit
replaces all three with a single predicate:

    elif any(spc.multiplicity == 1 and spc.number_of_radicals is not None
             and spc.number_of_radicals > 1
             for spc in self.species):

guess=INDO and guess=read keep their existing precedence and ordering.

Corrected Gaussian behaviour
----------------------------
g16 does NOT ignore guess=mix on a restricted reference - it applies it.
arc/testing/restart/1_restart_thermo/calcs/freq_a19031.out is a closed-shell
singlet (`9 alpha electrons  9 beta electrons`, route `#P guess=mix
wb97xd/def2tzvp`, no `u` prefix) whose L401 prints a single
`Mixing orbitals, IMix= 1 ... Coef=  7.07106781D-01  7.07106781D-01`, followed
by `IOpCl= 0` and a normal `E(RwB97XD)`. Across the fixture tree a restricted
reference prints ONE mixing line, an unrestricted one prints TWO (alpha
+0.7071, beta -0.7071), and a job without the keyword prints
`Harris functional ... diagonalized for initial guess`. So on a restricted
reference the keyword rotates HOMO into LUMO within the single orbital set: it
is not inert, it perturbs a good Harris guess for no benefit. #973's log text
"guess=mix only acts on an unrestricted reference" is therefore false and is
not carried over; no log line replaces it, because nothing both true and
useful can be said concisely at that point.

The removed comment block claimed the same thing ("serves no purpose for
restricted closed-shell species") and cited 426 doublet jobs with clean
<S**2> ~ 0.755 as evidence that doublets do not need mix. That evidence is
non-probative - a clean <S**2> cannot detect a converged wrong-STATE solution.
Both claims are dropped rather than reworded, since this project does not
carry inline comments on code lines.

Why is_restricted is not usable as the gate
-------------------------------------------
arc/job/adapters/common.py::is_species_restricted early-returns True for
force_field, composite and semiempirical method types BEFORE any multiplicity
check, so it answers "should the route omit the `u` prefix", not "is the
reference restricted". arc/testing/composite/TS0_composite_2043.out is the
counterexample: `Multiplicity = 2` at cbs-qb3 with no `u` in the route, yet
Gaussian prints two `Mixing orbitals` lines and `SCF Done: E(UB3LYP)`. A gate
built on is_restricted would call that species restricted while g16 runs it
unrestricted. The new predicate reads multiplicity and number_of_radicals
directly and never consults is_restricted.

The physical rule
-----------------
guess=mix is wanted only where the reference is genuinely unrestricted AND
Ms = 0, i.e. a bi-radical singlet. Only Ms = 0 retains an alpha-beta symmetry
left to break, which is Gaussian's own documented use case ("producing UHF
wavefunctions for singlet states"). For any Ms != 0 system Nalpha != Nbeta
already, so guess=mix degenerates into a pure spatial symmetry-breaking
request; for delocalised radicals (allyl, benzyl, NO2) that drives the classic
artificial-localisation artifact. Hence doublets, triplets and non-singlet
TSs - which c2972c7's `spc.is_ts` disjunct still admitted - no longer get it.

Honest consequence
------------------
number_of_radicals is user-input-only (arc/species/species.py: "inputted by
the user, ARC won't attempt to determine it"), so for any species where the
user did not declare it, the correct gate emits guess=mix never. That is
accepted deliberately: no heuristic is invented to derive number_of_radicals,
and is_species_restricted is left untouched - whether singlet TSs deserve an
unrestricted reference is a separate physics question under separate review.

Multi-species path
------------------
input_dict['job_type_1'] is rendered once and reused for every --link1--
section, while ${restricted} is substituted per species, so a route-wide guess
keyword lands on every section. This commit keeps the keyword route-wide and
collapses the predicate with any(), which is the honest collapse for
"does any species here need a broken-symmetry seed". The run_multi_species
path is knowingly not covered per species: job_type_1 is assembled route-wide
throughout (opt=, irc=, scf=, SCRF=, integral=, plus the "Fix OPT"/"Fix
SCF"/"Fix IRC" recombination that runs after this block), and making only
guess per-species would require restructuring that whole assembly - out of
scope here. The pre-existing `self.species[0].number_of_atoms > 1` guard,
which gates the block on species[0] alone, is likewise left as found.

Tests
-----
arc/job/adapters/gaussian_test.py::TestGaussianAdapterGuessMixGating now
covers: bi-radical singlet -> guess=mix; closed-shell singlet -> none;
doublet -> none; triplet -> none; singlet TS with number_of_radicals=None ->
none (#973's target); doublet TS -> none (c2972c7's target); bi-radical
singlet TS -> guess=mix; open-shell species at cbs-qb3 decided by multiplicity
(doublet -> none, bi-radical singlet -> guess=mix); checkfile -> guess=read;
trsh guess=INDO -> guess=INDO only. Assertions target the specific keyword
rather than whole-route goldens. #973's tests wrote check.chk into
ARC_TESTING_PATH/test_GaussianAdapter, which arc/job/adapters/common_test.py
rmtree's from a different module's tearDownClass (an intermittent failure
under `pytest arc/ -n 6 --dist worksteal`); the checkfile now lives in a
tempfile.mkdtemp() directory registered with addCleanup.

Note for the eventual PR: arc/job/adapters/gaussian.py and arc/job/trsh.py are
each touched by several commits on this branch, so it will need a pre-PR
squash to satisfy the one-file-one-commit rule. Not performed here.
…ence

guess=mix mixes the HOMO and LUMO of the alpha and beta sets so as to break
alpha-beta and spatial symmetry. A restricted reference has a single set of
doubly-occupied spatial orbitals, so there is nothing for the keyword to mix
and it cannot act.

Whether the reference is unrestricted is decided by is_species_restricted(),
which returns unrestricted only for multiplicity > 1 or a user-declared
number_of_radicals > 1. number_of_radicals is user-input-only and is None for
essentially every species, so a singlet TS is written with a restricted
reference (no 'u' prefix) while the route still carried guess=mix. Measured on
the running benchmark, 235 of 1058 TS routes carrying guess=mix were on a
restricted reference. The keyword only ever appears on the first job of a
chain; every later job finds a checkfile and uses guess=read instead.

The route is now self-consistent: the guess keyword is chosen from the same
is_restricted() result that decides the 'u' prefix, which is computed once and
reused for both. A restricted TS reports the omission once per job, because a
singlet TS being treated with a spin-pure reference is a decision worth seeing
in the log; a restricted non-TS is silent, since a closed-shell species on an
RKS reference is the expected treatment and logging it would add a line per
species per project.

This is a consistency fix only. It does not change which species run
unrestricted, and it does not change any SCF result: it removes a keyword from
routes where that keyword had no effect. Test expectations that enshrined
guess=mix on restricted (no 'u') routes are updated accordingly.
@calvinp0
calvinp0 force-pushed the fix_guess_mix_restricted_reference branch from 0b3a26b to 958390f Compare August 22, 2026 06:45
calvinp0 added a commit that referenced this pull request Aug 22, 2026
…ss=mix

Four defects in the route the Gaussian adapter renders, all of which made a
troubleshooting resubmit either a no-op or actively wrong. Tests live in
arc/job/adapters/gaussian_test.py and assert the specific keyword rather than a
whole-route golden wherever the route is otherwise unrelated.

The qc -> xqc upgrade could not be opted out of
    A requested scf=(qc) is upgraded to scf=(xqc), and an l508 failure is
    supposed to record 'no_xqc' to stop that. The check read
    'no_xqc' in self.args['trsh'].values(), which can never match the shape the
    scheduler builds ({'trsh': [keyword_list]}), so the opt-out never fired and
    the SCF ladder oscillated between qc and no_xqc. Decide from
    ess_trsh_methods instead.

int=(Acc2E=14) was silently dropped on non-fine jobs
    integral_algorithm was only emitted under `if self.fine`, so a non-fine opt
    or IRC whose only remedy was int=(Acc2E=14) produced a route byte-identical
    to the un-troubleshot one - a guaranteed no-op resubmit. Capture
    acc2e_requested and emit a bare integral=(Acc2E=14) through the route's fine
    slot for non-fine opt/IRC jobs. Fine jobs still fold Acc2E into the
    ultrafine grid, and non-fine jobs without the remedy are unchanged.

Two conflicting DFT integration grids in one route
    The InaccurateQuadrature remedy escalates to a finer grid recorded as
    'int=grid=NNNMMM', but a fine job also emits integral=(grid=ultrafine, ...).
    Both are the same Gaussian Int keyword, so the route carried two grid specs
    (ultrafine = (99,590) against the remedy's 300590 = (300,590)) and the finer
    grid was either rejected or silently overridden. Fold the remedy grid into
    the single integral=() keyword, replacing ultrafine, and drop the standalone
    int=grid= token. The grid is sourced from ess_trsh_methods so it persists
    while the ladder keeps escalating, and the non-fine opt/IRC path emits it
    too. job_18 loses its duplicated grid; job_20/21 now keep grid=300590
    instead of reverting to ultrafine; job_19, which has no grid remedy, is
    unchanged, confirming the change is scoped to InaccurateQuadrature routes.
    Relatedly, the base route's calcfc is dropped when the MaxOptCycles ladder
    supplies recalcfc/calcall, so opt=() never carries conflicting Hessian
    directives; the job_23/job_24 route fixtures are updated to the new ladder.

guess=mix is now gated on Ms = 0 broken symmetry only
    The adapter emitted guess=mix for every polyatomic species with no
    checkfile. The gate is now

        elif any(spc.multiplicity == 1 and spc.number_of_radicals is not None
                 and spc.number_of_radicals > 1
                 for spc in self.species):

    This predicate deliberately supersedes two earlier attempts. An earlier
    commit on this branch gated on `spc.is_ts or (multiplicity == 1 and
    number_of_radicals > 1)`; draft PR #973 hoisted is_restricted(self) and
    gated on `not all(restricted_flags)`. Both are replaced.

    g16 does not ignore guess=mix on a restricted reference - it applies it.
    arc/testing/restart/1_restart_thermo/calcs/freq_a19031.out is a closed-shell
    singlet (9 alpha, 9 beta electrons; route `#P guess=mix wb97xd/def2tzvp`,
    no `u` prefix) whose L401 prints a single "Mixing orbitals, IMix= 1 ...
    Coef= 7.07106781D-01 7.07106781D-01", then IOpCl= 0 and a normal E(RwB97XD).
    Across the fixture tree a restricted reference prints one mixing line, an
    unrestricted one prints two (alpha +0.7071, beta -0.7071), and a job without
    the keyword prints the Harris functional line instead. On a restricted
    reference the keyword rotates HOMO into LUMO within the single orbital set:
    it is not inert, it perturbs a good Harris guess for no benefit. #973's log
    text "guess=mix only acts on an unrestricted reference" is therefore false
    and is not carried over.

    is_restricted is not usable as the gate. is_species_restricted() early-
    returns True for force_field, composite and semiempirical method types
    before any multiplicity check, so it answers "should the route omit the `u`
    prefix", not "is the reference restricted".
    arc/testing/composite/TS0_composite_2043.out is the counterexample:
    multiplicity 2 at cbs-qb3 with no `u` in the route, yet Gaussian prints two
    mixing lines and SCF Done: E(UB3LYP). The new predicate reads multiplicity
    and number_of_radicals directly and never consults is_restricted, which is
    left untouched.

    The physical rule is that guess=mix is wanted only where the reference is
    genuinely unrestricted and Ms = 0, i.e. a bi-radical singlet: only Ms = 0
    still has an alpha-beta symmetry left to break, which is Gaussian's own
    documented use case ("producing UHF wavefunctions for singlet states"). For
    Ms != 0 systems Nalpha != Nbeta already, so guess=mix degenerates into a
    pure spatial symmetry-breaking request, which for delocalised radicals
    (allyl, benzyl, NO2) drives the classic artificial-localisation artifact.
    Doublets, triplets and non-singlet TSs - which the earlier `spc.is_ts`
    disjunct still admitted - therefore no longer get it.

    Honest consequence: number_of_radicals is user-input-only (ARC does not
    attempt to determine it), so where the user did not declare it the correct
    gate emits guess=mix never. That is accepted deliberately; no heuristic is
    invented to derive it. Whether singlet TSs deserve an unrestricted reference
    is a separate physics question under separate review.

    input_dict['job_type_1'] is rendered once and reused for every --link1--
    section while ${restricted} is substituted per species, so a route-wide
    guess keyword lands on every section. The keyword stays route-wide and the
    predicate collapses with any(), the honest collapse for "does any species
    here need a broken-symmetry seed". Making guess per-species in the
    run_multi_species path would require restructuring the whole route-wide
    assembly (opt=, irc=, scf=, SCRF=, integral=, plus the Fix OPT / Fix SCF /
    Fix IRC recombination that follows) and is out of scope. The pre-existing
    `self.species[0].number_of_atoms > 1` guard is likewise left as found.

    guess=read (checkfile) and guess=INDO (troubleshooting) keep their existing
    precedence and ordering.

    TestGaussianAdapterGuessMixGating covers: bi-radical singlet -> guess=mix;
    closed-shell singlet, doublet and triplet -> none; singlet TS with
    number_of_radicals=None -> none (#973's target); doublet TS -> none (the
    earlier predicate's target); bi-radical singlet TS -> guess=mix; open-shell
    species at cbs-qb3 decided by multiplicity; checkfile -> guess=read; trsh
    guess=INDO -> guess=INDO only. The checkfile fixture lives in a
    tempfile.mkdtemp() directory registered with addCleanup rather than in
    ARC_TESTING_PATH/test_GaussianAdapter, which another module's tearDownClass
    rmtree's and which caused intermittent failures under
    `pytest arc/ -n 6 --dist worksteal`.
@calvinp0

Copy link
Copy Markdown
Member Author

Superseded by #1006. That branch carries the same INDO → guess=read → guess=mix restructure, but gates guess=mix on multiplicity == 1 and number_of_radicals > 1 (bi-radical singlets — the only case with an α-β symmetry left to break) rather than not all(restricted_flags), which would still emit the keyword for every ordinary doublet and triplet. The two predicates conflict and the narrower one is the intended behaviour, so keeping both branches alive would only create a merge conflict over a decision already made.

@calvinp0 calvinp0 closed this Aug 22, 2026
calvinp0 added a commit that referenced this pull request Aug 23, 2026
…ss=mix

Four defects in the route the Gaussian adapter renders, all of which made a
troubleshooting resubmit either a no-op or actively wrong. Tests live in
arc/job/adapters/gaussian_test.py and assert the specific keyword rather than a
whole-route golden wherever the route is otherwise unrelated.

The qc -> xqc upgrade could not be opted out of
    A requested scf=(qc) is upgraded to scf=(xqc), and an l508 failure is
    supposed to record 'no_xqc' to stop that. The check read
    'no_xqc' in self.args['trsh'].values(), which can never match the shape the
    scheduler builds ({'trsh': [keyword_list]}), so the opt-out never fired and
    the SCF ladder oscillated between qc and no_xqc. Decide from
    ess_trsh_methods instead.

int=(Acc2E=14) was silently dropped on non-fine jobs
    integral_algorithm was only emitted under `if self.fine`, so a non-fine opt
    or IRC whose only remedy was int=(Acc2E=14) produced a route byte-identical
    to the un-troubleshot one - a guaranteed no-op resubmit. Capture
    acc2e_requested and emit a bare integral=(Acc2E=14) through the route's fine
    slot for non-fine opt/IRC jobs. Fine jobs still fold Acc2E into the
    ultrafine grid, and non-fine jobs without the remedy are unchanged.

Two conflicting DFT integration grids in one route
    The InaccurateQuadrature remedy escalates to a finer grid recorded as
    'int=grid=NNNMMM', but a fine job also emits integral=(grid=ultrafine, ...).
    Both are the same Gaussian Int keyword, so the route carried two grid specs
    (ultrafine = (99,590) against the remedy's 300590 = (300,590)) and the finer
    grid was either rejected or silently overridden. Fold the remedy grid into
    the single integral=() keyword, replacing ultrafine, and drop the standalone
    int=grid= token. The grid is sourced from ess_trsh_methods so it persists
    while the ladder keeps escalating, and the non-fine opt/IRC path emits it
    too. job_18 loses its duplicated grid; job_20/21 now keep grid=300590
    instead of reverting to ultrafine; job_19, which has no grid remedy, is
    unchanged, confirming the change is scoped to InaccurateQuadrature routes.
    Relatedly, the base route's calcfc is dropped when the MaxOptCycles ladder
    supplies recalcfc/calcall, so opt=() never carries conflicting Hessian
    directives; the job_23/job_24 route fixtures are updated to the new ladder.

guess=mix is now gated on Ms = 0 broken symmetry only
    The adapter emitted guess=mix for every polyatomic species with no
    checkfile. The gate is now

        elif any(spc.multiplicity == 1 and spc.number_of_radicals is not None
                 and spc.number_of_radicals > 1
                 for spc in self.species):

    This predicate deliberately supersedes two earlier attempts. An earlier
    commit on this branch gated on `spc.is_ts or (multiplicity == 1 and
    number_of_radicals > 1)`; draft PR #973 hoisted is_restricted(self) and
    gated on `not all(restricted_flags)`. Both are replaced.

    g16 does not ignore guess=mix on a restricted reference - it applies it.
    arc/testing/restart/1_restart_thermo/calcs/freq_a19031.out is a closed-shell
    singlet (9 alpha, 9 beta electrons; route `#P guess=mix wb97xd/def2tzvp`,
    no `u` prefix) whose L401 prints a single "Mixing orbitals, IMix= 1 ...
    Coef= 7.07106781D-01 7.07106781D-01", then IOpCl= 0 and a normal E(RwB97XD).
    Across the fixture tree a restricted reference prints one mixing line, an
    unrestricted one prints two (alpha +0.7071, beta -0.7071), and a job without
    the keyword prints the Harris functional line instead. On a restricted
    reference the keyword rotates HOMO into LUMO within the single orbital set:
    it is not inert, it perturbs a good Harris guess for no benefit. #973's log
    text "guess=mix only acts on an unrestricted reference" is therefore false
    and is not carried over.

    is_restricted is not usable as the gate. is_species_restricted() early-
    returns True for force_field, composite and semiempirical method types
    before any multiplicity check, so it answers "should the route omit the `u`
    prefix", not "is the reference restricted".
    arc/testing/composite/TS0_composite_2043.out is the counterexample:
    multiplicity 2 at cbs-qb3 with no `u` in the route, yet Gaussian prints two
    mixing lines and SCF Done: E(UB3LYP). The new predicate reads multiplicity
    and number_of_radicals directly and never consults is_restricted, which is
    left untouched.

    The physical rule is that guess=mix is wanted only where the reference is
    genuinely unrestricted and Ms = 0, i.e. a bi-radical singlet: only Ms = 0
    still has an alpha-beta symmetry left to break, which is Gaussian's own
    documented use case ("producing UHF wavefunctions for singlet states"). For
    Ms != 0 systems Nalpha != Nbeta already, so guess=mix degenerates into a
    pure spatial symmetry-breaking request, which for delocalised radicals
    (allyl, benzyl, NO2) drives the classic artificial-localisation artifact.
    Doublets, triplets and non-singlet TSs - which the earlier `spc.is_ts`
    disjunct still admitted - therefore no longer get it.

    Honest consequence: number_of_radicals is user-input-only (ARC does not
    attempt to determine it), so where the user did not declare it the correct
    gate emits guess=mix never. That is accepted deliberately; no heuristic is
    invented to derive it. Whether singlet TSs deserve an unrestricted reference
    is a separate physics question under separate review.

    input_dict['job_type_1'] is rendered once and reused for every --link1--
    section while ${restricted} is substituted per species, so a route-wide
    guess keyword lands on every section. The keyword stays route-wide and the
    predicate collapses with any(), the honest collapse for "does any species
    here need a broken-symmetry seed". Making guess per-species in the
    run_multi_species path would require restructuring the whole route-wide
    assembly (opt=, irc=, scf=, SCRF=, integral=, plus the Fix OPT / Fix SCF /
    Fix IRC recombination that follows) and is out of scope. The pre-existing
    `self.species[0].number_of_atoms > 1` guard is likewise left as found.

    guess=read (checkfile) and guess=INDO (troubleshooting) keep their existing
    precedence and ordering.

    TestGaussianAdapterGuessMixGating covers: bi-radical singlet -> guess=mix;
    closed-shell singlet, doublet and triplet -> none; singlet TS with
    number_of_radicals=None -> none (#973's target); doublet TS -> none (the
    earlier predicate's target); bi-radical singlet TS -> guess=mix; open-shell
    species at cbs-qb3 decided by multiplicity; checkfile -> guess=read; trsh
    guess=INDO -> guess=INDO only. The checkfile fixture lives in a
    tempfile.mkdtemp() directory registered with addCleanup rather than in
    ARC_TESTING_PATH/test_GaussianAdapter, which another module's tearDownClass
    rmtree's and which caused intermittent failures under
    `pytest arc/ -n 6 --dist worksteal`.
calvinp0 added a commit that referenced this pull request Aug 23, 2026
…ss=mix

Four defects in the route the Gaussian adapter renders, all of which made a
troubleshooting resubmit either a no-op or actively wrong. Tests live in
arc/job/adapters/gaussian_test.py and assert the specific keyword rather than a
whole-route golden wherever the route is otherwise unrelated.

The qc -> xqc upgrade could not be opted out of
    A requested scf=(qc) is upgraded to scf=(xqc), and an l508 failure is
    supposed to record 'no_xqc' to stop that. The check read
    'no_xqc' in self.args['trsh'].values(), which can never match the shape the
    scheduler builds ({'trsh': [keyword_list]}), so the opt-out never fired and
    the SCF ladder oscillated between qc and no_xqc. Decide from
    ess_trsh_methods instead.

int=(Acc2E=14) was silently dropped on non-fine jobs
    integral_algorithm was only emitted under `if self.fine`, so a non-fine opt
    or IRC whose only remedy was int=(Acc2E=14) produced a route byte-identical
    to the un-troubleshot one - a guaranteed no-op resubmit. Capture
    acc2e_requested and emit a bare integral=(Acc2E=14) through the route's fine
    slot for non-fine opt/IRC jobs. Fine jobs still fold Acc2E into the
    ultrafine grid, and non-fine jobs without the remedy are unchanged.

Two conflicting DFT integration grids in one route
    The InaccurateQuadrature remedy escalates to a finer grid recorded as
    'int=grid=NNNMMM', but a fine job also emits integral=(grid=ultrafine, ...).
    Both are the same Gaussian Int keyword, so the route carried two grid specs
    (ultrafine = (99,590) against the remedy's 300590 = (300,590)) and the finer
    grid was either rejected or silently overridden. Fold the remedy grid into
    the single integral=() keyword, replacing ultrafine, and drop the standalone
    int=grid= token. The grid is sourced from ess_trsh_methods so it persists
    while the ladder keeps escalating, and the non-fine opt/IRC path emits it
    too. job_18 loses its duplicated grid; job_20/21 now keep grid=300590
    instead of reverting to ultrafine; job_19, which has no grid remedy, is
    unchanged, confirming the change is scoped to InaccurateQuadrature routes.
    Relatedly, the base route's calcfc is dropped when the MaxOptCycles ladder
    supplies recalcfc/calcall, so opt=() never carries conflicting Hessian
    directives; the job_23/job_24 route fixtures are updated to the new ladder.

guess=mix is now gated on Ms = 0 broken symmetry only
    The adapter emitted guess=mix for every polyatomic species with no
    checkfile. The gate is now

        elif any(spc.multiplicity == 1 and spc.number_of_radicals is not None
                 and spc.number_of_radicals > 1
                 for spc in self.species):

    This predicate deliberately supersedes two earlier attempts. An earlier
    commit on this branch gated on `spc.is_ts or (multiplicity == 1 and
    number_of_radicals > 1)`; draft PR #973 hoisted is_restricted(self) and
    gated on `not all(restricted_flags)`. Both are replaced.

    g16 does not ignore guess=mix on a restricted reference - it applies it.
    arc/testing/restart/1_restart_thermo/calcs/freq_a19031.out is a closed-shell
    singlet (9 alpha, 9 beta electrons; route `#P guess=mix wb97xd/def2tzvp`,
    no `u` prefix) whose L401 prints a single "Mixing orbitals, IMix= 1 ...
    Coef= 7.07106781D-01 7.07106781D-01", then IOpCl= 0 and a normal E(RwB97XD).
    Across the fixture tree a restricted reference prints one mixing line, an
    unrestricted one prints two (alpha +0.7071, beta -0.7071), and a job without
    the keyword prints the Harris functional line instead. On a restricted
    reference the keyword rotates HOMO into LUMO within the single orbital set:
    it is not inert, it perturbs a good Harris guess for no benefit. #973's log
    text "guess=mix only acts on an unrestricted reference" is therefore false
    and is not carried over.

    is_restricted is not usable as the gate. is_species_restricted() early-
    returns True for force_field, composite and semiempirical method types
    before any multiplicity check, so it answers "should the route omit the `u`
    prefix", not "is the reference restricted".
    arc/testing/composite/TS0_composite_2043.out is the counterexample:
    multiplicity 2 at cbs-qb3 with no `u` in the route, yet Gaussian prints two
    mixing lines and SCF Done: E(UB3LYP). The new predicate reads multiplicity
    and number_of_radicals directly and never consults is_restricted, which is
    left untouched.

    The physical rule is that guess=mix is wanted only where the reference is
    genuinely unrestricted and Ms = 0, i.e. a bi-radical singlet: only Ms = 0
    still has an alpha-beta symmetry left to break, which is Gaussian's own
    documented use case ("producing UHF wavefunctions for singlet states"). For
    Ms != 0 systems Nalpha != Nbeta already, so guess=mix degenerates into a
    pure spatial symmetry-breaking request, which for delocalised radicals
    (allyl, benzyl, NO2) drives the classic artificial-localisation artifact.
    Doublets, triplets and non-singlet TSs - which the earlier `spc.is_ts`
    disjunct still admitted - therefore no longer get it.

    Honest consequence: number_of_radicals is user-input-only (ARC does not
    attempt to determine it), so where the user did not declare it the correct
    gate emits guess=mix never. That is accepted deliberately; no heuristic is
    invented to derive it. Whether singlet TSs deserve an unrestricted reference
    is a separate physics question under separate review.

    input_dict['job_type_1'] is rendered once and reused for every --link1--
    section while ${restricted} is substituted per species, so a route-wide
    guess keyword lands on every section. The keyword stays route-wide and the
    predicate collapses with any(), the honest collapse for "does any species
    here need a broken-symmetry seed". Making guess per-species in the
    run_multi_species path would require restructuring the whole route-wide
    assembly (opt=, irc=, scf=, SCRF=, integral=, plus the Fix OPT / Fix SCF /
    Fix IRC recombination that follows) and is out of scope. The pre-existing
    `self.species[0].number_of_atoms > 1` guard is likewise left as found.

    guess=read (checkfile) and guess=INDO (troubleshooting) keep their existing
    precedence and ordering.

    TestGaussianAdapterGuessMixGating covers: bi-radical singlet -> guess=mix;
    closed-shell singlet, doublet and triplet -> none; singlet TS with
    number_of_radicals=None -> none (#973's target); doublet TS -> none (the
    earlier predicate's target); bi-radical singlet TS -> guess=mix; open-shell
    species at cbs-qb3 decided by multiplicity; checkfile -> guess=read; trsh
    guess=INDO -> guess=INDO only. The checkfile fixture lives in a
    tempfile.mkdtemp() directory registered with addCleanup rather than in
    ARC_TESTING_PATH/test_GaussianAdapter, which another module's tearDownClass
    rmtree's and which caused intermittent failures under
    `pytest arc/ -n 6 --dist worksteal`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant