Skip to main content

findiff

PyPI version build Coverage Doc Status PyPI downloads Downloads

A Python package for finite difference numerical derivatives and partial differential equations in any number of dimensions.

Main Features

  • Differentiate arrays of any number of dimensions along any axis with any desired accuracy order
  • Accurate treatment of grid boundary
  • Can handle uniform and non-uniform grids
  • Can handle arbitrary linear combinations of derivatives with constant and variable coefficients
  • Fully vectorized for speed
  • GPU / JAX / CuPy support — pass JAX or CuPy arrays directly, combine with jax.jit for acceleration
  • Standard operators from vector calculus: gradient, divergence, curl, Laplacian
  • Matrix representations of arbitrary linear differential operators
  • Solve partial differential equations with Dirichlet, Neumann or Robin boundary conditions
  • Solve eigenvalue problems (e.g. Schrodinger equation, vibration modes)
  • Direct and iterative solvers with preconditioner support
  • Calculate raw finite difference coefficients for any derivative and accuracy order
  • Generate differential operators for arbitrary stencils
  • Symbolic representation of finite difference schemes
  • Estimate truncation error by comparing accuracy orders
  • Solve time-dependent PDEs via Method of Lines (Forward Euler, RK4, Backward Euler, Crank-Nicolson)
  • New in version 0.11: More comfortable API (keeping the old API available)
  • New in version 0.12: Periodic boundary conditions for differential operators and PDEs.
  • New in version 0.13: Compact (implicit) finite differences with spectral-like resolution.
  • New in version 0.14: Time-dependent PDE solving via Method of Lines. GPU / JAX / CuPy backend support for operator application. Eigenvalue problems, iterative solvers and Robin boundary conditions for PDEs. Error estimation via accuracy order comparison.
  • New in version 0.15: Compact finite differences on non-uniform grids, optimal (Lele) and spectral-like compact schemes, much faster compact schemes.

Installation

pip install --upgrade findiff

For GPU / JAX support, install JAX separately (findiff detects it automatically):

pip install jax          # CPU-only
pip install jax[cuda12]  # NVIDIA GPU

For CuPy support:

pip install cupy-cuda12x

Documentation and Examples

You can find the documentation of the code including examples of application at https://maroba.github.io/findiff/.

Taking Derivatives

findiff allows to easily define derivative operators that you can apply to numpy arrays of any dimension. JAX and CuPy arrays work too — see GPU / JAX Support below.

Consider the simple 1D case of a equidistant grid with a first derivative $\displaystyle \frac{\partial}{\partial x}$ along the only axis (0):

import numpy as np
from findiff import Diff

# define the grid:
x = np.linspace(0, 1, 100)

# the array to differentiate:
f = np.sin(x)  # as an example

# Define the derivative:
d_dx = Diff(0, x[1] - x[0])

# Apply it:
df_dx = d_dx(f) 

Similarly, you can define partial derivatives along other axes, for example, if $z$ is the 2-axis, we can write $\frac{\partial}{\partial z}$ as:

Diff(2, dz)

Diff always creates a first derivative. For higher derivatives, you simply exponentiate them, for example for $\frac{\partial^2}{\partial_x^2}$

d2_dx2 = Diff(0, dx)**2

and apply it as before.

You can also define more general differential operators intuitively, like

$$ 2x \frac{\partial^3}{\partial x^2 \partial z} + 3 \sin(y)z^2 \frac{\partial^3}{\partial x \partial y^2} $$

which can be written as

# define the operator
diff_op = 2 * X * Diff(0)**2 * Diff(2) + 3 * sin(Y) * Z**2 * Diff(0) * Diff(1)**2

# set the grid you use (equidistant here)
diff_op.set_grid({0: dx, 1: dy, 2: dz})

# apply the operator
result = diff_op(f)

where X, Y, Z are numpy arrays with meshed grid points. Here you see that you can also define your grid lazily.

Of course, standard operators from vector calculus like gradient, divergence and curl are also available as shortcuts.

If one or more axis of your grid are periodic, you can specify that when defining the derivative or later when setting the grid. For example:

d_dx = Diff(0, dx, periodic=True)

# or later
d_dx = Diff(0)
d_dx.set_grid({0: {"h": dx, "periodic": True}})

More examples can be found in the documentation and in this blog.

GPU / JAX Support

All operators (Diff, Gradient, Divergence, Curl, Laplacian) accept JAX and CuPy arrays directly. Just pass the array — findiff detects the backend automatically:

import jax
import jax.numpy as jnp
from findiff import Diff, Laplacian

jax.config.update("jax_enable_x64", True)

x = jnp.linspace(0, 2 * jnp.pi, 1000)
dx = x[1] - x[0]
f = jnp.sin(x)

d_dx = Diff(0, dx)
df_dx = d_dx(f)  # returns a JAX array

For best performance, wrap operators with jax.jit to fuse all slice operations into a single compiled kernel:

d_dx_jit = jax.jit(d_dx)
df_dx = d_dx_jit(f)  # first call compiles, subsequent calls are fast

# Works for any operator, including higher-order and vector calculus:
lap = Laplacian(h=[dx, dy, dz])
lap_jit = jax.jit(lap)
result = lap_jit(f_3d)

Note: The matrix-based path (.matrix(), PDE solving, eigenvalue problems) remains NumPy/SciPy only. GPU support applies to the operator application path (operator(array)).

Accuracy Control

When constructing an instance of Diff, you can request the desired accuracy order by setting the keyword argument acc. For example:

d_dx = Diff(0, dx, acc=4)
df_dx = d_dx(f)

Alternatively, you can also split operator definition and configuration:

d_dx = Diff(0, dx)
d_dx.set_accuracy(4)
df_dx = d_dx(f)

which comes in handy if you have a complicated expression of differential operators, because then you can specify it on the whole expression and it will be passed down to all basic operators.

If not specified, second order accuracy will be taken by default.

Compact Finite Differences

Standard finite differences only use function values to approximate a derivative. Compact (or implicit) finite differences also couple derivative values at neighboring points, which gives you spectral-like resolution from small stencils. The tradeoff is that applying the operator requires solving a banded linear system — but for tridiagonal systems that's $O(N)$ and very fast.

You can define a compact scheme explicitly by specifying the left-hand side coefficients and right-hand side offsets:

from findiff import Diff, CompactScheme

scheme = CompactScheme(
    deriv=1,
    left={-1: 1/3, 0: 1, 1: 1/3},     # tridiagonal LHS
    right=[-3, -2, -1, 0, 1, 2, 3],    # RHS offsets (coefficients computed automatically)
)

d_dx = Diff(0, dx, scheme=scheme, periodic=True)
df_dx = d_dx(np.sin(x))  # 6th-order accurate first derivative

Or let findiff pick a scheme for you:

d_dx = Diff(0, dx, compact=3, acc=6, periodic=True)

Here compact=3 sets the LHS bandwidth (must be odd). findiff determines the LHS and RHS coefficients jointly (as in Lele, 1992) and picks the narrowest RHS stencil that reaches the requested accuracy — e.g. compact=3, acc=6 gives Lele's classic tridiagonal scheme and compact=5, acc=10 his pentadiagonal 10th-order scheme. Higher derivatives work the same as usual:

d2_dx2 = d_dx ** 2

Non-periodic grids are handled automatically — findiff uses one-sided compact stencils near the boundaries. Non-uniform grids work as well: pass the coordinates instead of the spacing, e.g. Diff(0, x, compact=3, acc=6). For multi-scale problems, spectral=True gives Lele's spectral-like schemes, which resolve short waves much better. The matrix() method is also supported. For more details, see the compact finite differences documentation.

Finite Difference Coefficients

Sometimes you may want to have the raw finite difference coefficients. These can be obtained for any derivative and accuracy order using findiff.coefficients(deriv, acc). For instance,

import findiff
coefs = findiff.coefficients(deriv=3, acc=4, symbolic=True)

gives

{'backward': {'coefficients': [15/8, -13, 307/8, -62, 461/8, -29, 49/8],
              'offsets': [-6, -5, -4, -3, -2, -1, 0]},
 'center': {'coefficients': [1/8, -1, 13/8, 0, -13/8, 1, -1/8],
            'offsets': [-3, -2, -1, 0, 1, 2, 3]},
 'forward': {'coefficients': [-49/8, 29, -461/8, 62, -307/8, 13, -15/8],
             'offsets': [0, 1, 2, 3, 4, 5, 6]}}

If you want to specify the detailed offsets instead of the accuracy order, you can do this by setting the offset keyword argument:

import findiff
coefs = findiff.coefficients(deriv=2, offsets=[-2, 1, 0, 2, 3, 4, 7], symbolic=True)

The resulting accuracy order is computed and part of the output:

{'coefficients': [187/1620, -122/27, 9/7, 103/20, -13/5, 31/54, -19/2835], 
 'offsets': [-2, 1, 0, 2, 3, 4, 7], 
 'accuracy': 5}

Matrix Representation

For a given differential operator, you can get the matrix representation using the matrix(shape) method, e.g. for a small 1D grid of 10 points:

d2_dx2 = Diff(0, dx)**2
mat = d2_dx2.matrix((10,))  # this method returns a scipy sparse matrix
print(mat.toarray())

has the output

[[ 2. -5.  4. -1.  0.  0.  0.]
 [ 1. -2.  1.  0.  0.  0.  0.]
 [ 0.  1. -2.  1.  0.  0.  0.]
 [ 0.  0.  1. -2.  1.  0.  0.]
 [ 0.  0.  0.  1. -2.  1.  0.]
 [ 0.  0.  0.  0.  1. -2.  1.]
 [ 0.  0.  0. -1.  4. -5.  2.]]

If you have periodic boundary conditions, the matrix looks like that:

d2_dx2 = Diff(0, dx, periodic=True)**2
mat = d2_dx2.matrix((10,))  # this method returns a scipy sparse matrix
print(mat.toarray())
[[-2.  1.  0.  0.  0.  0.  1.]
 [ 1. -2.  1.  0.  0.  0.  0.]
 [ 0.  1. -2.  1.  0.  0.  0.]
 [ 0.  0.  1. -2.  1.  0.  0.]
 [ 0.  0.  0.  1. -2.  1.  0.]
 [ 0.  0.  0.  0.  1. -2.  1.]
 [ 1.  0.  0.  0.  0.  1. -2.]]

Stencils

findiff uses standard stencils (patterns of grid points) to evaluate the derivative. However, you can design your own stencil. A picture says more than a thousand words, so look at the following example for a standard second order accurate stencil for the 2D Laplacian $\displaystyle \frac{\partial^2}{\partial x^2} + \frac{\partial^2}{\partial y^2}$:

This can be reproduced by findiff writing

offsets = [(0, 0), (1, 0), (-1, 0), (0, 1), (0, -1)]
stencil = Stencil(offsets, partials={(2, 0): 1, (0, 2): 1}, spacings=(1, 1))

The attribute stencil.values contains the coefficients

{(0, 0): -4.0, (1, 0): 1.0, (-1, 0): 1.0, (0, 1): 1.0, (0, -1): 1.0}

Now for a some more exotic stencil. Consider this one:

With findiff you can get it easily:

offsets = [(0, 0), (1, 1), (-1, -1), (1, -1), (-1, 1)]
stencil = Stencil(offsets, partials={(2, 0): 1, (0, 2): 1}, spacings=(1, 1))
stencil.values

which returns

{(0, 0): -2.0, (1, 1): 0.5, (-1, -1): 0.5, (1, -1): 0.5, (-1, 1): 0.5}

Symbolic Representations

As of version 0.10, findiff can also provide a symbolic representation of finite difference schemes suitable for using in conjunction with sympy. The main use case is to facilitate deriving your own iteration schemes.

from findiff import SymbolicMesh, SymbolicDiff

mesh = SymbolicMesh("x, y")
u = mesh.create_symbol("u")
d2_dx2, d2_dy2 = [SymbolicDiff(mesh, axis=k, degree=2) for k in range(2)]

(
    d2_dx2(u, at=(m, n), offsets=(-1, 0, 1)) + 
    d2_dy2(u, at=(m, n), offsets=(-1, 0, 1))
)

Outputs:

$$ \frac{u_{m,n + 1} + u_{m,n - 1} - 2 u_{m,n}}{\Delta y^2} + \frac{u_{m + 1,n} + u_{m - 1,n} - 2 u_{m,n}}{\Delta x^2} $$

Also see the example notebook.

Partial Differential Equations

findiff can be used to easily formulate and solve partial differential equation problems

$$ \mathcal{L}u(\vec{x}) = f(\vec{x}) $$

where $\mathcal{L}$ is a general linear differential operator.

In order to obtain a unique solution, Dirichlet, Neumann or more general boundary conditions can be applied.

Boundary Value Problems

Example 1: 1D forced harmonic oscillator with friction

Find the solution of

$$ \left( \frac{d^2}{dt^2} - \alpha \frac{d}{dt} + \omega^2 \right)u(t) = \sin{(2t)} $$

subject to the (Dirichlet) boundary conditions

$$ u(0) = 0, \hspace{1em} u(10) = 1 $$

from findiff import Diff, Id, PDE

shape = (300, )
t = numpy.linspace(0, 10, shape[0])
dt = t[1]-t[0]

L = Diff(0, dt)**2 - Diff(0, dt) + 5 * Id()
f = numpy.cos(2*t)

bc = BoundaryConditions(shape)
bc[0] = 0
bc[-1] = 1

pde = PDE(L, f, bc)
u = pde.solve()

Result:

ResultHOBVP

Example 2: 2D heat conduction

A plate with temperature profile given on one edge and zero heat flux across the other edges, i.e.

$$ \left( \frac{\partial^2}{\partial x^2} + \frac{\partial^2}{\partial y^2} \right) u(x,y) = f(x,y) $$

with Dirichlet boundary condition

$$ \begin{align*} u(x,0) &= 300 \ u(1,y) &= 300 - 200y \end{align*} $$

and Neumann boundary conditions

$$ \begin{align*} \frac{\partial u}{\partial x} &= 0, & \text{ for } x = 0 \ \frac{\partial u}{\partial y} &= 0, & \text{ for } y = 0 \end{align*} $$

shape = (100, 100)
x, y = np.linspace(0, 1, shape[0]), np.linspace(0, 1, shape[1])
dx, dy = x[1]-x[0], y[1]-y[0]
X, Y = np.meshgrid(x, y, indexing='ij')

L = Diff(0, dx)**2 + Diff(1, dy)**2
f = np.zeros(shape)

bc = BoundaryConditions(shape)
bc[1,:] = Diff(0, dx), 0  # Neumann BC
bc[-1,:] = 300. - 200*Y   # Dirichlet BC
bc[:, 0] = 300.   # Dirichlet BC
bc[1:-1, -1] = Diff(1, dy), 0  # Neumann BC

pde = PDE(L, f, bc)
u = pde.solve()

Result:

Citations

You have used findiff in a publication? Here is how you can cite it:

M. Baer. findiff software package. URL: https://github.com/maroba/findiff. 2018

BibTeX entry:

@misc{findiff,
  title = {{findiff} Software Package},
  author = {M. Baer},
  url = {https://github.com/maroba/findiff},
  key = {findiff},
  note = {\url{https://github.com/maroba/findiff}},
  year = {2018}
}

Development

Set up development environment

  • Fork the repository
  • Clone your fork to your machine
  • Install in development mode:
pip install -e .

Running tests

From the console:

pip install pytest
pytest tests

Metadata

Release files for findiff 0.15.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for findiff 0.15.0
File Size Uploaded Provenance Actions
findiff-0.15.0.tar.gz 1.7 MB Without provenance Details for findiff-0.15.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for findiff 0.15.0
File Interpreter ABI Platform Actions
findiff-0.15.0-py3-none-any.whl Python 3 none any Details for findiff-0.15.0-py3-none-any.whl

Total release size: 1.8 MB

Release files / findiff-0.15.0.tar.gz

Download URL findiff-0.15.0.tar.gz
Size 1.7 MB
Tags Source
SHA-256 checksum
How to use checksums
e1b8d200d96dafb96f9aa6dd9b432b6f8855b91d97ddb533ae5a3c20b22d4a05
BLAKE2b-256 checksum
How to use checksums
615639e0cadc9f139fcba7cea2bff44f08aaa6a8e6ef658ed1b270bd041be0c9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.0

Release files / findiff-0.15.0-py3-none-any.whl

Download URL findiff-0.15.0-py3-none-any.whl
Size 48.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9138a8a9d1813b9b62303e9f261ca944b1d0810b2ef74ebcfa711ed38eb22053
BLAKE2b-256 checksum
How to use checksums
7defbd5014a8964e4b477c27ba9a93380cdfc9e08d10fae2bf67dc10f2421722
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.0

Security for findiff 0.15.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.15.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page