Skip to content

Commit c1a50ed

Browse files
authored
feat(dp, pt): add charge spin embedding (#5295)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Optional charge/spin electronic embedding for DPA3 (toggleable). * Descriptor and model APIs accept optional per-frame parameters (fparam); when enabled, default per-frame params are forwarded into descriptor embeddings. * **Tests** * Expanded test coverage for charge/spin embedding, fparam construction/propagation, backend paths, and serialization. * **Documentation** * Added help/config entry for the charge/spin embedding option. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Duo <50307526+iProzd@users.noreply.github.com>
1 parent 387ee25 commit c1a50ed

28 files changed

Lines changed: 463 additions & 14 deletions

File tree

deepmd/dpmodel/atomic_model/dp_atomic_model.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ def __init__(
7474
if hasattr(self.fitting_net, "reinit_exclude"):
7575
self.fitting_net.reinit_exclude(self.atom_exclude_types)
7676
self.type_map = type_map
77+
self.add_chg_spin_ebd: bool = getattr(
78+
self.descriptor, "add_chg_spin_ebd", False
79+
)
7780
super().init_out_stat()
7881

7982
def fitting_output_def(self) -> FittingOutputDef:
@@ -180,11 +183,38 @@ def forward_atomic(
180183
"""
181184
nframes, nloc, nnei = nlist.shape
182185
atype = xp_take_first_n(extended_atype, 1, nloc)
186+
187+
# Handle default fparam if fitting net supports it
188+
if (
189+
hasattr(self.fitting_net, "get_dim_fparam")
190+
and self.fitting_net.get_dim_fparam() > 0
191+
and fparam is None
192+
):
193+
# use default fparam
194+
from deepmd.dpmodel.array_api import (
195+
array_api_compat,
196+
)
197+
198+
default_fparam = self.fitting_net.get_default_fparam()
199+
assert default_fparam is not None
200+
xp = array_api_compat.array_namespace(extended_coord)
201+
default_fparam_array = xp.asarray(
202+
default_fparam,
203+
dtype=extended_coord.dtype,
204+
device=array_api_compat.device(extended_coord),
205+
)
206+
fparam_input_for_des = xp.tile(
207+
xp.reshape(default_fparam_array, (1, -1)), (nframes, 1)
208+
)
209+
else:
210+
fparam_input_for_des = fparam
211+
183212
descriptor, rot_mat, g2, h2, sw = self.descriptor(
184213
extended_coord,
185214
extended_atype,
186215
nlist,
187216
mapping=mapping,
217+
fparam=fparam_input_for_des if self.add_chg_spin_ebd else None,
188218
)
189219
ret = self.fitting_net(
190220
descriptor,

deepmd/dpmodel/descriptor/dpa1.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ def call(
493493
atype_ext: Array,
494494
nlist: Array,
495495
mapping: Array | None = None,
496+
fparam: Array | None = None,
496497
) -> Array:
497498
"""Compute the descriptor.
498499

deepmd/dpmodel/descriptor/dpa2.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,7 @@ def call(
828828
atype_ext: Array,
829829
nlist: Array,
830830
mapping: Array | None = None,
831+
fparam: Array | None = None,
831832
) -> tuple[Array, Array, Array, Array, Array]:
832833
"""Compute the descriptor.
833834

deepmd/dpmodel/descriptor/dpa3.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
)
2121
from deepmd.dpmodel.utils.network import (
2222
NativeLayer,
23+
get_activation_fn,
2324
)
2425
from deepmd.dpmodel.utils.seed import (
2526
child_seed,
@@ -356,6 +357,7 @@ def __init__(
356357
use_tebd_bias: bool = False,
357358
use_loc_mapping: bool = True,
358359
type_map: list[str] | None = None,
360+
add_chg_spin_ebd: bool = False,
359361
) -> None:
360362
super().__init__()
361363

@@ -410,6 +412,7 @@ def init_subclass_params(sub_data: dict | Any, sub_class: type) -> Any:
410412
)
411413

412414
self.use_econf_tebd = use_econf_tebd
415+
self.add_chg_spin_ebd = add_chg_spin_ebd
413416
self.use_tebd_bias = use_tebd_bias
414417
self.use_loc_mapping = use_loc_mapping
415418
self.type_map = type_map
@@ -428,6 +431,38 @@ def init_subclass_params(sub_data: dict | Any, sub_class: type) -> Any:
428431
)
429432
self.concat_output_tebd = concat_output_tebd
430433
self.precision = precision
434+
435+
if self.add_chg_spin_ebd:
436+
self.cs_activation_fn = get_activation_fn(activation_function)
437+
# -100 ~ 100 is a conservative bound
438+
self.chg_embedding = TypeEmbedNet(
439+
ntypes=200,
440+
neuron=[self.tebd_dim],
441+
padding=True,
442+
activation_function="Linear",
443+
precision=precision,
444+
seed=child_seed(seed, 3),
445+
)
446+
# 100 is a conservative upper bound
447+
self.spin_embedding = TypeEmbedNet(
448+
ntypes=100,
449+
neuron=[self.tebd_dim],
450+
padding=True,
451+
activation_function="Linear",
452+
precision=precision,
453+
seed=child_seed(seed, 4),
454+
)
455+
self.mix_cs_mlp = NativeLayer(
456+
2 * self.tebd_dim,
457+
self.tebd_dim,
458+
precision=precision,
459+
seed=child_seed(seed, 5),
460+
)
461+
else:
462+
self.chg_embedding = None
463+
self.spin_embedding = None
464+
self.mix_cs_mlp = None
465+
431466
self.exclude_types = exclude_types
432467
self.env_protection = env_protection
433468
self.trainable = trainable
@@ -579,6 +614,7 @@ def call(
579614
atype_ext: Array,
580615
nlist: Array,
581616
mapping: Array | None = None,
617+
fparam: Array | None = None,
582618
) -> tuple[Array, Array, Array, Array, Array]:
583619
"""Compute the descriptor.
584620
@@ -625,6 +661,27 @@ def call(
625661
xp.take(type_embedding, xp.reshape(atype_ext, (-1,)), axis=0),
626662
(nframes, nall, self.tebd_dim),
627663
)
664+
665+
if self.add_chg_spin_ebd:
666+
assert fparam is not None
667+
assert self.chg_embedding is not None
668+
assert self.spin_embedding is not None
669+
chg_tebd = self.chg_embedding.call()
670+
spin_tebd = self.spin_embedding.call()
671+
charge = xp.astype(fparam[:, 0], xp.int64) + 100
672+
spin = xp.astype(fparam[:, 1], xp.int64)
673+
chg_ebd = xp.reshape(
674+
xp.take(chg_tebd, xp.reshape(charge, (-1,)), axis=0),
675+
(nframes, self.tebd_dim),
676+
)
677+
spin_ebd = xp.reshape(
678+
xp.take(spin_tebd, xp.reshape(spin, (-1,)), axis=0),
679+
(nframes, self.tebd_dim),
680+
)
681+
cs_cat = xp.concat([chg_ebd, spin_ebd], axis=-1)
682+
sys_cs_embd = self.cs_activation_fn(self.mix_cs_mlp.call(cs_cat))
683+
node_ebd_ext = node_ebd_ext + xp.expand_dims(sys_cs_embd, axis=1)
684+
628685
node_ebd_inp = node_ebd_ext[:, :nloc, :]
629686
# repflows
630687
node_ebd, edge_ebd, h2, rot_mat, sw = self.repflows(
@@ -655,9 +712,14 @@ def serialize(self) -> dict:
655712
"use_econf_tebd": self.use_econf_tebd,
656713
"use_tebd_bias": self.use_tebd_bias,
657714
"use_loc_mapping": self.use_loc_mapping,
715+
"add_chg_spin_ebd": self.add_chg_spin_ebd,
658716
"type_map": self.type_map,
659717
"type_embedding": self.type_embedding.serialize(),
660718
}
719+
if self.add_chg_spin_ebd:
720+
data["chg_embedding"] = self.chg_embedding.serialize()
721+
data["spin_embedding"] = self.spin_embedding.serialize()
722+
data["mix_cs_mlp"] = self.mix_cs_mlp.serialize()
661723
repflow_variable = {
662724
"edge_embd": repflows.edge_embd.serialize(),
663725
"angle_embd": repflows.angle_embd.serialize(),
@@ -684,10 +746,18 @@ def deserialize(cls, data: dict) -> "DescrptDPA3":
684746
data.pop("type")
685747
repflow_variable = data.pop("repflow_variable").copy()
686748
type_embedding = data.pop("type_embedding")
749+
chg_embedding = data.pop("chg_embedding", None)
750+
spin_embedding = data.pop("spin_embedding", None)
751+
mix_cs_mlp = data.pop("mix_cs_mlp", None)
687752
data["repflow"] = RepFlowArgs(**data.pop("repflow_args"))
688753
obj = cls(**data)
689754
obj.type_embedding = TypeEmbedNet.deserialize(type_embedding)
690755

756+
if obj.add_chg_spin_ebd and chg_embedding is not None:
757+
obj.chg_embedding = TypeEmbedNet.deserialize(chg_embedding)
758+
obj.spin_embedding = TypeEmbedNet.deserialize(spin_embedding)
759+
obj.mix_cs_mlp = NativeLayer.deserialize(mix_cs_mlp)
760+
691761
# deserialize repflow
692762
statistic_repflows = repflow_variable.pop("@variables")
693763
env_mat = repflow_variable.pop("env_mat")

deepmd/dpmodel/descriptor/hybrid.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ def call(
275275
atype_ext: Array,
276276
nlist: Array,
277277
mapping: Array | None = None,
278+
fparam: Array | None = None,
278279
) -> tuple[
279280
Array,
280281
Array | None,

deepmd/dpmodel/descriptor/make_base_descriptor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ def fwd(
186186
extended_atype: Array,
187187
nlist: Array,
188188
mapping: Array | None = None,
189+
fparam: Array | None = None,
189190
) -> Array:
190191
"""Calculate descriptor."""
191192
pass

deepmd/dpmodel/descriptor/se_e2_a.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ def call(
397397
atype_ext: Array,
398398
nlist: Array,
399399
mapping: Array | None = None,
400+
fparam: Array | None = None,
400401
) -> Array:
401402
"""Compute the descriptor.
402403

deepmd/dpmodel/descriptor/se_r.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ def call(
369369
atype_ext: Array,
370370
nlist: Array,
371371
mapping: Array | None = None,
372+
fparam: Array | None = None,
372373
) -> Array:
373374
"""Compute the descriptor.
374375

deepmd/dpmodel/descriptor/se_t.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ def call(
344344
atype_ext: Array,
345345
nlist: Array,
346346
mapping: Array | None = None,
347+
fparam: Array | None = None,
347348
) -> tuple[Array, Array]:
348349
"""Compute the descriptor.
349350

deepmd/dpmodel/descriptor/se_t_tebd.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ def call(
352352
atype_ext: Array,
353353
nlist: Array,
354354
mapping: Array | None = None,
355+
fparam: Array | None = None,
355356
) -> tuple[Array, Array]:
356357
"""Compute the descriptor.
357358

0 commit comments

Comments
 (0)