Support for model architecture manipulation
Summary
This MR adds functionality for modifying the architecture of a trained NEP model so that it
can serve as the starting point for further training, avoiding a full restart from random
parameters. Five new Model methods cover the main use cases:
| Method | What it does |
|---|---|
augment() |
Expand neuron count, enable higher-body descriptor terms, or add a charge output head |
prune() |
Reduce neuron count, disable descriptor terms, or remove the charge output head |
remove_species() |
Remove one or more species (refactored; also fixes bugs) |
keep_species() |
Retain a subset of species, dropping all others |
add_species() |
Extend an existing model with new chemical species |
All methods return a new Model object, leaving the source unchanged. They share a
common scheme for the SNES restart file: existing parameters receive adaptive sigma
(max(sigma_floor, sigma_factor × |μ|)), preserving trained values and keeping near-zero
parameters dormant; new parameters are initialized to match the fresh-model behavior of the
nep executable of the GPUMD package (uniform μ ∈ [-1, 1], σ = sigma_new).
A training_parameters property derives the correct nep.in architecture fields from any
modified model. This addresses a critical issue: GPUMD reads nep.in (not nep.txt) to
determine the model size when loading nep.restart for training. Using a stale nep.in
after augmentation causes a stride mismatch that scrambles all parameter values, producing
training-from-scratch-level initial loss even with sigma --> 0.
Changes
Model.augment() — new method
Applies one or more structural changes atomically and returns a new Model:
- Neuron expansion (
n_neuron): padsw0,b0,w1with zeros; new rows/columns receivesigma_newin the restart. - Descriptor expansion (
l_max_4b,l_max_5b,has_q_112/123/233/134): adds columns tow0and appends entries toq_scaler. Increasing an already-enabledl_maxvalue only updates the header — no new descriptor dimensions are created. - Charge head (
charge_head=True): promotespotential-->potential_with_charges; addsw1_chargeper species (initialized to zero) and initializessqrt_epsilon_infinity = 1(ε∞ = 1, the vacuum value). - Requires restart; raises
ValueErrorotherwise.
Model.prune() — new method
Applies one or more structure-reducing changes atomically and returns a new Model:
- Neuron reduction (
n_neuron): ranks neurons by importance scoremean_s(||w0_s[n,:]||₂ × |w1_s[n]|)(charge models add|w1_charge_s[n]|), keeps the top-scoring neurons, and discards the rest. - Descriptor reduction (
l_max_4b=0,has_q_112=False, etc.): removes the corresponding descriptor columns fromw0andq_scaler. Setting a non-zerol_maxto a smaller non-zero value only updates the header. - Charge head removal (
charge_head=True): demotespotential_with_charges-->potential; removesw1_chargeper species andsqrt_epsilon_infinityfrom the restart. - Surviving parameters receive adaptive sigma. Raises
ValueErrorif the target would expand the model (useaugment()instead) or ifrestart_parametersis not loaded.
Model.remove_species() — refactored
- Breaking change (intentional): now returns a new
Modelinstead of modifying in-place, consistent with the other methods. - Bug fix:
n_ann_output_weightsandn_biaswere computed incorrectly forpotential_with_chargesmodels (missing×2multiplier and wrong bias count), causing write-->read roundtrips to fail. - Bug fix: typewise
radial_cutoff/angular_cutofflists were not pruned when a species was removed, so surviving species inherited the deleted species' cutoff values. - Bug fix:
sqrt_epsilon_infinitywas dropped from restart parameters when pruning a charge model. - Applies adaptive sigma to surviving parameters when restart is loaded.
Model.keep_species() — new method
Convenience complement to remove_species(). Accepts the species to retain and derives the
removal list automatically. Thin wrapper around remove_species().
Model.add_species() — new method
- Adds a per-species ANN sub-network (NEP4 only; raises for NEP3) and all new
(s1, s2)descriptor weight pairs for each new element. - New ANN weights and descriptor coefficients are initialized with
μdrawn uniformly from [-1, 1], matching GPUMD's fresh-model initialization. Charge-specific parameters (w1_charge) are kept atμ = 0. sigma_newdefaults to0.1, matching GPUMD'ssigma0default. Accepts aseedparameter for reproducible initialization.- Handles typewise cutoffs:
radial_cutoff/angular_cutoffarguments are required when the model uses per-type cutoffs.
Model.training_parameters — new property
Returns the model's architecture fields (version, type, cutoff, n_max, basis_size,
l_max, neuron, and optionally zbl) in the format accepted by write_nepfile():
params = read_nepfile('nep.in')
params.update(aug.training_parameters)
write_nepfile(params, '.')Bug fix — get_parity_data transpose in calorine/nep/io.py
The transpose of force/BEC/atomic-virial arrays (extracted_property.T) was applied inside the for select in selection: loop, flipping the orientation on every pass. For multi-component selections (e.g. ['x', 'y']) this silently returned wrong data on even-length selections. The transpose is now applied once, before the loop.
Internal refactoring — calorine/nep/model.py
_adaptive_sigma()extracted as a module-level function (was duplicated inside each method)._apply_adaptive_sigma_to_restart()extracted as a module-level helper, replacing ~25-line blocks repeated acrossaugment,prune,add_species, andremove_species. The unified helper also corrects a pre-existing inconsistency: remove_species previously used the rawmax()formula forb1/sqrt_epsilon_infinityscalars while all other methods used_adaptive_sigma._recalculate_parameter_counts()extracted as a module-level helper, replacing duplicate parameter-count blocks across the same four methods. The unified helper also adds the previously missing polarizability doubling in add_species, augment, and prune.
Display fixes (Model.__str__, Model._repr_html_)
restart_parameters previously printed its full nested dict; it now shows "available" or
"not available".
Tutorial notebook (tutorials/modifying_models.ipynb)
New notebook added to the Training NEP models section of the documentation.
Covers all five methods and the training_parameters property with concrete before/after
output. Includes:
- Bar charts of parameter-count change under augmentation and pruning
- Sigma-distribution histograms: new vs. existing species (after add_species()), and before vs. after pruning (adaptive \sigma \propto |\mu| for surviving parameters)
- Chained-operation examples (
remove_species-->add_species,augment-->add_species,prune-->add_species) training_parameters/write_nepfileintegration example
Tests
130 tests pass; calorine/nep/model.py reaches 100% coverage.