Chemostat + Alt Pump automation

Hello everyone,

I was wondering if anyone had already written the following dosing automation or plugin before we attempted to write it ourselves:

We want to run our pioreactors as chemostats using the media pump and in addition, run the alt_pump to add an inducer after each of the media exchanges in order to keep the concentration of the inducer constant. Note: (We cannot keep the inducer in the media being fed into the pioreactor).

Seems like something that should be scriptable but was hoping someone in the community had already done the work before having to write it ourselves.

Thank you very much,
Chris

:wave: hi @cwilsonPrime!

This is an interesting automation, and I couldn’t help but attempt it myself. Here’s my implementation of the automation.

It looks a lot like a chemostat, but there’s an additional parameter target_inducer_fraction. This is how much alt-media you want in the vial (which is proportional to your concentration of the inducer).

The idea is to perform the usual chemostat operation with media, and then calculate an additional amount of alt-media to dose to adjust the alt_media_fraction (which gets updated in real-time) to equal target_inducer_fraction

# -*- coding: utf-8 -*-
from __future__ import annotations

from pioreactor.automations import events
from pioreactor.automations.dosing.base import DosingAutomationJob
from pioreactor.exc import CalibrationError
from pioreactor.utils import local_persistant_storage


class ChemostatWithInducer(DosingAutomationJob):

    automation_name = "chemostat_with_inducer"
    published_settings = {
        "volume": {"datatype": "float", "settable": True, "unit": "mL"},
        "target_inducer_fraction": {"datatype": "float", "settable": True, "unit": "%"},
    }

    def __init__(self, volume: float | str, target_inducer_fraction: float | str, **kwargs) -> None:
        super().__init__(**kwargs)

        with local_persistant_storage("current_pump_calibration") as cache:
            if "media" not in cache:
                raise CalibrationError("Media pump calibration must be performed first.")
            elif "waste" not in cache:
                raise CalibrationError("Waste pump calibration must be performed first.")
            elif "alt_media" not in cache:
                raise CalibrationError("Alt-media pump calibration must be performed first.")

        self.volume = float(volume)
        self.target_inducer_fraction = float(target_inducer_fraction)

    def execute(self) -> events.DilutionEvent:
        media_exchanged = self.execute_io_action(media_ml=self.volume, waste_ml=self.volume)

        # after this occurs, our alt_media_fraction has been reduced. We need to get it back up to target_inducer_fraction.
        # the math of this:
        # target_inducer_fraction = (alt_media_fraction * vial_volume + delta_alt_media) / (vial_volume + delta_alt_media)
        # solving for delta_alt_media:
        # delta_alt_media = vial_volume * (target_inducer_fraction - alt_media_fraction) / (1 - target_inducer_fraction)
        delta_alt_media = max(self.vial_volume * (self.target_inducer_fraction - self.alt_media_fraction) / (1 - self.target_inducer_fraction), 0)

        # now add that much alt media
        alt_media_exchanged = self.execute_io_action(alt_media_ml=delta_alt_media, waste_ml=delta_alt_media)

        # I _think_ there is a case where if alt_media_exchanged > 0.75, this might fail, however, it would correct on the next run.
        # assert abs(self.alt_media_fraction - self.target_inducer_fraction) < 0.01 # less than 1% error

        return events.DilutionEvent(
            f"exchanged {media_exchanged['media_ml']:.2f}ml of media and {alt_media_exchanged['alt_media_ml']:.2f}mL of alt media",
        )

You can try this out locally. Copy and paste this code into a file in ~/.pioreactor/plugins/chemostat_with_inducer.py,

then run (this won’t activate any pumps, since we are setting TESTING=1).

GLOBAL_CONFIG=~/.pioreactor/config.ini TESTING=1 pio run dosing_control --automation-name chemostat_with_inducer --volume 0.5 --duration 0.75 --target_inducer_fraction 0.05

You may need to install sudo pip install fake-rpi, too…
In a separate SSH shell, you can watch the updates with:

pio mqtt -t pioreactor/+/+/dosing_automation/alt_media_fraction

Thanks so much, CamDavidsonPilon, this helps a bunch. I’ll test it and let you know how it goes.