Blood Distribution: arrivals from different generators "pairing off"

58 views
Skip to first unread message

Ralph Asher

unread,
Jan 12, 2024, 12:19:24 AMJan 12
to simmer-devel
I'm modeling the distribution of blood from a blood bank, to a hospital where it is used.

So far, I have two trajectories and two generators, one each for blood donation and for patients that need blood.

In the blood trajectory, blood "arrives" into the system via donations. Upon arrival into the system, unit of blood is given attributes of blood type, donation time, expiration time (42 days after donation), and Status (available, used, or expired).

It then takes about 6 days to be tested and transported to the hospital, represented by timeouts.

I then have a branch/timeout/rollback section of the blood trajectory that checks, every 6 hours, to see if the blood is past its expiration time. If so, then it leaves the system with probability 1. (I switch the status to Expired just before the blood leaves the system).

For the patient trajectory, the patient arrives into the system and is given attributes of blood type, arrival time, need-by time, and Status (waiting, received blood, or did not receive blood by need-by time). If they don't get blood by the need-by time, they exit the system via a branch/timeout/rollback section like with the blood trajectory, with a corresponding Status change.

So far, so good...

I'm struggling with how to have the model, for each patient arrival, check to see what blood is present in the simulation environment, "pair off" the patient with a unit of blood, then make both the patient and the blood leave the system (with the appropriate Status changes).

It gets even trickier when you consider blood types (a patient with AB+ blood can take blood of any type, but prefers AB+ blood; a patient with O- blood can only take O- blood, etc.). Further complications include a FIFO policy for blood use (want to use the oldest unexpired blood first) and prioritization of patients (generally, the one closest to its need-by time is priority).

I could probably make a gigantic dataframe of blood as resources and have each patient seize a resource, then never un-seize it. But I suspect that may be much slower, and harder to scale.

All help is appreciated!

Philemon Cyclone

unread,
Jan 12, 2024, 9:42:20 AMJan 12
to simmer-devel
I think the only way for one arrival to get information about other arrivals during a simulation is with get_mon_attributes(), which IIRC, returns a dataframe of all arrivals and their attributes, including when the attributes were set/modified. You could then possibly use a mix of local and global attributes to handle the patient-blood interaction (since one arrival cannot set a local attribute of another arrival) but this will probably be messy and slow. 

Another approach might be to leverage your gigantic dataframe idea, but instead of using it to dynamically add resources (which I don't know if is possible in simmer - I think they all need to be defined a priori), use it as a dynamic lookup table to keep track of available and consumed blood. The lookup table should be initialized outside the simulation, then you can use the super-assignment operator, <--, to update it from within the simulation. You could in principle use any simmer function that takes a function argument to do the updates, with simmer::log_() probably being the simplest, since you could use it simultaneously for debugging by printing out the result of your update. You would of course also need to write your own update functions, i.e., to 1) add new blood from the blood trajectory and 2) select the best available blood and flag it as "consumed" from the patient trajectory.

Ralph Asher

unread,
Jan 12, 2024, 10:07:47 AMJan 12
to simmer-devel
I'm making some progress with using signals. So far I'm able to have the patient, upon arrival, send a "Blood Needed" signal.  The blood trajectory has a `trap` function that looks for the signal and, when seen, changes the status of the blood to 'used', sends a "Blood Used" signal back to the patient, and exits the system.  The patient, having `trap`pped the "Blood Used" signal, then changes their status to 'received blood' and exits the system.

Problem so far is that the signal traps all the blood: ie if there are two units available, both trap the signal and are used by one patient (not the plan).  And this totally ignores blood type. But it's progress.

Philemon Cyclone

unread,
Jan 12, 2024, 11:26:54 AMJan 12
to simmer-devel
The signal use is an interesting approach, but doesn't it also run into the problem of missing blood units that have arrived before the patient has signaled? As for the blood type problem maybe using branch() in the handler trajectory may help?

Ralph Asher

unread,
Jan 12, 2024, 12:09:19 PMJan 12
to simmer-devel
No, when the patient signals, all blood units that are in the system respond.  (The blood trajectory ends with checking for expiration via a branch, exiting the system if expired, rollback otherwise... so any unexpired blood is "around" to get the signal.)

The issue I'm having with the bloodtype is that while the patient can send a signal with their bloodtype (e.g., signal is named "BloodNeeded_APOS"), I am getting error messages when I try to use get_attribute inside trap.


Code snippet, this is what the Patient sends as a signal:
send(signals= function() {
      paste0("BloodNeeded_Type_",
      get_attribute(env,'bloodtype') )


I can set up the trap function to look for all 8 different signals (for 8 differnt blood types), and reply to all 8 types. What I can't do is set up the trap function to only look for signals that match only the unit's blood's type. e.g., if I format it as :

trap(signals= function() {
      paste0("BloodNeeded_Type_",
      get_attribute(env,'bloodtype') ) ,

I get the "there is no arrival running" error. I assume that is an issue with trying to get the blood arrival's attribute inside a trap function?

Philemon Cyclone

unread,
Jan 12, 2024, 4:12:44 PMJan 12
to simmer-devel
The syntax for trap() looks ok, but I see a trailing comma after the last closing bracket in your code snippet - was that intentional or is there a typo? In my experience, the "no arrival running" errors are often because the function passed to one of the simmer function arguments is somehow misspecified, but in other cases I've had to resort to inelegant workarounds...

Ralph Asher

unread,
Jan 12, 2024, 5:41:09 PMJan 12
to simmer-devel
Yes looks like I needed to use function() for my signal. So far, I've been able to:

1) send a patient-specific signal that says "BloodNeeded_Type_1"  (since attributes are numeric, I have 1 through 8 as representing their bloodtype)
2) Each blood unit traps the signal " BloodNeeded_Type_1", then if the blood unit's bloodtype is 1, then consume the blood and send it out of the system. Send a message back that the blood is consumed, which the patient then traps and leaves the system.

When I run postprocessing on output data, only blood of Type 1 is used, and only patients with Type 1 blood, receive blood. So I think I can just replicate the same for the other blood types. It doesn't take into account that some people can take multiple blood types (but still prefer their own), but it's big progress!

Iñaki Ucar

unread,
Jan 15, 2024, 7:22:37 AMJan 15
to simmer...@googlegroups.com
I think that for this kind of application I would go just for the dataframe option, i.e. having a global database of blood available with all the attributes you need. The donation trajectory would:
  • Increase a global counter that would represent a unique identifier for this donation
  • Read that global counter and set it as a private attribute for later use. Also append the donation (attributes and this unique identifier) to the dataframe.
  • Timeout with some expiration time.
  • Delete the donation from the dataframe using the previous identifier.
Then, you could just write a function for the patients that take their preferences and look for blood in the dataframe, select the proper row, then decrease the amount of blood if they use any. This function would return just TRUE/FALSE, if the amount of blood requested was available. And then you can use that in a branch.

Iñaki

--
You received this message because you are subscribed to the Google Groups "simmer-devel" group.
To unsubscribe from this group and stop receiving emails from it, send an email to simmer-devel...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/simmer-devel/aed844bc-b562-4b3e-af44-eef2ed843f1en%40googlegroups.com.


--
Iñaki Úcar

Ralph Asher

unread,
Jan 15, 2024, 8:50:09 AMJan 15
to simmer-devel
Thanks Inaki. I ended up doing a slightly different approach. Outside simmer, I made a dataframe of all donations, including their time available to patients, expiration time, bloodtype, status (available, used, or expired), and using patient (initially NA). There was no blood trajectory.  In a set_attribute, the patient trajectory looked at this dataframe, filtered to just the blood units that were available, sorted by preference, then identified the most-preferred unit. The blood unit's status was then modified to "used", and the using patient's ID was put into the dataframe. The attribute was either set to -1 (no blood available) or 1 (a unit of blood available).

If set_attribute was 1, the patient trajectory then modified the number of units required by the patient (an attribute) down by 1. The patient exited the trajectory when either:
a) they've received all the units necessary, or
b) they ran out of time in the system (6 hours) without getting the blood they needed.

I liked this system because it was important for me to track status, usage, and results by individual blood unit. However, I may modify it to build up the blood dataframe via a blood trajetory as you suggested.  I wouldn't delete the blood unit from the dataframe at the end, though. I'd keep it in there for tracking.  Even typing this out I realized I can improve the logic flow even more.

Really appreciate both of your help!

Ralph
Reply all
Reply to author
Forward
0 new messages