CySolver & PySolver Reuses

See the demos in “2 - Advanced Examples” jupyter notebook.

CyRK’s cysolve_ivp and pysolve_ivp methods require allocating various chunks of memory that are required to solve the ODE. Depending on your needs you may need to rerun a ODE many many times with only minor changes. For example slightly different initial conditions. If your ODE does not require many integration steps (particularly true for small time spans) then the allocation of memory for the storage can start to become a significant fraction of the time. To mitigate this CyRK provides ways to reuse result storage to avoid allocations (or have minor reallocations). The examples below show you how you can make use of this system.

Warning! Reusing a result structure will erase all previously stored data.

PySolver Example

# Note if using this format, `dy` must be the first argument. Additionally, a special flag must be set to True when calling pysolve_ivp, see below.
def cy_diffeq(dy, t, y):
    dy[0] = (1. - 0.01 * y[1]) * y[0]
    dy[1] = (0.02 * y[0] - 1.) * y[1]

# Since this is pure python we can njit it safely
from numba import njit
cy_diffeq = njit(cy_diffeq)
    
import numpy as np
from CyRK import pysolve_ivp

initial_conds = np.asarray((20., 20.), dtype=np.float64, order='C')
time_span = (0., 50.)
rtol = 1.0e-7
atol = 1.0e-8

result = \
    pysolve_ivp(cy_diffeq, time_span, initial_conds,
                method="RK45", rtol=rtol, atol=atol, pass_dy_as_arg=True)

# Use the result's memory allocation to re-call pysolve_ivp
result = \
    pysolve_ivp(cy_diffeq, time_span, initial_conds,
                method="RK45", rtol=rtol, atol=atol, pass_dy_as_arg=True,
                solution_reuse=result) # Set the reuse argument.

print("Was Integration was successful?", result.success)
print(result.message)
print("Size of solution: ", result.size)
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot(result.t, result.y[0], c='r')
ax.plot(result.t, result.y[1], c='b')
plt.show()

# We can change parameters too
new_initial_conds = np.asarray((3., 0.1), dtype=np.float64, order='C')
result = \
    pysolve_ivp(cy_diffeq, time_span, new_initial_conds,
                method="RK45", rtol=rtol, atol=atol, pass_dy_as_arg=True,
                solution_reuse=result) # Set the reuse argument.

print("Was Integration was successful?", result.success)
print(result.message)
print("Size of solution: ", result.size)
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot(result.t, result.y[0], c='r')
ax.plot(result.t, result.y[1], c='b')
plt.show()

CySolver Example

%%cython --force
# cython: boundscheck=False, wraparound=False, nonecheck=False, cdivision=True, initializedcheck=False
from libcpp.utility cimport move
from libcpp.vector cimport vector

import numpy as np
cimport numpy as np
np.import_array()

from CyRK cimport cysolve_ivp, cysolve_ivp_noreturn, DiffeqFuncType, WrapCySolverResult, CySolveOutput, PreEvalFunc

cdef void cython_diffeq(double* dy, double t, double* y, char* args, PreEvalFunc pre_eval_func) noexcept nogil:

    # Build Coeffs
    cdef double coeff_1 = (1. - 0.01 * y[1])
    cdef double coeff_2 = (0.02 * y[0] - 1.)
    
    # Store results
    dy[0] = coeff_1 * y[0]
    dy[1] = coeff_2 * y[1]

# Import the required functions from CyRK
from CyRK cimport cysolve_ivp, DiffeqFuncType, WrapCySolverResult, CySolveOutput

# Let's get the integration number for the RK45 method
from CyRK cimport ODEMethod

def run_reuse_cysolver(tuple t_span, double[::1] y0):
    
    # Cast our diffeq to the accepted format
    cdef DiffeqFuncType diffeq = cython_diffeq
    
    # Convert the python user input to pure C types
    cdef size_t num_y          = len(y0)
    cdef double t_start        = t_span[0]
    cdef double t_end          = t_span[1]
    cdef vector[double] y0_vec = vector[double](num_y)
    cdef size_t yi
    for yi in range(num_y):
        y0_vec[yi] = y0[yi]

    # Run the integrator!
    cdef CySolveOutput result = cysolve_ivp(
        diffeq,
        t_start,
        t_end,
        y0_vec,
        method = ODEMethod.RK45,
        rtol = 1.0e-7,
        atol = 1.0e-8
    )

    # Run again using the noreturn version so we can reuse the integration storage.
    cysolve_ivp_noreturn(
        result.get(),  # result is a unique pointer; we need to pass the raw pointer to the baseline_cysolve_ivp
        diffeq,
        t_start,
        t_end,
        y0_vec,
        rtol = 1.0e-7,
        atol = 1.0e-8
    )

    # Like with PySolver, we can change the inputs 
    cdef double t_end_2 = 10.0 * t_end
    cysolve_ivp_noreturn(
        result.get(),  # result is a unique pointer; we need to pass the raw pointer to the baseline_cysolve_ivp
        diffeq,
        t_start,
        t_end_2,
        y0_vec,
        rtol = 1.0e-7,
        atol = 1.0e-8
    )

    # The CySolveOutput is not accesible via Python. We need to wrap it first
    cdef WrapCySolverResult pysafe_result = WrapCySolverResult()
    pysafe_result.set_cyresult_pointer(move(result))

    return pysafe_result