def execute(self, *args, **kwargs) -> events.Event: if self.latest_od <= self.min_od: return events.NoEvent(f"latest OD less than OD to start diluting, {self.min_od:.2f}") else: fraction_of_alt_media_to_add = self.pid.update( self.latest_growth_rate, dt=self.duration ) # duration is measured in minutes, not seconds (as simple_pid would want) # dilute more if our OD keeps creeping up - we want to stay in the linear range. if self.latest_od > self.max_od: publish( f"morbidostat/{self.unit}/{self.experiment}/log", f"[{JOB_NAME}]: executing double dilution since we are above max OD, {self.max_od:.2f}.", verbose=self.verbose, ) volume = 2 * self.volume else: volume = self.volume alt_media_ml = fraction_of_alt_media_to_add * volume media_ml = (1 - fraction_of_alt_media_to_add) * volume self.execute_io_action(alt_media_ml=alt_media_ml, media_ml=media_ml, waste_ml=volume) event = events.AltMediaEvent( f"PID output={fraction_of_alt_media_to_add:.2f}, alt_media_ml={alt_media_ml:.2f}mL, media_ml={media_ml:.2f}mL" ) event.media_ml = media_ml # can be used for testing later event.alt_media_ml = alt_media_ml return event
def execute(self, *args, **kwargs) -> events.Event: if self.latest_od <= self.min_od: return events.NoEvent(f"current OD, {self.latest_od:.2f}, less than OD to start diluting, {self.min_od:.2f}") else: output = self.pid.update(self.latest_od, dt=self.duration) volume_to_cycle = output * self.volume if volume_to_cycle < 0.01: return events.NoEvent(f"PID output={output:.2f}, so practically no volume to cycle") else: self.execute_io_action(media_ml=volume_to_cycle, waste_ml=volume_to_cycle) e = events.DilutionEvent(f"PID output={output:.2f}, volume to cycle={volume_to_cycle:.2f}mL") e.volume_to_cycle = volume_to_cycle e.pid_output = output return e
def run(self, counter=None): if (self.latest_growth_rate is None) or (self.latest_od is None): time.sleep(10) # wait some time for data to arrive, and try again. return self.run(counter=counter) if self.state != self.READY: event = events.NoEvent(f"currently in state {self.state}") elif (time.time() - self.most_stale_time) > 5 * 60: event = events.NoEvent( "readings are too stale (over 5 minutes old) - are `Optical density job` and `Growth rate job` running?" ) else: event = self.execute(counter) publish(f"morbidostat/{self.unit}/{self.experiment}/log", f"[{JOB_NAME}]: triggered {event}.", verbose=self.verbose) return event
def execute(self, *args, **kwargs) -> events.Event: """ morbidostat mode - keep cell density below and threshold using chemical means. The conc. of the chemical is diluted slowly over time, allowing the microbes to recover. """ if self.previous_od is None: return events.NoEvent("skip first event to wait for OD readings.") elif self.latest_od >= self.target_od and self.latest_od >= self.previous_od: # if we are above the threshold, and growth rate is greater than dilution rate # the second condition is an approximation of this. self.execute_io_action(alt_media_ml=self.volume, waste_ml=self.volume) return events.AltMediaEvent( f"latest OD, {self.latest_od:.2f} >= Target OD, {self.target_od:.2f} and Latest OD, {self.latest_od:.2f} >= Previous OD, {self.previous_od:.2f}" ) else: self.execute_io_action(media_ml=self.volume, waste_ml=self.volume) return events.DilutionEvent( f"latest OD, {self.latest_od:.2f} < Target OD, {self.target_od:.2f} or Latest OD, {self.latest_od:.2f} < Previous OD, {self.previous_od:.2f}" )
def execute(self, *args, **kwargs) -> events.Event: if self.latest_od >= self.target_od: self.execute_io_action(media_ml=self.volume, waste_ml=self.volume) return events.DilutionEvent(f"latest OD={self.latest_od:.2f}V >= target OD={self.target_od:.2f}V") else: return events.NoEvent(f"latest OD={self.latest_od:.2f}V < target OD={self.target_od:.2f}V")
def execute(self, *args, **kwargs) -> events.Event: return events.NoEvent("never execute IO events in Silent mode")