From 3eef86cdfe4f318511c08adfd73b1fa9d8ecb4a2 Mon Sep 17 00:00:00 2001 From: mayukhkundu14 <79770572+mayukh33@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:38:54 -0600 Subject: [PATCH 1/7] Documentation first draft --- python/flyft/dynamics.py | 260 +++++++++++++++++++++++++++++++++++ python/flyft/functional.py | 254 ++++++++++++++++++++++++++++++++++ python/flyft/parameter.py | 107 +++++++++++++++ python/flyft/solver.py | 60 ++++++++ python/flyft/state.py | 275 +++++++++++++++++++++++++++++++++++++ 5 files changed, 956 insertions(+) diff --git a/python/flyft/dynamics.py b/python/flyft/dynamics.py index 9ff8fdb..f5e9142 100644 --- a/python/flyft/dynamics.py +++ b/python/flyft/dynamics.py @@ -4,11 +4,43 @@ class Flux(mirror.Mirror, mirrorclass=_flyft.Flux): + """Base class for computing particle fluxes. + + `Flux` computes the flux of particles for different confining geometries. + The flux is used by integrators to update density fields over time. + + All flux classes must implement the `compute` method to calculate fluxes + for all particle types in the system. + """ + compute = mirror.Method() fluxes = mirror.WrappedProperty(Fields) class CompositeFlux(Flux, CompositeMixin, mirrorclass=_flyft.CompositeFlux): + """Composite flux combining multiple flux components. + + A `CompositeFlux` allows combining multiple flux objects into a single + flux calculation. + + Parameters + ---------- + objects : list, optional + List of flux objects to combine. Can be set later using the `objects` + attribute. + + Example + ------- + Create a composite flux from multiple components: + + .. code-block:: python + + diffusive_flux_1 = flyft.dynamics.BrownianDiffusiveFlux() + diffusive_flux_2 = flyft.dynamics.BrownianDiffusiveFlux() + composite = flyft.dynamics.CompositeFlux([diffusive_flux_1, diffusive_flux_2]) + + """ + def __init__(self, objects=None): super().__init__() if objects is not None: @@ -16,15 +48,104 @@ def __init__(self, objects=None): class BrownianDiffusiveFlux(Flux, mirrorclass=_flyft.BrownianDiffusiveFlux): + """Brownian diffusive flux for particle transport. + + Computes particle flux due to Brownian diffusion, including both ideal + (Fickian) diffusion and contributions from gradients in the excess chemical + potential and external potentials. The flux is given by: + + .. math:: + \\mathbf{J} = -D \\left( \\nabla \\rho + \\rho \\nabla \\mu_{\\text{ex}} + \\rho \\nabla V \\right) + + where :math:`D` is the diffusivity, :math:`\\rho` is the density, + :math:`\\mu_{\\text{ex}}` is the excess chemical potential, and :math:`V` + is the external potential. + + Attributes + ---------- + diffusivities : dict + Dictionary mapping particle type names to their diffusivity values. + + Example + ------- + Create a Brownian diffusive flux and set diffusivities: + + .. code-block:: python + + flux = flyft.dynamics.BrownianDiffusiveFlux() + flux.diffusivities['A'] = 1.0 + flux.diffusivities['B'] = 0.5 + + """ + diffusivities = mirror.WrappedProperty(mirror.MutableMapping) class RPYDiffusiveFlux(Flux, mirrorclass=_flyft.RPYDiffusiveFlux): + """Rotne-Prager-Yamakawa (RPY) diffusive flux with hydrodynamic interactions. + + Computes particle flux including hydrodynamic interactions between particles + using the Rotne-Prager-Yamakawa mobility tensor for hard spheres. For particles + i and j separated by distance r > d (where d = (a_i + a_j)): + + .. math:: + \\mathbf{M}_{ij} = \\frac{1}{8\\pi\\eta r} \\left[\\left(1 + \\frac{a_i^2+a_j^2}{3r^2}\\right)\\mathbf{I} + + \\left(1 - \\frac{a_i^2+ a_j^2}{r^2}\\right)\\hat{\\mathbf{r}}\\hat{\\mathbf{r}} \\right] + + where :math:`\\eta` is the fluid viscosity, :math:`a_i` and :math:`a_j` are the + particle radii, :math:`r` is the distance between particles, :math:`\\mathbf{I}` + is the identity tensor, and :math:`\\hat{\\mathbf{r}}` is the unit vector connecting + the particle centers. + + Attributes + ---------- + diameters : dict + Dictionary mapping particle type names to their diameters. + viscosity : float + Fluid viscosity. + + Example + ------- + Create an RPY diffusive flux and set parameters: + + .. code-block:: python + + flux = flyft.dynamics.RPYDiffusiveFlux() + flux.diameters['A'] = 1.0 + flux.diameters['B'] = 2.0 + flux.viscosity = 1.0 + + """ + diameters = mirror.WrappedProperty(mirror.MutableMapping) viscosity = mirror.Property() class Integrator(mirror.Mirror, mirrorclass=_flyft.Integrator): + """Base class for time integration of density fields. + + An `Integrator` advances the system state in time by applying fluxes + computed from the grand potential and current state. It supports both + fixed and adaptive timestep schemes for stability and efficiency. + + All integrator classes must implement specific integration schemes + (explicit, implicit, etc.) while sharing common functionality for + timestep management and adaptive stepping. + + Attributes + ---------- + timestep : float + Integration timestep. + adaptive : bool + Whether to use adaptive timestep control. + adapt_delay : float + Time delay before starting adaptive timestep control. + adapt_tolerance : float + Error tolerance for adaptive timestep control. + adapt_minimum : float + Minimum allowed timestep for adaptive control. + """ + advance = mirror.Method() timestep = mirror.Property() adaptive = mirror.Property() @@ -33,6 +154,30 @@ class Integrator(mirror.Mirror, mirrorclass=_flyft.Integrator): adapt_minimum = mirror.Property() def use_adaptive(self, delay=0, tolerance=1.0e-8, minimum=1.0e-8): + """Enable adaptive timestep control. + + Configures the integrator to use adaptive timestep control based on + error estimation. The timestep is automatically adjusted to maintain + the specified error tolerance. + + Parameters + ---------- + delay : float, optional + Time delay before adaptive control begins. Default is 0. + tolerance : float, optional + Error tolerance for timestep adjustment. Default is 1e-8. + minimum : float, optional + Minimum allowed timestep. Default is 1e-8. + + Example + ------- + Enable adaptive timestep control: + + .. code-block:: python + + integrator.use_adaptive(delay=10.0, tolerance=1e-6, minimum=1e-10) + + """ self.adaptive = True self.adapt_delay = delay self.adapt_tolerance = tolerance @@ -42,11 +187,82 @@ def use_adaptive(self, delay=0, tolerance=1.0e-8, minimum=1.0e-8): class CrankNicolsonIntegrator( Integrator, FixedPointAlgorithmMixin, mirrorclass=_flyft.CrankNicolsonIntegrator ): + """Crank-Nicolson time integrator with second-order accuracy. + + Implements the Crank-Nicolson scheme, which is second-order accurate in time + and unconditionally stable. The scheme uses an implicit trapezoidal rule + that averages the flux at the current and next timesteps: + + .. math:: + \\rho^{n+1} = \\rho^n + \\frac{\\Delta t}{2} \\left( F[\\rho^n] + F[\\rho^{n+1}] \\right) + + where :math:`F[\\rho]` represents the flux evaluation. Since this is an + implicit method, it requires iterative solution at each timestep. + + Parameters + ---------- + timestep : float + Integration timestep. + mix_parameter : float + Mixing parameter for fixed-point iteration convergence. + max_iterations : int + Maximum number of iterations for implicit solve. + tolerance : float + Convergence tolerance for implicit solve. + + Example + ------- + Create a Crank-Nicolson integrator: + + .. code-block:: python + + integrator = flyft.dynamics.CrankNicolsonIntegrator( + timestep=0.01, + mix_parameter=0.1, + max_iterations=100, + tolerance=1e-8 + ) + + """ + def __init__(self, timestep, mix_parameter, max_iterations, tolerance): super().__init__(timestep, mix_parameter, max_iterations, tolerance) class ExplicitEulerIntegrator(Integrator, mirrorclass=_flyft.ExplicitEulerIntegrator): + """Explicit Euler time integrator with first-order accuracy. + + Implements the explicit (forward) Euler method, which is first-order + accurate in time and conditionally stable. The scheme uses the current + flux to advance the density: + + .. math:: + \\rho^{n+1} = \\rho^n + \\Delta t \\, F[\\rho^n] + + where :math:`F[\\rho]` represents the flux evaluation. This explicit + method is simple and efficient but requires small timesteps for stability. + + Parameters + ---------- + timestep : float + Integration timestep. Must be sufficiently small for stability. + + Example + ------- + Create an explicit Euler integrator: + + .. code-block:: python + + integrator = flyft.dynamics.ExplicitEulerIntegrator(timestep=0.001) + + Note + ---- + The explicit Euler method has stability constraints that depend on the + diffusivity and mesh spacing. Typically, the timestep must satisfy + :math:`\\Delta t < \\Delta x^2 / (2D)` where :math:`\\Delta x` is the + mesh spacing and :math:`D` is the diffusivity. + """ + def __init__(self, timestep): super().__init__(timestep) @@ -54,5 +270,49 @@ def __init__(self, timestep): class ImplicitEulerIntegrator( Integrator, FixedPointAlgorithmMixin, mirrorclass=_flyft.ImplicitEulerIntegrator ): + """Implicit Euler time integrator with first-order accuracy. + + Implements the implicit (backward) Euler method, which is first-order + accurate in time and unconditionally stable. The scheme uses the flux + at the next timestep to advance the density: + + .. math:: + \\rho^{n+1} = \\rho^n + \\Delta t \\, F[\\rho^{n+1}] + + where :math:`F[\\rho]` represents the flux evaluation. Since this is an + implicit method, it requires iterative solution at each timestep but + offers superior stability compared to explicit methods. + + Parameters + ---------- + timestep : float + Integration timestep. + mix_parameter : float + Mixing parameter for fixed-point iteration convergence. + max_iterations : int + Maximum number of iterations for implicit solve. + tolerance : float + Convergence tolerance for implicit solve. + + Example + ------- + Create an implicit Euler integrator: + + .. code-block:: python + + integrator = flyft.dynamics.ImplicitEulerIntegrator( + timestep=0.1, + mix_parameter=0.1, + max_iterations=100, + tolerance=1e-8 + ) + + Note + ---- + The implicit Euler method is unconditionally stable, allowing larger + timesteps than explicit methods. However, it requires solving a nonlinear + system at each timestep, which can be computationally expensive. + """ + def __init__(self, timestep, mix_parameter, max_iterations, tolerance): super().__init__(timestep, mix_parameter, max_iterations, tolerance) diff --git a/python/flyft/functional.py b/python/flyft/functional.py index 520831a..168a6b3 100644 --- a/python/flyft/functional.py +++ b/python/flyft/functional.py @@ -4,22 +4,97 @@ class Functional(mirror.Mirror, mirrorclass=_flyft.Functional): + r"""Base class for free energy functionals in density functional theory. + + `Functional` computes the free energy contribution and its functional + derivatives with respect to the density fields. + + All functionals implement the `compute` method to evaluate both the total + free energy value and the derivatives (chemical potential contributions) + for all particle types in the system. + + Attributes + ---------- + derivatives : Fields + Functional derivatives (chemical potential contributions) for each particle type. + value : float + Total free energy contribution from this functional. + """ + derivatives = mirror.WrappedProperty(Fields) value = mirror.Property() _compute = mirror.Method(mirrorname="compute") def compute(self, state, value=True): + """Compute the functional and its derivatives. + + Parameters + ---------- + state : State + System state containing density fields and mesh information. + value : bool, optional + Whether to compute the total free energy value. Default is True. + """ self._compute(state, value) class BoublikHardSphere(Functional, mirrorclass=_flyft.BoublikHardSphereFunctional): + r"""BMCSL hard sphere functional for excess free energy of hard spheres + systems. + + Implements the BMCSL equation of state for hard sphere mixtures using + local density approximations (LDA). + + Attributes + ---------- + diameters : dict + Dictionary mapping particle type names to their hard sphere diameters. + + Example + ------- + Apply BMCSL hard sphere free-energy functional: + + .. code-block:: python + + functional = flyft.functional.BoublikHardSphere() + functional.diameters['A'] = 1.0 + functional.diameters['B'] = 1.5 + + """ + diameters = mirror.WrappedProperty(mirror.MutableMapping) class CompositeFunctional( Functional, CompositeMixin, mirrorclass=_flyft.CompositeFunctional ): + r"""Composite functional combining multiple functional components. + + `CompositeFunctional` allows combining multiple functional objects + into a single functional calculation. + + The total free energy is the sum of all component functionals, and + derivatives are computed by summing the individual contributions. + + Parameters + ---------- + objects : list, optional + List of functional objects to combine. Can be set later using + the `objects` attribute. + + Example + ------- + Create a composite functional from multiple components: + + .. code-block:: python + + ideal = flyft.functional.IdealGas() + hard_sphere = flyft.functional.WhiteBear() + composite = flyft.CompositeFunctional([ideal, hard_sphere]) + + """ + def __init__(self, objects=None): super().__init__() if objects is not None: @@ -27,6 +102,54 @@ def __init__(self, objects=None): class GrandPotential(Functional, mirrorclass=_flyft.GrandPotential): + """Grand potential functional for density functional theory calculations. + + The `GrandPotential` combines ideal gas, excess, and external potential + contributions to form the complete grand potential functional: + + .. math:: + \\Omega[\\rho] = F_{\\text{ideal}}[\\rho] + F_{\\text{excess}}[\\rho] + \\int V_{\\text{ext}}(\\mathbf{r}) \\rho(\\mathbf{r}) d\\mathbf{r} + + where :math:`F_{\\text{ideal}}` is the ideal gas contribution, + :math:`F_{\\text{excess}}` represents particle interactions, and + :math:`V_{\\text{ext}}` is the external potential. + + + Parameters + ---------- + ideal : Functional, optional + Ideal gas functional component. + excess : Functional, optional + Excess free energy functional for particle interactions. + external : ExternalPotential, optional + External potential component. + + Attributes + ---------- + ideal : Functional + Ideal gas functional. + excess : Functional + Excess interaction functional. + external : ExternalPotential + External potential. + constraints : dict + Constraint values for different particle types. + constraint_types : dict + Types of constraints applied to each particle type. + + Example + ------- + Create a grand potential functional: + + .. code-block:: python + + ideal = flyft.functional.IdealGas() + excess = flyft.functional.WhiteBear() + external = flyft.external.HardWall(origin=0.0, normal=1.0) + grand = flyft.functional.GrandPotential(ideal=ideal, excess=excess, external=external) + + """ + def __init__(self, ideal=None, excess=None, external=None): super().__init__() if ideal is not None: @@ -39,6 +162,17 @@ def __init__(self, ideal=None, excess=None, external=None): Constraint = _flyft.GrandPotential.Constraint def constrain(self, key, value, constraint_type): + """Apply a constraint to a particle type. + + Parameters + ---------- + key : str + Particle type name. + value : float + Constraint value. + constraint_type : Constraint + Type of constraint to apply. + """ self.constraints[key] = value self.constraint_types[key] = constraint_type @@ -50,20 +184,140 @@ def constrain(self, key, value, constraint_type): class IdealGas(Functional, mirrorclass=_flyft.IdealGasFunctional): + """Ideal gas functional for non-interacting particles. + + Implements the ideal gas free energy functional: + + .. math:: + F_{\\text{ideal}}[\\rho] = k_B T \\int \\rho(\\mathbf{r}) \\left[ \\ln(\\rho(\\mathbf{r}) v) - 1 \\right] d\\mathbf{r} + + where :math:`\\rho(\\mathbf{r})` is the density field, :math:`v` is the + molecular volume, :math:`k_B` is Boltzmann's constant, and :math:`T` + is temperature. + + This functional provides the entropic contribution for non-interacting + particles and is typically combined with excess functionals to describe + interacting systems. + + Attributes + ---------- + volumes : dict + Dictionary mapping particle type names to their molecular volumes. + + Example + ------- + Create an ideal gas functional: + + .. code-block:: python + + ideal = flyft.functional.IdealGas() + ideal.volumes['A'] = 1.0 + ideal.volumes['B'] = 1.5 + + """ + volumes = mirror.WrappedProperty(mirror.MutableMapping) class RosenfeldFMT(Functional, mirrorclass=_flyft.RosenfeldFMT): + """Rosenfeld fundamental measure theory functional for hard spheres. + + Implements the original Rosenfeld fundamental measure theory (FMT) + functional for hard sphere systems. This functional provides an + accurate description of hard sphere correlations and excluded volume + effects using weighted densities based on fundamental measures. + + The Rosenfeld functional is particularly accurate for inhomogeneous + hard sphere systems and forms the basis for more advanced FMT + functionals like White Bear. + + Attributes + ---------- + diameters : dict + Dictionary mapping particle type names to their hard sphere diameters. + + Example + ------- + Create a Rosenfeld FMT functional: + + .. code-block:: python + + fmt = flyft.functional.RosenfeldFMT() + fmt.diameters['A'] = 1.0 + fmt.diameters['B'] = 1.2 + + """ + diameters = mirror.WrappedProperty(mirror.MutableMapping) class VirialExpansion(Functional, mirrorclass=_flyft.VirialExpansion): + """Virial expansion functional for weakly interacting systems. + + Implements a virial expansion functional that approximates the excess + free energy using virial coefficients: + + .. math:: + F_{\\text{excess}}[\\rho] = k_B T \\int \\left[ B_2 \\rho^2 + B_3 \\rho^3 + \\ldots \\right] d\\mathbf{r} + + where :math:`B_n` are the virial coefficients. + + Attributes + ---------- + coefficients : dict + Dictionary mapping particle type pairs to virial coefficient values. + Keys should be tuples like ('A', 'B') for cross-interactions. + + Example + ------- + Create a virial expansion functional: + + .. code-block:: python + + virial = flyft.functional.VirialExpansion() + virial.coefficients[('A', 'A')] = -0.5 # Second virial coefficient + virial.coefficients[('A', 'B')] = -0.3 + + """ + coefficients = mirror.WrappedProperty(mirror.MutableMapping) class WhiteBear(RosenfeldFMT, mirrorclass=_flyft.WhiteBear): + """White Bear fundamental measure theory functional for hard spheres. + + Implements the White Bear version of fundamental measure theory (FMT), + which is an variation of the Rosenfeld FMT functional. + + Example + ------- + Create a White Bear FMT functional: + + .. code-block:: python + + fmt = flyft.functional.WhiteBear() + fmt.diameters['A'] = 1.0 + fmt.diameters['B'] = 1.2 + + """ pass class WhiteBearMarkII(WhiteBear, mirrorclass=_flyft.WhiteBearMarkII): + """White Bear Mark II fundamental measure theory functional. + + Implements the White Bear Mark II version of fundamental measure theory, + which is another variation of the White Bear functional. + + Example + ------- + Create a White Bear Mark II FMT functional: + + .. code-block:: python + + fmt = flyft.functional.WhiteBearMarkII() + fmt.diameters['A'] = 1.0 + fmt.diameters['B'] = 1.2 + + """ pass diff --git a/python/flyft/parameter.py b/python/flyft/parameter.py index 87ae030..580f08b 100644 --- a/python/flyft/parameter.py +++ b/python/flyft/parameter.py @@ -2,14 +2,80 @@ class CustomParameter(_flyft.DoubleParameter): + """Custom parameter class for user-defined time-dependent behavior. + + A `CustomParameter` allows users to define arbitrary time-dependent + parameter behavior by subclassing and implementing custom evaluation + methods. This provides maximum flexibility for complex time protocols. + + Example + ------- + Create a custom parameter with user-defined behavior: + + .. code-block:: python + + class MyParameter(flyft.CustomParameter): + def __call__(self, state): + # Custom time-dependent behavior + return some_function(state.time) + + """ pass class _DoubleParameter(mirror.Mirror, mirrorclass=_flyft.DoubleParameter): + """Base class for time-dependent double-valued parameters. + + A `_DoubleParameter` provides the base interface for parameters that + return double values and can depend on the system state and time. + All parameter classes implement the callable interface to evaluate + their value at a given state. + """ __call__ = mirror.Method() class LinearParameter(_DoubleParameter, mirrorclass=_flyft.LinearParameter): + """Linear time-dependent parameter. + + Implements a parameter that varies linearly with time: + + .. math:: + p(t) = p_0 + r \\cdot (t - t_0) + + where :math:`p_0` is the initial value, :math:`r` is the rate of change, + :math:`t` is the current time, and :math:`t_0` is the origin time. + + This is useful for implementing linear ramps, constant rates of change, + or linear boundary condition variations. + + Parameters + ---------- + initial : float + Initial parameter value at the origin time. + origin : float + Reference time point. + rate : float + Rate of change of the parameter per unit time. + + Attributes + ---------- + initial : float + Initial parameter value. + origin : float + Reference time. + rate : float + Rate of change. + + Example + ------- + Create a linear parameter that decreases at rate 0.1 per time unit: + + .. code-block:: python + + param = flyft.LinearParameter(initial=1.0, origin=0.0, rate=-0.1) + + """ + def __init__(self, initial, origin, rate): super().__init__(initial, origin, rate) @@ -19,6 +85,47 @@ def __init__(self, initial, origin, rate): class SquareRootParameter(_DoubleParameter, mirrorclass=_flyft.SquareRootParameter): + """Square root time-dependent parameter. + + Implements a parameter that varies as the square root of time: + + .. math:: + p(t) = \\sqrt{p_0^2 + r \\cdot (t - t_0)} + + where :math:`p_0` is the initial value, :math:`r` is the rate coefficient, + :math:`t` is the current time, and :math:`t_0` is the origin time. + + This functional form is useful for modeling parameters that evolve with a + square root time dependence. + + Parameters + ---------- + initial : float + Initial parameter value at the origin time. + origin : float + Reference time point. + rate : float + Rate coefficient for the square root dependence. + + Attributes + ---------- + initial : float + Initial parameter value. + origin : float + Reference time. + rate : float + Rate coefficient. + + Example + ------- + Create a square root parameter: + + .. code-block:: python + + param = flyft.SquareRootParameter(initial=1.0, origin=0.0, rate=-0.5) + + """ + def __init__(self, initial, origin, rate): super().__init__(initial, origin, rate) diff --git a/python/flyft/solver.py b/python/flyft/solver.py index 7982181..94484e9 100644 --- a/python/flyft/solver.py +++ b/python/flyft/solver.py @@ -2,10 +2,70 @@ class Solver(mirror.Mirror, mirrorclass=_flyft.Solver): + r"""Base class for iterative solvers in density functional theory. + + `Solver` implements iterative algorithms to find equilibrium density + distributions by minimizing the grand potential functional. The solver + iteratively updates density fields until convergence is achieved. + + All solver classes implement the `solve` method to perform the iterative + procedure and find the equilibrium solution. + """ + solve = mirror.Method() class PicardIteration(Solver, mirrorclass=_flyft.PicardIteration): + r"""Picard iteration solver for equilibrium DFT calculations. + + Implements Picard iteration method (also known as fixed-point iteration) + to solve the equilibrium condition in density functional theory: + + .. math:: + \\rho_{i}^{j+1}(\\mathbf{r}) = (1-\\alpha) \\rho_i^{j}(\\mathbf{r}) + \\alpha \\rho_i^{j}(\\mathbf{r}) + + where :math:`\\alpha` is the mixing parameter, :math:`\\rho_i^{j+1}(\\mathbf{r})` is the new density field + value, and :math:`\\rho_i^{j}(\\mathbf{r})` is the current estimate of the density field which is given by: + + .. math:: + \\rho_i^{j}(\\mathbf{r}) = \\exp\\left(\\beta \\frac{\\delta F_{ex}}{\\delta \\rho_i(\\mathbf{r})} - \\beta V_{ext,i}(\\mathbf{r})\\right) + + The mixing parameter controls convergence stability: smaller values provide + better stability but slower convergence, while larger values converge + faster but may become unstable. + + Parameters + ---------- + mix_parameter : float + Mixing parameter for controlling convergence (0 < α ≤ 1). + max_iterations : int + Maximum number of iterations before stopping. + tolerance : float + Convergence tolerance for the density change. + + Attributes + ---------- + mix_parameter : float + Mixing parameter value. + max_iterations : int + Maximum iterations allowed. + tolerance : float + Convergence tolerance. + + Example + ------- + Create a Picard iteration solver: + + .. code-block:: python + + solver = flyft.solver.PicardIteration( + mix_parameter=0.1, + max_iterations=1000, + tolerance=1e-8 + ) + + """ + def __init__(self, mix_parameter, max_iterations, tolerance): super().__init__(mix_parameter, max_iterations, tolerance) diff --git a/python/flyft/state.py b/python/flyft/state.py index 746e488..16f8e24 100644 --- a/python/flyft/state.py +++ b/python/flyft/state.py @@ -4,12 +4,57 @@ class Communicator(mirror.Mirror, mirrorclass=_flyft.Communicator): + r"""MPI communicator for parallel simulations. + + `Communicator` manages parallel communication between processes in + distributed simulations. It provides information about the parallel + environment and handles synchronization of data across processes. + + Attributes + ---------- + size : int + Total number of processes in the communicator. + rank : int + Rank (ID) of the current process. + root : int + Rank of the root process (typically 0). + """ + size = mirror.Property() rank = mirror.Property() root = mirror.Property() class Field(mirror.Mirror, mirrorclass=_flyft.Field): + r"""Density field on a computational mesh. + + `Field` represents a density field discretized on a computational mesh. + It provides access to density values. + + Parameters + ---------- + shape : int + Number of mesh points in the field. + + Attributes + ---------- + shape : int + Number of mesh points. + data : numpy.ndarray + Array view of the field data. + + Example + ------- + Create a field and manipulate its data: + + .. code-block:: python + + field = flyft.Field(100) + field.data = np.ones(100) * 0.5 # Set uniform density + field[50] = 1.0 # Set specific point + + """ + def __init__(self, shape): super().__init__(shape) @@ -43,6 +88,27 @@ def data(self, value): class Fields(mirror.Mapping): + r"""Collection of density fields for different particle types. + + `Fields` object provides dictionary-like access to density fields + for different particle types. It automatically manages the mapping + between particle type names and their corresponding `Field` objects. + + This class handles caching and ensures that field objects remain + consistent with their underlying C++ counterparts. + + Example + ------- + Access fields for different particle types: + + .. code-block:: python + + # Accessed through State.fields + state.fields['A'] # Get field for particle type 'A' + state.fields['B'] # Get field for particle type 'B' + + """ + def __init__(self, _self): self._self = _self self._cache = {} @@ -56,6 +122,27 @@ def __getitem__(self, key): class Mesh(mirror.Mirror, mirrorclass=_flyft.Mesh): + r"""Base class for computational meshes. + + `Mesh` discretizes the domain for the density field. It sets up and provides information regarding + mesh points, mesh volumes, and geometry boundaries. + + Attributes + ---------- + L : float + Domain length. + shape : int + Number of mesh points. + step : float + Mesh spacing. + centers : numpy.ndarray + Array of mesh point center positions. + lower_boundary_condition : str + Type of boundary condition at the lower boundary. + upper_boundary_condition : str + Type of boundary condition at the upper boundary. + """ + @property def centers(self): if not hasattr(self, "_centers"): @@ -63,6 +150,18 @@ def centers(self): return self._centers def volume(self, bin=None): + """Get volume of mesh element(s). + + Parameters + ---------- + bin : int, optional + Specific bin index. If None, returns total volume. + + Returns + ------- + float + Volume of the specified bin or total volume. + """ if bin is None: v = self._self.volume else: @@ -70,6 +169,18 @@ def volume(self, bin=None): return v def lower_bound(self, bin=None): + """Get lower bound of mesh element(s). + + Parameters + ---------- + bin : int, optional + Specific bin index. If None, returns domain lower bound. + + Returns + ------- + float + Lower bound of the specified bin or domain. + """ if bin is None: lo = self._self.lower_bound else: @@ -77,6 +188,18 @@ def lower_bound(self, bin=None): return lo def upper_bound(self, bin=None): + """Get upper bound of mesh element(s). + + Parameters + ---------- + bin : int, optional + Specific bin index. If None, returns domain upper bound. + + Returns + ------- + float + Upper bound of the specified bin or domain. + """ if bin is None: u = self._self.upper_bound else: @@ -109,6 +232,40 @@ def upper_boundary_condition(self): class CartesianMesh(Mesh, mirrorclass=_flyft.CartesianMesh): + r"""Cartesian mesh for one-dimensional systems. + + `CartesianMesh` provides a uniform discretization of a one-dimensional + Cartesian domain. + + Parameters + ---------- + L : float + Domain length. + shape : int + Number of mesh points. + boundary_condition : str or tuple + Boundary condition type(s). Can be a single string for both boundaries + or a tuple of (lower, upper) boundary conditions. + area : float, optional + Cross-sectional area for quasi-1D systems. Default is 1.0. + + Example + ------- + Create a Cartesian mesh with periodic boundaries: + + .. code-block:: python + + mesh = flyft.CartesianMesh(L=10.0, shape=100, boundary_condition="periodic") + + Create a mesh with different boundary conditions: + + .. code-block:: python + + mesh = flyft.CartesianMesh(L=10.0, shape=100, + boundary_condition=("reflect", "periodic")) + + """ + def __init__(self, L, shape, boundary_condition, area=1.0): if isinstance(boundary_condition, str): lower_bc = upper_bc = Mesh._parse_boundary_condition(boundary_condition) @@ -119,12 +276,82 @@ def __init__(self, L, shape, boundary_condition, area=1.0): class SphericalMesh(Mesh, mirrorclass=_flyft.SphericalMesh): + + r"""Spherical mesh for radially symmetric systems. + + `SphericalMesh` provides a uniform discretization of a spherical domain + in radial coordinates. The mesh extends from r=0 to r=R with appropriate + boundary conditions for spherical symmetry. + + Parameters + ---------- + R : float + Maximum radius of the spherical domain. + shape : int + Number of radial mesh points. + area : float, optional + Surface area scaling factor. Default is 4π for a full sphere. + + Notes + ----- + The spherical mesh automatically applies Neumann boundary conditions at + r=0 (center) and Dirichlet boundary conditions at r=R (surface) to + ensure proper spherical symmetry. + + Example + ------- + Create a spherical mesh for a droplet: + + .. code-block:: python + + mesh = flyft.SphericalMesh(R=5.0, shape=100) + + Create a spherical mesh with custom surface area: + + .. code-block:: python + + mesh = flyft.SphericalMesh(R=5.0, shape=100, area=2*math.pi) # hemisphere + + """ def __init__(self, R, shape, boundary_condition): upper_bc = Mesh._parse_boundary_condition(boundary_condition) super().__init__(0, R, shape, _flyft.BoundaryType.reflect, upper_bc) class ParallelMesh(mirror.Mirror, mirrorclass=_flyft.ParallelMesh): + """Parallel mesh wrapper for distributed computing. + + `ParallelMesh` wraps an existing mesh to enable distributed computing + across multiple MPI processes. It provides access to both the full mesh + and local mesh partitions for parallel computations. + + Parameters + ---------- + mesh : Mesh + The underlying mesh to parallelize. + + Attributes + ---------- + full : Mesh + The complete mesh across all processes. + local : Mesh + The local portion of the mesh for this process. + + Example + ------- + Create a parallel wrapper for a Cartesian mesh: + + .. code-block:: python + + mesh = flyft.CartesianMesh(L=10.0, shape=100, boundary_condition="periodic") + parallel_mesh = flyft.ParallelMesh(mesh) + + # Access full and local meshes + full_mesh = parallel_mesh.full + local_mesh = parallel_mesh.local + + """ + def __init__(self, mesh): communicator = Communicator() super().__init__(mesh, communicator) @@ -135,6 +362,54 @@ def __init__(self, mesh): class State(mirror.Mirror, mirrorclass=_flyft.State): + """System state for density functional theory simulations. + + `State` represents the complete state of a DFT system, including + the computational mesh, field variables, and simulation time. It + manages the density fields for all particle types in the system. + + Parameters + ---------- + mesh : Mesh + Computational mesh for the simulation domain. + types : str or tuple of str + Particle type names. Can be a single string or tuple of strings. + + Attributes + ---------- + communicator : Communicator + MPI communicator for parallel operations. + mesh : Mesh + The computational mesh. + fields : Fields + Collection of density fields for all particle types. + time : float + Current simulation time. + + Example + ------- + Create a state for a single-component system: + + .. code-block:: python + + mesh = flyft.CartesianMesh(L=10.0, shape=100, boundary_condition="periodic") + state = flyft.State(mesh, "A") + + Create a state for a binary mixture: + + .. code-block:: python + + state = flyft.State(mesh, ("A", "B")) + + Access density fields: + + .. code-block:: python + + rho_A = state.fields["A"] + rho_A[:] = 0.5 # Set uniform density + + """ + def __init__(self, mesh, types): if isinstance(types, str): types = (types,) From f5e0f37e25e4822479f334c601056a25221d9234 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 23:39:46 +0000 Subject: [PATCH 2/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- python/flyft/dynamics.py | 124 +++++++-------- python/flyft/functional.py | 132 ++++++++-------- python/flyft/parameter.py | 56 +++---- python/flyft/solver.py | 26 ++-- python/flyft/state.py | 142 +++++++++--------- python/tests/conftest.py | 3 +- python/tests/test_boublik_hard_sphere.py | 3 +- python/tests/test_brownian_diffusive_flux.py | 3 +- .../test_composite_external_potential.py | 3 +- python/tests/test_composite_flux.py | 3 +- python/tests/test_composite_functional.py | 3 +- .../tests/test_crank_nicolson_integrator.py | 3 +- .../tests/test_explicit_euler_integrator.py | 3 +- .../tests/test_exponential_wall_potential.py | 3 +- python/tests/test_external_field.py | 3 +- python/tests/test_field.py | 3 +- python/tests/test_grand_potential.py | 3 +- python/tests/test_hard_wall_potential.py | 3 +- python/tests/test_harmonic_wall_potential.py | 3 +- .../tests/test_implicit_euler_integrator.py | 3 +- .../test_lennard_jones_93_wall_potential.py | 3 +- python/tests/test_mirror.py | 3 +- python/tests/test_parameter.py | 3 +- python/tests/test_picard_iteration.py | 3 +- python/tests/test_rpy_diffusive_flux.py | 3 +- python/tests/test_state.py | 3 +- python/tests/test_virial_expansion.py | 3 +- python/tests/test_white_bear.py | 3 +- python/tests/test_white_bear_mark_ii.py | 3 +- python/validate/binary_mixture.py | 3 +- python/validate/brownian_diffusion.py | 3 +- python/validate/hard_sphere_evaporation.py | 3 +- python/validate/hard_sphere_slit.py | 3 +- python/validate/stratification.py | 3 +- 34 files changed, 271 insertions(+), 296 deletions(-) diff --git a/python/flyft/dynamics.py b/python/flyft/dynamics.py index f5e9142..bdcc4e5 100644 --- a/python/flyft/dynamics.py +++ b/python/flyft/dynamics.py @@ -6,33 +6,33 @@ class Flux(mirror.Mirror, mirrorclass=_flyft.Flux): """Base class for computing particle fluxes. - `Flux` computes the flux of particles for different confining geometries. + `Flux` computes the flux of particles for different confining geometries. The flux is used by integrators to update density fields over time. - + All flux classes must implement the `compute` method to calculate fluxes for all particle types in the system. """ - + compute = mirror.Method() fluxes = mirror.WrappedProperty(Fields) class CompositeFlux(Flux, CompositeMixin, mirrorclass=_flyft.CompositeFlux): """Composite flux combining multiple flux components. - + A `CompositeFlux` allows combining multiple flux objects into a single flux calculation. - + Parameters ---------- objects : list, optional List of flux objects to combine. Can be set later using the `objects` attribute. - + Example ------- Create a composite flux from multiple components: - + .. code-block:: python diffusive_flux_1 = flyft.dynamics.BrownianDiffusiveFlux() @@ -40,7 +40,7 @@ class CompositeFlux(Flux, CompositeMixin, mirrorclass=_flyft.CompositeFlux): composite = flyft.dynamics.CompositeFlux([diffusive_flux_1, diffusive_flux_2]) """ - + def __init__(self, objects=None): super().__init__() if objects is not None: @@ -49,89 +49,89 @@ def __init__(self, objects=None): class BrownianDiffusiveFlux(Flux, mirrorclass=_flyft.BrownianDiffusiveFlux): """Brownian diffusive flux for particle transport. - + Computes particle flux due to Brownian diffusion, including both ideal (Fickian) diffusion and contributions from gradients in the excess chemical potential and external potentials. The flux is given by: - + .. math:: \\mathbf{J} = -D \\left( \\nabla \\rho + \\rho \\nabla \\mu_{\\text{ex}} + \\rho \\nabla V \\right) - + where :math:`D` is the diffusivity, :math:`\\rho` is the density, :math:`\\mu_{\\text{ex}}` is the excess chemical potential, and :math:`V` is the external potential. - + Attributes ---------- diffusivities : dict Dictionary mapping particle type names to their diffusivity values. - + Example ------- Create a Brownian diffusive flux and set diffusivities: - + .. code-block:: python flux = flyft.dynamics.BrownianDiffusiveFlux() flux.diffusivities['A'] = 1.0 flux.diffusivities['B'] = 0.5 - + """ - + diffusivities = mirror.WrappedProperty(mirror.MutableMapping) class RPYDiffusiveFlux(Flux, mirrorclass=_flyft.RPYDiffusiveFlux): """Rotne-Prager-Yamakawa (RPY) diffusive flux with hydrodynamic interactions. - + Computes particle flux including hydrodynamic interactions between particles using the Rotne-Prager-Yamakawa mobility tensor for hard spheres. For particles i and j separated by distance r > d (where d = (a_i + a_j)): - + .. math:: - \\mathbf{M}_{ij} = \\frac{1}{8\\pi\\eta r} \\left[\\left(1 + \\frac{a_i^2+a_j^2}{3r^2}\\right)\\mathbf{I} + \\mathbf{M}_{ij} = \\frac{1}{8\\pi\\eta r} \\left[\\left(1 + \\frac{a_i^2+a_j^2}{3r^2}\\right)\\mathbf{I} + \\left(1 - \\frac{a_i^2+ a_j^2}{r^2}\\right)\\hat{\\mathbf{r}}\\hat{\\mathbf{r}} \\right] - - where :math:`\\eta` is the fluid viscosity, :math:`a_i` and :math:`a_j` are the - particle radii, :math:`r` is the distance between particles, :math:`\\mathbf{I}` - is the identity tensor, and :math:`\\hat{\\mathbf{r}}` is the unit vector connecting + + where :math:`\\eta` is the fluid viscosity, :math:`a_i` and :math:`a_j` are the + particle radii, :math:`r` is the distance between particles, :math:`\\mathbf{I}` + is the identity tensor, and :math:`\\hat{\\mathbf{r}}` is the unit vector connecting the particle centers. - + Attributes ---------- diameters : dict Dictionary mapping particle type names to their diameters. viscosity : float Fluid viscosity. - + Example ------- Create an RPY diffusive flux and set parameters: - + .. code-block:: python - + flux = flyft.dynamics.RPYDiffusiveFlux() flux.diameters['A'] = 1.0 flux.diameters['B'] = 2.0 flux.viscosity = 1.0 - + """ - + diameters = mirror.WrappedProperty(mirror.MutableMapping) viscosity = mirror.Property() class Integrator(mirror.Mirror, mirrorclass=_flyft.Integrator): """Base class for time integration of density fields. - + An `Integrator` advances the system state in time by applying fluxes computed from the grand potential and current state. It supports both fixed and adaptive timestep schemes for stability and efficiency. - + All integrator classes must implement specific integration schemes (explicit, implicit, etc.) while sharing common functionality for timestep management and adaptive stepping. - + Attributes ---------- timestep : float @@ -145,7 +145,7 @@ class Integrator(mirror.Mirror, mirrorclass=_flyft.Integrator): adapt_minimum : float Minimum allowed timestep for adaptive control. """ - + advance = mirror.Method() timestep = mirror.Property() adaptive = mirror.Property() @@ -155,11 +155,11 @@ class Integrator(mirror.Mirror, mirrorclass=_flyft.Integrator): def use_adaptive(self, delay=0, tolerance=1.0e-8, minimum=1.0e-8): """Enable adaptive timestep control. - + Configures the integrator to use adaptive timestep control based on error estimation. The timestep is automatically adjusted to maintain the specified error tolerance. - + Parameters ---------- delay : float, optional @@ -168,15 +168,15 @@ def use_adaptive(self, delay=0, tolerance=1.0e-8, minimum=1.0e-8): Error tolerance for timestep adjustment. Default is 1e-8. minimum : float, optional Minimum allowed timestep. Default is 1e-8. - + Example ------- Enable adaptive timestep control: - + .. code-block:: python - + integrator.use_adaptive(delay=10.0, tolerance=1e-6, minimum=1e-10) - + """ self.adaptive = True self.adapt_delay = delay @@ -188,17 +188,17 @@ class CrankNicolsonIntegrator( Integrator, FixedPointAlgorithmMixin, mirrorclass=_flyft.CrankNicolsonIntegrator ): """Crank-Nicolson time integrator with second-order accuracy. - + Implements the Crank-Nicolson scheme, which is second-order accurate in time and unconditionally stable. The scheme uses an implicit trapezoidal rule that averages the flux at the current and next timesteps: - + .. math:: \\rho^{n+1} = \\rho^n + \\frac{\\Delta t}{2} \\left( F[\\rho^n] + F[\\rho^{n+1}] \\right) - + where :math:`F[\\rho]` represents the flux evaluation. Since this is an implicit method, it requires iterative solution at each timestep. - + Parameters ---------- timestep : float @@ -209,11 +209,11 @@ class CrankNicolsonIntegrator( Maximum number of iterations for implicit solve. tolerance : float Convergence tolerance for implicit solve. - + Example ------- Create a Crank-Nicolson integrator: - + .. code-block:: python integrator = flyft.dynamics.CrankNicolsonIntegrator( @@ -222,35 +222,35 @@ class CrankNicolsonIntegrator( max_iterations=100, tolerance=1e-8 ) - + """ - + def __init__(self, timestep, mix_parameter, max_iterations, tolerance): super().__init__(timestep, mix_parameter, max_iterations, tolerance) class ExplicitEulerIntegrator(Integrator, mirrorclass=_flyft.ExplicitEulerIntegrator): """Explicit Euler time integrator with first-order accuracy. - + Implements the explicit (forward) Euler method, which is first-order accurate in time and conditionally stable. The scheme uses the current flux to advance the density: - + .. math:: \\rho^{n+1} = \\rho^n + \\Delta t \\, F[\\rho^n] - + where :math:`F[\\rho]` represents the flux evaluation. This explicit method is simple and efficient but requires small timesteps for stability. - + Parameters ---------- timestep : float Integration timestep. Must be sufficiently small for stability. - + Example ------- Create an explicit Euler integrator: - + .. code-block:: python integrator = flyft.dynamics.ExplicitEulerIntegrator(timestep=0.001) @@ -262,7 +262,7 @@ class ExplicitEulerIntegrator(Integrator, mirrorclass=_flyft.ExplicitEulerIntegr :math:`\\Delta t < \\Delta x^2 / (2D)` where :math:`\\Delta x` is the mesh spacing and :math:`D` is the diffusivity. """ - + def __init__(self, timestep): super().__init__(timestep) @@ -271,18 +271,18 @@ class ImplicitEulerIntegrator( Integrator, FixedPointAlgorithmMixin, mirrorclass=_flyft.ImplicitEulerIntegrator ): """Implicit Euler time integrator with first-order accuracy. - + Implements the implicit (backward) Euler method, which is first-order accurate in time and unconditionally stable. The scheme uses the flux at the next timestep to advance the density: - + .. math:: \\rho^{n+1} = \\rho^n + \\Delta t \\, F[\\rho^{n+1}] - + where :math:`F[\\rho]` represents the flux evaluation. Since this is an implicit method, it requires iterative solution at each timestep but offers superior stability compared to explicit methods. - + Parameters ---------- timestep : float @@ -293,11 +293,11 @@ class ImplicitEulerIntegrator( Maximum number of iterations for implicit solve. tolerance : float Convergence tolerance for implicit solve. - + Example ------- Create an implicit Euler integrator: - + .. code-block:: python integrator = flyft.dynamics.ImplicitEulerIntegrator( @@ -306,13 +306,13 @@ class ImplicitEulerIntegrator( max_iterations=100, tolerance=1e-8 ) - + Note ---- The implicit Euler method is unconditionally stable, allowing larger timesteps than explicit methods. However, it requires solving a nonlinear system at each timestep, which can be computationally expensive. """ - + def __init__(self, timestep, mix_parameter, max_iterations, tolerance): super().__init__(timestep, mix_parameter, max_iterations, tolerance) diff --git a/python/flyft/functional.py b/python/flyft/functional.py index 168a6b3..b49fc9f 100644 --- a/python/flyft/functional.py +++ b/python/flyft/functional.py @@ -5,14 +5,14 @@ class Functional(mirror.Mirror, mirrorclass=_flyft.Functional): r"""Base class for free energy functionals in density functional theory. - + `Functional` computes the free energy contribution and its functional - derivatives with respect to the density fields. - + derivatives with respect to the density fields. + All functionals implement the `compute` method to evaluate both the total free energy value and the derivatives (chemical potential contributions) for all particle types in the system. - + Attributes ---------- derivatives : Fields @@ -20,7 +20,7 @@ class Functional(mirror.Mirror, mirrorclass=_flyft.Functional): value : float Total free energy contribution from this functional. """ - + derivatives = mirror.WrappedProperty(Fields) value = mirror.Property() @@ -28,7 +28,7 @@ class Functional(mirror.Mirror, mirrorclass=_flyft.Functional): def compute(self, state, value=True): """Compute the functional and its derivatives. - + Parameters ---------- state : State @@ -40,29 +40,29 @@ def compute(self, state, value=True): class BoublikHardSphere(Functional, mirrorclass=_flyft.BoublikHardSphereFunctional): - r"""BMCSL hard sphere functional for excess free energy of hard spheres + r"""BMCSL hard sphere functional for excess free energy of hard spheres systems. - - Implements the BMCSL equation of state for hard sphere mixtures using + + Implements the BMCSL equation of state for hard sphere mixtures using local density approximations (LDA). - + Attributes ---------- diameters : dict Dictionary mapping particle type names to their hard sphere diameters. - + Example ------- Apply BMCSL hard sphere free-energy functional: - + .. code-block:: python functional = flyft.functional.BoublikHardSphere() functional.diameters['A'] = 1.0 functional.diameters['B'] = 1.5 - + """ - + diameters = mirror.WrappedProperty(mirror.MutableMapping) @@ -72,29 +72,29 @@ class CompositeFunctional( r"""Composite functional combining multiple functional components. `CompositeFunctional` allows combining multiple functional objects - into a single functional calculation. - + into a single functional calculation. + The total free energy is the sum of all component functionals, and derivatives are computed by summing the individual contributions. - + Parameters ---------- objects : list, optional List of functional objects to combine. Can be set later using the `objects` attribute. - + Example ------- Create a composite functional from multiple components: - + .. code-block:: python ideal = flyft.functional.IdealGas() hard_sphere = flyft.functional.WhiteBear() composite = flyft.CompositeFunctional([ideal, hard_sphere]) - + """ - + def __init__(self, objects=None): super().__init__() if objects is not None: @@ -103,18 +103,18 @@ def __init__(self, objects=None): class GrandPotential(Functional, mirrorclass=_flyft.GrandPotential): """Grand potential functional for density functional theory calculations. - + The `GrandPotential` combines ideal gas, excess, and external potential contributions to form the complete grand potential functional: - + .. math:: \\Omega[\\rho] = F_{\\text{ideal}}[\\rho] + F_{\\text{excess}}[\\rho] + \\int V_{\\text{ext}}(\\mathbf{r}) \\rho(\\mathbf{r}) d\\mathbf{r} - + where :math:`F_{\\text{ideal}}` is the ideal gas contribution, :math:`F_{\\text{excess}}` represents particle interactions, and :math:`V_{\\text{ext}}` is the external potential. - - + + Parameters ---------- ideal : Functional, optional @@ -123,7 +123,7 @@ class GrandPotential(Functional, mirrorclass=_flyft.GrandPotential): Excess free energy functional for particle interactions. external : ExternalPotential, optional External potential component. - + Attributes ---------- ideal : Functional @@ -136,20 +136,20 @@ class GrandPotential(Functional, mirrorclass=_flyft.GrandPotential): Constraint values for different particle types. constraint_types : dict Types of constraints applied to each particle type. - + Example ------- Create a grand potential functional: - + .. code-block:: python - + ideal = flyft.functional.IdealGas() excess = flyft.functional.WhiteBear() external = flyft.external.HardWall(origin=0.0, normal=1.0) grand = flyft.functional.GrandPotential(ideal=ideal, excess=excess, external=external) - + """ - + def __init__(self, ideal=None, excess=None, external=None): super().__init__() if ideal is not None: @@ -163,7 +163,7 @@ def __init__(self, ideal=None, excess=None, external=None): def constrain(self, key, value, constraint_type): """Apply a constraint to a particle type. - + Parameters ---------- key : str @@ -185,139 +185,141 @@ def constrain(self, key, value, constraint_type): class IdealGas(Functional, mirrorclass=_flyft.IdealGasFunctional): """Ideal gas functional for non-interacting particles. - + Implements the ideal gas free energy functional: - + .. math:: F_{\\text{ideal}}[\\rho] = k_B T \\int \\rho(\\mathbf{r}) \\left[ \\ln(\\rho(\\mathbf{r}) v) - 1 \\right] d\\mathbf{r} - + where :math:`\\rho(\\mathbf{r})` is the density field, :math:`v` is the molecular volume, :math:`k_B` is Boltzmann's constant, and :math:`T` is temperature. - + This functional provides the entropic contribution for non-interacting particles and is typically combined with excess functionals to describe interacting systems. - + Attributes ---------- volumes : dict Dictionary mapping particle type names to their molecular volumes. - + Example ------- Create an ideal gas functional: - + .. code-block:: python ideal = flyft.functional.IdealGas() ideal.volumes['A'] = 1.0 ideal.volumes['B'] = 1.5 - + """ - + volumes = mirror.WrappedProperty(mirror.MutableMapping) class RosenfeldFMT(Functional, mirrorclass=_flyft.RosenfeldFMT): """Rosenfeld fundamental measure theory functional for hard spheres. - + Implements the original Rosenfeld fundamental measure theory (FMT) functional for hard sphere systems. This functional provides an accurate description of hard sphere correlations and excluded volume effects using weighted densities based on fundamental measures. - + The Rosenfeld functional is particularly accurate for inhomogeneous hard sphere systems and forms the basis for more advanced FMT functionals like White Bear. - + Attributes ---------- diameters : dict Dictionary mapping particle type names to their hard sphere diameters. - + Example ------- Create a Rosenfeld FMT functional: - + .. code-block:: python fmt = flyft.functional.RosenfeldFMT() fmt.diameters['A'] = 1.0 fmt.diameters['B'] = 1.2 - + """ - + diameters = mirror.WrappedProperty(mirror.MutableMapping) class VirialExpansion(Functional, mirrorclass=_flyft.VirialExpansion): """Virial expansion functional for weakly interacting systems. - + Implements a virial expansion functional that approximates the excess free energy using virial coefficients: - + .. math:: F_{\\text{excess}}[\\rho] = k_B T \\int \\left[ B_2 \\rho^2 + B_3 \\rho^3 + \\ldots \\right] d\\mathbf{r} - + where :math:`B_n` are the virial coefficients. - + Attributes ---------- coefficients : dict Dictionary mapping particle type pairs to virial coefficient values. Keys should be tuples like ('A', 'B') for cross-interactions. - + Example ------- Create a virial expansion functional: - + .. code-block:: python virial = flyft.functional.VirialExpansion() virial.coefficients[('A', 'A')] = -0.5 # Second virial coefficient virial.coefficients[('A', 'B')] = -0.3 - + """ - + coefficients = mirror.WrappedProperty(mirror.MutableMapping) class WhiteBear(RosenfeldFMT, mirrorclass=_flyft.WhiteBear): """White Bear fundamental measure theory functional for hard spheres. - + Implements the White Bear version of fundamental measure theory (FMT), which is an variation of the Rosenfeld FMT functional. - + Example ------- Create a White Bear FMT functional: - + .. code-block:: python fmt = flyft.functional.WhiteBear() fmt.diameters['A'] = 1.0 fmt.diameters['B'] = 1.2 - + """ + pass class WhiteBearMarkII(WhiteBear, mirrorclass=_flyft.WhiteBearMarkII): """White Bear Mark II fundamental measure theory functional. - + Implements the White Bear Mark II version of fundamental measure theory, which is another variation of the White Bear functional. - + Example ------- Create a White Bear Mark II FMT functional: - + .. code-block:: python fmt = flyft.functional.WhiteBearMarkII() fmt.diameters['A'] = 1.0 fmt.diameters['B'] = 1.2 - + """ + pass diff --git a/python/flyft/parameter.py b/python/flyft/parameter.py index 580f08b..f4ccf98 100644 --- a/python/flyft/parameter.py +++ b/python/flyft/parameter.py @@ -3,51 +3,53 @@ class CustomParameter(_flyft.DoubleParameter): """Custom parameter class for user-defined time-dependent behavior. - + A `CustomParameter` allows users to define arbitrary time-dependent parameter behavior by subclassing and implementing custom evaluation methods. This provides maximum flexibility for complex time protocols. - + Example ------- Create a custom parameter with user-defined behavior: - + .. code-block:: python - + class MyParameter(flyft.CustomParameter): def __call__(self, state): # Custom time-dependent behavior return some_function(state.time) - + """ + pass class _DoubleParameter(mirror.Mirror, mirrorclass=_flyft.DoubleParameter): """Base class for time-dependent double-valued parameters. - + A `_DoubleParameter` provides the base interface for parameters that return double values and can depend on the system state and time. All parameter classes implement the callable interface to evaluate their value at a given state. """ + __call__ = mirror.Method() class LinearParameter(_DoubleParameter, mirrorclass=_flyft.LinearParameter): """Linear time-dependent parameter. - + Implements a parameter that varies linearly with time: - + .. math:: p(t) = p_0 + r \\cdot (t - t_0) - + where :math:`p_0` is the initial value, :math:`r` is the rate of change, :math:`t` is the current time, and :math:`t_0` is the origin time. - + This is useful for implementing linear ramps, constant rates of change, or linear boundary condition variations. - + Parameters ---------- initial : float @@ -56,7 +58,7 @@ class LinearParameter(_DoubleParameter, mirrorclass=_flyft.LinearParameter): Reference time point. rate : float Rate of change of the parameter per unit time. - + Attributes ---------- initial : float @@ -65,17 +67,17 @@ class LinearParameter(_DoubleParameter, mirrorclass=_flyft.LinearParameter): Reference time. rate : float Rate of change. - + Example ------- Create a linear parameter that decreases at rate 0.1 per time unit: - + .. code-block:: python - + param = flyft.LinearParameter(initial=1.0, origin=0.0, rate=-0.1) - + """ - + def __init__(self, initial, origin, rate): super().__init__(initial, origin, rate) @@ -86,18 +88,18 @@ def __init__(self, initial, origin, rate): class SquareRootParameter(_DoubleParameter, mirrorclass=_flyft.SquareRootParameter): """Square root time-dependent parameter. - + Implements a parameter that varies as the square root of time: - + .. math:: p(t) = \\sqrt{p_0^2 + r \\cdot (t - t_0)} - + where :math:`p_0` is the initial value, :math:`r` is the rate coefficient, :math:`t` is the current time, and :math:`t_0` is the origin time. - - This functional form is useful for modeling parameters that evolve with a + + This functional form is useful for modeling parameters that evolve with a square root time dependence. - + Parameters ---------- initial : float @@ -106,7 +108,7 @@ class SquareRootParameter(_DoubleParameter, mirrorclass=_flyft.SquareRootParamet Reference time point. rate : float Rate coefficient for the square root dependence. - + Attributes ---------- initial : float @@ -115,17 +117,17 @@ class SquareRootParameter(_DoubleParameter, mirrorclass=_flyft.SquareRootParamet Reference time. rate : float Rate coefficient. - + Example ------- Create a square root parameter: - + .. code-block:: python param = flyft.SquareRootParameter(initial=1.0, origin=0.0, rate=-0.5) """ - + def __init__(self, initial, origin, rate): super().__init__(initial, origin, rate) diff --git a/python/flyft/solver.py b/python/flyft/solver.py index 94484e9..ca1ebb3 100644 --- a/python/flyft/solver.py +++ b/python/flyft/solver.py @@ -3,37 +3,37 @@ class Solver(mirror.Mirror, mirrorclass=_flyft.Solver): r"""Base class for iterative solvers in density functional theory. - + `Solver` implements iterative algorithms to find equilibrium density distributions by minimizing the grand potential functional. The solver iteratively updates density fields until convergence is achieved. - + All solver classes implement the `solve` method to perform the iterative procedure and find the equilibrium solution. """ - + solve = mirror.Method() class PicardIteration(Solver, mirrorclass=_flyft.PicardIteration): r"""Picard iteration solver for equilibrium DFT calculations. - + Implements Picard iteration method (also known as fixed-point iteration) to solve the equilibrium condition in density functional theory: - + .. math:: \\rho_{i}^{j+1}(\\mathbf{r}) = (1-\\alpha) \\rho_i^{j}(\\mathbf{r}) + \\alpha \\rho_i^{j}(\\mathbf{r}) - where :math:`\\alpha` is the mixing parameter, :math:`\\rho_i^{j+1}(\\mathbf{r})` is the new density field + where :math:`\\alpha` is the mixing parameter, :math:`\\rho_i^{j+1}(\\mathbf{r})` is the new density field value, and :math:`\\rho_i^{j}(\\mathbf{r})` is the current estimate of the density field which is given by: - + .. math:: \\rho_i^{j}(\\mathbf{r}) = \\exp\\left(\\beta \\frac{\\delta F_{ex}}{\\delta \\rho_i(\\mathbf{r})} - \\beta V_{ext,i}(\\mathbf{r})\\right) The mixing parameter controls convergence stability: smaller values provide better stability but slower convergence, while larger values converge faster but may become unstable. - + Parameters ---------- mix_parameter : float @@ -42,7 +42,7 @@ class PicardIteration(Solver, mirrorclass=_flyft.PicardIteration): Maximum number of iterations before stopping. tolerance : float Convergence tolerance for the density change. - + Attributes ---------- mix_parameter : float @@ -51,11 +51,11 @@ class PicardIteration(Solver, mirrorclass=_flyft.PicardIteration): Maximum iterations allowed. tolerance : float Convergence tolerance. - + Example ------- Create a Picard iteration solver: - + .. code-block:: python solver = flyft.solver.PicardIteration( @@ -63,9 +63,9 @@ class PicardIteration(Solver, mirrorclass=_flyft.PicardIteration): max_iterations=1000, tolerance=1e-8 ) - + """ - + def __init__(self, mix_parameter, max_iterations, tolerance): super().__init__(mix_parameter, max_iterations, tolerance) diff --git a/python/flyft/state.py b/python/flyft/state.py index 16f8e24..716b5a7 100644 --- a/python/flyft/state.py +++ b/python/flyft/state.py @@ -5,11 +5,11 @@ class Communicator(mirror.Mirror, mirrorclass=_flyft.Communicator): r"""MPI communicator for parallel simulations. - + `Communicator` manages parallel communication between processes in distributed simulations. It provides information about the parallel environment and handles synchronization of data across processes. - + Attributes ---------- size : int @@ -19,7 +19,7 @@ class Communicator(mirror.Mirror, mirrorclass=_flyft.Communicator): root : int Rank of the root process (typically 0). """ - + size = mirror.Property() rank = mirror.Property() root = mirror.Property() @@ -30,31 +30,31 @@ class Field(mirror.Mirror, mirrorclass=_flyft.Field): `Field` represents a density field discretized on a computational mesh. It provides access to density values. - + Parameters ---------- shape : int Number of mesh points in the field. - + Attributes ---------- shape : int Number of mesh points. data : numpy.ndarray Array view of the field data. - + Example ------- Create a field and manipulate its data: - + .. code-block:: python - + field = flyft.Field(100) field.data = np.ones(100) * 0.5 # Set uniform density field[50] = 1.0 # Set specific point - + """ - + def __init__(self, shape): super().__init__(shape) @@ -89,26 +89,26 @@ def data(self, value): class Fields(mirror.Mapping): r"""Collection of density fields for different particle types. - + `Fields` object provides dictionary-like access to density fields for different particle types. It automatically manages the mapping between particle type names and their corresponding `Field` objects. - + This class handles caching and ensures that field objects remain consistent with their underlying C++ counterparts. - + Example ------- Access fields for different particle types: - + .. code-block:: python - + # Accessed through State.fields state.fields['A'] # Get field for particle type 'A' state.fields['B'] # Get field for particle type 'B' - + """ - + def __init__(self, _self): self._self = _self self._cache = {} @@ -124,9 +124,9 @@ def __getitem__(self, key): class Mesh(mirror.Mirror, mirrorclass=_flyft.Mesh): r"""Base class for computational meshes. - `Mesh` discretizes the domain for the density field. It sets up and provides information regarding + `Mesh` discretizes the domain for the density field. It sets up and provides information regarding mesh points, mesh volumes, and geometry boundaries. - + Attributes ---------- L : float @@ -142,7 +142,7 @@ class Mesh(mirror.Mirror, mirrorclass=_flyft.Mesh): upper_boundary_condition : str Type of boundary condition at the upper boundary. """ - + @property def centers(self): if not hasattr(self, "_centers"): @@ -151,12 +151,12 @@ def centers(self): def volume(self, bin=None): """Get volume of mesh element(s). - + Parameters ---------- bin : int, optional Specific bin index. If None, returns total volume. - + Returns ------- float @@ -170,12 +170,12 @@ def volume(self, bin=None): def lower_bound(self, bin=None): """Get lower bound of mesh element(s). - + Parameters ---------- bin : int, optional Specific bin index. If None, returns domain lower bound. - + Returns ------- float @@ -189,12 +189,12 @@ def lower_bound(self, bin=None): def upper_bound(self, bin=None): """Get upper bound of mesh element(s). - + Parameters ---------- bin : int, optional Specific bin index. If None, returns domain upper bound. - + Returns ------- float @@ -235,8 +235,8 @@ class CartesianMesh(Mesh, mirrorclass=_flyft.CartesianMesh): r"""Cartesian mesh for one-dimensional systems. `CartesianMesh` provides a uniform discretization of a one-dimensional - Cartesian domain. - + Cartesian domain. + Parameters ---------- L : float @@ -248,24 +248,24 @@ class CartesianMesh(Mesh, mirrorclass=_flyft.CartesianMesh): or a tuple of (lower, upper) boundary conditions. area : float, optional Cross-sectional area for quasi-1D systems. Default is 1.0. - + Example ------- Create a Cartesian mesh with periodic boundaries: - + .. code-block:: python - + mesh = flyft.CartesianMesh(L=10.0, shape=100, boundary_condition="periodic") - + Create a mesh with different boundary conditions: - + .. code-block:: python - - mesh = flyft.CartesianMesh(L=10.0, shape=100, + + mesh = flyft.CartesianMesh(L=10.0, shape=100, boundary_condition=("reflect", "periodic")) - + """ - + def __init__(self, L, shape, boundary_condition, area=1.0): if isinstance(boundary_condition, str): lower_bc = upper_bc = Mesh._parse_boundary_condition(boundary_condition) @@ -276,13 +276,12 @@ def __init__(self, L, shape, boundary_condition, area=1.0): class SphericalMesh(Mesh, mirrorclass=_flyft.SphericalMesh): - r"""Spherical mesh for radially symmetric systems. - + `SphericalMesh` provides a uniform discretization of a spherical domain in radial coordinates. The mesh extends from r=0 to r=R with appropriate boundary conditions for spherical symmetry. - + Parameters ---------- R : float @@ -291,28 +290,29 @@ class SphericalMesh(Mesh, mirrorclass=_flyft.SphericalMesh): Number of radial mesh points. area : float, optional Surface area scaling factor. Default is 4π for a full sphere. - + Notes ----- The spherical mesh automatically applies Neumann boundary conditions at r=0 (center) and Dirichlet boundary conditions at r=R (surface) to ensure proper spherical symmetry. - + Example ------- Create a spherical mesh for a droplet: - + .. code-block:: python - + mesh = flyft.SphericalMesh(R=5.0, shape=100) - + Create a spherical mesh with custom surface area: - + .. code-block:: python - + mesh = flyft.SphericalMesh(R=5.0, shape=100, area=2*math.pi) # hemisphere - + """ + def __init__(self, R, shape, boundary_condition): upper_bc = Mesh._parse_boundary_condition(boundary_condition) super().__init__(0, R, shape, _flyft.BoundaryType.reflect, upper_bc) @@ -320,38 +320,38 @@ def __init__(self, R, shape, boundary_condition): class ParallelMesh(mirror.Mirror, mirrorclass=_flyft.ParallelMesh): """Parallel mesh wrapper for distributed computing. - + `ParallelMesh` wraps an existing mesh to enable distributed computing across multiple MPI processes. It provides access to both the full mesh and local mesh partitions for parallel computations. - + Parameters ---------- mesh : Mesh The underlying mesh to parallelize. - + Attributes ---------- full : Mesh The complete mesh across all processes. local : Mesh The local portion of the mesh for this process. - + Example ------- Create a parallel wrapper for a Cartesian mesh: - + .. code-block:: python - + mesh = flyft.CartesianMesh(L=10.0, shape=100, boundary_condition="periodic") parallel_mesh = flyft.ParallelMesh(mesh) - + # Access full and local meshes full_mesh = parallel_mesh.full local_mesh = parallel_mesh.local - + """ - + def __init__(self, mesh): communicator = Communicator() super().__init__(mesh, communicator) @@ -363,18 +363,18 @@ def __init__(self, mesh): class State(mirror.Mirror, mirrorclass=_flyft.State): """System state for density functional theory simulations. - + `State` represents the complete state of a DFT system, including the computational mesh, field variables, and simulation time. It manages the density fields for all particle types in the system. - + Parameters ---------- mesh : Mesh Computational mesh for the simulation domain. types : str or tuple of str Particle type names. Can be a single string or tuple of strings. - + Attributes ---------- communicator : Communicator @@ -385,31 +385,31 @@ class State(mirror.Mirror, mirrorclass=_flyft.State): Collection of density fields for all particle types. time : float Current simulation time. - + Example ------- Create a state for a single-component system: - + .. code-block:: python - + mesh = flyft.CartesianMesh(L=10.0, shape=100, boundary_condition="periodic") state = flyft.State(mesh, "A") - + Create a state for a binary mixture: - + .. code-block:: python - + state = flyft.State(mesh, ("A", "B")) - + Access density fields: - + .. code-block:: python - + rho_A = state.fields["A"] rho_A[:] = 0.5 # Set uniform density - + """ - + def __init__(self, mesh, types): if isinstance(types, str): types = (types,) diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 04d65ed..4adb701 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -1,8 +1,7 @@ +import flyft import pytest from pytest_lazy_fixtures import lf as lazy_fixture -import flyft - @pytest.fixture def ig(): diff --git a/python/tests/test_boublik_hard_sphere.py b/python/tests/test_boublik_hard_sphere.py index 0b7c3cb..98c4463 100644 --- a/python/tests/test_boublik_hard_sphere.py +++ b/python/tests/test_boublik_hard_sphere.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - def fex_cs(eta, d): """Carnahan-Starling free-energy density of hard spheres""" diff --git a/python/tests/test_brownian_diffusive_flux.py b/python/tests/test_brownian_diffusive_flux.py index f7099c1..e55ad57 100644 --- a/python/tests/test_brownian_diffusive_flux.py +++ b/python/tests/test_brownian_diffusive_flux.py @@ -1,9 +1,8 @@ +import flyft import numpy as np import pytest from pytest_lazy_fixtures import lf as lazy_fixture -import flyft - @pytest.fixture def cartesian_mesh_grand(): diff --git a/python/tests/test_composite_external_potential.py b/python/tests/test_composite_external_potential.py index 9515921..e075944 100644 --- a/python/tests/test_composite_external_potential.py +++ b/python/tests/test_composite_external_potential.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def comp(): diff --git a/python/tests/test_composite_flux.py b/python/tests/test_composite_flux.py index f1785eb..b92b7d0 100644 --- a/python/tests/test_composite_flux.py +++ b/python/tests/test_composite_flux.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def comp(): diff --git a/python/tests/test_composite_functional.py b/python/tests/test_composite_functional.py index ebd37f6..ec070a1 100644 --- a/python/tests/test_composite_functional.py +++ b/python/tests/test_composite_functional.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def comp(): diff --git a/python/tests/test_crank_nicolson_integrator.py b/python/tests/test_crank_nicolson_integrator.py index dfcba14..2a9153f 100644 --- a/python/tests/test_crank_nicolson_integrator.py +++ b/python/tests/test_crank_nicolson_integrator.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def cn(): diff --git a/python/tests/test_explicit_euler_integrator.py b/python/tests/test_explicit_euler_integrator.py index 2889542..1e0e3e6 100644 --- a/python/tests/test_explicit_euler_integrator.py +++ b/python/tests/test_explicit_euler_integrator.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def euler(): diff --git a/python/tests/test_exponential_wall_potential.py b/python/tests/test_exponential_wall_potential.py index 5da332b..18573b1 100644 --- a/python/tests/test_exponential_wall_potential.py +++ b/python/tests/test_exponential_wall_potential.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def ew(): diff --git a/python/tests/test_external_field.py b/python/tests/test_external_field.py index b07c5d0..f896a48 100644 --- a/python/tests/test_external_field.py +++ b/python/tests/test_external_field.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def spherical_mesh_grand(): diff --git a/python/tests/test_field.py b/python/tests/test_field.py index 2409aff..c7f3d78 100644 --- a/python/tests/test_field.py +++ b/python/tests/test_field.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def field(): diff --git a/python/tests/test_grand_potential.py b/python/tests/test_grand_potential.py index 768cd2c..bbf9897 100644 --- a/python/tests/test_grand_potential.py +++ b/python/tests/test_grand_potential.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - from .test_ideal_gas import f_ig, mu_ig from .test_rosenfeld_fmt import fex_py, muex_py diff --git a/python/tests/test_hard_wall_potential.py b/python/tests/test_hard_wall_potential.py index 697d82e..5730945 100644 --- a/python/tests/test_hard_wall_potential.py +++ b/python/tests/test_hard_wall_potential.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def hw(): diff --git a/python/tests/test_harmonic_wall_potential.py b/python/tests/test_harmonic_wall_potential.py index 7d6dbd2..a700c2a 100644 --- a/python/tests/test_harmonic_wall_potential.py +++ b/python/tests/test_harmonic_wall_potential.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def hw(): diff --git a/python/tests/test_implicit_euler_integrator.py b/python/tests/test_implicit_euler_integrator.py index aa39865..dc7bc7e 100644 --- a/python/tests/test_implicit_euler_integrator.py +++ b/python/tests/test_implicit_euler_integrator.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def euler(): diff --git a/python/tests/test_lennard_jones_93_wall_potential.py b/python/tests/test_lennard_jones_93_wall_potential.py index efa5692..ca70f83 100644 --- a/python/tests/test_lennard_jones_93_wall_potential.py +++ b/python/tests/test_lennard_jones_93_wall_potential.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def lj(): diff --git a/python/tests/test_mirror.py b/python/tests/test_mirror.py index f721114..1828282 100644 --- a/python/tests/test_mirror.py +++ b/python/tests/test_mirror.py @@ -1,6 +1,5 @@ -import pytest - import flyft.mirror +import pytest class _A: diff --git a/python/tests/test_parameter.py b/python/tests/test_parameter.py index 82631cc..3e91ea5 100644 --- a/python/tests/test_parameter.py +++ b/python/tests/test_parameter.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - class TimeParameter(flyft.CustomParameter): def __call__(self, state): diff --git a/python/tests/test_picard_iteration.py b/python/tests/test_picard_iteration.py index 5bc1cf5..b5ad3de 100644 --- a/python/tests/test_picard_iteration.py +++ b/python/tests/test_picard_iteration.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - from .test_ideal_gas import mu_ig from .test_rosenfeld_fmt import muex_py diff --git a/python/tests/test_rpy_diffusive_flux.py b/python/tests/test_rpy_diffusive_flux.py index 71d4524..89e433c 100644 --- a/python/tests/test_rpy_diffusive_flux.py +++ b/python/tests/test_rpy_diffusive_flux.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - @pytest.fixture def spherical_mesh_grand(): diff --git a/python/tests/test_state.py b/python/tests/test_state.py index 24924a6..a1aed37 100644 --- a/python/tests/test_state.py +++ b/python/tests/test_state.py @@ -1,6 +1,5 @@ -import pytest - import flyft +import pytest def test_init(state): diff --git a/python/tests/test_virial_expansion.py b/python/tests/test_virial_expansion.py index 4b5d0e1..3464b95 100644 --- a/python/tests/test_virial_expansion.py +++ b/python/tests/test_virial_expansion.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - def f_ex(B, rho): """Excess free-energy density of virial expansion""" diff --git a/python/tests/test_white_bear.py b/python/tests/test_white_bear.py index 2924ae2..6ef571b 100644 --- a/python/tests/test_white_bear.py +++ b/python/tests/test_white_bear.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - def fex_cs(eta, v): """Carnahan--Starling free-energy density of hard spheres""" diff --git a/python/tests/test_white_bear_mark_ii.py b/python/tests/test_white_bear_mark_ii.py index 73feed1..48ee0ec 100644 --- a/python/tests/test_white_bear_mark_ii.py +++ b/python/tests/test_white_bear_mark_ii.py @@ -1,8 +1,7 @@ +import flyft import numpy as np import pytest -import flyft - def fex_cs(eta, v): """Carnahan--Starling free-energy density of hard spheres""" diff --git a/python/validate/binary_mixture.py b/python/validate/binary_mixture.py index 4cdae6b..28e6620 100644 --- a/python/validate/binary_mixture.py +++ b/python/validate/binary_mixture.py @@ -1,6 +1,5 @@ -import numpy as np - import flyft +import numpy as np L = 50.0 dx = 0.2 diff --git a/python/validate/brownian_diffusion.py b/python/validate/brownian_diffusion.py index 8183677..0fc5d5a 100644 --- a/python/validate/brownian_diffusion.py +++ b/python/validate/brownian_diffusion.py @@ -1,6 +1,5 @@ -import numpy as np - import flyft +import numpy as np L = 10.0 dx = 0.05 diff --git a/python/validate/hard_sphere_evaporation.py b/python/validate/hard_sphere_evaporation.py index af01e9e..be85854 100644 --- a/python/validate/hard_sphere_evaporation.py +++ b/python/validate/hard_sphere_evaporation.py @@ -1,6 +1,5 @@ -import numpy as np - import flyft +import numpy as np L = 10.0 dx = 0.05 diff --git a/python/validate/hard_sphere_slit.py b/python/validate/hard_sphere_slit.py index 73d6fdd..bb2a014 100644 --- a/python/validate/hard_sphere_slit.py +++ b/python/validate/hard_sphere_slit.py @@ -1,6 +1,5 @@ -import numpy as np - import flyft +import numpy as np L = 16.0 diameters = {"A": 1.0} diff --git a/python/validate/stratification.py b/python/validate/stratification.py index a8cdb6f..ee04a00 100644 --- a/python/validate/stratification.py +++ b/python/validate/stratification.py @@ -1,6 +1,5 @@ -import numpy as np - import flyft +import numpy as np L = 38.5 dx = 0.05 From 13ee940b624a093333ff71362d81355de4989208 Mon Sep 17 00:00:00 2001 From: mayukhkundu14 <79770572+mayukh33@users.noreply.github.com> Date: Wed, 5 Nov 2025 18:04:14 -0600 Subject: [PATCH 3/7] Pre-commit error fixes --- python/flyft/dynamics.py | 28 +++++++++++++++++----------- python/flyft/functional.py | 17 ++++++++++++----- python/flyft/solver.py | 14 ++++++++++---- python/flyft/state.py | 3 ++- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/python/flyft/dynamics.py b/python/flyft/dynamics.py index bdcc4e5..62d83ae 100644 --- a/python/flyft/dynamics.py +++ b/python/flyft/dynamics.py @@ -55,7 +55,8 @@ class BrownianDiffusiveFlux(Flux, mirrorclass=_flyft.BrownianDiffusiveFlux): potential and external potentials. The flux is given by: .. math:: - \\mathbf{J} = -D \\left( \\nabla \\rho + \\rho \\nabla \\mu_{\\text{ex}} + \\rho \\nabla V \\right) + \\mathbf{J} = -D \\left( \\nabla \\rho + \\rho \\nabla \\mu_{\\text{ex}} + + \\rho \\nabla V \\right) where :math:`D` is the diffusivity, :math:`\\rho` is the density, :math:`\\mu_{\\text{ex}}` is the excess chemical potential, and :math:`V` @@ -82,20 +83,24 @@ class BrownianDiffusiveFlux(Flux, mirrorclass=_flyft.BrownianDiffusiveFlux): class RPYDiffusiveFlux(Flux, mirrorclass=_flyft.RPYDiffusiveFlux): - """Rotne-Prager-Yamakawa (RPY) diffusive flux with hydrodynamic interactions. + """Rotne-Prager-Yamakawa (RPY) diffusive flux with hydrodynamic + interactions. Computes particle flux including hydrodynamic interactions between particles - using the Rotne-Prager-Yamakawa mobility tensor for hard spheres. For particles - i and j separated by distance r > d (where d = (a_i + a_j)): + using the Rotne-Prager-Yamakawa mobility tensor for hard spheres. + For particles i and j separated by distance r > d (where d = (a_i + a_j)): .. math:: - \\mathbf{M}_{ij} = \\frac{1}{8\\pi\\eta r} \\left[\\left(1 + \\frac{a_i^2+a_j^2}{3r^2}\\right)\\mathbf{I} - + \\left(1 - \\frac{a_i^2+ a_j^2}{r^2}\\right)\\hat{\\mathbf{r}}\\hat{\\mathbf{r}} \\right] + \\mathbf{M}_{ij} = \\frac{1}{8\\pi\\eta r} \\left[\\left(1 + + \\frac{a_i^2+a_j^2}{3r^2}\\right)\\mathbf{I} + + \\left(1 - + \\frac{a_i^2+ a_j^2}{r^2}\\right)\\mathbf{r}} + \\mathbf{r} \\right] - where :math:`\\eta` is the fluid viscosity, :math:`a_i` and :math:`a_j` are the - particle radii, :math:`r` is the distance between particles, :math:`\\mathbf{I}` - is the identity tensor, and :math:`\\hat{\\mathbf{r}}` is the unit vector connecting - the particle centers. + where :math:`\\eta` is the fluid viscosity, :math:`a_i` and :math:`a_j` + are the particle radii, :math:`r` is the distance between particles, + :math:`\\mathbf{I}` is the identity tensor, and :math:`\\mathbf{r}` + is the unit vector connecting the particle centers. Attributes ---------- @@ -194,7 +199,8 @@ class CrankNicolsonIntegrator( that averages the flux at the current and next timesteps: .. math:: - \\rho^{n+1} = \\rho^n + \\frac{\\Delta t}{2} \\left( F[\\rho^n] + F[\\rho^{n+1}] \\right) + \\rho^{n+1} = \\rho^n + \\frac{\\Delta t}{2} \\left( F[\\rho^n] + + F[\\rho^{n+1}] \\right) where :math:`F[\\rho]` represents the flux evaluation. Since this is an implicit method, it requires iterative solution at each timestep. diff --git a/python/flyft/functional.py b/python/flyft/functional.py index b49fc9f..02ba81e 100644 --- a/python/flyft/functional.py +++ b/python/flyft/functional.py @@ -16,7 +16,8 @@ class Functional(mirror.Mirror, mirrorclass=_flyft.Functional): Attributes ---------- derivatives : Fields - Functional derivatives (chemical potential contributions) for each particle type. + Functional derivatives (chemical potential contributions) for each + particle type. value : float Total free energy contribution from this functional. """ @@ -108,7 +109,9 @@ class GrandPotential(Functional, mirrorclass=_flyft.GrandPotential): contributions to form the complete grand potential functional: .. math:: - \\Omega[\\rho] = F_{\\text{ideal}}[\\rho] + F_{\\text{excess}}[\\rho] + \\int V_{\\text{ext}}(\\mathbf{r}) \\rho(\\mathbf{r}) d\\mathbf{r} + \\Omega[\\rho] = F_{\\text{ideal}}[\\rho] + F_{\\text{excess}}[\\rho] + + \\int V_{\\text{ext}}(\\mathbf{r}) + \\rho(\\mathbf{r}) d\\mathbf{r} where :math:`F_{\\text{ideal}}` is the ideal gas contribution, :math:`F_{\\text{excess}}` represents particle interactions, and @@ -146,7 +149,8 @@ class GrandPotential(Functional, mirrorclass=_flyft.GrandPotential): ideal = flyft.functional.IdealGas() excess = flyft.functional.WhiteBear() external = flyft.external.HardWall(origin=0.0, normal=1.0) - grand = flyft.functional.GrandPotential(ideal=ideal, excess=excess, external=external) + grand = flyft.functional.GrandPotential(ideal=ideal, excess=excess, + external=external) """ @@ -189,7 +193,9 @@ class IdealGas(Functional, mirrorclass=_flyft.IdealGasFunctional): Implements the ideal gas free energy functional: .. math:: - F_{\\text{ideal}}[\\rho] = k_B T \\int \\rho(\\mathbf{r}) \\left[ \\ln(\\rho(\\mathbf{r}) v) - 1 \\right] d\\mathbf{r} + F_{\\text{ideal}}[\\rho] = k_B T \\int \\rho(\\mathbf{r}) \\left[ + \\ln(\\rho(\\mathbf{r}) v) - 1 \\right] + d\\mathbf{r} where :math:`\\rho(\\mathbf{r})` is the density field, :math:`v` is the molecular volume, :math:`k_B` is Boltzmann's constant, and :math:`T` @@ -258,7 +264,8 @@ class VirialExpansion(Functional, mirrorclass=_flyft.VirialExpansion): free energy using virial coefficients: .. math:: - F_{\\text{excess}}[\\rho] = k_B T \\int \\left[ B_2 \\rho^2 + B_3 \\rho^3 + \\ldots \\right] d\\mathbf{r} + F_{\\text{excess}}[\\rho] = k_B T \\int \\left[B_2 \\rho^2 + + B_3 \\rho^3 + \\ldots \\right] d\\mathbf{r} where :math:`B_n` are the virial coefficients. diff --git a/python/flyft/solver.py b/python/flyft/solver.py index ca1ebb3..67831ad 100644 --- a/python/flyft/solver.py +++ b/python/flyft/solver.py @@ -22,13 +22,19 @@ class PicardIteration(Solver, mirrorclass=_flyft.PicardIteration): to solve the equilibrium condition in density functional theory: .. math:: - \\rho_{i}^{j+1}(\\mathbf{r}) = (1-\\alpha) \\rho_i^{j}(\\mathbf{r}) + \\alpha \\rho_i^{j}(\\mathbf{r}) + \\rho_{i}^{j+1}(\\mathbf{r}) = (1-\\alpha) \\rho_i^{j}(\\mathbf{r}) + + \\alpha \\rho_i^{j}(\\mathbf{r}) - where :math:`\\alpha` is the mixing parameter, :math:`\\rho_i^{j+1}(\\mathbf{r})` is the new density field - value, and :math:`\\rho_i^{j}(\\mathbf{r})` is the current estimate of the density field which is given by: + where :math:`\\alpha` is the mixing parameter, + :math:`\\rho_i^{j+1}(\\mathbf{r})` is the new density field + value, and :math:`\\rho_i^{j}(\\mathbf{r})` is the current estimate of the + density field which is given by: .. math:: - \\rho_i^{j}(\\mathbf{r}) = \\exp\\left(\\beta \\frac{\\delta F_{ex}}{\\delta \\rho_i(\\mathbf{r})} - \\beta V_{ext,i}(\\mathbf{r})\\right) + \\rho_i^{j}(\\mathbf{r}) = \rho^i_{bulk}\\exp\\left(\\beta + \\frac{\\delta F_{ex}}{\\delta + \\rho_i(\\mathbf{r})} - + \\beta V_{ext,i}(\\mathbf{r})\\right) The mixing parameter controls convergence stability: smaller values provide better stability but slower convergence, while larger values converge diff --git a/python/flyft/state.py b/python/flyft/state.py index 716b5a7..a9ae43b 100644 --- a/python/flyft/state.py +++ b/python/flyft/state.py @@ -124,7 +124,8 @@ def __getitem__(self, key): class Mesh(mirror.Mirror, mirrorclass=_flyft.Mesh): r"""Base class for computational meshes. - `Mesh` discretizes the domain for the density field. It sets up and provides information regarding + `Mesh` discretizes the domain for the density field. It sets up and + provides information regarding mesh points, mesh volumes, and geometry boundaries. Attributes From a7126bab1d7f3a98181dc48f1ba24682c35b58df Mon Sep 17 00:00:00 2001 From: mayukhkundu14 <79770572+mayukh33@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:20:32 -0600 Subject: [PATCH 4/7] Create documentation for flyft --- .gitignore | 4 +- .readthedocs.yml | 17 +++++ LICENSE | 26 +++++++ doc/Makefile | 20 +++++ doc/make.bat | 35 +++++++++ doc/requirements.txt | 7 ++ doc/source/_images/flyft_logo.svg | 36 +++++++++ doc/source/_templates/autosummary/class.rst | 29 +++++++ doc/source/api.rst | 56 ++++++++++++++ doc/source/conf.py | 77 +++++++++++++++++++ doc/source/index.rst | 31 ++++++++ doc/source/install.rst | 83 +++++++++++++++++++++ doc/source/license.rst | 6 ++ python/flyft/dynamics.py | 60 +++++---------- python/flyft/functional.py | 12 ++- requirements.txt | 4 + 16 files changed, 452 insertions(+), 51 deletions(-) create mode 100644 .readthedocs.yml create mode 100644 LICENSE create mode 100644 doc/Makefile create mode 100644 doc/make.bat create mode 100644 doc/requirements.txt create mode 100644 doc/source/_images/flyft_logo.svg create mode 100644 doc/source/_templates/autosummary/class.rst create mode 100644 doc/source/api.rst create mode 100644 doc/source/conf.py create mode 100644 doc/source/index.rst create mode 100644 doc/source/install.rst create mode 100644 doc/source/license.rst create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index af5310d..bd09e5d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .DS_Store -/build +build __pycache__ +doc/source/generated +*.egg-info diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..ce85599 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,17 @@ +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.11" + +sphinx: + configuration: doc/source/conf.py + fail_on_warning: true + +python: + install: + - requirements: requirements.txt + - requirements: doc/requirements.txt + - method: pip + path: . diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..14a0229 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2021-2025, Auburn University + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 0000000..ceca0a2 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,7 @@ +furo +ipython==9.3.0 +MyST-Parser +nbsphinx==0.9.7 +sphinx==8.1.3 +sphinx_design==0.6.1 +sphinx_favicon==1.0.1 diff --git a/doc/source/_images/flyft_logo.svg b/doc/source/_images/flyft_logo.svg new file mode 100644 index 0000000..d5aa312 --- /dev/null +++ b/doc/source/_images/flyft_logo.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/source/_templates/autosummary/class.rst b/doc/source/_templates/autosummary/class.rst new file mode 100644 index 0000000..25ca004 --- /dev/null +++ b/doc/source/_templates/autosummary/class.rst @@ -0,0 +1,29 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + + {% block methods %} + {% if methods %} + .. rubric:: {{ _('Methods:') }} + + .. autosummary:: + {% for item in methods %} + {%- if not item in ['__init__'] %} + ~{{ name }}.{{ item }} + {%- endif -%} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Attributes:') }} + + .. autosummary:: + {% for item in attributes %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/doc/source/api.rst b/doc/source/api.rst new file mode 100644 index 0000000..874145a --- /dev/null +++ b/doc/source/api.rst @@ -0,0 +1,56 @@ +API +--- + +Free-energy models for hard-sphere fluids +========================================= + +Free-energy functionals is an important component of the dynamic density functional functional +theory (DDFT) framework. Free-energy functional has three components: ideal-gas, excess, and external +potential contributions. ``flyft`` package provides several built-in free-energy +functionals for hard-sphere fluids to model the different excess and external contributions. + + + +Excess free-energy functional +============================= + +Depending on the required accuracy and density of the system, different +free-energy functionals can be employed. + +.. autosummary:: + :nosignatures: + :toctree: generated/ + + flyft.functional.IdealGas + flyft.functional.VirialExpansion + flyft.functional.BoublikHardSphere + flyft.functional.RosenfeldFMT + +External free-energy functional +=============================== + +Depending on the required + +.. autosummary:: + :nosignatures: + :toctree: generated/ + + flyft.external.LinearPotential + flyft.external.HardWall + flyft.external.HarmonicWall + +Flux models for hard-sphere fluids +================================== + +In addition to free-energy functionals, flux models are also a key component of the DDFT +framework. Flux models define how particles move in response to gradients in chemical potential +and external forces. The ``flyft`` package provides several built-in flux models for hard-sphere +fluids. These include: + +.. autosummary:: + :nosignatures: + :toctree: generated/ + + flyft.dynamics.BrownianDiffusiveFlux + flyft.dynamics.CompositeFlux + flyft.dynamics.RPYDiffusiveFlux diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..34b5170 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,77 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import datetime +import os +import sys + +project = 'flyft' +year = datetime.date.today().year +copyright = f"2021-{year}, Auburn University" +author = 'Michael P. Howard' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "IPython.sphinxext.ipython_console_highlighting", + "nbsphinx", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.intersphinx", + "sphinx.ext.mathjax", + "sphinx.ext.napoleon", + "sphinx_design", + "sphinx_favicon", + "myst_parser", +] + +templates_path = ['_templates'] +exclude_patterns = [] + + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "furo" +html_static_path = [] +html_theme_options = { + "navigation_with_keys": True, + "top_of_page_buttons": [], + "dark_css_variables": { + "color-brand-primary": "#ef904d", + "color-brand-content": "#ef904d", + "color-admonition-background": "#ef904d", + }, + "light_css_variables": { + "color-brand-primary": "#e86100", + "color-brand-content": "#e86100", + "color-admonition-background": "#e86100", + }, + "sidebar_hide_name": True, +} +html_logo = "_images/flyft_logo.svg" +# html_favicon = "_images/flyft_icon.svg" + +# -- Options for autodoc & autosummary --------------------------------------- + +autosummary_generate = True + +autodoc_member_order = "bysource" + +autodoc_default_options = {"inherited-members": None, "special-members": False} +autodoc_typehints = "none" + +# -- Options for intersphinx ------------------------------------------------- + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable", None), +} + diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..3bb749c --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,31 @@ +.. flyft documentation master file, created by + sphinx-quickstart on Thu Dec 4 19:14:04 2025. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +flyft documentation +=================== + +flyft is a Python package for conducting dynamic density functional theory (DDFT) +simulations of hard-sphere fluids. It provides a flexible framework for defining +free-energy functionals, external potentials, and numerical solvers to study the +time evolution of density profiles in various geometries. + +.. toctree:: + :maxdepth: 1 + :caption: Getting started + + ./install + +.. toctree:: + :maxdepth: 2 + :caption: Reference + + ./api + +.. toctree:: + :maxdepth: 1 + :caption: Additional information + + ./license + diff --git a/doc/source/install.rst b/doc/source/install.rst new file mode 100644 index 0000000..989a31e --- /dev/null +++ b/doc/source/install.rst @@ -0,0 +1,83 @@ +============ +Installation +============ + +Building from source +==================== + +``flyft`` currently can only be installed from source. + +Before installing ``flyft``, the pre-requisite software should be installed. + + * `CMake (version > 3.18) `__ + * `FFTW `__ + + +First, clone the repository from GitHub: + +.. code:: bash + + git clone git@github.com:mphowardlab/flyft.git + cd flyft + +Then, make a `build` directory and change directory to it + +.. code:: bash + + mkdir build + cd build + +Create a virtual environment in the `build` directory and activate it + +.. code:: bash + + conda create -p ./env python=3.11 + conda activate ./env + +Now, install the packages required for building ``flyft`` and can be installed using the +requirements file + +.. code:: bash + + pip install -r requirements.txt + +Finally, to install ``flyft``, run the following command from the `build` directory + +.. code:: bash + + cmake .. + make + make install + +.. note:: + + Before running `make install`, make sure the `CMAKE_INSTALL_PREFIX` environment variable + points to your conda environment. Check it by going to the build directory + and run the following command: + + .. code:: bash + + ccmake .. + + `CMAKE_INSTALL_PREFIX` variable should have the path of the site packages directory of your conda + environment. + + You can find the path of the site packages directory by running the following command + + .. code:: bash + + python -c "import site; print(site.getsitepackages()[0])" + +A suite of unit tests is provided with the source code and can be run with `pytest` from the `build` directory: + +.. code:: bash + + pytest python/ + +You can build the documentation from source with: + +.. code:: bash + + cd doc + pip install -r doc/requirements.txt + make html diff --git a/doc/source/license.rst b/doc/source/license.rst new file mode 100644 index 0000000..ffd9466 --- /dev/null +++ b/doc/source/license.rst @@ -0,0 +1,6 @@ +======= +License +======= + +.. literalinclude:: ../../LICENSE + :language: none diff --git a/python/flyft/dynamics.py b/python/flyft/dynamics.py index 62d83ae..ac320f7 100644 --- a/python/flyft/dynamics.py +++ b/python/flyft/dynamics.py @@ -4,23 +4,14 @@ class Flux(mirror.Mirror, mirrorclass=_flyft.Flux): - """Base class for computing particle fluxes. - - `Flux` computes the flux of particles for different confining geometries. - The flux is used by integrators to update density fields over time. - - All flux classes must implement the `compute` method to calculate fluxes - for all particle types in the system. - """ - compute = mirror.Method() fluxes = mirror.WrappedProperty(Fields) class CompositeFlux(Flux, CompositeMixin, mirrorclass=_flyft.CompositeFlux): - """Composite flux combining multiple flux components. + r"""Composite flux combining multiple flux components. - A `CompositeFlux` allows combining multiple flux objects into a single + `CompositeFlux` allows combining multiple flux objects into a single flux calculation. Parameters @@ -48,18 +39,17 @@ def __init__(self, objects=None): class BrownianDiffusiveFlux(Flux, mirrorclass=_flyft.BrownianDiffusiveFlux): - """Brownian diffusive flux for particle transport. + r"""Brownian diffusive flux for particle transport. - Computes particle flux due to Brownian diffusion, including both ideal - (Fickian) diffusion and contributions from gradients in the excess chemical - potential and external potentials. The flux is given by: + Computes particle flux with free-draining hydrodynamics. The flux is given by: .. math:: - \\mathbf{J} = -D \\left( \\nabla \\rho + \\rho \\nabla \\mu_{\\text{ex}} - + \\rho \\nabla V \\right) - where :math:`D` is the diffusivity, :math:`\\rho` is the density, - :math:`\\mu_{\\text{ex}}` is the excess chemical potential, and :math:`V` + \mathbf{j_{\rm BD}} = -D\rho(\mathbf{r}, t) \nabla_{\mathbf{r}} + \frac{\delta\mathcal{F}(\mathbf{r}, t)}{\delta \rho(\mathbf{r}, t)} + + where :math:`D` is the diffusivity, :math:`\rho` is the density, + :math:`\mu_{\text{ex}}` is the excess chemical potential, and :math:`V` is the external potential. Attributes @@ -83,24 +73,12 @@ class BrownianDiffusiveFlux(Flux, mirrorclass=_flyft.BrownianDiffusiveFlux): class RPYDiffusiveFlux(Flux, mirrorclass=_flyft.RPYDiffusiveFlux): - """Rotne-Prager-Yamakawa (RPY) diffusive flux with hydrodynamic + r"""Rotne-Prager-Yamakawa (RPY) diffusive flux with hydrodynamic interactions. Computes particle flux including hydrodynamic interactions between particles - using the Rotne-Prager-Yamakawa mobility tensor for hard spheres. - For particles i and j separated by distance r > d (where d = (a_i + a_j)): - - .. math:: - \\mathbf{M}_{ij} = \\frac{1}{8\\pi\\eta r} \\left[\\left(1 - + \\frac{a_i^2+a_j^2}{3r^2}\\right)\\mathbf{I} - + \\left(1 - - \\frac{a_i^2+ a_j^2}{r^2}\\right)\\mathbf{r}} - \\mathbf{r} \\right] - - where :math:`\\eta` is the fluid viscosity, :math:`a_i` and :math:`a_j` - are the particle radii, :math:`r` is the distance between particles, - :math:`\\mathbf{I}` is the identity tensor, and :math:`\\mathbf{r}` - is the unit vector connecting the particle centers. + using the Rotne-Prager-Yamakawa mobility tensor for hard spheres. The flux + is given by: Attributes ---------- @@ -127,7 +105,7 @@ class RPYDiffusiveFlux(Flux, mirrorclass=_flyft.RPYDiffusiveFlux): class Integrator(mirror.Mirror, mirrorclass=_flyft.Integrator): - """Base class for time integration of density fields. + r"""Base class for time integration of density fields. An `Integrator` advances the system state in time by applying fluxes computed from the grand potential and current state. It supports both @@ -149,6 +127,7 @@ class Integrator(mirror.Mirror, mirrorclass=_flyft.Integrator): Error tolerance for adaptive timestep control. adapt_minimum : float Minimum allowed timestep for adaptive control. + """ advance = mirror.Method() @@ -192,7 +171,7 @@ def use_adaptive(self, delay=0, tolerance=1.0e-8, minimum=1.0e-8): class CrankNicolsonIntegrator( Integrator, FixedPointAlgorithmMixin, mirrorclass=_flyft.CrankNicolsonIntegrator ): - """Crank-Nicolson time integrator with second-order accuracy. + r"""Crank-Nicolson time integrator with second-order accuracy. Implements the Crank-Nicolson scheme, which is second-order accurate in time and unconditionally stable. The scheme uses an implicit trapezoidal rule @@ -236,7 +215,7 @@ def __init__(self, timestep, mix_parameter, max_iterations, tolerance): class ExplicitEulerIntegrator(Integrator, mirrorclass=_flyft.ExplicitEulerIntegrator): - """Explicit Euler time integrator with first-order accuracy. + r"""Explicit Euler time integrator with first-order accuracy. Implements the explicit (forward) Euler method, which is first-order accurate in time and conditionally stable. The scheme uses the current @@ -261,12 +240,6 @@ class ExplicitEulerIntegrator(Integrator, mirrorclass=_flyft.ExplicitEulerIntegr integrator = flyft.dynamics.ExplicitEulerIntegrator(timestep=0.001) - Note - ---- - The explicit Euler method has stability constraints that depend on the - diffusivity and mesh spacing. Typically, the timestep must satisfy - :math:`\\Delta t < \\Delta x^2 / (2D)` where :math:`\\Delta x` is the - mesh spacing and :math:`D` is the diffusivity. """ def __init__(self, timestep): @@ -318,6 +291,7 @@ class ImplicitEulerIntegrator( The implicit Euler method is unconditionally stable, allowing larger timesteps than explicit methods. However, it requires solving a nonlinear system at each timestep, which can be computationally expensive. + """ def __init__(self, timestep, mix_parameter, max_iterations, tolerance): diff --git a/python/flyft/functional.py b/python/flyft/functional.py index 02ba81e..68ca553 100644 --- a/python/flyft/functional.py +++ b/python/flyft/functional.py @@ -233,9 +233,6 @@ class RosenfeldFMT(Functional, mirrorclass=_flyft.RosenfeldFMT): accurate description of hard sphere correlations and excluded volume effects using weighted densities based on fundamental measures. - The Rosenfeld functional is particularly accurate for inhomogeneous - hard sphere systems and forms the basis for more advanced FMT - functionals like White Bear. Attributes ---------- @@ -261,13 +258,14 @@ class VirialExpansion(Functional, mirrorclass=_flyft.VirialExpansion): """Virial expansion functional for weakly interacting systems. Implements a virial expansion functional that approximates the excess - free energy using virial coefficients: + free energy using virial coefficients. In this case the second order virial + expansion is considered: .. math:: - F_{\\text{excess}}[\\rho] = k_B T \\int \\left[B_2 \\rho^2 + - B_3 \\rho^3 + \\ldots \\right] d\\mathbf{r} - where :math:`B_n` are the virial coefficients. + F^{\rm{ex}}[\rho] = k_B T 4 \\eta + + where, :math:`\\eta` is the volume fraction of the system. Attributes ---------- diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f067114 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +numpy +pytest +pytest_lazy_fixtures +packaging From 031566ee1fc3a9c41d3d175c0a3a64201cce1211 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 22:21:09 +0000 Subject: [PATCH 5/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/source/_images/flyft_logo.svg | 2 +- doc/source/api.rst | 12 ++++++------ doc/source/conf.py | 8 +++----- doc/source/index.rst | 3 +-- doc/source/install.rst | 26 +++++++++++++------------- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/doc/source/_images/flyft_logo.svg b/doc/source/_images/flyft_logo.svg index d5aa312..bb9b5cc 100644 --- a/doc/source/_images/flyft_logo.svg +++ b/doc/source/_images/flyft_logo.svg @@ -33,4 +33,4 @@ - \ No newline at end of file + diff --git a/doc/source/api.rst b/doc/source/api.rst index 874145a..d1935c5 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -15,26 +15,26 @@ Excess free-energy functional ============================= Depending on the required accuracy and density of the system, different -free-energy functionals can be employed. +free-energy functionals can be employed. .. autosummary:: :nosignatures: :toctree: generated/ - + flyft.functional.IdealGas flyft.functional.VirialExpansion flyft.functional.BoublikHardSphere flyft.functional.RosenfeldFMT - + External free-energy functional =============================== -Depending on the required +Depending on the required .. autosummary:: :nosignatures: :toctree: generated/ - + flyft.external.LinearPotential flyft.external.HardWall flyft.external.HarmonicWall @@ -50,7 +50,7 @@ fluids. These include: .. autosummary:: :nosignatures: :toctree: generated/ - + flyft.dynamics.BrownianDiffusiveFlux flyft.dynamics.CompositeFlux flyft.dynamics.RPYDiffusiveFlux diff --git a/doc/source/conf.py b/doc/source/conf.py index 34b5170..7385d01 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -10,10 +10,10 @@ import os import sys -project = 'flyft' +project = "flyft" year = datetime.date.today().year copyright = f"2021-{year}, Auburn University" -author = 'Michael P. Howard' +author = "Michael P. Howard" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration @@ -31,11 +31,10 @@ "myst_parser", ] -templates_path = ['_templates'] +templates_path = ["_templates"] exclude_patterns = [] - # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output @@ -74,4 +73,3 @@ "python": ("https://docs.python.org/3", None), "numpy": ("https://numpy.org/doc/stable", None), } - diff --git a/doc/source/index.rst b/doc/source/index.rst index 3bb749c..f49077c 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -20,7 +20,7 @@ time evolution of density profiles in various geometries. .. toctree:: :maxdepth: 2 :caption: Reference - + ./api .. toctree:: @@ -28,4 +28,3 @@ time evolution of density profiles in various geometries. :caption: Additional information ./license - diff --git a/doc/source/install.rst b/doc/source/install.rst index 989a31e..6f428e2 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -7,12 +7,12 @@ Building from source ``flyft`` currently can only be installed from source. -Before installing ``flyft``, the pre-requisite software should be installed. +Before installing ``flyft``, the pre-requisite software should be installed. * `CMake (version > 3.18) `__ * `FFTW `__ - + First, clone the repository from GitHub: .. code:: bash @@ -34,7 +34,7 @@ Create a virtual environment in the `build` directory and activate it conda create -p ./env python=3.11 conda activate ./env -Now, install the packages required for building ``flyft`` and can be installed using the +Now, install the packages required for building ``flyft`` and can be installed using the requirements file .. code:: bash @@ -51,19 +51,19 @@ Finally, to install ``flyft``, run the following command from the `build` direct .. note:: - Before running `make install`, make sure the `CMAKE_INSTALL_PREFIX` environment variable - points to your conda environment. Check it by going to the build directory - and run the following command: - + Before running `make install`, make sure the `CMAKE_INSTALL_PREFIX` environment variable + points to your conda environment. Check it by going to the build directory + and run the following command: + .. code:: bash ccmake .. - + `CMAKE_INSTALL_PREFIX` variable should have the path of the site packages directory of your conda - environment. - - You can find the path of the site packages directory by running the following command - + environment. + + You can find the path of the site packages directory by running the following command + .. code:: bash python -c "import site; print(site.getsitepackages()[0])" @@ -79,5 +79,5 @@ You can build the documentation from source with: .. code:: bash cd doc - pip install -r doc/requirements.txt + pip install -r doc/requirements.txt make html From 26cec5d67f1b9e9516c19ebf44bbc5a49f88cd38 Mon Sep 17 00:00:00 2001 From: mayukhkundu14 <79770572+mayukh33@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:22:44 -0600 Subject: [PATCH 6/7] Pre-commit fixes --- doc/source/conf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 7385d01..93e0340 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -7,8 +7,6 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information import datetime -import os -import sys project = "flyft" year = datetime.date.today().year From 6ac3d693269be198c4f19ed35da9226662a2ba2a Mon Sep 17 00:00:00 2001 From: mayukhkundu14 <79770572+mayukh33@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:16:02 -0600 Subject: [PATCH 7/7] Update documentation and installation instructions; add conda environment configuration --- CMakeLists.txt | 17 ++++++++++++ doc/source/_images/flyft_logo.svg | 22 ++++++---------- doc/source/api.rst | 25 +++++++++++++----- doc/source/install.rst | 43 ++++++++++--------------------- env.yml | 10 +++++++ requirements.txt | 4 --- 6 files changed, 66 insertions(+), 55 deletions(-) create mode 100644 env.yml delete mode 100644 requirements.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 097066e..51cf7b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,23 @@ if(FLYFT_TESTING) endif() add_subdirectory(src) + if(FLYFT_PYTHON) add_subdirectory(python) + # set(PYTHON_SITE_INSTALL_DIR "${_python_site_package_rel}/flyft" CACHE PATH "Python site-packages directory (relative to CMAKE_INSTALL_PREFIX)" FORCE) endif() + +find_package(Python REQUIRED COMPONENTS Interpreter Development) +execute_process( + COMMAND ${Python_EXECUTABLE} -c "import site; print(site.getsitepackages()[0])" + OUTPUT_VARIABLE _python_site_packages + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_VARIABLE _error +) + +if(_error) + message(FATAL_ERROR "Failed to find Python site-packages: ${_error}") +endif() + +set(CMAKE_INSTALL_PREFIX ${_python_site_packages} CACHE PATH "Install prefix" FORCE) +message(STATUS "Setting CMAKE_INSTALL_PREFIX to Python site-packages: ${CMAKE_INSTALL_PREFIX}") diff --git a/doc/source/_images/flyft_logo.svg b/doc/source/_images/flyft_logo.svg index bb9b5cc..a06fd72 100644 --- a/doc/source/_images/flyft_logo.svg +++ b/doc/source/_images/flyft_logo.svg @@ -1,5 +1,5 @@ - + - + - + - - - - - - - + + + + + diff --git a/doc/source/api.rst b/doc/source/api.rst index d1935c5..7252cdb 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -2,15 +2,13 @@ API --- Free-energy models for hard-sphere fluids -========================================= +----------------------------------------- Free-energy functionals is an important component of the dynamic density functional functional theory (DDFT) framework. Free-energy functional has three components: ideal-gas, excess, and external potential contributions. ``flyft`` package provides several built-in free-energy functionals for hard-sphere fluids to model the different excess and external contributions. - - Excess free-energy functional ============================= @@ -29,7 +27,8 @@ free-energy functionals can be employed. External free-energy functional =============================== -Depending on the required +We also provide several built-in external potential models to represent +various confinement geometries and external fields for hard-sphere fluids. .. autosummary:: :nosignatures: @@ -39,11 +38,11 @@ Depending on the required flyft.external.HardWall flyft.external.HarmonicWall -Flux models for hard-sphere fluids -================================== +Mobility tensor for hard-sphere fluids +-------------------------------------- In addition to free-energy functionals, flux models are also a key component of the DDFT -framework. Flux models define how particles move in response to gradients in chemical potential +framework. Mobility tensor define how particles move in response to gradients in chemical potential and external forces. The ``flyft`` package provides several built-in flux models for hard-sphere fluids. These include: @@ -54,3 +53,15 @@ fluids. These include: flyft.dynamics.BrownianDiffusiveFlux flyft.dynamics.CompositeFlux flyft.dynamics.RPYDiffusiveFlux + +Iterative solvers +----------------- + +The ``flyft`` package also provides iterative solvers to find equilibrium density profiles +for hard-sphere fluids under various free-energy functionals and external potentials. + +.. autosummary:: + :nosignatures: + :toctree: generated/ + + flyft.solver.PicardIteration diff --git a/doc/source/install.rst b/doc/source/install.rst index 6f428e2..0db0beb 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -20,53 +20,36 @@ First, clone the repository from GitHub: git clone git@github.com:mphowardlab/flyft.git cd flyft -Then, make a `build` directory and change directory to it +Then, make a `build` directory with a conda environment using the following command: .. code:: bash - mkdir build - cd build + conda env create --prefix=build/env -f env.yml -Create a virtual environment in the `build` directory and activate it +After running the aforementioned command, activate the conda environment using: .. code:: bash - conda create -p ./env python=3.11 - conda activate ./env + conda activate build/env -Now, install the packages required for building ``flyft`` and can be installed using the -requirements file -.. code:: bash - - pip install -r requirements.txt - -Finally, to install ``flyft``, run the following command from the `build` directory +Configure ``flyft`` with CMake: .. code:: bash - cmake .. - make - make install - -.. note:: + cmake -B ./build - Before running `make install`, make sure the `CMAKE_INSTALL_PREFIX` environment variable - points to your conda environment. Check it by going to the build directory - and run the following command: +Build ``flyft``: - .. code:: bash - - ccmake .. +.. code:: bash - `CMAKE_INSTALL_PREFIX` variable should have the path of the site packages directory of your conda - environment. + cmake --build build - You can find the path of the site packages directory by running the following command +Install ``flyft``: - .. code:: bash +.. code:: bash - python -c "import site; print(site.getsitepackages()[0])" + cmake --install build A suite of unit tests is provided with the source code and can be run with `pytest` from the `build` directory: @@ -79,5 +62,5 @@ You can build the documentation from source with: .. code:: bash cd doc - pip install -r doc/requirements.txt + pip install -r requirements.txt make html diff --git a/env.yml b/env.yml new file mode 100644 index 0000000..42ebbbc --- /dev/null +++ b/env.yml @@ -0,0 +1,10 @@ +name: env +channels: + - conda-forge +dependencies: + - python=3.12 #Sphinx-design still does not support 3.13 + - numpy + - pytest + - pybind11 + - pip: + - pytest_lazy_fixtures diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f067114..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -numpy -pytest -pytest_lazy_fixtures -packaging