Module degann.equations.system_ode

Provide class for solve system of ODE

Expand source code
"""
Provide class for solve system of ODE
"""

from types import FunctionType
from typing import Union, List, Tuple

import numpy as np
from matplotlib import pyplot as plt
from scipy.integrate import solve_ivp

from . import equation_utils
from degann.networks.utils import export_csv_table

__all__ = ["SystemODE"]


class SystemODE(object):
    def __init__(self, debug=False):
        self._sol = None
        self._func = []
        self._func_arguments = ""
        self._initial_values = []
        self._size = 0
        self._debug = debug

    def _f(self, t, y):
        """
        Additional function for scipy.solve_ivp

        Parameters
        ----------
        t
        y: list

        Returns
        -------
        res: list
        """
        res = []
        for i, y_i in enumerate(y):
            res.append(self._func[i](*y))
        return res

    def prepare_equations(self, n: int, equations: List[List[str]]) -> None:
        """
        Parse passed equations and build functions for them

        Parameters
        ----------
        n: int
            System size
        equations: list[list[str]]
            System of equations. Equation `yi' = f(x, yj...)` and Cauchy initial value `yi(x0) = yi0`
        """
        self._size = n
        self._func_arguments = ""
        for i in range(n):
            self._func_arguments += f"y{i}, "
        self._func_arguments = self._func_arguments[:-2]

        for i, cond in enumerate(equations):
            string_func = f"def dy{i}_dt({self._func_arguments}): return {cond[0]}"
            if self._debug:
                print("Builded function:", string_func)
            code = compile(string_func, "<string>", "exec")

            self._func.append(FunctionType(code.co_consts[0], globals(), "temp"))
            self._initial_values.append(equation_utils.extract_iv(cond[1])[1])

    def solve(
        self, interval: Tuple[float, float], points: Union[int, list] = None
    ) -> None:
        """
        Solve given equations

        Parameters
        ----------
        interval: tuple[int, int]
            Decision interval
        points: int | list
            Amount of points per interval (or list of points)
        """
        t_span = np.array([interval[0], interval[1]])
        if points is not None:
            if isinstance(points, list):
                times = points
            else:
                times = np.linspace(t_span[0], t_span[1], points)
            self._sol = solve_ivp(
                self._f, t_span, self._initial_values, t_eval=times, method="LSODA"
            )
        else:
            self._sol = solve_ivp(self._f, t_span, self._initial_values, method="LSODA")
        if self._debug:
            print("Success solve")

            plt.rc("font", size=24)
            plt.figure()
            t = self._sol.t
            y = self._sol.y

            result_path = f"solveTable.csv"
            t_for_export = t[:, np.newaxis]
            export_csv_table(np.concatenate((t_for_export, y.T), axis=1), result_path)

            for i, y_i in enumerate(y):
                plt.plot(t, y_i, "-", label=f"{i}")
            plt.legend()
            plt.show()

    def build_table(self, eq_num: list = None) -> np.ndarray:
        """
        Builds a table for solution

        Parameters
        ----------
        eq_num : list
            For which equation in system build table

        Returns
        -------
        table: list
            Table with 1+k column, where 1 is the number of variables (ODE have 1 variable),
            and the last k columns contain the solution of ODE system
        """
        if self._sol is None:
            return np.array([])

        if eq_num is None:
            t = self._sol.t
            y = self._sol.y
            res = np.vstack([t, *y])
            res = res.T
        else:
            t = self._sol.t
            y = self._sol.y[eq_num, :]
            res = np.vstack([t, *y])
            res = res.T
        return res

Classes

class SystemODE (debug=False)
Expand source code
class SystemODE(object):
    def __init__(self, debug=False):
        self._sol = None
        self._func = []
        self._func_arguments = ""
        self._initial_values = []
        self._size = 0
        self._debug = debug

    def _f(self, t, y):
        """
        Additional function for scipy.solve_ivp

        Parameters
        ----------
        t
        y: list

        Returns
        -------
        res: list
        """
        res = []
        for i, y_i in enumerate(y):
            res.append(self._func[i](*y))
        return res

    def prepare_equations(self, n: int, equations: List[List[str]]) -> None:
        """
        Parse passed equations and build functions for them

        Parameters
        ----------
        n: int
            System size
        equations: list[list[str]]
            System of equations. Equation `yi' = f(x, yj...)` and Cauchy initial value `yi(x0) = yi0`
        """
        self._size = n
        self._func_arguments = ""
        for i in range(n):
            self._func_arguments += f"y{i}, "
        self._func_arguments = self._func_arguments[:-2]

        for i, cond in enumerate(equations):
            string_func = f"def dy{i}_dt({self._func_arguments}): return {cond[0]}"
            if self._debug:
                print("Builded function:", string_func)
            code = compile(string_func, "<string>", "exec")

            self._func.append(FunctionType(code.co_consts[0], globals(), "temp"))
            self._initial_values.append(equation_utils.extract_iv(cond[1])[1])

    def solve(
        self, interval: Tuple[float, float], points: Union[int, list] = None
    ) -> None:
        """
        Solve given equations

        Parameters
        ----------
        interval: tuple[int, int]
            Decision interval
        points: int | list
            Amount of points per interval (or list of points)
        """
        t_span = np.array([interval[0], interval[1]])
        if points is not None:
            if isinstance(points, list):
                times = points
            else:
                times = np.linspace(t_span[0], t_span[1], points)
            self._sol = solve_ivp(
                self._f, t_span, self._initial_values, t_eval=times, method="LSODA"
            )
        else:
            self._sol = solve_ivp(self._f, t_span, self._initial_values, method="LSODA")
        if self._debug:
            print("Success solve")

            plt.rc("font", size=24)
            plt.figure()
            t = self._sol.t
            y = self._sol.y

            result_path = f"solveTable.csv"
            t_for_export = t[:, np.newaxis]
            export_csv_table(np.concatenate((t_for_export, y.T), axis=1), result_path)

            for i, y_i in enumerate(y):
                plt.plot(t, y_i, "-", label=f"{i}")
            plt.legend()
            plt.show()

    def build_table(self, eq_num: list = None) -> np.ndarray:
        """
        Builds a table for solution

        Parameters
        ----------
        eq_num : list
            For which equation in system build table

        Returns
        -------
        table: list
            Table with 1+k column, where 1 is the number of variables (ODE have 1 variable),
            and the last k columns contain the solution of ODE system
        """
        if self._sol is None:
            return np.array([])

        if eq_num is None:
            t = self._sol.t
            y = self._sol.y
            res = np.vstack([t, *y])
            res = res.T
        else:
            t = self._sol.t
            y = self._sol.y[eq_num, :]
            res = np.vstack([t, *y])
            res = res.T
        return res

Methods

def build_table(self, eq_num: list = None) ‑> numpy.ndarray

Builds a table for solution

Parameters

eq_num : list
For which equation in system build table

Returns

table : list
Table with 1+k column, where 1 is the number of variables (ODE have 1 variable), and the last k columns contain the solution of ODE system
Expand source code
def build_table(self, eq_num: list = None) -> np.ndarray:
    """
    Builds a table for solution

    Parameters
    ----------
    eq_num : list
        For which equation in system build table

    Returns
    -------
    table: list
        Table with 1+k column, where 1 is the number of variables (ODE have 1 variable),
        and the last k columns contain the solution of ODE system
    """
    if self._sol is None:
        return np.array([])

    if eq_num is None:
        t = self._sol.t
        y = self._sol.y
        res = np.vstack([t, *y])
        res = res.T
    else:
        t = self._sol.t
        y = self._sol.y[eq_num, :]
        res = np.vstack([t, *y])
        res = res.T
    return res
def prepare_equations(self, n: int, equations: List[List[str]]) ‑> None

Parse passed equations and build functions for them

Parameters

n : int
System size
equations : list[list[str]]
System of equations. Equation yi' = f(x, yj...) and Cauchy initial value yi(x0) = yi0
Expand source code
def prepare_equations(self, n: int, equations: List[List[str]]) -> None:
    """
    Parse passed equations and build functions for them

    Parameters
    ----------
    n: int
        System size
    equations: list[list[str]]
        System of equations. Equation `yi' = f(x, yj...)` and Cauchy initial value `yi(x0) = yi0`
    """
    self._size = n
    self._func_arguments = ""
    for i in range(n):
        self._func_arguments += f"y{i}, "
    self._func_arguments = self._func_arguments[:-2]

    for i, cond in enumerate(equations):
        string_func = f"def dy{i}_dt({self._func_arguments}): return {cond[0]}"
        if self._debug:
            print("Builded function:", string_func)
        code = compile(string_func, "<string>", "exec")

        self._func.append(FunctionType(code.co_consts[0], globals(), "temp"))
        self._initial_values.append(equation_utils.extract_iv(cond[1])[1])
def solve(self, interval: Tuple[float, float], points: Union[int, list] = None) ‑> None

Solve given equations

Parameters

interval : tuple[int, int]
Decision interval
points : int | list
Amount of points per interval (or list of points)
Expand source code
def solve(
    self, interval: Tuple[float, float], points: Union[int, list] = None
) -> None:
    """
    Solve given equations

    Parameters
    ----------
    interval: tuple[int, int]
        Decision interval
    points: int | list
        Amount of points per interval (or list of points)
    """
    t_span = np.array([interval[0], interval[1]])
    if points is not None:
        if isinstance(points, list):
            times = points
        else:
            times = np.linspace(t_span[0], t_span[1], points)
        self._sol = solve_ivp(
            self._f, t_span, self._initial_values, t_eval=times, method="LSODA"
        )
    else:
        self._sol = solve_ivp(self._f, t_span, self._initial_values, method="LSODA")
    if self._debug:
        print("Success solve")

        plt.rc("font", size=24)
        plt.figure()
        t = self._sol.t
        y = self._sol.y

        result_path = f"solveTable.csv"
        t_for_export = t[:, np.newaxis]
        export_csv_table(np.concatenate((t_for_export, y.T), axis=1), result_path)

        for i, y_i in enumerate(y):
            plt.plot(t, y_i, "-", label=f"{i}")
        plt.legend()
        plt.show()