Skip to content

refactor(model): express analytical bridging as an explicit linear_ener composition - #5964

Merged
wanghan-iapcm merged 14 commits into
deepmodeling:masterfrom
wanghan-iapcm:refactor-bridging-linear-ener
Aug 15, 2026
Merged

refactor(model): express analytical bridging as an explicit linear_ener composition#5964
wanghan-iapcm merged 14 commits into
deepmodeling:masterfrom
wanghan-iapcm:refactor-bridging-linear-ener

Conversation

@wanghan-iapcm

@wanghan-iapcm wanghan-iapcm commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Close #5948.

A bridged model IS a linear composition, but it was spelled as a bridging_method flag on a non-composite model type. The type you requested was not the type you got, and every builder that accepted the flag re-implemented the composition (and drifted; see #5947).

Canonical spelling

"model": {
  "type": "linear_ener", "weights": "sum", "type_map": ["Ni", "O"],
  "models": [
    {"type": "dpa4", "descriptor": {"...": "..."}, "fitting_net": {"...": "..."}},
    {"type": "inner_potential", "mode": "zbl", "r_inner": 0.8, "r_outer": 1.2}
  ]
}

Changes, by issue task

  1. inner_potential is a config-level model type. Registered in argcheck (mode, r_inner, r_outer), so it can be named as a linear_ener child. It is buildable only inside a composition.
  2. The composition derives the descriptor coupling. The linear builder writes the learned sibling descriptor's inner_clamp_r_inner/_outer from the inner_potential child at build time. The radii are written once; one source of truth.
  3. pair_exclude_types belongs to the composition. The canonical builders do no promotion; the composition-level key governs both children by construction. The legacy promotion semantics of the pt type: "dpa4" builder survive only inside the sugar expansion, so the two bridged routes of Deprecate descriptor-scoped exclude_types in favour of the model-level build-seam owner #5947 can no longer diverge (the ~80 eV builder disagreement is gone: both spellings of the flag now expand identically). Full deletion of the non-bridged promotion stays with Deprecate descriptor-scoped exclude_types in favour of the model-level build-seam owner #5947's deprecation cycle.
  4. bridging_method is sugar with ONE owner. deepmd.utils.bridging.expand_bridging_method expands the flag into the canonical form at every backend's get_model entry. The non-composite builders (get_standard_model, get_sezm_model) fail fast on the flag instead of composing — or, as pt's standard route used to do, silently dropping it.
  5. pt and pt_expt land together. dpmodel gains a real linear_ener config builder (it previously had none); its child-parsing core is shared with pt_expt. pt realizes the canonical form through its existing SeZMModel implementation, so pt checkpoints and physics are unchanged.

Native-scheme spin combines with the canonical form: a top-level spin section on a linear_ener config wraps the composition as NativeSpinEnergyModel (dpmodel/pt_expt) or routes to the SeZM spin builder (pt).

Both spellings are supported, by design

The concise type: "dpa4" + bridging_method form is the recommended user interface (it is shorter, and existing inputs/checkpoints keep working with no migration). The explicit linear_ener + inner_potential form is the canonical internal semantics: all builders construct only it, so the concise form is pure rewriting and cannot drift. examples/water/dpa4/input-zbl.json keeps the concise form; doc/model/dpa4.md documents the concise form first and shows the explicit equivalent.

Tests

  • source/tests/common/test_bridging.py (new): 16 normalizer unit tests — key routing, promotion, mismatch error, spin passthrough, rejections, inactive-flag passthrough.
  • source/tests/common/dpmodel/test_zbl_bridging.py: canonical-vs-sugar tests — identical energy (exact equality) and identical serialized wire dict, plus shape rejections and the standard-builder fail-fast.
  • source/tests/pt/model/test_get_model_bridging.py (new): canonical → SeZMModel with identical serialization to the sugar form; rejections (weights != "sum", non-DPA4 sibling, two inner children); plain linear_ener unaffected.
  • source/tests/pt_expt/model/test_get_model_bridging.py: updated to the new contract (flag on type: standard now composes through get_model); canonical composition, canonical native-spin, serialize parity, builder fail-fasts.
  • Suites run locally and green: dpmodel common (1127 passed), pt_expt test_zbl_bridging / test_get_model_bridging / test_get_model_dpa4 / test_linear_model, pt sezm model + spin + linear suites, consistency test_linear_ener, test_examples.

Known limitations

  • pt is a mapping, not a composition. The pt backend implements bridging inside SeZMModel, so its linear builder maps the canonical config back onto SeZMModel constructor arguments. Physics and checkpoint format are byte-identical to the flag form (pinned by a serialize-equality test), but pt's internal ownership still differs from dpmodel/pt_expt.
  • pt is stricter on split exclusion scopes. A canonical config whose learned child sets descriptor.exclude_types different from the composition's pair_exclude_types raises in pt (SeZM mismatch check) while dpmodel/pt_expt accept the two scopes independently (Deprecate descriptor-scoped exclude_types in favour of the model-level build-seam owner #5947 territory).
  • The non-bridged descriptor.exclude_types promotion in get_sezm_model (pt, pt_expt) is untouched; removing it is Deprecate descriptor-scoped exclude_types in favour of the model-level build-seam owner #5947's staged deprecation.
  • No end-to-end dp train run or GPU .pt2 export with a canonical config in this PR; construction, argcheck, serialization, and energy parity are unit-tested. Two locally failing AOTI freeze tests are the known pre-existing torch 2.11 CPU-SIMD inductor bug (they pass with cpp.simdlen = 1; master fails identically).
  • Multi-task shared_dict combined with an inner_potential child is rejected, not supported.

Summary by CodeRabbit

  • New Features

    • Added linear-energy compositions combining learned, descriptor-based, pair-tabulated, and analytical inner-potential models.
    • Added native-spin support and configurable clamping radii for these compositions.
    • Added automatic ZBL bridging expansion with validation, shared exclusions, and equivalent shorthand/canonical configurations.
    • Added support for bridged models in inference, checkpoint freezing, serialization, and model updates.
  • Documentation

    • Updated DPA4 ZBL bridging guidance, recommended configurations, and compatibility details.

…er composition

Close deepmodeling#5948. A bridged model IS a linear composition, but it was spelled
as a bridging_method flag on a non-composite model type, so the type
requested was not the type returned, and every builder accepting the
flag re-implemented the composition (and drifted, see deepmodeling#5947).

- Register inner_potential as a config-level model type (argcheck), so
  it can be named as a linear_ener child:
  {type: inner_potential, mode: zbl, r_inner, r_outer}.
- The linear builder derives the learned sibling descriptor's
  InnerClamp/BridgingSwitch radii from the inner_potential child at
  build time: the radii are written once, one source of truth.
- Keep bridging_method as sugar expanded by ONE shared normalizer
  (deepmd.utils.bridging.expand_bridging_method) at every backend's
  get_model entry; the non-composite builders (get_standard_model,
  get_sezm_model) now fail fast on the flag instead of composing or
  silently dropping it (pt's standard route used to drop it).
- dpmodel gains a real linear_ener config builder (it had none); the
  linear child-parsing core is shared between dpmodel and pt_expt.
- pt realizes the canonical form via its existing SeZMModel
  implementation, so pt checkpoints and physics are unchanged; both
  spellings serialize to the identical wire dict (pinned by tests in
  all three backends).
- Migrate examples/water/dpa4/input-zbl.json and doc/model/dpa4.md to
  the canonical spelling.
@dosubot dosubot Bot added the breaking change Breaking changes that should notify users. label Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds inner_potential configuration and centralizes bridging_method expansion into canonical linear_ener compositions. Generic, PyTorch, and PyTorch Exportable builders support these compositions, native spin, shared exclusions, clamp-radius derivation, and validation.

Changes

Linear energy bridging

Layer / File(s) Summary
Bridging normalizer and inner-potential contract
deepmd/utils/bridging.py, deepmd/utils/argcheck.py, doc/model/dpa4.md, source/tests/common/test_bridging.py
Defines the inner_potential configuration and expands active bridging settings into canonical linear_ener compositions. Validates exclusions, radii, supported source models, immutability, and spin placement.
Shared linear model construction
deepmd/dpmodel/model/model.py, deepmd/dpmodel/model/model_factory.py, source/tests/common/dpmodel/test_zbl_bridging.py
Adds linear model construction for learned, pair-tabulated, and inner-potential children. Derives descriptor clamp radii, supports native spin, validates composition cardinality, and rejects direct standard-model bridging flags.
PyTorch composition routing
deepmd/pt/model/model/__init__.py, deepmd/pt/model/model/dp_linear_model.py, deepmd/pt/entrypoints/freeze_pt2.py, deepmd/pt/infer/deep_eval.py, source/tests/pt/model/test_get_model_bridging.py
Routes bridged compositions to validated DPA4 or SeZM construction. It preserves plain linear_ener behavior, recognizes bridged checkpoints and inference models, and skips analytical children during selection updates.
PyTorch Exportable routing and native spin
deepmd/pt_expt/model/get_model.py, deepmd/pt_expt/model/dp_linear_model.py, source/tests/pt_expt/model/test_get_model_bridging.py
Moves bridging expansion to dispatcher level and uses shared linear assembly. It supports native-spin wrapping, derived clamp radii, analytical-child filtering, and canonical and shorthand serialization equivalence.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • deepmodeling/deepmd-kit#5901 — Covers the LinearEnergyAtomicModel and inner_potential composition extended by this PR.

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: njzjz, outisli

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant get_model
  participant expand_bridging_method
  participant get_linear_model
  participant LinearEnergyAtomicModel
  Caller->>get_model: submit model configuration
  get_model->>expand_bridging_method: expand bridging_method
  expand_bridging_method-->>get_model: return linear_ener configuration
  get_model->>get_linear_model: dispatch linear_ener composition
  get_linear_model->>LinearEnergyAtomicModel: build learned and inner-potential children
  LinearEnergyAtomicModel-->>get_linear_model: return composed atomic model
  get_linear_model-->>Caller: return energy model
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.84% 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 title clearly summarizes the main change: converting analytical bridging into an explicit linear_ener composition.
Linked Issues check ✅ Passed The changes satisfy issue #5948 by adding inner_potential composition support, centralized expansion, shared exclusions, radius derivation, and cross-backend behavior.
Out of Scope Changes check ✅ Passed The code and documentation changes directly support issue #5948 and its stated cross-backend compatibility, validation, and serialization requirements.
✨ 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.

@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: 2

🧹 Nitpick comments (6)
source/tests/pt_expt/model/test_get_model_bridging.py (1)

209-227: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a rejection test for non-sum weights.

The pt suite pins this contract in source/tests/pt/model/test_get_model_bridging.py::test_canonical_requires_sum_weights, but no dpmodel or pt_expt test does. After the shared builder gains the guard described on deepmd/dpmodel/model/model.py lines 225-232, add the same test here so the three backends stay aligned.

🤖 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/pt_expt/model/test_get_model_bridging.py` around lines 209 -
227, Add a test near test_canonical_matches_sugar_serialize that verifies the
canonical model configuration rejects non-"sum" weights, matching
test_canonical_requires_sum_weights in the pt suite. Use the existing shared
model-building helpers and assert the expected exception or validation failure
after changing the canonical weights value.
deepmd/utils/argcheck.py (1)

3728-3741: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The schema allows inner_potential as a top-level model type.

model_args_plugin.register("inner_potential") adds the type to the top-level model Variant as well as to the linear_ener models list. The doc string states the type is usable only as a sub-model, but no schema rule enforces this. A user who writes "model": {"type": "inner_potential", ...} passes argcheck and then fails inside the backend factory with an unrelated message.

Consider rejecting the type at the dispatcher entry with an explicit message that points to linear_ener.

🤖 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/utils/argcheck.py` around lines 3728 - 3741, Reject top-level use of
the inner_potential model in the argument-checking dispatcher, while continuing
to allow it in the linear_ener models list. Add an explicit validation error for
model type inner_potential that directs users to configure it as a linear_ener
sub-model, before backend construction is reached.
deepmd/dpmodel/model/model.py (1)

187-192: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

A non-DPA4 learned sibling fails with an unrelated TypeError.

Lines 191-192 write inner_clamp_r_inner and inner_clamp_r_outer into the learned child's descriptor config unconditionally. Only the DPA4/SeZM descriptor accepts those keywords. For any other descriptor type the keys reach the constructor through model_components_factory and raise TypeError: __init__() got an unexpected keyword argument 'inner_clamp_r_inner'. The pt backend raises a clear NotImplementedError for this case (deepmd/pt/model/model/__init__.py lines 322-327).

Add the same capability check before deriving the radii, so the message names the real constraint.

🤖 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/dpmodel/model/model.py` around lines 187 - 192, The learned descriptor
clamp radii are currently injected for every descriptor type, causing
non-DPA4/SeZM constructors to reject unexpected keywords. In the composition
logic around learned_descriptor, add the same DPA4/SeZM capability check used by
the pt backend before reading inner_cfg or setting inner_clamp_r_inner and
inner_clamp_r_outer, and raise the clear NotImplementedError for unsupported
descriptors.
deepmd/pt/model/model/__init__.py (1)

291-320: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the docstring with the raised exception type.

The Raises section lists ValueError for shared_dict, and line 296 lists NotImplementedError only for a non-DPA4 learned sibling. Line 318 raises NotImplementedError for shared_dict. Move shared_dict to the NotImplementedError entry, or raise ValueError to match the documented contract.

♻️ Proposed docstring fix
     ValueError
         If the composition shape is not the bridging one (child counts,
-        ``weights``, ``shared_dict``).
+        ``weights``).
     NotImplementedError
-        If the learned sibling is not of the DPA4/SeZM family: the pt
-        backend has no bridging implementation for other descriptors.
+        If the learned sibling is not of the DPA4/SeZM family (the pt
+        backend has no bridging implementation for other descriptors), or
+        if ``shared_dict`` is combined with an ``inner_potential`` child.
🤖 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/pt/model/model/__init__.py` around lines 291 - 320, Align the function
docstring’s Raises section with the implementation: document the shared_dict
rejection under NotImplementedError rather than ValueError, while retaining
ValueError for invalid composition shape and NotImplementedError for unsupported
learned descriptor families.
deepmd/pt_expt/model/get_model.py (1)

299-301: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Import a public helper instead of the private _build_linear_atomic_model.

_build_linear_atomic_model is private by name but is now a cross-package contract between deepmd/dpmodel/model/model.py and this module. A future rename or signature change inside dpmodel breaks pt_expt silently, and the leading underscore tells maintainers the opposite. Export the builder under a public name, for example next to BackendModelFactory in deepmd/dpmodel/model/model_factory.py, and import that name in both backends.

🤖 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/pt_expt/model/get_model.py` around lines 299 - 301, Replace the
private _build_linear_atomic_model dependency with a public builder exported
from deepmd.dpmodel.model.model_factory alongside BackendModelFactory, then
update both backend imports and call sites to use the public symbol while
preserving the existing builder behavior and signature.
source/tests/common/test_bridging.py (1)

144-162: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a case for the uppercase type aliases.

expand_bridging_method lowercases the model type before the membership test, so "DPA4" and "SeZM" are accepted and are preserved verbatim on the learned child. No test pins that behavior. Parametrize test_standard_type_is_supported with "DPA4" and "SeZM" to lock both the acceptance and the preserved spelling.

🤖 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/common/test_bridging.py` around lines 144 - 162, The
test_standard_type_is_supported test currently covers only the lowercase
standard type; parametrize it with "DPA4" and "SeZM" and assert the learned
child’s models[0]["type"] preserves each input spelling verbatim while
confirming expansion succeeds.
🤖 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/dpmodel/model/model.py`:
- Around line 225-232: The shared _build_linear_atomic_model must reject
compositions with inner_potential children unless weights is "sum": inside its
inner_indices block, validate data.get("weights", "mean") and raise ValueError
using the exact message from the pt backend when it differs from "sum". Update
deepmd/dpmodel/model/model.py lines 225-232; deepmd/pt_expt/model/get_model.py
lines 327-332 requires no direct change, but add a pt_expt test mirroring
test_canonical_requires_sum_weights to verify the inherited guard.

In `@deepmd/pt_expt/model/get_model.py`:
- Around line 307-332: Restore shared_dict preprocessing in get_linear_model()
before calling _build_linear_atomic_model(): detect shared_dict, emit the
existing warning, and invoke preprocess_shared_params() so multi-task heads
receive shared component configurations. Preserve the current spin normalization
and ensure the processed model_params, rather than the unexpanded config, is
delegated to _build_linear_atomic_model().

---

Nitpick comments:
In `@deepmd/dpmodel/model/model.py`:
- Around line 187-192: The learned descriptor clamp radii are currently injected
for every descriptor type, causing non-DPA4/SeZM constructors to reject
unexpected keywords. In the composition logic around learned_descriptor, add the
same DPA4/SeZM capability check used by the pt backend before reading inner_cfg
or setting inner_clamp_r_inner and inner_clamp_r_outer, and raise the clear
NotImplementedError for unsupported descriptors.

In `@deepmd/pt_expt/model/get_model.py`:
- Around line 299-301: Replace the private _build_linear_atomic_model dependency
with a public builder exported from deepmd.dpmodel.model.model_factory alongside
BackendModelFactory, then update both backend imports and call sites to use the
public symbol while preserving the existing builder behavior and signature.

In `@deepmd/pt/model/model/__init__.py`:
- Around line 291-320: Align the function docstring’s Raises section with the
implementation: document the shared_dict rejection under NotImplementedError
rather than ValueError, while retaining ValueError for invalid composition shape
and NotImplementedError for unsupported learned descriptor families.

In `@deepmd/utils/argcheck.py`:
- Around line 3728-3741: Reject top-level use of the inner_potential model in
the argument-checking dispatcher, while continuing to allow it in the
linear_ener models list. Add an explicit validation error for model type
inner_potential that directs users to configure it as a linear_ener sub-model,
before backend construction is reached.

In `@source/tests/common/test_bridging.py`:
- Around line 144-162: The test_standard_type_is_supported test currently covers
only the lowercase standard type; parametrize it with "DPA4" and "SeZM" and
assert the learned child’s models[0]["type"] preserves each input spelling
verbatim while confirming expansion succeeds.

In `@source/tests/pt_expt/model/test_get_model_bridging.py`:
- Around line 209-227: Add a test near test_canonical_matches_sugar_serialize
that verifies the canonical model configuration rejects non-"sum" weights,
matching test_canonical_requires_sum_weights in the pt suite. Use the existing
shared model-building helpers and assert the expected exception or validation
failure after changing the canonical weights value.
🪄 Autofix

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 Plus

Run ID: 1e258e88-0fc6-44ba-b700-05cdd71a3e3e

📥 Commits

Reviewing files that changed from the base of the PR and between bc902da and fb52e0f.

📒 Files selected for processing (11)
  • deepmd/dpmodel/model/model.py
  • deepmd/pt/model/model/__init__.py
  • deepmd/pt_expt/model/get_model.py
  • deepmd/utils/argcheck.py
  • deepmd/utils/bridging.py
  • doc/model/dpa4.md
  • examples/water/dpa4/input-zbl.json
  • source/tests/common/dpmodel/test_zbl_bridging.py
  • source/tests/common/test_bridging.py
  • source/tests/pt/model/test_get_model_bridging.py
  • source/tests/pt_expt/model/test_get_model_bridging.py

Comment thread deepmd/dpmodel/model/model.py Outdated
Comment thread deepmd/pt_expt/model/get_model.py

@njzjz-bot njzjz-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.

I found several actionable regressions in the canonical bridging workflow. In particular, the migrated example fails during the default neighbor-stat pass, canonical PT checkpoints are no longer recognized as SeZM/DPA4 for inference or .pt2 freeze, and multiple accepted options are silently ignored.

I used three independent subagent review passes on the same head and then reproduced the findings independently. The new bridging tests pass (71 tests), the existing linear/example regression selection passes (35 tests and 204 subtests), and Ruff reports clean; those tests do not exercise the failing CLI/checkpoint paths described inline.

Codex quota is about to reset, so I am using the remaining token budget to complete a concentrated review pass over the outstanding PRs.

Coding agent: Codex
Codex version: codex-cli 0.144.6
Model: gpt-5.6-sol
Reasoning effort: xhigh

Comment thread examples/water/dpa4/input-zbl.json Outdated
Comment thread deepmd/utils/bridging.py
Comment thread deepmd/pt_expt/model/get_model.py Outdated
Comment thread deepmd/pt/model/model/__init__.py
Comment thread deepmd/utils/bridging.py
Comment thread deepmd/utils/bridging.py
Comment thread deepmd/pt/model/model/__init__.py
Comment thread deepmd/utils/argcheck.py Outdated
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.33333% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.53%. Comparing base (62cd093) to head (edf02fb).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
deepmd/dpmodel/model/model_factory.py 93.65% 4 Missing ⚠️
deepmd/dpmodel/model/model.py 90.90% 2 Missing ⚠️
deepmd/pt/model/model/__init__.py 96.66% 2 Missing ⚠️
deepmd/pt_expt/model/get_model.py 93.54% 2 Missing ⚠️
deepmd/utils/bridging.py 98.73% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5964      +/-   ##
==========================================
- Coverage   79.64%   79.53%   -0.11%     
==========================================
  Files        1085     1086       +1     
  Lines      126583   127382     +799     
  Branches     4592     4598       +6     
==========================================
+ Hits       100811   101313     +502     
- Misses      24120    24416     +296     
- Partials     1652     1653       +1     

☔ 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.

Han Wang added 3 commits August 11, 2026 09:00
…s the explicit equivalent

Both spellings stay supported: the concise 'type: dpa4' +
bridging_method form is the recommended user interface, and the
explicit linear_ener + inner_potential composition is the canonical
internal form it expands to (one shared normalizer defines the
equivalence). Revert the example to the concise form; document both.
…odelFactory

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.

@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: 2

🧹 Nitpick comments (2)
deepmd/dpmodel/model/model_factory.py (2)

229-236: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the atomic-model accessors for rcut and sel. Replace the direct .descriptor access with built[learned_indices[0]].get_rcut() and .get_sel() to avoid coupling the shared factory to backend storage details.

🤖 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/dpmodel/model/model_factory.py` around lines 229 - 236, Update the
loop constructing InnerPotentialAtomicModel so it obtains rcut and sel through
built[learned_indices[0]].get_rcut() and get_sel() directly, removing the
intermediate .descriptor access and preserving the existing constructor
behavior.

199-200: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize the radius defaults. The values currently match across the schema, normalizer, and factory. Use shared constants to prevent future drift; explicit linear_ener configs can still reach these fallbacks directly.

🤖 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/dpmodel/model/model_factory.py` around lines 199 - 200, Replace the
hard-coded 0.5 and 0.8 fallbacks in the learned descriptor construction with the
shared radius-default constants already used by the schema and normalizer.
Update the relevant inner_cfg fallback lookups while preserving explicit
linear_ener configuration values and the existing float conversion.
🤖 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/dpmodel/model/model_factory.py`:
- Line 182: Update the learned_indices construction in the model factory to
include descriptor-bearing children only when their index is not in
inner_indices. Preserve the existing inner_potential detection and ensure
overlapping children are excluded so the guard in the surrounding factory logic
raises the intended ValueError.
- Around line 237-245: Update the LinearEnergyAtomicModel construction in the
model factory to detect compositions whose linear_ener children include an
inner_potential and require weights to be explicitly set to "sum"; reject
omitted or "mean" weights instead of applying the current default. Preserve
existing weight handling for non-bridging compositions.

---

Nitpick comments:
In `@deepmd/dpmodel/model/model_factory.py`:
- Around line 229-236: Update the loop constructing InnerPotentialAtomicModel so
it obtains rcut and sel through built[learned_indices[0]].get_rcut() and
get_sel() directly, removing the intermediate .descriptor access and preserving
the existing constructor behavior.
- Around line 199-200: Replace the hard-coded 0.5 and 0.8 fallbacks in the
learned descriptor construction with the shared radius-default constants already
used by the schema and normalizer. Update the relevant inner_cfg fallback
lookups while preserving explicit linear_ener configuration values and the
existing float conversion.
🪄 Autofix

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 Plus

Run ID: 76e77054-4fc4-4371-891c-676ab2697819

📥 Commits

Reviewing files that changed from the base of the PR and between 1994c2c and 2fde06a.

📒 Files selected for processing (3)
  • deepmd/dpmodel/model/model.py
  • deepmd/dpmodel/model/model_factory.py
  • deepmd/pt_expt/model/get_model.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • deepmd/dpmodel/model/model.py
  • deepmd/pt_expt/model/get_model.py

Comment thread deepmd/dpmodel/model/model_factory.py Outdated
Comment thread deepmd/dpmodel/model/model_factory.py
@wanghan-iapcm
wanghan-iapcm requested review from OutisLi and njzjz August 11, 2026 01:58
…ders

Guards (shared builder, dpmodel + pt_expt via BackendModelFactory):
- a bridged composition requires weights: "sum" ("mean" silently
  halved both energy terms; pt already rejected it)
- an inner_potential child must not carry a descriptor (was a KeyError)
- a bridging_method flag on a linear child is rejected instead of
  silently dropped (also guarded in the pt linear builder)

pt canonical realization:
- reject lora on the learned child (the trainer reads top-level lora
  only, so it would silently train without adapters)
- reject a child type_map that differs from the composition's instead
  of silently overwriting it

pt checkpoint consumers: _is_sezm_model_params, is_sezm_checkpoint and
freeze_sezm_to_pt2 now recognize the canonical bridged spelling via the
shared is_bridged_sezm_config predicate, so DeepPot no-jit routing and
the .pt2 freeze route work for canonical checkpoints.

update_sel (pt + pt_expt linear models): skip inner_potential children
instead of crashing with KeyError: 'descriptor' on the default CLI path.

pt_expt: DPA4/SeZM-family linear children now route through
get_sezm_model via a descriptor_child_builder hook on the shared
builder, restoring the family defaults and the loud rejections of
lora / use_compile / preset_out_bias.

argcheck: bridging args shared with the standard variant (the
documented standard sugar previously failed strict normalization);
inner_potential is now child-only (top-level model.type:
inner_potential is rejected at normalization, not at build).
@wanghan-iapcm

Copy link
Copy Markdown
Collaborator Author

All review findings are addressed in b25bd482a, each pinned by a new test. Resolution per finding:

Fixed

  • weights: "mean" accepted with inner_potential (CodeRabbit, 2×): the shared builder now requires weights: "sum" for bridged compositions, so dpmodel/pt_expt match pt's rejection — model_factory.py guard; tests in all three backends.
  • learned_indices / inner_indices overlap (CodeRabbit): exclusivity applied, plus an explicit rejection of an inner_potential child carrying a descriptor (a config error should not surface as KeyError).
  • [P1] update_sel KeyError on inner_potential children: both linear update_sel implementations (pt, pt_expt) skip analytical children; covered by monkeypatch tests asserting only the learned child is visited.
  • [P1] PT checkpoint consumers miss the canonical shape: new shared predicate is_bridged_sezm_config wired into _is_sezm_model_params, is_sezm_checkpoint (single-task and model_dict branches) and the freeze_sezm_to_pt2 guard; tests cover the no-jit routing and both checkpoint layouts.
  • [P1] pt_expt learned child bypasses get_sezm_model: DPA4/SeZM-family children now route through get_sezm_model via a descriptor_child_builder hook on the shared builder, restoring the descriptor/fitting type defaults and the loud lora/use_compile/preset_out_bias rejections; tests pin the defaults and the lora rejection.
  • [P1] canonical child lora silently ignored in pt: rejected with a NotImplementedError directing to the concise form with top-level lora (the trainer owns adapter injection and reads the top level only).
  • [P1] nested bridging_method on linear children silently dropped: rejected in the shared builder and in the pt linear builder.
  • [P2] standard sugar unreachable through argcheck: the three bridging arguments are now shared between the dpa4 and standard variants (_bridging_method_args()), so the documented standard spelling passes strict normalization.
  • [P2] pt overwrites an explicit child type_map: now fails fast on mismatch, as suggested.
  • [P2] model.type: inner_potential valid at top level: de-registered from model_args_plugin; injected only into the linear_ener models variant via a new extra_model_types parameter of model_args(), so top-level use is rejected at normalization.

Skipped (with reason)

  • Restore shared_dict for pt_expt linear_ener (CodeRabbit): not a regression — the pt_expt get_linear_model never had shared_dict handling (verified against upstream/master); only pt implements it. Behavior is unchanged by this PR.

The failing Test C++ (false, false, false, true) job was an infrastructure flake (Paddle inference-library download failure in CMake FetchContent), unrelated to this PR; the new push re-runs it.

@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

Caution

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

⚠️ Outside diff range comments (1)
deepmd/dpmodel/model/model_factory.py (1)

234-251: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject child type_map values that differ from the composition type_map.

Line 239 only fills a missing child map. Line 251 accepts an explicit, different child map. A reordered map with the same length does not fail construction, but the learned child then interprets atom-type indices differently from LinearEnergyAtomicModel and InnerPotentialAtomicModel.

Validate equality before building any child. Add a regression test with reordered child types.

Proposed fix
         if "type_map" not in sub:
             sub["type_map"] = copy.deepcopy(type_map)
+        elif sub["type_map"] != type_map:
+            raise ValueError(
+                "A linear_ener sub-model `type_map` must match the "
+                "composition `type_map`."
+            )
🤖 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/dpmodel/model/model_factory.py` around lines 234 - 251, The
child-building loop must reject any explicit child type_map that differs from
the composition type_map, including reordered entries. In the child handling
around descriptor_child_builder and atomic_model, validate an existing
sub["type_map"] for exact equality before constructing the child; retain the
deep-copied composition map only when it is missing, and raise the established
model-construction error for mismatches. Add a regression test covering a
reordered child type_map.
🤖 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/pt_expt/model/get_model.py`:
- Around line 300-302: Update the child-type selection in the callback around
get_sezm_model so it also reads sub["descriptor"]["type"] when the child-level
type is absent, normalizes recognized DPA4/SeZM variants to the expected model
type, and then routes the child through get_sezm_model. Add a regression test
covering a descriptor-typed child with no child-level type.

---

Outside diff comments:
In `@deepmd/dpmodel/model/model_factory.py`:
- Around line 234-251: The child-building loop must reject any explicit child
type_map that differs from the composition type_map, including reordered
entries. In the child handling around descriptor_child_builder and atomic_model,
validate an existing sub["type_map"] for exact equality before constructing the
child; retain the deep-copied composition map only when it is missing, and raise
the established model-construction error for mismatches. Add a regression test
covering a reordered child type_map.
🪄 Autofix

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 Plus

Run ID: bab72aa1-32d6-42fd-9f9c-41d9d3713767

📥 Commits

Reviewing files that changed from the base of the PR and between 2fde06a and 508ac20.

📒 Files selected for processing (13)
  • deepmd/dpmodel/model/model_factory.py
  • deepmd/pt/entrypoints/freeze_pt2.py
  • deepmd/pt/infer/deep_eval.py
  • deepmd/pt/model/model/__init__.py
  • deepmd/pt/model/model/dp_linear_model.py
  • deepmd/pt_expt/model/dp_linear_model.py
  • deepmd/pt_expt/model/get_model.py
  • deepmd/utils/argcheck.py
  • deepmd/utils/bridging.py
  • source/tests/common/dpmodel/test_zbl_bridging.py
  • source/tests/common/test_bridging.py
  • source/tests/pt/model/test_get_model_bridging.py
  • source/tests/pt_expt/model/test_get_model_bridging.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • deepmd/pt/model/model/init.py

Comment thread deepmd/pt_expt/model/get_model.py Outdated
Han Wang added 2 commits August 11, 2026 12:11
… family builder

A child spelled 'type: standard' with a DPA4/SeZM descriptor (the shape
the sugar on 'type: standard' expands to) bypassed get_sezm_model and
so lost the family defaults and the loud lora/use_compile/
preset_out_bias rejections. The child-builder hook now keys on the
descriptor type too, matching the pt backend's routing.
…pcm/deepmd-kit into refactor-bridging-linear-ener

@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 (1)
deepmd/pt_expt/model/get_model.py (1)

332-362: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Inject use_spin into defaulted DPA4/SeZM children.

get_linear_model injects use_spin only when a child already contains "descriptor". However, _dpa4_family_child_builder accepts a type-only DPA4/SeZM child and lets get_sezm_model create its descriptor defaults later. That descriptor then misses use_spin, so the native-spin capability check can reject an otherwise valid composition.

Initialize the descriptor before setting use_spin for learned DPA4/SeZM children. Do not modify inner_potential or pairtab children. Add a regression test for a type-only DPA4 child with top-level native spin.

Proposed fix
 for sub in model_params["models"]:
-    if "descriptor" in sub:
+    child_type = str(sub.get("type", "")).lower()
+    if "descriptor" in sub or child_type in ("dpa4", "sezm"):
+        sub.setdefault("descriptor", {})
         sub["descriptor"]["use_spin"] = use_spin
🤖 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/pt_expt/model/get_model.py` around lines 332 - 362, Update the
native-spin child-processing logic in get_linear_model to initialize a missing
descriptor for learned DPA4/SeZM children before assigning use_spin, while
leaving inner_potential and pairtab children unchanged. Preserve existing
descriptors and ensure type-only children receive the spin setting before
_dpa4_family_child_builder creates defaults; add a regression test covering a
type-only DPA4 child with top-level native spin.
🤖 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/pt_expt/model/get_model.py`:
- Around line 332-362: Update the native-spin child-processing logic in
get_linear_model to initialize a missing descriptor for learned DPA4/SeZM
children before assigning use_spin, while leaving inner_potential and pairtab
children unchanged. Preserve existing descriptors and ensure type-only children
receive the spin setting before _dpa4_family_child_builder creates defaults; add
a regression test covering a type-only DPA4 child with top-level native spin.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b8cdb3d6-8794-483e-8fa5-bd5df8930dc3

📥 Commits

Reviewing files that changed from the base of the PR and between 508ac20 and 2f32baf.

📒 Files selected for processing (2)
  • deepmd/pt_expt/model/get_model.py
  • source/tests/pt_expt/model/test_get_model_bridging.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/tests/pt_expt/model/test_get_model_bridging.py

@wanghan-iapcm wanghan-iapcm added the Test CUDA Trigger test CUDA workflow label Aug 11, 2026
@github-actions github-actions Bot removed the Test CUDA Trigger test CUDA workflow label Aug 11, 2026

@njzjz-bot njzjz-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.

Three blocking correctness issues were independently confirmed and reproduced against the current PR head: the recommended bridging+LoRA form regresses, the normalized default neighbor-stat path still crashes, and the shared builder accepts bridge compositions with no executable graph/dense route. Details are attached inline.

Coding agent: Codex
Codex version: codex-cli 0.144.6
Model: gpt-5.6-sol
Reasoning effort: xhigh

Comment thread deepmd/utils/bridging.py Outdated
Comment thread deepmd/pt/model/model/dp_linear_model.py
Comment thread deepmd/dpmodel/model/model_factory.py Outdated

@njzjz njzjz 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.

see above

1. Sugar expansion keeps trainer-owned top-level `lora` at the
   composition level instead of forwarding it to the learned child
   (which the pt bridge builder rejects), restoring the concise
   dpa4+bridging_method+lora form. The key routing is now a four-way
   table (composition / consumed / trainer / learned-child) whose
   coverage of the standard+dpa4 argcheck schemas is pinned by
   test_routing_covers_the_argcheck_schema: a new argcheck model key
   fails the test until it gets an explicit routing decision, instead
   of silently landing on the child. The guard immediately surfaced
   use_compile and enable_tf32 as previously-undecided keys (both
   routed to the learned child, preserving behavior).

2. pt LinearEnergyModel.update_sel: the shared-config reconstruction
   loop now skips the `inner_potential` child too. Normalization
   always inserts `shared_dict: {}`, so the default CLI path entered
   the reconstruction loop and dereferenced the analytical child's
   missing descriptor (KeyError). New test runs on a NORMALIZED config.

3. The shared dpmodel/pt_expt linear builder rejects bridged
   compositions with a third child (e.g. pairtab): no common execution
   route exists (pairtab is dense-only, the bridged pair graph-only),
   matching the pt builder's exact-two-child constraint and message.
Comment thread deepmd/utils/bridging.py Fixed
@wanghan-iapcm
wanghan-iapcm requested a review from njzjz August 12, 2026 10:08

@OutisLi-Bot OutisLi-Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review (head d725272)

Re-checked the previously blocking items against current head (neighbor-stat / update_sel with inner_potential, concise form + top-level lora, canonical checkpoint / DeepEval / .pt2 recognition, mixed bridge graphs, nested sugar rejection, DPA4-family child routing). Those look fixed in code, and CI on this head looks green.

I did a second adversarial pass focused on training/infer regressions and silent config drops on the recommended dpa4 + bridging_method path and did not find a remaining blocker of that class.

Approve.

@njzjz-bot njzjz-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.

Three independent review passes plus a final verification pass found five reproducible correctness issues on the current head: dpmodel neighbor-stat dispatch crashes for the newly registered composition, dpmodel/pt_expt accept a bridged type-map shape that cannot execute, the PT spin route bypasses exclusion validation, explicit canonical configs silently drop learned-model options, and pt_expt silently ignores LoRA after sugar expansion. Details are attached inline.

Coding agent: Codex
Codex version: codex-cli 0.144.6
Model: gpt-5.6-sol
Reasoning effort: xhigh

Comment thread deepmd/dpmodel/model/model.py
Comment thread deepmd/dpmodel/model/model_factory.py
Comment thread deepmd/pt/model/model/__init__.py
Comment thread deepmd/pt/model/model/__init__.py
Comment thread deepmd/pt_expt/model/get_model.py

@njzjz njzjz 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.

see above again

Han Wang added 2 commits August 14, 2026 09:09
- dpmodel LinearEnergyModel gains a composite update_sel (twin of
  pt_expt): update learned children, skip inner_potential/pairtab,
  aggregate min_nbor_dist; BaseModel.update_sel dispatch no longer
  crashes with KeyError 'descriptor' on normalized linear configs.
- the shared linear builder rejects a bridged learned child whose
  type_map differs from the composition's (the graph route rejects the
  non-identity remap on every forward; fail at construction like pt).
- pt SeZM pair-exclusion reconciliation is factored into ONE helper
  used by all three builders (plain, native spin, virtual spin), so a
  pair_exclude_types vs descriptor.exclude_types mismatch fails fast on
  the spin routes too instead of being silently overwritten.
- canonical top-level learned-model options (data_stat_protect,
  preset_out_bias, use_compile, ...) are routed to the learned child by
  route_canonical_learned_options with conflict checks, in both the pt
  bridged builder and the shared factory; silently dropped before.
- pt_expt get_model rejects trainer-owned lora after bridge expansion
  (pt_expt has no LoRA support; it silently trained a plain model).
- module-level assert pins the routing tables disjoint (also resolves
  the CodeQL unused-variable finding on _LEARNED_CHILD_KEYS).
@wanghan-iapcm
wanghan-iapcm requested a review from njzjz August 14, 2026 06:48
Self-audit of the linear_ener key x backend x route matrix (the same
probe pattern the review rounds used) found four residual holes; all are
closed by explicit rejections instead of silent drops:

- the shared linear builder (dpmodel/pt_expt) now rejects top-level
  `lora`, non-empty `shared_dict`, and child-level `lora` -- the pt
  backend's consumers (trainer lora, linear shared_dict) do not exist in
  these backends, so all three silently built a different model than the
  config asked for (empty shared_dict from strict normalization still
  passes).
- the shared builder mirrors pt's DPA4/SeZM family restriction on the
  bridged learned sibling: other families died on an obscure
  unknown-kwarg TypeError from the clamp injection.
- tf registers linear_ener but cannot build inner_potential children;
  its update_sel now rejects them explicitly before any neighbor
  statistics run, instead of an 'unknown model type' dispatch error.
- a table-walk test runs EVERY _LEARNED_CHILD_KEYS entry through
  route_canonical_learned_options (copy-down and conflict branches), so
  a future per-key special case cannot land untested.

All rejections are pinned by tests verified to fail without the guards.

@njzjz njzjz 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.

[P1] Preserve top-level canonical options after argcheck normalization

route_canonical_learned_options() cannot distinguish a child value explicitly set by the user from a default inserted by argcheck normalization. On the normal CLI path, normalization populates learned-child defaults before this helper runs. A valid canonical config such as a top-level data_stat_protect: 0.123 can therefore acquire the child schema default (for example 0.01) and then be rejected as a parent/child conflict.

This makes the advertised composition-level routing unusable for learned-owned options whenever the requested top-level value differs from the child default. It potentially affects data_stat_protect, data_stat_nbatch, preset_out_bias, use_compile, enable_tf32, and the other fields in _LEARNED_CHILD_KEYS.

Please route these options before child defaults are injected, avoid duplicating the relevant defaults at the child level for canonical compositions, or retain enough provenance to reject only two explicitly configured conflicting values. Please add a regression test that first normalizes a canonical config with a non-default top-level learned option and then calls get_model().

Delegated review submitted on behalf of @njzjz by ChatGPT.

Agent: ChatGPT
Version: 1.2026.209 (client build)
Model: GPT-5.6 Pro

Strict normalization injects schema defaults on BOTH the composition top
level and the learned child before any builder runs, so the previous
strict conflict check rejected every explicitly set top-level
learned-owned option on the normal CLI path (top-level 0.123 vs the
injected child default 0.01) -- the advertised composition-level routing
was unusable.

route_canonical_learned_options now recovers the lost provenance by
comparing each level against the key's argcheck default (collected once
from the model schema, with a consistency check): a level holding
exactly the default is treated as not explicitly configured and the
other level wins; only two explicit non-default values still raise.
Known residual ambiguity: explicitly setting a level to the default
value is indistinguishable from not setting it.

Regression per review: normalize a canonical config with a top-level
data_stat_protect=0.123 (asserting the child default injection), then
get_model() must build with 0.123; verified to fail before the fix.
@wanghan-iapcm

Copy link
Copy Markdown
Collaborator Author

@njzjz Confirmed and fixed in edf02fb, using the provenance-recovery option you listed.

route_canonical_learned_options now compares each level against the key's argcheck default (collected once from the model schema via a cached walk, with a consistency check that refuses to guess if a key were ever declared with two different defaults): a level holding exactly the schema default is treated as not explicitly configured, so an explicit top-level data_stat_protect: 0.123 wins over the injected child default 0.01 on the normal CLI path — for every key in _LEARNED_CHILD_KEYS, since the resolution is inside the one shared helper used by both the pt builder and the dpmodel/pt_expt factory. Only two explicitly configured non-default values still raise a conflict.

The regression test is exactly the one you asked for: test_normalized_canonical_top_level_option_survives_child_defaults normalizes a canonical config with top-level data_stat_protect: 0.123 through model_args().normalize_value(...), asserts the child indeed acquired the injected 0.01 default, then builds via get_model() and asserts the model carries 0.123 (verified to fail before the fix). Unit tests cover all three resolution branches (child-default → top wins, top-default → child wins, two non-defaults → error).

Known residual ambiguity, documented in the helper's docstring: explicitly setting a level to exactly the default value is indistinguishable from not setting it, and loses to an explicit non-default on the other level.

@wanghan-iapcm
wanghan-iapcm requested a review from njzjz August 15, 2026 04:54

@njzjz njzjz 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.

Re-reviewed the fix at head edf02fb7 with particular attention to the normal argcheck-normalize → model-build path and to failure modes introduced by schema-default inference.

The previous blocker is resolved: an injected child default no longer creates a false conflict with an explicit top-level learned option, while two different non-default values still fail loudly. The new integration regression test exercises the actual normalized canonical configuration before get_model(), rather than testing only the routing helper in isolation.

I also checked the less-obvious risks in the implementation: the schema traversal is finite for the current model schema, inconsistent duplicate defaults fail explicitly rather than being guessed, the result is cached only after successful collection, and the routing helper is shared by the affected backend paths. The documented ambiguity between “unset” and “explicitly set to the schema default” is inherent once normalization has erased provenance; it only affects duplicate parent/child declarations and is not a merge blocker here.

All current checks for this head have completed without a failure. I did not find another blocking correctness regression in the updated change.

Approved.

Delegated review submitted on behalf of @njzjz by ChatGPT.

Agent: ChatGPT
Model: GPT-5.6 Pro

@wanghan-iapcm
wanghan-iapcm added this pull request to the merge queue Aug 15, 2026
Merged via the queue into deepmodeling:master with commit ed691aa Aug 15, 2026
58 checks passed
@wanghan-iapcm
wanghan-iapcm deleted the refactor-bridging-linear-ener branch August 15, 2026 12:54
wanghan-iapcm pushed a commit to wanghan-iapcm/deepmd-kit that referenced this pull request Aug 15, 2026
Conflict in deepmd/pt_expt/model/get_model.py: master (deepmodeling#5964) removed
_compose_bridging in favour of the canonical linear_ener route, so the
branch-side edit to that function is obsolete -- took master's deletion.

The new route regressed this branch's use_amp contract, caught by
TestUseAmpSurvivesAssembly: get_linear_atomic_model hardcoded the dpmodel
InnerPotential/LinearEnergy atomic classes, so pt_expt's composition was a
raw dpmodel instance that LinearEnergyModel(atomic_model_=...) had to
convert -- and conversion round-trips through deserialize(serialize()),
which keeps only the portable record. Added inner_potential_model and
linear_atomic_model to the backend-class injection the factory already
does for atomic_model/pairtab_model/zbl_model, and pt_expt now passes its
wrapped classes, so the composition is assembled from backend-native
children with no conversion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change Breaking changes that should notify users. Docs Examples Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Express analytical bridging as an explicit linear_ener composition instead of a bridging_method flag

6 participants