Coverage for physioblocks / library / functions / first_order.py: 100%
35 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-09 16:40 +0100
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-09 16:40 +0100
1# SPDX-FileCopyrightText: Copyright INRIA
2#
3# SPDX-License-Identifier: LGPL-3.0-only
4#
5# Copyright INRIA
6#
7# This file is part of PhysioBlocks, a library mostly developed by the
8# [Ananke project-team](https://team.inria.fr/ananke) at INRIA.
9#
10# Authors:
11# - Colin Drieu
12# - Dominique Chapelle
13# - François Kimmig
14# - Philippe Moireau
15#
16# PhysioBlocks is free software: you can redistribute it and/or modify it under the
17# terms of the GNU Lesser General Public License as published by the Free Software
18# Foundation, version 3 of the License.
19#
20# PhysioBlocks is distributed in the hope that it will be useful, but WITHOUT ANY
21# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
22# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
23#
24# You should have received a copy of the GNU Lesser General Public License along with
25# PhysioBlocks. If not, see <https://www.gnu.org/licenses/>.
27from __future__ import annotations
29from typing import Any
31import numpy as np
32from numpy.typing import NDArray
34from physioblocks.registers.type_register import register_type
35from physioblocks.simulation import AbstractFunction
37# First order function id
38FIRST_ORDER_NAME = "first_order"
41@register_type(FIRST_ORDER_NAME)
42class FirstOrder(AbstractFunction):
43 """
44 Defines an evaluation method to get the value of a sum of first order functions
45 with heavyside for the given time.
46 """
48 times_start: NDArray[np.float64]
49 """Start times of first order components"""
51 amplitudes: NDArray[np.float64]
52 """Amplitudes of first order components"""
54 time_constants: NDArray[np.float64]
55 """Time constants of first order components"""
57 baseline_value: np.float64
58 """Baseline value of the function"""
60 def __init__(
61 self,
62 times_start: NDArray[np.float64],
63 amplitudes: NDArray[np.float64],
64 time_constants: NDArray[np.float64],
65 baseline_value: np.float64,
66 ):
67 # test arguments size consistency
68 if not (
69 (len(times_start) == len(amplitudes))
70 and (len(times_start) == len(time_constants))
71 ):
72 msg_error = (
73 "arguments 'times_start', 'amplitudes' and 'time_constants' "
74 "must have the same length. Got: "
75 f"- times_start (len = {len(times_start)}): {times_start}; "
76 f"- amplitudes (len = {len(amplitudes)}): {amplitudes}; "
77 f"- time_constants (len = {len(time_constants)}): {time_constants}."
78 )
79 raise ValueError(msg_error)
81 # reorder array arguments to have ascending times_start
82 sorted_indices = np.argsort(times_start)
83 amplitudes = amplitudes[sorted_indices]
84 time_constants = time_constants[sorted_indices]
86 # assign properties
87 self.times_start = times_start
88 self.amplitudes = amplitudes
89 self.time_constants = time_constants
90 self.baseline_value = baseline_value
92 def eval(self, time: np.float64) -> Any:
93 """
94 Evaluate function value at the given time.
96 :param time: evaluation time
97 :type time: np.float64
99 :return: the function value
100 :rtype: np.float64
101 """
103 mask_activated = self.times_start <= time
104 amplitudes_activ = self.amplitudes[mask_activated]
105 time_cst_activ = self.time_constants[mask_activated]
106 times_start_activ = self.times_start[mask_activated]
108 output = self.baseline_value + np.sum(
109 amplitudes_activ
110 * (1 - np.exp(-(time - times_start_activ) / time_cst_activ))
111 )
113 return output