Skip to content

Add optional dump_final_structure flag for MD runs#363

Merged
jan-janssen merged 8 commits into
mainfrom
add-write-dump-on-missing-final-step
Jun 30, 2026
Merged

Add optional dump_final_structure flag for MD runs#363
jan-janssen merged 8 commits into
mainfrom
add-write-dump-on-missing-final-step

Conversation

@jan-janssen

@jan-janssen jan-janssen commented Jun 27, 2026

Copy link
Copy Markdown
Member

Summary

  • LAMMPS's periodic dump command silently skips the final configuration of an MD run when n_ionic_steps is not evenly divisible by n_print, even though log.lammps still reports thermo data for that step (demonstrated in notebooks/2026/2026-06-27-lammps-dump/dump_frequency_v1.ipynb).
  • Adds an opt-in write_dump_if_missing flag (default False) to lammps_file_interface_function. When enabled and the MD run's step count isn't evenly divisible by n_print, a write_dump ... append yes command is appended right after the run command, capturing the missing final structure into the same dump.out file using the same fields/format as the regular dump command.
  • When the run length is evenly divisible (no frame is missing), the command is skipped to avoid a duplicate final frame.

Test plan

  • tests/test_compatibility_file.py: new tests assert the generated lmp.in does/doesn't contain the write_dump line for (a) flag on + non-divisible, (b) flag on + divisible, (c) flag off (default)
  • tests/test_output.py + new static fixtures (tests/static/dump_missing_final_step, tests/static/dump_with_final_step): characterize the bug (steps/positions length mismatch) and confirm parsing recovers correctly once the extra frame is present
  • python -m pytest tests/ — all pass except the pre-existing test_compatibility_integration.py failure (segfaults locally due to MPI/LAMMPS binary setup, unrelated to this change, reproduced on main too)
  • pre-commit run ruff / ruff-format

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added an dump_final_structure option (off by default) to capture and append the final MD structure via an additional write_dump ... append yes when the last step isn’t aligned to the dump cadence.
  • Bug Fixes
    • Prevented loss of the final dump frame when the run length isn’t evenly divisible by the dump interval.
    • Validates usage: enabling this option in static/minimize mode now raises an error.
  • Tests
    • Added regression tests and new static LAMMPS log fixtures for both missing-final-step and recovered-final-step scenarios.

When n_ionic_steps is not evenly divisible by n_print, LAMMPS's periodic
dump command silently skips the final configuration even though log.lammps
still reports that step (see notebooks/2026/2026-06-27-lammps-dump). With
this flag enabled, a write_dump command is appended after the run command
to append the missing final frame to dump.out. Off by default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an optional final write_dump in MD input generation, updates dump command formatting, and adds regression fixtures and tests for missing and recovered final dump frames.

Changes

MD final dump capture

Layer / File(s) Summary
API flag and dump shape
src/lammpsparser/compatibility/file.py
Adds dump_final_structure to lammps_file_interface_function, documents the MD-mode write_dump behavior, and builds dump.out commands from shared field and format values.
Conditional final write_dump
src/lammpsparser/compatibility/file.py
In md mode, computes whether the last step matches the dump cadence, appends write_dump all custom dump.out ... append yes when needed, and rejects the flag in static and minimize modes.
Regression fixtures and assertions
tests/test_compatibility_file.py, tests/test_output.py, tests/static/dump_missing_final_step/log.lammps, tests/static/dump_with_final_step/log.lammps
Adds tests for generator behavior and parsed output alignment, plus static log fixtures with and without a final dump frame.

Sequence Diagram(s)

sequenceDiagram
  participant lammps_file_interface_function
  participant LAMMPS
  participant parse_lammps_output_files
  lammps_file_interface_function->>LAMMPS: write lmp.in with periodic dump.out and optional write_dump all custom dump.out ... append yes
  LAMMPS->>LAMMPS: run MD steps and emit log.lammps / dump.out
  parse_lammps_output_files->>LAMMPS: read log.lammps and dump.out
  LAMMPS-->>parse_lammps_output_files: generic["steps"] and generic["positions"]
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped where final frames could hide,
Then tugged one more from dump.out's side.
The logs and steps now match their pace,
With tidy prints in every space.
A jaunty thump, a happy trace.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% 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 accurately summarizes the main change: an optional dump_final_structure flag added for MD runs.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-write-dump-on-missing-final-step

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.

@codecov

codecov Bot commented Jun 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.77%. Comparing base (8b95b60) to head (dfa9f99).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #363   +/-   ##
=======================================
  Coverage   98.76%   98.77%           
=======================================
  Files          13       13           
  Lines        1214     1223    +9     
=======================================
+ Hits         1199     1208    +9     
  Misses         15       15           

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

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

Caution

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

⚠️ Outside diff range comments (1)
src/lammpsparser/compatibility/file.py (1)

188-203: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Honor input_control_file overrides in the appended write_dump.

_modify_input_dict() can replace the regular dump / dump_modify lines, but this post-run write_dump is still built from the hard-coded defaults. With write_dump_if_missing=True, that can produce a final frame in dump.out with different columns or formatting than the periodic frames, which breaks the “same fields/format” contract and risks inconsistent parsing. Please derive the appended command from the effective post-override dump configuration, or reject dump/dump_modify overrides when this flag is enabled.

🤖 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 `@src/lammpsparser/compatibility/file.py` around lines 188 - 203, The appended
write_dump command in the input assembly still uses hard-coded dump defaults, so
it can ignore overrides applied by _modify_input_dict(). Update the logic around
the lmp_str_lst construction to build the post-run write_dump from the effective
dump/dump_modify configuration produced by input_control_file, or explicitly
disallow dump-related overrides when write_dump_if_missing is enabled. Use the
existing _modify_input_dict(), write_dump_if_missing, and
dump_fields/dump_format handling to keep the final frame consistent with the
periodic dumps.
🤖 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 `@src/lammpsparser/compatibility/file.py`:
- Around line 188-203: The appended write_dump command in the input assembly
still uses hard-coded dump defaults, so it can ignore overrides applied by
_modify_input_dict(). Update the logic around the lmp_str_lst construction to
build the post-run write_dump from the effective dump/dump_modify configuration
produced by input_control_file, or explicitly disallow dump-related overrides
when write_dump_if_missing is enabled. Use the existing _modify_input_dict(),
write_dump_if_missing, and dump_fields/dump_format handling to keep the final
frame consistent with the periodic dumps.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d6d95e57-2dda-457b-9cd3-b134c39dc102

📥 Commits

Reviewing files that changed from the base of the PR and between 8b95b60 and 5a070c5.

⛔ Files ignored due to path filters (2)
  • tests/static/dump_missing_final_step/dump.out is excluded by !**/*.out
  • tests/static/dump_with_final_step/dump.out is excluded by !**/*.out
📒 Files selected for processing (5)
  • src/lammpsparser/compatibility/file.py
  • tests/static/dump_missing_final_step/log.lammps
  • tests/static/dump_with_final_step/log.lammps
  • tests/test_compatibility_file.py
  • tests/test_output.py

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

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)
src/lammpsparser/compatibility/file.py (1)

192-200: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Missing divisibility guard — n_print is read but never used, and the final frame is duplicated when the run is evenly divisible.

Line 192 reads n_print but it is never referenced. Per the PR objective, the write_dump should only be appended when the final step would otherwise be missing, i.e. when n_ionic_steps % n_print != 0; otherwise the periodic dump already captured the final step and appending here writes a duplicate final frame into dump.out. The downstream regression (tests/test_output.py::test_final_step_recovered_with_write_dump) expects dump frame count to equal log step count, which an unconditional append breaks for the divisible case.

The docstring at Lines 89-90 should also be updated to describe this conditional behavior.

🐛 Proposed fix: only append when the final step is actually missing
         lmp_str_lst += ["run {} ".format(n_ionic_steps)]
         n_print = calc_kwargs.get("n_print", 1)
-        if dump_final_structure:
+        if dump_final_structure and n_print and n_ionic_steps % n_print != 0:
             lmp_str_lst += [
                 "write_dump all custom dump.out "
                 + dump_fields
                 + " modify sort id format line "
                 + dump_format
                 + " append yes"
             ]
🤖 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 `@src/lammpsparser/compatibility/file.py` around lines 192 - 200, The
final-structure dump handling in file.py is unconditional even though `n_print`
is already read, which can duplicate the last frame when the run length is
evenly divisible. Update the logic around the `dump_final_structure` /
`write_dump` path in the relevant compatibility function so it only appends the
final dump when `n_ionic_steps % n_print != 0`, using `n_print` as the
divisibility guard. Also update the associated docstring/comments for this
behavior so the conditional final-frame recovery is documented clearly.
🤖 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 `@src/lammpsparser/compatibility/file.py`:
- Line 36: The parser compatibility API is inconsistent because the function
signature still exposes dump_final_structure while callers use
write_dump_if_missing, causing a TypeError. Update the relevant function
signature and any internal references in the file.py compatibility path to use
write_dump_if_missing consistently, and adjust the docstring to match the new
kwarg name so the public API and documentation align.

---

Outside diff comments:
In `@src/lammpsparser/compatibility/file.py`:
- Around line 192-200: The final-structure dump handling in file.py is
unconditional even though `n_print` is already read, which can duplicate the
last frame when the run length is evenly divisible. Update the logic around the
`dump_final_structure` / `write_dump` path in the relevant compatibility
function so it only appends the final dump when `n_ionic_steps % n_print != 0`,
using `n_print` as the divisibility guard. Also update the associated
docstring/comments for this behavior so the conditional final-frame recovery is
documented clearly.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 762ac59f-b08f-4650-a851-f1f193d4c63e

📥 Commits

Reviewing files that changed from the base of the PR and between 5a070c5 and 5c6d23c.

📒 Files selected for processing (2)
  • src/lammpsparser/compatibility/file.py
  • tests/test_compatibility_file.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_compatibility_file.py

write_restart_file: bool = False,
read_restart_file: bool = False,
restart_file: str = "restart.out",
dump_final_structure: bool = False,

Copy link
Copy Markdown

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the kwarg name used by callers vs. the function signature.
rg -nP '\bwrite_dump_if_missing\b|\bdump_final_structure\b' -g '*.py'

Repository: pyiron/lammpsparser

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## repo files"
git ls-files | sed -n '1,200p'

echo "## locate target file and test file"
fd -a 'file.py|test_compatibility_file.py' . || true

echo "## search for compatibility/file and relevant symbols"
rg -n --hidden --glob '!**/.git/**' 'compatibility/file.py|def .*dump|write_dump_if_missing|dump_final_structure|test_compatibility_file' .

Repository: pyiron/lammpsparser

Length of output: 4622


Rename the kwarg to write_dump_if_missing The current signature exposes dump_final_structure, but the call sites pass write_dump_if_missing=..., which raises TypeError. Update the parameter name and matching docstring to keep the API consistent.

🤖 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 `@src/lammpsparser/compatibility/file.py` at line 36, The parser compatibility
API is inconsistent because the function signature still exposes
dump_final_structure while callers use write_dump_if_missing, causing a
TypeError. Update the relevant function signature and any internal references in
the file.py compatibility path to use write_dump_if_missing consistently, and
adjust the docstring to match the new kwarg name so the public API and
documentation align.

@jan-janssen

Copy link
Copy Markdown
Member Author

based on lammps/lammps#5005

@jan-janssen jan-janssen changed the title Add optional write_dump_if_missing flag for MD runs Add optional dump_final_structure flag for MD runs Jun 28, 2026
@jan-janssen

Copy link
Copy Markdown
Member Author

@Gitdowski This is the most simple implementation to fix the dumping of the final structure. It still comes with a couple of challenges:

  • The user has to specify if the final structure is dumped or not. The challenging part here is to get the current number of steps, for example if there is a minimization first and then the structure is relaxed then we would have to get the number of steps from the first simulation. A way to address this would be to use a LAMMPS internal if statement which checks and compares to the n_print value. Maybe in a follow up pull request.
  • The final structure is appended to the same output file, because I could really understand why this feature is available in LAMMPS if it does not work. My feeling was that Alex feared that we could write to the same dump file with different dump formats but that is something we can prevent from the python side.

Can you check if this resolves your issue?

@jan-janssen

Copy link
Copy Markdown
Member Author

@copilot Please add more unit tests to cover the missing lines.

Copilot AI commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

@copilot Please add more unit tests to cover the missing lines.

Added additional unit tests to cover the missing lines in the new dump_final_structure logic:

  • test_dump_final_structure_raises_for_static_mode
  • test_dump_final_structure_raises_for_minimize_mode

Addressed in commit 7d96501.

@jan-janssen

Copy link
Copy Markdown
Member Author

In LAMMPS we could use something like :

variable n_print equal 1000
variable n_steps equal 10000

# example: dump during MD
dump dmp all custom ${n_print} dump.*.lammpstrj id type x y z

run ${n_steps}

# true if final timestep was not written by the periodic dump
variable need_final_dump equal "mod(step,v_n_print) != 0"

if "${need_final_dump}" then &
  "write_dump all custom dump.final.lammpstrj id type x y z"

@Gitdowski

Copy link
Copy Markdown
Contributor

Thank you Jan! On a first glance this could do the thing

  • The user has to specify if the final structure is dumped or not. The challenging part here is to get the current number of steps, for example if there is a minimization first and then the structure is relaxed then we would have to get the number of steps from the first simulation. A way to address this would be to use a LAMMPS internal if statement which checks and compares to the n_print value. Maybe in a follow up pull request.

I am not sure if I understand your concern here. Every lammps simulation triggered by lammpsparser should only start one minimize or one run, never both or mutliple of them. Running a sequence of simulations is always handled on the workflow side. Therefore, the timestep for every individual simulation will always start at zero and there should be no need to get the number of steps from the previous simulation(s).

  • The final structure is appended to the same output file, because I could really understand why this feature is available in LAMMPS if it does not work. My feeling was that Alex feared that we could write to the same dump file with different dump formats but that is something we can prevent from the python side.

I am also on your side here. For reference, here is the comment on "appending to files is always a bad idea": lammps/lammps#5005 (comment)

Can you check if this resolves your issue?

I will test it today and let you know!

@jan-janssen

Copy link
Copy Markdown
Member Author

I am not sure if I understand your concern here. Every lammps simulation triggered by lammpsparser should only start one minimize or one run, never both or mutliple of them. Running a sequence of simulations is always handled on the workflow side. Therefore, the timestep for every individual simulation will always start at zero and there should be no need to get the number of steps from the previous simulation(s).

I thought when somebody does a minimization, writes a restart file and then from this restart file starts and MD trajectory, then the total number of steps could be the sum of the steps from the minimization and the molecular dynamics trajectory. I have not check this, but that is what I understood from the documentation.

@Gitdowski

Copy link
Copy Markdown
Contributor

I didn't consider restart files. Yes, this could indeed happen... I'll think about this

@Gitdowski

Gitdowski commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Concerning the restart files: I think we are save here because this is already handled in lammps_file_interface_function via:

        if read_restart_file:
            lmp_str_lst += ["reset_timestep 0"]

Therefore, frames always reset to 0 and no too complicated logic is required. I'll add a check to avoid that the last frame is dumped twice if it falls on a regular dump frame.

@Gitdowski

Copy link
Copy Markdown
Contributor

Check PR #365

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

Caution

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

⚠️ Outside diff range comments (1)
tests/test_compatibility_file.py (1)

241-246: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Enable dump_final_structure in the divisible-case test.

Line 241 evaluates to False for 1000 % 100, so this test never covers the intended “flag enabled, but no duplicate final frame” branch. It currently overlaps with test_write_dump_if_missing_default_off instead.

Suggested fix
-            dump_final_structure=n_ionic_steps % n_print != 0,
+            dump_final_structure=True,
🤖 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 `@tests/test_compatibility_file.py` around lines 241 - 246, The divisible-case
coverage in test_write_dump_if_missing_default_off is wrong because
dump_final_structure is currently disabled when n_ionic_steps is divisible by
n_print, so this test never exercises the enabled-flag branch. Update the setup
in test_compatibility_file.py to explicitly pass dump_final_structure=True for
that case, and keep the existing assertions in place to verify that lmp.in does
not contain a duplicate write_dump line.
🤖 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 `@tests/test_compatibility_file.py`:
- Around line 241-246: The divisible-case coverage in
test_write_dump_if_missing_default_off is wrong because dump_final_structure is
currently disabled when n_ionic_steps is divisible by n_print, so this test
never exercises the enabled-flag branch. Update the setup in
test_compatibility_file.py to explicitly pass dump_final_structure=True for that
case, and keep the existing assertions in place to verify that lmp.in does not
contain a duplicate write_dump line.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d2c77544-6122-458b-96ae-eace6d9bb565

📥 Commits

Reviewing files that changed from the base of the PR and between 5c6d23c and dfa9f99.

📒 Files selected for processing (2)
  • src/lammpsparser/compatibility/file.py
  • tests/test_compatibility_file.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lammpsparser/compatibility/file.py

@jan-janssen

Copy link
Copy Markdown
Member Author

@Gitdowski From my perspective this is ready to be merged. If you agree we can merge these changes and release a new version.

@Gitdowski

Copy link
Copy Markdown
Contributor

@jan-janssen Yes!
Once this is done, we can also close Issue #337

@jan-janssen jan-janssen merged commit 93e4f5a into main Jun 30, 2026
27 checks passed
@jan-janssen jan-janssen deleted the add-write-dump-on-missing-final-step branch June 30, 2026 06:27
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.

3 participants