Source code for CADETProcess.modelBuilder.serialColumnsBuilder

import copy
from typing import Optional

from CADETProcess.processModel import (
    ChromatographicColumnBase,
    FlowSheet,
    Inlet,
    Outlet,
    Process,
)


def _copy_column(
    column: ChromatographicColumnBase, own_binding_model: bool
) -> ChromatographicColumnBase:
    col = copy.copy(column)
    if own_binding_model:
        col._binding_model = copy.deepcopy(column.binding_model)
        col._binding_model.component_system = col.component_system
    return col


[docs] class SerialColumns(Process): """ Serial columns process. The flowsheet is configured with the following unit operations: - feed: Inlet - eluent_1: Inlet - eluent_2: Inlet - column_1: ChromatographicColumnBase - column_2: ChromatographicColumnBase - outlet_1: Outlet for column 1 - outlet_2: Outlet for column 2 --- Injection --- - `feed_on`: Sets `feed.flow_rate` to `flow_rate`. - `eluent_1_off`: Sets `eluent_1.flow_rate` to 0.0; triggered by `feed_on`. - `serial_on`: Connects column 1 to column 2. - `eluent_2_off`: Sets `eluent_2.flow_rate` to 0.0; triggered by `serial_on`. --- Elution --- - `feed_off`: Sets `feed.flow_rate` to 0.0; triggered by `feed_on` and `feed_duration`. - `eluent_1_on`: Sets `eluent_1.flow_rate` to `flow_rate`; triggered by `feed_off`. --- Serial Connection --- - `serial_off`: Disconnects column 1 from column 2 (sets `output_states.column_1` to 0). - `eluent_2_on`: Sets `eluent_2.flow_rate` to `flow_rate`; triggered by `serial_off`. """ def __init__( self, column: ChromatographicColumnBase, split_ratio: float, c_feed: list[float], flow_rate: float, feed_duration: float, t_serial_on: float, t_serial_off: float, cycle_time: float, c_eluent: Optional[list[float] | float] = 0.0, own_binding_model: bool = False, ) -> None: """ Initialize batch elution process. Parameters ---------- column : ChromatographicColumnBase Template chromatographic column object. Both columns are derived from this template. split_ratio : float Split ratio for column lengths (must be between 0 and 1). column_1.length = split_ratio * column.length column_2.length = (1 - split_ratio) * column.length c_feed : list[float] Feed concentration. flow_rate : float Flow rate. feed_duration : float Feed duration. t_serial_on: float Time at which to connect columns serially. t_serial_off: float Time at which to cut serial connection. cycle_time : float Cycle time. c_eluent : list[float] | float, optional Eluent concentration. The default is 0.0. own_binding_model : bool, optional If True, each column gets its own deep-copied binding model. If False (default), all columns share the same binding model instance. """ if not isinstance(column, ChromatographicColumnBase): raise TypeError("Expected ChromatographicColumnBase.") flow_sheet = self._build_flow_sheet( column, split_ratio, c_feed, c_eluent, own_binding_model, ) super().__init__(flow_sheet, "Serial Columns") self.cycle_time = cycle_time # Durations self.add_duration("feed_duration", feed_duration) # Injection self.add_event("feed_on", "flow_sheet.feed.flow_rate", flow_rate, time=0.0) self.add_event("eluent_1_off", "flow_sheet.eluent_1.flow_rate", 0.0) self.add_event_dependency("eluent_1_off", ["feed_on"]) # Serial Connection self.add_event( 'serial_on', 'flow_sheet.output_states.column_1', {"column_2": 1}, time=t_serial_on, ) self.add_event('eluent_2_off', 'flow_sheet.eluent_2.flow_rate', 0.0) self.add_event_dependency('eluent_2_off', ['serial_on'], [1]) # Elution self.add_event("feed_off", "flow_sheet.feed.flow_rate", 0.0) self.add_event_dependency("feed_off", ["feed_on", "feed_duration"], [1, 1]) self.add_event("eluent_1_on", "flow_sheet.eluent_1.flow_rate", flow_rate) self.add_event_dependency("eluent_1_on", ["feed_off"]) # Cut serial connection self.add_event( 'serial_off', 'flow_sheet.output_states.column_1', {"outlet_1": 1}, time=t_serial_off, ) self.add_event('eluent_2_on', 'flow_sheet.eluent_2.flow_rate', flow_rate) self.add_event_dependency('eluent_2_on', ['serial_off'], [1]) def _build_flow_sheet( self, column: ChromatographicColumnBase, split_ratio: float, c_feed: list[float], c_eluent: list[float] | float | None = 0.0, own_binding_model: bool = False, ) -> FlowSheet: """Build and return the flow sheet for batch elution process.""" component_system = column.component_system # Unit Operations feed = Inlet(component_system, name="feed") feed.c = c_feed eluent_1 = Inlet(component_system, name="eluent_1") eluent_1.c = c_eluent eluent_2 = Inlet(component_system, name="eluent_2") eluent_2.c = c_eluent column_1 = _copy_column(column, own_binding_model) column_1.name = "column_1" column_1.length = split_ratio * column.length column_2 = _copy_column(column, own_binding_model) column_2.name = "column_2" column_2.length = (1 - split_ratio) * column.length outlet_1 = Outlet(component_system, name="outlet_1") outlet_2 = Outlet(component_system, name="outlet_2") # Flow Sheet flow_sheet = FlowSheet(component_system) flow_sheet.add_unit(feed, feed_inlet=True) flow_sheet.add_unit(eluent_1, eluent_inlet=True) flow_sheet.add_unit(eluent_2, eluent_inlet=True) flow_sheet.add_unit(column_1) flow_sheet.add_unit(column_2) flow_sheet.add_unit(outlet_1, product_outlet=True) flow_sheet.add_unit(outlet_2, product_outlet=True) flow_sheet.add_connection(feed, column_1) flow_sheet.add_connection(eluent_1, column_1) flow_sheet.add_connection(column_1, outlet_1) flow_sheet.add_connection(column_1, column_2) flow_sheet.add_connection(eluent_2, column_2) flow_sheet.add_connection(column_2, outlet_2) return flow_sheet