Skip to content

Commit fb575da

Browse files
Merge pull request #281 from CiwPython/updates-by-pr
Updates PR
2 parents 0ff4697 + 300f0f2 commit fb575da

35 files changed

+343
-95
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
- name: Run tests
3636
run: |
3737
python -m pip install -r test_requirements.txt
38-
coverage run --source=ciw -m unittest discover
38+
coverage run -m unittest discover
3939
4040
- name: Report coverage
4141
run: |

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ dist/
1010
*ciw/.hypothesis*
1111
*ciw/disciplines/.hypothesis*
1212
env/
13+
venv/
14+
.venv/

CHANGES.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ History
22
-------
33

44
+ **3.2.5 (2025-03-25)**
5-
+ Fixes bug in PoissonIntervals distribution where if the last rate is zero or so small there is no samples, the cycle begins again too early.
5+
+ Fixes bug in PoissonIntervals distribution where if the last rate is zero or so small there is no samples, the cycle begins again too early.
66

77
+ **3.2.4 (2024-12-04)**
88
+ Adds a new method for the `simulate_until_max_customers`: "Complete" simulates until a specific number of completed customer journeys; while "Finish" simulates until a specific number of customers have reached the exit node (through bailking or reneging).

CONTRIBUTING.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ Install a development version of the library::
1919

2020
Make sure the tests pass (Ciw uses unit & doc testing)::
2121

22-
python -m unittest discover ciw
22+
python -m unittest discover
2323
python doctests.py
2424

2525
We encourage the use of coverage, ensuring all aspects of the code are tested::
2626

27-
coverage run --source=ciw -m unittest discover ciw.tests
27+
coverage run --source=ciw -m unittest discover
2828
coverage report -m
2929

3030
Add tests for your change. Make your change and make the tests pass.

ciw/deadlock/deadlock_detector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import networkx as nx
22

33

4-
class NoDetection(object):
4+
class NoDetection:
55
"""
66
A generic class for all deadlock detector classes to inherit from.
77
Using this class is equivalent to having no deadlock detection

ciw/dists/distributions.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,10 @@ class PhaseType(Distribution):
349349
"""
350350

351351
def __init__(self, initial_state, absorbing_matrix):
352-
if any(p < 0 or p > 1.0 for p in initial_state):
353-
raise ValueError("Initial state vector must have valid probabilities.")
352+
if any(p < 0 for p in initial_state):
353+
raise ValueError("Initial state vector must have positive probabilities.")
354+
if any(p > 1.0 for p in initial_state):
355+
raise ValueError("Initial state vector probabilities must be no more than 1.0.")
354356
if sum(initial_state) > 1.0 + 10 ** (-10) or sum(initial_state) < 1.0 - 10 ** (
355357
-10
356358
):

ciw/exactnode.py

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,34 @@
1+
from typing import List
2+
13
from .node import Node
24
from .arrival_node import ArrivalNode
3-
from decimal import Decimal, getcontext
5+
from decimal import Decimal
46
from .server import Server
57

68

79
class ExactNode(Node):
8-
"""
9-
Inherits from the Node class, implements a more
10-
precise version of addition to fix discrepencies
11-
with floating point numbers.
10+
"""Exact numerical precision node.
11+
12+
This class inherits from the Node class, and
13+
implements a more precise version of addition to
14+
fix discrepencies with floating point numbers.
1215
"""
1316

1417
@property
15-
def now(self):
16-
"""
17-
Gets the current time
18-
"""
18+
def now(self) -> Decimal:
19+
"""Get the current time."""
1920
return Decimal(self.simulation.current_time)
2021

21-
def create_starting_servers(self):
22-
"""
23-
Initialise the servers
24-
"""
22+
def create_starting_servers(self) -> List[Server]:
23+
"""Initialise the servers at this node."""
2524
return [Server(self, i + 1, Decimal("0.0")) for i in range(self.c)]
2625

27-
def increment_time(self, original, increment):
28-
"""
29-
Increments the original time by the increment
30-
"""
26+
def increment_time(self, original, increment) -> Decimal:
27+
"""Increment the original time by the increment."""
3128
return Decimal(str(original)) + Decimal(str(increment))
3229

33-
def get_service_time(self, ind):
34-
"""
35-
Returns a service time for the given customer class
36-
"""
30+
def get_service_time(self, ind) -> Decimal:
31+
"""Return a service time for the given customer class."""
3732
return Decimal(
3833
str(
3934
self.simulation.service_times[self.id_number][
@@ -44,21 +39,25 @@ def get_service_time(self, ind):
4439

4540

4641
class ExactArrivalNode(ArrivalNode):
47-
"""
42+
"""Node with exact numerical time precision.
43+
4844
Inherits from the ArrivalNode class, implements a
4945
more precise version of addition to fix discrepencies
5046
with floating point numbers.
5147
"""
5248

53-
def increment_time(self, original, increment):
54-
"""
55-
Increments the original time by the increment
56-
"""
49+
def increment_time(self, original, increment) -> Decimal:
50+
"""Increment the original time by the increment."""
5751
return Decimal(str(original)) + Decimal(str(increment))
5852

59-
def inter_arrival(self, nd, clss):
60-
"""
61-
Samples the inter-arrival time for next class and node.
53+
def inter_arrival(self, nd, clss) -> Decimal:
54+
"""Samples the inter-arrival time for next class and node.
55+
56+
Parameters
57+
----------
58+
nd (Node): Next node.
59+
clss (Individual): Individual class to be selected next.
60+
6261
"""
6362
return Decimal(
6463
str(

ciw/exit_node.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
class ExitNode(object):
2-
"""
3-
Class for the exit node on our network.
4-
"""
1+
class ExitNode:
2+
"""Exit node on our network."""
53

64
def __init__(self):
7-
"""
8-
Initialise the exit node.
9-
"""
5+
"""Initialise the exit node."""
106
self.all_individuals = []
117
self.number_of_individuals = 0
128
self.number_of_completed_individuals = 0
@@ -25,6 +21,7 @@ def accept(self, next_individual, completed=True):
2521
Adds individual to the list of completed individuals.
2622
"""
2723
self.all_individuals.append(next_individual)
24+
next_individual.node = -1
2825
self.number_of_individuals += 1
2926
if completed:
3027
self.number_of_completed_individuals += 1

ciw/import_params.py

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import copy
2-
import types
31
import ciw.dists
42
from .network import *
53
from .schedules import *
@@ -75,10 +73,9 @@ def create_network_from_dictionary(params_input):
7573
params = fill_out_dictionary(params_input)
7674
validify_dictionary(params)
7775
# Then make the Network object
78-
number_of_classes = params["number_of_classes"]
7976
number_of_nodes = params["number_of_nodes"]
8077
if isinstance(params["priority_classes"], dict):
81-
preempt_priorities = [False for _ in range(number_of_nodes)]
78+
preempt_priorities = [False] * number_of_nodes
8279
if isinstance(params["priority_classes"], tuple):
8380
preempt_priorities = params["priority_classes"][1]
8481
params["priority_classes"] = {
@@ -87,16 +84,15 @@ def create_network_from_dictionary(params_input):
8784
}
8885
class_change_matrices = params.get(
8986
"class_change_matrices",
90-
[None for nd in range(number_of_nodes)],
87+
[None] * number_of_nodes,
9188
)
9289
class_change_time_distributions = {clss2: {clss1: None for clss1 in params['customer_class_names']} for clss2 in params['customer_class_names']}
9390
if 'class_change_time_distributions' in params:
9491
for clss1 in params['customer_class_names']:
95-
for clss2 in params['customer_class_names']:
96-
try:
97-
class_change_time_distributions[clss1][clss2] = params['class_change_time_distributions'][clss1][clss2]
98-
except:
99-
pass
92+
if clss1 in params['class_change_time_distributions']:
93+
for clss2 in params['customer_class_names']:
94+
if clss2 in params['class_change_time_distributions'][clss1]:
95+
class_change_time_distributions[clss1][clss2] = params['class_change_time_distributions'][clss1][clss2]
10096

10197
nodes, classes = [], {}
10298
for nd in range(number_of_nodes):
@@ -165,28 +161,27 @@ def fill_out_dictionary(params):
165161
class_names = sorted(params["arrival_distributions"].keys())
166162
params["customer_class_names"] = class_names
167163

164+
number_of_nodes = len(params["number_of_servers"])
168165
default_dict = {
169166
"name": "Simulation",
170167
"routing": {class_name: routing.TransitionMatrix(transition_matrix=[[0.0]]) for class_name in class_names},
171-
"number_of_nodes": len(params["number_of_servers"]),
168+
"number_of_nodes": number_of_nodes,
172169
"number_of_classes": len(class_names),
173-
"queue_capacities": [float("inf") for _ in range(len(params["number_of_servers"]))],
170+
"queue_capacities": [float("inf")] * number_of_nodes,
174171
"priority_classes": {class_name: 0 for class_name in class_names},
175-
"baulking_functions": {class_name: [None for _ in range(len(params["number_of_servers"]))]for class_name in class_names},
172+
"baulking_functions": {class_name: [None] * number_of_nodes for class_name in class_names},
176173
"batching_distributions": {class_name: [
177-
ciw.dists.Deterministic(1) for _ in range(len(params["number_of_servers"]))
178-
] for class_name in class_names},
179-
"ps_thresholds": [1 for _ in range(len(params["number_of_servers"]))],
174+
ciw.dists.Deterministic(1)
175+
] * number_of_nodes for class_name in class_names},
176+
"ps_thresholds": [1] * number_of_nodes,
180177
"server_priority_functions": [
181-
None for _ in range(len(params["number_of_servers"]))
182-
],
178+
None
179+
] * number_of_nodes,
183180
"reneging_time_distributions": {
184-
class_name: [None for _ in range(len(params["number_of_servers"]))]
181+
class_name: [None] * number_of_nodes
185182
for class_name in class_names
186183
},
187-
"service_disciplines": [
188-
ciw.disciplines.FIFO for _ in range(len(params["number_of_servers"]))
189-
],
184+
"service_disciplines": [ciw.disciplines.FIFO for _ in range(number_of_nodes)],
190185
"system_capacity": float('inf')
191186
}
192187

ciw/node.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,46 @@ def write_individual_record(self, individual):
871871
)
872872
individual.data_records.append(record)
873873

874+
def write_incomplete_record(self, individual):
875+
"""
876+
Write an incomplete data record for an individual
877+
at the end of the simulation run.
878+
"""
879+
if not individual.service_time: # Still in queue
880+
service_start_date = None
881+
waiting_time = None
882+
service_time = None
883+
service_end_date = None
884+
else:
885+
service_start_date = individual.service_start_date
886+
waiting_time = individual.service_start_date - individual.arrival_date
887+
if individual.service_end_date > self.now: # Still in service
888+
service_time = None
889+
service_end_date = None
890+
else: # Still blocked
891+
service_time = individual.service_time
892+
service_end_date = individual.service_end_date
893+
894+
record = DataRecord(
895+
id_number=individual.id_number,
896+
customer_class=individual.previous_class,
897+
original_customer_class=individual.original_class,
898+
node=self.id_number,
899+
arrival_date=individual.arrival_date,
900+
waiting_time=waiting_time,
901+
service_start_date=service_start_date,
902+
service_time=service_time,
903+
service_end_date=service_end_date,
904+
time_blocked=None,
905+
exit_date=None,
906+
destination=None,
907+
queue_size_at_arrival=individual.queue_size_at_arrival,
908+
queue_size_at_departure=None,
909+
server_id=False,
910+
record_type="incomplete",
911+
)
912+
return record
913+
874914
def write_interruption_record(self, individual, destination=nan):
875915
"""
876916
Write a data record for an individual when being interrupted.

0 commit comments

Comments
 (0)