Coverage for physioblocks / simulation / time_manager.py: 97%
103 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/>.
27"""
28Defines a Time object, and a manager to hold the simulation time.
29"""
31from typing import Any
33import numpy as np
35from physioblocks.computing.quantities import Quantity
36from physioblocks.registers.type_register import register_type
38# Constant for the time quantity id in the simulation
39TIME_QUANTITY_ID = "time"
41_DEFAULT_STEP = 0.001
42_DEFAULT_MIN_STEP = _DEFAULT_STEP / 16.0
45class Time(Quantity[np.float64]):
46 """
47 Extend :class:`~physioblocks.computing.quantities.Quantity` class to define
48 simulation time.
50 Whenever the time is updated, it recomputes the difference between the new
51 and the current value and its inverse (accessibles trough the ``dt`` and ``inv_dt``
52 properties)
53 """
55 _dt: float
56 """Difference between ``new`` and ``current`` values"""
58 _inv_dt: float
59 """Inverse of ``dt``"""
61 def __init__(self, value: float):
62 """
63 Initialize ``dt`` and ``inv_dt`` to 0.
65 :param value: the initial time value
66 :type value: float
67 """
68 super().__init__(value)
69 self._dt = 0.0
70 self._inv_dt = 0.0
72 @property
73 def dt(self) -> float:
74 """
75 Get the difference between the ``new`` and the ``current`` value of the time.
77 :return: the delta time value
78 :rtype: float
79 """
80 return self._dt
82 @property
83 def inv_dt(self) -> float:
84 """
85 Get the inverse of ``dt``.
87 :return: the delta time inverse
88 :rtype: float
89 """
90 return self._inv_dt
92 def update(self, new: Any) -> None:
93 """
94 Update the ``new`` value of the time and recomputes ``dt`` and ``inv_dt``.
96 .. note::
98 If ``dt`` is 0.0, ``inv_dt`` is set to 0.0.
100 :param new: the new value to set
101 :type new: float
102 """
103 super().update(new)
105 self._dt = self._new - self._current
106 if self._dt != 0.0:
107 self._inv_dt = 1.0 / self._dt
108 else:
109 self._inv_dt = 0.0
111 def initialize(self, value: Any) -> None:
112 """
113 Initialize the ``new`` and ``current`` value.
115 .. note::
117 It set both ``dt`` and ``inv_dt`` to 0.
119 :param value: the value to set
120 :type new: Any
121 """
122 super().initialize(value)
123 self._dt = 0.0
124 self._inv_dt = 0.0
127# Constant for time manager type
128TIME_MANAGER_ID = "time"
131@register_type(TIME_MANAGER_ID)
132class TimeManager:
133 """
134 Updates the time value during the simulation.
136 :param start: start value for the simulation time.
137 :type start: float
139 :param end: end value for the simulation time.
140 The time ``new`` and ``current`` value can not exceed it.
141 :type end: float
143 :param time_step: time increment when update time is called.
144 :type time_step: float
146 :param min_step: minimum allowed value of the time increment.
147 :type time_step: float
149 :raise ValueError: ValueError is raised when :
150 * the start parameter is superior to the end parameter.
151 * the time_step parameter is negative.
153 """
155 _time: Time
156 """Store the current time value"""
158 start: float
159 """The starting time of the time manager."""
161 def __init__(
162 self,
163 start: float = 0.0,
164 duration: float = _DEFAULT_STEP,
165 step_size: float = _DEFAULT_STEP,
166 min_step: float = _DEFAULT_MIN_STEP,
167 ):
168 if min_step <= 0.0:
169 raise ValueError(
170 str.format(
171 "Time Manager minimum time step value can not be 0 or negative",
172 )
173 )
174 self._min_step = min_step
175 self._time = Time(start)
176 self.start = start
177 self.duration = duration
178 self.step_size = step_size
179 self.current_step_size = step_size
181 @property
182 def min_step(self) -> float:
183 """
184 Get the minimum time step size allowed.
186 :return: the minimum time step size.
187 :rtype: float
188 """
189 return self._min_step
191 @min_step.setter
192 def min_step(self, value: float) -> None:
193 """
194 Set the start time.
196 :param value: the start time to set
197 :type value: float
199 :raise ValueError: Raises a ValueError if the given value is greater than the
200 end time.
201 """
203 if value <= 0.0:
204 raise ValueError(
205 str.format(
206 "Time Manager minimum time step value can not be 0 or negative",
207 )
208 )
209 elif value > self.step_size:
210 raise ValueError(
211 str.format(
212 "Time Manager minimum step value can not be superior to time_step",
213 )
214 )
215 self._min_step = value
217 @property
218 def end(self) -> float:
219 """
220 Get the current end time.
222 :return: the end time
223 :rtype: float
224 """
225 return self.start + self._duration
227 @property
228 def duration(self) -> float:
229 """
230 Get the duration.
232 :return: the duration
233 :rtype: float
234 """
235 return self._duration
237 @duration.setter
238 def duration(self, value: float) -> None:
239 """
240 Set the duration
242 :return: the duration
243 :rtype: float
244 """
245 if value <= 0.0:
246 raise ValueError(
247 str.format(
248 "Time Manager duration value can not be 0 or negative",
249 )
250 )
251 self._duration = value
253 @property
254 def step_size(self) -> float:
255 """
256 Get the standard step size
258 :return: the standard step size
259 :rtype: float
260 """
261 return self._max_step
263 @step_size.setter
264 def step_size(self, value: float) -> None:
265 """
266 Get the standard step size
268 :return: the standard step size
269 :rtype: float
270 """
271 if value <= 0.0:
272 raise ValueError(
273 str.format(
274 "Time Manager time step value can not be 0 or negative",
275 )
276 )
277 self._max_step = value
278 self.current_step_size = value
280 @property
281 def current_step_size(self) -> float:
282 """
283 Get the current time step size.
285 :return: the time step
286 :rtype: float
287 """
288 return self._time_step
290 @current_step_size.setter
291 def current_step_size(self, value: float) -> None:
292 """
293 Set the current time step size.
295 :param value: the time step to set
296 :type value: float
298 :raise ValueError: Raises a ValueError if the given value is
299 less than min_step or greater than max_step.
300 """
302 if value < self.min_step:
303 raise ValueError(
304 "Time Manager time step value can not be inferior to minimum step",
305 )
306 elif value > self._max_step:
307 raise ValueError(
308 "Time Manager time step value can not be superior to default step",
309 )
311 self._time_step = value
313 @property
314 def time(self) -> Time:
315 """
316 Get the current :class:`~.Time` quantity
318 :return: the time quantity
319 :rtype: Time
320 """
321 return self._time
323 @property
324 def ended(self) -> bool:
325 """
326 Get if the time has reached the end time.
328 :return: True if the new time value has reached the end time, False otherwise.
329 :rtype: bool
330 """
331 return bool(self._time.new >= self.end)
333 def update_time(self) -> None:
334 """
335 Set the ``current`` time value to the ``new`` time value and update the ``new``
336 time value with the defined time step increment.
338 If the ``end`` value is reached, the ``new`` time value is set
339 to the ``end`` value.
340 """
341 updated_time = self._time.current + self._time_step
342 if updated_time < self.end:
343 self._time.initialize(self._time.new)
344 self._time.update(self._time.current + self._time_step)
345 else:
346 # time manager reached end time
347 self._time.initialize(self._time.new)
348 self._time.update(self.end)
350 def initialize(self) -> None:
351 """
352 Initialize the time with the ``start`` value.
353 """
354 self._time.initialize(self.start)