Hello. I believe I have identified some inconsistent behavior with the /automations/dosing/base.py execute_io_action() function (line 281). I wanted to bring this to your attention so that you are aware of the possible bug. I have included the code I am running and the log at the bottom of my post.
I wanted to test how the pioreactor handled a main media and an alternative media, so I wrote an automation that should exchange 1 mL main media and 1 mL alternative media every 2 minutes. Here is how the pioreactor performs this automation:
[add_media] add 0.5 mL of main media
[remove_media] remove 0.5 mL of media
[remove_media] remove 1.0 mL of media[add_media] add 0.5 mL of main media
[add_alt_media] add 0.5 mL of alt media
[remove_media] remove 1.0 mL of media
[remove_media] remove 2.0 mL of media[add_alt_media] add 0.5 mL of alt media
[remove_media] remove 0.5 mL of media
[remove_media] remove 1.0 mL of media
The issue is that the Pioreactor only checks to see if the volume of any single media added is above the maximum volume limit, but does not check whether the total volume of media added is also above that limit. If, for example, I added 5 different pumps/sources of media, the Pioreactor would not detect that I am adding 2.5 mL at a single time and am risking overflow. I have provided two examples below of what I think a more consistent dosing behavior would look like.
Currently, it looks like this:
Exchange 1 - [add_media = 0.5 mL, add_alt_media = 0 mL, remove_waste = 0.5 mL]
Exchange 2 - [add_media = 0.5 mL, add_alt_media = 0.5 mL, remove_waste = 1.0 mL]
Exchange 3 - [add_media = 0 mL, add_alt_media = 0.5 mL, remove_waste = 0.5 mL]
Solution 1:
Exchange 1 - [add_media = 0.5 mL, add_alt_media = 0.5 mL, remove_waste = 1.0 mL]
Exchange 2 - [add_media = 0.5 mL, add_alt_media = 0.5 mL, remove_waste = 1.0 mL]
Solution 2:
Exchange 1 - [add_media = 0.5 mL, add_alt_media = 0 mL, remove_waste = 0.5 mL]
Exchange 2 - [add_media = 0 mL, add_alt_media = 0.5 mL, remove_waste = 0.5 mL]
Exchange 3 - [add_media = 0.5 mL, add_alt_media = 0 mL, remove_waste = 0.5 mL]
Exchange 4 - [add_media = 0 mL, add_alt_media = 0.5 mL, remove_waste = 0.5 mL]
Here is the code I am running via ssh.
# -*- coding: utf-8 -*-
from __future__ import annotations
from pioreactor.automations import events
from pioreactor.automations.dosing.base import DosingAutomationJobContrib
from pioreactor.exc import CalibrationError
from pioreactor.utils import local_persistant_storage
class ChemostatAltMedia(DosingAutomationJobContrib):
"""
Chemostat mode - try to keep [nutrient] constant.
"""
automation_name = "chemostat_alt_media"
published_settings = {
"volume": {"datatype": "float", "settable": True, "unit": "mL"},
"fraction_alt_media": {"datatype": "float", "settable": True, "unit": "%"},
"duration": {"datatype": "float", "settable": True, "unit": "min"},
}
def __init__(self, volume: float, fraction_alt_media: float, **kwargs):
super(ChemostatAltMedia, self).__init__(**kwargs)
self.volume = float(volume)
self.fraction_alt_media = float(fraction_alt_media)
def execute(self) -> events.DilutionEvent:
alt_media_ml = self.fraction_alt_media * self.volume
media_ml = (1 - self.fraction_alt_media) * self.volume
self.execute_io_action(alt_media_ml=alt_media_ml, media_ml=media_ml, waste_ml=self.volume)
volume_actually_cycled = self.execute_io_action(alt_media_ml=alt_media_ml, media_ml=media_ml, waste_ml=self.volume)
return events.DilutionEvent(
f"exchanged {volume_actually_cycled[0]}mL",
data={"volume_actually_cycled": volume_actually_cycled[0]},
)
if __name__ == "__main__":
from pioreactor.background_jobs.dosing_control import DosingController
dc = DosingController(
"chemostat_alt_media",
duration=2,
fraction_alt_media=0.5,
volume=2.0,
unit="test_unit",
experiment="test_experiment"
)
dc.block_until_disconnected()
Here is a portion of the Log file.
2023-01-04T00:12:17+0000 [add_media] [app] INFO 0.5mL
2023-01-04T00:12:29+0000 [remove_waste] [app] INFO 0.5mL
2023-01-04T00:12:36+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:12:50+0000 [add_media] [app] INFO 0.5mL
2023-01-04T00:13:02+0000 [add_alt_media] [app] INFO 0.5mL
2023-01-04T00:13:10+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:13:23+0000 [remove_waste] [app] INFO 2.0mL
2023-01-04T00:13:48+0000 [add_alt_media] [app] INFO 0.5mL
2023-01-04T00:13:57+0000 [remove_waste] [app] INFO 0.5mL
2023-01-04T00:14:04+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:14:17+0000 [add_media] [app] INFO 0.5mL
2023-01-04T00:14:29+0000 [remove_waste] [app] INFO 0.5mL
2023-01-04T00:14:37+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:14:50+0000 [add_media] [app] INFO 0.5mL
2023-01-04T00:15:02+0000 [add_alt_media] [app] INFO 0.5mL
2023-01-04T00:15:10+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:15:24+0000 [remove_waste] [app] INFO 2.0mL
2023-01-04T00:15:48+0000 [add_alt_media] [app] INFO 0.5mL
2023-01-04T00:15:56+0000 [remove_waste] [app] INFO 0.5mL
2023-01-04T00:16:04+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:16:17+0000 [dosing_automation] [app] INFO DilutionEvent: exchanged 1.0mL
2023-01-04T00:18:17+0000 [add_media] [app] INFO 0.5mL
2023-01-04T00:18:29+0000 [remove_waste] [app] INFO 0.5mL
2023-01-04T00:18:36+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:18:49+0000 [add_media] [app] INFO 0.5mL
2023-01-04T00:19:02+0000 [add_alt_media] [app] INFO 0.5mL
2023-01-04T00:19:10+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:19:23+0000 [remove_waste] [app] INFO 2.0mL
2023-01-04T00:19:48+0000 [add_alt_media] [app] INFO 0.5mL
2023-01-04T00:19:56+0000 [remove_waste] [app] INFO 0.5mL
2023-01-04T00:20:03+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:20:16+0000 [add_media] [app] INFO 0.5mL
2023-01-04T00:20:29+0000 [remove_waste] [app] INFO 0.5mL
2023-01-04T00:20:36+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:20:49+0000 [add_media] [app] INFO 0.5mL
2023-01-04T00:21:01+0000 [add_alt_media] [app] INFO 0.5mL
2023-01-04T00:21:09+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:21:23+0000 [remove_waste] [app] INFO 2.0mL
2023-01-04T00:21:48+0000 [add_alt_media] [app] INFO 0.5mL
2023-01-04T00:21:56+0000 [remove_waste] [app] INFO 0.5mL
2023-01-04T00:22:04+0000 [remove_waste] [app] INFO 1.0mL
2023-01-04T00:22:17+0000 [dosing_automation] [app] INFO DilutionEvent: exchanged 1.0mL