API Reference
Complete reference for all modules, classes, and functions in wetting_angle_kit.
Parser Module
- class wetting_angle_kit.parsers.AseParser(filepath: str)[source]
Bases:
BaseParserASE trajectory parser for any ASE-readable file format.
- box_length_max(frame_index: int) float[source]
Return the maximum lattice vector length for a frame.
- Parameters:
frame_index (int) – Frame index.
- Returns:
Max
|a_i|over lattice vectors.- Return type:
float
- box_size_x(frame_index: int) float[source]
Return the length of the first lattice vector for a frame.
- box_size_y(frame_index: int) float[source]
Return the length of the second lattice vector for a frame.
- parse(frame_index: int, indices: ndarray | None = None) ndarray[source]
Return Cartesian coordinates for selected atoms in a frame.
- Parameters:
frame_index (int) – Frame index.
indices (ndarray, optional) – Atom indices to select; if None all atoms are returned.
- Returns:
Atom coordinates.
- Return type:
ndarray, shape (M, 3)
- parse_liquid_particles(liquid_particle_types: list[str], frame_index: int) ndarray[source]
Return positions of liquid atoms filtered by atomic symbol.
- Parameters:
liquid_particle_types (sequence[str]) – Symbols identifying liquid atoms.
frame_index (int) – Frame index.
- Returns:
Liquid atom positions.
- Return type:
ndarray, shape (L, 3)
- class wetting_angle_kit.parsers.AseWallParser(filepath: str, liquid_particle_types: list[str])[source]
Bases:
BaseParserParser extracting wall particle coordinates (excluding liquid types).
Wall particles are everything not in
liquid_particle_types. Theindicesargument ofparse()is treated as 0-based positional indices into the wall-only positions for compatibility with theBaseParsercontract.- box_length_max(frame_index: int) float[source]
Return the maximum lattice vector length for a frame.
- Parameters:
frame_index (int) – Frame index.
- Returns:
Max
|a_i|over lattice vectors.- Return type:
float
- box_size_x(frame_index: int) float[source]
Return the length of the first lattice vector for a frame.
- box_size_y(frame_index: int) float[source]
Return the length of the second lattice vector for a frame.
- find_highest_wall_part(*args: Any, **kwargs: Any) float[source]
Deprecated alias for find_highest_wall_particle.
- find_highest_wall_particle(frame_index: int) float[source]
Return the maximum z-coordinate among wall particles for a frame.
- Parameters:
frame_index (int) – Frame index.
- Returns:
Maximum z-coordinate.
- Return type:
float
- parse(frame_index: int, indices: ndarray | None = None) ndarray[source]
Return wall atom positions for a frame.
- Parameters:
frame_index (int) – Frame index.
indices (ndarray, optional) – Indices into the wall-only positions to further restrict the wall atoms; if None all wall atoms are returned.
- Returns:
Wall atoms coordinates.
- Return type:
ndarray, shape (M, 3)
- class wetting_angle_kit.parsers.AseWaterFinder(filepath: str, oxygen_type: str = 'O', hydrogen_type: str = 'H', oh_cutoff: float = 1.2)[source]
Bases:
objectIdentify water oxygen atoms by counting hydrogen neighbors via ASE neighbor list.
- class wetting_angle_kit.parsers.BaseParser[source]
Bases:
ABCAbstract interface for trajectory parsers consumed by analyzers.
Subclasses must implement
parse(),frame_count(), and the cell-geometry helpersbox_size_x(),box_size_y(), andbox_length_max(). The cell helpers are abstract because the analyzers rely on per-frame box information (PBC-aware droplet recentering, default sampling extent); a parser without it would silently degrade their accuracy.- abstractmethod box_length_max(frame_index: int) float[source]
Return the maximum lattice vector length for a frame.
- Parameters:
frame_index (int) – Frame index.
- Returns:
Max
|a_i|over lattice vectors.- Return type:
float
- abstractmethod box_size_x(frame_index: int) float[source]
Return the length of the first lattice vector for a frame.
- abstractmethod box_size_y(frame_index: int) float[source]
Return the length of the second lattice vector for a frame.
- abstractmethod parse(frame_index: int, indices: ndarray | None = None) ndarray[source]
Return Cartesian coordinates for selected atoms in a frame.
- Parameters:
frame_index (int) – Frame index.
indices (ndarray, optional) –
Atom indices to select; if None all atoms are returned. The meaning of
indicesdiffers by parser because the underlying file formats do not all preserve atom ordering across frames:LammpsDumpParser(LAMMPS) —indicesare LAMMPS particle identifiers. LAMMPS may reorder atoms between frames, so a persistent ID is needed to track the same atom.XYZParser,AseParser—indicesare positional indices into the per-frame coordinate array. These formats keep atom ordering stable across frames during simulations.
- Returns:
Atom coordinates.
- Return type:
ndarray, shape (M, 3)
- class wetting_angle_kit.parsers.LammpsDumpParser(filepath: str)[source]
Bases:
BaseParserLAMMPS dump trajectory parser backed by an OVITO pipeline.
- box_length_max(frame_index: int) float[source]
Return the maximum lattice vector length for a frame.
- Parameters:
frame_index (int) – Frame index.
- Returns:
Max
|a_i|over lattice vectors.- Return type:
float
- box_size_x(frame_index: int) float[source]
Return the length of the first lattice vector for a frame.
- box_size_y(frame_index: int) float[source]
Return the length of the second lattice vector for a frame.
- parse(frame_index: int, indices: ndarray | None = None) ndarray[source]
Return Cartesian coordinates for selected atoms in a frame.
- Parameters:
frame_index (int) – Frame index.
indices (ndarray, optional) – LAMMPS particle IDs to select; if None all atoms are returned.
- Returns:
Atom coordinates.
- Return type:
ndarray, shape (M, 3)
- class wetting_angle_kit.parsers.LammpsDumpWallParser(filepath: str, liquid_particle_types: list[int])[source]
Bases:
BaseParserLAMMPS dump file parser for extracting wall particle coordinates.
Wall particles are everything not in
liquid_particle_types; filtering is done inside the OVITO pipeline. Theindicesargument ofparse()is therefore typically ignored, but it is accepted (as LAMMPS particle IDs, likeLammpsDumpParser) to satisfy theBaseParsercontract.- box_length_max(frame_index: int) float[source]
Return the maximum lattice vector length for a frame.
- Parameters:
frame_index (int) – Frame index.
- Returns:
Max
|a_i|over lattice vectors.- Return type:
float
- box_size_x(frame_index: int) float[source]
Return the length of the first lattice vector for a frame.
- box_size_y(frame_index: int) float[source]
Return the length of the second lattice vector for a frame.
- find_highest_wall_particle(frame_index: int) float[source]
Return the maximum z-coordinate among wall particles for a frame.
- Parameters:
frame_index (int) – Frame index.
- Returns:
Maximum z-coordinate.
- Return type:
float
- load_dump_ovito() Any[source]
Build and return the OVITO pipeline for wall-only extraction.
Returns
Anybecause OVITO’s Python bindings ship without type stubs; the pipeline is opaque from the type checker’s perspective.
- parse(frame_index: int, indices: ndarray | None = None) ndarray[source]
Return wall atom positions for a frame.
- Parameters:
frame_index (int) – Frame index.
indices (ndarray, optional) – LAMMPS particle IDs to further restrict the wall atoms; if None all wall atoms are returned.
- Returns:
Wall atom coordinates.
- Return type:
ndarray, shape (M, 3)
- class wetting_angle_kit.parsers.LammpsDumpWaterFinder(filepath: str, oxygen_type: int, hydrogen_type: int, oh_cutoff: float = 1.2)[source]
Bases:
objectIdentify water oxygen atoms in a LAMMPS trajectory via an OVITO pipeline.
- class wetting_angle_kit.parsers.XYZParser(filepath: str)[source]
Bases:
BaseParserExtended XYZ trajectory parser.
- box_length_max(frame_index: int) float[source]
Return the maximum lattice vector length for a frame.
- Parameters:
frame_index (int) – Frame index.
- Returns:
Max
|a_i|over lattice vectors.- Return type:
float
- box_size_x(frame_index: int) float[source]
Return the length of the first lattice vector for a frame.
- box_size_y(frame_index: int) float[source]
Return the length of the second lattice vector for a frame.
- load_xyz_file() list[dict[str, Any]][source]
Load all frames from the XYZ file into memory using numpy.
- Returns:
Each entry has keys:
symbols,positions,lattice_matrix.- Return type:
list[dict]
- parse(frame_index: int, indices: ndarray | None = None) ndarray[source]
Return Cartesian coordinates for selected atoms in a frame.
- Parameters:
frame_index (int) – Frame index.
indices (ndarray, optional) – Atom indices to select; if None all atoms are returned.
- Returns:
Atom coordinates.
- Return type:
ndarray, shape (M, 3)
- parse_liquid_particles(liquid_particle_types: list[str], frame_index: int) ndarray[source]
Return positions of liquid atoms identified by their atomic symbol.
- Parameters:
liquid_particle_types (sequence[str]) – Symbols identifying liquid atoms.
frame_index (int) – Frame index.
- Returns:
Liquid atom positions.
- Return type:
ndarray, shape (L, 3)
- class wetting_angle_kit.parsers.XYZWallParser(filepath: str, liquid_particle_types: list[str])[source]
Bases:
BaseParserParser extracting wall particle coordinates from an XYZ trajectory.
Wall particles are everything not in
liquid_particle_types; the mask is applied atparse()time over the per-frame symbol array. Theindicesargument ofparse()is treated as 0-based positional indices into the wall-only positions, mirroringAseWallParser.- box_length_max(frame_index: int) float[source]
Return the maximum lattice vector length for a frame.
- box_size_x(frame_index: int) float[source]
Return the length of the first lattice vector for a frame.
- box_size_y(frame_index: int) float[source]
Return the length of the second lattice vector for a frame.
- find_highest_wall_particle(frame_index: int) float[source]
Return the maximum z-coordinate among wall particles for a frame.
- parse(frame_index: int, indices: ndarray | None = None) ndarray[source]
Return wall atom positions for a frame.
- Parameters:
frame_index (int) – Frame index.
indices (ndarray, optional) – 0-based indices into the wall-only positions to further restrict the result; if None all wall atoms are returned.
- Returns:
Wall atom coordinates.
- Return type:
ndarray, shape (M, 3)
- class wetting_angle_kit.parsers.XYZWaterFinder(filepath: str, oxygen_type: str = 'O', hydrogen_type: str = 'H', oh_cutoff: float = 1.2)[source]
Bases:
objectHelper for identifying water oxygen atoms in XYZ trajectories.
This is a standalone helper (not a
BaseParser) because itsparsesignature filters by atomic symbol rather than frame index, which is incompatible with the parser ABC contract.- box_length_max(frame_index: int) float[source]
Return the maximum lattice vector length for a frame.
- Parameters:
frame_index (int) – Frame index.
- Returns:
Max
|a_i|over lattice vectors.- Return type:
float
- get_water_oxygen_indices(frame_index: int) ndarray[source]
Return indices of oxygen atoms bonded to exactly two hydrogens.
- Parameters:
frame_index (int) – Frame index.
- Returns:
Oxygen atom indices belonging to water molecules.
- Return type:
ndarray
- get_water_oxygen_positions(frame_index: int) ndarray[source]
Return positions of water oxygen atoms for a frame.
- Parameters:
frame_index (int) – Frame index.
- Returns:
Oxygen atom positions; empty array if none detected.
- Return type:
ndarray, shape (N, 3)
- load_xyz_file() list[dict[str, Any]][source]
Load frames including the lattice matrix for box-size queries.
- parse(liquid_particle_types: list[str], frame_index: int) ndarray[source]
Return liquid particle coordinates filtering wall types.
- Parameters:
liquid_particle_types (sequence[str]) – Symbols for liquid particles.
frame_index (int) – Frame index.
- Returns:
Liquid atom positions.
- Return type:
ndarray, shape (L, 3)
- wetting_angle_kit.parsers.get_water_finder(filename: str, oxygen_type: Any, hydrogen_type: Any) Any[source]
Return the appropriate water oxygen finder for a given trajectory file.
- Parameters:
filename (str) – Path to trajectory file; extension determines the finder class.
oxygen_type (Any) – Oxygen type identifier (symbol or integer depending on file format).
hydrogen_type (Any) – Hydrogen type identifier (symbol or integer depending on file format).
- Returns:
Finder instance matching the file format.
- Return type:
Analysis
Top-level analyzers
The decomposed TrajectoryAnalyzer.
TrajectoryAnalyzer ties together the five strategy components
that define a contact-angle analysis pipeline:
DropletGeometry— droplet symmetry / internal axis layoutTemporalAggregator— per-frame vs pooled-batch schedulingInterfaceExtractor— atom → interface pointsSurfaceFitter— interface points → contact angleWallDetector— wall plane location
The class extends the shared _BatchedTrajectoryAnalyzer
worker-pool scaffolding by implementing the four extension points
documented there. The per-batch wiring lives in
_process_batch_worker().
The coupled-fit analyzers (CoupledFit2DAnalyzer,
CoupledFit3DAnalyzer) live in their own modules and
share only the worker-pool scaffolding, not this strategy pipeline.
- class wetting_angle_kit.analysis.trajectory.TrajectoryAnalyzer(parser: Any, atom_indices: ndarray | None = None, droplet_geometry: DropletGeometry | str = 'spherical', *, interface_extractor: InterfaceExtractor, surface_fitter: SurfaceFitter, wall_detector: WallDetector | None = None, temporal_aggregator: TemporalAggregator | None = None, precentered: bool = False, wall_atom_indices: ndarray | None = None)[source]
Bases:
_BatchedTrajectoryAnalyzerDecomposed contact-angle analyzer: extractor → wall → fitter.
- Parameters:
parser (BaseParser) – Trajectory parser instance. Only
parser.filepathandparser.frame_count()are read in the parent process; each worker rebuilds its own parser fromfilepath.atom_indices (ndarray, optional) – Indices of the liquid atoms; passed through to the parser’s per-frame
parsecall. Empty by default.droplet_geometry (DropletGeometry or str, default
"spherical") – Either aDropletGeometryinstance or the bare name string. Drives the internal axis convention and the per-slice layout used by the extractor.interface_extractor (InterfaceExtractor) – Composes a
SpaceSampling(built viaSpaceSampling.rays()orSpaceSampling.grid()) with aDensityEstimator(built viaDensityEstimator.gaussian()orDensityEstimator.binning()).surface_fitter (SurfaceFitter) – Built via
SurfaceFitter.slicing()orSurfaceFitter.whole(). Itskindmust match the extractor’s natural output, which is enforced viaInterfaceExtractor.validate_compatibility()at construction.wall_detector (WallDetector, optional) – Built via
WallDetector.min_plus_offset()/WallDetector.explicit()/WallDetector.from_atoms(). Defaults toWallDetector.min_plus_offset(offset=2.0).temporal_aggregator (TemporalAggregator, optional) – Defaults to per-frame analysis (
batch_size=1).precentered (bool, default
False) – Skip per-frame circular-mean PBC recentering. Setting this on a trajectory that does NOT satisfy the precondition will produce wrong results.wall_atom_indices (ndarray, optional) – Required when
wall_detectoris aWallDetector.from_atoms()instance. The analyzer gathers and pools these coordinates per batch and supplies them to the detector viaWallContext.wall_coords.
Coupled-fit contact-angle analyzers.
Two top-level analyzers that solve interface extraction, wall detection, and surface fitting together via a hyperbolic-tangent density model:
CoupledFit2DAnalyzer— seven-parameter fit on a 2D(xi, zi)density grid (radial symmetry assumption).CoupledFit3DAnalyzer— nine-parameter fit on a full 3D(xi, yi, zi)density grid (no symmetry assumption; spherical droplets only — cylinder droplets are rejected at construction).
Both analyzers accept a DensityEstimator strategy that
controls how the per-cell density is computed from pooled atom
positions: DensityEstimator.binning() (top-hat histogram, the
default) or DensityEstimator.gaussian() (3D Gaussian KDE on
the cell centres). Switching to the Gaussian variant trades a small
constant cost per fit for a smooth density field with no per-cell
Poisson noise.
Use these analyzers when you have many frames per batch and want a
single robust estimate; use TrajectoryAnalyzer with
separable strategies for per-frame time resolution.
- class wetting_angle_kit.analysis.coupled_fit.CoupledFit2DAnalyzer(parser: Any, atom_indices: ndarray | None = None, droplet_geometry: DropletGeometry | str = 'spherical', *, grid_params: dict[str, Any] | None = None, density_estimator: DensityEstimator | None = None, initial_params: list[float] | None = None, temporal_aggregator: TemporalAggregator | None = None, precentered: bool = False)[source]
Bases:
_CoupledFitAnalyzerCoupled contact-angle fit on a 2D density grid.
- Parameters:
parser (BaseParser) – Trajectory parser. Only
parser.filepathandparser.frame_count()are read in the parent process; each worker rebuilds its own parser.atom_indices (ndarray, optional) – Indices of the liquid atoms.
droplet_geometry (DropletGeometry or str, default
"spherical") – Either an instance or the bare name string. Determines the per-frame projection onto the(xi, zi)plane: spherical droplets use the in-plane radial coordinatexi = sqrt(x^2 + y^2); cylindrical droplets use the coordinate perpendicular to the cylinder axis.grid_params (dict, optional) – 2D grid spec with keys
"xi_0","xi_f","dx","zi_0","zi_f","dz". The range bounds are honoured exactly; the effective cell width is rounded to fit and may differ slightly from the requesteddx/dz. IfNone, an atom-derived default is used:xi/zispan half the largest in-plane box dimension, withdx/dz= 0.5 Å (half the model’s default interface thicknesst1). A warning is emitted when the default is used.density_estimator (DensityEstimator, optional) – How the per-cell density is computed from the pooled atom positions. Built via
DensityEstimator.binning()(the default, top-hat histogram with geometry-awaredVnormalisation) orDensityEstimator.gaussian()(3D Gaussian KDE evaluated at the cell centres; the same kernelSpaceSampling.rays()/SpaceSampling.grid()withDensityEstimator.gaussian()use). Switching to the Gaussian variant smooths out per-cell Poisson noise — useful on per-frame / small-batch analyses where the histogram density is degenerate.initial_params (list[float], optional) – Initial guess for the seven tanh-model parameters
[rho1, rho2, R_eq, zi_c, zi_0, t1, t2]. Defaults to the values tuned for room-temperature water in the existing_HyperbolicTangentModel2D.temporal_aggregator (TemporalAggregator, optional) – Defaults to a single fully pooled batch (
batch_size=-1) — the coupled fit benefits from as much statistics as possible. Setbatch_size=Nto compute independent angles for eachN-frame block.precentered (bool, default
False) – Skip per-frame circular-mean PBC recentering. Setting this on a trajectory that does NOT satisfy the precondition will produce wrong results.
- class wetting_angle_kit.analysis.coupled_fit.CoupledFit3DAnalyzer(parser: Any, atom_indices: ndarray | None = None, droplet_geometry: DropletGeometry | str = 'spherical', *, grid_params: dict[str, Any] | None = None, density_estimator: DensityEstimator | None = None, initial_params: list[float] | None = None, temporal_aggregator: TemporalAggregator | None = None, precentered: bool = False)[source]
Bases:
_CoupledFitAnalyzerCoupled contact-angle fit on a 3D binned density grid.
- Parameters:
parser (BaseParser) – Trajectory parser. Only
parser.filepathandparser.frame_count()are read in the parent process; each worker rebuilds its own parser.atom_indices (ndarray, optional) – Indices of the liquid atoms.
droplet_geometry (DropletGeometry or str, default
"spherical") – Must be spherical. Cylindrical droplets are rejected at construction because their translational symmetry already collapses the 3D problem onto the 2D one solved byCoupledFit2DAnalyzer.grid_params (dict, optional) – 3D grid spec with keys
"xi_0","xi_f","dx","yi_0","yi_f","dy","zi_0","zi_f","dz". The range bounds are honoured exactly; the effective cell width is rounded to fit. IfNone, an atom-derived default is used (lateral half-box for all axes,dx/dy/dz= 1 Å to keep the 9-parameter NLLS tractable).xi/yiare in the droplet-centred frame (atoms are recentred on the per-frame COM before binning);ziis in the lab frame so the wall position retains physical meaning. IfNone, a heuristic default is used.initial_params (list[float], optional) – Initial guess for the nine tanh-model parameters
[rho1, rho2, R_eq, xi_c, yi_c, zi_c, zi_0, t1, t2].temporal_aggregator (TemporalAggregator, optional) – Defaults to a single fully pooled batch (
batch_size=-1). The 3D density needs more frames than the 2D one for comparable per-cell noise.precentered (bool, default
False) – Skip per-frame circular-mean PBC recentering.
- class wetting_angle_kit.analysis.coupled_fit.DensityEstimator[source]
Bases:
ABCStrategy interface for density estimation.
Concrete instances come from one of the classmethod factories
binning()orgaussian(); the abstract methods are dispatched by the analyzer / extractor that consumes them.- classmethod binning(*, bin_width: float | None = None) DensityEstimator[source]
Top-hat histogram density estimator.
- Parameters:
bin_width (float, optional) – Side length (Å) of the 3D top-hat kernel used by
build_field()for pointwise evaluation (SpaceSampling.rays()). Ignored byevaluate_on_slice(),evaluate_on_3d_grid(),evaluate_2d(), andevaluate_3d()— those consumers derive their cell sizes from the grid spec they’re given. Required only when the estimator is consumed pointwise.
- abstractmethod build_field(atoms: ndarray) DensityFieldProtocol[source]
Pointwise 3D density evaluator on the given atom set.
Returns an object exposing
evaluate(positions)for arbitrary(N, 3)query points. Used bySpaceSampling.rays()to sample density along each ray.The binning estimator requires
bin_widthto have been set on the factory call; callingbuild_field()without one raisesValueError.
- abstractmethod evaluate_2d(*, atoms_pooled: ndarray, n_frames: int, droplet_geometry: DropletGeometry, xi_edges: ndarray, zi_edges: ndarray, box_dimension: float | None) ndarray[source]
Coupled-fit 2D: radial-projected
(xi, zi)density.Returns a
(n_xi, n_zi)array in atoms/ų, averaged across then_framespooled into the batch. For spherical, atoms fold ontoxi = hypot(x, y)with annulardV; for cylinder, atoms fold ontoxi = |x|with cylinder-lengthdV.
- abstractmethod evaluate_3d(*, atoms_pooled: ndarray, n_frames: int, droplet_geometry: DropletGeometry, xi_edges: ndarray, yi_edges: ndarray, zi_edges: ndarray) ndarray[source]
Coupled-fit 3D: Cartesian
(xi, yi, zi)density.Returns a
(n_xi, n_yi, n_zi)array in atoms/ų, averaged across then_framespooled into the batch.Only
sphericalis currently exercised — the 3D coupled-fit analyzer rejects cylinder droplets at construction.
- abstractmethod evaluate_on_3d_grid(atoms: ndarray, x_centers: ndarray, y_centers: ndarray, z_centers: ndarray, *, x_offset: float, y_offset: float) ndarray[source]
3D density on the cell centres of a Cartesian grid.
Returns a
(len(x_centers), len(y_centers), len(z_centers))array. The grid is laterally droplet-centred (x_offset,y_offsetshift the cell coordinates back to the lab frame for evaluation against the lab-frame atoms).
- abstractmethod evaluate_on_slice(atoms: ndarray, slice_center: ndarray, in_plane_axis: ndarray, s_centers: ndarray, z_centers: ndarray, slab_thickness: float) ndarray[source]
2D density on the cell centres of a slice plane.
Returns a
(len(s_centers), len(z_centers))array. The slice plane is defined byslice_center(a 3D point on the plane) andin_plane_axis(a horizontal unit vector defining the radial coordinates).For the Gaussian estimator, the KDE is evaluated at each cell-centre 3D point on the plane. For the binning estimator, atoms inside the slab
|perp| ≤ slab_thickness / 2are histogrammed in(s, z); each cell’s density iscounts / (ds · dz · slab_thickness).
- classmethod gaussian(*, density_sigma: float = 3.0, cutoff_sigma: float = 5.0) DensityEstimator[source]
3D Gaussian KDE density estimator.
- Parameters:
density_sigma (float, default 3.0) – Gaussian kernel width (Å).
cutoff_sigma (float, default 5.0) – Per-atom kernel truncation in units of
density_sigma. Larger values are slower but more accurate in the kernel’s tails.
- kind: ClassVar[str]
kind tag (used in tqdm labels).
Strategy components
The interface-finding subsystem.
The submodule owns everything related to recovering the liquid–vapor
interface from atom positions. An InterfaceExtractor composes
two orthogonal strategy objects:
a
SpaceSampling(built viaSpaceSampling.rays()orSpaceSampling.grid()) that decides where density is evaluated;a
DensityEstimator(built viaDensityEstimator.gaussian()orDensityEstimator.binning()) that decides how it is computed.
Both choices are independent — any sampling can be paired with any density estimator:
extractor = InterfaceExtractor(
sampling=SpaceSampling.rays(
delta_azimuthal=20.0, delta_polar=8.0,
),
density=DensityEstimator.gaussian(density_sigma=3.0),
)
The pairing between the chosen extractor and the analyzer’s
SurfaceFitter is validated at TrajectoryAnalyzer
construction via InterfaceExtractor.validate_compatibility(),
which forwards to SpaceSampling.validate_compatibility().
- class wetting_angle_kit.analysis.interface.InterfaceExtractor(sampling: SpaceSampling, density: DensityEstimator)[source]
Bases:
objectComposes a sampling layout with a density estimator.
- Parameters:
sampling (SpaceSampling) – Space-sampling strategy. Built via
SpaceSampling.rays()orSpaceSampling.grid().density (DensityEstimator) – Density-estimation strategy. Built via
DensityEstimator.gaussian()orDensityEstimator.binning().
Examples
>>> from wetting_angle_kit.analysis import ( ... DensityEstimator, InterfaceExtractor, SpaceSampling, ... ) >>> extractor = InterfaceExtractor( ... sampling=SpaceSampling.rays( ... delta_azimuthal=20.0, delta_polar=8.0, ... ), ... density=DensityEstimator.gaussian(density_sigma=3.0), ... )
- extract(liquid_coordinates: ndarray, center_geom: ndarray, droplet_geometry: DropletGeometry, surface_kind: Literal['slicing', 'whole']) list[ndarray] | ndarray[source]
Build the interface point set for one batch.
Delegates to
SpaceSampling.extract(), threadingself.densitythrough.
- property sampling_kind: Literal['rays', 'grid']
Tag identifying the sampling layout (
"rays"or"grid").
- validate_compatibility(surface_kind: Literal['slicing', 'whole'], droplet_geometry: DropletGeometry) None[source]
Raise if this extractor cannot serve
(surface_kind, geometry).Forwards to
SpaceSampling.validate_compatibility(); the sampling owns the validation rules (e.g.delta_azimuthalis required for slicing-spherical rays).
- class wetting_angle_kit.analysis.interface.SpaceSampling[source]
Bases:
ABCStrategy interface for space-sampling layouts.
Concrete instances come from one of the classmethod factories
rays()orgrid(); the abstractextract()andvalidate_compatibility()methods are dispatched by the composingInterfaceExtractorafter pooling atom positions.- abstractmethod extract(liquid_coordinates: ndarray, center_geom: ndarray, droplet_geometry: DropletGeometry, surface_kind: Literal['slicing', 'whole'], density: DensityEstimator) list[ndarray] | ndarray[source]
Build the interface point set for one batch.
- Parameters:
liquid_coordinates (ndarray, shape (N, 3)) – Pooled liquid-atom coordinates in the internal frame.
center_geom (ndarray, shape (3,)) – Geometric droplet center.
droplet_geometry (DropletGeometry) – Droplet symmetry; drives the per-slice axis choice for slicing modes and the ray-fan / grid layout for whole modes.
surface_kind ({"slicing", "whole"}) – What the downstream
SurfaceFitterwill consume.density (DensityEstimator) – Density-estimation strategy. The sampling delegates per-cell or per-ray-sample density to this strategy.
- Returns:
list[ndarray]of(M_i, 2)per-slice points whensurface_kind="slicing"; a single(N, 3)shell whensurface_kind="whole".- Return type:
InterfaceData
- classmethod grid(*, grid_params: dict[str, Any] | None = None, delta_azimuthal: float | None = 15.0, delta_cylinder: float | None = None) SpaceSampling[source]
Fixed-cell grid sampling layout.
Per-slice in slicing mode: spherical droplets iterate over azimuthal angles
γ ∈ [0°, 180°)controlled bydelta_azimuthal; cylindrical droplets iterate over axial steps controlled bydelta_cylinder. Each slice produces an(s, z)density grid and one iso-contour. Whole mode builds a 3D(x, y, z)grid centred laterally on the droplet COM and runs marching cubes.- Parameters:
grid_params (dict, optional) – Grid spec. For slicing, six keys:
"xi_0","xi_f","dx","zi_0","zi_f","dz".xi_0should be negative for a centred slice that spans both halves of the diameter. For whole, add three more:"yi_0","yi_f","dy"(xi/yi grids are in the droplet-centred lateral frame; zi stays in the lab frame). IfNone(default), the grid is auto-derived per batch from the atom bounding box plus a 5 Å buffer, with cell width set todensity_sigma / 2for Gaussian or2 Åfor binning.delta_azimuthal (float or None, default 15.0) – Azimuthal step (degrees) between slicing planes for
slicing + spherical. Ignored for cylinder geometries and whole-fit modes.delta_cylinder (float, optional) – Step (Å) along the cylinder axis between slicing planes for
slicing + cylinder. Required for that case; ignored otherwise.
- classmethod rays(*, delta_azimuthal: float | None = 15.0, delta_cylinder: float | None = None, n_rays_sphere: int | None = None, delta_polar: float = 8.0, points_per_angstrom: float = 1.0) SpaceSampling[source]
Ray-fan sampling layout.
Required ray-fan parameters depend on the
(surface_kind, droplet_geometry)the sampling is paired with:surface_kind, geometry
required ray params
slicing, spherical
[
delta_azimuthal] (+delta_polar)slicing, cylinder_x/y
delta_cylinder(+delta_polar)whole, spherical
n_rays_spherewhole, cylinder_x/y
delta_cylinder(+delta_polar)- Parameters:
delta_azimuthal (float or None, default 15.0) – Azimuthal step (degrees) between slicing planes for the spherical slicing mode.
Nonedisables the parameter (useful when only cylinder modes are needed).delta_cylinder (float, optional) – Step (Å) along the cylinder axis between slices for the cylinder modes (both slicing and whole).
n_rays_sphere (int, optional) – Total number of rays covering the full sphere for the spherical whole-fit mode. Rays are placed via an equal-area Fibonacci
(cos θ, φ)construction so the angular density is uniform from south to north pole. Full-sphere (rather than upper-hemisphere) coverage is intentional: downward rays from the droplet COM traverse the liquid and hit the wall plane, producing interface points at the wall — that keepsWallDetector.min_plus_offset()consistent with the physical wall position.delta_polar (float, default 8.0) – In-plane ray step (degrees) for every mode that emits rays in the
(x, z)plane (i.e. everything except whole + spherical).points_per_angstrom (float, default 1.0) – Sampling density along each ray (samples per Å).
- abstractmethod validate_compatibility(surface_kind: Literal['slicing', 'whole'], droplet_geometry: DropletGeometry) None[source]
Raise if this sampling cannot serve
(surface_kind, geometry).Called by
TrajectoryAnalyzer.__init__so misconfigurations fail fast at construction instead of at the first batch.
Surface fitters: derive a contact angle from interface points + wall.
A SurfaceFitter consumes an interface point set produced by an
InterfaceExtractor plus a wall z-coordinate produced by a
WallDetector, and returns one BatchResult per call
holding the contact angle and fit diagnostics.
Two fitter kinds are supported:
slicing: one algebraic-circle fit per slice in the slice’s(x, z)plane, then a mean across slices.whole: one algebraic-sphere fit (spherical droplet) or algebraic-cylinder fit (cylindrical droplet) to the 3D interface shell.
Users construct fitters through classmethod factories on the base class:
SurfaceFitter.slicing()
SurfaceFitter.whole(bootstrap_samples=0)
- class wetting_angle_kit.analysis.fitters.FitOutput[source]
Bases:
ABCFrames-less per-batch fit output returned by
SurfaceFitter.fit().The fitter computes the geometric fit and returns one of
SlicingFitOutputorWholeFitOutput. The analyzer then callsto_batch_result()with the batch’s frame indices to produce the user-facingBatchResult— keeping bookkeeping (frames) and computation (the fit) separate.- abstractmethod to_batch_result(frames: list[int]) BatchResult[source]
Attach
framesto this fit output and return a BatchResult.
- class wetting_angle_kit.analysis.fitters.SlicingFitOutput(*, angle: float, z_wall: float, rms_residual: float, angle_std: float, per_slice_angles: ndarray, slice_surfaces: list[ndarray], slice_popts: ndarray, n_slices_total: int, n_slices_used: int)[source]
Bases:
FitOutputOutput of
SurfaceFitter.slicing()for one batch.Carries the same payload as
SlicingBatchResultminusframes. Field semantics are identical; see that class for documentation.- to_batch_result(frames: list[int]) SlicingBatchResult[source]
Attach
framesto this fit output and return a BatchResult.
- class wetting_angle_kit.analysis.fitters.SurfaceFitter[source]
Bases:
ABCAbstract base for contact-angle surface fitters.
Concrete fitters are constructed through the classmethod factories
slicing()andwhole(). Direct subclassing is supported for custom strategies but the factories cover all built-in cases.- abstractmethod fit(interface_data: list[ndarray] | ndarray, z_wall: float, droplet_geometry: DropletGeometry) FitOutput[source]
Fit the contact angle for one batch.
- Parameters:
interface_data (InterfaceData) – Interface point set produced by the
InterfaceExtractor. Per-slice 2D points forkind="slicing"; a 3D shell forkind="whole".z_wall (float) – Wall-plane z-coordinate from the
WallDetector.droplet_geometry (DropletGeometry) – Droplet symmetry; controls the geometric model (circle per slice / sphere / cylinder).
- Returns:
SlicingFitOutputfor slicing fitters,WholeFitOutputfor whole fitters. The analyzer attaches the batch’s frame indices viaFitOutput.to_batch_result()to produce the user-facingBatchResult.- Return type:
- kind: ClassVar[Literal['slicing', 'whole']]
Surface-representation kind this fitter consumes. Set by each concrete subclass; the analyzer matches this against the chosen
InterfaceExtractorat construction time.
- classmethod slicing(*, surface_filter_offset: float = 2.0) SurfaceFitter[source]
Per-slice algebraic circle fits, averaged across slices.
Each slice’s 2D interface points are filtered to
z > z_wall + surface_filter_offset(to exclude wall-adjacent density distortions), an algebraic Taubin circle is fit to the kept points, and the contact angle is the angle of intersection between that circle and the linez = z_wall. The batch angle is the mean over slices;BatchResult.angle_stdis the empirical std across slices.- Parameters:
surface_filter_offset (float, default 2.0) – Vertical offset above
z_wall(Å) below which interface points are excluded from the circle fit. This is distinct from any offset baked into theWallDetector: this offset is a fit-quality knob for the per-slice circle, and the wall detector’s offset (if it uses one, e.g.WallDetector.min_plus_offset()) defines where the wall plane sits.
- abstractmethod validate_compatibility(droplet_geometry: DropletGeometry) None[source]
Raise if this fitter cannot handle
droplet_geometry.Called by
TrajectoryAnalyzer.__init__. The kind compatibility (slicing vs whole) is enforced separately at the analyzer level by matchingSurfaceFitter.kindagainst the extractor’s chosensurface_kind.
- classmethod whole(*, surface_filter_offset: float = 2.0, bootstrap_samples: int = 0) SurfaceFitter[source]
Algebraic sphere or cylinder fit to the 3D interface shell.
Spherical droplets get a sphere fit; cylindrical droplets get a circular-cylinder fit whose axis is parallel to
y(internal frame, post axis-swap forcylinder_x). The contact angle follows from the cap geometry:cos θ = (z_wall - z_center) / R.- Parameters:
surface_filter_offset (float, default 2.0) – Vertical offset above
z_wall(Å) below which shell points are excluded from the geometric fit. Same role as inslicing(): distinct from the wall detector’s offset.bootstrap_samples (int, default 0) – If positive, the fit is repeated on this many bootstrap resamples of the filtered shell, and the resulting std of the angles is reported as
BatchResult.angle_std.0disables bootstrap; the field is thenNonein the returnedWholeBatchResult.
- class wetting_angle_kit.analysis.fitters.WholeFitOutput(*, angle: float, z_wall: float, rms_residual: float, angle_std: float | None, interface_shell: ndarray, popt: ndarray)[source]
Bases:
FitOutputOutput of
SurfaceFitter.whole()for one batch.Carries the same payload as
WholeBatchResultminusframes. Field semantics are identical; see that class for documentation.- to_batch_result(frames: list[int]) WholeBatchResult[source]
Attach
framesto this fit output and return a BatchResult.
Wall-plane detectors used by trajectory analyzers.
A WallDetector returns the z-coordinate of the wall plane
that a SurfaceFitter intersects to compute the contact angle.
Three strategies are supported:
min_plus_offset: take the lowest interface point and shift up by a configurable offset. Cheap and self-contained but picks up thermal noise from the liquid–vapor interface bottom.explicit: use a fixed user-supplied z value. Best when the wall plane is known a priori from the simulation setup.from_atoms: derive z from a pool of wall atom positions (e.g. mean z of the topmost layer). Most physical but requires the analyzer to be told which atoms form the wall.
Users construct detectors through classmethod factories on the base class:
WallDetector.min_plus_offset(offset=2.0)
WallDetector.explicit(z_wall=15.0)
WallDetector.from_atoms(wall_atom_indices=indices)
- class wetting_angle_kit.analysis.wall.WallContext(interface_data: list[ndarray] | ndarray, wall_coords: ndarray | None = None)[source]
Bases:
objectPer-batch data passed to
WallDetector.detect().Wrapping the inputs in a single object keeps the detector method signature forward-compatible: new detectors can read new fields without changing the protocol.
- interface_data
Interface point set produced by the
InterfaceExtractor; format depends on the extractor kind (per-slice 2D points or a 3D shell).- Type:
list[ndarray] or ndarray
- wall_coords
Pooled
(N, 3)positions of wall atoms in the internal coordinate frame, if the analyzer was constructed withwall_atom_indices. Required byfrom_atomsdetectors and unused by the others.- Type:
ndarray, optional
- class wetting_angle_kit.analysis.wall.WallDetector[source]
Bases:
ABCAbstract base for wall-plane detection strategies.
Construct concrete detectors with one of the classmethod factories
min_plus_offset(),explicit(), orfrom_atoms(). Direct subclassing is supported for custom strategies but the factories cover all built-in cases.- abstractmethod detect(ctx: WallContext) float[source]
Return the wall-plane z-coordinate for one batch.
- Parameters:
ctx (WallContext) – Per-batch data; see
WallContext.- Returns:
Wall-plane z in the internal coordinate frame (Å).
- Return type:
float
- classmethod explicit(z_wall: float) WallDetector[source]
Use a fixed wall z-coordinate.
- Parameters:
z_wall (float) – Wall-plane z in the internal coordinate frame (Å).
- classmethod from_atoms(wall_atom_indices: ndarray, method: Literal['max_z', 'mean_top_layer'] = 'mean_top_layer', top_layer_tolerance: float = 1.0) WallDetector[source]
Derive wall z from a set of wall atom positions.
The analyzer must be constructed with the matching
wall_atom_indicesso the wall atoms are gathered and supplied throughWallContext.wall_coords.- Parameters:
wall_atom_indices (ndarray) – Indices of the atoms that form the wall.
method ({"max_z", "mean_top_layer"}, default "mean_top_layer") – How to reduce wall atom z values to a single plane.
"max_z"uses the highest wall atom z; cheap but noisy."mean_top_layer"averages over all atoms withintop_layer_toleranceÅ of the maximum, smoothing thermal motion.top_layer_tolerance (float, default 1.0) – Vertical window (Å) defining the “top layer” for
method="mean_top_layer". Ignored for"max_z".
- classmethod min_plus_offset(offset: float = 2.0) WallDetector[source]
Take the lowest interface point and shift up by
offset.- Parameters:
offset (float, default 2.0) – Vertical shift (Å) added to the minimum z to skip the wall-adjacent density spike. The default of 2.0 Å matches the slicing analyzer’s historical behaviour for water on silica-like surfaces; tune for other systems.
Temporal aggregation across frames for trajectory analysis.
A TemporalAggregator groups frame indices into batches. Each
batch is later processed by an analyzer as a single fitting unit,
producing one contact angle estimate per batch.
The batch_size parameter controls the time-vs-statistics trade-off:
batch_size=1(default) — per-frame analysis. Produces a time series with one angle per frame; statistics come from frame-to-frame variation.batch_size=N(N > 1) — pool consecutive groups ofNframes together before fitting. Reduces thermal noise per fit at the cost of time resolution.batch_size=-1— fully pooled. Every requested frame goes into a single batch, producing one angle estimate for the trajectory.
- class wetting_angle_kit.analysis.temporal.TemporalAggregator(batch_size: int = 1)[source]
Bases:
objectGroup frame indices into batches for per-batch surface fitting.
Designed to be held by a
TrajectoryAnalyzerand driven from insideanalyze(), which supplies the frame indices to walk. Standalone use is fine for inspection (e.g. previewing batch boundaries) but the caller must always provideframe_range.- Parameters:
batch_size (int, default 1) – Number of consecutive frames pooled per surface fit.
batch_size=1(the default) gives per-frame analysis: each frame is its own batch. Larger values pool consecutive groups of frames, trading time resolution for statistics; the last batch is shorter if the range isn’t evenly divisible.batch_size=-1is the “all” sentinel: every supplied frame is pooled into a single batch.
- iter_batches(frame_range: list[int]) Iterator[list[int]][source]
Yield successive lists of frame indices, one per fitting unit.
- Parameters:
frame_range (list[int]) – The frame indices to distribute. The analyzer normally populates this with
range(parser.frame_count())or with a caller-supplied subset; the aggregator only groups what it is given and never consults the parser itself. May be empty (no batches yielded).- Yields:
list[int] – One batch of frame indices. Order within and across batches preserves the order of
frame_range.
- n_batches(n_frames: int) int[source]
Return the number of batches that would be yielded.
Useful for sizing progress bars before iteration starts.
- Parameters:
n_frames (int) – Length of the
frame_rangethat would be passed toiter_batches().- Returns:
Number of batches the aggregator will produce for that input.
- Return type:
int
Droplet symmetry and the internal axis convention.
Every analyzer in wetting_angle_kit.analysis operates on a
DropletGeometry instance. The class normalises the three
supported cases (spherical, cylinder_x, cylinder_y) and
exposes a single helper, to_internal_coords(), that downstream
code can use to assume the cylinder axis is always y.
User-facing APIs accept either a DropletGeometry instance or
the bare string name; DropletGeometry.coerce() is the canonical
entry point that performs the conversion.
- class wetting_angle_kit.analysis.geometry.DropletGeometry(name: Literal['spherical', 'cylinder_x', 'cylinder_y'])[source]
Bases:
objectDroplet symmetry descriptor with axis-layout helpers.
Three cases are supported:
spherical: the droplet is a 3D cap with no preferred horizontal axis. Rays sweep over the upper hemisphere(theta, phi).cylinder_y: the droplet is a ridge whose translational symmetry axis isy. In-plane analysis happens in(x, z)and slices are taken at successiveypositions. No internal axis swap.cylinder_x: the droplet is a ridge whose translational symmetry axis isx. A[1, 0, 2]swap is applied at the analyzer boundary so every downstream routine can assume the cylinder axis isyinternally. The swap is self-inverse, so the same helper maps internal coordinates back to user coordinates.
Picking
cylinder_xvscylinder_yPick the one whose name matches your trajectory’s lab-frame axis along which the ridge is invariant:
If your dump file’s atoms are uniformly distributed along
y(i.e. the simulation box’sydirection is the periodic cylinder axis), pass"cylinder_y".If the same situation holds along
xinstead, pass"cylinder_x".
The two are not interchangeable — picking the wrong one is the cylinder analogue of confusing the in-plane radial axis with the cylinder axis. Symptoms of a mismatch: the slicing fitter iterates over the wrong axis (slicing planes go across the ridge instead of along it), so each “slice” sees almost no atoms and the per-slice circle fit either NaNs out or recovers a non-physical angle.
Internally everything happens in the
cylinder_yframe:cylinder_xsimply applies a self-inversex↔ycolumn swap at the parser/analyzer boundary so all downstream extractors, fitters, and visualisers can assume the cylinder axis isy. No analysis logic is duplicated between the two cases — they’re distinguished only by where the swap is (or isn’t) applied.If you’re not sure which axis your trajectory uses, the safe diagnostic is to load one frame, plot atom positions, and look at which lateral coordinate the droplet spans the full box.
- classmethod coerce(value: DropletGeometry | Literal['spherical', 'cylinder_x', 'cylinder_y'] | str) DropletGeometry[source]
Return a
DropletGeometryfor either an instance or a name.- Parameters:
value (DropletGeometry or str) – Either an existing instance (returned unchanged) or one of the bare name strings
"spherical","cylinder_x","cylinder_y".- Return type:
- property cylinder_axis: Literal['x', 'y'] | None
User-frame axis along which the cylinder extends, or
None.
- to_internal_coords(coords: ndarray) ndarray[source]
Map coordinates from the user frame to the internal frame.
For
cylinder_xthis applies the[1, 0, 2]swap so the cylinder axis ends up on theycolumn. Spherical andcylinder_yare returned unchanged. Accepts any array whose last axis has length 3 (a single point(3,)or a batch(..., 3)).
- to_user_coords(coords: ndarray) ndarray[source]
Map coordinates from the internal frame back to the user frame.
Mirror of
to_internal_coords(): applies the[1, 0, 2]swap forcylinder_x(which is its own inverse), and returns the input unchanged forsphericalandcylinder_y.
- wetting_angle_kit.analysis.geometry.DropletGeometryName
Public type alias for the three accepted droplet geometry names.
alias of
Literal[‘spherical’, ‘cylinder_x’, ‘cylinder_y’]
Results dataclasses
In-memory containers for trajectory analyzer outputs.
The unified TrajectoryAnalyzer returns TrajectoryResults
holding one BatchResult per batch produced by the
TemporalAggregator. The specific BatchResult subclass
depends on the SurfaceFitter kind:
slicing fitters →
SlicingBatchResultwhole fitters →
WholeBatchResult
The two coupled-fit analyzers — CoupledFit2DAnalyzer and
CoupledFit3DAnalyzer — each return their own results type
(CoupledFit2DResults, CoupledFit3DResults).
They carry density grids plus coupled-fit parameters and are therefore
not part of the TrajectoryResults hierarchy.
- class wetting_angle_kit.analysis.results.BatchResult(*, frames: list[int], angle: float, z_wall: float, rms_residual: float, angle_std: float | None = None)[source]
Bases:
objectCommon fields shared by all per-batch trajectory results.
All fields are keyword-only so that subclasses can interleave their own fields with the parent’s defaulted ones without ordering constraints.
- frames
Frame indices pooled into this batch.
- Type:
list[int]
- angle
Representative contact angle for the batch (degrees). For slicing fits this is the mean across slices; for whole fits it is the single fitted angle.
- Type:
float
- z_wall
Wall-plane z used by the surface fitter for this batch (Å).
- Type:
float
- rms_residual
Aggregate fit residual (Å). For slicing fits, an aggregate of the per-slice circle-fit residuals; for whole fits, the single sphere/cylinder-fit residual.
- Type:
float
- angle_std
Within-batch standard deviation of the contact angle (degrees), describing the spread of the per-batch
angle. For slicing fits, thenanstdofper_slice_angles(always populated). For whole fits, the bootstrap std when the fitter was constructed withbootstrap_samples > 0, otherwiseNone.- Type:
float, optional
- class wetting_angle_kit.analysis.results.CoupledFit2DBatchResult(frames: list[int], angle: float, model_params: dict[str, float], xi_grid: ndarray, zi_grid: ndarray, density: ndarray)[source]
Bases:
objectPer-batch result from
CoupledFit2DAnalyzer.- frames
Frame indices pooled into this batch.
- Type:
list[int]
- angle
Contact angle (degrees) from the 2D coupled tanh-model fit.
- Type:
float
- model_params
Fitted parameters of the 2D hyperbolic tangent model; keys are
"rho1","rho2","R_eq","zi_c","zi_0","t1","t2".- Type:
dict[str, float]
- xi_grid
In-plane grid-cell centers (Å).
- Type:
ndarray
- zi_grid
Vertical grid-cell centers (Å).
- Type:
ndarray
- density
(len(xi_grid), len(zi_grid))density sampled on the grid.- Type:
ndarray
- class wetting_angle_kit.analysis.results.CoupledFit2DResults(batches: list[~wetting_angle_kit.analysis.results.CoupledFit2DBatchResult], method_metadata: dict[str, ~typing.Any] = <factory>)[source]
Bases:
_AngleResultsMixinIn-memory results of a
CoupledFit2DAnalyzer.analyzerun.- batches
Per-batch results, in the order produced by the aggregator.
- Type:
list[CoupledFit2DBatchResult]
- method_metadata
Free-form descriptor (droplet geometry, grid params, initial parameters, batch size).
- Type:
dict
- class wetting_angle_kit.analysis.results.CoupledFit3DBatchResult(frames: list[int], angle: float, model_params: dict[str, float], xi_grid: ndarray, yi_grid: ndarray, zi_grid: ndarray, density: ndarray)[source]
Bases:
objectPer-batch result from
CoupledFit3DAnalyzer.Only meaningful for spherical droplets; cylindrical droplets carry a translational symmetry along the cylinder axis that the 2D analyzer already exploits, so
CoupledFit3DAnalyzerrejects non-spherical geometries at construction.- frames
Frame indices pooled into this batch.
- Type:
list[int]
- angle
Contact angle (degrees) from the 3D coupled tanh-model fit.
- Type:
float
- model_params
Fitted parameters of the 3D hyperbolic tangent model; keys are
"rho1","rho2","R_eq","xi_c","yi_c","zi_c","zi_0","t1","t2". The droplet horizontal centersxi_c/yi_care reported even if the underlying fit fixes them to zero by symmetry.- Type:
dict[str, float]
- xi_grid
x grid-cell centers (Å).
- Type:
ndarray
- yi_grid
y grid-cell centers (Å).
- Type:
ndarray
- zi_grid
Vertical grid-cell centers (Å).
- Type:
ndarray
- density
(len(xi_grid), len(yi_grid), len(zi_grid))density sampled on the 3D grid.- Type:
ndarray
- class wetting_angle_kit.analysis.results.CoupledFit3DResults(batches: list[~wetting_angle_kit.analysis.results.CoupledFit3DBatchResult], method_metadata: dict[str, ~typing.Any] = <factory>)[source]
Bases:
_AngleResultsMixinIn-memory results of a
CoupledFit3DAnalyzer.analyzerun.- batches
Per-batch results, in the order produced by the aggregator.
- Type:
list[CoupledFit3DBatchResult]
- method_metadata
Free-form descriptor (droplet geometry — always spherical for this analyzer, grid params, initial parameters, batch size).
- Type:
dict
- class wetting_angle_kit.analysis.results.SlicingBatchResult(*, frames: list[int], angle: float, z_wall: float, rms_residual: float, angle_std: float | None = None, per_slice_angles: ndarray, slice_surfaces: list[ndarray], slice_popts: ndarray, n_slices_total: int, n_slices_used: int)[source]
Bases:
BatchResultPer-batch result from a slicing-kind surface fitter.
All per-slice arrays are full length (one entry per attempted slice) and index-aligned: a slice that produced no valid contact angle (empty, too few points, degenerate circle fit, or a circle that does not reach the wall) is marked
nanrather than dropped, so attrition is visible and the slice index is preserved.The inherited
BatchResult.anglefield stores the mean across slices (nanmean). Usemedian_anglefor the median, which is more robust to outlier slices.- per_slice_angles
(n_slices_total,)array of per-slice contact angles (degrees), withnanfor slices that produced no angle.BatchResult.angleisnanmeanof this array andBatchResult.angle_stditsnanstd.- Type:
ndarray
- slice_surfaces
One
(M_i, 2)array per slice of interface points in the slice(x, z)plane (kept for every slice, including those that produced no angle).- Type:
list[ndarray]
- slice_popts
(n_slices_total, 4)array of fitted circle parameters per slice; columns[xc, zc, R, z_wall]. Rows for slices with no valid fit arenan.- Type:
ndarray
- n_slices_total
Number of slices the extractor produced for this batch.
- Type:
int
- n_slices_used
Number of those slices that produced a valid contact angle (the count of non-
nanentries inper_slice_angles).n_slices_used < n_slices_totalsignals per-slice attrition.- Type:
int
- property median_angle: float
Median contact angle across slices (degrees).
More robust than
angle(the mean) when one or two slices are outliers — e.g. due to asymmetric density near the periodic boundary.nanslices are ignored.
- class wetting_angle_kit.analysis.results.TrajectoryResults(batches: list[~wetting_angle_kit.analysis.results.BatchResult], method_metadata: dict[str, ~typing.Any] = <factory>)[source]
Bases:
_AngleResultsMixinIn-memory results of a
TrajectoryAnalyzer.analyzerun.Holds one
BatchResultper batch produced by theTemporalAggregator. Within a single Results object all batches share the same subclass (SlicingBatchResultorWholeBatchResult), determined by the analyzer’sSurfaceFitterkind.- batches
Per-batch results, in the order produced by the aggregator.
- Type:
list[BatchResult]
- method_metadata
Free-form descriptor of the analyzer configuration (kind, droplet geometry, batch size, …) for downstream plotting / serialization.
- Type:
dict
- class wetting_angle_kit.analysis.results.WholeBatchResult(*, frames: list[int], angle: float, z_wall: float, rms_residual: float, angle_std: float | None = None, interface_shell: ndarray, popt: ndarray)[source]
Bases:
BatchResultPer-batch result from a whole-kind surface fitter.
- interface_shell
(N, 3)array of interface points used in the fit, in the internal(x, y, z)frame.- Type:
ndarray
- popt
Fitted shape parameters extended by the wall plane. Spherical fitter:
[xc, yc, zc, R, z_wall]. Cylinder fitter:[xc, zc, R, z_wall].- Type:
ndarray
Visualisation
- class wetting_angle_kit.visualization.AngleEvolutionPlotter(results: Any, *, label: str = 'trajectory', timestep: float = 1.0, time_unit: str = 'ps', stat: Literal['median', 'mean'] = 'median', method_name: str | None = None)[source]
Bases:
BaseTrajectoryPlotterPlot per-batch contact-angle evolution across a trajectory.
- Parameters:
results – A
*Resultsobject exposing.batcheswith.angleand.frames(i.e.TrajectoryResults,CoupledFit2DResults, orCoupledFit3DResults). For per-frame analyses useTemporalAggregatorwithbatch_size=1; for pooled batches the evolution is shown per batch with each x-value at the pooled-frames midpoint.label (str, default
"trajectory") – Display label used in legend entries and inTrajectoryStats.timestep (float, default 1.0) – Time between two consecutive frames in the trajectory file (dump interval × MD integration timestep, not the integration timestep itself). Applied to
framesto produce the x-axis.time_unit (str, default
"ps") – Unit shown on the x-axis label.stat ({“median”, “mean”}, default
"median") – Per-batch central-tendency aggregation across slices for slicing results. Ignored for other result shapes (where only a singleBatchResult.angleis defined).method_name (str, optional) – Free-form method tag used in
TrajectoryStats. Defaults to the underlying analyzer’s class name when inferable.
- plot(*, per_frame_std: bool = True, running_mean: bool = True, title: str | None = None, save_path: str | None = None) Figure[source]
Build the angle-evolution figure.
- Parameters:
per_frame_std (bool, default True) – If
True, draw a transparent±σband around the per-batch curve using the within-batch standard deviation (per-slice scatter for slicing fits, bootstrap σ for whole fits, otherwise no band).running_mean (bool, default True) – If
True, overlay the cumulative running mean of the per-batch central tendency as a dashed line, plus a transparent±σband of that cumulative series.title (str, optional) – Figure title. Defaults to a
"Contact angle evolution ({stat})"string.save_path (str, optional) – If provided, also write the figure to standalone HTML.
- Returns:
Figure with the per-batch line (always), the within-batch band (when
per_frame_stdand the batches expose a finiteangle_std), and the running mean line + its cumulative band (whenrunning_mean).- Return type:
plotly.graph_objects.Figure
- summary() list[TrajectoryStats][source]
Return per-trajectory summary statistics.
- class wetting_angle_kit.visualization.BaseTrajectoryPlotter[source]
Bases:
ABCAbstract base for trajectory plotters.
Subclasses own their own data layout (per-method result containers, directories, etc.) and must implement
summary()returning oneTrajectoryStatsper trajectory.- abstractmethod summary() list[TrajectoryStats][source]
Return per-trajectory summary statistics.
- class wetting_angle_kit.visualization.DensityContourPlotter(source: Any, *, label: str = 'trajectory', colorscale: str = 'Jet')[source]
Bases:
objectPlot a binned density field with the fitted cap and wall overlaid.
- Parameters:
source – Single batch or full results object as listed in the module docstring.
label (str, default
"trajectory") – Display label used in the figure title.colorscale (str, default
"Jet") – Plotly colorscale for the density contour.
- plot(*, title: str | None = None, save_path: str | None = None) Figure[source]
Build the density contour figure.
- Parameters:
title (str, optional) – Figure title. Defaults to a
"Density field — {label} (batch_descriptor)"string, where the batch descriptor names the batch when the source is a single batch and"averaged"when it is a full results object.save_path (str, optional) – If provided, write the figure to standalone HTML.
- Returns:
Contour + dashed fitted cap + dotted wall line.
- Return type:
plotly.graph_objects.Figure
- plot_3d_isosurface(*, n_levels: int = 10, show_fit: bool = True, title: str | None = None, save_path: str | None = None) Figure[source]
3D isosurface of the density field with a density-threshold slider.
Only accepts
CoupledFit3DBatchResultorCoupledFit3DResultssources (the full 3D density grid is required).- Parameters:
n_levels (int, default 10) – Number of iso-density levels exposed in the slider.
show_fit (bool, default True) – If
True, overlay the fitted sphere as a black dashed wireframe (meridians + parallels).title (str, optional) – Figure title. Defaults to
"Isosurface ρ ≥ ... — {label}".save_path (str, optional) – If provided, write the figure to standalone HTML.
- Returns:
3D isosurface with a wall plane and a density slider.
- Return type:
plotly.graph_objects.Figure
- class wetting_angle_kit.visualization.DropletSlicePlotter(center: bool = True)[source]
Bases:
objectInteractive Plotly slice visualization with toggleable layers.
- plot_surface_points(oxygen_position: ndarray, surface_data: list[ndarray], popt: Sequence[float], wall_coords: ndarray, alpha: float | None = None, y_com: float | None = None, pbc_y: float | None = None, show_water: bool = True, show_surface: bool = True, show_circle: bool = True, show_tangent: bool = True, show_wall: bool = True) Any[source]
Create interactive Plotly figure for a single frame slice.
- Parameters:
oxygen_position (ndarray (N, 3)) – Oxygen atom coordinates.
surface_data (list[array]) – List of surface contours for selected slice.
popt (sequence) – Fitted circle parameters (x_center, z_center, radius, extra).
wall_coords (ndarray (M, 3)) – Wall particle coordinates.
alpha (float, optional) – Contact angle for tangent construction.
y_com (float, optional) – Mean y used for slicing; computed if None.
pbc_y (float, optional) – Y box length for periodic slicing.
show_water (bool) – Layer visibility toggles.
show_surface (bool) – Layer visibility toggles.
show_circle (bool) – Layer visibility toggles.
show_tangent (bool) – Layer visibility toggles.
show_wall (bool) – Layer visibility toggles.
- Returns:
Configured figure object (not saved).
- Return type:
plotly.graph_objects.Figure
- class wetting_angle_kit.visualization.TrajectoryStats(method_name: str, label: str, mean_surface_area: float, mean_contact_angle: float, std_contact_angle: float, n_samples: int)[source]
Bases:
objectSummary statistics for a single contact-angle trajectory.
A plotter’s
.summary()method returns this dataclass so callers can both display the block (print(stats)) and reuse the underlying numbers programmatically.- method_name
Name of the analysis method (e.g.
"Slicing Analysis").- Type:
str
- label
Display label identifying the trajectory.
- Type:
str
- mean_surface_area
Mean droplet/cap surface area in Ų.
- Type:
float
- mean_contact_angle
Mean contact angle in degrees.
- Type:
float
- std_contact_angle
Standard deviation of the contact angle in degrees.
- Type:
float
- n_samples
Number of samples (frames or batches) contributing to the means.
- Type:
int