Skip to main content

A Python library to parse MD trajectories from LAMMPS and ASE and measure the contact angle through different methods

Project description

wetting-angle-kit

tests docs code coverage pypi version Python versions License: BSD 3-Clause Documentation

wetting-angle-kit parses MD trajectories (LAMMPS dump, XYZ, ASE) and computes the contact angle of a droplet sitting on a planar wall. The package follows the same conceptual recipe every method uses — extract the liquid-vapor interface from atom positions, decide where the wall plane sits, fit a geometric shape, read off the angle from the shape/wall intersection — but exposes each step as a swappable component so users can match the method to their system.

How the methods are built

Interface extraction: how do we turn atoms into a surface?

The liquid-vapor interface isn't a sharp surface in an MD simulation — the density drops smoothly over ~1 Å. Two extraction strategies recover a clean set of interface points from the noisy atom cloud:

The package exposes two orthogonal strategy axes for interface extraction. A SpaceSampling decides where density is evaluated; a DensityEstimator decides how. An InterfaceExtractor composes one of each.

  • Sampling: SpaceSampling.rays(...) emits a fan of rays from the droplet centre of mass; the interface along each ray is the half-density point of a 1D tanh fit. The fan layout is azimuthal slices in the (x, z) plane (for a per-slice fit) or a Fibonacci sphere of directions (for a whole-shape fit).
  • Sampling: SpaceSampling.grid(...) builds a fixed-cell grid in space; the interface is the iso-density contour at the half-bulk level, traced via marching squares (slicing mode) or marching cubes (whole mode). In slicing mode the grid iterates per slice (per azimuthal angle for spherical droplets, per axial step for cylinder droplets), so the slicing fit sees one (s, z) contour per slice and can expose per-slice asymmetry. Closer to the "average over many frames" intuition than ray fans; works well when atom statistics are limited per frame.
  • Density: DensityEstimator.gaussian(density_sigma=…) is a 3D Gaussian KDE (smooth, no per-cell Poisson noise).
  • Density: DensityEstimator.binning(bin_width=…) is a 3D top-hat histogram (cheap; bin_width required only for the rays sampling, where it sets the pointwise kernel size).

Any sampling × any density is a valid extractor.

Surface fitting: what geometric shape do we fit to those points?

  • Slicing fit — independently fits an algebraic circle in each slice's (x, z) plane, then averages the per-slice contact angles. Good when the droplet might be slightly non-spherical: the per-slice scatter naturally reports a ±σ band.
  • Whole fit — fits a single sphere (spherical droplet) or cylinder (cylindrical droplet) to the entire 3D interface shell. Uses the algebraic Taubin method, plus optional bootstrap resampling to put an uncertainty on the recovered angle.
  • Coupled fit (joint approach) — a 7-parameter (2D) or 9-parameter (3D) hyperbolic-tangent density model that solves "where is the interface", "where is the wall plane", and "what's the cap geometry" in one nonlinear least-squares fit on a density field. The per-cell density is computed by a pluggable DensityEstimator strategy: a top-hat histogram (DensityEstimator.binning(), default) or a 3D Gaussian KDE evaluated at the cell centres (DensityEstimator.gaussian(density_sigma=…)); the KDE variant trades a small constant cost for a smooth, Poisson-noise-free density. Statistically efficient when you pool many frames per batch.

Wall detection: where is the wall plane?

The contact angle is measured at the cap–wall intersection, so the wall plane has to be located explicitly:

  • min_plus_offset: derive the wall from the interface itself (lowest interface point + offset). Works for slicing geometries and full-sphere ray fans, where the interface points reach the wall.
  • from_atoms: read the actual wall atom positions from the trajectory and place the wall at the mean of the top atomic layer. Most physically faithful when the simulation explicitly contains substrate atoms.
  • explicit: caller supplies the wall z directly — useful when the wall position is known a priori from the simulation setup.

Frame batching: per-frame angle or pooled batch?

The TemporalAggregator groups trajectory frames into batches before fitting. batch_size=1 runs the full pipeline once per frame (giving you an angle vs time curve); batch_size=N pools N frames together and fits one angle per pool (more atoms per fit → less noise, less time resolution); batch_size=-1 pools everything into a single batch.

Two top-level entry points

  1. TrajectoryAnalyzer — composes the four strategies above (InterfaceExtractor × SurfaceFitter × WallDetector × TemporalAggregator). Use it when you want per-frame time resolution or when you want to mix-and-match approaches (e.g. ray-fan extractor + whole-fit + explicit wall + 5-frame batches).
  2. CoupledFit2DAnalyzer / CoupledFit3DAnalyzer — the joint-fit alternative. One robust angle per pooled batch via the hyperbolic-tangent density model. The per-cell density estimator is pluggable (DensityEstimator.binning() or DensityEstimator.gaussian(...)). Best when you have many frames and don't need per-frame time resolution.

The documentation is available here, with worked examples and tutorials.

Installation

Prerequisites

Before installing wetting-angle-kit, ensure you have Python 3.10 or higher installed on your system.

Core (only to analyse simple xyz trajectories):

pip install wetting-angle-kit

With OVITO:

pip install wetting-angle-kit[ovito]

With ASE:

pip install wetting-angle-kit[ase]

All optional:

pip install wetting-angle-kit[all]

Install OVITO

OVITO must be installed first using pip:

pip install  ovito==3.11.3

Quick Start

from wetting_angle_kit.analysis import (
    CoupledFit2DAnalyzer,
    DensityEstimator,
    InterfaceExtractor,
    SpaceSampling,
    SurfaceFitter,
    TrajectoryAnalyzer,
    WallDetector,
)
from wetting_angle_kit.analysis.temporal import TemporalAggregator
from wetting_angle_kit.parsers import XYZParser, XYZWaterFinder

trajectory_file = "trajectory.xyz"

# Identify water oxygen atoms by neighbour count.
finder = XYZWaterFinder(trajectory_file)
oxygen_ids = finder.get_water_oxygen_indices(frame_index=0)

parser = XYZParser(trajectory_file)

# --- Composable pipeline (per-frame slicing-fit angles) ---
slicing = TrajectoryAnalyzer(
    parser=parser,
    atom_indices=oxygen_ids,
    droplet_geometry="spherical",
    interface_extractor=InterfaceExtractor(
        sampling=SpaceSampling.rays(
            delta_azimuthal=5.0,  # 5° between slicing planes
            delta_polar=8.0,
        ),
        density=DensityEstimator.gaussian(),
    ),
    surface_fitter=SurfaceFitter.slicing(surface_filter_offset=2.0),
    wall_detector=WallDetector.min_plus_offset(offset=0.0),
    temporal_aggregator=TemporalAggregator(batch_size=1),  # one angle per frame
)
results = slicing.analyze(range(0, 24))
print(results.mean_angle, results.std_angle)

# --- Joint coupled-fit (one robust angle over a pooled batch) ---
coupled_fit = CoupledFit2DAnalyzer(
    parser=parser,
    atom_indices=oxygen_ids,
    droplet_geometry="spherical",
    binning_params={
        "xi_0": 0.0, "xi_f": 70.0, "bin_width_x": 2.0,
        "zi_0": 0.0, "zi_f": 70.0, "bin_width_z": 2.0,
    },
    # Default: histogram density. Swap in `DensityEstimator.gaussian(
    # density_sigma=2.5)` for a smooth Gaussian-KDE density field —
    # useful on per-frame batches or sparse systems.
    density_estimator=DensityEstimator.binning(),
)
results_coupled_fit = coupled_fit.analyze(range(0, 200))
print(results_coupled_fit.mean_angle, results_coupled_fit.std_angle)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wetting_angle_kit-1.2.0.tar.gz (85.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wetting_angle_kit-1.2.0-py3-none-any.whl (100.2 kB view details)

Uploaded Python 3

File details

Details for the file wetting_angle_kit-1.2.0.tar.gz.

File metadata

  • Download URL: wetting_angle_kit-1.2.0.tar.gz
  • Upload date:
  • Size: 85.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wetting_angle_kit-1.2.0.tar.gz
Algorithm Hash digest
SHA256 767aebbf6574f74cad555f0d8fa2f45071c71dff8ae9045b06eb8a91a4209323
MD5 0404da4597ab3f71e0e5f1012bc0cfc2
BLAKE2b-256 0f320777297a652dbc071cb8ac4a007d5e75baae347ad805437596eddf7bdeca

See more details on using hashes here.

File details

Details for the file wetting_angle_kit-1.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for wetting_angle_kit-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 818a5acfda847b0c2588ea90888abdbf50bdb7ddd9f9c8e65a9ed9f7d809c8ef
MD5 038dda94ac407eb2e2122b6059cf535e
BLAKE2b-256 45bb75718ab97c1f17f2f6d1540bde238c9c4e9886705136a84ad043e5526b83

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page