zjb.main.simulation.simulator 源代码

from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable

import numpy as np
from mako.template import Template
from traits.api import Dict
from traits.api import Enum as TraitEnum
from traits.api import (
    Expression,
    Float,
    HasPrivateTraits,
    HasRequiredTraits,
    List,
    Property,
    Str,
    Union,
    property_depends_on,
)

from zjb._traits.types import Instance, TraitCallable

from ..dtb.dynamics_model import DynamicsModel
from ..trait_types import ArrayLike, FloatVector
from .monitor import Monitor
from .solver import EulerSolver, Solver

TEMPLATE = Template(
    filename=str(Path(__file__).parent / "_templates" / "simulator.mako")
)

_EXPR_TEMPLATE = Template(
    filename=str(Path(__file__).parent / "_templates" / "expr.mako")
)


[文档] class BUILTIN_PARAMETER(str, Enum): # 使用(str, Enum)替换(StrEnum)以兼容Python3.11之前的版本 """内建参数, 仿真器内部预先定义的一些参数, 可以在表达式中直接使用这些参数""" t = "__t" """仿真总时长""" dt = "__dt" """仿真时间步长, 由求解器定义""" C = "__C" """脑网络的连接矩阵""" C_1 = "__C_1" """脑网络连接矩阵的行求和, 等于`np.sum(__C, 1)`""" nt = "__nt" """仿真的总时间步数, 等于`int(__t / __dt)`""" nr = "__nr" """脑网络的节点数, 等于`__C.shape[0]`""" it = "__it" """仿真当前迭代的时间步, 整数, 从0到__nt-1""" ct = "__ct" """仿真当前迭代的时间, 等于`__dt * __it`"""
[文档] class NumbaFuncParameter(HasPrivateTraits, HasRequiredTraits): """Numba函数参数 Attributes ---------- dependencise: list[BUILTIN_PARAMETER] 函数所依赖的内建参数, by default (,) numba_func: Callable[..., float | array[float]] `numba.njit`装饰的可执行函数 """ dependencies = List(TraitEnum(BUILTIN_PARAMETER)) numba_func: "Callable[..., float | NDArray[float_]]" = TraitCallable(required=True) _arg_str: str = Property() @property_depends_on("dependencies") def _get__arg_str(self): return ",".join(self.dependencies)
[文档] class ExprParameter(NumbaFuncParameter): """表达式参数 Attributes ---------- expression: str Python表达式 parameters: dict[str, float | array[float]], shape (n_regions) 表达式内部的参数字典 numba_func: Callable[..., float | array[float]], 只读 numba装饰的可执行函数 """ expr = Expression() parameters = Dict(Str, Union(Float, FloatVector)) numba_func: "Callable[..., float | NDArray[float_]]" = Property() @property_depends_on(["expression", "dependencies", "parameters"]) def _get_numba_func(self): code = _EXPR_TEMPLATE.render(expr=self) env = self.parameters.copy() exec(code, env) return env["func"]
[文档] class Simulator(HasPrivateTraits, HasRequiredTraits): """(脑网络模型)仿真器 Attributes ---------- model : DynamicsModel 动力学模型实例,用于仿真。 states : dict[str, float | array[float]], shape (n_regions) 仿真器当前的状态变量字典 parameters : dict[str, ExprParameter | float | array[float]], shape (n_regions) 仿真器当前的参数字典 connectivity : array_like, shape (n_regions, n_regions) 脑网络的连接矩阵 solver : Solver 求解器实例,用于数值求解仿真的微分方程。 monitors : list[Monitor] 监视器列表,用于在仿真过程中采样数据。 t : float 仿真总时长。 """ model = Instance(DynamicsModel, required=True) states = Dict(Str, Union(Float, FloatVector)) parameters = Dict(Str, Union(Instance(NumbaFuncParameter), Float, FloatVector)) connectivity = ArrayLike(required=True) solver = Instance(Solver, EulerSolver) monitors = List(Instance(Monitor), required=True) t = Float(1000)
[文档] def build(self): """ 构建仿真所需的代码和环境。这个方法将根据动力学模型的状态变量、参数和解算器设置来准备仿真的执行环境。 """ for name in self.model.state_variables: self.states.setdefault(name, 0) for name, parameter in self.model.parameters.items(): self.parameters.setdefault(name, parameter) self._args = { "__t": self.t, "__dt": self.solver.dt, "__C": np.asarray(self.connectivity, dtype=float), } | self.states self._env: dict[str, Any] = self._update_env_from_parameter({}) self._code = TEMPLATE.render( # type: ignore simulator=self, solver=self.solver, monitors=self.monitors, model=self.model, env=self._env, ) exec(self._code, self._env) self._simulator: "SimulatorFunction" = self._env["simulator"]
def __call__(self): """ 执行仿真并返回结果。如果仿真环境尚未构建,首先调用 build 方法构建环境。 Returns ------- results 仿真结果,通常包括各个状态变量和监视器采集的数据。 """ if self._simulator is None: # type: ignore self.build() states, results = self._simulator(**self._args) for name, state in zip(self.states, states): self.states[name] = state return results
[文档] def _update_env_from_parameter(self, env: "dict[str, Any]"): """根据仿真器参数更新编译所需的环境变量""" for name, para in self.parameters.items(): if isinstance(para, NumbaFuncParameter): env[f"_{name}_func"] = para.numba_func else: env[name] = para return env
if TYPE_CHECKING: from numpy import float_ from numpy.typing import NDArray SimulatorFunction = Callable[ ..., tuple[ tuple[NDArray[float_], ...], tuple[NDArray[float_], ...], ], ]