Skip to content

Core

dissmodel.core.Environment

Bases: Environment

Simulation environment with support for a custom time window.

Extends :class:salabim.Environment with start_time and end_time to define the simulation boundaries explicitly.

Parameters:

Name Type Description Default
start_time float

Simulation start time, by default 0.

0
end_time float

Simulation end time. Can also be set via till in :meth:run.

None
*args Any

Extra positional arguments forwarded to :class:salabim.Environment.

()
**kwargs Any

Extra keyword arguments forwarded to :class:salabim.Environment.

{}

Examples:

>>> env = Environment(start_time=0, end_time=10)
>>> env.start_time
0
>>> env.end_time
10
Source code in dissmodel/core/environment.py
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
class Environment(sim.Environment):
    """
    Simulation environment with support for a custom time window.

    Extends :class:`salabim.Environment` with ``start_time`` and ``end_time``
    to define the simulation boundaries explicitly.

    Parameters
    ----------
    start_time : float, optional
        Simulation start time, by default 0.
    end_time : float, optional
        Simulation end time. Can also be set via ``till`` in :meth:`run`.
    *args :
        Extra positional arguments forwarded to :class:`salabim.Environment`.
    **kwargs :
        Extra keyword arguments forwarded to :class:`salabim.Environment`.

    Examples
    --------
    >>> env = Environment(start_time=0, end_time=10)
    >>> env.start_time
    0
    >>> env.end_time
    10
    """

    def __init__(
        self,
        start_time: float = 0,
        end_time: Optional[float] = None,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        kwargs.pop("animation", None)
        kwargs.pop("trace", False)
        super().__init__(*args, trace=False, **kwargs)
        self.start_time = start_time
        self.end_time = end_time

    def run(self, till: Optional[float] = None) -> None:
        """
        Run the simulation over the configured time window.

        Parameters
        ----------
        till : float, optional
            Duration to run from ``start_time``. If provided, overrides
            ``end_time``. If omitted, ``end_time`` must be set.

        Raises
        ------
        ValueError
            If neither ``till`` nor ``end_time`` is defined.

        Examples
        --------
        >>> env = Environment(start_time=0, end_time=10)
        >>> env.run()
        Running from 0 to 10 (duration: 10)
        """
        self.reset()

        if till is not None:
            self.end_time = self.start_time + till
        elif self.end_time is not None:
            till = self.end_time - self.start_time
        else:
            raise ValueError("Provide 'till' or set 'end_time' before calling run().")

        print(f"Running from {self.start_time} to {self.end_time} (duration: {till})")
        super().run(till=till)

    def reset(self) -> None:
        """
        Clear accumulated plot data.

        This method is called automatically at the start of :meth:`run` to
        ensure charts start fresh on each simulation run.

        Examples
        --------
        >>> env = Environment()
        >>> env._plot_metadata = {"x": {"data": [1, 2, 3]}}
        >>> env.reset()
        >>> env._plot_metadata["x"]["data"]
        []
        """
        if hasattr(self, "_plot_metadata"):
            for item in self._plot_metadata.values():
                item["data"].clear()

    def now(self) -> float:
        """
        Return the current simulation time adjusted by ``start_time``.

        Returns
        -------
        float
            Current time as ``salabim.now() + start_time``.

        Examples
        --------
        >>> env = Environment(start_time=5)
        >>> env.now()
        5.0
        """
        return super().now() + self.start_time

now()

Return the current simulation time adjusted by start_time.

Returns:

Type Description
float

Current time as salabim.now() + start_time.

Examples:

>>> env = Environment(start_time=5)
>>> env.now()
5.0
Source code in dissmodel/core/environment.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def now(self) -> float:
    """
    Return the current simulation time adjusted by ``start_time``.

    Returns
    -------
    float
        Current time as ``salabim.now() + start_time``.

    Examples
    --------
    >>> env = Environment(start_time=5)
    >>> env.now()
    5.0
    """
    return super().now() + self.start_time

reset()

Clear accumulated plot data.

This method is called automatically at the start of :meth:run to ensure charts start fresh on each simulation run.

Examples:

>>> env = Environment()
>>> env._plot_metadata = {"x": {"data": [1, 2, 3]}}
>>> env.reset()
>>> env._plot_metadata["x"]["data"]
[]
Source code in dissmodel/core/environment.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def reset(self) -> None:
    """
    Clear accumulated plot data.

    This method is called automatically at the start of :meth:`run` to
    ensure charts start fresh on each simulation run.

    Examples
    --------
    >>> env = Environment()
    >>> env._plot_metadata = {"x": {"data": [1, 2, 3]}}
    >>> env.reset()
    >>> env._plot_metadata["x"]["data"]
    []
    """
    if hasattr(self, "_plot_metadata"):
        for item in self._plot_metadata.values():
            item["data"].clear()

run(till=None)

Run the simulation over the configured time window.

Parameters:

Name Type Description Default
till float

Duration to run from start_time. If provided, overrides end_time. If omitted, end_time must be set.

None

Raises:

Type Description
ValueError

If neither till nor end_time is defined.

Examples:

>>> env = Environment(start_time=0, end_time=10)
>>> env.run()
Running from 0 to 10 (duration: 10)
Source code in dissmodel/core/environment.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def run(self, till: Optional[float] = None) -> None:
    """
    Run the simulation over the configured time window.

    Parameters
    ----------
    till : float, optional
        Duration to run from ``start_time``. If provided, overrides
        ``end_time``. If omitted, ``end_time`` must be set.

    Raises
    ------
    ValueError
        If neither ``till`` nor ``end_time`` is defined.

    Examples
    --------
    >>> env = Environment(start_time=0, end_time=10)
    >>> env.run()
    Running from 0 to 10 (duration: 10)
    """
    self.reset()

    if till is not None:
        self.end_time = self.start_time + till
    elif self.end_time is not None:
        till = self.end_time - self.start_time
    else:
        raise ValueError("Provide 'till' or set 'end_time' before calling run().")

    print(f"Running from {self.start_time} to {self.end_time} (duration: {till})")
    super().run(till=till)

dissmodel.core.Model

Bases: Component

Base class for simulation models backed by a salabim Component.

Provides a time-stepped execution loop and automatic tracking of attributes marked for plotting via the :func:~dissmodel.visualization.track_plot decorator.

Parameters:

Name Type Description Default
step float

Time increment between successive :meth:execute calls, by default 1.

1
start_time float

Time at which the model starts executing, by default 0.

0
end_time float

Time at which the model stops executing, by default math.inf.

inf
name str

Component name, by default "".

''
*args Any

Extra positional arguments forwarded to :class:salabim.Component.

()
**kwargs Any

Extra keyword arguments forwarded to :class:salabim.Component.

{}

Examples:

>>> class MyModel(Model):
...     def execute(self):
...         pass
>>> env = Environment()
>>> model = MyModel(step=1, start_time=0, end_time=5)
>>> model.start_time
0
>>> model.end_time
5
Source code in dissmodel/core/model.py
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
class Model(sim.Component):
    """
    Base class for simulation models backed by a salabim Component.

    Provides a time-stepped execution loop and automatic tracking of
    attributes marked for plotting via the :func:`~dissmodel.visualization.track_plot`
    decorator.

    Parameters
    ----------
    step : float, optional
        Time increment between successive :meth:`execute` calls, by default 1.
    start_time : float, optional
        Time at which the model starts executing, by default 0.
    end_time : float, optional
        Time at which the model stops executing, by default ``math.inf``.
    name : str, optional
        Component name, by default ``""``.
    *args :
        Extra positional arguments forwarded to :class:`salabim.Component`.
    **kwargs :
        Extra keyword arguments forwarded to :class:`salabim.Component`.

    Examples
    --------
    >>> class MyModel(Model):
    ...     def execute(self):
    ...         pass
    >>> env = Environment()
    >>> model = MyModel(step=1, start_time=0, end_time=5)
    >>> model.start_time
    0
    >>> model.end_time
    5
    """

    def __init__(
        self,
        step: float = 1,
        start_time: float = 0,
        end_time: float = math.inf,
        name: str = "",
        *args: Any,
        **kwargs: Any,
    ) -> None:
        super().__init__(*args, **kwargs)
        self._step = step
        self.start_time = start_time
        self.end_time = end_time

    def process(self) -> None:
        """
        salabim process loop.

        Waits until ``start_time``, then calls :meth:`execute` every
        ``step`` time units until ``end_time``.
        """
        if self.env.now() < self.start_time:
            self.hold(self.start_time - self.env.now())

        while self.env.now() < self.end_time:
            self.execute()
            self.hold(self._step)

    def execute(self) -> None:
        """
        Called once per time step.

        Override in subclasses to define model behaviour.
        """
        pass

    def __setattr__(self, name: str, value: Any) -> None:
        """
        Intercept attribute assignment to record values marked for plotting.

        If the class defines ``_plot_info`` (via the
        :func:`~dissmodel.visualization.track_plot` decorator) and ``name``
        matches a tracked attribute, the value is appended to the plot data
        buffer and registered in ``env._plot_metadata``.

        Parameters
        ----------
        name : str
            Attribute name being set.
        value : Any
            Value being assigned.
        """
        cls = self.__class__

        if hasattr(cls, "_plot_info") and name.lower() in cls._plot_info:
            plot_info: dict[str, Any] = cls._plot_info[name.lower()]
            plot_info["data"].append(value)

            if not hasattr(self.env, "_plot_metadata"):
                self.env._plot_metadata = {}

            if plot_info["label"] not in self.env._plot_metadata:
                self.env._plot_metadata[plot_info["label"]] = plot_info

        super().__setattr__(name, value)

__setattr__(name, value)

Intercept attribute assignment to record values marked for plotting.

If the class defines _plot_info (via the :func:~dissmodel.visualization.track_plot decorator) and name matches a tracked attribute, the value is appended to the plot data buffer and registered in env._plot_metadata.

Parameters:

Name Type Description Default
name str

Attribute name being set.

required
value Any

Value being assigned.

required
Source code in dissmodel/core/model.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def __setattr__(self, name: str, value: Any) -> None:
    """
    Intercept attribute assignment to record values marked for plotting.

    If the class defines ``_plot_info`` (via the
    :func:`~dissmodel.visualization.track_plot` decorator) and ``name``
    matches a tracked attribute, the value is appended to the plot data
    buffer and registered in ``env._plot_metadata``.

    Parameters
    ----------
    name : str
        Attribute name being set.
    value : Any
        Value being assigned.
    """
    cls = self.__class__

    if hasattr(cls, "_plot_info") and name.lower() in cls._plot_info:
        plot_info: dict[str, Any] = cls._plot_info[name.lower()]
        plot_info["data"].append(value)

        if not hasattr(self.env, "_plot_metadata"):
            self.env._plot_metadata = {}

        if plot_info["label"] not in self.env._plot_metadata:
            self.env._plot_metadata[plot_info["label"]] = plot_info

    super().__setattr__(name, value)

execute()

Called once per time step.

Override in subclasses to define model behaviour.

Source code in dissmodel/core/model.py
73
74
75
76
77
78
79
def execute(self) -> None:
    """
    Called once per time step.

    Override in subclasses to define model behaviour.
    """
    pass

process()

salabim process loop.

Waits until start_time, then calls :meth:execute every step time units until end_time.

Source code in dissmodel/core/model.py
59
60
61
62
63
64
65
66
67
68
69
70
71
def process(self) -> None:
    """
    salabim process loop.

    Waits until ``start_time``, then calls :meth:`execute` every
    ``step`` time units until ``end_time``.
    """
    if self.env.now() < self.start_time:
        self.hold(self.start_time - self.env.now())

    while self.env.now() < self.end_time:
        self.execute()
        self.hold(self._step)