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/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/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..a06fd72 --- /dev/null +++ b/doc/source/_images/flyft_logo.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + 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..7252cdb --- /dev/null +++ b/doc/source/api.rst @@ -0,0 +1,67 @@ +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 +=============================== + +We also provide several built-in external potential models to represent +various confinement geometries and external fields for hard-sphere fluids. + +.. autosummary:: + :nosignatures: + :toctree: generated/ + + flyft.external.LinearPotential + flyft.external.HardWall + flyft.external.HarmonicWall + +Mobility tensor for hard-sphere fluids +-------------------------------------- + +In addition to free-energy functionals, flux models are also a key component of the DDFT +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: + +.. autosummary:: + :nosignatures: + :toctree: generated/ + + 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/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..93e0340 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,73 @@ +# 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 + +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..f49077c --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,30 @@ +.. 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..0db0beb --- /dev/null +++ b/doc/source/install.rst @@ -0,0 +1,66 @@ +============ +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 with a conda environment using the following command: + +.. code:: bash + + conda env create --prefix=build/env -f env.yml + +After running the aforementioned command, activate the conda environment using: + +.. code:: bash + + conda activate build/env + + +Configure ``flyft`` with CMake: + +.. code:: bash + + cmake -B ./build + +Build ``flyft``: + +.. code:: bash + + cmake --build build + +Install ``flyft``: + +.. code:: bash + + cmake --install build + +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 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/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/python/flyft/dynamics.py b/python/flyft/dynamics.py index 9ff8fdb..ac320f7 100644 --- a/python/flyft/dynamics.py +++ b/python/flyft/dynamics.py @@ -9,6 +9,29 @@ class Flux(mirror.Mirror, mirrorclass=_flyft.Flux): class CompositeFlux(Flux, CompositeMixin, mirrorclass=_flyft.CompositeFlux): + r"""Composite flux combining multiple flux components. + + `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 +39,97 @@ def __init__(self, objects=None): class BrownianDiffusiveFlux(Flux, mirrorclass=_flyft.BrownianDiffusiveFlux): + r"""Brownian diffusive flux for particle transport. + + Computes particle flux with free-draining hydrodynamics. The flux is given by: + + .. math:: + + \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 + ---------- + 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): + 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. The flux + is given by: + + 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): + 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 + 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 +138,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 +171,77 @@ def use_adaptive(self, delay=0, tolerance=1.0e-8, minimum=1.0e-8): class CrankNicolsonIntegrator( Integrator, FixedPointAlgorithmMixin, mirrorclass=_flyft.CrankNicolsonIntegrator ): + 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 + 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): + 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 + 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) + + """ + def __init__(self, timestep): super().__init__(timestep) @@ -54,5 +249,50 @@ 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..68ca553 100644 --- a/python/flyft/functional.py +++ b/python/flyft/functional.py @@ -4,22 +4,98 @@ 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 +103,57 @@ 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 +166,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 +188,143 @@ 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. + + + 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. In this case the second order virial + expansion is considered: + + .. math:: + + F^{\rm{ex}}[\rho] = k_B T 4 \\eta + + where, :math:`\\eta` is the volume fraction of the system. + + 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..f4ccf98 100644 --- a/python/flyft/parameter.py +++ b/python/flyft/parameter.py @@ -2,14 +2,82 @@ 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 +87,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..67831ad 100644 --- a/python/flyft/solver.py +++ b/python/flyft/solver.py @@ -2,10 +2,76 @@ 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}) = \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 + 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..a9ae43b 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,28 @@ 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 +151,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 +170,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 +189,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 +233,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 +277,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 +363,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,) 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