Fix guess keyword consistency for restricted TS routes - #973
Conversation
ed3e5f4 to
0b3a26b
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
0b3a26b to
958390f
Compare
…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`.
|
Superseded by #1006. That branch carries the same INDO → guess=read → guess=mix restructure, but gates guess=mix on |
…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`.
…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`.
This pull request refines the logic for adding the
guess=mixkeyword 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 ofguess=mixand related logging.Gaussian input file generation improvements:
write_input_fileingaussian.pyto only addguess=mixfor 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, ensuringguess=mixis not added inappropriately.restricted_listto use the newrestricted_flagsvariable for clarity and correctness.Testing enhancements:
gaussian_test.pyto verify thatguess=mixis 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]guess=mixfrom cases where it should not appear. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13]Documentation and clarity:
guess=mixhandling, especially in troubleshooting scenarios.These changes ensure that the Gaussian adapter generates input files with the correct
guesskeyword usage, improving both correctness and maintainability.