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
Context: LES is a full second electrostatics family in develop and it needs its own ticket: folding it into ELEC-1 (#1591) as one bullet ("port --les_arguments") understated it by an order of magnitude, and v1 without it is a regression. MACELES (mace/modules/extensions.py:142, decorated @compile_mode("script") at :141) subclasses ScaleShiftMACE, forces keep_last_layer_irreps=True so the LES readouts can see vector features, and wraps the external les package (https://github.com/ChengUCB/les, pinned by commit fadfb0ca in requirements/les.txt, import name les, marker les, probe _module_available("les") at tests/conftest.py:65). It is not the same extra as [electrostatics] and must not be merged into it: the two externals are independent, separately pinned, and separately CI'd (ci-extensions.yaml runs a les job with require-caps: les, filtered over the shared set plus tests/extensions/les/** and requirements/les.txt).
What the port actually has to carry, all of it live on develop:
A 16-key les_arguments dict, not three flags. MACELES.__init__ reads use_atomwise (the only key in the default {"use_atomwise": False}), compute_bec, bec_output_index, use_dipole, use_quad, use_induced_charge, use_induced_dipole, use_anisotropic_polarizability, alpha_irreps (default "0e+1o+2e"), alpha_1o_nonlinear_readout, alpha_1o_linear_w_pos (default True), make_alpha_positive, make_kappa_positive, output_scale (0.1), kappa_scale (0.01), alpha_scale (0.01). It reaches the trainer as one flag, --les_arguments (mace/tools/arg_parser.py:531).
Eight parallel readout ModuleLists cloned off the base readouts — les_readouts, les_u_readouts, les_quad_2e_readouts, les_quad_1o_readouts, les_alpha_readouts, les_alpha_1o_readouts, les_alpha_2e_readouts, les_kappa_readouts — built by _copy_mace_readout / _copy_mace_readout_tp (extensions.py:70,98) over LinearLesReadoutBlock / NonLinearLesReadoutBlock (exported from mace/modules/__init__.py).
Five latent outputs the eval CLI writes to atoms.arrays: latent_charges, latent_dipoles, latent_kappas, latent_alphas, latent_quads (mace/cli/eval_configs.py:252-286 reading them, :411-421 writing them), behind --compute_bec (:59) which refuses on a non-MACELES model at :161.
Born effective charges and the external-field force. The ASE calculator takes compute_bec, external_field, eps_infty, keep_neutral (mace/calculators/mace.py:122-126), surfaces LES_alphas (:798), LES_kappas (:802) and bec (:806), and — when an external field is set — adds a field force into results["forces"] (:808-857): BEC of shape [N,2,3,3] is summed over axis 1, keep_neutral subtracts the per-atom mean, eps_infty plus the LES polarizabilities give chi = tr(alpha)/3/V/eps_0 (eps_0 = 5.52635e-3) and eps_r = eps_infty/(1+chi), and F_nj = sum_i Z*_nij · E_i · sqrt(eps_r) · electric_field_unit.
Trainer plumbing: model_script_utils.py:431-435 builds MACELES, and :131-132 rewrites --model MACELES to the internal FoundationMACELES when a foundation model is supplied (built at :367-371); scripts_utils.py:233,346 lists MACELES in the config-extraction allowlist and its config branch, and :1000,1047-1050 documents that the external LES Atomwise MLP exists only when use_atomwise=True and therefore slips past the optimizer's parameter-group construction.
Two develop behaviours are fixes, not defects, and are the target.keep_neutral subtracts the mean not in place (mace/calculators/mace.py:816-822): the older in-place -= aliased self.results["bec"] on the 3-D path and silently neutralised the stored BEC, while the 4-D path escaped because np.sum copies — the same flag behaved differently per BEC layout. And --compute_becrefuses up front on a non-LES model rather than failing inside the forward. Reproduce both.
keep_last_layer_irreps is a model requirement, not a user flag. Legacy silently overwrites the kwarg; v1 declares it as a constraint of the model and errors loudly if the config contradicts it, rather than overwriting the user's value in __init__.
The five latent quantities and BEC are declared observables (CORE-1 (CORE-1 — Typed outputs + declarative observable specification #1555)), produced by this model and consumed by mace eval and the calculator; they are not forward kwargs threaded through the model. --compute_bec's "only for LES models" refusal becomes an observable-availability check that names the model.
The external-field BEC force is part of the calculator contract, not a post-hoc patch of results["forces"]. In v1 the field contribution is a declared term of the force observable so that its presence is visible in the outputs schema; the numeric formula (including the eps_infty/chi correction and electric_field_unit) reproduces develop exactly, and keep_neutral is non-mutating.
Separate [les] extra. Distinct from [electrostatics], pinning the les package by commit (migrated from requirements/les.txt), with its own paths-filtered CI job carrying require-caps: les. The les capability marker keeps its name so MACE_REQUIRE_CAPS and the skip-o-fail contract are unchanged.
The foundation-model path is part of the port.--model MACELES with a foundation checkpoint routes to a distinct construction path in legacy (FoundationMACELES); v1 expresses that as ordinary fine-tuning config over the registered LES model, and the equivalence is tested, not assumed.
The optimizer parameter-group hazard is designed out. Legacy's own comment records that LES's external Atomwise MLP does not yet exist when parameter groups are built, so its parameters slip past both the group construction and the completeness check. v1 builds parameter groups after the model is fully constructed and asserts every parameter lands in exactly one group.
Task:
Port MACELES to models/electrostatics.py as a registered BaseMACE subclass + typed MACEOutputs + model-transform hook; drop @compile_mode and e3nn.
Create the [les] extra (pin by commit, migrated from requirements/les.txt) and its paths-filtered CI job; keep the les marker name.
Turn les_arguments into a typed, validated config section covering all 16 keys with develop's defaults; make keep_last_layer_irreps a declared model constraint.
Declare BEC and the five latent quantities (latent_charges, latent_dipoles, latent_kappas, latent_alphas, latent_quads) as observables; wire mace eval to write them to atoms.arrays under the same names, and reproduce the "not a LES model" refusal.
Port the foundation-model construction path and prove it equals the fine-tuning-config route.
Fix the parameter-group ordering so no LES parameter is unassigned; assert it.
Port the 9 cases in tests/extensions/les/test_maceles.py, including its 3 xfails — each xfail is re-examined and either fixed or carried forward with its reason recorded, never silently dropped.
Out of scope: the solver dispatch layer and reference solver (ELEC-1 (#1591)); PolarMACE (ELEC-2 (#1592)); the split-charge/SCF families (ELEC-3 (#1593)); the generic ASE calculator (DEP-1 (#1583)); deleting legacy mace/modules/extensions.py (RET-4 (#1600)).
Acceptance criteria:
MACELES is a registered v1 model instantiable from config, with the LES coupling as a model-transform hook; no @compile_mode, no e3nn.
All 16 les_arguments keys are typed config fields with develop's defaults; an unknown key is a config error, not a silently ignored .get().
BEC and the five latent quantities are declared observables; mace eval writes latent_{charges,dipoles,kappas,alphas,quads} to atoms.arrays under the legacy names, and requesting BEC from a non-LES model fails with an error naming the model.
The external-field force reproduces develop numerically on a fixture with external_field, eps_infty and keep_neutral set, for both BEC layouts ([N,2,3,3] and [N,3,3]); keep_neutral does not mutate the reported BEC on either layout.
keep_last_layer_irreps is enforced as a model constraint with a loud error on a contradicting config, not silently overwritten.
The [les] extra exists, pins les by commit, and is separate from [electrostatics]; the les marker still selects the tests and MACE_REQUIRE_CAPS=les still fails a broken install.
A LES model built from a foundation checkpoint through the v1 fine-tuning config matches the legacy FoundationMACELES construction path.
Every LES parameter (including the Atomwise MLP when use_atomwise=True) belongs to exactly one optimizer parameter group — asserted.
The 9 test_maceles.py cases are ported; each of the 3 xfails is resolved or carried with a written reason.
Verify:
pip install -e "packages/mace-torch[les]"
python -m pytest packages/mace-torch/tests/extensions/les/test_maceles.py -v # ported model + xfail dispositions
python -m pytest packages/mace-torch/tests/extensions/les/test_les_bec.py -v # BEC, both layouts, keep_neutral non-mutating, external field
python -m pytest packages/mace-torch/tests -m les # marker + skip-without-extra discipline
Review focus: that all 16 les_arguments keys and all five latent outputs survive with their legacy names (this is the surface v1 is most likely to quietly shrink), that the external-field force reproduces develop for both BEC layouts with keep_neutral non-mutating, and that the LES coupling is a hook rather than a forward override.
Depends on: ELEC-1 (#1591), ARCH-3 (#1562), TRN-1 (#1575), DEP-1 (#1583) · Blocks: RET-4 (#1600)
Context: LES is a full second electrostatics family in develop and it needs its own ticket: folding it into ELEC-1 (#1591) as one bullet ("port
--les_arguments") understated it by an order of magnitude, and v1 without it is a regression.MACELES(mace/modules/extensions.py:142, decorated@compile_mode("script")at:141) subclassesScaleShiftMACE, forceskeep_last_layer_irreps=Trueso the LES readouts can see vector features, and wraps the externallespackage (https://github.com/ChengUCB/les, pinned by commitfadfb0cainrequirements/les.txt, import nameles, markerles, probe_module_available("les")attests/conftest.py:65). It is not the same extra as[electrostatics]and must not be merged into it: the two externals are independent, separately pinned, and separately CI'd (ci-extensions.yamlruns alesjob withrequire-caps: les, filtered over the shared set plustests/extensions/les/**andrequirements/les.txt).What the port actually has to carry, all of it live on develop:
les_argumentsdict, not three flags.MACELES.__init__readsuse_atomwise(the only key in the default{"use_atomwise": False}),compute_bec,bec_output_index,use_dipole,use_quad,use_induced_charge,use_induced_dipole,use_anisotropic_polarizability,alpha_irreps(default"0e+1o+2e"),alpha_1o_nonlinear_readout,alpha_1o_linear_w_pos(defaultTrue),make_alpha_positive,make_kappa_positive,output_scale(0.1),kappa_scale(0.01),alpha_scale(0.01). It reaches the trainer as one flag,--les_arguments(mace/tools/arg_parser.py:531).ModuleLists cloned off the base readouts —les_readouts,les_u_readouts,les_quad_2e_readouts,les_quad_1o_readouts,les_alpha_readouts,les_alpha_1o_readouts,les_alpha_2e_readouts,les_kappa_readouts— built by_copy_mace_readout/_copy_mace_readout_tp(extensions.py:70,98) overLinearLesReadoutBlock/NonLinearLesReadoutBlock(exported frommace/modules/__init__.py).atoms.arrays:latent_charges,latent_dipoles,latent_kappas,latent_alphas,latent_quads(mace/cli/eval_configs.py:252-286reading them,:411-421writing them), behind--compute_bec(:59) which refuses on a non-MACELESmodel at:161.compute_bec,external_field,eps_infty,keep_neutral(mace/calculators/mace.py:122-126), surfacesLES_alphas(:798),LES_kappas(:802) andbec(:806), and — when an external field is set — adds a field force intoresults["forces"](:808-857): BEC of shape[N,2,3,3]is summed over axis 1,keep_neutralsubtracts the per-atom mean,eps_inftyplus the LES polarizabilities givechi = tr(alpha)/3/V/eps_0(eps_0 = 5.52635e-3) andeps_r = eps_infty/(1+chi), andF_nj = sum_i Z*_nij · E_i · sqrt(eps_r) · electric_field_unit.model_script_utils.py:431-435buildsMACELES, and:131-132rewrites--model MACELESto the internalFoundationMACELESwhen a foundation model is supplied (built at:367-371);scripts_utils.py:233,346listsMACELESin the config-extraction allowlist and its config branch, and:1000,1047-1050documents that the external LES Atomwise MLP exists only whenuse_atomwise=Trueand therefore slips past the optimizer's parameter-group construction.Two develop behaviours are fixes, not defects, and are the target.
keep_neutralsubtracts the mean not in place (mace/calculators/mace.py:816-822): the older in-place-=aliasedself.results["bec"]on the 3-D path and silently neutralised the stored BEC, while the 4-D path escaped becausenp.sumcopies — the same flag behaved differently per BEC layout. And--compute_becrefuses up front on a non-LES model rather than failing inside the forward. Reproduce both.Interface & constraints:
MACELESbecomes a registered model inmodels/electrostatics.py: aBaseMACEsubclass returning a typedMACEOutputs, with the LES coupling as a model-transform hook (TRN-1 (TRN-1 — Staged training pipeline with typed stage contracts and an explicit training loop #1575)), never aScaleShiftMACE.forwardoverride. No@compile_mode, no e3nn, no TorchScript.keep_last_layer_irrepsis a model requirement, not a user flag. Legacy silently overwrites the kwarg; v1 declares it as a constraint of the model and errors loudly if the config contradicts it, rather than overwriting the user's value in__init__.les_argumentsbecomes a typed config section, one field per key of §Context, with the same defaults. It is validated at config time (CFG-1 (CFG-1 — Full training config schema with cross-section validation #1574)), not read out of an untyped dict with.get()at construction.mace evaland the calculator; they are notforwardkwargs threaded through the model.--compute_bec's "only for LES models" refusal becomes an observable-availability check that names the model.results["forces"]. In v1 the field contribution is a declared term of the force observable so that its presence is visible in the outputs schema; the numeric formula (including theeps_infty/chicorrection andelectric_field_unit) reproduces develop exactly, andkeep_neutralis non-mutating.[les]extra. Distinct from[electrostatics], pinning thelespackage by commit (migrated fromrequirements/les.txt), with its own paths-filtered CI job carryingrequire-caps: les. Thelescapability marker keeps its name soMACE_REQUIRE_CAPSand the skip-o-fail contract are unchanged.--model MACELESwith a foundation checkpoint routes to a distinct construction path in legacy (FoundationMACELES); v1 expresses that as ordinary fine-tuning config over the registered LES model, and the equivalence is tested, not assumed.Task:
MACELEStomodels/electrostatics.pyas a registeredBaseMACEsubclass + typedMACEOutputs+ model-transform hook; drop@compile_modeand e3nn.[les]extra (pin by commit, migrated fromrequirements/les.txt) and its paths-filtered CI job; keep thelesmarker name.les_argumentsinto a typed, validated config section covering all 16 keys with develop's defaults; makekeep_last_layer_irrepsa declared model constraint.latent_charges,latent_dipoles,latent_kappas,latent_alphas,latent_quads) as observables; wiremace evalto write them toatoms.arraysunder the same names, and reproduce the "not a LES model" refusal.eps_infty/chi,electric_field_unit, non-mutatingkeep_neutral) as a declared force term in the DEP-1 (DEP-1 — ASE calculator for the v1 engine (committee, Hessians, descriptors, key pass-through) #1583) calculator.tests/extensions/les/test_maceles.py, including its 3 xfails — each xfail is re-examined and either fixed or carried forward with its reason recorded, never silently dropped.Out of scope: the solver dispatch layer and reference solver (ELEC-1 (#1591));
PolarMACE(ELEC-2 (#1592)); the split-charge/SCF families (ELEC-3 (#1593)); the generic ASE calculator (DEP-1 (#1583)); deleting legacymace/modules/extensions.py(RET-4 (#1600)).Acceptance criteria:
MACELESis a registered v1 model instantiable from config, with the LES coupling as a model-transform hook; no@compile_mode, no e3nn.les_argumentskeys are typed config fields with develop's defaults; an unknown key is a config error, not a silently ignored.get().mace evalwriteslatent_{charges,dipoles,kappas,alphas,quads}toatoms.arraysunder the legacy names, and requesting BEC from a non-LES model fails with an error naming the model.external_field,eps_inftyandkeep_neutralset, for both BEC layouts ([N,2,3,3]and[N,3,3]);keep_neutraldoes not mutate the reported BEC on either layout.keep_last_layer_irrepsis enforced as a model constraint with a loud error on a contradicting config, not silently overwritten.[les]extra exists, pinslesby commit, and is separate from[electrostatics]; thelesmarker still selects the tests andMACE_REQUIRE_CAPS=lesstill fails a broken install.FoundationMACELESconstruction path.use_atomwise=True) belongs to exactly one optimizer parameter group — asserted.test_maceles.pycases are ported; each of the 3 xfails is resolved or carried with a written reason.Verify:
Review focus: that all 16
les_argumentskeys and all five latent outputs survive with their legacy names (this is the surface v1 is most likely to quietly shrink), that the external-field force reproduces develop for both BEC layouts withkeep_neutralnon-mutating, and that the LES coupling is a hook rather than aforwardoverride.