Skip to content

Conversation

@PythonFZ
Copy link
Member

@PythonFZ PythonFZ commented Aug 7, 2025

Summary by CodeRabbit

  • New Features
    • Added tools for building crystal surface structures and placing adsorbate molecules with collision avoidance.
    • Introduced atom selection strategies and constraints for targeted atom fixing and bond length control during simulations.
  • Tests
    • Included comprehensive unit and integration tests for surface building, adsorbate placement, atom selection, and constraint application in optimization workflows.

@coderabbitai
Copy link

coderabbitai bot commented Aug 7, 2025

Walkthrough

The ipsuite.configuration_generation module is extended with two new classes, BuildSurface and AddAdsorbate, for constructing crystal surfaces and adding adsorbates respectively. The public API is updated to export these classes. Comprehensive integration tests verify surface building, adsorbate placement, collision detection, and parameter-driven behaviors. Additionally, new atom selection and constraint modules, interfaces, and related tests are introduced, enabling flexible atom selection strategies and constraints for geometry optimization workflows.

Changes

Cohort / File(s) Change Summary
Public API Updates
ipsuite/configuration_generation/__init__.pyi, ipsuite/__init__.pyi
Exported BuildSurface and AddAdsorbate in configuration generation; added multiple atom selection classes and interfaces (AtomSelector, AtomConstraint) to the public API.
Surface Builder Implementation
ipsuite/configuration_generation/surface_builder.py
Added BuildSurface for generating crystal surfaces and AddAdsorbate for placing adsorbates on surfaces, both using ASE and zntrack with parameter management, collision avoidance, and output handling.
Atom Selection Module
ipsuite/atom_selection/__init__.py
New module importing and exporting atom selection classes and constraints for centralized access.
Atom Selection Constraints
ipsuite/atom_selection/constraints.py
Added constraint classes (FixAtomsConstraint, FixBondLengthConstraint, FixBondLengthsConstraint) implementing ASE constraints based on atom selectors.
Atom Selection Strategies
ipsuite/atom_selection/selections.py
Implemented various atom selectors (ElementTypeSelection, ZPositionSelection, RadialSelection, LayerSelection, SurfaceSelection) for selecting atoms based on element, position, radius, layers, and surface criteria.
Interface Protocols
ipsuite/interfaces.py
Added AtomSelector and AtomConstraint protocols defining interfaces for atom selection and constraint classes.
Integration Tests for Surface Building and Adsorbates
tests/integration/test_surface_builder.py
Added tests covering surface construction with various parameters, adsorbate addition including multiple adsorbates and collision detection, and integration with relaxation workflows.
Integration Tests for Atom Selection Constraints
tests/integration/test_atom_selection_integration.py
Added tests validating atom selection constraints in geometry optimizations, including fixing atomic layers and element-based constraints with ASEGeoOpt and MACEMPModel.
Unit Tests for Atom Selection and Constraints
tests/unit_tests/atom_selection/test_selections.py
Added comprehensive unit tests for atom selectors and constraints, verifying correct atom indices selection, constraint creation, and error handling for empty selections or invalid inputs.
Dependency Update
pyproject.toml
Updated zntrack dependency version requirement from ">=0.8.7" to ">=0.8.9".

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant BuildSurface
    participant ASE
    participant zntrack
    participant HDF5

    User->>BuildSurface: instantiate with parameters
    User->>BuildSurface: call run()
    BuildSurface->>ASE: create bulk structure
    BuildSurface->>ASE: generate surface slab with Miller indices, layers
    BuildSurface->>ASE: add vacuum padding
    BuildSurface->>zntrack: manage output path and parameter tracking
    BuildSurface->>HDF5: save surface slab frames
    User->>BuildSurface: access frames property
    BuildSurface->>HDF5: load saved surface frames
    BuildSurface-->>User: return list of ASE Atoms objects
Loading
sequenceDiagram
    participant User
    participant AddAdsorbate
    participant ASE
    participant zntrack
    participant HDF5

    User->>AddAdsorbate: instantiate with slab, adsorbate data, parameters
    User->>AddAdsorbate: call run()
    AddAdsorbate->>AddAdsorbate: select adsorbates based on indices
    AddAdsorbate->>AddAdsorbate: compute adsorbate positions avoiding collisions
    AddAdsorbate->>ASE: place adsorbates on slab at computed positions
    AddAdsorbate->>zntrack: manage output path and parameter tracking
    AddAdsorbate->>HDF5: save slab with adsorbates frames
    User->>AddAdsorbate: access frames property
    AddAdsorbate->>HDF5: load saved frames
    AddAdsorbate-->>User: return list of ASE Atoms objects with adsorbates
Loading
sequenceDiagram
    participant User
    participant AtomSelector
    participant AtomConstraint
    participant ASEGeoOpt
    participant MACEMPModel

    User->>AtomSelector: define selection criteria
    AtomSelector-->>User: return atom indices
    User->>AtomConstraint: create constraint using AtomSelector
    AtomConstraint-->>User: ASE constraint object
    User->>ASEGeoOpt: run geometry optimization with constraints and MACEMPModel
    ASEGeoOpt->>MACEMPModel: compute forces
    ASEGeoOpt-->>User: optimized structure and trajectory
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

Upon the lattice, layers rise,
Surfaces gleam before my eyes.
Adsorbates dance, placed with care,
No collisions found anywhere.
Select and fix with gentle touch,
Constraints that guide, but not too much.
A rabbit codes, with joy and flair! 🐇🌿✨

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch surface-builder

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
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
  • 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 explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @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

🧹 Nitpick comments (10)
ipsuite/atom_selection/constraints.py (2)

75-87: Minor inefficiency – duplicate selector calls

Each get_constraint path calls select() twice per selector. Cache the result to avoid redundant work, especially for expensive selectors (e.g. neighbor analysis).


121-140: Long error strings flagged by Ruff

Lines 129 & 133 exceed 90 chars (E501). Split them for CI green:

- f"Bond pair {i}, atom1 selection must select exactly 1 atom, got {len(indices1)}"
+ (
+     f"Bond pair {i}: atom1 selection must select exactly one atom "
+     f"(got {len(indices1)})"
+ )

Repeat for the second string.

ipsuite/atom_selection/selections.py (1)

246-257: Surface selection is O(N²) and ignores PBC

The double loop over atoms can be slow (>10 k atoms) and mis-counts across cell boundaries.
Consider ase.neighborlist.NeighborList or scipy.spatial.cKDTree with PBC support for an O(N)–ish solution and correct periodic handling.

tests/integration/test_atom_selection_integration.py (1)

6-15: Mark heavy integration test as slow

The test spins up an ML model and geometry optimisation – likely minutes. Add @pytest.mark.slow (or project equivalent) to keep routine CI fast.

tests/unit_tests/atom_selection/test_selections.py (1)

13-15: Make randomness deterministic

np.random.rand without a fixed seed can lead to rare flaky failures in geometry-based tests.
Set np.random.seed(0) at module setup or use fixed coordinates.

ipsuite/configuration_generation/surface_builder.py (3)

288-293: Propagate original stack-trace for easier debugging.

Re-raise with from err to distinguish bugs in caller code from bugs in the except block.

-                except IndexError:
-                    raise ValueError(
+                except IndexError as err:
+                    raise ValueError(
                         f"Index {index} is out of bounds for adsorbate list {i} "
                         f"with length {len(adsorbate_list)}"
-                    )
+                    ) from err

135-151: Unused private helper.

_get_com_position is never called after implementation. Delete it or integrate it (e.g., use mol_index=None with COM placement) to keep codebase lean.


234-246: Non-deterministic placement due to uncontrolled RNG.

np.random.uniform is called without seeding, making node outputs non-reproducible across runs.
Inject a deterministic seed via project config or expose a random_seed param.

tests/integration/test_surface_builder.py (2)

234-239: Variable never used – replace with “_” to silence F841.

-        adsorbed_system = ips.AddAdsorbate(
+        _ = ips.AddAdsorbate(

204-206: Line >90 chars flagged by Ruff (E501).

Consider breaking the comprehension across lines to keep within the project’s style limits.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 397b32f and aabaeb7.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • ipsuite/__init__.pyi (5 hunks)
  • ipsuite/atom_selection/__init__.py (1 hunks)
  • ipsuite/atom_selection/constraints.py (1 hunks)
  • ipsuite/atom_selection/selections.py (1 hunks)
  • ipsuite/configuration_generation/__init__.pyi (1 hunks)
  • ipsuite/configuration_generation/surface_builder.py (1 hunks)
  • ipsuite/interfaces.py (4 hunks)
  • pyproject.toml (1 hunks)
  • tests/integration/test_atom_selection_integration.py (1 hunks)
  • tests/integration/test_surface_builder.py (1 hunks)
  • tests/unit_tests/atom_selection/test_selections.py (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • pyproject.toml
  • ipsuite/atom_selection/init.py
  • ipsuite/init.pyi
🚧 Files skipped from review as they are similar to previous changes (1)
  • ipsuite/configuration_generation/init.pyi
🧰 Additional context used
🧬 Code Graph Analysis (1)
ipsuite/atom_selection/constraints.py (1)
ipsuite/interfaces.py (5)
  • interfaces (131-133)
  • AtomConstraint (88-108)
  • AtomSelector (65-85)
  • get_constraint (95-108)
  • select (72-85)
🪛 Ruff (0.12.2)
ipsuite/atom_selection/constraints.py

129-129: Line too long (101 > 90)

(E501)


133-133: Line too long (101 > 90)

(E501)

ipsuite/configuration_generation/surface_builder.py

290-293: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


297-297: run is too complex (11 > 10)

(C901)

tests/integration/test_surface_builder.py

217-217: Line too long (96 > 90)

(E501)


234-234: Local variable adsorbed_system is assigned to but never used

Remove assignment to unused variable adsorbed_system

(F841)

⏰ 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: pytest (3.11, ubuntu-latest)
🔇 Additional comments (3)
ipsuite/interfaces.py (2)

65-85: Interfaces look solid

The two new protocols are well-scoped, clearly documented, and typed. No issues spotted.


131-134: Potential breaking change in interfaces()

interfaces() now returns {"ipsuite": __all__} instead of {"ipsuite.interfaces": __all__}.
If any downstream code relies on the previous key it will silently break. Please double-check call-sites or provide a backward-compatibility shim.

ipsuite/atom_selection/constraints.py (1)

36-40: Guard clause OK

FixAtomsConstraint correctly raises when nothing is selected – nice touch.

Comment on lines +69 to +71
surface_atoms = surface(
lattice=bulk_atoms, indices=self.indices, layers=self.layers
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect keyword argument – this will raise TypeError.

ase.build.surface signature is surface(atoms, indices, layers, …).
Passing the bulk object via lattice= is invalid and will crash at runtime.

- surface_atoms = surface(
-     lattice=bulk_atoms, indices=self.indices, layers=self.layers
- )
+surface_atoms = surface(bulk_atoms, indices=self.indices, layers=self.layers)
📝 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
surface_atoms = surface(
lattice=bulk_atoms, indices=self.indices, layers=self.layers
)
surface_atoms = surface(bulk_atoms, indices=self.indices, layers=self.layers)
🤖 Prompt for AI Agents
In ipsuite/configuration_generation/surface_builder.py around lines 69 to 71,
the call to the surface function incorrectly uses the keyword argument
lattice=bulk_atoms, which is not valid and will cause a TypeError. Replace
lattice=bulk_atoms with the correct positional argument atoms=bulk_atoms by
passing bulk_atoms as the first positional argument without a keyword, followed
by indices=self.indices and layers=self.layers as positional arguments to match
the surface function signature.

Comment on lines +194 to +197
distance = np.sqrt((new_pos[0] - ex_x) ** 2 + (new_pos[1] - ex_y) ** 2)
min_distance = max(new_radius, existing_radii[i])
if distance < min_distance:
return True
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Collision test uses max instead of sum of radii.

To avoid overlap the allowed minimum distance between two exclusion circles should be r1 + r2, not max(r1, r2).
Using max silently allows collisions when radii differ.

-            min_distance = max(new_radius, existing_radii[i])
+            min_distance = new_radius + existing_radii[i]
📝 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
distance = np.sqrt((new_pos[0] - ex_x) ** 2 + (new_pos[1] - ex_y) ** 2)
min_distance = max(new_radius, existing_radii[i])
if distance < min_distance:
return True
distance = np.sqrt((new_pos[0] - ex_x) ** 2 + (new_pos[1] - ex_y) ** 2)
- min_distance = max(new_radius, existing_radii[i])
+ min_distance = new_radius + existing_radii[i]
if distance < min_distance:
return True
🤖 Prompt for AI Agents
In ipsuite/configuration_generation/surface_builder.py around lines 194 to 197,
the collision test incorrectly uses the maximum of the two radii to determine
minimum allowed distance, which can allow overlaps. Replace the use of
max(new_radius, existing_radii[i]) with the sum new_radius + existing_radii[i]
to correctly enforce that the distance between centers must be at least the sum
of their radii to avoid collisions.

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.

2 participants