From 60b80f698704a0480fb6e508c58a0afb3399acc6 Mon Sep 17 00:00:00 2001 From: Geraint Palmer Date: Wed, 4 Dec 2024 17:48:48 +0000 Subject: [PATCH] adds a new method for simulate_until_max_customers --- CHANGES.rst | 3 ++ ciw/node.py | 4 +-- ciw/simulation.py | 13 +++++++-- ciw/tests/test_simulation.py | 39 ++++++++++++++++++------- ciw/version.py | 2 +- docs/Guides/Simulation/sim_numcusts.rst | 19 +++++++++--- 6 files changed, 59 insertions(+), 21 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 076c16f..53c42be 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,9 @@ History ------- ++ **3.2.4 (2024-12-04)** + + 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). + + **3.2.3 (2024-10-15)** + Allow some numerical imprecision in the PMF probability sums. This allows for very large arrays of probabilities and use of Pandas and Numpy to define probabilities. diff --git a/ciw/node.py b/ciw/node.py index 84b6b4d..aaf5b21 100644 --- a/ciw/node.py +++ b/ciw/node.py @@ -86,7 +86,7 @@ def __repr__(self): """ return "Node %s" % self.id_number - def accept(self, next_individual): + def accept(self, next_individual, completed=False): """ Accepts a new customer to the queue: - remove previous exit date and blockage status @@ -654,7 +654,7 @@ def renege(self): self.write_reneging_record(reneging_individual) self.reset_individual_attributes(reneging_individual) self.simulation.statetracker.change_state_renege(self, next_node, reneging_individual, False) - next_node.accept(reneging_individual) + next_node.accept(reneging_individual, completed=False) self.release_blocked_individual() def get_reneging_date(self, ind): diff --git a/ciw/simulation.py b/ciw/simulation.py index 6a19d56..1f710d0 100644 --- a/ciw/simulation.py +++ b/ciw/simulation.py @@ -273,13 +273,18 @@ def simulate_until_max_time(self, max_simulation_time, progress_bar=False): self.progress_bar.close() def simulate_until_max_customers( - self, max_customers, progress_bar=False, method="Finish" + self, max_customers, progress_bar=False, method="Complete" ): """ Runs the simulation until max_customers is reached: + - Method: Complete + Simulates until max_customers has reached the Exit Node after + completing their journey - Method: Finish - Simulates until max_customers has reached the Exit Node + Simulates until max_customers has reached the Exit Node whether + they have completed their journey or not (included baulkers and + renegers) - Method: Arrive Simulates until max_customers have spawned at the Arrival Node - Method: Accept @@ -292,8 +297,10 @@ def simulate_until_max_customers( if progress_bar: self.progress_bar = tqdm.tqdm(total=max_customers) - if method == "Finish": + if method == "Complete": check = lambda: self.nodes[-1].number_of_completed_individuals + elif method == "Finish": + check = lambda: self.nodes[-1].number_of_individuals elif method == "Arrive": check = lambda: self.nodes[0].number_of_individuals elif method == "Accept": diff --git a/ciw/tests/test_simulation.py b/ciw/tests/test_simulation.py index 958384b..e721bba 100644 --- a/ciw/tests/test_simulation.py +++ b/ciw/tests/test_simulation.py @@ -194,7 +194,7 @@ def test_simulate_until_max_time_with_pbar_method(self): self.assertEqual(Q.progress_bar.total, 150) self.assertEqual(Q.progress_bar.n, 150) - def test_simulate_until_max_customers_finish(self): + def test_simulate_until_max_customers(self): N = ciw.create_network( arrival_distributions=[ciw.dists.Exponential(1.0)], service_distributions=[ciw.dists.Exponential(0.5)], @@ -202,21 +202,35 @@ def test_simulate_until_max_customers_finish(self): routing=[[0.0]], queue_capacities=[3], ) - # Test default method, 'Finish' + # Test default method, 'Complete' ciw.seed(2) - Q1 = ciw.Simulation(N) - Q1.simulate_until_max_customers(10, method="Finish") - self.assertEqual(Q1.nodes[-1].number_of_completed_individuals, 10) - completed_records = Q1.get_all_records(only=["service"]) + Q = ciw.Simulation(N) + Q.simulate_until_max_customers(10) + self.assertEqual(Q.nodes[-1].number_of_completed_individuals, 10) + self.assertEqual(Q.nodes[-1].number_of_individuals, 34) + completed_records = Q.get_all_records(only=["service"]) self.assertEqual(len(completed_records), 10) + # Test 'Complete' method + ciw.seed(2) + Q0 = ciw.Simulation(N) + Q0.simulate_until_max_customers(10, method="Complete") + self.assertEqual(Q0.nodes[-1].number_of_completed_individuals, 10) + self.assertEqual(Q0.nodes[-1].number_of_individuals, 34) + completed_records = Q0.get_all_records(only=["service"]) + self.assertEqual(len(completed_records), 10) + + next_active_node = Q0.find_next_active_node() + end_time_complete = next_active_node.next_event_date + # Test 'Finish' method ciw.seed(2) Q2 = ciw.Simulation(N) - Q2.simulate_until_max_customers(10) - self.assertEqual(Q2.nodes[-1].number_of_completed_individuals, 10) + Q2.simulate_until_max_customers(10, method="Finish") + self.assertEqual(Q2.nodes[-1].number_of_completed_individuals, 3) + self.assertEqual(Q2.nodes[-1].number_of_individuals, 10) completed_records = Q2.get_all_records(only=["service"]) - self.assertEqual(len(completed_records), 10) + self.assertEqual(len(completed_records), 3) next_active_node = Q2.find_next_active_node() end_time_finish = next_active_node.next_event_date @@ -248,8 +262,11 @@ def test_simulate_until_max_customers_finish(self): next_active_node = Q4.find_next_active_node() end_time_accept = next_active_node.next_event_date - # Assert that finish time of finish > accept > arrive - self.assertGreater(end_time_finish, end_time_accept) + # Assert that finish time of complete > accept > finish > arrive + self.assertGreater(end_time_complete, end_time_accept) + self.assertGreater(end_time_complete, end_time_finish) + self.assertGreater(end_time_complete, end_time_arrive) + self.assertGreater(end_time_accept, end_time_finish) self.assertGreater(end_time_accept, end_time_arrive) self.assertGreater(end_time_finish, end_time_arrive) diff --git a/ciw/version.py b/ciw/version.py index 3348d7f..79e4386 100644 --- a/ciw/version.py +++ b/ciw/version.py @@ -1 +1 @@ -__version__ = "3.2.3" +__version__ = "3.2.4" diff --git a/docs/Guides/Simulation/sim_numcusts.rst b/docs/Guides/Simulation/sim_numcusts.rst index dfff563..8edcdb1 100644 --- a/docs/Guides/Simulation/sim_numcusts.rst +++ b/docs/Guides/Simulation/sim_numcusts.rst @@ -9,11 +9,12 @@ This can be done using the :code:`simulate_until_max_customers` method. The method takes in a variable :code:`max_customers`. There are three methods of counting customers: - - :code:`'Finish'`: Simulates until :code:`max_customers` has reached the Exit Node. + - :code:`'Complete'`: Simulates until :code:`max_customers` has reached the Exit Node due to completing their journey through the system. + - :code:`'Finish'`: Simulates until :code:`max_customers` has reached the Exit Node, regardless if the customer reaches there without completing their journey, for example by :ref:`baulking ` or :ref:`reneging `. - :code:`'Arrive'`: Simulates until :code:`max_customers` have spawned at the Arrival Node. - :code:`'Accept'`: Simulates until :code:`max_customers` have been spawned and accepted (not rejected) at the Arrival Node. -The method of counting customers is specified with the optional keyword argument :code:`method`. The default value is is :code:`'Finish'`. +The method of counting customers is specified with the optional keyword argument :code:`method`. The default value is is :code:`'Complete'`. Consider an :ref:`M/M/1/3 ` queue:: @@ -25,15 +26,25 @@ Consider an :ref:`M/M/1/3 ` queue:: ... queue_capacities=[3] ... ) -To simulate until 30 customers have finished service:: + +To simulate until 30 customers have completed:: >>> ciw.seed(1) >>> Q = ciw.Simulation(N) - >>> Q.simulate_until_max_customers(30, method='Finish') + >>> Q.simulate_until_max_customers(30, method='Complete') >>> recs = Q.get_all_records() >>> len([r for r in recs if r.record_type=="service"]) 30 +To simulate until 30 customers have finished:: + + >>> ciw.seed(1) + >>> Q = ciw.Simulation(N) + >>> Q.simulate_until_max_customers(30, method='Finish') + >>> recs = Q.get_all_records() + >>> len(recs) + 30 + To simulate until 30 customers have arrived:: >>> ciw.seed(1)