We can now access our logger where we need it by calling self.logger.
We will place all of the logging steps in the .attend_clinic() method.
Let’s first add an arrival logging step!
b_simple_one_step_model_vidigi.py
def attend_clinic(self, patient):self.logger.log_arrival(entity_id=patient.id) # NEW
Because we already gave our logger access to the SimPy env, it handles recording the time the event happened! So the only parameter we need to give our logger is an identifier for this patient, which they already record.
Adding Logging Steps - Queuing
We now record a queuing step with .log_queue()
This time, we also need to decide on a name for our event. This can be anything we like, though it’s good practice to use snake_case (i.e. lowercase with underscores instead of spaces).
We’re going to start with the simplest way of logging that someone is being seen by the nurse - also treating it as a queue in vidigi’s language.
b_simple_one_step_model_vidigi.py
def attend_clinic(self, patient): ...withself.nurse.request() as req:yield req end_q_nurse =self.env.nowself.logger.log_queue(entity_id=patient.id, event="being_seen_by_nurse") # NEW patient.q_time_nurse = end_q_nurse - start_q_nurse sampled_nurse_act_time =self.nurse_consult_time_dist.sample()yieldself.env.timeout(sampled_nurse_act_time)self.logger.log_queue(entity_id=patient.id, event="nurse_treatment_ends") # NEW
But it’s not a queue?!
Later, we’ll look at vidigi’s more advanced ways of tracking resource use, but treating it as a ‘queue’ step for now allows us to get a simple animation up and running very quickly.
Adding Logging Steps - Departure
Finally, every patient needs one departure event.
b_simple_one_step_model_vidigi.py
def attend_clinic(self, patient): ...self.logger.log_departure(entity_id=patient.id) # NEW
It’s important that each patient only has one arrival and one departure event per model run.
Things get messy if they don’t!
(So if there’s any way they can ‘leave and come back’ in your model, think carefully about how you’ll handle it…)
All the logging steps
Let’s recap the changes we made.
b_simple_one_step_model_vidigi.py
def attend_clinic(self, patient):self.logger.log_arrival(entity_id=patient.id) # NEW start_q_nurse =self.env.nowself.logger.log_queue(entity_id=patient.id, event="nurse_wait_begins") # NEWwithself.nurse.request() as req:yield req end_q_nurse =self.env.nowself.logger.log_queue(entity_id=patient.id, event="being_seen_by_nurse") # NEW patient.q_time_nurse = end_q_nurse - start_q_nurse sampled_nurse_act_time =self.nurse_consult_time_dist.sample()yieldself.env.timeout(sampled_nurse_act_time)self.logger.log_queue(entity_id=patient.id, event="nurse_treatment_ends") # NEWself.logger.log_departure(entity_id=patient.id) # NEW
Accessing our event log
Once our model has run, our EventLogger is sitting on our model as my_model.logger.
We’ll want to look at the event log as a pandas dataframe, so we ask the logger to convert it for us with to_dataframe().
Vidigi’s to_dataframe() method will look at the EventLogger object, find where it stores the event logs, and convert it into this format for us.
Where does the animation code go?
Our models contain some key classes: Params, Patient, Model
(also Trial, but in this first example, we’re just working at the level of a single model - we’ll look at wiring Vidigi into Trial a bit later)
This time, we don’t need a new class. Vidigi’s animation function can be called directly, so our animation code goes at the bottom of our script, after our model has run.
We pass a list of all our EventPosition objects to the function create_event_position_df. Remember - a Python list uses [] with each item separated by a ,
layout = create_event_position_df( [ ... EventPosition( event="being_seen_by_nurse", x=200, y=150, label="Being Seen By Nurse", resource="num_nurses", # NEW ), EventPosition(event="depart", x=200, y=50, label="Exit"), ])
When we pass the resource name, this will be looked up from the params we pass to our animation (next slide), so we need to make sure the name exactly matches how it’s written in our Param class
We still don’t need to visualise the ‘nurse_treatment_ends’ step!
Vidigi also provides process map-style outputs. These may be familiar if you’ve played around with the BupaR package.
While these aren’t animated, they can provide a valuable way to check your model is working correctly, particularly for different subsets of patients with different pathways.
We can see an example below - this type of graph is called a Directly Follows Graph (DFG)
These become even more valuable as your systems become more complex.
Exercise: an animation is worth 10 thousand words
You will be working through a series of exercises in a custom web app.
Work through exercises 1 to 4
Each exercise may have multiple parts
Feel free to experiment! While there are some questions and suggested things to try out, it’s mostly a chance to get a bit more comfortable with vidigi’s code and what you can do with it, as well as getting more practice with interpreting simulations
Note that some of the parameters have changed compared to the notebook you worked on earlier…