from typing import Union
from enum import Enum
from functools import total_ordering
from wolfgpu.utils import parse_duration_to_seconds
[docs]
class SimulationDurationType(Enum):
""" A type of duration.
This was introduced to abstract the duration of a simulation.
"""
# Note that the way we put it, there's no way you can do
# a simulation that has no planned finite time (it's time
# step could be infinitely small though).
""" A duration expressed in seconds. """
""" A duration expressed in steps. """
@total_ordering
[docs]
class SimulationDuration:
""" In the GPU simulator, we represent durations about the simulation
either as number of simulation steps either as simulation time (in seconds).
This class can represent both.
"""
def __init__(self, _type:SimulationDurationType, duration: Union[float, int]):
assert isinstance(_type, SimulationDurationType)
#assert duration > 0, f"A duration must be strictly positive. You gave {duration}."
if _type == SimulationDurationType.STEPS:
assert type(duration) is int, "Simulation total number of steps must be an integer"
[docs]
self.duration = duration # Expressed in seconds
def __str__(self):
if self.type == SimulationDurationType.STEPS:
if self.duration <= 1:
return f"{self.duration} step"
else:
return f"{self.duration} steps"
elif self.type == SimulationDurationType.SECONDS:
return f"{self.duration:g} s."
[docs]
def to_dict(self):
return {
"type": self.type.name,
"duration": self.duration
}
@classmethod
[docs]
def from_dict(self, d):
t = SimulationDurationType[d["type"]]
match t:
case SimulationDurationType.SECONDS:
d = float(d["duration"])
case SimulationDurationType.STEPS:
d = int(d["duration"])
case _:
raise Exception(f"Unexpected duration type, you gave {self.type}")
return SimulationDuration(t,d)
def __add__(self, value:"SimulationDuration"):
assert self.type == value.type, "You are mixing different types of durations"
return SimulationDuration( self.type, self.duration + value.duration)
def __eq__(self, value: object) -> bool:
return self.type == value.type and self.duration == value.duration
def __lt__(self, value: "SimulationDuration") -> bool:
if self.type == SimulationDurationType.SECONDS and value.type == SimulationDurationType.SECONDS or \
self.type == SimulationDurationType.STEPS and value.type == SimulationDurationType.STEPS:
return self.duration < value.duration
else:
raise Exception(f"Can't compare SimulationDuration objects. {self.type} != {value.type}")
@classmethod
[docs]
def from_seconds(cls, seconds) -> "SimulationDuration":
return SimulationDuration(SimulationDurationType.SECONDS, parse_duration_to_seconds(seconds))
@classmethod
[docs]
def from_steps(cls, steps) -> "SimulationDuration":
return SimulationDuration(SimulationDurationType.STEPS, int(steps))
@classmethod
[docs]
def from_str(cls, d: str) -> "SimulationDuration":
# An integer is a number of steps
# Anything else (float or hms) is a duration
try:
steps = int(d)
except ValueError:
return cls.from_seconds(d)
return cls.from_steps(steps)
@classmethod
[docs]
def zero_like(cls, d) -> "SimulationDuration":
return SimulationDuration(d.type, 0)
[docs]
def is_zero(self):
return self.duration == 0
[docs]
class ReportFrequencyType(Enum):
""" When specifying the report period, one can
use two different units.
"""
# FIXME Can't we just use a simulationduration ?
# These are the values used by Wolf
""" Report period in iterations """
""" Report period in simulation time (seconds) """
[docs]
class TimeStepStrategy(Enum):
""" Describes the way the time step is computed on each simulation step.
"""
""" The time step is constant over all simulation steps.
"""
""" The time step is the maximum of the minimum time steps authorized
by the Courant formula.
"""