Skip to content

Commit

Permalink
Merge pull request #246 from CiwPython/for3.2.0
Browse files Browse the repository at this point in the history
For 3.2.0
  • Loading branch information
geraintpalmer authored May 7, 2024
2 parents 2fd69b0 + 8155d5e commit d223453
Show file tree
Hide file tree
Showing 98 changed files with 8,785 additions and 6,447 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v2
Expand Down
40 changes: 21 additions & 19 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ Install with :code:`pip install ciw`.

Current supported version of Python:

- Python 3.7
- Python 3.8
- Python 3.9
- Python 3.10
- Python 3.11
- Python 3.12

Usage
-----
Expand Down Expand Up @@ -75,23 +75,25 @@ Features

A number of other features are also implemented, including:

+ `Type I blocking <https://ciw.readthedocs.io/en/latest/Tutorial-II/tutorial_vi.html>`_
+ `Type I blocking <https://ciw.readthedocs.io/en/latest/Guides/Queues/queue_capacities.html>`_
+ `A large range of sampling distributions <https://ciw.readthedocs.io/en/latest/Reference/distributions.html>`_
+ `Phase-Type distributions <https://ciw.readthedocs.io/en/latest/Guides/phasetype.html>`_
+ `Time-dependent and state-dependent distributions <https://ciw.readthedocs.io/en/latest/Guides/time_dependent.html>`_
+ `Batch arrivals <https://ciw.readthedocs.io/en/latest/Guides/batching.html>`_
+ `Baulking customers <https://ciw.readthedocs.io/en/latest/Guides/baulking.html>`_
+ `Reneging customers <https://ciw.readthedocs.io/en/latest/Guides/reneging.html>`_
+ `Processor sharing <https://ciw.readthedocs.io/en/latest/Guides/processor-sharing.html>`_
+ `Multiple customer classes <https://ciw.readthedocs.io/en/latest/Tutorial-II/tutorial_vii.html>`_
+ `Priorities <https://ciw.readthedocs.io/en/latest/Guides/priority.html>`_
+ `Server priorities <https://ciw.readthedocs.io/en/latest/Guides/server_priority.html>`_
+ `Service disciplines <https://ciw.readthedocs.io/en/latest/Guides/service_disciplines.html>`_
+ `Customers changing classes <https://ciw.readthedocs.io/en/latest/Guides/dynamic_customerclasses.html>`_
+ `Server schedules <https://ciw.readthedocs.io/en/latest/Guides/server_schedule.html>`_
+ `Slotted services <https://ciw.readthedocs.io/en/latest/Guides/slotted.html>`_
+ `State tracking <https://ciw.readthedocs.io/en/latest/Guides/state_trackers.html>`_
+ `Stopping the simulation after a certain amount of customers <https://ciw.readthedocs.io/en/latest/Guides/sim_numcusts.html>`_
+ `Process-based routing <https://ciw.readthedocs.io/en/latest/Guides/process_based.html>`_
+ `Deadlock detection <https://ciw.readthedocs.io/en/latest/Guides/deadlock.html>`_
+ `Phase-Type distributions <https://ciw.readthedocs.io/en/latest/Guides/Distributions/phasetype.html>`_
+ `Time-dependent and state-dependent distributions <https://ciw.readthedocs.io/en/latest/Guides/Distributions/time_dependent.html>`_
+ `Batch arrivals <https://ciw.readthedocs.io/en/latest/Guides/Arrivals/batching.html>`_
+ `Baulking customers <https://ciw.readthedocs.io/en/latest/Guides/CustomerBehaviour/baulking.html>`_
+ `Reneging customers <https://ciw.readthedocs.io/en/latest/Guides/CustomerBehaviour/reneging.html>`_
+ `Processor sharing <https://ciw.readthedocs.io/en/latest/Guides/Services/processor-sharing.html>`_
+ `Multiple customer classes <https://ciw.readthedocs.io/en/latest/Guides/CustomerClasses/customer-classes.html>`_
+ `Priorities <https://ciw.readthedocs.io/en/latest/Guides/CustomerClasses/priority.html>`_
+ `Server priorities <https://ciw.readthedocs.io/en/latest/Guides/Services/server_priority.html>`_
+ `Service disciplines <https://ciw.readthedocs.io/en/latest/Guides/Services/service_disciplines.html>`_
+ `Customers changing classes while queueing <https://ciw.readthedocs.io/en/latest/Guides/CustomerClasses/change-class-while-queueing.html>`_
+ `Customers changing classes after service <https://ciw.readthedocs.io/en/latest/Guides/CustomerClasses/change-class-after-service.html>`_
+ `Server schedules <https://ciw.readthedocs.io/en/latest/Guides/Services/server_schedule.html>`_
+ `Slotted services <https://ciw.readthedocs.io/en/latest/Guides/Services/slotted.html>`_
+ `State tracking <https://ciw.readthedocs.io/en/latest/Guides/System/state_trackers.html>`_
+ `Stopping the simulation after a certain amount of customers <https://ciw.readthedocs.io/en/latest/Guides/Simulation/sim_numcusts.html>`_
+ `Process-based routing <https://ciw.readthedocs.io/en/latest/Guides/Routing/process_based.html>`_
+ `Logical routing <https://ciw.readthedocs.io/en/latest/Guides/Reference/routers.html>`_
+ `Deadlock detection <https://ciw.readthedocs.io/en/latest/Guides/System/deadlock.html>`_

7 changes: 4 additions & 3 deletions ciw/arrival_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(self, simulation):
self.number_of_individuals_per_class = {clss: 0 for clss in self.simulation.network.customer_class_names}
self.number_accepted_individuals = 0
self.number_accepted_individuals_per_class = {clss: 0 for clss in self.simulation.network.customer_class_names}
self.system_capacity = self.simulation.network.system_capacity
self.event_dates_dict = {
nd + 1: {clss: False for clss in self.simulation.network.customer_class_names
} for nd in range(self.simulation.network.number_of_nodes)
Expand Down Expand Up @@ -91,9 +92,9 @@ def have_event(self):
priority_class,
simulation=self.simulation,
)
if self.simulation.network.process_based:
next_individual.route = self.simulation.network.customer_classes[next_individual.customer_class].routing[self.next_node - 1](next_individual)
next_node = self.simulation.transitive_nodes[self.next_node - 1]
next_individual.starting_node = next_node.id_number
self.simulation.routers[next_individual.customer_class].initialise_individual(next_individual)
self.release_individual(next_node, next_individual)

self.event_dates_dict[self.next_node][self.next_class] = self.increment_time(
Expand Down Expand Up @@ -153,7 +154,7 @@ def release_individual(self, next_node, next_individual):
Either rejects the next_individual die to lack of capacity,
or sends that individual to baulk or not.
"""
if next_node.number_of_individuals >= next_node.node_capacity:
if (next_node.number_of_individuals >= next_node.node_capacity) or (self.simulation.number_of_individuals >= self.system_capacity):
self.record_rejection(next_node, next_individual)
self.simulation.nodes[-1].accept(next_individual, completed=False)
else:
Expand Down
173 changes: 66 additions & 107 deletions ciw/import_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ciw.dists
from .network import *
from .schedules import *
from .routing import *


def create_network(
Expand All @@ -21,6 +22,7 @@ def create_network(
reneging_time_distributions=None,
reneging_destinations=None,
service_disciplines=None,
system_capacity=float('inf')
):
"""
Takes in kwargs, creates dictionary.
Expand All @@ -41,6 +43,7 @@ def create_network(
"arrival_distributions": arrival_distributions,
"number_of_servers": number_of_servers,
"service_distributions": service_distributions,
'system_capacity': system_capacity
}

if baulking_functions is not None:
Expand Down Expand Up @@ -115,35 +118,19 @@ def create_network_from_dictionary(params_input):
)
)
for clss_name in params['customer_class_names']:
if all(isinstance(f, types.FunctionType) or isinstance(f, types.MethodType) for f in params["routing"]):
classes[clss_name] = CustomerClass(
params['arrival_distributions'][clss_name],
params['service_distributions'][clss_name],
params['routing'],
params["priority_classes"][clss_name],
params["baulking_functions"][clss_name],
params["batching_distributions"][clss_name],
params["reneging_time_distributions"][clss_name],
params["reneging_destinations"][clss_name],
class_change_time_distributions[clss_name],
)
else:
classes[clss_name] = CustomerClass(
params['arrival_distributions'][clss_name],
params['service_distributions'][clss_name],
params['routing'][clss_name],
params["priority_classes"][clss_name],
params["baulking_functions"][clss_name],
params["batching_distributions"][clss_name],
params["reneging_time_distributions"][clss_name],
params["reneging_destinations"][clss_name],
class_change_time_distributions[clss_name],
)
classes[clss_name] = CustomerClass(
params['arrival_distributions'][clss_name],
params['service_distributions'][clss_name],
params['routing'][clss_name],
params["priority_classes"][clss_name],
params["baulking_functions"][clss_name],
params["batching_distributions"][clss_name],
params["reneging_time_distributions"][clss_name],
params["reneging_destinations"][clss_name],
class_change_time_distributions[clss_name],
)
n = Network(nodes, classes)
if all(isinstance(f, types.FunctionType) or isinstance(f, types.MethodType) for f in params["routing"]):
n.process_based = True
else:
n.process_based = False
n.system_capacity = params['system_capacity']
return n


Expand All @@ -159,9 +146,16 @@ def fill_out_dictionary(params):
srv_dists = params["service_distributions"]
params["service_distributions"] = {"Customer": srv_dists}
if "routing" in params:
if all(isinstance(f, list) for f in params["routing"]):
rtng_mat = params["routing"]
params["routing"] = {"Customer": rtng_mat}
if isinstance(params["routing"], list):
transition_matrix = params["routing"]
params["routing"] = {"Customer": routing.TransitionMatrix(transition_matrix=transition_matrix)}
elif isinstance(params["routing"], dict):
for clss in params["routing"]:
if isinstance(params["routing"][clss], list):
transition_matrix = params["routing"][clss]
params["routing"][clss] = routing.TransitionMatrix(transition_matrix=transition_matrix)
else:
params["routing"] = {"Customer": params["routing"]}
if "baulking_functions" in params:
if isinstance(params["baulking_functions"], list):
blk_fncs = params["baulking_functions"]
Expand All @@ -184,7 +178,7 @@ def fill_out_dictionary(params):

default_dict = {
"name": "Simulation",
"routing": {class_name: [[0.0]] for class_name in class_names},
"routing": {class_name: routing.TransitionMatrix(transition_matrix=[[0.0]]) for class_name in class_names},
"number_of_nodes": len(params["number_of_servers"]),
"number_of_classes": len(class_names),
"queue_capacities": [float("inf") for _ in range(len(params["number_of_servers"]))],
Expand All @@ -208,6 +202,7 @@ def fill_out_dictionary(params):
"service_disciplines": [
ciw.disciplines.FIFO for _ in range(len(params["number_of_servers"]))
],
"system_capacity": float('inf')
}

for a in default_dict:
Expand All @@ -220,87 +215,46 @@ def validify_dictionary(params):
Raises errors if there is something wrong with the
parameters dictionary.
"""
if all(isinstance(f, types.FunctionType) or isinstance(f, types.MethodType) for f in params["routing"]):
consistant_num_classes = (
params["number_of_classes"]
== len(params["arrival_distributions"])
== len(params["service_distributions"])
== len(params["batching_distributions"])
== len(params["reneging_time_distributions"])
== len(params["reneging_destinations"])
)
else:
consistant_num_classes = (
params["number_of_classes"]
== len(params["arrival_distributions"])
== len(params["service_distributions"])
== len(params["routing"])
== len(params["batching_distributions"])
== len(params["reneging_time_distributions"])
== len(params["reneging_destinations"])
)
consistant_num_classes = (
params["number_of_classes"]
== len(params["arrival_distributions"])
== len(params["service_distributions"])
== len(params["routing"])
== len(params["batching_distributions"])
== len(params["reneging_time_distributions"])
== len(params["reneging_destinations"])
)
if not consistant_num_classes:
raise ValueError("Ensure consistant number of classes is used throughout.")
if all(isinstance(f, types.FunctionType) or isinstance(f, types.MethodType) for f in params["routing"]):
consistant_class_names = (
set(params["arrival_distributions"])
== set(params["service_distributions"])
== set(params["batching_distributions"])
== set(params["reneging_time_distributions"])
== set(params["reneging_destinations"])
)
else:
consistant_class_names = (
set(params["arrival_distributions"])
== set(params["service_distributions"])
== set(params["routing"])
== set(params["batching_distributions"])
== set(params["reneging_time_distributions"])
== set(params["reneging_destinations"])
) and (
len(params["arrival_distributions"])
== len(params["service_distributions"])
== len(params["batching_distributions"])
== len(params["reneging_time_distributions"])
== len(params["reneging_destinations"])
)
consistant_class_names = (
set(params["arrival_distributions"])
== set(params["service_distributions"])
== set(params["routing"])
== set(params["batching_distributions"])
== set(params["reneging_time_distributions"])
== set(params["reneging_destinations"])
) and (
len(params["arrival_distributions"])
== len(params["service_distributions"])
== len(params["batching_distributions"])
== len(params["reneging_time_distributions"])
== len(params["reneging_destinations"])
)
if not consistant_class_names:
raise ValueError("Ensure consistant names for customer classes.")
if all(isinstance(f, types.FunctionType) or isinstance(f, types.MethodType) for f in params["routing"]):
num_nodes_count = (
[params["number_of_nodes"]]
+ [len(obs) for obs in params["arrival_distributions"].values()]
+ [len(obs) for obs in params["service_distributions"].values()]
+ [len(obs) for obs in params["batching_distributions"].values()]
+ [len(obs) for obs in params["reneging_time_distributions"].values()]
+ [len(obs) for obs in params["reneging_destinations"].values()]
+ [len(params["routing"])]
+ [len(params["number_of_servers"])]
+ [len(params["server_priority_functions"])]
+ [len(params["queue_capacities"])]
)
else:
num_nodes_count = (
[params["number_of_nodes"]]
+ [len(obs) for obs in params["arrival_distributions"].values()]
+ [len(obs) for obs in params["service_distributions"].values()]
+ [len(obs) for obs in params["routing"].values()]
+ [len(obs) for obs in params["batching_distributions"].values()]
+ [len(obs) for obs in params["reneging_time_distributions"].values()]
+ [len(obs) for obs in params["reneging_destinations"].values()]
+ [len(row) for row in [obs for obs in params["routing"].values()][0]]
+ [len(params["number_of_servers"])]
+ [len(params["server_priority_functions"])]
+ [len(params["queue_capacities"])]
+ [len(params["service_disciplines"])]
)
num_nodes_count = (
[params["number_of_nodes"]]
+ [len(obs) for obs in params["arrival_distributions"].values()]
+ [len(obs) for obs in params["service_distributions"].values()]
+ [len(obs) for obs in params["batching_distributions"].values()]
+ [len(obs) for obs in params["reneging_time_distributions"].values()]
+ [len(obs) for obs in params["reneging_destinations"].values()]
+ [len(params["number_of_servers"])]
+ [len(params["server_priority_functions"])]
+ [len(params["queue_capacities"])]
)
if len(set(num_nodes_count)) != 1:
raise ValueError("Ensure consistant number of nodes is used throughout.")
if not all(isinstance(f, types.FunctionType) or isinstance(f, types.MethodType) for f in params["routing"]):
for clss in params["routing"].values():
for row in clss:
if sum(row) > 1.0 or min(row) < 0.0 or max(row) > 1.0:
raise ValueError("Ensure that routing matrix is valid.")
neg_numservers = any(
[(isinstance(obs, int) and obs < 0) for obs in params["number_of_servers"]]
)
Expand Down Expand Up @@ -352,3 +306,8 @@ def validify_dictionary(params):
)
if not correct_destinations:
raise ValueError("Ensure all reneging destinations are possible.")

if not isinstance(params['system_capacity'], int) and params['system_capacity'] != float('inf'):
raise ValueError("Ensure system capacity is a positive integer.")
if params['system_capacity'] <= 0:
raise ValueError("Ensure system capacity is a positive integer.")
Loading

0 comments on commit d223453

Please sign in to comment.