Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: tests

on:
push:

branches: [main]
pull_request:
branches: [main]

Expand All @@ -26,8 +28,10 @@ jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.9', '3.10', '3.11']
# OVITO does not yet ship wheels for Python 3.13, so we cap at 3.12.
python-version: ['3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v4
Expand All @@ -48,6 +52,15 @@ jobs:
- name: Test
run: pytest --cov=wetting_angle_kit --cov-report=xml

- name: Upload coverage to Codecov
if: matrix.python-version == '3.11'
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
fail_ci_if_error: false
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

docs:
runs-on: ubuntu-latest

Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Auto-generated version file
wetting_angle_kit/_version.py
src/wetting_angle_kit/_version.py

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
7 changes: 2 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
default_language_version:
python: python3
exclude: ^(.github/|tests/test_data/abinit/)
exclude: ^(\.github/|docs/build/)
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.4.6
Expand Down Expand Up @@ -33,12 +33,9 @@ repos:
rev: v1.10.0
hooks:
- id: mypy
files: ^src/
files: ^wetting_angle_kit/
additional_dependencies:
- tokenize-rt==4.1.0
- types-paramiko
- pydantic~=2.0
- types-python-dateutil
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,16 @@ conda install --strict-channel-priority -c https://conda.ovito.org -c conda-forg


```python
from wetting_angle_kit import (
XYZParser, SlicedContactAngleAnalyzer, BinningContactAngleAnalyzer,
detect_parser_type, contact_angle_analyzer
from wetting_angle_kit.contact_angle_methods import (
BinningContactAngleAnalyzer,
SlicedContactAngleAnalyzer,
)
from wetting_angle_kit.parsers import XYZParser

trajectory_file = "trajectory.xyz"
parser = XYZParser(trajectory_file)

sliced = SlicedContactAngleAnalyzer(parser, output_repo="out_sliced", atom_indices=oxygen_ids, droplet_geometry="spherical", delta_gamma=5)
sliced = SlicedContactAngleAnalyzer(parser, output_dir="out_sliced", atom_indices=oxygen_ids, droplet_geometry="spherical", delta_gamma=5)
results = sliced.analyze(frame_range=range(0, 50))
print(results["mean_angle"], results["std_angle"])

Expand Down
8 changes: 4 additions & 4 deletions docs/examples/binning_ca.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Import necessary modules
from wetting_angle_kit.contact_angle_method import contact_angle_analyzer
from wetting_angle_kit.parser import DumpParser, DumpWaterMoleculeFinder
from wetting_angle_kit.contact_angle_methods import contact_angle_analyzer
from wetting_angle_kit.parsers import LammpsDumpParser, LammpsDumpWaterFinder

# --- Step 1: Define the trajectory file ---
filename = "../../tests/trajectories/traj_spherical_drop_4k.lammpstrj"

# --- Step 2: Initialize the water molecule finder ---
# This identifies O and H atoms in water molecules
wat_find = DumpWaterMoleculeFinder(
wat_find = LammpsDumpWaterFinder(
filename,
particle_type_wall={3}, # Wall atom types
oxygen_type=1, # Oxygen atom type
Expand All @@ -29,7 +29,7 @@
}

# --- Step 5: Initialize the parser ---
parser = DumpParser(filename)
parser = LammpsDumpParser(filename)

# --- Step 6: Create the contact angle analyzer ---
analyzer = contact_angle_analyzer(
Expand Down
28 changes: 16 additions & 12 deletions docs/examples/parsing_trajectory_files.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
"""
Example: Using DumpParser and DumpWaterMoleculeFinder
Example: Using LammpsDumpParser and LammpsDumpWaterFinder

This example shows how to:
1. Identify water molecules in a LAMMPS dump file.
2. Extract only their oxygen atom coordinates.
"""

from wetting_angle_kit.parser import (
ASE_Parser,
ASE_WaterMoleculeFinder,
DumpParser,
DumpWaterMoleculeFinder,
from wetting_angle_kit.parsers import (
AseParser,
AseWaterFinder,
LammpsDumpParser,
LammpsDumpWaterFinder,
XYZParser,
)

# --- Define input file ---
filename = "../../tests/trajectories/traj_10_3_330w_nve_4k_reajust.lammpstrj"

# --- Initialize water molecule finder ---
wat_find = DumpWaterMoleculeFinder(
wat_find = LammpsDumpWaterFinder(
filename,
particle_type_wall={3}, # atom type for wall
oxygen_type=1, # atom type for oxygen
Expand All @@ -30,9 +30,12 @@
print(f"Number of water molecules: {len(oxygen_indices)}")

# --- Initialize parser ---
parser = DumpParser(filename)
parser = LammpsDumpParser(filename)

# --- Extract only oxygen coordinates for frame 0 ---
# For LammpsDumpParser, `indices` are LAMMPS particle IDs (because LAMMPS may
# reorder atoms between frames). For XYZParser/AseParser, `indices` are
# 0-based positional indices.
oxygen_positions = parser.parse(frame_index=0, indices=oxygen_indices)
print("Extracted oxygen coordinates shape:", oxygen_positions.shape)

Expand All @@ -41,7 +44,7 @@
# print("All atom positions shape:", all_positions.shape)

"""
Example: Using ASE_Parser and ASE_WaterMoleculeFinder
Example: Using AseParser and AseWaterFinder

This example demonstrates how to:
1. Identify water oxygens in an ASE trajectory.
Expand All @@ -52,18 +55,19 @@
filename = "../../tests/trajectories/slice_10_mace_mlips_cylindrical_2_5.traj"

# --- Initialize water molecule finder ---
wat_find = ASE_WaterMoleculeFinder(
wat_find = AseWaterFinder(
filename,
particle_type_wall=["C"], # element name for wall
oh_cutoff=0.4, # O–H cutoff distance (Å)
oh_cutoff=1.2, # O–H bond cutoff (Å); ASE NeighborList handles the
# per-atom splitting internally now.
)

# --- Get oxygen indices for frame 0 ---
oxygen_indices = wat_find.get_water_oxygen_indices(frame_index=0)
print(f"Number of water molecules: {len(oxygen_indices)}")

# --- Initialize parser ---
parser = ASE_Parser(filename)
parser = AseParser(filename)

# --- Extract oxygen coordinates only ---
oxygen_positions = parser.parse(frame_index=0, indices=oxygen_indices)
Expand Down
39 changes: 39 additions & 0 deletions docs/examples/sliced_ca.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Sliced contact-angle example.

Runs the per-frame sliced (circle-fitting) analyzer on a LAMMPS dump
file and prints the resulting mean contact angle.
"""

from wetting_angle_kit.contact_angle_methods import contact_angle_analyzer
from wetting_angle_kit.parsers import LammpsDumpParser, LammpsDumpWaterFinder

# --- Step 1: Define the trajectory file ---
filename = "../../tests/trajectories/traj_spherical_drop_4k.lammpstrj"

# --- Step 2: Identify the water molecules (oxygen-bonded-to-two-H) ---
wat_find = LammpsDumpWaterFinder(
filename,
particle_type_wall={3}, # Wall atom types
oxygen_type=1,
hydrogen_type=2,
)

# `oxygen_indices` are LAMMPS particle IDs for the dump format.
oxygen_indices = wat_find.get_water_oxygen_ids(frame_index=0)
print("Number of water molecules:", len(oxygen_indices))

# --- Step 3: Build the sliced analyzer ---
parser = LammpsDumpParser(filename)
analyzer = contact_angle_analyzer(
method="sliced",
parser=parser,
output_dir="results_sliced_example",
atom_indices=oxygen_indices,
droplet_geometry="spherical",
delta_gamma=20, # Azimuthal step for spherical slicing (degrees)
)

# --- Step 4: Run analysis for a frame range ---
results = analyzer.analyze([1])
print("Mean contact angle (°):", results["mean_angle"])
print("Frames analyzed:", results["frames_analyzed"])
43 changes: 23 additions & 20 deletions docs/examples/visualisation_sliced_traj.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
"""End-to-end example: sliced contact-angle pipeline plus visualization.

Run a single-frame sliced analysis on a LAMMPS dump file and save a PNG of
the droplet with the fitted circle, surface contour, and tangent at the
contact point.
"""

import numpy as np

from wetting_angle_kit.contact_angle_method.sliced_method import ContactAngleSliced
from wetting_angle_kit.parser import (
DumpParser,
DumpWallParser,
DumpWaterMoleculeFinder,
from wetting_angle_kit.contact_angle_methods.sliced import ContactAngleSliced
from wetting_angle_kit.parsers import (
LammpsDumpParser,
LammpsDumpWallParser,
LammpsDumpWaterFinder,
)
from wetting_angle_kit.visualization_statistics_angles import Droplet_sliced_Plotter
from wetting_angle_kit.visualization import DropletSlicePlotter

# --- 1. Define the Input Trajectory ---
# Note: Ensure this path points to your actual .lammpstrj file location
filename = (
"../wetting_angle_kit/tests/trajectories/traj_10_3_330w_nve_4k_reajust.lammpstrj"
)
# Adjust this to point to your local .lammpstrj file.
filename = "../../tests/trajectories/traj_10_3_330w_nve_4k_reajust.lammpstrj"

# --- 2. Identify Water Molecules ---
wat_find = DumpWaterMoleculeFinder(
wat_find = LammpsDumpWaterFinder(
filename, particle_type_wall={3}, oxygen_type=1, hydrogen_type=2
)

oxygen_indices = wat_find.get_water_oxygen_ids(frame_index=0)
print("Number of water molecules detected:", len(oxygen_indices))

# --- 3. Parse Atomic Coordinates ---
parser = DumpParser(filepath=filename)
parser = LammpsDumpParser(filepath=filename)
oxygen_position = parser.parse(frame_index=10, indices=oxygen_indices)

coord_wall = DumpWallParser(filename, particule_liquid_type={1, 2})
wall_coords = coord_wall.parse(frame_index=1)
# Wall particles are everything not in the liquid types.
coord_wall = LammpsDumpWallParser(filename, liquid_particle_types=[1, 2])
wall_coords = coord_wall.parse(frame_index=10)

# --- 4. Compute Contact Angles ---
#


processor = ContactAngleSliced(
liquid_coordinates=oxygen_position,
liquid_geom_center=np.mean(oxygen_position, axis=0),
Expand All @@ -43,10 +46,10 @@
)

list_alfas, array_surfaces, array_popt = processor.predict_contact_angle()
print("Mean contact angles (°):", list_alfas)
print("Per-slice contact angles (°):", list_alfas)

# --- 5. Visualize the Droplet ---
plotter = Droplet_sliced_Plotter(center=True, show_wall=True, molecule_view=True)
plotter = DropletSlicePlotter(center=True, show_wall=True, molecule_view=True)

plotter.plot_surface_points(
oxygen_position=oxygen_position,
Expand All @@ -57,4 +60,4 @@
alpha=list_alfas[0],
)

print(" Plot saved as 'droplet_plot.png'")
print("Plot saved as 'droplet_plot.png'")
10 changes: 5 additions & 5 deletions docs/source/API/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Complete reference for all modules, classes, and functions in wetting_angle_kit.
Parser Module
-------------

.. automodule:: wetting_angle_kit.parser
.. automodule:: wetting_angle_kit.parsers
:members:
:undoc-members:
:show-inheritance:
Expand All @@ -17,14 +17,14 @@ Contact Angle Methods
Base Analyzer
^^^^^^^^^^^^^

.. automodule:: wetting_angle_kit.contact_angle_method.contact_angle_analyzer
.. automodule:: wetting_angle_kit.contact_angle_methods.analyzer
:members:
:show-inheritance:

Sliced Method
^^^^^^^^^^^^^

.. automodule:: wetting_angle_kit.contact_angle_method.sliced_method
.. automodule:: wetting_angle_kit.contact_angle_methods.sliced
:members:
:undoc-members:
:show-inheritance:
Expand All @@ -33,7 +33,7 @@ Sliced Method
Binning Method
^^^^^^^^^^^^^

.. automodule:: wetting_angle_kit.contact_angle_method.binning_method
.. automodule:: wetting_angle_kit.contact_angle_methods.binning
:members:
:undoc-members:
:show-inheritance:
Expand All @@ -42,7 +42,7 @@ Binning Method
Visualization and Statistics
-----------------------------

.. automodule:: wetting_angle_kit.visualization_angles
.. automodule:: wetting_angle_kit.visualization
:members:
:undoc-members:
:show-inheritance:
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import os
import sys

sys.path.insert(0, os.path.abspath("../../"))
sys.path.insert(0, os.path.abspath("../../src"))

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
Expand Down
6 changes: 4 additions & 2 deletions docs/source/introduction/Installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ Before installing wetting_angle_kit, ensure you have the following prerequisites
1. **Python 3.9 or higher**: Make sure you have Python 3.9 or higher installed on your system.
2. **Conda**: Ensure you have Conda installed. If not, you can install it from `here <https://docs.conda.io/en/latest/miniconda.html>`_.

## Optional Dependencies Strategy
Optional Dependencies Strategy
------------------------------

OVITO and ASE are only imported inside the respective parser classes. Installing the package
without extras keeps dependencies minimal. Calling an OVITO/ASE parser without installing
raises a clear ImportError with installation instructions.
Expand All @@ -18,7 +20,7 @@ Installation Options
--------------------

Core (only for xyz file analysis)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: bash

Expand Down
Loading
Loading