Skip to content

Commit 083e5b2

Browse files
committed
refactor(model): unify dpmodel backend factories
Centralize descriptor and fitting parameter injection, standard-model selection, ZBL assembly, and model-type routing across dpmodel, pt_expt, JAX, and TF2. Keep backend modules limited to their native class registries and supported special cases. Coding-Agent: Codex Codex-Version: codex-cli 0.144.1 Model: gpt-5.6-sol Reasoning-Effort: xhigh
1 parent 2c0a54e commit 083e5b2

7 files changed

Lines changed: 581 additions & 370 deletions

File tree

deepmd/dpmodel/model/model.py

Lines changed: 29 additions & 119 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,
@@ -16,29 +13,24 @@
1613
from deepmd.dpmodel.fitting.base_fitting import (
1714
BaseFitting,
1815
)
19-
from deepmd.dpmodel.fitting.ener_fitting import (
20-
EnergyFittingNet,
21-
)
16+
from deepmd.dpmodel.model import dipole_model as _dipole_model # noqa: F401
17+
from deepmd.dpmodel.model import dos_model as _dos_model # noqa: F401
18+
from deepmd.dpmodel.model import polar_model as _polar_model # noqa: F401
2219
from deepmd.dpmodel.model.base_model import (
2320
BaseModel,
2421
)
25-
from deepmd.dpmodel.model.dipole_model import (
26-
DipoleModel,
27-
)
28-
from deepmd.dpmodel.model.dos_model import (
29-
DOSModel,
30-
)
3122
from deepmd.dpmodel.model.dp_zbl_model import (
3223
DPZBLModel,
3324
)
3425
from deepmd.dpmodel.model.ener_model import (
3526
EnergyModel,
3627
)
37-
from deepmd.dpmodel.model.polar_model import (
38-
PolarModel,
28+
from deepmd.dpmodel.model.model_factory import get_model as get_model_from_factory
29+
from deepmd.dpmodel.model.model_factory import (
30+
get_standard_model as get_standard_model_from_factory,
3931
)
40-
from deepmd.dpmodel.model.property_model import (
41-
PropertyModel,
32+
from deepmd.dpmodel.model.model_factory import (
33+
get_zbl_model as get_zbl_model_from_factory,
4234
)
4335
from deepmd.dpmodel.model.spin_model import (
4436
SpinModel,
@@ -48,31 +40,6 @@
4840
)
4941

5042

51-
def _get_standard_model_components(
52-
data: dict[str, Any], ntypes: int
53-
) -> tuple[BaseDescriptor, BaseFitting, str]:
54-
# descriptor
55-
data["descriptor"]["ntypes"] = ntypes
56-
data["descriptor"]["type_map"] = copy.deepcopy(data["type_map"])
57-
descriptor = BaseDescriptor(**data["descriptor"])
58-
# fitting
59-
fitting_net = data.get("fitting_net", {})
60-
fitting_net["type"] = fitting_net.get("type", "ener")
61-
fitting_net["ntypes"] = descriptor.get_ntypes()
62-
fitting_net["type_map"] = copy.deepcopy(data["type_map"])
63-
fitting_net["mixed_types"] = descriptor.mixed_types()
64-
if fitting_net["type"] in ["dipole", "polar"]:
65-
fitting_net["embedding_width"] = descriptor.get_dim_emb()
66-
fitting_net["dim_descrpt"] = descriptor.get_dim_out()
67-
grad_force = "direct" not in fitting_net["type"]
68-
if not grad_force:
69-
fitting_net["out_dim"] = descriptor.get_dim_emb()
70-
if "ener" in fitting_net["type"]:
71-
fitting_net["return_energy"] = True
72-
fitting = BaseFitting(**fitting_net)
73-
return descriptor, fitting, fitting_net["type"]
74-
75-
7643
def get_standard_model(data: dict) -> EnergyModel:
7744
"""Get a EnergyModel from a dictionary.
7845
@@ -81,78 +48,24 @@ def get_standard_model(data: dict) -> EnergyModel:
8148
data : dict
8249
The data to construct the model.
8350
"""
84-
if "type_embedding" in data:
85-
raise ValueError(
86-
"In the DP backend, type_embedding is not at the model level, but within the descriptor. See type embedding documentation for details."
87-
)
88-
data = copy.deepcopy(data)
89-
ntypes = len(data["type_map"])
90-
descriptor, fitting, fitting_net_type = _get_standard_model_components(data, ntypes)
91-
atom_exclude_types = data.get("atom_exclude_types", [])
92-
pair_exclude_types = data.get("pair_exclude_types", [])
93-
94-
if fitting_net_type == "dipole":
95-
modelcls = DipoleModel
96-
elif fitting_net_type == "polar":
97-
modelcls = PolarModel
98-
elif fitting_net_type == "dos":
99-
modelcls = DOSModel
100-
elif fitting_net_type in ["ener", "direct_force_ener"]:
101-
modelcls = EnergyModel
102-
elif fitting_net_type == "property":
103-
modelcls = PropertyModel
104-
else:
105-
raise RuntimeError(f"Unknown fitting type: {fitting_net_type}")
106-
107-
model = modelcls(
108-
descriptor=descriptor,
109-
fitting=fitting,
110-
type_map=data["type_map"],
111-
atom_exclude_types=atom_exclude_types,
112-
pair_exclude_types=pair_exclude_types,
51+
return get_standard_model_from_factory(
52+
data,
53+
descriptor_base=BaseDescriptor,
54+
fitting_base=BaseFitting,
55+
model_base=BaseModel,
56+
backend_name="DP",
11357
)
114-
return model
11558

11659

11760
def get_zbl_model(data: dict) -> DPZBLModel:
118-
data = copy.deepcopy(data)
119-
data["descriptor"]["ntypes"] = len(data["type_map"])
120-
data["descriptor"]["type_map"] = data["type_map"]
121-
descriptor = BaseDescriptor(**data["descriptor"])
122-
fitting_type = data["fitting_net"].pop("type")
123-
data["fitting_net"]["type_map"] = data["type_map"]
124-
if fitting_type == "ener":
125-
fitting = EnergyFittingNet(
126-
ntypes=descriptor.get_ntypes(),
127-
dim_descrpt=descriptor.get_dim_out(),
128-
mixed_types=descriptor.mixed_types(),
129-
**data["fitting_net"],
130-
)
131-
else:
132-
raise ValueError(f"Unknown fitting type {fitting_type}")
133-
134-
dp_model = DPAtomicModel(descriptor, fitting, type_map=data["type_map"])
135-
# pairtab
136-
filepath = data["use_srtab"]
137-
pt_model = PairTabAtomicModel(
138-
filepath,
139-
descriptor.get_rcut(),
140-
descriptor.get_sel(),
141-
type_map=data["type_map"],
142-
)
143-
144-
rmin = data["sw_rmin"]
145-
rmax = data["sw_rmax"]
146-
atom_exclude_types = data.get("atom_exclude_types", [])
147-
pair_exclude_types = data.get("pair_exclude_types", [])
148-
return DPZBLModel(
149-
dp_model,
150-
pt_model,
151-
rmin,
152-
rmax,
153-
type_map=data["type_map"],
154-
atom_exclude_types=atom_exclude_types,
155-
pair_exclude_types=pair_exclude_types,
61+
return get_zbl_model_from_factory(
62+
data,
63+
descriptor_base=BaseDescriptor,
64+
fitting_base=BaseFitting,
65+
atomic_model=DPAtomicModel,
66+
pairtab_model=PairTabAtomicModel,
67+
zbl_model=DPZBLModel,
68+
backend_name="DP",
15669
)
15770

15871

@@ -198,13 +111,10 @@ def get_model(data: dict) -> BaseModel:
198111
data : dict
199112
The data to construct the model.
200113
"""
201-
model_type = data.get("type", "standard")
202-
if model_type == "standard":
203-
if "spin" in data:
204-
return get_spin_model(data)
205-
elif "use_srtab" in data:
206-
return get_zbl_model(data)
207-
else:
208-
return get_standard_model(data)
209-
else:
210-
return BaseModel.get_class_by_type(model_type).get_model(data)
114+
return get_model_from_factory(
115+
data,
116+
base_model=BaseModel,
117+
standard_model_factory=get_standard_model,
118+
spin_model_factory=get_spin_model,
119+
zbl_model_factory=get_zbl_model,
120+
)
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# SPDX-License-Identifier: LGPL-3.0-or-later
2+
"""Shared model-factory dispatch for dpmodel-driven backends."""
3+
4+
import copy
5+
from collections.abc import (
6+
Callable,
7+
Mapping,
8+
)
9+
from typing import (
10+
Any,
11+
)
12+
13+
ModelFactory = Callable[[dict], Any]
14+
15+
16+
def get_model_components(
17+
data: dict,
18+
*,
19+
descriptor_base: type,
20+
fitting_base: type,
21+
backend_name: str,
22+
) -> tuple[Any, Any, str]:
23+
"""Construct a backend descriptor and fitting net from model config.
24+
25+
The backend registries expose the same descriptor/fitting constructor
26+
contract. Keeping the parameter injection here prevents subtle differences
27+
in ``type_map``, ``ntypes``, embedding width, and direct-force handling.
28+
"""
29+
data = copy.deepcopy(data)
30+
if "type_embedding" in data:
31+
raise ValueError(
32+
f"In the {backend_name} backend, type_embedding is not at the model "
33+
"level, but within the descriptor. See type embedding documentation "
34+
"for details."
35+
)
36+
type_map = copy.deepcopy(data["type_map"])
37+
descriptor_data = data["descriptor"]
38+
descriptor_type = descriptor_data.pop("type")
39+
descriptor_data["ntypes"] = len(type_map)
40+
descriptor_data["type_map"] = copy.deepcopy(type_map)
41+
descriptor = descriptor_base.get_class_by_type(descriptor_type)(**descriptor_data)
42+
43+
fitting_data = data.get("fitting_net", {})
44+
fitting_type = fitting_data.pop("type", "ener")
45+
fitting_data["ntypes"] = descriptor.get_ntypes()
46+
fitting_data["type_map"] = copy.deepcopy(type_map)
47+
fitting_data["mixed_types"] = descriptor.mixed_types()
48+
if fitting_type in {"dipole", "polar"}:
49+
fitting_data["embedding_width"] = descriptor.get_dim_emb()
50+
fitting_data["dim_descrpt"] = descriptor.get_dim_out()
51+
if "direct" in fitting_type:
52+
fitting_data["out_dim"] = descriptor.get_dim_emb()
53+
if "ener" in fitting_type:
54+
fitting_data["return_energy"] = True
55+
fitting = fitting_base.get_class_by_type(fitting_type)(**fitting_data)
56+
return descriptor, fitting, fitting_type
57+
58+
59+
def get_standard_model(
60+
data: dict,
61+
*,
62+
descriptor_base: type,
63+
fitting_base: type,
64+
model_base: type,
65+
backend_name: str,
66+
) -> Any:
67+
"""Construct a standard model through backend registries."""
68+
descriptor, fitting, fitting_type = get_model_components(
69+
data,
70+
descriptor_base=descriptor_base,
71+
fitting_base=fitting_base,
72+
backend_name=backend_name,
73+
)
74+
model_type = "ener" if fitting_type == "direct_force_ener" else fitting_type
75+
model_cls = model_base.get_class_by_type(model_type)
76+
return model_cls(
77+
descriptor=descriptor,
78+
fitting=fitting,
79+
type_map=data["type_map"],
80+
atom_exclude_types=data.get("atom_exclude_types", []),
81+
pair_exclude_types=data.get("pair_exclude_types", []),
82+
)
83+
84+
85+
def get_zbl_model(
86+
data: dict,
87+
*,
88+
descriptor_base: type,
89+
fitting_base: type,
90+
atomic_model: type,
91+
pairtab_model: type,
92+
zbl_model: type,
93+
backend_name: str,
94+
) -> Any:
95+
"""Construct a ZBL model from backend-native atomic model classes."""
96+
data = copy.deepcopy(data)
97+
descriptor, fitting, fitting_type = get_model_components(
98+
data,
99+
descriptor_base=descriptor_base,
100+
fitting_base=fitting_base,
101+
backend_name=backend_name,
102+
)
103+
if fitting_type != "ener":
104+
raise ValueError(f"Unknown fitting type {fitting_type}")
105+
dp_model = atomic_model(descriptor, fitting, type_map=data["type_map"])
106+
pairtab = pairtab_model(
107+
data["use_srtab"],
108+
descriptor.get_rcut(),
109+
descriptor.get_sel(),
110+
type_map=data["type_map"],
111+
)
112+
return zbl_model(
113+
dp_model,
114+
pairtab,
115+
data["sw_rmin"],
116+
data["sw_rmax"],
117+
type_map=data["type_map"],
118+
atom_exclude_types=data.get("atom_exclude_types", []),
119+
pair_exclude_types=data.get("pair_exclude_types", []),
120+
)
121+
122+
123+
def get_model(
124+
data: dict,
125+
*,
126+
base_model: type,
127+
standard_model_factory: ModelFactory,
128+
spin_model_factory: ModelFactory | None = None,
129+
zbl_model_factory: ModelFactory | None = None,
130+
model_factories: Mapping[str, ModelFactory] | None = None,
131+
) -> Any:
132+
"""Construct a backend model using the shared model-type routing rules.
133+
134+
Backend modules supply the concrete constructors while this function owns
135+
the routing precedence. In particular, legacy ``standard`` configurations
136+
select spin before ZBL, matching the established dpmodel and PyTorch input
137+
contract. Explicit model types may be handled by backend-specific factories
138+
before falling back to the backend model plugin registry.
139+
140+
Parameters
141+
----------
142+
data : dict
143+
Model configuration.
144+
base_model : type
145+
Backend model base class providing ``get_class_by_type``.
146+
standard_model_factory : callable
147+
Constructor for an ordinary standard model.
148+
spin_model_factory : callable, optional
149+
Constructor for a legacy standard model containing ``spin``.
150+
zbl_model_factory : callable, optional
151+
Constructor for a legacy standard model containing ``use_srtab``.
152+
model_factories : mapping, optional
153+
Backend-specific constructors keyed by explicit model type.
154+
155+
Returns
156+
-------
157+
Any
158+
The backend-native model instance.
159+
"""
160+
model_type = data.get("type", "standard")
161+
if model_type == "standard":
162+
if "spin" in data:
163+
if spin_model_factory is None:
164+
raise NotImplementedError("Spin model is not implemented yet.")
165+
return spin_model_factory(data)
166+
if "use_srtab" in data:
167+
if zbl_model_factory is None:
168+
raise NotImplementedError("ZBL model is not implemented yet.")
169+
return zbl_model_factory(data)
170+
return standard_model_factory(data)
171+
172+
if model_factories is not None and model_type in model_factories:
173+
return model_factories[model_type](data)
174+
return base_model.get_class_by_type(model_type).get_model(data)

0 commit comments

Comments
 (0)