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