Skip to content

Fix sfd nan at large negative x from log(0)#347

Merged
QG-phy merged 1 commit into
deepmodeling:mainfrom
QG-phy:fix-sfd-log-zero-warning
Jun 25, 2026
Merged

Fix sfd nan at large negative x from log(0)#347
QG-phy merged 1 commit into
deepmodeling:mainfrom
QG-phy:fix-sfd-log-zero-warning

Conversation

@QG-phy

@QG-phy QG-phy commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

sfd(x) (Fermi-Dirac entropy, -f*log(f) - (1-f)*log(1-f)) returned nan for x <= -39. For sufficiently negative x, np.expm1(x) rounds to -1.0 in float64, so f -> 1.0 and 1-f -> 0.0, producing log(0) -> nan plus RuntimeWarning: divide by zero in log / invalid value in multiply. The physical entropy limit there is 0 (x*log(x) -> 0 as x -> 0).

Fix: mask the f=0/1 boundaries to 0 instead of computing the log. Interior values are unchanged.

This was a real numerical bug, not just warning noise: test_band and other paths consumed nan-valued entropy from sfd. The existing test_sfd_properties was silently tolerating the nan (filtering with np.isfinite before asserting non-negativity), which masked the issue.

Reproduction (before fix)

sfd([-100, -50, -40, -39, -20, 0, 20, 40, 50, 100])
=> [0, 0, nan, nan, ..., 0]   # x=-40, -39 are nan

After fix

All entries finite; boundaries vanish to 0; interior unchanged (x=0 -> ln(2)).

Scope

dptb/utils/occupy.py (sfd only) + dptb/tests/test_occupy.py (regression test + drop the nan-tolerating filter).

Tests

  • New test_sfd_finite_at_large_negative_x: asserts finiteness on x in [-100, 100], vanishing boundaries, and x=0 -> ln(2).
  • test_sfd_properties updated to assert non-negativity directly (no nan filter).
  • dptb/tests/test_occupy.py: 54 passed.
  • smoke or regression suite: 57 passed, 0 failed (no regression).
  • test_band.py: the two occupy.py:150 RuntimeWarnings are gone.

Risk

Low. The fix only changes behavior at the f=0/1 boundaries (previously nan, now 0 — the physical limit). Interior values are bit-for-bit unchanged.

Merge decision

Mergeable. Independent of #346 (dependency baseline); this is a code-level fix that applies regardless of numpy version.

Summary by CodeRabbit

  • Bug Fixes

    • Improved numerical stability when calculating entropy near boundary values, preventing nan results from rounding issues.
    • Boundary inputs now return zero entropy consistently, while interior values remain unchanged.
  • Tests

    • Strengthened unit coverage for extreme negative inputs and verified finite outputs, correct zero boundaries, tiny positive interior values, and the expected maximum at zero.

sfd(x) computes the Fermi-Dirac entropy -f*log(f) - (1-f)*log(1-f).
For x <= -39, expm1(x) rounds to -1.0 in float64, so f -> 1.0 and
1-f -> 0.0, producing log(0) -> nan and RuntimeWarnings (divide by zero
in log, invalid value in multiply). The physical entropy limit there is
0 (x*log(x) -> 0 as x -> 0).

Mask the f=0/1 boundaries to 0 instead of computing the log. Interior
values are unchanged. This was a real numerical bug, not just noise:
test_band and other tests consumed nan-valued entropy from sfd.

Add a regression test covering x in [-100, 100] asserting finiteness and
the vanishing boundary. Drop the NaN-tolerating filter in
test_sfd_properties since sfd no longer returns nan.
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Sfd now avoids boundary log(0) behavior by computing entropy only for interior values and returning zero at exact boundaries. The unit test now checks finiteness, boundary zeros, a tiny positive interior value, and the x=0 reference value.

Changes

Boundary-safe Fermi-Dirac entropy

Layer / File(s) Summary
Entropy boundary guard
dptb/utils/occupy.py
sfd computes entropy only for values strictly between 0 and 1 and assigns zero at boundary entries.
Entropy assertions
dptb/tests/test_occupy.py
The sfd test asserts finite outputs, exact zeros at selected extremes, a tiny positive interior value, and log(2) at x=0.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: preventing sfd nan values for large negative inputs.
Description check ✅ Passed The description covers the main change, tests, risk, and merge decision, but it omits several template sections and compatibility details.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

@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 `@dptb/utils/occupy.py`:
- Around line 155-161: The occupancy entropy handling in occupy.py is zeroing
every non-interior value, which unintentionally converts NaN inputs into 0.0
instead of preserving them. Update the logic around the interior mask and
out_valid initialization so only the exact boundary cases where f_valid is 0 or
1 collapse to zero, while NaN values remain NaN through the final assignment to
out[mask_in_limit].
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67e8017d-04b0-428c-8ea6-2ae0d9b714f7

📥 Commits

Reviewing files that changed from the base of the PR and between d89ecfb and 6647caf.

📒 Files selected for processing (2)
  • dptb/tests/test_occupy.py
  • dptb/utils/occupy.py

Comment thread dptb/utils/occupy.py
Comment on lines +155 to +161
interior = (f_valid > 0.0) & (f_valid < 1.0)
out_valid = np.zeros_like(f_valid)
out_valid[interior] = (
-f_valid[interior] * np.log(f_valid[interior])
- f1_valid[interior] * np.log(f1_valid[interior])
)
out[mask_in_limit] = out_valid

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve NaN propagation instead of zeroing every non-interior value.

Line 156 seeds out_valid with zeros, so this change also turns x=np.nan into 0.0 because interior is false there. That silently hides invalid upstream states; only the exact f==0/1 boundary should collapse to zero.

Suggested fix
-    interior = (f_valid > 0.0) & (f_valid < 1.0)
-    out_valid = np.zeros_like(f_valid)
+    boundary = np.isfinite(f_valid) & ((f_valid == 0.0) | (f_valid == 1.0))
+    interior = np.isfinite(f_valid) & ~boundary
+    out_valid = np.full_like(f_valid, np.nan, dtype=np.float64)
+    out_valid[boundary] = 0.0
     out_valid[interior] = (
         -f_valid[interior] * np.log(f_valid[interior])
         - f1_valid[interior] * np.log(f1_valid[interior])
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interior = (f_valid > 0.0) & (f_valid < 1.0)
out_valid = np.zeros_like(f_valid)
out_valid[interior] = (
-f_valid[interior] * np.log(f_valid[interior])
- f1_valid[interior] * np.log(f1_valid[interior])
)
out[mask_in_limit] = out_valid
boundary = np.isfinite(f_valid) & ((f_valid == 0.0) | (f_valid == 1.0))
interior = np.isfinite(f_valid) & ~boundary
out_valid = np.full_like(f_valid, np.nan, dtype=np.float64)
out_valid[boundary] = 0.0
out_valid[interior] = (
-f_valid[interior] * np.log(f_valid[interior])
- f1_valid[interior] * np.log(f1_valid[interior])
)
out[mask_in_limit] = out_valid
🤖 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 `@dptb/utils/occupy.py` around lines 155 - 161, The occupancy entropy handling
in occupy.py is zeroing every non-interior value, which unintentionally converts
NaN inputs into 0.0 instead of preserving them. Update the logic around the
interior mask and out_valid initialization so only the exact boundary cases
where f_valid is 0 or 1 collapse to zero, while NaN values remain NaN through
the final assignment to out[mask_in_limit].

@github-actions

Copy link
Copy Markdown

This advisory review plan was generated from changed file names using trusted base-branch code.
此审查计划由受信任的 base 分支代码根据变更文件名生成,仅作为维护者辅助。

DeePTB PR Review Plan / DeePTB PR 审查计划

Risk / 风险等级: Medium (中) · Changed files / 变更文件: 2

Why / 风险来源

  • Tests changed. / 测试 有变更。
  • Some files are unclassified by current risk_map.md. / 部分文件尚未被当前风险地图覆盖。

Recommended Review / 建议审查重点

  • Run the Maintainer Review Prompt. / 运行维护者审查 prompt。
  • Run the Test Gap Review Prompt. / 运行测试缺口审查 prompt。
  • Confirm the changed behavior matches the PR scope. / 确认变更行为和 PR 范围一致。
Detailed risk areas
  • Tests (Medium): dptb/tests/test_occupy.py
  • Unclassified by current risk_map.md: dptb/utils/occupy.py
Human review focus
  • Tests: marker choice, regression coverage, skipped environments, and assertions
Local commands and hold conditions

Suggested local commands:

  • uv run pytest ./dptb/tests -m smoke
  • uv run pytest ./dptb/tests -m regression

Hold conditions:

  • CI failed and the failure is not explained.
  • The PR scope is unclear or mixes unrelated changes.
  • AI review found a plausible correctness issue that was not fixed or explicitly waived.

Advisory only. / 仅作为审查辅助。

@QG-phy QG-phy merged commit 98b4048 into deepmodeling:main Jun 25, 2026
6 checks passed
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.

1 participant