Source code for brainpy._src.integrators.fde.GL

# -*- coding: utf-8 -*-

"""
This module provides numerical solvers for Grünwald–Letnikov derivative FDEs.
"""

from typing import Dict, Union, Callable, Any

import jax

import brainpy.math as bm
from brainpy._src.integrators.constants import DT
from brainpy._src.integrators.utils import check_inits, format_args
from brainpy.errors import UnsupportedError
from .base import FDEIntegrator
from .generic import register_fde_integrator

__all__ = [
  'GLShortMemory'
]


[docs] class GLShortMemory(FDEIntegrator): r"""Efficient Computation of the Short-Memory Principle in Grünwald-Letnikov Method [1]_. According to the explicit numerical approximation of Grünwald-Letnikov, the fractional-order derivative :math:`q` for a discrete function :math:`f(t_K)` can be described as follows: .. math:: {{}_{k-\frac{L_{m}}{h}}D_{t_{k}}^{q}}f(t_{k})\approx h^{-q} \sum\limits_{j=0}^{k}C_{j}^{q}f(t_{k-j}) where :math:`L_{m}` is the memory lenght, :math:`h` is the integration step size, and :math:`C_{j}^{q}` are the binomial coefficients which are calculated recursively with .. math:: C_{0}^{q}=1,\ C_{j}^{q}=\left(1- \frac{1+q}{j}\right)C_{j-1}^{q},\ j=1,2, \ldots k. Then, the numerical solution for a fractional-order differential equation (FODE) expressed in the form .. math:: D_{t_{k}}^{q}x(t_{k})=f(x(t_{k})) can be obtained by .. math:: x(t_{k})=f(x(t_{k-1}))h^{q}- \sum\limits_{j=1}^{k}C_{j}^{q}x(t_{k-j}). for :math:`0 < q < 1`. The above expression requires infinity memory length for numerical solution since the summation term depends on the discritized time :math:`t_k`. This implies relatively high simulation times. To reduce the computational time, the upper bound of summation needs to be modified by :math:`k=v`, where .. math:: v=\begin{cases} k, & k\leq M,\\ L_{m}, & k > M. \end{cases} This is known as the short-memory principle, where :math:`M` is the memory window with a width defined by :math:`M=\frac{L_{m}}{h}`. As was reported in [2]_, the accuracy increases by increaing the width of memory window. Examples -------- >>> import brainpy as bp >>> >>> a, b, c = 10, 28, 8 / 3 >>> def lorenz(x, y, z, t): >>> dx = a * (y - x) >>> dy = x * (b - z) - y >>> dz = x * y - c * z >>> return dx, dy, dz >>> >>> integral = bp.fde.GLShortMemory(lorenz, >>> alpha=0.96, >>> num_memory=500, >>> inits=[1., 0., 1.]) >>> runner = bp.IntegratorRunner(integral, >>> monitors=list('xyz'), >>> inits=[1., 0., 1.], >>> dt=0.005) >>> runner.run(100.) >>> >>> import matplotlib.pyplot as plt >>> plt.plot(runner.mon.x.flatten(), runner.mon.z.flatten()) >>> plt.show() Parameters ---------- f : callable The derivative function. alpha: int, float, jnp.ndarray, bm.ndarray, sequence The fractional-order of the derivative function. Should be in the range of ``(0., 1.)``. num_memory: int The length of the short memory. .. versionchanged:: 2.1.11 inits: sequence A sequence of the initial values for variables. dt: float, int The numerical precision. name: str The integrator name. References ---------- .. [1] Clemente-López, D., et al. "Efficient computation of the Grünwald-Letnikov method for arm-based implementations of fractional-order chaotic systems." 2019 8th International Conference on Modern Circuits and Systems Technologies (MOCAST). IEEE, 2019. .. [2] M. F. Tolba, A. M. AbdelAty, N. S. Soliman, L. A. Said, A. H. Madian, A. T. Azar, et al., "FPGA implementation of two fractional order chaotic systems", International Journal of Electronics and Communications, vol. 78, pp. 162-172, 2017. """ def __init__( self, f: Callable, alpha: Any, inits: Any, num_memory: int, dt: float = None, name: str = None, state_delays: Dict[str, Union[bm.LengthDelay, bm.TimeDelay]] = None, ): super(GLShortMemory, self).__init__(f=f, alpha=alpha, dt=dt, name=name, num_memory=num_memory, state_delays=state_delays) # fractional order if not bm.all(bm.logical_and(self.alpha <= 1, self.alpha > 0)): raise UnsupportedError(f'Only support the fractional order in (0, 1), ' f'but we got {self.alpha}.') # initial values inits = check_inits(inits, self.variables) # delays self.delays = bm.VarDict() for key, val in inits.items(): delay = bm.zeros((self.num_memory,) + val.shape, dtype=val.dtype) delay[0] = val self.delays[key+'_delay'] = bm.Variable(delay) self._idx = bm.Variable(bm.asarray([1])) # binomial coefficients bc = (1 - (1 + self.alpha.reshape((-1, 1))) / bm.arange(1, num_memory + 1)) bc = bm.cumprod(bm.vstack([bm.ones_like(self.alpha), bc.T]), axis=0) self._binomial_coef = bm.flip(bc[1:], axis=0) # integral function self.set_integral(self._integral_func)
[docs] def reset(self, inits): """Reset function of the delay variables.""" self._idx.value = bm.asarray([1]) inits = check_inits(inits, self.variables) for key, val in inits.items(): delay = bm.zeros((self.num_memory,) + val.shape, dtype=val.dtype) delay[0] = val self.delays[key].value = delay
@property def binomial_coef(self): return bm.as_numpy(bm.flip(self._binomial_coef, axis=0)) def _integral_func(self, *args, **kwargs): # format arguments all_args = format_args(args, kwargs, self.arg_names) dt = all_args.pop(DT, self.dt) # derivative values devs = self.f(**all_args) if len(self.variables) == 1: if not isinstance(devs, (bm.Array, jax.Array)): raise ValueError('Derivative values must be a tensor when there ' 'is only one variable in the equation.') devs = {self.variables[0]: devs} else: if not isinstance(devs, (tuple, list)): raise ValueError('Derivative values must be a list/tuple of tensors ' 'when there are multiple variables in the equation.') devs = {var: devs[i] for i, var in enumerate(self.variables)} # integral results integrals = [] idx = (self._idx + bm.arange(self.num_memory)) % self.num_memory for i, var in enumerate(self.variables): delay_var = var + '_delay' summation = self._binomial_coef[:, i] @ self.delays[delay_var][idx] integral = (dt ** self.alpha[i]) * devs[var] - summation self.delays[delay_var][self._idx[0]] = integral integrals.append(integral) self._idx.value = (self._idx + 1) % self.num_memory # return integrals if len(self.variables) == 1: return integrals[0] else: return integrals
register_fde_integrator(name='short-memory', integrator=GLShortMemory)