Since #5733 pair exclusion is a graph-native transform, but there are still two owners with two different scopes, and one builder silently promotes between them. That promotion makes two otherwise-equivalent model builders disagree by ~80 eV on the same input.
The two owners
| key |
applied where |
scope |
model-level pair_exclude_types |
graph build seam, folded into the graph before the atomic model sees it |
every child of the composition, including analytical bridging (ZBL) |
descriptor-level exclude_types |
inside the descriptor forward, apply_pair_exclusion(graph, atype, self.emask) |
the learned descriptor features only |
Application sites for the descriptor-scoped key: deepmd/dpmodel/descriptor/dpa4.py:1514, deepmd/dpmodel/descriptor/dpa1.py:1866, deepmd/dpmodel/descriptor/repformers.py:622.
The split is already documented in deepmd/dpmodel/descriptor/dpa4_nn/edge_cache.py:279:
Model-level pair_exclude_types is a separate, graph-BUILD transform, already folded into the incoming graph.
The promotion
get_sezm_model copies the descriptor-scoped key into the model-scoped one, writes it back down onto the descriptor, and raises if both are set to different values:
deepmd/pt_expt/model/get_model.py:148-162
deepmd/pt/model/model/__init__.py:388-403 (identical)
get_standard_model does not do this, in either pt_expt or dpmodel.
Observable consequence
Same config in both builders — descriptor.exclude_types=[[0,1]], bridging_method: ZBL, bridging_r_inner: 0.8, bridging_r_outer: 1.2, type_map: [Ni, O] — evaluated on a 0.9 A Ni-O dimer:
| route |
energy |
type: "dpa4" (promotes) |
-0.909 eV |
type: "standard" (does not) |
+79.056 eV |
dE = 79.97 eV, max|dF| = 318.48 eV/A. The difference is entirely whether the analytical ZBL child still sees the excluded pair. Surfaced during review of #5939.
Why the promotion is the wrong side to keep
A descriptor-scoped setting should stay descriptor-scoped. The promotion also forecloses a physically meaningful configuration: hiding a pair from the network while keeping its real short-range nuclear repulsion. And because only one of the two builders promotes, "which builder did you use" silently changes the physics.
Proposal
- Emit a
DeprecationWarning when descriptor.exclude_types is set, directing users to model-level pair_exclude_types.
- Remove the promotion from
get_sezm_model in pt and pt_expt together, so the backends cannot diverge mid-flight.
- Leave
pair_exclude_types as the single build-seam owner; at that point there is nothing left to reconcile and every builder agrees by construction.
- Cover it in the cross-backend consistency harness (a data row exercising descriptor-only vs model-level exclusion), not a bespoke test, so a backend that skips the seam is visible.
Backwards compatibility
Step 2 is a behaviour change for existing dpa4/sezm configs that set only descriptor.exclude_types and rely on it reaching the bridging term — their energies will change. Hence the staged warning first. Configs that set pair_exclude_types explicitly are unaffected.
Since #5733 pair exclusion is a graph-native transform, but there are still two owners with two different scopes, and one builder silently promotes between them. That promotion makes two otherwise-equivalent model builders disagree by ~80 eV on the same input.
The two owners
pair_exclude_typesexclude_typesapply_pair_exclusion(graph, atype, self.emask)Application sites for the descriptor-scoped key:
deepmd/dpmodel/descriptor/dpa4.py:1514,deepmd/dpmodel/descriptor/dpa1.py:1866,deepmd/dpmodel/descriptor/repformers.py:622.The split is already documented in
deepmd/dpmodel/descriptor/dpa4_nn/edge_cache.py:279:The promotion
get_sezm_modelcopies the descriptor-scoped key into the model-scoped one, writes it back down onto the descriptor, and raises if both are set to different values:deepmd/pt_expt/model/get_model.py:148-162deepmd/pt/model/model/__init__.py:388-403(identical)get_standard_modeldoes not do this, in either pt_expt or dpmodel.Observable consequence
Same config in both builders —
descriptor.exclude_types=[[0,1]],bridging_method: ZBL,bridging_r_inner: 0.8,bridging_r_outer: 1.2,type_map: [Ni, O]— evaluated on a 0.9 A Ni-O dimer:type: "dpa4"(promotes)type: "standard"(does not)dE = 79.97 eV, max|dF| = 318.48 eV/A. The difference is entirely whether the analytical ZBL child still sees the excluded pair. Surfaced during review of #5939.
Why the promotion is the wrong side to keep
A descriptor-scoped setting should stay descriptor-scoped. The promotion also forecloses a physically meaningful configuration: hiding a pair from the network while keeping its real short-range nuclear repulsion. And because only one of the two builders promotes, "which builder did you use" silently changes the physics.
Proposal
DeprecationWarningwhendescriptor.exclude_typesis set, directing users to model-levelpair_exclude_types.get_sezm_modelin pt and pt_expt together, so the backends cannot diverge mid-flight.pair_exclude_typesas the single build-seam owner; at that point there is nothing left to reconcile and every builder agrees by construction.Backwards compatibility
Step 2 is a behaviour change for existing dpa4/sezm configs that set only
descriptor.exclude_typesand rely on it reaching the bridging term — their energies will change. Hence the staged warning first. Configs that setpair_exclude_typesexplicitly are unaffected.