Skip to content

Fix stress normalization for partially periodic systems (#1509)#1523

Closed
darthjaja6 wants to merge 1 commit into
ACEsuit:developfrom
darthjaja6:fix/slab-stress-volume-1509
Closed

Fix stress normalization for partially periodic systems (#1509)#1523
darthjaja6 wants to merge 1 commit into
ACEsuit:developfrom
darthjaja6:fix/slab-stress-volume-1509

Conversation

@darthjaja6

@darthjaja6 darthjaja6 commented Jul 14, 2026

Copy link
Copy Markdown

Summary

Fixes #1509. The analytic stress returned for partially periodic (slab) systems was silently scaled down by a large factor (~18x in the reported case), while energies and forces were unaffected.

Root cause

get_neighborhood extends the cell along non-periodic directions so that matscipy can bin the atoms:

if not pbc_x:
    cell[0, :] = max_positions * 5 * cutoff * identity[0, :]

This inflated cell was then returned from the function and stored in AtomicData. Stress is normalized by det(cell) in compute_forces_virials, so the artificial volume along the vacuum direction shrinks the stress by exactly the ratio of the inflated extent to the true extent. For the phosphorene example in the issue this ratio is max_positions * 5 * cutoff / z_true ≈ 17.8, matching the reported 17.81. Energies and forces don't touch det(cell), which is why only stress was affected and the bug went unnoticed.

Fix

Use the extended cell only for the neighbour-list call, and return the physical cell whenever at least one direction is periodic. unit_shifts are zero along non-periodic directions, so the shifts (and therefore the energy/forces) are identical. Fully non-periodic systems keep the extended cell, since stress is meaningless there and long-range models rely on a non-degenerate cell — so their behaviour is unchanged.

Verification

Finite-difference check (analytic stress vs. strain derivative of the energy), model-independent:

system before after
bulk Si (pbc TTT, control) ratio 1.0000 ratio 1.0000
Si slab (pbc TTF) ratio ~16.6 ratio 1.0000

Added a regression test (test_stress_partial_pbc) that fails on the current code and passes with the fix, plus an assertion in test_half_periodic that the physical cell is returned. Full existing test suite passes.


Note

Medium Risk
Touches core neighbourhood/cell plumbing used by all models; behaviour change is scoped to partial PBC (fully periodic and fully aperiodic paths are preserved) but stress and any volume-derived quantities now differ for slabs.

Overview
Fixes incorrect analytic stress for slabs and other partially periodic systems, where stress was scaled down by the ratio of the artificially inflated vacuum cell to the true cell.

get_neighborhood still builds an extended cell along non-periodic axes for matscipy neighbour binning, but passes that cell only into neighbour_list. When any direction is periodic, it now returns a copy of the physical cell (not the blown-up one) so downstream stress normalization via det(cell) uses the real volume. Fully aperiodic systems still return the extended cell, unchanged.

Tests add a physical-cell assertion in test_half_periodic and a test_stress_partial_pbc regression that compares finite-difference strain energy to analytic sigma_yy on a Si TTF slab.

Reviewed by Cursor Bugbot for commit b1c210e. Bugbot is set up for automated code reviews on this repo. Configure here.

get_neighborhood extends the cell along non-periodic directions so that
matscipy can bin atoms, but the extended cell leaked out of the function
and was stored in AtomicData. Stress is normalized by det(cell), so for
slabs the analytic stress was silently scaled down by the ratio of the
artificial extent to the true vacuum extent (~18x in the reported case),
while energies and forces were unaffected.

Keep the extended cell internal to the neighbour-list call and return
the physical cell whenever at least one direction is periodic. Shifts
are unchanged because unit_shifts are zero along non-periodic
directions. Fully non-periodic systems retain the previous behaviour.

Add a finite-difference regression test comparing analytic stress with
the strain derivative of the energy on a slab.
@darthjaja6
darthjaja6 force-pushed the fix/slab-stress-volume-1509 branch from 9f27457 to b1c210e Compare July 14, 2026 04:33
@aacostadiaz

Copy link
Copy Markdown
Collaborator

@cursor review

@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

Bugbot couldn't run

Bugbot is not enabled for your user on this team.

Ask your team administrator to increase your team's hard limit for Bugbot seats or add you to the allowlist in the Cursor dashboard.

@aacostadiaz

Copy link
Copy Markdown
Collaborator

@cursor review

@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

Bugbot couldn't run

Bugbot is not enabled for your user on this team.

Ask your team administrator to increase your team's hard limit for Bugbot seats or add you to the allowlist in the Cursor dashboard.

@ilyes319

Copy link
Copy Markdown
Contributor

@cursor review

@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

Bugbot couldn't run

Bugbot is not enabled for your user on this team.

Ask your team administrator to increase your team's hard limit for Bugbot seats or add you to the allowlist in the Cursor dashboard.

@gabor1

gabor1 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

@cursor review

@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

Bugbot couldn't run

Bugbot is not enabled for your user on this team.

Ask your team administrator to increase your team's hard limit for Bugbot seats or add you to the allowlist in the Cursor dashboard.

@ilyes319

Copy link
Copy Markdown
Contributor

@cursor review

@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

Bugbot couldn't run

Bugbot is not enabled for your user on this team.

Ask your team administrator to increase your team's hard limit for Bugbot seats or add you to the allowlist in the Cursor dashboard.

@gabor1

gabor1 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

@cursor review

@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

Bugbot couldn't run

Bugbot is not enabled for your user on this team.

Ask your team administrator to increase your team's hard limit for Bugbot seats or add you to the allowlist in the Cursor dashboard.

@gabor1

gabor1 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

@cursor review

@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

Bugbot couldn't run

Bugbot is not enabled for your user on this team.

Ask your team administrator to increase your team's hard limit for Bugbot seats or add you to the allowlist in the Cursor dashboard.

@gabor1

gabor1 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

@cursor review

@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

Bugbot couldn't run

Bugbot is not enabled for your user on this team.

Ask your team administrator to increase your team's hard limit for Bugbot seats or add you to the allowlist in the Cursor dashboard.

@aacostadiaz

Copy link
Copy Markdown
Collaborator

@cursor review

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit b1c210e. Configure here.

@aacostadiaz aacostadiaz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for fixing this. I left a few comments but the fix itself looks correct to me.

Comment thread mace/data/neighborhood.py
# stress divides by det(cell) later, so the fake volume shrinks it (#1509).
# keep it for fully aperiodic systems though (no stress there anyway)
if any(pbc):
cell = np.array(cell, dtype=float, copy=True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Edge case worth considering: a zero row along a non-periodic direction now gives det(cell)=0, so stress comes out as zeros and training with stress in the loss silently NaNs all gradients. Hard to debug when it happens.

Suggested change
cell = np.array(cell, dtype=float, copy=True)
cell = np.array(cell, dtype=float, copy=True)
# a zero row along a non-periodic direction would make det(cell)=0
# downstream (stress division, rcell) — keep the extended row there
for dim in range(3):
if not pbc[dim] and not cell[dim].any():
cell[dim] = extended_cell[dim]

Comment thread tests/unit/test_data.py
Comment on lines +209 to +213
edge_index, shifts, _, cell = get_neighborhood(
config.positions, cutoff=2.9, pbc=(True, True, False), cell=config.cell
)
# should give back the real cell, not the blown-up one (#1509)
assert np.allclose(cell, config.cell)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't think this assert can catch the bug it references: the old code mutated the passed-in cell in place and returned that same array, so on the old code this compares the inflated cell with itself and passes. Snapshotting the cell before the call makes it a real regression guard.

Suggested change
edge_index, shifts, _, cell = get_neighborhood(
config.positions, cutoff=2.9, pbc=(True, True, False), cell=config.cell
)
# should give back the real cell, not the blown-up one (#1509)
assert np.allclose(cell, config.cell)
cell_before = config.cell.copy()
edge_index, shifts, _, cell = get_neighborhood(
config.positions, cutoff=2.9, pbc=(True, True, False), cell=config.cell
)
# should give back the real cell, not the blown-up one (#1509)
assert np.allclose(cell, cell_before)

Copy link
Copy Markdown
Author

Thanks for incorporating this fix and the regression test into #1533. Since the combined PR supersedes this one and also addresses the review comments here, I'll close this and follow the discussion there.

@darthjaja6 darthjaja6 closed this Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants