Skip to content

Getting Started

Installation

pip install voles[full]

This gives you the fully-capable package, so everything just works out of the box. Pre-built wheels are provided for Linux x86_64, macOS arm64 (Apple Silicon), and Windows x64. The D extension is bundled in the wheel and requires no extra tooling. Intel Macs are no longer supported as of 0.3.2; users can pin to volterra-equation-solvers==0.3.1 or build from source (see CONTRIBUTING.md).

Requirements: Python ≥ 3.10, numpy, scipy

If you have trouble installing a dependency, you can use a slimmer install instead. numba and scipy are only needed for some features (see below), so any of these will still give you a working package:

pip install voles          # core: numpy + scipy (no numba)
pip install voles --no-deps && pip install numpy   # leanest: numpy only, no scipy or numba

What the optional pieces buy you: - scipy (core dependency) — required for the callable-input function_solve_* family. - numba (added by [full]) — only needed for the array-based solvers when using non-standard collocation settings not compiled into the D extension.

To build from source (e.g. on an unsupported platform), see CONTRIBUTING.md.

Input format

The two solver families take different input shapes — see the relevant section below.

Array-based solvers (solve_VIE_1, solve_VIE_2, solve_VIDE)

The key input is kernel_values: an array of kernel values K(s) sampled from s=0 in steps of time_step. Its length must satisfy len(kernel_values) = (N × coll_divs²) + 1 for some positive integer N. If the length is larger than required (e.g. 92 when 91 was needed), the arrays are silently truncated to the nearest valid length and a warning is printed. If the length is smaller than coll_divs² + 1 — too short to form a single mesh interval — a ValueError is raised.

Callable-input solvers (function_solve_*)

You pass the kernel, forcing, and (for VIDE) coefficient a as Python callables, plus a 1-D mesh_breakpoints array starting at 0. There's no fixed-length constraint; resolution is controlled by how many breakpoints you pass. See the Callable-Input Solvers section below.

solve_VIE_1

Solves \(g(t) = \int_0^t K(t-s)y(s)ds\) for unknown \(y(t)\).

import numpy as np
from voles import solve_VIE_1

time_step = 0.1
times = np.arange(0, 9.1, time_step)   # 91 points = 10×3² + 1
kernel = np.exp(times)                  # K(s) = e^s
g = np.sin(times)
g[0] = 0.0                              # g(0) must be 0 for Type-1 VIEs

soln = solve_VIE_1(
    kernel_values=kernel,
    g_values=g,
    time_step=time_step,
    coll_divs=3,
    coll_choices=[1, 2, 3],
)
# exact solution: cos(t) - sin(t)

solve_VIE_2

Solves \(y(t) = g(t) + \int_0^t K(t-s)y(s)ds\) for unknown \(y(t)\).

import numpy as np
from voles import solve_VIE_2

time_step = 0.05
times = np.arange(0, 4.55, time_step)  # 91 points = 10×3² + 1
kernel = np.exp(-times)
g = np.sin(times) - 0.5 * (np.exp(-times) + np.sin(times) - np.cos(times))

soln = solve_VIE_2(
    kernel_values=kernel,
    g_values=g,
    time_step=time_step,
    coll_divs=3,
    coll_choices=[0, 1, 2, 3],
)
# exact solution: sin(t)

solve_VIDE

Solves \(y'(t) = a(t)y(t) + g(t) + \int_0^t K(t-s)y(s)ds\) for unknown \(y(t)\).

import numpy as np
from voles import solve_VIDE

time_step = 0.01
times = np.arange(0, 0.91, time_step)  # 91 points = 10×3² + 1
kernel = np.exp(-times)
a = 1.0 / (1.0 + times**2)
g = (np.cos(times) - 0.5*(np.exp(-times) + np.sin(times) - np.cos(times))
     - np.sin(times) / (1.0 + times**2))

soln = solve_VIDE(
    kernel_values=kernel,
    g_values=g,
    a_values=a,
    soln_init_value=0.0,
    time_step=time_step,
    coll_divs=3,
    coll_choices=[1, 2, 3],
)
# exact solution: sin(t)

Returning a callable solution

Pass return_function=True to also get a callable solution object. Call it at any time to evaluate the piecewise polynomial; it also indexes and iterates like a list of per-interval numpy.polynomial.Polynomial objects (matching the callable-input solvers' return_function):

import numpy as np
from voles import solve_VIE_2

time_step = 0.05
times = np.arange(0, 4.55, time_step)  # 91 points = 10×3² + 1
kernel = np.exp(-times)
g = np.sin(times) - 0.5 * (np.exp(-times) + np.sin(times) - np.cos(times))

soln_values, solution = solve_VIE_2(
    kernel_values=kernel, g_values=g, time_step=time_step,
    coll_divs=3, coll_choices=[0, 1, 2, 3],
    return_function=True,
)

solution(0.42)             # evaluate the piecewise polynomial at any time
first_interval = solution[0]   # indexes/iterates like the per-interval polynomials
for poly in solution:          # each is a numpy.polynomial.Polynomial
    pass

return_polys=True remains as a deprecated alias.

Callable-input solvers

The companion function_solve_VIE_1, function_solve_VIE_2, and function_solve_VIDE accept the kernel, forcing, and (for VIDE) coefficient a as Python callables instead of pre-sampled arrays, run on an arbitrary mesh you supply via mesh_breakpoints, and natively handle weakly singular convolution kernels. Same equations, same scalar / vector / matrix shape support.

import numpy as np
from voles import function_solve_VIE_2

# Same problem as solve_VIE_2 above; kernel and g are callables now.
kernel = lambda u: np.exp(-u)
g      = lambda t: np.sin(t) - 0.5 * (np.exp(-t) + np.sin(t) - np.cos(t))

mesh = np.linspace(0, 4.5, 91)
soln_values, y = function_solve_VIE_2(
    kernel=kernel, g=g, mesh_breakpoints=mesh,
    coll_divs=3, coll_choices=[0, 1, 2, 3],
    return_function=True,
)
# y is callable: y(0.37) → scalar, y(np.linspace(0, 4.5, 200)) → array

For weakly singular kernels \(K(u) \sim u^{-\alpha}\) (e.g. Abel-type), declare the singularity and use optimal_graded_mesh to recover the full collocation convergence order:

import numpy as np
from voles import function_solve_VIE_2, optimal_graded_mesh

kernel = lambda u: 1.0 / np.sqrt(u) if u > 0 else 0.0
g      = lambda t: np.sqrt(t) - 0.5 * np.pi * t   # exact y(t) = sqrt(t)

coll_choices = [0, 1, 2]
mesh = optimal_graded_mesh(alpha=0.5, T=1.0, M=30, order=len(coll_choices))
soln = function_solve_VIE_2(
    kernel=kernel, g=g, mesh_breakpoints=mesh,
    coll_divs=2, coll_choices=coll_choices,
    kernel_singularity=0.0,
)

See the Callable Inputs and Arbitrary Mesh example for the full walkthrough including vector and matrix-valued cases.