Skip to content

Commit 2fde06a

Browse files
author
Han Wang
committed
refactor(dpmodel): move the linear atomic-model builder into BackendModelFactory
The linear child-parsing core was a private function in deepmd/dpmodel/model/model.py that pt_expt imported cross-module. The factory is the established home for registry-parameterized composition builders (get_zbl_model already builds the srtab two-child composition there), so get_linear_atomic_model joins it: dpmodel and pt_expt now call _model_factory.get_linear_atomic_model(data), with the backend classes bound once at factory construction. No behavior change.
1 parent 1994c2c commit 2fde06a

3 files changed

Lines changed: 136 additions & 119 deletions

File tree

deepmd/dpmodel/model/model.py

Lines changed: 1 addition & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
# SPDX-License-Identifier: LGPL-3.0-or-later
22
import copy
3-
from typing import (
4-
Any,
5-
)
63

74
from deepmd.dpmodel.atomic_model.dp_atomic_model import (
85
DPAtomicModel,
@@ -117,12 +114,7 @@ def get_linear_model(data: dict) -> BaseModel:
117114
for sub in data["models"]:
118115
if "descriptor" in sub:
119116
sub["descriptor"]["use_spin"] = use_spin
120-
composed = _build_linear_atomic_model(
121-
data,
122-
model_components_factory=_model_factory.get_model_components,
123-
dp_atomic_model=DPAtomicModel,
124-
pairtab_atomic_model=PairTabAtomicModel,
125-
)
117+
composed = _model_factory.get_linear_atomic_model(data)
126118
if spin is not None:
127119
if not composed.supports_native_spin():
128120
raise NotImplementedError(
@@ -133,106 +125,6 @@ def get_linear_model(data: dict) -> BaseModel:
133125
return LinearEnergyModel(atomic_model_=composed)
134126

135127

136-
def _build_linear_atomic_model(
137-
data: dict,
138-
*,
139-
model_components_factory: Any,
140-
dp_atomic_model: type,
141-
pairtab_atomic_model: type,
142-
) -> Any:
143-
"""Build the ``LinearEnergyAtomicModel`` composition from a config.
144-
145-
Shared between the dpmodel and pt_expt linear builders: the caller
146-
supplies its backend's component factory and atomic-model classes.
147-
``data`` is mutated (callers pass a private deep copy).
148-
149-
Parameters
150-
----------
151-
data : dict
152-
The ``linear_ener`` model configuration.
153-
model_components_factory : callable
154-
Backend factory building (descriptor, fitting, type_map) from a
155-
standard sub-model config.
156-
dp_atomic_model : type
157-
Backend learned atomic-model class.
158-
pairtab_atomic_model : type
159-
Backend pair-tabulation atomic-model class.
160-
"""
161-
from deepmd.dpmodel.atomic_model.inner_potential import (
162-
InnerPotentialAtomicModel,
163-
)
164-
from deepmd.dpmodel.atomic_model.linear_atomic_model import (
165-
LinearEnergyAtomicModel,
166-
)
167-
168-
type_map = data["type_map"]
169-
ntypes = len(type_map)
170-
children = data["models"]
171-
inner_indices = [
172-
i for i, sub in enumerate(children) if sub.get("type") == "inner_potential"
173-
]
174-
learned_indices = [i for i, sub in enumerate(children) if "descriptor" in sub]
175-
if inner_indices:
176-
if len(inner_indices) > 1:
177-
raise ValueError(
178-
"A linear_ener composition supports at most one "
179-
"`inner_potential` sub-model."
180-
)
181-
if len(learned_indices) != 1:
182-
raise ValueError(
183-
"An `inner_potential` sub-model bridges exactly one learned "
184-
f"sibling, but got {len(learned_indices)} sub-models with a "
185-
"descriptor."
186-
)
187-
# The composition derives the sibling descriptor's clamp window from
188-
# the inner_potential child: one source of truth for the radii.
189-
inner_cfg = children[inner_indices[0]]
190-
learned_descriptor = children[learned_indices[0]]["descriptor"]
191-
learned_descriptor["inner_clamp_r_inner"] = float(inner_cfg.get("r_inner", 0.5))
192-
learned_descriptor["inner_clamp_r_outer"] = float(inner_cfg.get("r_outer", 0.8))
193-
194-
built: dict[int, Any] = {}
195-
for i, sub in enumerate(children):
196-
if i in inner_indices:
197-
continue
198-
if "type_map" not in sub:
199-
sub["type_map"] = copy.deepcopy(type_map)
200-
if "descriptor" in sub:
201-
sub["descriptor"]["ntypes"] = ntypes
202-
descriptor, fitting, _ = model_components_factory(sub)
203-
built[i] = dp_atomic_model(descriptor, fitting, type_map=sub["type_map"])
204-
else:
205-
if sub.get("type") != "pairtab":
206-
raise ValueError(
207-
"Sub-models in LinearEnergyModel must be a standard model, "
208-
"a pairtab model, or an inner_potential model, but got "
209-
f"type {sub.get('type')!r}."
210-
)
211-
built[i] = pairtab_atomic_model(
212-
sub["tab_file"],
213-
sub["rcut"],
214-
sub["sel"],
215-
type_map=copy.deepcopy(type_map),
216-
)
217-
for i in inner_indices:
218-
learned_descriptor_obj = built[learned_indices[0]].descriptor
219-
built[i] = InnerPotentialAtomicModel(
220-
type_map=copy.deepcopy(type_map),
221-
mode=children[i].get("mode", "zbl"),
222-
rcut=learned_descriptor_obj.get_rcut(),
223-
sel=learned_descriptor_obj.get_sel(),
224-
)
225-
return LinearEnergyAtomicModel(
226-
models=[built[i] for i in range(len(children))],
227-
type_map=type_map,
228-
weights=data.get("weights", "mean"),
229-
# Both exclusions belong to the composition: its children share one
230-
# graph, so "excluded" must cover the analytical term too.
231-
atom_exclude_types=data.get("atom_exclude_types", []),
232-
pair_exclude_types=data.get("pair_exclude_types", []),
233-
)
234-
235-
236128
def get_spin_model(data: dict) -> SpinModel:
237129
"""Get a spin model from a dictionary.
238130

deepmd/dpmodel/model/model_factory.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,127 @@ def get_zbl_model(
124124
)
125125

126126

127+
def get_linear_atomic_model(
128+
data: dict,
129+
*,
130+
descriptor_base: type,
131+
fitting_base: type,
132+
backend_name: str,
133+
atomic_model: type,
134+
pairtab_model: type,
135+
) -> Any:
136+
"""Build the ``LinearEnergyAtomicModel`` composition from a config.
137+
138+
Children with a ``descriptor`` build as learned atomic models through
139+
the backend registries; ``pairtab`` children build as pair-tabulation
140+
atomic models; an ``inner_potential`` child builds the analytical
141+
bridging term. The composition is the ONE owner of the bridging
142+
coupling: it derives the learned sibling descriptor's
143+
``inner_clamp_r_inner``/``_outer`` from the ``inner_potential``
144+
child's ``r_inner``/``r_outer``, so the radii are written once in the
145+
config (issue #5948).
146+
147+
Parameters
148+
----------
149+
data : dict
150+
The ``linear_ener`` model configuration.
151+
descriptor_base : type
152+
Backend descriptor registry base class.
153+
fitting_base : type
154+
Backend fitting registry base class.
155+
backend_name : str
156+
Backend name used in error messages.
157+
atomic_model : type
158+
Backend learned atomic-model class.
159+
pairtab_model : type
160+
Backend pair-tabulation atomic-model class.
161+
162+
Raises
163+
------
164+
ValueError
165+
If more than one ``inner_potential`` child is given, if an
166+
``inner_potential`` child has no unique learned sibling, or if a
167+
child is of an unsupported kind.
168+
"""
169+
from deepmd.dpmodel.atomic_model.inner_potential import (
170+
InnerPotentialAtomicModel,
171+
)
172+
from deepmd.dpmodel.atomic_model.linear_atomic_model import (
173+
LinearEnergyAtomicModel,
174+
)
175+
176+
data = copy.deepcopy(data)
177+
type_map = data["type_map"]
178+
children = data["models"]
179+
inner_indices = [
180+
i for i, sub in enumerate(children) if sub.get("type") == "inner_potential"
181+
]
182+
learned_indices = [i for i, sub in enumerate(children) if "descriptor" in sub]
183+
if inner_indices:
184+
if len(inner_indices) > 1:
185+
raise ValueError(
186+
"A linear_ener composition supports at most one "
187+
"`inner_potential` sub-model."
188+
)
189+
if len(learned_indices) != 1:
190+
raise ValueError(
191+
"An `inner_potential` sub-model bridges exactly one learned "
192+
f"sibling, but got {len(learned_indices)} sub-models with a "
193+
"descriptor."
194+
)
195+
# The composition derives the sibling descriptor's clamp window from
196+
# the inner_potential child: one source of truth for the radii.
197+
inner_cfg = children[inner_indices[0]]
198+
learned_descriptor = children[learned_indices[0]]["descriptor"]
199+
learned_descriptor["inner_clamp_r_inner"] = float(inner_cfg.get("r_inner", 0.5))
200+
learned_descriptor["inner_clamp_r_outer"] = float(inner_cfg.get("r_outer", 0.8))
201+
202+
built: dict[int, Any] = {}
203+
for i, sub in enumerate(children):
204+
if i in inner_indices:
205+
continue
206+
if "type_map" not in sub:
207+
sub["type_map"] = copy.deepcopy(type_map)
208+
if "descriptor" in sub:
209+
descriptor, fitting, _ = get_model_components(
210+
sub,
211+
descriptor_base=descriptor_base,
212+
fitting_base=fitting_base,
213+
backend_name=backend_name,
214+
)
215+
built[i] = atomic_model(descriptor, fitting, type_map=sub["type_map"])
216+
else:
217+
if sub.get("type") != "pairtab":
218+
raise ValueError(
219+
"Sub-models in LinearEnergyModel must be a standard model, "
220+
"a pairtab model, or an inner_potential model, but got "
221+
f"type {sub.get('type')!r}."
222+
)
223+
built[i] = pairtab_model(
224+
sub["tab_file"],
225+
sub["rcut"],
226+
sub["sel"],
227+
type_map=copy.deepcopy(type_map),
228+
)
229+
for i in inner_indices:
230+
learned_descriptor_obj = built[learned_indices[0]].descriptor
231+
built[i] = InnerPotentialAtomicModel(
232+
type_map=copy.deepcopy(type_map),
233+
mode=children[i].get("mode", "zbl"),
234+
rcut=learned_descriptor_obj.get_rcut(),
235+
sel=learned_descriptor_obj.get_sel(),
236+
)
237+
return LinearEnergyAtomicModel(
238+
models=[built[i] for i in range(len(children))],
239+
type_map=type_map,
240+
weights=data.get("weights", "mean"),
241+
# Both exclusions belong to the composition: its children share one
242+
# graph, so "excluded" must cover the analytical term too.
243+
atom_exclude_types=data.get("atom_exclude_types", []),
244+
pair_exclude_types=data.get("pair_exclude_types", []),
245+
)
246+
247+
127248
def get_spin_model(
128249
data: dict,
129250
*,
@@ -257,6 +378,19 @@ def get_standard_model(self, data: dict) -> Any:
257378
backend_name=self.backend_name,
258379
)
259380

381+
def get_linear_atomic_model(self, data: dict) -> Any:
382+
"""Construct the linear atomic-model composition for this backend."""
383+
if self.atomic_model is None or self.pairtab_model is None:
384+
raise NotImplementedError("Linear model is not implemented yet.")
385+
return get_linear_atomic_model(
386+
data,
387+
descriptor_base=self.descriptor_base,
388+
fitting_base=self.fitting_base,
389+
backend_name=self.backend_name,
390+
atomic_model=self.atomic_model,
391+
pairtab_model=self.pairtab_model,
392+
)
393+
260394
def get_zbl_model(self, data: dict) -> Any:
261395
"""Construct a ZBL model for this backend."""
262396
if (

deepmd/pt_expt/model/get_model.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,6 @@ def get_linear_model(model_params: dict) -> BaseModel:
296296
model_params : dict
297297
The model parameters.
298298
"""
299-
from deepmd.dpmodel.model.model import (
300-
_build_linear_atomic_model,
301-
)
302-
303299
from .dp_linear_model import (
304300
LinearEnergyModel,
305301
)
@@ -324,12 +320,7 @@ def get_linear_model(model_params: dict) -> BaseModel:
324320
for sub in model_params["models"]:
325321
if "descriptor" in sub:
326322
sub["descriptor"]["use_spin"] = use_spin
327-
composed = _build_linear_atomic_model(
328-
model_params,
329-
model_components_factory=_model_factory.get_model_components,
330-
dp_atomic_model=DPAtomicModel,
331-
pairtab_atomic_model=PairTabAtomicModel,
332-
)
323+
composed = _model_factory.get_linear_atomic_model(model_params)
333324
if spin is not None:
334325
if not composed.supports_native_spin():
335326
raise NotImplementedError(

0 commit comments

Comments
 (0)