Skip to content

fix(pd): add default chg spin parameter#5469

Open
HydrogenSulfate wants to merge 7 commits into
deepmodeling:masterfrom
HydrogenSulfate:copilot/add-default-chg-spin-parameter
Open

fix(pd): add default chg spin parameter#5469
HydrogenSulfate wants to merge 7 commits into
deepmodeling:masterfrom
HydrogenSulfate:copilot/add-default-chg-spin-parameter

Conversation

@HydrogenSulfate

@HydrogenSulfate HydrogenSulfate commented May 27, 2026

Copy link
Copy Markdown
Collaborator

This pull request introduces support for handling charge_spin information in atomic and descriptor models. The main changes add new methods to query and provide default charge_spin values, update the forward methods to accept a charge_spin argument, and refactor code to use this argument consistently instead of the previous fparam field. Additionally, the DPA3 descriptor now supports default charge_spin values and serializes them.

Support for charge_spin in descriptor and atomic models:

  • All descriptor classes (dpa1.py, dpa2.py, dpa3.py, se_a.py, se_t_tebd.py) now implement get_dim_chg_spin, has_default_chg_spin, and get_default_chg_spin methods to standardize how charge and spin information is queried. [1] [2] [3] [4] [5]
  • The forward methods in all descriptor classes, as well as in the atomic model (dp_atomic_model.py), now accept a charge_spin argument instead of (or in addition to) fparam, and all internal logic is updated to use charge_spin. [1] [2] [3] [4] [5] [6]

DPA3 descriptor enhancements:

  • The DPA3 descriptor now supports a default_chg_spin parameter, validates its shape, and serializes it as part of its configuration. [1] [2] [3]
  • The logic for embedding charge and spin in DPA3 is updated to use the new charge_spin argument.

These changes standardize how charge and spin information is handled across descriptors and atomic models, making the codebase more extensible and robust for future features involving charge and spin.

Summary by CodeRabbit

  • New Features

    • Added standardized optional charge/spin (charge_spin, shape nf×2) support across atomic, energy, and model forward pipelines, including model wrapper interfaces.
    • Descriptors now advertise charge/spin capability and defaults; DPA3 can use an optional built-in default charge/spin when not provided.
    • Training now declares and conditionally supplies charge_spin based on model support and configuration.
  • Tests

    • Updated descriptor and DPA3 consistency tests to use the new charge_spin input and improved backend coverage checks.

Copilot AI and others added 3 commits May 27, 2026 03:30
… charge_spin

Co-authored-by: HydrogenSulfate <23737287+HydrogenSulfate@users.noreply.github.com>
…t_tebd descriptors

Co-authored-by: HydrogenSulfate <23737287+HydrogenSulfate@users.noreply.github.com>
…e_spin keyword

Co-authored-by: HydrogenSulfate <23737287+HydrogenSulfate@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 27, 2026 09:21
@dosubot dosubot Bot added the enhancement label May 27, 2026
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Uniform charge-spin interface methods are added to multiple descriptors; DPA3 gains default value support, validation, serialization, and uses charge_spin in its forward embedding when enabled. Forward APIs (model/atomic) and training wrapper accept and propagate charge_spin through the full model stack. Test harness and tests are updated to construct and pass charge_spin instead of fparam.

Changes

Charge-spin descriptor interface and DPA3 implementation

Layer / File(s) Summary
Non-supporting descriptors expose charge-spin interface
deepmd/pd/model/descriptor/dpa1.py, deepmd/pd/model/descriptor/dpa2.py, deepmd/pd/model/descriptor/se_a.py, deepmd/pd/model/descriptor/se_t_tebd.py
DPA1, DPA2, SeA, and SeTTebd add get_dim_chg_spin(), has_default_chg_spin(), and get_default_chg_spin() (reporting no support) and extend forward() to accept an optional charge_spin parameter.
DPA3 descriptor implements charge-spin support
deepmd/pd/model/descriptor/dpa3.py
DPA3 adds default_chg_spin constructor argument with validation (must be two values), stores and serializes it, exposes query methods, and updates forward() to accept charge_spin and compute/insert charge+spin embeddings when add_chg_spin_ebd is enabled.
Model and atomic forward path wiring
deepmd/pd/model/atomic_model/base_atomic_model.py, deepmd/pd/model/atomic_model/dp_atomic_model.py, deepmd/pd/model/model/ener_model.py, deepmd/pd/model/model/make_model.py
BaseAtomicModel, DPAtomicModel, EnergyModel, and CM public forward APIs accept optional charge_spin (nf × 2) and propagate it down; DPAtomicModel uses descriptor defaults when charge_spin is omitted and add_chg_spin_ebd is enabled, and passes charge_spin= to descriptors.
Training data and wrapper integration
deepmd/pd/train/training.py, deepmd/pd/train/wrapper.py
Trainer.get_data() and get_additional_data_requirement() handle charge_spin as a supported input with gating and data requirements (ndof=2, using descriptor defaults when available). ModelWrapper.forward() accepts charge_spin and includes it in the model's input dict.
Test harness and descriptor/model tests
source/tests/consistent/descriptor/common.py, source/tests/consistent/descriptor/test_dpa3.py, source/tests/pd/model/test_dpa3.py, source/tests/consistent/model/test_ener.py
eval_pd_descriptor() conditionally forwards charge_spin based on descriptor support; test_dpa3.py adds add_chg_spin_ebd to parameter grid and constructs/passes charge_spin instead of fparam; TestDPA3.skip_pd simplified; test_ener.py adds PD backend forward consistency coverage.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • iProzd
  • njzjz
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix(pd): add default chg spin parameter' accurately reflects the main objective of standardizing charge/spin support by introducing default values and new query methods across descriptors and atomic models.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deepmd/pd/model/atomic_model/dp_atomic_model.py`:
- Line 335: When constructing the call that sets charge_spin (the line currently
using "charge_spin=fparam if self.add_chg_spin_ebd else None"), ensure that when
self.add_chg_spin_ebd is True but fparam is None you fall back to the configured
default_chg_spin instead of passing None; change the expression to use fparam if
fparam is not None else self.default_chg_spin (only when self.add_chg_spin_ebd
is True), so charge_spin becomes (fparam if fparam is not None else
self.default_chg_spin) when self.add_chg_spin_ebd else None and avoids
triggering the DPA3 assertion path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6949ec6c-cc26-4f0f-a800-8b4381111ee6

📥 Commits

Reviewing files that changed from the base of the PR and between 4e64f8b and db26f5e.

📒 Files selected for processing (6)
  • deepmd/pd/model/atomic_model/dp_atomic_model.py
  • deepmd/pd/model/descriptor/dpa1.py
  • deepmd/pd/model/descriptor/dpa2.py
  • deepmd/pd/model/descriptor/dpa3.py
  • deepmd/pd/model/descriptor/se_a.py
  • deepmd/pd/model/descriptor/se_t_tebd.py

Comment thread deepmd/pd/model/atomic_model/dp_atomic_model.py Outdated
@codecov

codecov Bot commented May 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 46.91358% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.89%. Comparing base (3c44661) to head (c293b3e).
⚠️ Report is 12 commits behind head on master.

Files with missing lines Patch % Lines
deepmd/pd/model/atomic_model/dp_atomic_model.py 31.57% 13 Missing ⚠️
deepmd/pd/model/descriptor/dpa3.py 57.14% 6 Missing ⚠️
deepmd/pd/train/training.py 37.50% 5 Missing ⚠️
deepmd/pd/model/atomic_model/base_atomic_model.py 50.00% 4 Missing ⚠️
deepmd/pd/model/descriptor/dpa1.py 50.00% 3 Missing ⚠️
deepmd/pd/model/descriptor/dpa2.py 50.00% 3 Missing ⚠️
deepmd/pd/model/descriptor/se_a.py 50.00% 3 Missing ⚠️
deepmd/pd/model/descriptor/se_t_tebd.py 50.00% 3 Missing ⚠️
deepmd/pd/model/model/make_model.py 62.50% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5469      +/-   ##
==========================================
+ Coverage   81.41%   81.89%   +0.47%     
==========================================
  Files         871      892      +21     
  Lines       96952   101609    +4657     
  Branches     4240     4242       +2     
==========================================
+ Hits        78938    83217    +4279     
- Misses      16711    17087     +376     
- Partials     1303     1305       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@njzjz njzjz closed this May 30, 2026
@njzjz njzjz reopened this May 30, 2026
Co-authored-by: HydrogenSulfate <23737287+HydrogenSulfate@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
source/tests/consistent/descriptor/common.py (1)

272-282: ⚡ Quick win

Remove dead fparam_pd assignment in PD descriptor helper

fparam_pd is computed from fparam but never used/added to kwargs on the PD path; it’s safe to remove for clarity (Ruff ignores F841 in this repo, so it won’t fail CI).

♻️ Proposed cleanup
-        fparam_pd = (
-            paddle.to_tensor(fparam).to(PD_DEVICE) if fparam is not None else None
-        )
         charge_spin_pd = (
             paddle.to_tensor(charge_spin).to(PD_DEVICE)
             if charge_spin is not None
             else None
         )
         kwargs = {"nlist": nlist, "mapping": mapping}
         if charge_spin_pd is not None:
             kwargs["charge_spin"] = charge_spin_pd
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/tests/consistent/descriptor/common.py` around lines 272 - 282, The
temporary paddle tensor fparam_pd is created but never used; remove the unused
assignment "fparam_pd = (paddle.to_tensor(fparam).to(PD_DEVICE) if fparam is not
None else None)" from the PD descriptor helper so we don't allocate an unused
tensor, and ensure only charge_spin_pd is computed/added to kwargs (leave
PD_DEVICE, paddle usage and the kwargs mapping/nlist logic intact).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@source/tests/consistent/descriptor/common.py`:
- Around line 272-282: The temporary paddle tensor fparam_pd is created but
never used; remove the unused assignment "fparam_pd =
(paddle.to_tensor(fparam).to(PD_DEVICE) if fparam is not None else None)" from
the PD descriptor helper so we don't allocate an unused tensor, and ensure only
charge_spin_pd is computed/added to kwargs (leave PD_DEVICE, paddle usage and
the kwargs mapping/nlist logic intact).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7a52faf4-1efb-473c-9cc2-1c3928995a53

📥 Commits

Reviewing files that changed from the base of the PR and between db26f5e and 6ef79f2.

📒 Files selected for processing (3)
  • source/tests/consistent/descriptor/common.py
  • source/tests/consistent/descriptor/test_dpa3.py
  • source/tests/pd/model/test_dpa3.py

@njzjz njzjz requested a review from iProzd June 1, 2026 13:56
Comment thread source/tests/consistent/descriptor/common.py Fixed
@HydrogenSulfate

Copy link
Copy Markdown
Collaborator Author

@iProzd please help review this PR. Thanks

@iProzd iProzd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for you commit, but it seems that the charge_spin in model level still depends on the fparam. In #5431 , charge_spin is decoupled as an end to end independent param, including training data (charge_spin.npy) and forward. Please refer to this PR and change the corresponding implementation. Thanks!

mapping=mapping,
comm_dict=comm_dict,
fparam=fparam if self.add_chg_spin_ebd else None,
charge_spin=fparam if self.add_chg_spin_ebd else None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

charge_spin and fparam should be two seperated params, instead of getting charge_spin from fparam.

Copilot AI and others added 2 commits June 10, 2026 10:09
…r PT backend

Co-authored-by: HydrogenSulfate <23737287+HydrogenSulfate@users.noreply.github.com>
@HydrogenSulfate HydrogenSulfate requested a review from iProzd June 11, 2026 08:07

@iProzd iProzd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the training parts and the end-to-end training test of charge_spin are still missing. (From reading charge_spin.npy to model forward. Now it only changes the model forward parts.)

…hods

Co-authored-by: HydrogenSulfate <23737287+HydrogenSulfate@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
deepmd/pd/model/model/make_model.py (1)

139-140: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize charge_spin dtype/device via _input_type_cast before forwarding

Line 195 and Line 321 forward raw charge_spin, while other frame-level inputs are normalized through _input_type_cast (Line 168 and Line 309). This can introduce mixed-precision dtype mismatches in the downstream embedding path.

💡 Proposed fix
-            cc, bb, fp, ap, input_prec = self._input_type_cast(
-                coord, box=box, fparam=fparam, aparam=aparam
+            cc, bb, fp, ap, cs, input_prec = self._input_type_cast(
+                coord, box=box, fparam=fparam, aparam=aparam, charge_spin=charge_spin
             )
@@
-                charge_spin=charge_spin,
+                charge_spin=cs,
             )
@@
-            cc_ext, _, fp, ap, input_prec = self._input_type_cast(
-                extended_coord, fparam=fparam, aparam=aparam
+            cc_ext, _, fp, ap, cs, input_prec = self._input_type_cast(
+                extended_coord, fparam=fparam, aparam=aparam, charge_spin=charge_spin
             )
@@
-                charge_spin=charge_spin,
+                charge_spin=cs,
             )
         def _input_type_cast(
             self,
             coord: paddle.Tensor,
             box: paddle.Tensor | None = None,
             fparam: paddle.Tensor | None = None,
             aparam: paddle.Tensor | None = None,
+            charge_spin: paddle.Tensor | None = None,
         ) -> tuple[
             paddle.Tensor,
             paddle.Tensor | None,
             paddle.Tensor | None,
             paddle.Tensor | None,
+            paddle.Tensor | None,
             str,
         ]:
@@
-            _lst: list[paddle.Tensor | None] = [
+            _lst: list[paddle.Tensor | None] = [
                 vv.astype(coord.dtype) if vv is not None else None
-                for vv in [box, fparam, aparam]
+                for vv in [box, fparam, aparam, charge_spin]
             ]
-            box, fparam, aparam = _lst
+            box, fparam, aparam, charge_spin = _lst
@@
-                return coord, box, fparam, aparam, input_prec
+                return coord, box, fparam, aparam, charge_spin, input_prec
             else:
                 pp = self.global_pd_float_precision
                 return (
                     coord.to(pp),
                     box.to(pp) if box is not None else None,
                     fparam.to(pp) if fparam is not None else None,
                     aparam.to(pp) if aparam is not None else None,
+                    charge_spin.to(pp) if charge_spin is not None else None,
                     input_prec,
                 )

Also applies to: 195-196, 268-269, 321-322

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deepmd/pd/model/model/make_model.py` around lines 139 - 140, The charge_spin
parameter is being forwarded raw at multiple locations without dtype/device
normalization, while other frame-level inputs are normalized through
_input_type_cast. In deepmd/pd/model/model/make_model.py at lines 195-196,
268-269, and 321-322, apply the same _input_type_cast normalization to
charge_spin before forwarding it, consistent with how other frame-level inputs
are processed at lines 168 and 309. This ensures uniform dtype/device handling
across all inputs to prevent mixed-precision mismatches in the embedding path.
deepmd/pd/model/atomic_model/base_atomic_model.py (1)

248-249: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

charge_spin is not propagated in the bias-stat forward wrapper

Line 248/Line 304 add charge_spin to atomic inference, but _get_forward_wrapper_func.model_forward (Line 562 onward) still calls forward_common_atomic(...) without charge_spin. In change-by-statistic bias updates, that can drop per-frame charge/spin and produce incorrect bias estimates (or break when defaults are unavailable).

💡 Proposed fix
         def model_forward(
             coord: paddle.Tensor,
             atype: paddle.Tensor,
             box: paddle.Tensor | None,
             fparam: paddle.Tensor | None = None,
             aparam: paddle.Tensor | None = None,
+            charge_spin: paddle.Tensor | None = None,
         ) -> dict[str, paddle.Tensor]:
@@
                 atomic_ret = self.forward_common_atomic(
                     extended_coord,
                     extended_atype,
                     nlist,
                     mapping=mapping,
                     fparam=fparam,
                     aparam=aparam,
+                    charge_spin=charge_spin,
                 )

Also applies to: 304-305, 562-592

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deepmd/pd/model/atomic_model/base_atomic_model.py` around lines 248 - 249,
The `charge_spin` parameter is added to the method signatures around lines
248-249 and 304-305, but it is not being propagated through the forward wrapper
function. In the `_get_forward_wrapper_func` function (around lines 562-592),
the `model_forward` method needs to be updated to accept the `charge_spin`
parameter and pass it to the `forward_common_atomic` method call. Ensure that
`charge_spin` is properly threaded through from the wrapper's input to the
`forward_common_atomic` invocation so that charge and spin information is
preserved during bias-statistic updates and not lost in the inference pipeline.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@deepmd/pd/model/atomic_model/base_atomic_model.py`:
- Around line 248-249: The `charge_spin` parameter is added to the method
signatures around lines 248-249 and 304-305, but it is not being propagated
through the forward wrapper function. In the `_get_forward_wrapper_func`
function (around lines 562-592), the `model_forward` method needs to be updated
to accept the `charge_spin` parameter and pass it to the `forward_common_atomic`
method call. Ensure that `charge_spin` is properly threaded through from the
wrapper's input to the `forward_common_atomic` invocation so that charge and
spin information is preserved during bias-statistic updates and not lost in the
inference pipeline.

In `@deepmd/pd/model/model/make_model.py`:
- Around line 139-140: The charge_spin parameter is being forwarded raw at
multiple locations without dtype/device normalization, while other frame-level
inputs are normalized through _input_type_cast. In
deepmd/pd/model/model/make_model.py at lines 195-196, 268-269, and 321-322,
apply the same _input_type_cast normalization to charge_spin before forwarding
it, consistent with how other frame-level inputs are processed at lines 168 and
309. This ensures uniform dtype/device handling across all inputs to prevent
mixed-precision mismatches in the embedding path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 09db3ca1-14d5-48c3-ba61-61530cc3d526

📥 Commits

Reviewing files that changed from the base of the PR and between 3f3aca7 and c293b3e.

📒 Files selected for processing (5)
  • deepmd/pd/model/atomic_model/base_atomic_model.py
  • deepmd/pd/model/model/make_model.py
  • deepmd/pd/train/training.py
  • deepmd/pd/train/wrapper.py
  • source/tests/consistent/model/test_ener.py
💤 Files with no reviewable changes (1)
  • source/tests/consistent/model/test_ener.py

@njzjz njzjz requested a review from iProzd June 16, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants