Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional file-based interface for LAMMPS #409

Merged
merged 15 commits into from
Feb 24, 2025
Merged

Conversation

jan-janssen
Copy link
Member

@jan-janssen jan-janssen commented Feb 23, 2025

Summary by CodeRabbit

  • New Features
    • Introduced a new file-based approach for running simulations, including optimization, static analysis, and phonon evaluations.
  • Refactor
    • Streamlined and standardized simulation function naming for improved consistency across interactive notebooks and workflows.
  • Chores
    • Updated environment configurations with a new dependency to enhance LAMMPS integration and refreshed core library versions.
  • Tests
    • Expanded test coverage to verify simulation workflows, ensuring robust molecular dynamics and phonon calculations.

Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

Copy link

coderabbitai bot commented Feb 23, 2025

Walkthrough

The pull request primarily updates the naming convention for LAMMPS-related functions across multiple modules, notebooks, and tests. Functions previously suffixed with “with_lammps” are renamed to “with_lammpslib”, and corresponding import statements and export lists are updated. Additionally, new dependencies (e.g. pyiron_lammps==0.3.2) have been added to several environment files, and a new file (filecalculator.py) has been introduced with functions and a class for file-based LAMMPS interfacing. Several tests have been revised or added to support the new nomenclature and file-based evaluation.

Changes

File(s) Change Summary
atomistics/calculators/__init__.py
atomistics/calculators/lammps/__init__.py
atomistics/calculators/lammps/phonon.py
Renamed functions from ...with_lammps to ...with_lammpslib and updated the __all__ lists accordingly.
atomistics/calculators/lammps/libcalculator.py Updated function signatures and internal calls to reflect the new naming convention (appending “lib” to function names).
notebooks/*.ipynb
(e.g., lammps_workflows.ipynb, thermal_expansion_with_lammps.ipynb, free_energy_calculation.ipynb, simulation_codes.ipynb)
Revised import statements and function calls to use the new names (evaluate_with_lammpslib, etc.) while preserving overall logic.
.ci_support/environment*.yml
binder/environment.yml
pyproject.toml
Added new dependency pyiron_lammps==0.3.2 and updated versions for other packages (e.g., numpy, pandas in environment-old).
atomistics/calculators/lammps/filecalculator.py New file added with a class (GenericOutput) and functions for LAMMPS file-based operations (e.g., input file writing, optimization, static calculations, and task evaluation).
tests/*.py (multiple test files) Updated function calls and import statements to use the new naming convention (...with_lammpslib and evaluate_with_lammpslib) and added new tests for file-based optimizations and phonon evaluations (e.g., test_evcurve_lammpsfile.py, test_optimize_positions_lammpsfile.py).

Sequence Diagram(s)

sequenceDiagram
    participant User as User/Caller
    participant Eval as evaluate_with_lammpsfile
    participant Init as _lammps_file_initialization
    participant Writer as _write_lammps_input_file
    participant Exec as LAMMPS Executable
    participant Output as GenericOutput

    User->>Eval: Request evaluation with task parameters
    Eval->>Init: Generate initialization commands
    Init-->>Eval: Return config commands
    Eval->>Writer: Compose LAMMPS input file
    Writer-->>Eval: Write input file to directory
    Eval->>Exec: Invoke executable_function with input file
    Exec-->>Eval: Return simulation output
    Eval->>Output: Parse output to obtain forces, energy, stress, etc.
    Output-->>Eval: Return processed results
    Eval-->>User: Provide evaluation results
Loading

Possibly related PRs

Poem

I’m a rabbit in the code field, hopping with glee,
New names shine bright, as clear as can be.
From with_lammps to with_lammpslib, we jump and play,
Dependencies join the dance in a fresh new way.
In our coding warren, every change is a sunny day!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

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

🔭 Outside diff range comments (1)
atomistics/calculators/__init__.py (1)

55-68: ⚠️ Potential issue

Update __all__ list to match the new function names.

The __all__ list still contains the old function names with "with_lammps" suffix, while the imports have been updated to use "with_lammpslib". This mismatch will cause import errors.

Apply this diff to update the __all__ list:

     __all__ += [
-        "calc_molecular_dynamics_thermal_expansion_with_lammps",
-        "calc_molecular_dynamics_nph_with_lammps",
-        "calc_molecular_dynamics_npt_with_lammps",
-        "calc_molecular_dynamics_nvt_with_lammps",
-        "calc_molecular_dynamics_langevin_with_lammps",
-        "calc_static_with_lammps",
-        "evaluate_with_lammps",
-        "evaluate_with_lammps_library",
+        "calc_molecular_dynamics_thermal_expansion_with_lammpslib",
+        "calc_molecular_dynamics_nph_with_lammpslib",
+        "calc_molecular_dynamics_npt_with_lammpslib",
+        "calc_molecular_dynamics_nvt_with_lammpslib",
+        "calc_molecular_dynamics_langevin_with_lammpslib",
+        "calc_static_with_lammpslib",
+        "evaluate_with_lammpslib",
+        "evaluate_with_lammpslib_library_interface",
         "get_potential_dataframe",
         "get_potential_by_name",
-        "optimize_positions_and_volume_with_lammps",
-        "optimize_positions_with_lammps",
+        "optimize_positions_and_volume_with_lammpslib",
+        "optimize_positions_with_lammpslib",
     ]
🧹 Nitpick comments (3)
tests/test_hessian_lammps.py (1)

18-19: Remove unused import.

The function evaluate_with_lammpslib_library_interface is imported but never used in this file.

Apply this diff:

    from atomistics.calculators import (
        evaluate_with_lammpslib,
-        evaluate_with_lammpslib_library_interface,
        get_potential_by_name,
    )
🧰 Tools
🪛 Ruff (0.8.2)

19-19: atomistics.calculators.evaluate_with_lammpslib_library_interface imported but unused; consider using importlib.util.find_spec to test for availability

(F401)

notebooks/free_energy_calculation.ipynb (2)

33-39: Ensure Consistency in Import Naming.
The import block now includes the new evaluate_with_lammpslib_library_interface alongside the existing evaluate_with_lammps. Please verify that both functions are intended to coexist. If the goal is a complete rename, ensure that all references to the old function (evaluate_with_lammps) are updated accordingly or removed to avoid any confusion.


641-644: Update Function Invocation for Renamed Interface.
The function call at this hunk now uses evaluate_with_lammpslib_library_interface, which aligns with the new naming convention. Confirm that the parameters provided (task_dict, potential_dataframe, lmp) are fully compatible with the renamed function’s definition and that any remaining calls to the old interface are updated throughout the codebase.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e6d0ce and 91406d2.

📒 Files selected for processing (23)
  • atomistics/calculators/__init__.py (2 hunks)
  • atomistics/calculators/lammps/__init__.py (2 hunks)
  • atomistics/calculators/lammps/libcalculator.py (13 hunks)
  • atomistics/calculators/lammps/phonon.py (1 hunks)
  • notebooks/free_energy_calculation.ipynb (2 hunks)
  • notebooks/lammps_workflows.ipynb (22 hunks)
  • notebooks/thermal_expansion_with_lammps.ipynb (3 hunks)
  • tests/test_calc_stress_lammps.py (2 hunks)
  • tests/test_elastic_lammps.py (3 hunks)
  • tests/test_elastic_lammps_functional.py (3 hunks)
  • tests/test_evcurve_lammps.py (3 hunks)
  • tests/test_evcurve_lammps_function_parallel.py (3 hunks)
  • tests/test_evcurve_lammps_functional.py (3 hunks)
  • tests/test_hessian_lammps.py (3 hunks)
  • tests/test_lammps_md.py (7 hunks)
  • tests/test_lammps_md_phonons.py (2 hunks)
  • tests/test_langevin_lammps.py (2 hunks)
  • tests/test_molecular_dynamics_thermal_expansion_lammps.py (3 hunks)
  • tests/test_optimize_positions_lammps.py (2 hunks)
  • tests/test_phonons_lammps.py (3 hunks)
  • tests/test_phonons_lammps_functional.py (3 hunks)
  • tests/test_quasiharmonic_lammps.py (3 hunks)
  • tests/test_quasiharmonic_lammps_functional.py (3 hunks)
🔥 Files not summarized due to errors (1)
  • notebooks/free_energy_calculation.ipynb: Error: Server error: no LLM provider could handle the message
✅ Files skipped from review due to trivial changes (8)
  • tests/test_phonons_lammps_functional.py
  • tests/test_optimize_positions_lammps.py
  • atomistics/calculators/lammps/phonon.py
  • tests/test_phonons_lammps.py
  • tests/test_quasiharmonic_lammps.py
  • tests/test_molecular_dynamics_thermal_expansion_lammps.py
  • notebooks/thermal_expansion_with_lammps.ipynb
  • tests/test_lammps_md_phonons.py
🧰 Additional context used
🪛 Ruff (0.8.2)
tests/test_hessian_lammps.py

19-19: atomistics.calculators.evaluate_with_lammpslib_library_interface imported but unused; consider using importlib.util.find_spec to test for availability

(F401)

atomistics/calculators/__init__.py

41-41: atomistics.calculators.lammps.calc_molecular_dynamics_langevin_with_lammpslib imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


42-42: atomistics.calculators.lammps.calc_molecular_dynamics_nph_with_lammpslib imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


43-43: atomistics.calculators.lammps.calc_molecular_dynamics_npt_with_lammpslib imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


44-44: atomistics.calculators.lammps.calc_molecular_dynamics_nvt_with_lammpslib imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


45-45: atomistics.calculators.lammps.calc_molecular_dynamics_thermal_expansion_with_lammpslib imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


46-46: atomistics.calculators.lammps.calc_static_with_lammpslib imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


47-47: atomistics.calculators.lammps.evaluate_with_lammpslib imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


48-48: atomistics.calculators.lammps.evaluate_with_lammpslib_library_interface imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


51-51: atomistics.calculators.lammps.optimize_positions_and_volume_with_lammpslib imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


52-52: atomistics.calculators.lammps.optimize_positions_with_lammpslib imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


74-74: atomistics.calculators.lammps.phonon.calc_molecular_dynamics_phonons_with_lammpslib imported but unused; consider using importlib.util.find_spec to test for availability

(F401)

⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: unittest_matrix (ubuntu-latest, 3.10)
  • GitHub Check: unittest_matrix (ubuntu-latest, 3.11)
  • GitHub Check: unittest_siesta
  • GitHub Check: unittest_matrix (windows-latest, 3.12)
  • GitHub Check: unittest_qe
  • GitHub Check: unittest_old
  • GitHub Check: unittest_matrix (ubuntu-latest, 3.12)
  • GitHub Check: unittest_matrix (macos-latest, 3.12)
  • GitHub Check: unittest_matgl
  • GitHub Check: unittest_mace
  • GitHub Check: unittest_gpaw
  • GitHub Check: unittest_abinit
  • GitHub Check: pip_check
  • GitHub Check: coverage
  • GitHub Check: notebooks
  • GitHub Check: minimal
🔇 Additional comments (15)
atomistics/calculators/lammps/__init__.py (1)

1-43: LGTM! Consistent renaming of LAMMPS interface functions.

The changes systematically rename all LAMMPS-related functions to use the "lammpslib" suffix, maintaining consistency across the module. The __all__ list has been properly updated to reflect these changes.

tests/test_calc_stress_lammps.py (1)

10-10: LGTM! Consistent function name updates in test file.

All instances of evaluate_with_lammps have been properly updated to evaluate_with_lammpslib, maintaining consistency with the renamed interface.

Also applies to: 28-28, 33-33, 40-40

tests/test_langevin_lammps.py (1)

12-12: LGTM! Consistent function name updates in Langevin test.

All instances of evaluate_with_lammps_library have been properly updated to evaluate_with_lammpslib_library_interface, maintaining consistency with the renamed interface.

Also applies to: 52-52

tests/test_evcurve_lammps.py (1)

13-13: LGTM! Consistent function name updates in EV curve test.

All instances of evaluate_with_lammps have been properly updated to evaluate_with_lammpslib, maintaining consistency with the renamed interface.

Also applies to: 31-31, 45-45

tests/test_evcurve_lammps_functional.py (1)

14-14: LGTM! Function renaming is consistent.

The imports and function calls have been correctly updated to use the new evaluate_with_lammpslib name.

Also applies to: 31-34, 42-45

tests/test_evcurve_lammps_function_parallel.py (1)

16-16: LGTM! Function renaming is consistent in parallel execution context.

The imports and function calls have been correctly updated to use the new evaluate_with_lammpslib name in the parallel execution context.

Also applies to: 35-36, 48-49

tests/test_hessian_lammps.py (1)

40-43: LGTM! Function renaming is consistent.

The function calls have been correctly updated to use the new evaluate_with_lammpslib name.

Also applies to: 54-57

tests/test_quasiharmonic_lammps_functional.py (1)

15-15: LGTM! Function renaming is consistent.

The renaming from evaluate_with_lammps to evaluate_with_lammpslib is applied consistently across the import statement and function calls, maintaining the test's functionality.

Also applies to: 32-32, 48-48

tests/test_elastic_lammps.py (1)

10-10: LGTM! Function renaming is consistent.

The renaming from evaluate_with_lammps to evaluate_with_lammpslib is applied consistently across the import statement and function calls, maintaining the test's functionality.

Also applies to: 28-28, 40-40

tests/test_elastic_lammps_functional.py (1)

13-13: LGTM! Function renaming is consistent.

The renaming from evaluate_with_lammps to evaluate_with_lammpslib is applied consistently across the import statement and function calls, maintaining the test's functionality.

Also applies to: 30-30, 41-41

tests/test_lammps_md.py (1)

10-13: LGTM! Function renaming is consistent.

The renaming from *_with_lammps to *_with_lammpslib is applied consistently across:

  • Import statements for all molecular dynamics functions
  • Function calls in test methods
  • Signature check in test_calc_molecular_dynamics_signature

The test's functionality is maintained.

Also applies to: 32-32, 62-62, 87-87, 120-120, 151-151, 177-177

notebooks/lammps_workflows.ipynb (4)

24-24: LGTM! Import statements consistently updated.

The import statements have been systematically updated to use the new "lammpslib" naming convention, maintaining consistency across the notebook.

Also applies to: 63-63, 113-113, 231-232, 285-285, 338-338, 394-394, 448-449, 498-499, 711-711, 777-777, 997-997, 1130-1130, 1176-1176


29-29: LGTM! Function calls to evaluate_with_lammps consistently updated.

All calls to the evaluation function have been properly renamed to evaluate_with_lammpslib, maintaining consistency throughout the notebook.

Also applies to: 77-77, 129-129, 529-529, 578-578, 794-794, 1015-1015, 1137-1137, 1183-1183


238-238: LGTM! Molecular dynamics function calls consistently updated.

All molecular dynamics related function calls have been properly renamed to use the "lammpslib" suffix, including:

  • Langevin dynamics
  • NVT ensemble
  • NPT ensemble
  • NPH ensemble
  • Thermal expansion
  • Phonon calculations

Also applies to: 292-292, 345-345, 401-401, 456-456, 600-600


738-738: LGTM! Library interface function call properly updated.

The library interface evaluation function has been correctly renamed to evaluate_with_lammpslib_library_interface, maintaining consistency with the new naming scheme while preserving its distinct identity as a library interface.

@@ -71,7 +71,7 @@

try:
from atomistics.calculators.lammps.phonon import (
calc_molecular_dynamics_phonons_with_lammps,
calc_molecular_dynamics_phonons_with_lammpslib,
)

__all__ += ["calc_molecular_dynamics_phonons_with_lammps"]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update phonon function name in __all__ list.

The phonon function name in __all__ list needs to be updated to match the new naming convention.

Apply this diff:

-    __all__ += ["calc_molecular_dynamics_phonons_with_lammps"]
+    __all__ += ["calc_molecular_dynamics_phonons_with_lammpslib"]
📝 Committable suggestion

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

Suggested change
__all__ += ["calc_molecular_dynamics_phonons_with_lammps"]
__all__ += ["calc_molecular_dynamics_phonons_with_lammpslib"]

@@ -40,7 +40,7 @@
from atomistics.calculators.interface import TaskName


def optimize_positions_and_volume_with_lammps(
def optimize_positions_and_volume_with_lammpslib(
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

LGTM! Consistent renaming of LAMMPS-related functions.

The renaming from *_with_lammps to *_with_lammpslib is consistent across all functions and aligns with the PR objective to rename the LAMMPS interface.

Let's verify that all LAMMPS-related functions have been renamed consistently across the codebase:

Also applies to: 85-85, 118-118, 146-146, 200-200, 260-260, 315-315, 371-371, 424-424, 475-475, 498-498


🏁 Script executed:

#!/bin/bash
# Description: Check for any remaining instances of old function names.

# Search for any remaining instances of old function names
echo "Checking for any remaining instances of old function names..."
rg "with_lammps[^l]" -g '!*.md'

# Search for any potential inconsistencies in the new naming
echo "Checking for any inconsistencies in the new naming..."
rg "with_lammps(?:lib|[^l])" -g '!*.md'

Length of output: 25167


Refactor Approved in libcalculator.py – Inconsistent References Elsewhere
The refactoring in atomistics/calculators/lammps/libcalculator.py looks good: all LAMMPS-related functions have been consistently renamed to include the lib suffix. However, our repository-wide search shows that several files (notably in atomistics/calculators/__init__.py, documentation, and various notebooks) still reference the old function names (e.g. calc_molecular_dynamics_thermal_expansion_with_lammps, evaluate_with_lammps, etc.). Please review those occurrences to either update them for consistency with the new naming convention or confirm that their retention is intentional (for backward compatibility or documentation purposes).

  • Ensure that string references and exports in __init__.py reflect the renaming if full consistency is desired.
  • Verify and, if needed, update the notebooks and docs (e.g., thermal_expansion_with_lammps.ipynb) accordingly.

Copy link

codecov bot commented Feb 23, 2025

Codecov Report

Attention: Patch coverage is 96.66667% with 3 lines in your changes missing coverage. Please review.

Project coverage is 81.52%. Comparing base (091eb1b) to head (57d15fc).
Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
atomistics/calculators/lammps/filecalculator.py 95.89% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #409      +/-   ##
==========================================
+ Coverage   81.07%   81.52%   +0.45%     
==========================================
  Files          39       40       +1     
  Lines        2309     2382      +73     
==========================================
+ Hits         1872     1942      +70     
- Misses        437      440       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jan-janssen jan-janssen marked this pull request as draft February 23, 2025 18:16
@jan-janssen jan-janssen changed the title Rename lammps interface to lammpslib Add additional file-based interface for LAMMPS Feb 24, 2025
@jan-janssen jan-janssen marked this pull request as ready for review February 24, 2025 20:02
Copy link

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

🧹 Nitpick comments (11)
binder/environment.yml (1)

18-18: Ensure Newline at End-of-File
YAML lint has flagged that line 18 does not end with a newline. To adhere to formatting guidelines, please add a newline after the last dependency.

- - pyiron_lammps =0.3.2
+ - pyiron_lammps =0.3.2
+ 
🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 18-18: no new line character at the end of file

(new-line-at-end-of-file)

.ci_support/environment-old.yml (1)

22-22: Add newline at end of file.

Add a newline character at the end of the file to comply with YAML best practices.

 - pyiron_lammps =0.3.2
+
🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 22-22: no new line character at the end of file

(new-line-at-end-of-file)

tests/test_hessian_lammpslib.py (1)

18-19: Remove unused import.

The evaluate_with_lammpslib_library_interface function is imported but never used in this test file.

    from atomistics.calculators import (
        evaluate_with_lammpslib,
-       evaluate_with_lammpslib_library_interface,
        get_potential_by_name,
    )
🧰 Tools
🪛 Ruff (0.8.2)

19-19: atomistics.calculators.evaluate_with_lammpslib_library_interface imported but unused; consider using importlib.util.find_spec to test for availability

(F401)

tests/test_evcurve_lammpsfile.py (1)

43-86: Consider parameterizing the test with different materials.

The test only verifies aluminum's energy-volume curve. Adding more materials would increase test coverage.

Consider parameterizing the test:

@pytest.mark.parametrize("element,potential_name", [
    ("Al", "1999--Mishin-Y--Al--LAMMPS--ipr1"),
    ("Cu", "2001--Mishin-Y--Cu--LAMMPS--ipr1"),
    # Add more material-potential pairs
])
def test_calc_evcurve(self, element, potential_name):
    structure = bulk(element, cubic=True)
    # ... rest of the test
tests/test_phonons_lammpsfile.py (1)

100-115: Consider using pytest's approx for floating-point comparisons.

Using multiple assertTrue with range checks is verbose and harder to maintain.

Consider using pytest's approx:

from pytest import approx

# Replace multiple assertions with
assert thermal_dict["free_energy"][0] == approx(0.15, abs=0.05)
assert thermal_dict["free_energy"][-1] == approx(-2.65, abs=0.05)
# ... similar for other checks
atomistics/calculators/lammps/filecalculator.py (5)

26-41: Add test coverage for stress and volume extraction.
get_stress() (line 37) and get_volume() (line 40) are uncovered according to code coverage. Ensuring these methods are tested increases confidence in simulation output verification.

Would you like assistance in adding a unit test for these methods, perhaps verifying correct handling of parsing edge cases or empty data?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 37-37: atomistics/calculators/lammps/filecalculator.py#L37
Added line #L37 was not covered by tests


[warning] 40-40: atomistics/calculators/lammps/filecalculator.py#L40
Added line #L40 was not covered by tests


43-54: Consider parameterizing dimension for flexible use.
Currently, the dimension is hard-coded to 3 (line 44). This is fine for typical 3D simulations, but if 2D or 1D systems ever become relevant, a parameter or assertion might help avoid confusion.


56-76: Add basic file writing error handling.
While _write_lammps_input_file() is well-structured, adding minimal error handling (e.g., ensuring the working directory is valid) can make the code more robust.


173-210: Validate run success before parsing.
calc_static_with_lammpsfile() depends on _parse_lammps_output. If the run fails or the file is missing, parsing will fail. Consider verifying run success and providing more descriptive error handling.


212-255: Add a test for unsupported task coverage.
The raise statement at line 253 is uncovered by tests. Including a test that passes an unsupported task (e.g., "nonsensical_task") can ensure any usage error is correctly handled.

If you want a snippet demonstrating how to provoke this error and assert that it’s raised, I can prepare it.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 253-253: atomistics/calculators/lammps/filecalculator.py#L253
Added line #L253 was not covered by tests

tests/test_optimize_positions_lammpsfile.py (1)

11-17: Consider catching specific exceptions instead of using a broad ImportError.

While the current implementation works, it's a good practice to catch specific exceptions. This helps in better error handling and debugging.

 try:
     from atomistics.calculators.lammps.potential import get_potential_by_name
     from atomistics.calculators.lammps.filecalculator import evaluate_with_lammpsfile
 
     skip_lammps_test = False
-except ImportError:
+except (ImportError, ModuleNotFoundError) as e:
+    print(f"LAMMPS import failed: {e}")
     skip_lammps_test = True
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd83395 and 57d15fc.

📒 Files selected for processing (26)
  • .ci_support/environment-docs.yml (1 hunks)
  • .ci_support/environment-lammps.yml (1 hunks)
  • .ci_support/environment-notebooks.yml (1 hunks)
  • .ci_support/environment-old.yml (2 hunks)
  • atomistics/calculators/lammps/filecalculator.py (1 hunks)
  • binder/environment.yml (1 hunks)
  • pyproject.toml (1 hunks)
  • tests/test_calc_stress_lammpslib.py (2 hunks)
  • tests/test_elastic_lammpslib.py (3 hunks)
  • tests/test_elastic_lammpslib_functional.py (3 hunks)
  • tests/test_evcurve_lammpsfile.py (1 hunks)
  • tests/test_evcurve_lammpslib.py (3 hunks)
  • tests/test_evcurve_lammpslib_function_parallel.py (3 hunks)
  • tests/test_evcurve_lammpslib_functional.py (3 hunks)
  • tests/test_hessian_lammpslib.py (3 hunks)
  • tests/test_lammpslib_md.py (7 hunks)
  • tests/test_lammpslib_md_phonons.py (2 hunks)
  • tests/test_langevin_lammpslib.py (2 hunks)
  • tests/test_molecular_dynamics_thermal_expansion_lammpslib.py (3 hunks)
  • tests/test_optimize_positions_lammpsfile.py (1 hunks)
  • tests/test_optimize_positions_lammpslib.py (2 hunks)
  • tests/test_phonons_lammpsfile.py (1 hunks)
  • tests/test_phonons_lammpslib.py (3 hunks)
  • tests/test_phonons_lammpslib_functional.py (3 hunks)
  • tests/test_quasiharmonic_lammpslib.py (3 hunks)
  • tests/test_quasiharmonic_lammpslib_functional.py (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/test_lammpslib_md.py
🧰 Additional context used
🪛 YAMLlint (1.35.1)
binder/environment.yml

[error] 18-18: no new line character at the end of file

(new-line-at-end-of-file)

.ci_support/environment-notebooks.yml

[error] 12-12: no new line character at the end of file

(new-line-at-end-of-file)

.ci_support/environment-old.yml

[error] 22-22: no new line character at the end of file

(new-line-at-end-of-file)

🪛 Ruff (0.8.2)
tests/test_hessian_lammpslib.py

19-19: atomistics.calculators.evaluate_with_lammpslib_library_interface imported but unused; consider using importlib.util.find_spec to test for availability

(F401)

🪛 GitHub Check: codecov/patch
atomistics/calculators/lammps/filecalculator.py

[warning] 37-37: atomistics/calculators/lammps/filecalculator.py#L37
Added line #L37 was not covered by tests


[warning] 40-40: atomistics/calculators/lammps/filecalculator.py#L40
Added line #L40 was not covered by tests


[warning] 253-253: atomistics/calculators/lammps/filecalculator.py#L253
Added line #L253 was not covered by tests

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: unittest_matrix (ubuntu-latest, 3.10)
  • GitHub Check: unittest_matrix (windows-latest, 3.12)
  • GitHub Check: unittest_matrix (ubuntu-latest, 3.12)
  • GitHub Check: unittest_matrix (macos-latest, 3.12)
🔇 Additional comments (21)
.ci_support/environment-notebooks.yml (1)

12-12: Dependency Addition Verification for pyiron_lammps
The new dependency pyiron_lammps =0.3.2 has been correctly added to the notebooks environment configuration. This update is consistent with similar changes in related environment files.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 12-12: no new line character at the end of file

(new-line-at-end-of-file)

.ci_support/environment-lammps.yml (1)

11-11: Ensure Consistent Dependency Addition
The dependency pyiron_lammps =0.3.2 is correctly introduced here, aligning this environment with the other configurations supporting the LAMMPS interface.

.ci_support/environment-docs.yml (1)

22-22: Verify Dependency Update in Documentation Environment
The inclusion of pyiron_lammps =0.3.2 ensures that the docs environment now supports the LAMMPS-based interfaces. This change is consistent across the project.

binder/environment.yml (1)

18-18: Dependency Addition Verification
The new dependency pyiron_lammps =0.3.2 is properly added in the binder environment file, ensuring consistency with the other environment configurations.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 18-18: no new line character at the end of file

(new-line-at-end-of-file)

tests/test_langevin_lammpslib.py (1)

12-12: LGTM! Consistent function renaming.

The changes align with the broader effort to standardize LAMMPS-related function names across the codebase.

Also applies to: 52-52

tests/test_quasiharmonic_lammpslib_functional.py (1)

15-15: LGTM! Consistent function renaming.

The changes align with the broader effort to standardize LAMMPS-related function names across the codebase.

Also applies to: 32-32, 48-48

tests/test_evcurve_lammpslib_functional.py (1)

14-14: LGTM! Consistent function renaming.

The changes align with the broader effort to standardize LAMMPS-related function names across the codebase.

Also applies to: 31-31, 42-42

.ci_support/environment-old.yml (1)

12-13:

❓ Verification inconclusive

Verify compatibility with numpy 1.26.0.

The update to numpy 1.26.0 is a significant version jump. Please ensure compatibility with other dependencies.


🌐 Web query:

What are the known compatibility issues with numpy 1.26.0?

💡 Result:

NumPy 1.26.0 introduces several compatibility considerations across different environments and dependencies:

Build System Compatibility

The switch from distutils/setup.py to Meson build system creates breaking changes:

  • Environment variables like NPY_* for BLAS/LAPACK/SIMD configuration no longer work[1][3]
  • Custom site.cfg files for BLAS/LAPACK selection are deprecated[3]
  • Requires new build flags via pip config-settings interface[1]
  • Temporary workaround: Use pyproject.toml.setuppy for legacy builds (Python ≤3.11 only)[1][3]

Python Version Constraints

  • Officially supports Python 3.9-3.12[1][3]
  • Blocks installation on Python ≥3.13 via version specifier <3.13, causing conflicts in unconstrained environments[9]

Platform-Specific Issues

Apple Silicon (M1/M2):

  • Conda installations may fail with module recognition errors
  • Solution: Use native Python.org install + pip3 instead of Conda[7]

Intel Environments:

  • Potential binary incompatibility with older Intel Math Kernel Library (MKL) distributions[4]
  • Error pattern: ValueError: numpy.ufunc size changed indicating version mismatch[4]

Dependency Conflicts

With Numerical Libraries:

  • Gurobi's matrix API incompatible with NumPy 2.x when using v11.0.x (requires NumPy <2)[10]
  • Workaround: Pin to numpy<2 until library updates[10]

Common Installation Pitfalls:

# Error pattern when mixing package managers
ImportError: Numpy not available (PyTorch DataLoader)[8]

# Solution sequence
pip uninstall numpy && pip install numpy==1.26.0[4][8]

Key Recommendations

  1. Verify build customization uses Meson flags instead of legacy methods
  2. Constrain Python versions to <3.13 in environments using NumPy 1.26.x[9]
  3. Prefer pip over conda for ARM64 installations[7]
  4. Audit dependencies relying on NumPy C-API (e.g., ML libraries)[10]

These issues primarily stem from major infrastructure changes in NumPy's build system and expanded Python version support[1][3][9]. Most can be resolved through dependency pinning and build process updates[4][10].

Citations:


Ensure NumPy 1.26.0 Compatibility & Fix File Formatting

The upgrade to NumPy 1.26.0 is a major jump that introduces several compatibility considerations. Please verify the following before merging:

  • Build & Configuration:
    Confirm that our CI and build configurations do not rely on legacy settings (e.g., NPY_* environment variables or custom site.cfg files) that are incompatible with NumPy’s new Meson build process.

  • Python Version Constraints:
    Ensure the environment uses Python 3.9–3.12 since NumPy 1.26.0 supports these versions and explicitly prevents installation on Python ≥3.13.

  • Dependency Management:
    Audit dependencies that might interact with NumPy’s C-API or have strict version requirements (e.g., potential conflicts with libraries like Gurobi) to avoid runtime issues.

  • Formatting:
    Address the minor formatting issue by ensuring there is a newline at the end of .ci_support/environment-old.yml.

tests/test_evcurve_lammpslib.py (1)

13-13: LGTM! Consistent function renaming.

The changes correctly update the import statement and function calls from evaluate_with_lammps to evaluate_with_lammpslib, maintaining consistency with the broader refactoring effort.

Also applies to: 31-31, 45-45

tests/test_quasiharmonic_lammpslib.py (1)

10-10: LGTM! Consistent function renaming.

The changes correctly update the import statement and function calls from evaluate_with_lammps to evaluate_with_lammpslib, maintaining consistency with the broader refactoring effort.

Also applies to: 28-28, 44-44

tests/test_phonons_lammpslib.py (1)

10-10: LGTM! Consistent function renaming.

The changes correctly update the import statement and function calls from evaluate_with_lammps to evaluate_with_lammpslib, maintaining consistency with the broader refactoring effort.

Also applies to: 28-28, 42-42

tests/test_phonons_lammpslib_functional.py (1)

15-15: LGTM! Consistent function renaming.

The changes correctly update the import statement and function calls from evaluate_with_lammps to evaluate_with_lammpslib, maintaining consistency with the broader refactoring effort.

Also applies to: 32-32, 44-44

tests/test_elastic_lammpslib.py (1)

10-10: LGTM! Function renaming is consistent.

The renaming from evaluate_with_lammps to evaluate_with_lammpslib is consistent with the PR objectives to update the naming convention.

Also applies to: 28-28, 40-40

tests/test_elastic_lammpslib_functional.py (1)

13-13: LGTM! Function renaming is consistent.

The renaming from evaluate_with_lammps to evaluate_with_lammpslib is consistent with the PR objectives to update the naming convention.

Also applies to: 30-30, 41-41

pyproject.toml (1)

52-52: LGTM! New dependency added consistently.

The addition of pyiron_lammps==0.3.2 to both lammps and lammps_phonons sections is consistent and uses version pinning for reproducibility.

Also applies to: 59-59

tests/test_evcurve_lammpslib_function_parallel.py (1)

16-16: LGTM! Function renaming is consistent.

The renaming from evaluate_with_lammps to evaluate_with_lammpslib is consistent with the PR objectives to update the naming convention.

Also applies to: 35-35, 48-48

tests/test_optimize_positions_lammpslib.py (1)

10-10: LGTM! Function renaming is consistent.

The changes align with the PR's objective of standardizing LAMMPS function names across the codebase.

Also applies to: 23-23

tests/test_calc_stress_lammpslib.py (1)

10-10: LGTM! Function renaming is consistent across multiple calls.

The changes maintain consistency in the LAMMPS function naming convention across all three function calls.

Also applies to: 28-28, 33-33, 40-40

tests/test_lammpslib_md_phonons.py (1)

12-13: LGTM! Function renaming is consistent.

The changes maintain consistency in the LAMMPS function naming convention for both molecular dynamics and evaluation functions.

Also applies to: 42-42, 47-47

tests/test_molecular_dynamics_thermal_expansion_lammpslib.py (1)

78-100:

❓ Verification inconclusive

Verify ASE calculator configuration.

The test uses hardcoded Morse potential parameters which might not be optimal for aluminum.


🌐 Web query:

What are the recommended Morse potential parameters for aluminum in LAMMPS?

💡 Result:

The recommended Morse potential parameters for aluminum in LAMMPS depend on the specific application and interaction type (e.g., pure Al vs. cross-species interactions). Based on research papers and practical implementations:

For Al-Al interactions:

  • EAM/alloy potentials are generally preferred over pairwise potentials like Morse due to their ability to model metallic bonding more accurately[1][5][6].
  • If using a Morse potential explicitly:
    • Parameters derived from experimental data or quantum calculations include:
      • D₀ (well depth): ~0.4824 eV
      • α (width): ~1.322 Å⁻¹
      • r₀ (equilibrium distance): ~2.92 Å[8].

For cross-species interactions (e.g., Al-SiC interfaces):

  • Example implementations use:
    • Al-Si:
      • D₀ = 0.4824 eV
      • α = 1.322 Å⁻¹
      • r₀ = 2.92 Å
    • Al-C:
      • D₀ = 0.4691 eV
      • α = 1.738 Å⁻¹
      • r₀ = 2.246 Å[8].

Key considerations:

  1. EAM vs. Morse:

    • EAM/alloy potentials (example) better capture metallic properties like thermal expansion and defect energies[1][6].
    • Use pair_style hybrid when combining EAM/Al with other potentials[8].
  2. Parameter derivation:

    • Parameters can be fitted using quantum calculations or experimental data (e.g., lattice constants)[4][6].

For detailed implementation examples in LAMMPS scripts involving hybrid potentials like tersoff, eam/alloy, and morse, see[8].

Citations:


Attention: Verify ASE Calculator Configuration

It appears that the hardcoded Morse potential parameters in the test case might not align with commonly recommended values for aluminum. Currently, the test is configured with:

  • D₀: 0.5
  • α: 1.8
  • r₀: 2.95

According to recent literature and practical implementations for Al–Al interactions:

  • D₀ should be around 0.4824 eV
  • α should be near 1.322 Å⁻¹
  • r₀ is recommended as approximately 2.92 Å

Please verify whether these differences are intentional for testing purposes. If not, consider updating the values or adding an explanatory comment to clarify why the current parameters were chosen.

atomistics/calculators/lammps/filecalculator.py (1)

1-25: Imports and constants look good.
These imports, constants, and configurations seem structured and appropriate for a file-based LAMMPS interface. No immediate concerns on correctness, security, or maintainability.

Comment on lines +78 to +128
def optimize_positions_and_volume_with_lammpsfile(
structure: Atoms,
potential_dataframe: pandas.DataFrame,
working_directory: str,
executable_function: callable,
min_style: str = "cg",
etol: float = 0.0,
ftol: float = 0.0001,
maxiter: int = 100000,
maxeval: int = 10000000,
thermo: int = 10,
) -> Atoms:
template_str = (
LAMMPS_MINIMIZE_VOLUME
+ "\n"
+ LAMMPS_THERMO_STYLE
+ "\n"
+ LAMMPS_THERMO
+ "\n"
+ LAMMPS_MINIMIZE
)
input_template = Template(template_str).render(
min_style=min_style,
etol=etol,
ftol=ftol,
maxiter=maxiter,
maxeval=maxeval,
thermo=thermo,
)
_write_lammps_input_file(
working_directory=working_directory,
structure=structure,
potential_dataframe=potential_dataframe,
input_template=input_template,
)
executable_function(working_directory)
output = _parse_lammps_output(
working_directory=working_directory,
structure=structure,
potential_elements=potential_dataframe["Species"],
units="metal",
prism=None,
dump_h5_file_name="dump.h5",
dump_out_file_name="dump.out",
log_lammps_file_name="log.lammps",
)
structure_copy = structure.copy()
structure_copy.set_cell(output["generic"]["cells"][-1], scale_atoms=True)
structure_copy.positions = output["generic"]["positions"][-1]
return structure_copy

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle potential empty output errors.
optimize_positions_and_volume_with_lammpsfile() (lines 78–128) sets the cell/positions from the last step of the parsed output. If the output is empty (e.g., if LAMMPS fails), an IndexError or KeyError may occur. Consider gracefully handling or raising a meaningful exception.

Comment on lines +130 to +171
def optimize_positions_with_lammpsfile(
structure: Atoms,
potential_dataframe: pandas.DataFrame,
working_directory: str,
executable_function: callable,
min_style: str = "cg",
etol: float = 0.0,
ftol: float = 0.0001,
maxiter: int = 100000,
maxeval: int = 10000000,
thermo: int = 10,
) -> Atoms:
template_str = LAMMPS_THERMO_STYLE + "\n" + LAMMPS_THERMO + "\n" + LAMMPS_MINIMIZE
input_template = Template(template_str).render(
min_style=min_style,
etol=etol,
ftol=ftol,
maxiter=maxiter,
maxeval=maxeval,
thermo=thermo,
)
_write_lammps_input_file(
working_directory=working_directory,
structure=structure,
potential_dataframe=potential_dataframe,
input_template=input_template,
)
executable_function(working_directory)
output = _parse_lammps_output(
working_directory=working_directory,
structure=structure,
potential_elements=potential_dataframe["Species"],
units="metal",
prism=None,
dump_h5_file_name="dump.h5",
dump_out_file_name="dump.out",
log_lammps_file_name="log.lammps",
)
structure_copy = structure.copy()
structure_copy.positions = output["generic"]["positions"][-1]
return structure_copy

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Similar error handling consideration.
optimize_positions_with_lammpsfile() (lines 130–171) suffers from the same potential empty output scenario as the previous function. Adding the same protective checks is advisable.

Comment on lines +20 to +26
def evaluate_lammps(working_directory):
command = "mpiexec -n 1 --oversubscribe lmp_mpi -in lmp.in"
output = subprocess.check_output(
command, cwd=working_directory, shell=True, universal_newlines=True, env=os.environ.copy()
)
print(output)
return output
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Address security and robustness concerns in LAMMPS execution.

The current implementation has several issues:

  1. Using shell=True is a security risk as it's vulnerable to shell injection
  2. Hard-coded command string reduces flexibility
  3. Missing error handling for subprocess failures
 def evaluate_lammps(working_directory):
-    command = "mpiexec -n 1 --oversubscribe lmp_mpi -in lmp.in"
-    output = subprocess.check_output(
-        command, cwd=working_directory, shell=True, universal_newlines=True, env=os.environ.copy()
-    )
-    print(output)
-    return output
+    try:
+        command = ["mpiexec", "-n", "1", "--oversubscribe", "lmp_mpi", "-in", "lmp.in"]
+        output = subprocess.check_output(
+            command,
+            cwd=working_directory,
+            shell=False,
+            text=True,
+            env=os.environ.copy(),
+            stderr=subprocess.PIPE
+        )
+        print(output)
+        return output
+    except subprocess.CalledProcessError as e:
+        print(f"LAMMPS execution failed with return code {e.returncode}")
+        print(f"Error output: {e.stderr}")
+        raise
📝 Committable suggestion

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

Suggested change
def evaluate_lammps(working_directory):
command = "mpiexec -n 1 --oversubscribe lmp_mpi -in lmp.in"
output = subprocess.check_output(
command, cwd=working_directory, shell=True, universal_newlines=True, env=os.environ.copy()
)
print(output)
return output
def evaluate_lammps(working_directory):
try:
command = ["mpiexec", "-n", "1", "--oversubscribe", "lmp_mpi", "-in", "lmp.in"]
output = subprocess.check_output(
command,
cwd=working_directory,
shell=False,
text=True,
env=os.environ.copy(),
stderr=subprocess.PIPE
)
print(output)
return output
except subprocess.CalledProcessError as e:
print(f"LAMMPS execution failed with return code {e.returncode}")
print(f"Error output: {e.stderr}")
raise

Comment on lines +29 to +42
def optimize_structure(structure, potential_name, working_directory, resource_path=None):
df_pot_selected = get_potential_by_name(
potential_name=potential_name,
resource_path=resource_path,
)
task_dict = optimize_positions(structure=structure)
result_dict = evaluate_with_lammpsfile(
task_dict=task_dict,
potential_dataframe=df_pot_selected,
working_directory=working_directory,
executable_function=evaluate_lammps,
lmp_optimizer_kwargs={"ftol": 0.000001},
)
return result_dict["structure_with_optimized_positions"]
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation and error handling to the optimization function.

The function lacks input validation and error handling for potential retrieval failures.

 def optimize_structure(structure, potential_name, working_directory, resource_path=None):
+    if not structure or not potential_name or not working_directory:
+        raise ValueError("structure, potential_name, and working_directory are required")
+    
+    if not os.path.isdir(working_directory):
+        raise ValueError(f"working_directory '{working_directory}' does not exist")
+    
     df_pot_selected = get_potential_by_name(
         potential_name=potential_name,
         resource_path=resource_path,
     )
+    
+    if df_pot_selected is None or df_pot_selected.empty:
+        raise ValueError(f"Potential '{potential_name}' not found")
+    
     task_dict = optimize_positions(structure=structure)
     result_dict = evaluate_with_lammpsfile(
         task_dict=task_dict,
📝 Committable suggestion

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

Suggested change
def optimize_structure(structure, potential_name, working_directory, resource_path=None):
df_pot_selected = get_potential_by_name(
potential_name=potential_name,
resource_path=resource_path,
)
task_dict = optimize_positions(structure=structure)
result_dict = evaluate_with_lammpsfile(
task_dict=task_dict,
potential_dataframe=df_pot_selected,
working_directory=working_directory,
executable_function=evaluate_lammps,
lmp_optimizer_kwargs={"ftol": 0.000001},
)
return result_dict["structure_with_optimized_positions"]
def optimize_structure(structure, potential_name, working_directory, resource_path=None):
if not structure or not potential_name or not working_directory:
raise ValueError("structure, potential_name, and working_directory are required")
if not os.path.isdir(working_directory):
raise ValueError(f"working_directory '{working_directory}' does not exist")
df_pot_selected = get_potential_by_name(
potential_name=potential_name,
resource_path=resource_path,
)
if df_pot_selected is None or df_pot_selected.empty:
raise ValueError(f"Potential '{potential_name}' not found")
task_dict = optimize_positions(structure=structure)
result_dict = evaluate_with_lammpsfile(
task_dict=task_dict,
potential_dataframe=df_pot_selected,
working_directory=working_directory,
executable_function=evaluate_lammps,
lmp_optimizer_kwargs={"ftol": 0.000001},
)
return result_dict["structure_with_optimized_positions"]

Comment on lines +45 to +93
@unittest.skipIf(
skip_lammps_test, "LAMMPS is not installed, so the LAMMPS tests are skipped."
)
class TestOptimizePositionsLAMMPS(unittest.TestCase):
def setUp(self):
self.working_directory = os.path.abspath(os.path.join(__file__, "..", "lammps"))
os.makedirs(self.working_directory, exist_ok=True)

def tearDown(self):
if os.path.exists(self.working_directory):
shutil.rmtree(self.working_directory)

def test_optimize_positions_with_resource_path(self):
structure = bulk("Al", cubic=True)
positions_before_displacement = structure.positions.copy()
structure.positions[0] += [0.01, 0.01, 0.01]
structure_optimized = optimize_structure(
structure=structure,
potential_name="1999--Mishin-Y--Al--LAMMPS--ipr1",
resource_path=os.path.join(os.path.dirname(__file__), "static", "lammps"),
working_directory=self.working_directory,
)
self.assertTrue(
all(
np.isclose(
positions_before_displacement,
structure_optimized.positions - structure_optimized.positions[0],
).flatten()
)
)

def test_optimize_positions_without_resource_path(self):
structure = bulk("Al", cubic=True)
positions_before_displacement = structure.positions.copy()
structure.positions[0] += [0.01, 0.01, 0.01]
structure_optimized = optimize_structure(
structure=structure,
potential_name="1999--Mishin-Y--Al--LAMMPS--ipr1",
resource_path=None,
working_directory=self.working_directory,
)
self.assertTrue(
all(
np.isclose(
positions_before_displacement,
structure_optimized.positions - structure_optimized.positions[0],
).flatten()
)
)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve test coverage and reduce code duplication.

The test class has several areas for improvement:

  1. Test methods share identical logic, leading to code duplication
  2. Missing edge case tests (invalid potentials, invalid structures, etc.)
  3. Test data path construction could be more robust

Consider refactoring the tests to reduce duplication and improve coverage:

 @unittest.skipIf(
     skip_lammps_test, "LAMMPS is not installed, so the LAMMPS tests are skipped."
 )
 class TestOptimizePositionsLAMMPS(unittest.TestCase):
     def setUp(self):
         self.working_directory = os.path.abspath(os.path.join(__file__, "..", "lammps"))
+        self.test_data_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "static", "lammps"))
         os.makedirs(self.working_directory, exist_ok=True)
+        self.structure = bulk("Al", cubic=True)
+        self.positions_before_displacement = self.structure.positions.copy()
+        self.structure.positions[0] += [0.01, 0.01, 0.01]
 
     def tearDown(self):
         if os.path.exists(self.working_directory):
             shutil.rmtree(self.working_directory)
 
-    def test_optimize_positions_with_resource_path(self):
-        structure = bulk("Al", cubic=True)
-        positions_before_displacement = structure.positions.copy()
-        structure.positions[0] += [0.01, 0.01, 0.01]
+    def _run_optimization(self, resource_path=None):
         structure_optimized = optimize_structure(
-            structure=structure,
+            structure=self.structure,
             potential_name="1999--Mishin-Y--Al--LAMMPS--ipr1",
-            resource_path=os.path.join(os.path.dirname(__file__), "static", "lammps"),
+            resource_path=resource_path,
             working_directory=self.working_directory,
         )
         self.assertTrue(
             all(
                 np.isclose(
-                    positions_before_displacement,
+                    self.positions_before_displacement,
                     structure_optimized.positions - structure_optimized.positions[0],
                 ).flatten()
             )
         )
+        return structure_optimized
 
+    def test_optimize_positions_with_resource_path(self):
+        self._run_optimization(resource_path=self.test_data_path)
+
     def test_optimize_positions_without_resource_path(self):
-        structure = bulk("Al", cubic=True)
-        positions_before_displacement = structure.positions.copy()
-        structure.positions[0] += [0.01, 0.01, 0.01]
-        structure_optimized = optimize_structure(
-            structure=structure,
-            potential_name="1999--Mishin-Y--Al--LAMMPS--ipr1",
-            resource_path=None,
-            working_directory=self.working_directory,
-        )
-        self.assertTrue(
-            all(
-                np.isclose(
-                    positions_before_displacement,
-                    structure_optimized.positions - structure_optimized.positions[0],
-                ).flatten()
-            )
-        )
+        self._run_optimization()
+
+    def test_invalid_potential(self):
+        with self.assertRaises(ValueError):
+            optimize_structure(
+                structure=self.structure,
+                potential_name="non_existent_potential",
+                working_directory=self.working_directory,
+            )
+
+    def test_invalid_structure(self):
+        with self.assertRaises(ValueError):
+            optimize_structure(
+                structure=None,
+                potential_name="1999--Mishin-Y--Al--LAMMPS--ipr1",
+                working_directory=self.working_directory,
+            )
📝 Committable suggestion

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

Suggested change
@unittest.skipIf(
skip_lammps_test, "LAMMPS is not installed, so the LAMMPS tests are skipped."
)
class TestOptimizePositionsLAMMPS(unittest.TestCase):
def setUp(self):
self.working_directory = os.path.abspath(os.path.join(__file__, "..", "lammps"))
os.makedirs(self.working_directory, exist_ok=True)
def tearDown(self):
if os.path.exists(self.working_directory):
shutil.rmtree(self.working_directory)
def test_optimize_positions_with_resource_path(self):
structure = bulk("Al", cubic=True)
positions_before_displacement = structure.positions.copy()
structure.positions[0] += [0.01, 0.01, 0.01]
structure_optimized = optimize_structure(
structure=structure,
potential_name="1999--Mishin-Y--Al--LAMMPS--ipr1",
resource_path=os.path.join(os.path.dirname(__file__), "static", "lammps"),
working_directory=self.working_directory,
)
self.assertTrue(
all(
np.isclose(
positions_before_displacement,
structure_optimized.positions - structure_optimized.positions[0],
).flatten()
)
)
def test_optimize_positions_without_resource_path(self):
structure = bulk("Al", cubic=True)
positions_before_displacement = structure.positions.copy()
structure.positions[0] += [0.01, 0.01, 0.01]
structure_optimized = optimize_structure(
structure=structure,
potential_name="1999--Mishin-Y--Al--LAMMPS--ipr1",
resource_path=None,
working_directory=self.working_directory,
)
self.assertTrue(
all(
np.isclose(
positions_before_displacement,
structure_optimized.positions - structure_optimized.positions[0],
).flatten()
)
)
@unittest.skipIf(
skip_lammps_test, "LAMMPS is not installed, so the LAMMPS tests are skipped."
)
class TestOptimizePositionsLAMMPS(unittest.TestCase):
def setUp(self):
self.working_directory = os.path.abspath(os.path.join(__file__, "..", "lammps"))
self.test_data_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "static", "lammps"))
os.makedirs(self.working_directory, exist_ok=True)
self.structure = bulk("Al", cubic=True)
self.positions_before_displacement = self.structure.positions.copy()
self.structure.positions[0] += [0.01, 0.01, 0.01]
def tearDown(self):
if os.path.exists(self.working_directory):
shutil.rmtree(self.working_directory)
def _run_optimization(self, resource_path=None):
structure_optimized = optimize_structure(
structure=self.structure,
potential_name="1999--Mishin-Y--Al--LAMMPS--ipr1",
resource_path=resource_path,
working_directory=self.working_directory,
)
self.assertTrue(
all(
np.isclose(
self.positions_before_displacement,
structure_optimized.positions - structure_optimized.positions[0],
).flatten()
)
)
return structure_optimized
def test_optimize_positions_with_resource_path(self):
self._run_optimization(resource_path=self.test_data_path)
def test_optimize_positions_without_resource_path(self):
self._run_optimization()
def test_invalid_potential(self):
with self.assertRaises(ValueError):
optimize_structure(
structure=self.structure,
potential_name="non_existent_potential",
working_directory=self.working_directory,
)
def test_invalid_structure(self):
with self.assertRaises(ValueError):
optimize_structure(
structure=None,
potential_name="1999--Mishin-Y--Al--LAMMPS--ipr1",
working_directory=self.working_directory,
)

@jan-janssen jan-janssen merged commit 5382348 into main Feb 24, 2025
26 checks passed
@jan-janssen jan-janssen deleted the lammpslibrary branch February 24, 2025 20:11
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