Skip to content

Commit 3009cd2

Browse files
committed
fix(dpa4c): address review on metrics, compression and charge states
Full validation reports stress alongside the per-atom virial instead of replacing it, so the released `v:mae`/`v:rmse` selectors keep working. Stress becomes a field of the shared energy-type metrics, which repairs the TF2 and JAX validators that project the shared key map and had silently lost the second-rank column. The five parallel tables of a metric profile collapse into one family declaration, and the log reports whichever of stress and per-atom virial the selected metric names. Compression now rejects a non-empty `exclude_types` instead of emitting an artifact that can never reach the fused kernel, and the charge-state domain travels from the model through the archive metadata to the evaluator, so a condition that addresses no table row is refused on both the folded and the input-tensor path. Descriptors that embed the condition continuously declare no ranges and are unaffected. Also repairs a broken `LOG_COLUMN_ORDER` import that left TF2 full validation unimportable, corrects the `@since` of `DP_DeepPotComputeCanonicalGraphGPU`, fixes the LAMMPS spin example workflow, and restructures the DPA4C manual around deployment, including the Kokkos requirement of the device-resident inference path.
1 parent 9df1e97 commit 3009cd2

29 files changed

Lines changed: 1116 additions & 599 deletions

File tree

deepmd/dpmodel/atomic_model/base_atomic_model.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ def get_default_chg_spin(self) -> list[float] | None:
254254
"""Get the default charge_spin values."""
255255
return None
256256

257+
def get_chg_spin_table_ranges(self) -> list[tuple[int, int]] | None:
258+
"""Get the row range each charge_spin value indexes, or None."""
259+
return None
260+
257261
def reinit_atom_exclude(
258262
self,
259263
exclude_types: list[int] = [],

deepmd/dpmodel/atomic_model/dp_atomic_model.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ def get_default_chg_spin(self) -> list[float] | None:
154154
return self.descriptor.get_default_chg_spin()
155155
return None
156156

157+
def get_chg_spin_table_ranges(self) -> list[tuple[int, int]] | None:
158+
"""Get the row range each charge_spin value indexes, or None."""
159+
if self.add_chg_spin_ebd:
160+
return self.descriptor.get_chg_spin_table_ranges()
161+
return None
162+
157163
def uses_graph_lower(self) -> bool:
158164
"""Delegates to this model's own descriptor."""
159165
return bool(self.descriptor.uses_graph_lower())

deepmd/dpmodel/atomic_model/linear_atomic_model.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,20 @@ def get_default_chg_spin(self) -> "Array | None":
740740
lambda m: m.get_default_chg_spin(),
741741
)[1]
742742

743+
def get_chg_spin_table_ranges(self) -> list[tuple[int, int]] | None:
744+
"""The shared table row ranges, if the children agree.
745+
746+
A condition reaches every consuming child, so it must address the
747+
tables of all of them. Children that disagree share no acceptable
748+
state, which the composition reports as an unconstrained domain
749+
rather than silently enforcing one child's tables on the others.
750+
"""
751+
return self._agreed_default(
752+
self._chg_spin_consumers(),
753+
lambda m: m.get_chg_spin_table_ranges() is not None,
754+
lambda m: m.get_chg_spin_table_ranges(),
755+
)[1]
756+
743757
def has_default_fparam(self) -> bool:
744758
"""Whether every active child shares one default frame parameter."""
745759
return self._agreed_default(

deepmd/dpmodel/descriptor/dpa4c.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
resolve_swiglu_hidden_width,
7474
)
7575
from .dpa4c_nn import (
76+
CHARGE_STATE_TABLE_RANGES,
7677
ChargeStateEmbedding,
7778
InvariantReadout,
7879
OrderedPairFiLM,
@@ -2003,6 +2004,19 @@ def get_default_chg_spin(self) -> list[float] | None:
20032004
"""Return the fallback ``[charge, multiplicity]``, if configured."""
20042005
return self.default_chg_spin
20052006

2007+
def get_chg_spin_table_ranges(self) -> list[tuple[int, int]] | None:
2008+
"""Return the row range each value of the frame condition indexes.
2009+
2010+
The condition is embedded by gathering one row of the charge table and
2011+
one of the multiplicity table, so an acceptable state is a pair of
2012+
integers inside these half-open ranges. A folded condition indexes the
2013+
same tables at rebuild time, so the ranges hold whether or not the
2014+
descriptor is compressed.
2015+
"""
2016+
if self.charge_spin_embedding is None:
2017+
return None
2018+
return [tuple(rng) for rng in CHARGE_STATE_TABLE_RANGES]
2019+
20062020
def has_message_passing_across_ranks(self) -> bool:
20072021
"""Return whether intermediate halo communication is required."""
20082022
return False

deepmd/dpmodel/descriptor/dpa4c_nn/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
enumerate_degree_triples,
99
)
1010
from .charge_state import (
11+
CHARGE_STATE_TABLE_RANGES,
1112
ChargeStateEmbedding,
1213
canonicalize_charge_spin,
1314
validate_charge_state,
@@ -33,6 +34,7 @@
3334
)
3435

3536
__all__ = [
37+
"CHARGE_STATE_TABLE_RANGES",
3638
"MAX_ANGULAR_DEGREE",
3739
"NEIGHBOR_QUADRUPOLE_CHANNELS",
3840
"BispectrumLayout",

deepmd/dpmodel/descriptor/dpa4c_nn/charge_state.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@
8585
#: Half-open range of representable spin multiplicities.
8686
MULTIPLICITY_RANGE = (0, MULTIPLICITY_TABLE_ROWS)
8787

88+
#: Name of each value of a charge state, in order, for diagnostics.
89+
CHARGE_STATE_FIELDS = ("charge", "multiplicity")
90+
91+
#: Half-open row range addressed by each value of a charge state, in order.
92+
#: A condition is a pair of table row indices, so a host-side boundary that
93+
#: knows these ranges can reject an unaddressable state without knowing which
94+
#: descriptor holds the tables.
95+
CHARGE_STATE_TABLE_RANGES = (CHARGE_RANGE, MULTIPLICITY_RANGE)
96+
8897

8998
def validate_charge_state(charge_spin: Any) -> list[float]:
9099
"""Check that a frame condition addresses a row of each embedding table.
@@ -120,11 +129,11 @@ def validate_charge_state(charge_spin: Any) -> list[float]:
120129
)
121130
for value, name, (low, high) in zip(
122131
values,
123-
("charge", "multiplicity"),
124-
(CHARGE_RANGE, MULTIPLICITY_RANGE),
132+
CHARGE_STATE_FIELDS,
133+
CHARGE_STATE_TABLE_RANGES,
125134
strict=True,
126135
):
127-
if value != int(value):
136+
if not np.isfinite(value) or value != int(value):
128137
raise ValueError(f"The {name} must be an integer, got {value}")
129138
if not low <= value < high:
130139
raise ValueError(

deepmd/dpmodel/descriptor/make_base_descriptor.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,17 @@ def get_default_chg_spin(self) -> Any:
113113
"""Returns the default charge_spin value, or None."""
114114
return None
115115

116+
def get_chg_spin_table_ranges(self) -> list[tuple[int, int]] | None:
117+
"""Returns the row range each charge_spin value indexes, or None.
118+
119+
A descriptor that embeds the condition by indexing tables reports
120+
one half-open range per value, which makes every acceptable state
121+
an integer tuple inside those ranges. ``None``, the default, means
122+
the condition enters as a continuous quantity and only its width
123+
is constrained.
124+
"""
125+
return None
126+
116127
def get_geo_compress(self) -> bool:
117128
"""Return whether geometric tabulated compression is active.
118129

deepmd/dpmodel/model/base_model.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ def get_default_chg_spin(self) -> list | None:
125125
"""
126126
return None
127127

128+
def get_chg_spin_table_ranges(self) -> list[tuple[int, int]] | None:
129+
"""Return the row range each charge/spin value indexes, or ``None``.
130+
131+
``None`` means the condition is a continuous quantity, so only its
132+
width is constrained; a list means every acceptable value is an
133+
integer inside the matching half-open range.
134+
"""
135+
return None
136+
128137
def get_var_name(self) -> str | None:
129138
"""Return the fitted property's variable name, or ``None`` if
130139
this is not a property model. ``is not None`` is the support

deepmd/dpmodel/model/make_model.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,6 +1163,10 @@ def get_default_chg_spin(self) -> list[float] | None:
11631163
"""Get the default charge_spin values."""
11641164
return self.atomic_model.get_default_chg_spin()
11651165

1166+
def get_chg_spin_table_ranges(self) -> list[tuple[int, int]] | None:
1167+
"""Get the row range each charge_spin value indexes, or None."""
1168+
return self.atomic_model.get_chg_spin_table_ranges()
1169+
11661170
def get_sel_type(self) -> list[int]:
11671171
"""Get the selected atom types of this model.
11681172

deepmd/dpmodel/train/validation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def __init__(
232232
restart_training and self.full_val_file.exists()
233233
)
234234
self.table_column_specs = []
235-
for column_name, metric_key in self.profile.column_order:
235+
for column_name, metric_key in self.profile.columns(self.metric_name):
236236
_, metric_unit = format_metric_value_for_table(metric_key, 1.0)
237237
header_label = f"{column_name}({metric_unit})"
238238
self.table_column_specs.append(

0 commit comments

Comments
 (0)