It would be convenient to keep track of classes of customers in Ciw networks based on a collection of attributes. A first approximation is to try using tuples since they are immutable.
The following code works, and demonstrates that we can use tuples as the class names for Ciw.
"""Two customer classes named by consistently-shaped tuples.
Both class names are ``(str, int)`` pairs, so Python's lexicographic tuple
ordering compares like with like, and Ciw accepts them. This is the first
demonstration in the post: tuples work as class names so long as every tuple
can be ordered against the others.
"""
from __future__ import annotations
import ciw
def build_network() -> ciw.network.Network:
"""Return a three-node network with two tuple-named customer classes."""
return ciw.create_network(
arrival_distributions={
("Baby", 0): [
ciw.dists.Exponential(rate=1.0),
None,
None,
],
("Child", 1): [
ciw.dists.Exponential(rate=2.0),
None,
None,
],
},
service_distributions={
("Baby", 0): [
ciw.dists.Exponential(rate=4.0),
ciw.dists.Exponential(rate=1.0),
ciw.dists.Deterministic(value=0.0),
],
("Child", 1): [
ciw.dists.Exponential(rate=6.0),
ciw.dists.Deterministic(value=0.0),
ciw.dists.Exponential(rate=1.0),
],
},
routing={
("Baby", 0): [
[0.0, 1.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
],
("Child", 1): [
[0.0, 0.0, 1.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
],
},
number_of_servers=[1, 2, 3],
)
def run_simulation(max_time: float = 9) -> ciw.Simulation:
"""Create the network and simulate it until ``max_time``."""
network = build_network()
simulation = ciw.Simulation(network)
simulation.simulate_until_max_time(max_time)
return simulation
def main() -> None:
"""Run the simulation (no printed output in the post)."""
run_simulation()
if __name__ == "__main__":
main()Also note that the tuples do not need to be of the same length, since the lexicographical ordering used to evaluate tuples in Python will stop at the end of the shortest tuple.
But they do need to have order-compatible data. For example, the following code raises TypeError: '<' not supported between instances of 'int' and 'str'.
"""Mixed-type tuples fail: tuples must be mutually orderable.
Ciw sorts its class names, so a ``str``-first tuple such as ``("Baby", 0)``
cannot coexist with an ``int``-first tuple such as ``(1,)``. Python's
lexicographic comparison hits the first differing element, finds ``"Baby"``
against ``1``, and raises ``TypeError: '<' not supported between instances of
'int' and 'str'``. This module builds exactly that broken network so the
failure can be demonstrated and tested.
"""
from __future__ import annotations
import ciw
def build_network() -> ciw.network.Network:
"""Build the network whose mixed-type class names make Ciw raise.
Returns
-------
ciw.network.Network
This function never returns: sorting the class names raises
``TypeError`` before a network can be constructed.
"""
return ciw.create_network(
arrival_distributions={
("Baby", 0): [
ciw.dists.Exponential(rate=1.0),
None,
None,
],
(1,): [
ciw.dists.Exponential(rate=2.0),
None,
None,
],
},
service_distributions={
("Baby", 0): [
ciw.dists.Exponential(rate=4.0),
ciw.dists.Exponential(rate=1.0),
ciw.dists.Deterministic(value=0.0),
],
(1,): [
ciw.dists.Exponential(rate=6.0),
ciw.dists.Deterministic(value=0.0),
ciw.dists.Exponential(rate=1.0),
],
},
routing={
("Baby", 0): [
[0.0, 1.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
],
(1,): [
[0.0, 0.0, 1.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
],
},
number_of_servers=[1, 2, 3],
)
def main() -> None:
"""Attempt the broken build and report the resulting ``TypeError``."""
try:
build_network()
except TypeError as exc:
print(f"TypeError: {exc}")
if __name__ == "__main__":
main()One approach is to ensure equal tuple lengths and corresponding types by having empty types (e.g. empty string '') or zeros (i.e. 0). Here is the code using the empty string instead of 'child'.
"""Mixed-type tuples are fixed by padding with a matching type.
The workaround from the post is to keep every tuple the same length and give
each position a consistent type. Replacing the string ``"Child"`` with the
empty string ``""`` keeps the first element a ``str``, so ``("Baby", 0)`` and
``("", 1)`` order without error.
"""
from __future__ import annotations
import ciw
def build_network() -> ciw.network.Network:
"""Return the network with empty-string-padded tuple class names."""
return ciw.create_network(
arrival_distributions={
("Baby", 0): [
ciw.dists.Exponential(rate=1.0),
None,
None,
],
("", 1): [
ciw.dists.Exponential(rate=2.0),
None,
None,
],
},
service_distributions={
("Baby", 0): [
ciw.dists.Exponential(rate=4.0),
ciw.dists.Exponential(rate=1.0),
ciw.dists.Deterministic(value=0.0),
],
("", 1): [
ciw.dists.Exponential(rate=6.0),
ciw.dists.Deterministic(value=0.0),
ciw.dists.Exponential(rate=1.0),
],
},
routing={
("Baby", 0): [
[0.0, 1.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
],
("", 1): [
[0.0, 0.0, 1.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
],
},
number_of_servers=[1, 2, 3],
)
def run_simulation(max_time: float = 9) -> ciw.Simulation:
"""Create the network and simulate it until ``max_time``."""
network = build_network()
simulation = ciw.Simulation(network)
simulation.simulate_until_max_time(max_time)
return simulation
def main() -> None:
"""Run the padded simulation (no printed output in the post)."""
run_simulation()
if __name__ == "__main__":
main()Tuples are a good first start. It should be possible to make a data-containing class which is immutable and ordered.