You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Show a Level's args in its string form without losing its freq scale factor
`Level.__init__` normalises `args` into both a `keyword` and a `block` slot, so
a keyword-only level (the common case) has an empty `block` slot and
`all(val for val in self.args.values())` is False. `__str__` therefore dropped
args entirely: two levels differing only in args compared equal (`__eq__` is
str-based) while their `as_dict()` differed. The predicate now uses `any()`.
Flipping that predicate alone silently changes a physical quantity.
`assign_frequency_scale_factor()` keys on `str(level)` and returns None on a
miss. All 67 keys in `data/freq_scale_factors.yml` have the form
`method/basis[, solvation_method: ..., solvent: ..., software: ...]`; none
carries args. Once args entered the string, every args-carrying level lost its
scale factor, and since `calc_freq_factor` defaults to True that miss spawns a
full campaign of QM jobs to recompute the factor by Truhlar's method.
The database is keyed on the physical level of theory - method, basis,
dispersion, solvation, software - whereas args are ESS input-file directives
(extra route keywords and input blocks) that the database has never
distinguished. The lookup key is therefore built from a reduced string rather
than from `str(level)`: `get_freq_scale_factor_key()` hands the same key to both
consumers, `assign_frequency_scale_factor()` and
`output._resolve_freq_scale_factor_source()`.
`args` are the only attribute dropped. Everything else the string carries stays
in the key, including attributes the database happens not to use: `dispersion`,
`auxiliary_basis` and `cabs` are part of the model chemistry, and `year` and
`software_version` select a specific parameterisation elsewhere in ARC, so for
all of them "no published factor for this level" is the true answer rather than
a reason to widen the match. Keying on something narrower, such as
`Level.simple()`, would match nothing at all, since every key carries
`software:` and `simple()` omits it.
A level with both arg slots populated missed the database even before this
change and now resolves, consistent with the keyword-only and block-only cases.
The printing loop only ever emitted the `keyword` slot, so a block-only level
would have printed `keyword args: {}` - meaningless output and a broken key.
Every non-empty slot is now printed under its own name.
`utils/scale.py` prints a level as a yaml key for pasting into the database;
that key now comes from the same helper. Its project folder name still derives
from `str(level)`, so levels differing only in args keep separate directories,
but `rename_level()` substituted only `/ * + ( ) space :` and now maps every
remaining character outside `settings['valid_chars']` - the set ARC already
requires of a project name - onto `_`. Args reach that name and can carry
`{ } ' % = \ " $` and backticks, and the name is joined to a path that is passed
to `shutil.rmtree()` and interpolated into double-quoted shell commands.
`__eq__` is str-based, so levels differing only in args no longer compare equal.
Audited: the scheduler's "sp reuses the opt output" shortcut and its
conf_sp/conf_opt comparison now treat args-differing levels as different, which
is the conservative direction - an extra job rather than reusing an output
computed under different directives. Pipe batching is unaffected: `TaskSpec.level`
is a `Level.as_dict()` dict, and the planner compares dicts, not strings.
`as_dict()` is untouched here and still drops keyword-only args, so on its own
this change leaves `__str__` and `as_dict()` asymmetric in the opposite
direction to the one described above. The `as_dict()` predicate is fixed
separately in the branch that preserves args through the as_dict round-trip; the
two are independent and merge cleanly.
Looked for an existing normaliser before adding one: nothing strips args.
`Level.simple()` drops software and solvation, `statmech.arkane._level_to_str()`
builds Arkane `LevelOfTheory` strings, and `scale.rename_level()` only sanitises
a string for folder naming.
0 commit comments