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

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/>. 

26 

27from __future__ import annotations 

28 

29from typing import Any 

30 

31import numpy as np 

32from numpy.typing import NDArray 

33 

34from physioblocks.registers.type_register import register_type 

35from physioblocks.simulation import AbstractFunction 

36 

37# First order function id 

38FIRST_ORDER_NAME = "first_order" 

39 

40 

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 """ 

47 

48 times_start: NDArray[np.float64] 

49 """Start times of first order components""" 

50 

51 amplitudes: NDArray[np.float64] 

52 """Amplitudes of first order components""" 

53 

54 time_constants: NDArray[np.float64] 

55 """Time constants of first order components""" 

56 

57 baseline_value: np.float64 

58 """Baseline value of the function""" 

59 

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) 

80 

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] 

85 

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 

91 

92 def eval(self, time: np.float64) -> Any: 

93 """ 

94 Evaluate function value at the given time. 

95 

96 :param time: evaluation time 

97 :type time: np.float64 

98 

99 :return: the function value 

100 :rtype: np.float64 

101 """ 

102 

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] 

107 

108 output = self.baseline_value + np.sum( 

109 amplitudes_activ 

110 * (1 - np.exp(-(time - times_start_activ) / time_cst_activ)) 

111 ) 

112 

113 return output