findiff Documentation

repository·master·Indexed 19 days ago

https://github.com/maroba/findiff

A Python package for high-performance finite difference numerical derivatives and partial differential equations in any number of dimensions. It supports NumPy, JAX, and CuPy for CPU and GPU acceleration. Key features include the Diff class for differential operators, symbolic representations via sympy (SymbolicMesh and SymbolicDiff), a PDE class for solving boundary value problems, and vector calculus operations such as Gradient, Divergence, Curl, and Laplacian.

Tokens
35.9K
Snippets
128
Records
171
Agent score
67%

What's inside findiff

  1. Overview of findiff features

    master

    findiff is a Python package designed for computing numerical derivatives and solving partial differential equations (PDEs) in any number of dimensions.

    Key capabilities include:

    • Numerical Differentiation: Differentiate arrays of any dimension along any axis with configurable accuracy orders. Supports both uniform and non-uniform grids.
    • Vector Calculus: Built-in support for standard operators like gradient, divergence, and curl.
    • Advanced Operators: Handle arbitrary linear combinations of derivatives with constant or variable coefficients. Supports arbitrary stencils and compact (implicit) finite differences.
    • PDE Solving: Solve PDEs with Dirichlet, Neumann, Robin, or Periodic boundary conditions. Supports time-dependent PDEs via the Method of Lines (e.g., Forward Euler, RK4, Backward Euler, Crank-Nicolson).
    • Performance: Fully vectorized operations with support for GPU, JAX, and CuPy backends. Can be combined with jax.jit for acceleration.
    • Mathematical Tools: Generate matrix representations of operators, solve eigenvalue problems (e.g., Schrodinger equation), and calculate raw finite difference coefficients.
  2. Define Dirichlet, Neumann, and Robin boundary conditions

    master

    Boundary conditions are managed via the BoundaryConditions object. You can assign different types of conditions to specific indices or slices of the grid:

    • Dirichlet: Assign a scalar value (e.g., bc[0] = 1).
    • Neumann: Assign a tuple containing the derivative operator and the target value (e.g., bc[1, :] = Diff(0, dx), 0 for $du/dx = 0$).
    • Robin: Assign a tuple containing the operator, a coefficient, and the target value (e.g., bc[-1] = (1, Diff(0, dx), D / v, 0) for $u + (D/v)u' = 0$).
  3. Configure Dirichlet, Neumann, and Robin boundary conditions

    master

    The BoundaryConditions object allows you to specify different types of boundary constraints on your grid edges.

    Dirichlet Boundary Conditions

    Set a single value to specify the value of the function at the boundary. bc[index] = value

    Neumann Boundary Conditions

    Specify the derivative at the boundary by passing a 2-tuple: (diff_operator, value). bc[index] = (Diff(axis, dx), value)

    Robin (Mixed) Boundary Conditions

    Robin conditions follow the form $\alpha u + \beta \frac{\partial u}{\partial n} = g$. You can specify these in two ways:

    1. As a 4-tuple: (alpha, diff_op, beta, g)
    2. As a 2-tuple: Construct a custom operator using Identity() and Diff, then pass (operator, g).

    2D Boundary Conditions

    For 2D grids, use slicing to apply conditions to entire edges (e.g., bc[0, :] for the top edge).

    # Neumann Example
    bc[0] = 0                         # u(0) = 0
    bc[-1] = Diff(0, dx), 1.0         # u'(L) = 1.0
    
    # Robin Example (4-tuple)
    bc[-1] = (1, Diff(0, dx), 1, 3)   # u + u' = 3
    
    # Robin Example (Custom Operator)
    from findiff import Identity
    robin_op = alpha * Identity() + beta * Diff(0, dx)
    bc[-1] = robin_op, g
  4. Use Lazy Grid Setting with Diff

    master

    A key feature of the Diff class is the ability to define operators without an immediate grid. You can define the operator structure first and then provide the grid spacing for each dimension using the .set_grid() method. This was not possible with the old FinDiff class.

    # Define operator without grid
    L = Diff(0)**2 + Diff(1)**2
    
    # Set the grid later (e.g., dx for dimension 0, dy for dimension 1)
    L.set_grid({0: dx, 1: dy})
    
    # Apply to function f
    result = L(f)
  5. What are compact (implicit) finite differences?

    master

    Standard (explicit) finite differences express a derivative as a weighted sum of function values:

    $$f'i = \sum_k c_k , f{i+k}$$

    Compact (implicit) finite differences generalize this by including derivative values on the left-hand side of the equation:

    $$\sum_k \alpha_k , f'{i+k} = \sum_k c_k , f{i+k}$$

    Key Characteristics:

    • High Accuracy: Compact schemes (like the Lele, 1992 tridiagonal scheme) can achieve much higher accuracy (e.g., 6th-order) with a much smaller stencil width than explicit schemes.
    • Computational Cost: Applying a compact operator requires solving a banded linear system (such as a tridiagonal system via the Thomas algorithm). This has a modest $O(N)$ overhead.
    • Resolution: They resolve short-wavelength features significantly more accurately than explicit schemes of the same stencil width.

    For implementation details, refer to the compact-fd guide.

  6. Stability and accuracy considerations for time-stepping

    master

    Explicit Methods and the CFL Condition

    Explicit methods are subject to the CFL stability condition. For diffusion problems with coefficient $D$, the time step must satisfy: $$\Delta t < \frac{\Delta x^2}{2, D}$$ If $\Delta t$ is too large, the solution will blow up. forward-euler is the most restrictive; rk4 has a larger stability region.

    Implicit Methods

    Implicit methods (backward-euler, crank-nicolson) are unconditionally stable, meaning any $\Delta t$ will produce a bounded solution. However, accuracy still depends on the step size:

    • Backward Euler: $O(\Delta t)$ (first-order accurate)
    • Crank-Nicolson: $O(\Delta t^2)$ (second-order accurate)

    Recommendation: Use Crank-Nicolson for most problems as it provides a good balance of unconditional stability and second-order accuracy.

  7. Implement differential operators in non-Cartesian coordinates

    master

    You can implement vector calculus operators in non-Cartesian coordinate systems (like polar coordinates) by assembling general linear combinations of Diff operators with variable coefficients.

    For example, to implement the 2D polar Laplacian $\nabla^2 = \frac{\partial^2}{\partial r^2} + \frac{1}{r}\frac{\partial}{\partial r} + \frac{1}{r^2}\frac{\partial^2}{\partial \varphi^2}$, you combine Diff instances for the radial ($r$) and angular ($\varphi$) components using the appropriate variable coefficients ($1/R$ and $1/R^2$).

    import numpy as np
    from findiff import Diff
    
    # Setup grid
    r = np.linspace(0.1, 10, 100)
    phi = np.linspace(0, 2*np.pi, 100, endpoint=False)
    dr, dphi = r[1] - r[0], phi[1] - phi[0]
    R, Phi = np.meshgrid(r, phi, indexing='ij')
    
    # Define function f(r, phi) = r^2
    f_polar = R**2
    
    # Assemble the polar Laplacian operator
    laplace_polar = (
        Diff(0, dr)**2
        + (1/R) * Diff(0, dr)
        + (1/R**2) * Diff(1, dphi)**2
    )
    
    # Apply the operator
    result = laplace_polar(f_polar)
  8. How error estimation works via Richardson extrapolation

    master

    To estimate the truncation error of a finite difference approximation of accuracy order $p$, findiff uses a technique based on Richardson extrapolation.

    Instead of requiring a second, finer grid, findiff computes the same derivative at two consecutive accuracy orders, $p$ and $p+2$, by widening the stencil.

    The Logic:

    1. Compute the derivative at order $p$: $f'{(p)} = f'{\text{exact}} + C,h^p + \mathcal{O}(h^{p+2})$
    2. Compute the derivative at order $p+2$: $f'{(p+2)} = f'{\text{exact}} + C',h^{p+2} + \mathcal{O}(h^{p+4})$
    3. The pointwise error estimate is the difference: $|f'{(p)} - f'{(p+2)}| \approx |C|,h^p$

    Result:

    • The difference provides the error indicator.
    • The higher-order result $f'_{(p+2)}$ is returned as the extrapolated value.

    For API usage, see the error-estimation guide.

  9. Handling non-uniform grids in findiff

    master

    While standard finite differences assume a constant grid spacing $\Delta x$, findiff supports non-uniform grids where the spacing $\Delta x_i = x_{i+1} - x_i$ varies between points.

    When using non-uniform grids, the coefficients $c_j$ in the finite difference stencil depend on the specific location $k$ of the point being differentiated.

    To use non-uniform grids: Instead of passing a scalar spacing value to the Diff operator, pass a coordinate array representing the actual positions of the grid points. findiff will automatically solve the coefficient system at each point based on the local distances $(x_{k+j} - x_k)$.

  10. Solve time-dependent PDEs using the Method of Lines

    master

    To solve time-dependent PDEs of the form $\frac{\partial u}{\partial t} = L(u)$, use the TimeDependentPDE class. This implements the Method of Lines (MOL) by discretizing the spatial operator $L$ with findiff finite difference operators and advancing the resulting system of ODEs in time using built-in time-stepping methods.

    To set up a problem, you need:

    1. A spatial operator L (constructed using Diff).
    2. An initial condition u0 (an array representing the state at $t=0$).
    3. BoundaryConditions object defining the behavior at the edges of your grid.
    4. A time array t defining the temporal discretization.
    import numpy as np
    from findiff import Diff, TimeDependentPDE, BoundaryConditions
    
    # Setup spatial grid and operator
    nx = 101
    x = np.linspace(0, 1, nx)
    dx = x[1] - x[0]
    D = 0.01
    L = D * Diff(0, dx)**2
    
    # Initial condition
    u0 = np.sin(np.pi * x)
    
    # Boundary conditions
    bc = BoundaryConditions((nx,))
    bc[0] = 0
    bc[-1] = 0
    
    # Time steps
    t = np.linspace(0, 1, 500)
    
    # Solve
    pde = TimeDependentPDE(L, u0, bc, t)
    u_final = pde.solve()  # returns the solution at t=1
  11. How finite difference schemes work in findiff

    master

    The findiff package approximates differential operators numerically using finite difference schemes.

    1D Case

    In 1D, the $n$-th derivative of a function $f$ at a grid point $x_k$ is approximated by a linear combination of function values in a surrounding stencil:

    $$\left(\frac{d^n f}{dx^n}\right)k \approx \sum{j \in A} c_{j} f_{k+j}$$

    Where $A$ is a set of offsets. findiff supports:

    • Symmetric stencils: Uses points on both sides of $x_k$ (e.g., $p=q=1$). These are ideal for interior points but cannot be used at boundaries.
    • One-sided stencils: Uses points on only one side (e.g., forward or backward stencils) to handle boundary conditions where neighbors are missing.

    Multiple Dimensions

    For multi-dimensional functions, partial derivatives are calculated using stencils that are typically superpositions of 1D stencils. For example, a 2D Laplacian ($\frac{\partial^2}{\partial x^2} + \frac{\partial^2}{\partial y^2}$) uses a cross-shaped stencil composed of two 1D stencils.