Skip to content

Add Lammps function for backwards compatibility to pyiron_atomistics#262

Merged
jan-janssen merged 61 commits intomainfrom
lammps_function
Nov 27, 2025
Merged

Add Lammps function for backwards compatibility to pyiron_atomistics#262
jan-janssen merged 61 commits intomainfrom
lammps_function

Conversation

@jan-janssen
Copy link
Copy Markdown
Member

@jan-janssen jan-janssen commented Nov 8, 2025

Summary by CodeRabbit

  • New Features

    • MD workflows (NVE/NVT/NPT) with damping, optional Langevin, temperature ramping, initial velocity handling, static and minimization runs, and pressure-aware relaxations.
    • File-based LAMMPS interface to generate inputs, write data, run simulations, and parse outputs.
    • Enhanced structure export producing LAMMPS-compatible data (Atoms/Bonds/Angles) and an atom_type option when writing data.
    • Convenience helpers exposed at the package top level for easier access.
  • Tests

    • Comprehensive unit tests covering static, MD, and minimization workflows and generated input/output.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Nov 8, 2025

Warning

Rate limit exceeded

@jan-janssen has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 4 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 8b2d4a2 and dee5eb4.

📒 Files selected for processing (1)
  • tests/test_compatibility_file.py (1 hunks)

Walkthrough

Adds a LAMMPS compatibility layer: calculation helpers (calc_md, calc_minimize, calc_static), a file-based runner and initializer (lammps_file_interface_function, lammps_file_initialization), LAMMPS-compatible structure utilities and atom_type handling, plus tests and a static LAMMPS log fixture.

Changes

Cohort / File(s) Summary
Package exports
pyiron_lammps/__init__.py
Exposes calc_md, calc_minimize, calc_static (from compatibility.calculate) and lammps_file_initialization, lammps_file_interface_function (from compatibility.file) at package top-level.
Calculation helpers
pyiron_lammps/compatibility/calculate.py
New module adding calc_md, calc_minimize, calc_static and internal helpers (_get_thermo, _set_initial_velocity, _pressure_to_lammps, _get_rotation_matrix, _modify_structure_to_allow_requested_deformation, _is_isotropic_hydrostatic) to construct LAMMPS input lines for ensembles, minimization, unit/pressure handling, velocity setup, and thermo output.
File interface & initialization
pyiron_lammps/compatibility/file.py
Adds lammps_file_interface_function to assemble/write lmp.in, select calc mode (static, md, minimize), call write_lammps_datafile, run LAMMPS and parse outputs; adds lammps_file_initialization to generate initial LAMMPS directives (units, dimension, boundary, read_data).
Structure compatibility utilities
pyiron_lammps/compatibility/structure.py
New LammpsStructureCompatibility (extends LammpsStructure) with structure property, structure_bond, structure_full, and get_bonds helper to produce LAMMPS-compatible data blocks (Atoms, Bonds, Angles) and per-species charge extraction.
Structure / datafile changes
pyiron_lammps/structure.py
Adjusts coordinate/dimension extraction in structure_charge and adds atom_type: str = "atomic" parameter to write_lammps_datafile, propagated to LammpsStructure.
Tests & fixtures
tests/test_compatibility_file.py, tests/static/compatibility_output/log.lammps
New test suite TestCompatibilityFile covering MD (NVT/NPT/Langevin), minimize, static flows, temperature/pressure scenarios, lmp.in content assertions, plus a static log.lammps fixture used by parser tests.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Interface as lammps_file_interface_function
    participant Init as lammps_file_initialization
    participant Calc as calc_md/calc_minimize/calc_static
    participant DataUtil as write_lammps_datafile
    participant FS as Filesystem
    participant LAMMPS as LAMMPS_Process
    participant Parser as parse_lammps_output

    User->>Interface: call(working_directory, structure, potential, calc_mode, calc_kwargs)
    Interface->>FS: ensure working directory exists
    Interface->>Init: generate initial directives (units, dimension, boundary, read_data)
    Interface->>Calc: build calculation lines for selected mode
    Calc-->>Interface: return calc lines
    Interface->>DataUtil: write_lammps_datafile(structure, atom_type=...)
    Interface->>FS: write `lmp.in` (init + potential + calc lines)
    Interface->>LAMMPS: run lmp_command
    LAMMPS-->>FS: produce logs/output files
    Interface->>Parser: parse_lammps_output(log files)
    Parser-->>Interface: parsed results
    Interface-->>User: return (shell_output, parsed_output, False)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay extra attention to:
    • Pressure/tensor conversion and rotation logic (_pressure_to_lammps, _get_rotation_matrix, _modify_structure_to_allow_requested_deformation).
    • Ensemble branching and Langevin vs thermostat behavior in calc_md.
    • Subprocess invocation, file writes, and error handling in lammps_file_interface_function.
    • Datafile formatting and atom_type propagation in write_lammps_datafile.
    • Tests that assert exact lmp.in content and parser expectations.

Possibly related PRs

Poem

🐇 I hopped through atoms, bonds in tow,
Wrote tidy lmp lines so the sim can go.
NVT, NPT, minimize in a row,
I parsed the logs where energies glow.
A rabbit’s patch of code — ready to show.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.94% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Lammps function for backwards compatibility to pyiron_atomistics' clearly summarizes the main changes—adding new LAMMPS functions (calc_md, calc_minimize, calc_static, lammps_file_initialization, lammps_file_interface_function) for backwards compatibility integration.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

❤️ Share

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

@codecov
Copy link
Copy Markdown

codecov bot commented Nov 8, 2025

Codecov Report

❌ Patch coverage is 79.90196% with 41 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.03%. Comparing base (bd54e9e) to head (dee5eb4).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
pyiron_lammps/compatibility/calculate.py 74.21% 41 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #262      +/-   ##
==========================================
- Coverage   95.85%   92.03%   -3.83%     
==========================================
  Files           5        7       +2     
  Lines         652      854     +202     
==========================================
+ Hits          625      786     +161     
- Misses         27       68      +41     

☔ View full report in Codecov by Sentry.
📢 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.

Base automatically changed from potential to main November 19, 2025 20:58
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (6)
pyiron_lammps/compatibility/calculate.py (3)

71-88: Warnings should use an appropriate category and stacklevel

All deprecation/behaviour warnings (delta_temp, delta_press, langevin without temperature, n_print > max_iter) currently call warnings.warn without a category or stacklevel. This makes it hard for users to silence or locate the originating call site.

Consider, for each of these, using DeprecationWarning (for deprecated args) or UserWarning and setting stacklevel=2, e.g.:

-        warnings.warn(
-            "WARNING: `delta_temp` is deprecated, please use `temperature_damping_timescale`."
-        )
+        warnings.warn(
+            "`delta_temp` is deprecated, please use `temperature_damping_timescale`.",
+            DeprecationWarning,
+            stacklevel=2,
+        )

and similarly for delta_press, the Langevin‑ignored case, and the n_print adjustment.

Also applies to: 175-175, 240-243


117-137: force_skewed is dead code

force_skewed is initialised to False and potentially set to True in the pressure loop, but never read. This is just noise now.

You can safely remove it and, if you still want to keep the comment about skewed cells, keep that in _pressure_to_lammps where the actual handling lives.

-        force_skewed = False
@@
-                    if ii > 2:
-                        force_skewed = True

295-305: Minor: calc_static list construction can be simplified

calc_static works, but concatenating lists is a bit less clear than extending:

def calc_static():
    return ["variable thermotime equal 1"] + _get_thermo() + ["run 0"]

You may consider using unpacking for readability:

def calc_static():
    return [
        "variable thermotime equal 1",
        *_get_thermo(),
        "run 0",
    ]

This also addresses the Ruff RUF005 hint.

pyiron_lammps/__init__.py (1)

2-6: New compatibility helpers are imported but not added to __all__

Importing calc_md, calc_minimize, calc_static, lammps_file_initialization, and lammps_file_interface_function at module level correctly exposes them as pyiron_lammps.<name>.

However, they are not included in __all__, so from pyiron_lammps import * will not export them.

If the intent is to treat these as part of the official public API (for backwards compatibility), consider extending __all__:

 __all__ = [
     "get_potential_by_name",
     "get_potential_dataframe",
     "parse_lammps_output_files",
     "write_lammps_structure",
+    "calc_md",
+    "calc_minimize",
+    "calc_static",
+    "lammps_file_initialization",
+    "lammps_file_interface_function",
 ]

If you prefer a narrower star‑import surface, leaving __all__ unchanged is fine and the direct imports are sufficient.

Also applies to: 22-28

pyiron_lammps/compatibility/file.py (2)

101-105: Minor: use a clearer loop variable name when writing the input file

The list comprehension:

with open(..., "w") as f:
    f.writelines([l + "\n" for l in lmp_str_lst])

uses the single‑letter name l, which Ruff flags as ambiguous (E741) and is a bit hard to read.

A simple readability tweak:

-    with open(os.path.join(working_directory, "lmp.in"), "w") as f:
-        f.writelines([l + "\n" for l in lmp_str_lst])
+    with open(os.path.join(working_directory, "lmp.in"), "w") as f:
+        f.writelines([line + "\n" for line in lmp_str_lst])

also makes static analysis happy.

Also applies to: 123-124


146-155: lammps_file_initialization looks correct; just a tiny clean‑up opportunity

The initialization commands are straightforward and consistent with ASE’s pbc:

boundary = " ".join(["p" if coord else "f" for coord in structure.pbc])
init_commands = [
    "units " + units,
    "dimension " + str(dimension),
    "boundary " + boundary + "",
    "atom_style atomic",
    "read_data lammps.data",
]

The + "" is redundant; you could simplify that line to:

-        "boundary " + boundary + "",
+        f"boundary {boundary}",

but functionally it is fine as‑is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c08e4b and 98cb97a.

⛔ Files ignored due to path filters (1)
  • tests/static/potentials_lammps.csv is excluded by !**/*.csv
📒 Files selected for processing (3)
  • pyiron_lammps/__init__.py (1 hunks)
  • pyiron_lammps/compatibility/calculate.py (1 hunks)
  • pyiron_lammps/compatibility/file.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
pyiron_lammps/compatibility/file.py (4)
pyiron_lammps/compatibility/calculate.py (3)
  • calc_md (8-198)
  • calc_minimize (201-292)
  • calc_static (295-296)
pyiron_lammps/output.py (1)
  • parse_lammps_output (73-135)
pyiron_lammps/potential.py (1)
  • get_potential_by_name (349-355)
pyiron_lammps/structure.py (1)
  • write_lammps_datafile (453-464)
pyiron_lammps/__init__.py (2)
pyiron_lammps/compatibility/calculate.py (3)
  • calc_md (8-198)
  • calc_minimize (201-292)
  • calc_static (295-296)
pyiron_lammps/compatibility/file.py (2)
  • lammps_file_initialization (146-155)
  • lammps_file_interface_function (17-143)
🪛 Ruff (0.14.5)
pyiron_lammps/compatibility/calculate.py

73-73: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


82-82: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


107-107: Avoid specifying long messages outside the exception class

(TRY003)


113-115: Avoid specifying long messages outside the exception class

(TRY003)


136-136: Local variable force_skewed is assigned to but never used

Remove assignment to unused variable force_skewed

(F841)


155-157: Avoid specifying long messages outside the exception class

(TRY003)


175-175: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


242-242: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


255-258: Avoid specifying long messages outside the exception class

(TRY003)


267-267: Loop control variable ii not used within loop body

Rename unused ii to _ii

(B007)


296-296: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


388-388: Avoid specifying long messages outside the exception class

(TRY003)


396-403: Avoid specifying long messages outside the exception class

(TRY003)

pyiron_lammps/compatibility/file.py

119-121: Avoid specifying long messages outside the exception class

(TRY003)


124-124: Ambiguous variable name: l

(E741)


126-126: subprocess call with shell=True identified, security issue

(S602)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: unittest_matrix (windows-latest, 3.14)
🔇 Additional comments (1)
pyiron_lammps/compatibility/calculate.py (1)

307-313: _is_isotropic_hydrostatic assumes a sequence of length ≥ 6

The helper assumes pressure[:3] and pressure[3:] slices behave as expected for a 6‑element sequence and that None in ... is meaningful. Since _pressure_to_lammps normalizes to a 6‑entry list before calling this, that’s fine, but it’s tightly coupled.

If you expect external calls to _is_isotropic_hydrostatic, consider documenting that it must receive a length‑6 list and is not general‑purpose. Otherwise, this is OK as an internal helper.

Comment thread pyiron_lammps/compatibility/calculate.py
Comment thread pyiron_lammps/compatibility/calculate.py
Comment thread pyiron_lammps/compatibility/calculate.py
Comment thread pyiron_lammps/compatibility/calculate.py
Comment on lines +200 to +291

def calc_minimize(
ionic_energy_tolerance=0.0,
ionic_force_tolerance=1e-4,
max_iter=100000,
pressure=None,
n_print=100,
style="cg",
rotation_matrix=None,
units="metal",
):
"""
Sets parameters required for minimization.

Args:
ionic_energy_tolerance (float): If the magnitude of difference between energies of two consecutive steps is
lower than or equal to `ionic_energy_tolerance`, the minimisation terminates. (Default is 0.0 eV.)
ionic_force_tolerance (float): If the magnitude of the global force vector at a step is lower than or equal
to `ionic_force_tolerance`, the minimisation terminates. (Default is 1e-4 eV/angstrom.)
e_tol (float): Same as ionic_energy_tolerance (deprecated)
f_tol (float): Same as ionic_force_tolerance (deprecated)
max_iter (int): Maximum number of minimisation steps to carry out. If the minimisation converges before
`max_iter` steps, terminate at the converged step. If the minimisation does not converge up to
`max_iter` steps, terminate at the `max_iter` step. (Default is 100000.)
pressure (None/float/numpy.ndarray/list): Target pressure. If set to None, an isochoric (constant V)
calculation is performed. If set to a scalar, the shear of the cell and the ratio of the x, y, and
z components is kept constant, while an isotropic, hydrostatic pressure is applied. A list of up to
length 6 can be given to specify xx, yy, zz, xy, xz, and yz components of the pressure tensor,
respectively. These values can mix floats and `None` to allow only certain degrees of cell freedom
to change. (Default is None, run isochorically.)
n_print (int): Write (dump or print) to the output file every n steps (Default: 100)
style ('cg'/'sd'/other values from Lammps docs): The style of the numeric minimization, either conjugate
gradient, steepest descent, or other keys permissible from the Lammps docs on 'min_style'. (Default
is 'cg' -- conjugate gradient.)
rotation_matrix (numpy.ndarray): The rotation matrix from the pyiron to Lammps coordinate frame.
"""
# This docstring is a source for the calc_minimize method in pyiron_atomistics.lammps.base.LammpsBase.calc_minimize and
# pyiron_atomistics.lammps.interactive.LammpsInteractive.calc_minimize -- Please ensure that changes to signature or
# defaults stay consistent!

max_evaluations = 100 * max_iter
if n_print > max_iter:
warnings.warn("n_print larger than max_iter, adjusting to n_print=max_iter")
n_print = max_iter

if units not in LAMMPS_UNIT_CONVERSIONS.keys():
raise NotImplementedError
energy_units = LAMMPS_UNIT_CONVERSIONS[units]["energy"]
force_units = LAMMPS_UNIT_CONVERSIONS[units]["force"]

ionic_energy_tolerance *= energy_units
ionic_force_tolerance *= force_units

if pressure is not None:
if rotation_matrix is None:
raise ValueError(
"No rotation matrix given while trying to convert pressure. "
"This is most likely due to no structure being defined."
)
# force_skewed = False
pressure = _pressure_to_lammps(
pressure=pressure, rotation_matrix=rotation_matrix, units=units
)
if np.isscalar(pressure):
str_press = " iso {}".format(pressure)
else:
str_press = ""
for ii, (press, str_axis) in enumerate(
zip(pressure, ["x", "y", "z", "xy", "xz", "yz"])
):
if press is not None:
str_press += " {} {}".format(str_axis, press)
# if ii > 2:
# force_skewed = True
if len(str_press) > 1:
str_press += " couple none"
line_lst = [
"variable thermotime equal {} ".format(n_print),
]
line_lst += [
_get_thermo(),
r"fix ensemble all box/relax" + str_press,
"min_style " + style,
"minimize "
+ str(ionic_energy_tolerance)
+ " "
+ str(ionic_force_tolerance)
+ " "
+ str(int(max_iter))
+ " "
+ str(int(max_evaluations)),
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

calc_minimize has a likely NameError and nests _get_thermo() as a list

In calc_minimize:

  1. str_press is only defined inside the if pressure is not None: block, but you unconditionally use it in:
line_lst += [
    _get_thermo(),
    r"fix ensemble all box/relax" + str_press,
    ...
]

If pressure is None (isochoric minimisation) this will raise UnboundLocalError: local variable 'str_press' referenced before assignment.

  1. _get_thermo() is added as a single element inside the list literal, so line_lst ends up containing a nested list, and lammps_file_interface_function later does l + "\n" for each element. When it hits the nested list from _get_thermo(), this will raise TypeError.

A minimal fix:

-    if pressure is not None:
+    str_press = ""
+    if pressure is not None:
@@
-    line_lst = [
-        "variable thermotime equal {} ".format(n_print),
-    ]
-    line_lst += [
-        _get_thermo(),
-        r"fix ensemble all box/relax" + str_press,
-        "min_style " + style,
-        "minimize "
-        + str(ionic_energy_tolerance)
-        + " "
-        + str(ionic_force_tolerance)
-        + " "
-        + str(int(max_iter))
-        + " "
-        + str(int(max_evaluations)),
-    ]
+    line_lst = ["variable thermotime equal {} ".format(n_print)]
+    line_lst += _get_thermo()
+    line_lst += [
+        r"fix ensemble all box/relax" + str_press,
+        f"min_style {style}",
+        "minimize "
+        + str(ionic_energy_tolerance)
+        + " "
+        + str(ionic_force_tolerance)
+        + " "
+        + str(int(max_iter))
+        + " "
+        + str(int(max_evaluations)),
+    ]

This both avoids the NameError and keeps line_lst flat so file writing works.

🧰 Tools
🪛 Ruff (0.14.5)

242-242: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


255-258: Avoid specifying long messages outside the exception class

(TRY003)


267-267: Loop control variable ii not used within loop body

Rename unused ii to _ii

(B007)

🤖 Prompt for AI Agents
In pyiron_lammps/compatibility/calculate.py around lines 200-291, fix two
issues: ensure str_press is always defined and avoid inserting _get_thermo() as
a nested list; set str_press = "" before the pressure conditional so it exists
for the isochoric path, and replace the list insertion that does line_lst += [
_get_thermo(), ... ] with a flat extension (e.g. line_lst += _get_thermo() or
line_lst.extend(_get_thermo())) and then append the remaining string lines (or
use line_lst += [ "fix ensemble all box/relax" + str_press, "min_style "+style,
"minimize "+... ]) so line_lst remains a flat list of strings.

Comment thread pyiron_lammps/compatibility/calculate.py
Comment on lines +48 to +79
Args:
working_directory (str): directory in which the LAMMPS calculation is executed
structure (Atoms): ase.atoms.Atoms - atomistic structure
potential (str): Name of the LAMMPS potential based on the NIST database and the OpenKIM database
calc_mode (str): select mode of calculation ["static", "md", "minimize", "vcsgc"]
calc_kwargs (dict): key-word arguments for the calculate function, the input parameters depend on the calc_mode:
"static": No parameters
"md": "temperature", "pressure", "n_ionic_steps", "time_step", "n_print", "temperature_damping_timescale",
"pressure_damping_timescale", "seed", "tloop", "initial_temperature", "langevin", "delta_temp",
"delta_press", job_name", "rotation_matrix"
"minimize": "ionic_energy_tolerance", "ionic_force_tolerance", "max_iter", "pressure", "n_print", "style",
"rotation_matrix"
"vcsgc": "mu", "ordered_element_list", "target_concentration", "kappa", "mc_step_interval", "swap_fraction",
"temperature_mc", "window_size", "window_moves", "temperature", "pressure", "n_ionic_steps",
"time_step", "n_print", "temperature_damping_timescale", "pressure_damping_timescale", "seed",
"initial_temperature", "langevin", "job_name", "rotation_matrix"
cutoff_radius (float): cut-off radius for the interatomic potential
units (str): Units for LAMMPS
bonds_kwargs (dict): key-word arguments to create atomistic bonds:
"species", "element_list", "cutoff_list", "max_bond_list", "bond_type_list", "angle_type_list",
server_kwargs (dict): key-word arguments to create server object - the available parameters are:
"user", "host", "run_mode", "queue", "qid", "cores", "threads", "new_h5", "structure_id", "run_time",
"memory_limit", "accept_crash", "additional_arguments", "gpus", "conda_environment_name",
"conda_environment_path"
enable_h5md (bool): activate h5md mode for LAMMPS
write_restart_file (bool): enable writing the LAMMPS restart file
read_restart_file (bool): enable loading the LAMMPS restart file
restart_file (str): file name of the LAMMPS restart file to copy
executable_version (str): LAMMPS version to for the execution
executable_path (str): path to the LAMMPS executable
input_control_file (str|list|dict): Option to modify the LAMMPS input file directly

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Function docstring is out of sync with the actual signature and supported modes

lammps_file_interface_function’s docstring still describes the much richer pyiron_atomistics.lammps.lammps.lammps_function interface:

  • Mentions calc_mode options including "vcsgc" (not supported here).
  • Lists many parameters (cutoff_radius, bonds_kwargs, server_kwargs, enable_h5md, restart options, executable_*`, etc.) that are not present in the function signature and are not handled anywhere.

Currently, only:

(working_directory, structure, potential, calc_mode, calc_kwargs, units, lmp_command, resource_path)

are accepted, and calc_mode is restricted to "static", "md", "minimize" (other values raise ValueError).

To avoid confusion for users of this new entry point:

  • Either update the docstring to match this slimmer API and explicitly state that higher‑level features are provided elsewhere, or
  • Extend the function to accept and honour (or at least plumb through) the documented arguments if full drop‑in compatibility is required.

Similarly, remove or clarify "vcsgc" in both the calc_mode description and calc_kwargs breakdown unless support is planned here.

Also applies to: 87-105, 107-121


🏁 Script executed:

#!/bin/bash
# Extract the function definition, docstring, and implementation
cat pyiron_lammps/compatibility/file.py | head -150 | tail -110

Length of output: 5353


🏁 Script executed:

#!/bin/bash
# Search for calc_mode validation and ValueError handling
rg -n -A5 -B5 'calc_mode' pyiron_lammps/compatibility/file.py

Length of output: 2890


🏁 Script executed:

#!/bin/bash
# Look for parameter handling in the file (cutoff_radius, bonds_kwargs, etc.)
rg -n 'cutoff_radius|bonds_kwargs|server_kwargs|enable_h5md|restart_file|executable' pyiron_lammps/compatibility/file.py

Length of output: 793


Update docstring to match actual function signature and supported calc_modes

lammps_file_interface_function has a simplified API incompatible with its docstring. The function accepts only: working_directory, structure, potential, calc_mode, calc_kwargs, units, lmp_command, resource_path (8 parameters), but the docstring documents 18+ additional parameters (cutoff_radius, bonds_kwargs, server_kwargs, enable_h5md, restart/executable options, etc.) that are never handled.

Similarly, the docstring lists "vcsgc" as a supported calc_mode (line 52), but the implementation (lines 119–121) only accepts "static", "md", or "minimize", raising ValueError for any other value.

Users following the docstring will encounter immediate errors. Either:

  • Update the docstring to match the current slimmer API and clarify that advanced features are in the upstream interface, or
  • Accept and plumb the documented parameters if full compatibility is intended.

Also remove "vcsgc" from the docstring unless support is added.

Comment thread pyiron_lammps/compatibility/file.py
Comment on lines +87 to +105
os.makedirs(working_directory, exist_ok=True)
potential_dataframe = get_potential_by_name(
potential_name=potential, resource_path=resource_path
)
write_lammps_datafile(
structure=structure,
potential_elements=potential_dataframe["Species"],
bond_dict=None,
units=units,
file_name="lammps.data",
working_directory=working_directory,
)
lmp_str_lst = lammps_file_initialization(structure=structure)
lmp_str_lst += potential_dataframe["Config"]
lmp_str_lst += ["variable dumptime equal {} ".format(calc_kwargs.get("n_print", 1))]
lmp_str_lst += [
"dump 1 all custom ${dumptime} dump.out id type xsu ysu zsu fx fy fz vx vy vz",
'dump_modify 1 sort id format line "%d %d %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g"',
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

lammps_file_initialization ignores the units argument passed to lammps_file_interface_function

You accept units in lammps_file_interface_function and pass it to:

  • write_lammps_datafile(..., units=units, ...)
  • parse_lammps_output(..., units=units, ...)

but you call:

lmp_str_lst = lammps_file_initialization(structure=structure)

lammps_file_initialization has its own units parameter defaulting to "metal", so the LAMMPS input file will always contain:

units metal

even if the caller requested a different unit system. This desynchronises the simulation units from the datafile/output parsing.

You probably want:

-    lmp_str_lst = lammps_file_initialization(structure=structure)
+    lmp_str_lst = lammps_file_initialization(structure=structure, units=units)

so all components (input, datafile, parsing) agree on the unit system.

Also applies to: 123-124

🤖 Prompt for AI Agents
In pyiron_lammps/compatibility/file.py around lines 87 to 105 (and also check
lines 123-124), lammps_file_initialization is called without forwarding the
units parameter so the generated LAMMPS input always uses the default "metal";
update the call to pass units=units (e.g., lmp_str_lst =
lammps_file_initialization(structure=structure, units=units)) so the input,
datafile, and output parsing all use the same unit system.

Comment on lines +126 to +132
shell = subprocess.check_output(
lmp_command,
cwd=working_directory,
shell=True,
universal_newlines=True,
env=os.environ.copy(),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

subprocess.check_output(..., shell=True) may be risky if lmp_command is not trusted

The function executes:

shell = subprocess.check_output(
    lmp_command,
    cwd=working_directory,
    shell=True,
    universal_newlines=True,
    env=os.environ.copy(),
)

Using shell=True with a string command is standard in many HPC wrappers, but it means any untrusted content in lmp_command is interpreted by the shell.

If lmp_command can be influenced by untrusted input, you may want to:

  • Switch to shell=False and pass a list of arguments, e.g. ["mpiexec", "-n", "1", "--oversubscribe", "lmp_mpi", "-in", "lmp.in"], or
  • Clearly document that lmp_command must be trusted and is executed via the shell.

Given this is a low‑level interface, tightening it or documenting the expectation would be enough.


I'll verify this security concern by examining the code context to determine if lmp_command is actually user-controlled and if there's genuine risk.




🏁 Script executed:

#!/bin/bash
# Examine the full function context around the subprocess call
cat pyiron_lammps/compatibility/file.py | head -150 | tail -50

Length of output: 1799


🏁 Script executed:

#!/bin/bash
# Get broader context of the file to understand function signature and parameters
grep -n "def " pyiron_lammps/compatibility/file.py

Length of output: 113


🏁 Script executed:

#!/bin/bash
# Search for lmp_command usage and origin in the file
rg -n "lmp_command" pyiron_lammps/compatibility/file.py -B 5 -A 5

Length of output: 748


🏁 Script executed:

#!/bin/bash
# Check for any imports and the overall structure of this module
head -50 pyiron_lammps/compatibility/file.py

Length of output: 1609


I need to examine where this function is called to understand the actual risk profile.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find all callers of lammps_file_interface_function
rg -n "lammps_file_interface_function" --type py -B 2 -A 2

Length of output: 624


🏁 Script executed:

#!/bin/bash
# Check if the function is exported or documented as public API
grep -r "lammps_file_interface_function" --include="*.py" --include="*.md" . 2>/dev/null | head -20

Length of output: 137


🏁 Script executed:

#!/bin/bash
# Look for __init__.py or __all__ exports to see if function is in public API
find . -name "__init__.py" -type f -exec grep -l "lammps_file_interface_function" {} \;

Length of output: 27


Tighten lmp_command handling to prevent shell injection

The security concern is valid. lmp_command is a user-controllable function parameter exported in the public API, passed directly to subprocess.check_output(..., shell=True) without validation. Callers can inject arbitrary shell commands.

Recommended fixes:

  1. Switch to shell=False and accept lmp_command as a list: ["mpiexec", "-n", "1", "--oversubscribe", "lmp_mpi", "-in", "lmp.in"]
  2. Or, add input validation (allowlist known commands/patterns) and document that lmp_command must be trusted
  3. Or, parse and validate lmp_command to ensure it only contains expected executable and known-safe options
🧰 Tools
🪛 Ruff (0.14.5)

126-126: subprocess call with shell=True identified, security issue

(S602)

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/test_compatibility_file.py (2)

8-30: Minor readability nit in working-directory setup

os.path.join(__file__, "..", "lmp") works after abspath/normalization, but it’s slightly non‑obvious because it treats the file path as a directory. Using os.path.dirname(__file__) would make the intent clearer:

-        self.working_dir = os.path.abspath(os.path.join(__file__, "..", "lmp"))
+        self.working_dir = os.path.abspath(
+            os.path.join(os.path.dirname(__file__), "..", "lmp")
+        )

tearDown cleanup logic itself looks good.


53-75: Slightly improve path handling and consider brittleness of exact line matches

Two small points here:

  • For clarity and portability, prefer os.path.join over string concatenation when opening lmp.in:
-        with open(self.working_dir + "/lmp.in", "r") as f:
+        with open(os.path.join(self.working_dir, "lmp.in"), "r") as f:
  • The content_expected vs content comparison enforces exact line matches (including whitespace), which is excellent for strict backward‑compatibility but will also break on harmless formatting tweaks. If the goal is primarily to validate semantics rather than formatting, you might consider more flexible checks (e.g., prefix/substring or regex) for some lines. If strict reproduction of lmp.in is required, then this is fine as‑is and this point can be ignored.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98cb97a and 2a4e10c.

📒 Files selected for processing (1)
  • tests/test_compatibility_file.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_compatibility_file.py (1)
pyiron_lammps/compatibility/file.py (1)
  • lammps_file_interface_function (17-143)
🪛 GitHub Actions: Pipeline
tests/test_compatibility_file.py

[error] 41-41: test_calc_md failed due to upstream ValueError: No resource_path found during test execution.

🪛 Ruff (0.14.5)
tests/test_compatibility_file.py

41-41: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

Comment thread tests/test_compatibility_file.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
tests/test_compatibility_file.py (1)

31-52: Fix cp command path causing CalledProcessError and mark shell_output as unused

CI shows both tests failing because the cp ../static/compatibility_output/* . command exits non‑zero when run via lammps_file_interface_function (i.e., from cwd=self.working_dir). This usually means the compatibility_output directory or its contents are not found at the expected relative location.

To make this more robust and independent of the current working directory, construct an absolute path to compatibility_output from __file__, similar to how you handle resource_path, and reuse it in both tests:

@@
-    def test_calc_md_npt(self):
-        structure = bulk("Al", cubic=True).repeat([2, 2, 2])
-        potential = "1999--Mishin-Y--Al--LAMMPS--ipr1"
-        units = "metal"
-        calc_kwargs = {
+    def test_calc_md_npt(self):
+        structure = bulk("Al", cubic=True).repeat([2, 2, 2])
+        potential = "1999--Mishin-Y--Al--LAMMPS--ipr1"
+        units = "metal"
+        compatibility_output_dir = os.path.join(
+            os.path.dirname(__file__), "static", "compatibility_output"
+        )
+        calc_kwargs = {
@@
-        shell_output, parsed_output, job_crashed = lammps_file_interface_function(
+        _, parsed_output, job_crashed = lammps_file_interface_function(
@@
-            units=units,
-            lmp_command="cp ../static/compatibility_output/* .",
-            resource_path=os.path.abspath(
-                os.path.join("..", os.path.dirname(__file__), "static", "potential")
-            ),
+            units=units,
+            lmp_command=f"cp {compatibility_output_dir}/* .",
+            resource_path=os.path.abspath(
+                os.path.join(os.path.dirname(__file__), "static", "potential")
+            ),
         )
@@
-    def test_calc_md_nvt(self):
-        structure = bulk("Al", cubic=True).repeat([2, 2, 2])
-        potential = "1999--Mishin-Y--Al--LAMMPS--ipr1"
-        units = "metal"
-        calc_kwargs = {"temperature": 500.0, "n_ionic_steps": 1000, "n_print": 100}
-        shell_output, parsed_output, job_crashed = lammps_file_interface_function(
+    def test_calc_md_nvt(self):
+        structure = bulk("Al", cubic=True).repeat([2, 2, 2])
+        potential = "1999--Mishin-Y--Al--LAMMPS--ipr1"
+        units = "metal"
+        compatibility_output_dir = os.path.join(
+            os.path.dirname(__file__), "static", "compatibility_output"
+        )
+        calc_kwargs = {"temperature": 500.0, "n_ionic_steps": 1000, "n_print": 100}
+        _, parsed_output, job_crashed = lammps_file_interface_function(
@@
-            units=units,
-            lmp_command="cp ../static/compatibility_output/* .",
-            resource_path=os.path.abspath(
-                os.path.join("..", os.path.dirname(__file__), "static", "potential")
-            ),
+            units=units,
+            lmp_command=f"cp {compatibility_output_dir}/* .",
+            resource_path=os.path.abspath(
+                os.path.join(os.path.dirname(__file__), "static", "potential")
+            ),
         )

This should resolve the CalledProcessError as long as tests/static/compatibility_output is present in the repo, and it also addresses the Ruff warning by marking shell_output as intentionally unused. The small cleanup on resource_path removes the redundant ".." in front of an absolute path. (Based on relevant code in pyiron_lammps/compatibility/file.py.)

Also applies to: 80-96

🧹 Nitpick comments (1)
tests/test_compatibility_file.py (1)

9-30: Tighten working directory construction (optional)

self.working_dir = os.path.abspath(os.path.join(__file__, "..", "lmp")) works, but it treats __file__ as a directory component. It’s more idiomatic (and a bit clearer) to base this on the containing directory:

-        self.working_dir = os.path.abspath(os.path.join(__file__, "..", "lmp"))
+        self.working_dir = os.path.abspath(
+            os.path.join(os.path.dirname(__file__), "lmp")
+        )

This also makes it easier to reason about relative paths like ../static/... used later.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a4e10c and 8c91424.

📒 Files selected for processing (1)
  • tests/test_compatibility_file.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_compatibility_file.py (1)
pyiron_lammps/compatibility/file.py (1)
  • lammps_file_interface_function (17-143)
🪛 GitHub Actions: Pipeline
tests/test_compatibility_file.py

[error] 41-41: test_calc_md_npt failed due to subprocess.CalledProcessError from cp command.


[error] 85-85: test_calc_md_nvt failed due to subprocess.CalledProcessError from cp command.

🪛 Ruff (0.14.5)
tests/test_compatibility_file.py

41-41: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


85-85: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🔇 Additional comments (1)
tests/test_compatibility_file.py (1)

56-79: Good coverage and expectations on generated lmp.in content

The assertions on parsed_output["generic"] keys and the expected lmp.in lines (units, boundary, pair_style, fixes, thermo, run, etc.) are precise and align with the intended behavior of lammps_file_interface_function for MD/NPT and MD/NVT modes. This is a solid end‑to‑end regression test for the new compatibility layer.

Also applies to: 100-122

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (8)
tests/test_compatibility_file.py (2)

54-54: Unused shell_output variable in all test methods

Static analysis correctly identifies that shell_output is unpacked but never used in any of the test methods. Replace with _ to indicate it's intentionally unused.

Apply this change to all eight occurrences:

-        shell_output, parsed_output, job_crashed = lammps_file_interface_function(
+        _, parsed_output, job_crashed = lammps_file_interface_function(

(Apply to lines 54, 101, 143, 184, 225, 261, 298, and 337.)

Also applies to: 101-101, 143-143, 184-184, 225-225, 261-261, 298-298, 337-337


69-69: Non-portable path construction using string concatenation

The tests build file paths using string concatenation (e.g., self.working_dir + "/lmp.in"), which is not portable across operating systems. Use os.path.join instead.

Apply this change to all eight occurrences:

-        with open(self.working_dir + "/lmp.in", "r") as f:
+        with open(os.path.join(self.working_dir, "lmp.in"), "r") as f:

(Apply to lines 69, 116, 158, 199, 239, 275, 313, and 352.)

Also applies to: 116-116, 158-158, 199-199, 239-239, 275-275, 313-313, 352-352

pyiron_lammps/compatibility/calculate.py (6)

10-24: time_step parameter is documented but not accepted by calc_md

The docstring (line 41) describes a time_step parameter in femtoseconds, and the file interface function's documentation mentions it as part of calc_kwargs for "md" mode. However:

  1. The function signature (lines 10-24) does not include time_step
  2. The timestep is hard-coded to 0.001 at line 188
  3. Passing time_step via calc_kwargs will raise TypeError: calc_md() got an unexpected keyword argument 'time_step'

Add time_step to the signature with a default value and apply unit conversion:

 def calc_md(
     temperature=None,
     pressure=None,
     n_print=100,
+    time_step=1.0,
     temperature_damping_timescale=100.0,
     pressure_damping_timescale=1000.0,

Then replace the hard-coded timestep:

-    line_lst.append("timestep 0.001")
+    line_lst.append(f"timestep {time_step * time_units}")

Also applies to: 41-41, 188-188


92-107: Explicit initial_temperature is not unit-converted, causing mismatch in non-metal units

At line 102, temperature is multiplied by temperature_units. However, when a user provides an explicit initial_temperature (lines 105-106), it is never converted. Only the implicit default (2 * temperature[0]) uses the already-converted temperature value.

When temperature_units != 1 (non-metal units), this creates a unit mismatch where explicit initial_temperature is in pyiron units but is interpreted as LAMMPS units.

Apply unit conversion to explicit initial_temperature:

     # Apply initial overheating (default uses the theorem of equipartition of energy between KE and PE)
-    if initial_temperature is None and temperature is not None:
-        initial_temperature = 2 * temperature[0]
+    if temperature is not None:
+        if initial_temperature is None:
+            initial_temperature = 2 * temperature[0]
+        else:
+            initial_temperature = float(initial_temperature) * temperature_units

113-122: NPT pressure calculation requires rotation_matrix, but no guard prevents None

At line 120-122, _pressure_to_lammps is called with rotation_matrix without validating it first. Inside _pressure_to_lammps (line 400-401), the code does np.matrix.trace(rotation_matrix), which will raise a low-level NumPy exception if rotation_matrix is None.

The calc_minimize function already guards this case (lines 259-264). Apply the same pattern here.

Add validation before the pressure conversion:

     if pressure is not None:  # NPT
         if temperature is None or temperature.min() <= 0:
             raise ValueError(
                 "Target temperature for fix nvt/npt/nph cannot be 0 or negative"
             )
+
+        if rotation_matrix is None:
+            raise ValueError(
+                "No rotation matrix given while trying to convert pressure. "
+                "This is most likely due to no structure being defined."
+            )
 
         force_skewed = False

218-224: Docstring documents deprecated e_tol/f_tol parameters that are not accepted

The docstring (lines 222-223) mentions e_tol and f_tol as deprecated aliases, but the function signature (lines 203-212) does not include them. Callers using these names will get TypeError: unexpected keyword argument.

Either remove the parameters from the docstring, or add backwards compatibility via **kwargs:

def calc_minimize(
    structure: Atoms,
    ionic_energy_tolerance=0.0,
    ionic_force_tolerance=1e-4,
    ...,
    **kwargs
):
    if "e_tol" in kwargs:
        warnings.warn("e_tol is deprecated, use ionic_energy_tolerance", DeprecationWarning, stacklevel=2)
        ionic_energy_tolerance = kwargs.pop("e_tol")
    if "f_tol" in kwargs:
        warnings.warn("f_tol is deprecated, use ionic_force_tolerance", DeprecationWarning, stacklevel=2)
        ionic_force_tolerance = kwargs.pop("f_tol")
    if kwargs:
        raise TypeError(f"Unexpected keyword arguments: {tuple(kwargs)}")

324-354: initial_temperature=0 does not skip velocity initialization as documented

The calc_md docstring (lines 56-57) states: "If 0, the velocity field is not initialized (in which case the initial velocity given in structure will be used)."

However, _set_initial_velocity always returns a velocity all create ... command regardless of the temperature value, overwriting any existing velocities.

Special-case temperature == 0 to return an empty string:

 def _set_initial_velocity(
     temperature,
     seed=80996,
     ...
 ):
+    if temperature == 0:
+        return ""
+
     arg = ""

Then in calc_md, only append the command if non-empty:

-    line_lst.append(
-        _set_initial_velocity(
+    vel_cmd = _set_initial_velocity(
             temperature=initial_temperature,
             ...
-        )
-    )
+    )
+    if vel_cmd:
+        line_lst.append(vel_cmd)

Also applies to: 56-57


357-420: _pressure_to_lammps lacks None check for rotation_matrix, causing obscure errors

At lines 400-401, the function does np.matrix.trace(rotation_matrix) without first checking if rotation_matrix is None. When called with rotation_matrix=None, this produces a low-level NumPy exception instead of a clear error message.

Add an explicit guard:

     # If necessary, rotate the pressure tensor to the Lammps coordinate frame.
     # Isotropic, hydrostatic pressures are rotation invariant.
+    if rotation_matrix is None:
+        raise ValueError(
+            "rotation_matrix must be provided when converting a non-scalar pressure."
+        )
     if not np.isclose(
         np.matrix.trace(rotation_matrix), 3
     ) and not _is_isotropic_hydrostatic(pressure):
🧹 Nitpick comments (3)
pyiron_lammps/compatibility/calculate.py (3)

75-75: Missing stacklevel=2 in warnings.warn calls

Static analysis correctly flags that warnings.warn() calls should include stacklevel=2 so the warning points to the caller's code, not the library internals.

Add stacklevel=2 to all warnings.warn() calls:

-        warnings.warn(
-            "WARNING: `delta_temp` is deprecated, please use `temperature_damping_timescale`."
-        )
+        warnings.warn(
+            "WARNING: `delta_temp` is deprecated, please use `temperature_damping_timescale`.",
+            stacklevel=2
+        )

Apply the same change to lines 84, 177, 245, 440, and 469.

Also applies to: 84-84, 177-177, 245-245, 440-440, 469-469


138-138: Unused variable force_skewed can be removed

Static analysis correctly identifies that force_skewed is assigned at line 138 but never used.

Remove the unused variable:

-                    if ii > 2:
-                        force_skewed = True

277-277: Loop variable ii is unused in the loop body

Static analysis flags that the loop counter ii at line 277 is never referenced. Rename it to _ii or _ to indicate it's intentionally unused.

-            for ii, (press, str_axis) in enumerate(
+            for _, (press, str_axis) in enumerate(
                 zip(pressure, ["x", "y", "z", "xy", "xz", "yz"])
             ):
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc80c8e and 46ec6a0.

📒 Files selected for processing (2)
  • pyiron_lammps/compatibility/calculate.py (1 hunks)
  • tests/test_compatibility_file.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
pyiron_lammps/compatibility/calculate.py (1)
pyiron_lammps/structure.py (2)
  • UnfoldingPrism (38-191)
  • is_skewed (433-450)
tests/test_compatibility_file.py (1)
pyiron_lammps/compatibility/file.py (1)
  • lammps_file_interface_function (17-145)
🪛 Ruff (0.14.5)
pyiron_lammps/compatibility/calculate.py

75-75: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


84-84: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


109-109: Avoid specifying long messages outside the exception class

(TRY003)


115-117: Avoid specifying long messages outside the exception class

(TRY003)


138-138: Local variable force_skewed is assigned to but never used

Remove assignment to unused variable force_skewed

(F841)


157-159: Avoid specifying long messages outside the exception class

(TRY003)


177-177: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


245-245: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


261-264: Avoid specifying long messages outside the exception class

(TRY003)


277-277: Loop control variable ii not used within loop body

Rename unused ii to _ii

(B007)


304-304: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


396-396: Avoid specifying long messages outside the exception class

(TRY003)


404-411: Avoid specifying long messages outside the exception class

(TRY003)


440-440: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


469-469: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)

tests/test_compatibility_file.py

54-54: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


101-101: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


143-143: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


184-184: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


225-225: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


261-261: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


298-298: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


337-337: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: unittest_matrix (windows-latest, 3.14)
🔇 Additional comments (1)
tests/test_compatibility_file.py (1)

1-373: Tests look comprehensive and well-structured

The test suite provides excellent coverage of the compatibility interface:

  • Error handling for invalid calc_mode
  • MD modes: NPT, NPT+Langevin, NVT, NVT+Langevin
  • Static and minimize modes with and without pressure
  • Validation of both parsed outputs and generated LAMMPS input files

The structure is clean, setup/teardown is appropriate, and assertions are thorough.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
pyiron_lammps/structure.py (1)

403-411: Guard against empty structures when inferring coordinate dimensionality

dim = len(self._structure.positions[0]) will raise an IndexError for empty structures, whereas using self._structure.positions.shape[1] works for len(structure) == 0 as well. The same pattern appears elsewhere.

Consider switching to positions.shape[1] (and optionally aligning other callers) to keep empty-structure handling consistent:

-        for id_atom, (el, coord) in enumerate(zip(el_lst, coords)):
-            dim = len(self._structure.positions[0])
+        for id_atom, (el, coord) in enumerate(zip(el_lst, coords)):
+            dim = self._structure.positions.shape[1]
             c = np.zeros(3)
             c[:dim] = coord
♻️ Duplicate comments (5)
pyiron_lammps/compatibility/file.py (2)

128-134: Shell execution of lmp_command is potentially unsafe; consider tightening or documenting

subprocess.check_output is invoked with shell=True and a user-provided lmp_command string. This is idiomatic in many HPC workflows, but if lmp_command can be influenced by untrusted input, it allows shell injection.

Options:

  • Prefer shell=False and accept lmp_command as a List[str].
  • Or keep shell=True but clearly document that lmp_command is executed through the shell and must be trusted (e.g., user/job-level only).

Given this is a low-level compatibility entry point, explicit documentation may be enough if changing the calling convention is undesirable.

Check current Python `subprocess` documentation for best practices around `shell=True` vs. list arguments, especially for long-running HPC-style commands.

27-83: Docstring is out-of-sync with the actual API and supported calc_modes

The docstring for lammps_file_interface_function still describes the much richer pyiron_atomistics.lammps.lammps.lammps_function interface:

  • Documents many parameters that are not in the signature and never used here (cutoff_radius, bonds_kwargs, server_kwargs, restart options, enable_h5md, executable_*`, etc.).
  • Mentions a "vcsgc" mode and a very large calc_kwargs surface for it, but the implementation only supports "static", "md", and "minimize" and raises for anything else.
  • Contains a stray job_name" without an opening quote.

This will mislead callers of this compatibility entry point.

Either trim the docstring to match this slimmer API (and explicitly state that advanced features live in the upstream job API), or extend the function to accept and plumb through those arguments and implement "vcsgc" if full drop-in compatibility is required.

pyiron_lammps/compatibility/calculate.py (3)

96-112: Temperature handling inconsistencies: initial_temperature units and “no initialization” behaviour

Two related issues in the MD helper:

  1. Explicit initial_temperature is not unit-converted

    • temperature is scaled by temperature_units.
    • The implicit default initial_temperature = 2 * temperature[0] is therefore in LAMMPS units.
    • But an explicitly provided initial_temperature is passed straight through to _set_initial_velocity with no conversion, so for non-"metal" units it is interpreted in the wrong unit system.

    A minimal fix:

  • Apply initial overheating (default uses the theorem of equipartition of energy between KE and PE)

  • if initial_temperature is None and temperature is not None:
  •    initial_temperature = 2 * temperature[0]
    
  • Apply initial overheating (default uses the theorem of equipartition of energy between KE and PE)

  • if temperature is not None:
  •    if initial_temperature is None:
    
  •        initial_temperature = 2 * temperature[0]
    
  •    else:
    
  •        initial_temperature = float(initial_temperature) * temperature_units
    
    
    
  1. initial_temperature=0 does not match the docstring promise

    The docstring says initial_temperature = 0 should “not initialize” velocities, but _set_initial_velocity always returns a velocity all create ... command. To honour the docstring:

    def _set_initial_velocity(
        temperature,
        seed=80996,
        gaussian=False,
        append_value=False,
        zero_lin_momentum=True,
        zero_rot_momentum=True,
    ):

@@

  • arg = ""
  • if temperature == 0:
  •    return ""
    
  • arg = ""
    @@
  • return "velocity all create " + str(temperature) + " " + str(seed) + arg
  • return "velocity all create " + str(temperature) + " " + str(seed) + arg

and in `calc_md`:

```diff
-    line_lst.append(
-        _set_initial_velocity(
+    vel_cmd = _set_initial_velocity(
          temperature=initial_temperature,
          seed=seed,
          gaussian=True,
          append_value=False,
          zero_lin_momentum=True,
          zero_rot_momentum=True,
-        )
-    )
+        )
+    if vel_cmd:
+        line_lst.append(vel_cmd)

Also applies to: 192-203, 329-359


118-127: Constant-pressure MD still assumes a valid rotation_matrix and can fail with a low-level NumPy error

In the NPT branch of calc_md:

pressure = _pressure_to_lammps(
    pressure=pressure, rotation_matrix=rotation_matrix, units=units
)

and _pressure_to_lammps unconditionally does:

if not np.isclose(np.matrix.trace(rotation_matrix), 3) and ...:
    ...

If pressure is specified as a tensor/list and rotation_matrix is None (the default), this produces an opaque NumPy error instead of a clear user-facing message.

You already guard this case in calc_minimize via _get_rotation_matrix. For MD, mirror that behaviour or introduce a rotation_matrix is None check in _pressure_to_lammps:

-def _pressure_to_lammps(pressure, rotation_matrix, units="metal"):
+def _pressure_to_lammps(pressure, rotation_matrix, units="metal"):
@@
-    # If necessary, rotate the pressure tensor to the Lammps coordinate frame.
-    # Isotropic, hydrostatic pressures are rotation invariant.
-    if not np.isclose(
-        np.matrix.trace(rotation_matrix), 3
-    ) and not _is_isotropic_hydrostatic(pressure):
+    # If necessary, rotate the pressure tensor to the Lammps coordinate frame.
+    # Isotropic, hydrostatic pressures are rotation invariant.
+    if rotation_matrix is None:
+        raise ValueError(
+            "rotation_matrix must be provided when converting a non-isotropic pressure tensor."
+        )
+    if not np.isclose(np.matrix.trace(rotation_matrix), 3) and not _is_isotropic_hydrostatic(pressure):

and in calc_md, either:

  • Require callers to pass a valid rotation matrix when pressure is a tensor, or
  • Derive it from a structure (similar to calc_minimize) if you have one available in the calling context.

Also applies to: 362-425


208-243: calc_minimize docstring documents deprecated e_tol/f_tol that are not accepted

The docstring still mentions e_tol and f_tol as deprecated aliases, but the function signature does not accept them. Any caller using these names will now see a TypeError, contrary to the docstring.

Either:

  • Remove e_tol/f_tol from the docstring, or
  • Add a small compatibility layer via **kwargs:
-def calc_minimize(
-    structure: Atoms,
-    ionic_energy_tolerance=0.0,
-    ionic_force_tolerance=1e-4,
-    max_iter=100000,
-    pressure=None,
-    n_print=100,
-    style="cg",
-    rotation_matrix=None,
-    units="metal",
-):
+def calc_minimize(
+    structure: Atoms,
+    ionic_energy_tolerance=0.0,
+    ionic_force_tolerance=1e-4,
+    max_iter=100000,
+    pressure=None,
+    n_print=100,
+    style="cg",
+    rotation_matrix=None,
+    units="metal",
+    **kwargs,
+):
@@
-    max_evaluations = 100 * max_iter
+    if "e_tol" in kwargs:
+        ionic_energy_tolerance = kwargs.pop("e_tol")
+    if "f_tol" in kwargs:
+        ionic_force_tolerance = kwargs.pop("f_tol")
+    if kwargs:
+        raise TypeError(f"Unexpected keyword arguments: {tuple(kwargs)}")
+
+    max_evaluations = 100 * max_iter

so the deprecation note matches actual behaviour.

🧹 Nitpick comments (2)
pyiron_lammps/compatibility/structure.py (1)

127-133: Minor: unused loop index in bond construction

The loop

for i_shell, ib_shell_lst in enumerate(b_lst):
    for ib in np.unique(ib_shell_lst):
        ...

never uses i_shell. Renaming it to _ clarifies intent and quiets linters:

-            for ia, i_bonds in enumerate(bonds_lst):
+            for ia, i_bonds in enumerate(bonds_lst):
@@
-                    for i_shell, ib_shell_lst in enumerate(b_lst):
+                    for _shell_idx, ib_shell_lst in enumerate(b_lst):
pyiron_lammps/compatibility/file.py (1)

116-118: Minor: avoid ambiguous single-letter loop variable when writing input file

The list comprehension:

f.writelines([l + "\n" for l in lmp_str_lst])

works but uses the ambiguous name l. Renaming it improves readability and avoids E741:

-        f.writelines([l + "\n" for l in lmp_str_lst])
+        f.writelines([line + "\n" for line in lmp_str_lst])
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46ec6a0 and a263de3.

📒 Files selected for processing (4)
  • pyiron_lammps/compatibility/calculate.py (1 hunks)
  • pyiron_lammps/compatibility/file.py (1 hunks)
  • pyiron_lammps/compatibility/structure.py (1 hunks)
  • pyiron_lammps/structure.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
pyiron_lammps/compatibility/file.py (4)
pyiron_lammps/compatibility/calculate.py (3)
  • calc_md (10-205)
  • calc_minimize (208-305)
  • calc_static (308-309)
pyiron_lammps/output.py (1)
  • parse_lammps_output (73-135)
pyiron_lammps/potential.py (1)
  • get_potential_by_name (349-355)
pyiron_lammps/structure.py (1)
  • write_lammps_datafile (501-513)
pyiron_lammps/compatibility/calculate.py (1)
pyiron_lammps/structure.py (2)
  • UnfoldingPrism (38-191)
  • is_skewed (481-498)
pyiron_lammps/compatibility/structure.py (1)
pyiron_lammps/structure.py (12)
  • structure (228-234)
  • structure (237-251)
  • LammpsStructure (194-478)
  • _get_velocities_input_string (253-275)
  • get_lammps_id_dict (299-302)
  • el_eam_lst (278-284)
  • el_eam_lst (287-296)
  • rotate_positions (433-447)
  • lammps_header (305-335)
  • simulation_cell (337-358)
  • potential (220-221)
  • potential (224-225)
🪛 Ruff (0.14.5)
pyiron_lammps/compatibility/file.py

112-114: Avoid specifying long messages outside the exception class

(TRY003)


117-117: Ambiguous variable name: l

(E741)


128-128: subprocess call with shell=True identified, security issue

(S602)

pyiron_lammps/compatibility/calculate.py

80-80: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


89-89: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


114-114: Avoid specifying long messages outside the exception class

(TRY003)


120-122: Avoid specifying long messages outside the exception class

(TRY003)


143-143: Local variable force_skewed is assigned to but never used

Remove assignment to unused variable force_skewed

(F841)


162-164: Avoid specifying long messages outside the exception class

(TRY003)


182-182: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


250-250: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


266-269: Avoid specifying long messages outside the exception class

(TRY003)


282-282: Loop control variable ii not used within loop body

Rename unused ii to _ii

(B007)


309-309: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


401-401: Avoid specifying long messages outside the exception class

(TRY003)


409-416: Avoid specifying long messages outside the exception class

(TRY003)


445-445: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


474-474: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)

pyiron_lammps/compatibility/structure.py

100-100: Avoid specifying long messages outside the exception class

(TRY003)


131-131: Loop control variable i_shell not used within loop body

Rename unused i_shell to _i_shell

(B007)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: unittest_matrix (windows-latest, 3.14)
🔇 Additional comments (6)
pyiron_lammps/structure.py (1)

501-512: atom_type plumbing into write_lammps_datafile looks correct

The new atom_type parameter is cleanly added with a default, passed through to LammpsStructure, and keeps existing call sites working unchanged. This aligns with the compatibility layer expecting different atom styles.

pyiron_lammps/compatibility/structure.py (1)

310-336: get_bonds wrapper looks consistent with structuretoolkit neighbor API

The get_bonds helper simply configures get_neighbors(..., mode="ragged") and forwards to neighbors.get_bonds, exposing radius/max_shells/prec in a thin wrapper. This is a reasonable compatibility layer around structuretoolkit and fits the rest of the module.

pyiron_lammps/compatibility/file.py (1)

148-156: Initialization helper is simple and aligns with LAMMPS expectations

Aside from the units forwarding issue mentioned earlier, lammps_file_initialization cleanly derives periodic vs fixed boundaries from structure.pbc and emits a minimal, predictable header (units, dimension, boundary, atom_style atomic, read_data). This is a reasonable compatibility shim.

pyiron_lammps/compatibility/calculate.py (3)

248-304: Minimization command assembly and thermo setup look sane

calc_minimize now:

  • Validates units and converts tolerances using LAMMPS_UNIT_CONVERSIONS.
  • Builds a flat line_lst with variable thermotime, thermo setup via _get_thermo(), optional fix ensemble all box/relax, followed by min_style and minimize ... line.
  • Returns (line_lst, structure) for the caller to update its structure reference.

This is consistent and avoids the nested-list bug from earlier iterations.


308-317: Static calculation helper is minimal and consistent with MD/minimize thermo handling

calc_static reuses _get_thermo and adds a single "run 0" line, which is exactly what you want for a “static” evaluation. No issues here.


320-327: Hydrostatic/isotropic pressure helper behaves as expected

_is_isotropic_hydrostatic correctly checks:

  • Axial components all equal and non-None.
  • Shear components all None or all zero.

This is a good encapsulation for deciding whether a pressure tensor can be used without rotation. No changes needed.

Comment on lines +84 to +107
if calc_kwargs is None:
calc_kwargs = {}

os.makedirs(working_directory, exist_ok=True)
potential_dataframe = get_potential_by_name(
potential_name=potential, resource_path=resource_path
)
lmp_str_lst = lammps_file_initialization(structure=structure)
lmp_str_lst += potential_dataframe["Config"]
lmp_str_lst += ["variable dumptime equal {} ".format(calc_kwargs.get("n_print", 1))]
lmp_str_lst += [
"dump 1 all custom ${dumptime} dump.out id type xsu ysu zsu fx fy fz vx vy vz",
'dump_modify 1 sort id format line "%d %d %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g"',
]

if calc_mode == "static":
lmp_str_lst += calc_static()
elif calc_mode == "md":
if "n_ionic_steps" in calc_kwargs.keys():
n_ionic_steps = int(calc_kwargs.pop("n_ionic_steps"))
else:
n_ionic_steps = 1
lmp_str_lst += calc_md(**calc_kwargs)
lmp_str_lst += ["run {} ".format(n_ionic_steps)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

units argument is not consistently respected (input script always uses "metal")

The units parameter is correctly forwarded to:

  • write_lammps_datafile(..., units=units, ...)
  • parse_lammps_output(..., units=units, ...)

but the actual LAMMPS input and MD/minimization helpers still hard-code or default to "metal":

  • lammps_file_initialization(structure=structure) uses its own default units="metal".
  • calc_md and calc_minimize are called without a units argument, so they default to "metal" again.

For any call with units != "metal", the datafile and parsing will assume one unit system while the LAMMPS run itself is configured with another, which is a serious physical inconsistency.

Recommend:

-    lmp_str_lst = lammps_file_initialization(structure=structure)
+    lmp_str_lst = lammps_file_initialization(structure=structure, units=units)
@@
-    if calc_mode == "static":
-        lmp_str_lst += calc_static()
-    elif calc_mode == "md":
+    if calc_mode == "static":
+        lmp_str_lst += calc_static()
+    elif calc_mode == "md":
@@
-        lmp_str_lst += calc_md(**calc_kwargs)
+        lmp_str_lst += calc_md(units=units, **calc_kwargs)
@@
-    elif calc_mode == "minimize":
-        lmp_str_tmp_lst, structure = calc_minimize(structure=structure, **calc_kwargs)
+    elif calc_mode == "minimize":
+        lmp_str_tmp_lst, structure = calc_minimize(
+            structure=structure, units=units, **calc_kwargs
+        )

and keep lammps_file_initialization as the single source of truth for the units card.

Also applies to: 119-126, 148-156

🤖 Prompt for AI Agents
pyiron_lammps/compatibility/file.py lines ~84-107 (also apply same fixes to
~119-126 and ~148-156): the LAMMPS input currently hardcodes "metal" via
lammps_file_initialization and omitted units when calling calc_md/calc_minimize,
causing mismatch with write_lammps_datafile and parse_lammps_output; fix by
passing the local units variable through to lammps_file_initialization (e.g.,
lammps_file_initialization(structure=structure, units=units)) and also pass
units into calc_md and calc_minimize calls (e.g., calc_md(..., units=units) /
calc_minimize(..., units=units)), and remove or override any internal defaults
so the units card in the generated input script is always derived from the
single units parameter used elsewhere.

Comment on lines +12 to +21
class LammpsStructureCompatibility(LammpsStructure):
def __init__(
self,
bond_dict: Optional[Dict] = None,
units: str = "metal",
atom_type: str = "atomic",
):
super().__init__(bond_dict=bond_dict, units=units, atom_type=atom_type)
self._molecule_ids = []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

atom_type argument is effectively ignored by the compatibility structure

LammpsStructureCompatibility.__init__ forwards atom_type only to the base class, which stores it in _atom_type, but your overridden structure setter branches on self.atom_type (initially None). As a result, LammpsStructureCompatibility(atom_type="full") (or "bond"/"charge") will still execute the "atomic" branch unless the caller later mutates self.atom_type manually.

To make atom_type behave as expected for this compatibility entry point, either:

  • Explicitly mirror the argument into self.atom_type in __init__, or
  • Change the setter to branch on self._atom_type instead of self.atom_type.

For example:

 class LammpsStructureCompatibility(LammpsStructure):
     def __init__(
         self,
         bond_dict: Optional[Dict] = None,
         units: str = "metal",
         atom_type: str = "atomic",
     ):
-        super().__init__(bond_dict=bond_dict, units=units, atom_type=atom_type)
-        self._molecule_ids = []
+        super().__init__(bond_dict=bond_dict, units=units, atom_type=atom_type)
+        self.atom_type = atom_type
+        self._molecule_ids = []

@@
-        if self.atom_type == "full":
+        if self.atom_type == "full":
             input_str = self.structure_full()

Also applies to: 41-51

🤖 Prompt for AI Agents
In pyiron_lammps/compatibility/structure.py around lines 12-21 and 41-51, the
constructor forwards atom_type to the base but does not initialize the property
used by the overridden structure setter, so passing atom_type has no effect;
after the super().__init__(...) call assign the incoming atom_type to the
instance property used by the setter (e.g., self.atom_type = atom_type) so the
setter branches correctly, or alternatively change the setter to read
self._atom_type instead—implement the former by setting the property in both
constructors present in those line ranges.

Comment on lines +53 to +99
def structure_bond(self):
"""

Returns:

"""
species_lammps_id_dict = self.get_lammps_id_dict(self.el_eam_lst)
self.molecule_ids = None
# analyze structure to get molecule_ids, bonds, angles etc
coords = self.rotate_positions(self._structure)

elements = self._structure.get_chemical_symbols()

## Standard atoms stuff
atoms = "Atoms \n\n"
# atom_style bond
# format: atom-ID, molecule-ID, atom_type, x, y, z
format_str = "{0:d} {1:d} {2:d} {3:f} {4:f} {5:f} "
if len(self._structure.positions[0]) == 3:
for id_atom, (x, y, z) in enumerate(coords):
id_mol = self.molecule_ids[id_atom]
atoms += (
format_str.format(
id_atom + 1,
id_mol,
species_lammps_id_dict[elements[id_atom]],
x,
y,
z,
)
+ "\n"
)
elif len(self._structure.positions[0]) == 2:
for id_atom, (x, y) in enumerate(coords):
id_mol = self.molecule_ids[id_atom]
atoms += (
format_str.format(
id_atom + 1,
id_mol,
species_lammps_id_dict[elements[id_atom]],
x,
y,
0.0,
)
+ "\n"
)
else:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

molecule_ids is never initialized and will currently crash structure_bond/structure_full

Both structure_bond and structure_full index self.molecule_ids[id_atom], but:

  • There is no @property for molecule_ids.
  • __init__ only defines _molecule_ids = [].
  • structure_bond/structure_full start with self.molecule_ids = None, which simply sets an attribute to None.

This leads to TypeError: 'NoneType' object is not subscriptable as soon as you hit the for id_atom loops.

You probably want a lazily populated molecule ID array. A minimal fix is to add a proper property that uses _molecule_ids as backing storage and interprets None as "reset to default sequential IDs":

 class LammpsStructureCompatibility(LammpsStructure):
@@
-        super().__init__(bond_dict=bond_dict, units=units, atom_type=atom_type)
-        self._molecule_ids = []
+        super().__init__(bond_dict=bond_dict, units=units, atom_type=atom_type)
+        self._molecule_ids = None
+
+    @property
+    def molecule_ids(self):
+        if self._molecule_ids is None:
+            if self._structure is None:
+                raise ValueError("Structure not set; cannot infer molecule IDs")
+            # Default: each atom is its own molecule
+            self._molecule_ids = list(range(1, len(self._structure) + 1))
+        return self._molecule_ids
+
+    @molecule_ids.setter
+    def molecule_ids(self, value):
+        # `None` means "reset" and lazily re-infer on next access
+        self._molecule_ids = None if value is None else list(value)

@@ def structure_bond(self):
-        self.molecule_ids = None
+        self.molecule_ids = None
@@ def structure_full(self):
-        self.molecule_ids = None
+        self.molecule_ids = None

Also applies to: 168-175

Comment on lines +181 to +186
if len(self._bond_dict.keys()) > 0:
cutoff_list = list()
for val in self._bond_dict.values():
cutoff_list.append(np.max(val["cutoff_list"]))
max_cutoff = np.max(cutoff_list)
# Calculate neighbors only once
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle bond_dict=None in structure_full to avoid AttributeError

structure_full assumes self._bond_dict is a mapping:

if len(self._bond_dict.keys()) > 0:
    ...

but LammpsStructureCompatibility inherits _bond_dict from LammpsStructure, where it defaults to None. Calling structure_full with the default bond_dict will therefore raise AttributeError: 'NoneType' object has no attribute 'keys'.

If None means "no bonds defined", a simple and robust guard is:

-        if len(self._bond_dict.keys()) > 0:
+        if self._bond_dict and len(self._bond_dict.keys()) > 0:

so the rest of the code runs with zero bonds/angles and the header correctly reports zero types.

🤖 Prompt for AI Agents
In pyiron_lammps/compatibility/structure.py around lines 181 to 186, the code
assumes self._bond_dict is a mapping and calls self._bond_dict.keys(), which
raises AttributeError when _bond_dict is None; treat None as "no bonds" by using
a safe local mapping (e.g. bond_dict = self._bond_dict or {}) or check "if
self._bond_dict:" before accessing keys, then only build cutoff_list and compute
max_cutoff when the mapping is non-empty and otherwise set max_cutoff (and
derived neighbor logic) to a sensible default (e.g. 0) so the rest of the
function proceeds and headers report zero bond/angle types.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (5)
tests/test_compatibility_file.py (2)

55-381: Mark unused shell_output and use os.path.join for lmp.in paths

Across the MD/static/minimize tests:

  • The first element of the returned tuple (shell_output) is never used.
  • lmp.in is opened via string concatenation (self.working_dir + "/lmp.in"), which is less portable and is flagged in earlier reviews.

To clean this up and satisfy Ruff:

  1. Mark shell_output as intentionally unused in all tests:
-        shell_output, parsed_output, job_crashed = lammps_file_interface_function(
+        _, parsed_output, job_crashed = lammps_file_interface_function(

Apply this at lines 62, 109, 151, 192, 233, 269, 306, and 345.

  1. Use os.path.join when opening lmp.in in all tests:
-        with open(self.working_dir + "/lmp.in", "r") as f:
+        with open(os.path.join(self.working_dir, "lmp.in"), "r") as f:

Apply this at lines 77, 124, 167, 207, 247, 283, 321, and 360.

These changes keep the tests behaviour identical while improving clarity and portability.


37-54: test_calc_error still fails due to missing resource_path

Both calls to lammps_file_interface_function in test_calc_error are made without resource_path, so get_potential_by_name raises ValueError("No resource_path found") before you hit the intended error branches. The second assertRaises(NotImplementedError) therefore fails (pipeline error).

Pass the same resource_path you use elsewhere in this file:

     def test_calc_error(self):
         with self.assertRaises(ValueError):
             lammps_file_interface_function(
                 working_directory=self.working_dir,
                 structure=self.structure,
                 potential=self.potential,
                 calc_mode="error",
-                units=self.units,
+                units=self.units,
+                resource_path=os.path.join(self.static_path, "potential"),
             )
         with self.assertRaises(NotImplementedError):
             lammps_file_interface_function(
                 working_directory=self.working_dir,
                 structure=self.structure,
                 potential=self.potential,
                 units="error",
                 calc_mode="md",
+                resource_path=os.path.join(self.static_path, "potential"),
             )

This keeps the tests focused on mode/units error handling instead of environment configuration.

pyiron_lammps/compatibility/file.py (3)

27-83: Docstring still documents unsupported API (extra args, vcsgc, typo)

The docstring still describes the much richer pyiron_atomistics.lammps.lammps.lammps_function API: it lists vcsgc mode and many parameters (cutoff_radius, bonds_kwargs, server_kwargs, restart/executable options, etc.) that are neither in the signature nor handled in this implementation, and it contains a typo around job_name ("delta_press", job_name", "rotation_matrix").

This is misleading for users of this function.

Either:

  • Slim the docstring down to match the actual signature and the three supported modes (static, md, minimize), and fix the job_name typo, or
  • Extend the function to accept and honour the documented parameters and vcsgc if full drop‑in compatibility is required.

91-96: units parameter is ignored by lammps_file_initialization (simulation always uses metal)

You correctly pass units into calc_md, calc_minimize, write_lammps_datafile, and parse_lammps_output, but the LAMMPS input header is still hard‑coded to "units metal" via the default in lammps_file_initialization. This desynchronises the actual LAMMPS run from the unit system assumed by the helpers and output parsing when units != "metal".

Forward units into the initializer:

-    lmp_str_lst = lammps_file_initialization(structure=structure)
+    lmp_str_lst = lammps_file_initialization(structure=structure, units=units)

This keeps the input script, MD/minimize helpers, datafile writer, and output parser all on the same unit system.

Also applies to: 150-159


130-136: subprocess.check_output(..., shell=True) with user‑supplied lmp_command

lmp_command is a public parameter and is passed straight to subprocess.check_output with shell=True, so any unsanitised value is executed by the shell. This is a classic S602‑style shell‑injection vector if lmp_command is ever influenced by untrusted input.

Safer options:

  • Prefer shell=False and accept lmp_command as a list of arguments, e.g. ["mpiexec", "-n", "1", "--oversubscribe", "lmp_mpi", "-in", "lmp.in"], or
  • If you must keep shell=True for HPC convenience, clearly document that lmp_command must be trusted and not derived from user input, or add validation/allow‑listing around it.
🧹 Nitpick comments (2)
pyiron_lammps/compatibility/file.py (2)

84-107: Avoid mutating caller’s calc_kwargs in‑place

For "md"/"minimize" you pop("n_ionic_steps") and inject "units" directly into calc_kwargs. If the caller reuses the same dict across calls, behaviour depends on the first call.

Consider copying the dict once up front:

-    if calc_kwargs is None:
-        calc_kwargs = {}
+    if calc_kwargs is None:
+        calc_kwargs = {}
+    else:
+        # Work on a shallow copy to avoid surprising side effects for callers
+        calc_kwargs = dict(calc_kwargs)

The rest of the logic (pop, "units" injection) can stay unchanged.


118-119: Rename loop variable l in writelines for clarity and Ruff E741

Ruff flags the ambiguous name l here. Renaming improves readability and silences E741 without changing behaviour:

-    with open(os.path.join(working_directory, "lmp.in"), "w") as f:
-        f.writelines([l + "\n" for l in lmp_str_lst])
+    with open(os.path.join(working_directory, "lmp.in"), "w") as f:
+        f.writelines([line + "\n" for line in lmp_str_lst])
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a263de3 and 8ca0644.

📒 Files selected for processing (2)
  • pyiron_lammps/compatibility/file.py (1 hunks)
  • tests/test_compatibility_file.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
pyiron_lammps/compatibility/file.py (4)
pyiron_lammps/compatibility/calculate.py (3)
  • calc_md (10-205)
  • calc_minimize (208-305)
  • calc_static (308-309)
pyiron_lammps/output.py (1)
  • parse_lammps_output (73-135)
pyiron_lammps/potential.py (1)
  • get_potential_by_name (349-355)
pyiron_lammps/structure.py (1)
  • write_lammps_datafile (501-513)
tests/test_compatibility_file.py (1)
pyiron_lammps/compatibility/file.py (1)
  • lammps_file_interface_function (17-147)
🪛 GitHub Actions: Pipeline
pyiron_lammps/compatibility/file.py

[error] 88-88: lammps_file_interface_function raised ValueError: No resource_path found. Potential installation guide provided in stack trace.

tests/test_compatibility_file.py

[error] 47-47: Unit test failure during 'python -m unittest discover tests': ValueError: No resource_path found (see stack in pyiron_lammps/potential.py and compatibility/file.py).

🪛 Ruff (0.14.6)
pyiron_lammps/compatibility/file.py

114-116: Avoid specifying long messages outside the exception class

(TRY003)


119-119: Ambiguous variable name: l

(E741)


130-130: subprocess call with shell=True identified, security issue

(S602)

tests/test_compatibility_file.py

62-62: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


109-109: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


151-151: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


192-192: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


233-233: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


269-269: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


306-306: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


345-345: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
tests/test_compatibility_file.py (2)

63-74: Mark unused shell_output as intentionally ignored (Ruff RUF059)

In all these tests, shell_output is unpacked but never used, which triggers Ruff’s RUF059 warning. Since you only assert on parsed_output and job_crashed, it’s clearer to mark the first element as intentionally ignored.

For example, in test_calc_md_npt:

-        shell_output, parsed_output, job_crashed = lammps_file_interface_function(
+        _, parsed_output, job_crashed = lammps_file_interface_function(
             working_directory=self.working_dir,
             structure=self.structure,
             potential=self.potential,
             calc_mode="md",
             calc_kwargs=calc_kwargs,
             units=self.units,
             lmp_command="cp "
             + str(os.path.join(self.static_path, "compatibility_output"))
             + "/* .",
             resource_path=os.path.join(self.static_path, "potential"),
         )

Apply the same pattern to the other calls at Lines 110, 152, 193, 234, 270, 308, and 347.

Also applies to: 110-121, 152-163, 193-204, 234-244, 270-281, 308-319, 347-358


78-80: Use os.path.join instead of string concatenation for file paths

All these blocks open "lmp.in" via string concatenation (self.working_dir + "/lmp.in"). Using os.path.join is more idiomatic and portable.

For example, in test_calc_md_npt:

-        with open(self.working_dir + "/lmp.in", "r") as f:
+        with open(os.path.join(self.working_dir, "lmp.in"), "r") as f:
             content = f.readlines()

Apply the same change in the other tests that read lmp.in.

Also applies to: 125-127, 168-169, 209-210, 248-249, 285-287, 323-325, 363-364

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ca0644 and 87a577f.

📒 Files selected for processing (1)
  • tests/test_compatibility_file.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_compatibility_file.py (1)
pyiron_lammps/compatibility/file.py (1)
  • lammps_file_interface_function (17-147)
🪛 GitHub Actions: Pipeline
tests/test_compatibility_file.py

[error] 305-305: Test failed: expected 'variable dumptime equal 1' not found in output; actual content contains 'variable dumptime equal 100'.

🪛 Ruff (0.14.6)
tests/test_compatibility_file.py

63-63: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


110-110: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


152-152: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


193-193: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


234-234: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


270-270: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


308-308: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


347-347: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🔇 Additional comments (1)
tests/test_compatibility_file.py (1)

8-55: Good test scaffolding and error-path coverage

setUp/tearDown correctly manage a per-test working directory, and test_calc_error exercises both invalid calc_mode and invalid units, which is useful regression coverage for the new compatibility layer. No changes needed here.

Comment thread tests/test_compatibility_file.py
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
tests/test_compatibility_file.py (2)

63-64: Mark unused shell_output as intentionally ignored to satisfy linters

shell_output is unpacked but never used in all these tests, and Ruff flags this (RUF059). Replace it with _ (or _shell_output) to make the intent explicit:

-        shell_output, parsed_output, job_crashed = lammps_file_interface_function(
+        _, parsed_output, job_crashed = lammps_file_interface_function(

Apply the same change to each call at Lines 63, 110, 152, 193, 234, 270, 308, and 347.

Also applies to: 110-110, 152-152, 193-193, 234-234, 270-270, 308-308, 347-347


78-79: Use os.path.join instead of string concatenation for lmp.in path

The tests open self.working_dir + "/lmp.in" in several places. This works on POSIX, but using os.path.join is more idiomatic and portable:

-        with open(self.working_dir + "/lmp.in", "r") as f:
+        with open(os.path.join(self.working_dir, "lmp.in"), "r") as f:

Since os is already imported, you can apply this at all eight usages (Lines 78, 125, 167, 208, 248, 285, 323, 362).

Also applies to: 125-126, 167-168, 208-209, 248-249, 285-286, 323-324, 362-363

🧹 Nitpick comments (2)
tests/test_compatibility_file.py (2)

9-35: Improve test isolation and simplify path construction in setUp

Using __file__ directly in os.path.join(__file__, "..", "lmp") and mixing ".." with os.path.dirname(__file__) for static_path works but is a bit indirect and ties all tests to a shared tests/lmp directory.

You could make this more robust and parallel‑test friendly by switching to a per‑run temp dir and a simpler static path, e.g.:

-import os
+import os
+import tempfile
@@
-        self.working_dir = os.path.abspath(os.path.join(__file__, "..", "lmp"))
-        self.static_path = os.path.abspath(
-            os.path.join("..", os.path.dirname(__file__), "static")
-        )
+        self.working_dir = tempfile.mkdtemp(
+            prefix="lmp_", dir=os.path.dirname(__file__)
+        )
+        self.static_path = os.path.join(os.path.dirname(__file__), "static")

tearDown already removes self.working_dir, so this drops in cleanly.


37-55: Optionally assert error messages (or avoid dependency on real LAMMPS) in test_calc_error

The two branches correctly exercise invalid calc_mode and unsupported units. If you want these to guard against regressions more tightly, consider:

  • Using assertRaisesRegex (or inspecting str(cm.exception)) to ensure you’re getting the intended failure mode instead of some unrelated ValueError/NotImplementedError.
  • For the units="error" case, optionally overriding lmp_command (like in the other tests) so this test never depends on the presence of a real LAMMPS executable.

Not mandatory, but it makes the contract clearer and the test more self‑contained.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87a577f and bb9a8b6.

📒 Files selected for processing (1)
  • tests/test_compatibility_file.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_compatibility_file.py (1)
pyiron_lammps/compatibility/file.py (1)
  • lammps_file_interface_function (17-147)
🪛 Ruff (0.14.6)
tests/test_compatibility_file.py

63-63: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


110-110: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


152-152: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


193-193: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


234-234: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


270-270: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


308-308: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


347-347: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🔇 Additional comments (2)
tests/test_compatibility_file.py (2)

56-190: MD compatibility tests are comprehensive and consistent with the interface

The NPT/NVT (with and without Langevin) tests:

  • Correctly wire calc_kwargs (temperature, optional pressure, n_ionic_steps, n_print, langevin) through lammps_file_interface_function.
  • Rely on a copied static output instead of requiring a live LAMMPS run, which keeps tests fast and deterministic.
  • Validate both the parsed generic keys and key LAMMPS script lines (dumptime tied to n_print, run tied to n_ionic_steps or default 1, appropriate fix ensemble / Langevin configurations).

This looks well aligned with the intended backwards‑compatible MD behavior.


233-383: Static and minimize tests correctly pin key LAMMPS script semantics

The static and minimize* tests:

  • Check parsed generic output consistently against self.keys.
  • Pin important parts of the generated input: default vs explicit dumptime, thermotime behavior (e.g., driven by max_iter in test_calc_minimize vs default in pressure relax cases), and the expected box/relax variants for scalar vs 3‑component pressure.
  • Reflect the updated expectation that minimize respects n_print for dumptime (Line 294), which matches the current lammps_file_interface_function logic.

These tests give good coverage of the new minimize/static compatibility paths.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/test_compatibility_file.py (2)

64-64: Replace unused variable and use os.path.join for path construction.

Two portability and code quality improvements:

  1. The shell_output variable is unpacked but never used. Replace it with _ to indicate it's intentionally ignored.
  2. Line 79 uses string concatenation for path construction, which is not portable across operating systems.

Apply this diff:

-        shell_output, parsed_output, job_crashed = lammps_file_interface_function(
+        _, parsed_output, job_crashed = lammps_file_interface_function(
             working_directory=self.working_dir,
             structure=self.structure,
             potential=self.potential,
             calc_mode="md",
             calc_kwargs=calc_kwargs,
             units=self.units,
             lmp_command="cp "
             + str(os.path.join(self.static_path, "compatibility_output"))
             + "/* .",
             resource_path=os.path.join(self.static_path, "potential"),
         )
         self.assertFalse(job_crashed)
         for key in self.keys:
             self.assertIn(key, parsed_output["generic"])
-        with open(self.working_dir + "/lmp.in", "r") as f:
+        with open(os.path.join(self.working_dir, "lmp.in"), "r") as f:
             content = f.readlines()

Apply the same changes to the other test methods at lines 111, 126, 153, 168, 194, 209, 235, 249, 271, 286, 309, 324, 348, and 363.

Also applies to: 79-79


57-384: Consider reducing test code duplication.

All eight test methods follow a nearly identical pattern:

  1. Set up calc_kwargs
  2. Call lammps_file_interface_function
  3. Assert job_crashed is False
  4. Validate keys in parsed_output
  5. Read and validate lmp.in content

While each test validates different configurations, the structure duplication makes maintenance harder. Consider extracting a helper method for the common validation pattern.

For example:

def _run_and_validate_lammps(self, calc_mode, calc_kwargs, expected_lines):
    """Helper to run LAMMPS and validate output."""
    _, parsed_output, job_crashed = lammps_file_interface_function(
        working_directory=self.working_dir,
        structure=self.structure,
        potential=self.potential,
        calc_mode=calc_mode,
        calc_kwargs=calc_kwargs,
        units=self.units,
        lmp_command="cp " + str(os.path.join(self.static_path, "compatibility_output")) + "/* .",
        resource_path=os.path.join(self.static_path, "potential"),
    )
    self.assertFalse(job_crashed)
    for key in self.keys:
        self.assertIn(key, parsed_output["generic"])
    
    with open(os.path.join(self.working_dir, "lmp.in"), "r") as f:
        content = f.readlines()
    
    for line in expected_lines:
        self.assertIn(line, content)

Then each test becomes more concise:

def test_calc_md_npt(self):
    calc_kwargs = {"temperature": 500.0, "pressure": 0.0, "n_ionic_steps": 1000, "n_print": 100}
    expected_lines = [
        "units metal\n",
        "fix ensemble all npt temp 500.0 500.0 0.1 iso 0.0 0.0 1.0\n",
        # ... other expected lines
    ]
    self._run_and_validate_lammps("md", calc_kwargs, expected_lines)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb9a8b6 and 192006b.

📒 Files selected for processing (1)
  • tests/test_compatibility_file.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_compatibility_file.py (1)
pyiron_lammps/compatibility/file.py (1)
  • lammps_file_interface_function (17-147)
🪛 Ruff (0.14.6)
tests/test_compatibility_file.py

64-64: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


111-111: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


153-153: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


194-194: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


235-235: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


271-271: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


309-309: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


348-348: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: unittest_matrix (windows-latest, 3.14)
🔇 Additional comments (2)
tests/test_compatibility_file.py (2)

1-35: LGTM!

The test class setup and teardown are well-structured. The fixture initialization in setUp properly prepares common test data, and tearDown ensures proper cleanup.


37-55: LGTM!

The error handling tests properly validate that invalid calc_mode and units values raise appropriate exceptions.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/test_compatibility_file.py (1)

74-74: Replace unused shell_output with underscore.

The shell_output return value is unpacked but never used in any test method. While a past review comment indicated this was addressed, the issue persists and is flagged by Ruff. Since the shell output is not needed for test assertions, mark it as intentionally unused.

Apply this pattern to all nine affected test methods:

-        shell_output, parsed_output, job_crashed = lammps_file_interface_function(
+        _, parsed_output, job_crashed = lammps_file_interface_function(

This applies to test methods: test_calc_md_npt, test_calc_md_npt_langevin, test_calc_md_nvt, test_calc_md_nvt_langevin, test_calc_md_nve, test_calc_static, test_calc_minimize, test_calc_minimize_pressure, and test_calc_minimize_pressure_3d.

Also applies to: 121-121, 163-163, 204-204, 246-246, 285-285, 321-321, 359-359, 398-398

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 192006b and 8b2d4a2.

📒 Files selected for processing (1)
  • tests/test_compatibility_file.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_compatibility_file.py (1)
pyiron_lammps/compatibility/file.py (1)
  • lammps_file_interface_function (17-147)
🪛 Ruff (0.14.6)
tests/test_compatibility_file.py

74-74: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


121-121: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


163-163: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


204-204: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


246-246: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


285-285: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


321-321: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


359-359: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


398-398: Unpacked variable shell_output is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🔇 Additional comments (4)
tests/test_compatibility_file.py (4)

1-31: LGTM! Well-structured test setup.

The imports, class structure, and setUp method are well-organized. The comprehensive keys list for validating parsed output is good practice for ensuring consistent test coverage across all calculation modes.


33-35: LGTM! Proper test cleanup.

The tearDown method correctly removes the test working directory, ensuring a clean state between test runs.


37-65: LGTM! Comprehensive error handling tests.

The error tests cover invalid calc_mode, invalid units, and invalid calc_kwargs, ensuring the interface properly validates inputs and raises appropriate exceptions.


67-434: Excellent test coverage across calculation modes.

The test suite comprehensively covers error handling, MD variants (NPT, NPT with Langevin, NVT, NVT with Langevin, NVE), static calculations, and multiple minimize configurations. Using static output files via the cp command is a pragmatic approach that allows testing the interface without requiring a LAMMPS installation.

The assertion pattern using assertIn to verify expected LAMMPS commands are present in the generated lmp.in file is reasonable for unit testing, though it doesn't enforce command order or completeness. This trade-off is acceptable given the test's focus on validating interface behavior rather than LAMMPS execution semantics.

@jan-janssen jan-janssen merged commit 2d47127 into main Nov 27, 2025
19 checks passed
@jan-janssen jan-janssen deleted the lammps_function branch November 27, 2025 12: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.

1 participant