Procedural API — solve_complex_ivp

solve_complex_ivp() is the main entry point to ZVODE. Pass the right-hand side, a time span, and an initial condition; get back a ZVODEResult object with the solution and solver statistics as attributes (also accessible as dict keys).

from zvode import solve_complex_ivp

sol = solve_complex_ivp(fun, tspan, y0, **options)
# sol.t    — 1-D float array of output times, shape (m,)
# sol.y    — complex array, shape (n, m)
# sol.nfev — RHS evaluations (int)
# sol.njev — Jacobian evaluations (int)
# sol.nlu  — LU decompositions (int)

The complete parameter reference is in the solve_complex_ivp() docstring.

Note

ZVODE integrates complex-valued ODEs. For best results the right-hand side f(t, y) should be holomorphic (complex-analytic): the Cauchy–Riemann equations must hold for each component. Functions that involve abs, conj, or real/imaginary-part splitting are not holomorphic; for such systems, reformulate as an equivalent real system of doubled dimension and use a real-valued solver.


Output modes

Collect every accepted step (default) — pass a 2-element tspan:

sol = solve_complex_ivp(rhs, tspan=(0.0, 10.0), y0=[1.0 + 0j])
# sol.t[0] == 0.0, sol.t[-1] == 10.0; number of points depends on tolerances

Output at specific times — pass three or more values as tspan:

import numpy as np

t_out = np.linspace(0.0, 10.0, 101)
sol = solve_complex_ivp(rhs, tspan=t_out, y0=[1.0 + 0j])
# sol.t is t_out, sol.y.shape == (1, 101)

Endpoint onlysave_steps=False returns a scalar sol.t and a 1-D sol.y:

sol = solve_complex_ivp(rhs, tspan=(0.0, 10.0), y0=[1.0 + 0j],
                        save_steps=False)
# float(sol.t), sol.y.shape == (1,)

Smoothing output with refine

The refine parameter inserts additional interpolated output points inside each accepted step using ZVODE’s built-in dense output (Nordsieck interpolation). refine=N produces N 1 intermediate points per step, yielding smoother plots without extra function evaluations:

sol = solve_complex_ivp(rhs, tspan=(0.0, 10.0), y0=[1.0 + 0j], refine=4)

refine is only effective when save_steps=True; it is ignored in knot and endpoint-only modes.

When allow_overshoot=True, the solver may step slightly past the endpoint tspan[-1] to avoid constraining its step size; the last output time may then lie just beyond tspan[-1].


Choosing a method

method='BDF' (default) is for stiff problems; method='Adams' for non-stiff.


Providing a Jacobian

Supplying the Jacobian avoids finite differences and reduces RHS evaluations for stiff problems.

Dense — return an (n, n) array from jac(t, y):

import numpy as np

def rhs(t, y):
    return np.array([-100j * y[0] + y[1], -1j * y[1]])

def jac(t, y):
    return np.array([[-100j, 1.0], [0.0, -1j]])

sol = solve_complex_ivp(rhs, tspan=(0.0, 5.0),
                        y0=[1.0 + 0j, 0.0 + 1j],
                        method='BDF', jac=jac)

Banded — pass lband and uband; return a (lband + uband + 1, n) array where entry [uband + i j, j] holds df[i]/dy[j]. See Banded Jacobians for the compact-storage layout and a worked example:

sol = solve_complex_ivp(rhs, tspan=(0.0, 5.0), y0=y0,
                        method='BDF', jac=jac_banded,
                        lband=1, uband=1)

Backward integration

Set tspan in decreasing order; for knot mode pass times strictly decreasing:

sol = solve_complex_ivp(rhs, tspan=(10.0, 0.0), y0=y_at_t10)

Compiled callbacks

For performance-critical work, fun and jac can be compiled C function pointers (ctypes or numba @cfunc), called directly without re-entering the Python interpreter on each evaluation. Pass the optional ctx argument to share a user-data pointer between both compiled callbacks. See Compiled callbacks for setup and full examples.