Hello all,
I am trying to work with the Multi-Component Spreading Binding model. My end goal is to simulate a process with 3 components (A, B, and C) however I am having issues generating results. In order to trouble-fix, I am first looking at a process with just 2 components (A and B). My main issue is I keep getting negative concentrations at the outlet, so I decided to set site exchange rate constants to 0 to allow no exchange in the hope I could replicate the results from CODE 2 which is a working Multicomponent Bi-Langmuir model, however I still see this same negative concentration issue. This attempt is shown in CODE 1.
CODE 1 - attempt to replicate CODE 2 with spreading model:
# ============================================================
# Imports
# ============================================================
import numpy as np
import matplotlib.pyplot as plt
from CADETProcess.processModel import ComponentSystem
from CADETProcess.processModel import Spreading
from CADETProcess.processModel import (
Inlet, LumpedRateModelWithoutPores, Outlet
)
from CADETProcess.processModel import FlowSheet, Process
# ============================================================
# Feed concentrations (NOW 2 COMPONENTS)
# ============================================================
C_in = [
[7.5, 7.5],
[10.0, 10.0],
[15.0, 15.0]
]
c_store = []
q_store = []
# ============================================================
# Loop over datasets
# ============================================================
for i in range(len(C_in)):
# --------------------------------------------------------
# Component system
# --------------------------------------------------------
component_system = ComponentSystem()
component_system.add_component('A')
component_system.add_component('B')
#component_system.add_component('C')
# --------------------------------------------------------
# Spreading binding model (STATE SWITCHING)
# --------------------------------------------------------
binding_model = Spreading(component_system, n_binding_sites=2, name='spreading')
binding_model.is_kinetic = True
# IMPORTANT: multiple binding states required
#binding_model.n_binding_sites = 2
# adsorption/desorption (per component)
binding_model.adsorption_rate = [
0.015, 0.03, # state 1 (A, B, C)
0.02, 0.035 # state 2 (A, B, C)
]
binding_model.desorption_rate = [
1.0, 1.0,
1.0, 1.0
]
# capacity per state
binding_model.capacity = [
100.0, 100.0, # state 1 (A, B, C)
100.0, 100.0 # state 2 (A, B, C)
]
# --------------------------------------------------------
# STATE SWITCHING (KEY FEATURE)
# --------------------------------------------------------
# switching: state 1 -> state 2
binding_model.exchange_from_1_2 = [0.0, 0.0]
# switching: state 2 -> state 1
binding_model.exchange_from_2_1 = [0.0, 0.0]
print("Spreading model configured")
# --------------------------------------------------------
# Column system
# --------------------------------------------------------
feed = Inlet(component_system, name='feed')
feed.c = C_in[i]
eluent = Inlet(component_system, name='eluent')
eluent.c = [0, 0]
column = LumpedRateModelWithoutPores(component_system, name='column')
column.binding_model = binding_model
column.length = 0.6
column.diameter = 0.024
column.axial_dispersion = 4.7e-6
column.total_porosity = 0.65
column.solution_recorder.write_solution_bulk = True
column.solution_recorder.write_solution_solid = False
column.solution_recorder.split_components = True
column.solution_recorder.write_solution_bound = True
outlet = Outlet(component_system, name='outlet')
# --------------------------------------------------------
# Flow sheet
# --------------------------------------------------------
flow_sheet = FlowSheet(component_system)
flow_sheet.add_unit(feed, feed_inlet=True)
flow_sheet.add_unit(eluent, eluent_inlet=True)
flow_sheet.add_unit(column)
flow_sheet.add_unit(outlet, product_outlet=True)
flow_sheet.add_connection(feed, column)
flow_sheet.add_connection(eluent, column)
flow_sheet.add_connection(column, outlet)
process = Process(flow_sheet, 'batch elution (Spreading)')
# --------------------------------------------------------
# Events
# --------------------------------------------------------
Q = 60 / (60 * 1e6)
process.add_event('feed_on', 'flow_sheet.feed.flow_rate', Q)
process.add_event('feed_off', 'flow_sheet.feed.flow_rate', 0.0)
process.add_event('eluent_on', 'flow_sheet.eluent.flow_rate', Q)
process.add_event('eluent_off', 'flow_sheet.eluent.flow_rate', 0.0)
process.add_duration('feed_duration')
process.add_event_dependency('eluent_on', ['feed_off'])
process.add_event_dependency('eluent_off', ['feed_on'])
process.add_event_dependency('feed_off', ['feed_on','feed_duration'], [1,1])
process.cycle_time = 1000
process.feed_duration.time = 60
# --------------------------------------------------------
# Run CADET
# --------------------------------------------------------
print('here 1')
from CADETProcess.simulator import Cadet
print('here 2')
simulator = Cadet()
print('here 3')
simulation_results = simulator.simulate(process)
print('here 4')
# --------------------------------------------------------
# Extract results
# --------------------------------------------------------
print('here 5')
outlet_solution = simulation_results.solution.column.outlet
bound_solution = simulation_results.solution.column.bound
print('here 6')
t_full = outlet_solution.time
c = outlet_solution.solution
print('here 7')
c_store.append(c)
q_store.append(bound_solution.solution)
c_store = np.array(c_store)
print("DATA GENERATED (SPREADING MODEL)\n")
# ============================================================
# Plot outlet concentrations
# ============================================================
plt.figure(figsize=(8,6))
for i in range(len(C_in)):
c = c_store[i]
plt.plot(t_full, c[:,0], label=f"A dataset {i}")
plt.plot(t_full, c[:,1], '--', label=f"B dataset {i}")
#plt.plot(t_full, c[:,2], ':', label=f"C dataset {i}")
plt.xlabel("Time (s)")
plt.ylabel("Outlet concentration")
plt.title("Outlet Chromatograms (Spreading Model)")
plt.legend()
plt.grid()
plt.show()
# ============================================================
# Subplots per component
# ============================================================
fig, ax = plt.subplots(3, 2, figsize=(12,8), sharex=True)
labels = ['A', 'B']
for i in range(len(C_in)):
c = c_store[i]
for j in range(2):
ax[i,j].plot(t_full, c[:,j])
ax[i,j].set_title(f"Dataset {i} - {labels[j]}")
ax[i,j].grid()
for j in range(2):
ax[2,j].set_xlabel("Time (s)")
plt.suptitle("Outlet Chromatograms (Spreading Model)")
plt.tight_layout()
plt.show()
CODE 1 generates:
CODE 2:
import numpy as np
import matplotlib.pyplot as plt
# ============================================================
# CADET Simulation (Generate Synthetic Truth)
# ============================================================
from CADETProcess.processModel import ComponentSystem
from CADETProcess.processModel import BiLangmuir
from CADETProcess.processModel import (
Inlet, LumpedRateModelWithoutPores, Outlet
)
from CADETProcess.processModel import FlowSheet, Process
C_in = [[7.5, 7.5], [10, 10], [15, 15]]
c_store = []
q_store = []
for i in range(len(C_in)):
component_system = ComponentSystem()
component_system.add_component('A')
component_system.add_component('B')
# --------------------------------------------------------
# Bi-Langmuir binding model
# --------------------------------------------------------
binding_model = BiLangmuir(component_system, n_binding_sites=2, name='langmuir')
binding_model.is_kinetic = True
binding_model.adsorption_rate = [0.015, 0.03, 0.02, 0.035]
binding_model.desorption_rate = [1, 1, 1, 1]
binding_model.capacity = [100, 100, 100, 100]
#binding_model.bound_states = [2, 2]##
print('hi 1')
# --------------------------------------------------------
# Column system
# --------------------------------------------------------
feed = Inlet(component_system, name='feed')
feed.c = C_in[i]
eluent = Inlet(component_system, name='eluent')
eluent.c = [0, 0]
column = LumpedRateModelWithoutPores(component_system, name='column')
column.binding_model = binding_model
column.length = 0.6
column.diameter = 0.024
column.axial_dispersion = 4.7e-6
column.total_porosity = 0.65
column.solution_recorder.write_solution_bulk = True
column.solution_recorder.write_solution_solid = False
column.solution_recorder.split_components = True
column.solution_recorder.write_solution_bound = True # records bound states per site
#column.solution_recorder.split_components = False
#column.solution_recorder.write_solution_bound = True
outlet = Outlet(component_system, name='outlet')
flow_sheet = FlowSheet(component_system)
flow_sheet.add_unit(feed, feed_inlet=True)
flow_sheet.add_unit(eluent, eluent_inlet=True)
flow_sheet.add_unit(column)
flow_sheet.add_unit(outlet, product_outlet=True)
flow_sheet.add_connection(feed, column)
flow_sheet.add_connection(eluent, column)
flow_sheet.add_connection(column, outlet)
process = Process(flow_sheet, 'batch elution')
print('hi 2')
# --------------------------------------------------------
# Events
# --------------------------------------------------------
Q = 60 / (60 * 1e6)
process.add_event('feed_on', 'flow_sheet.feed.flow_rate', Q)
process.add_event('feed_off', 'flow_sheet.feed.flow_rate', 0.0)
process.add_event('eluent_on', 'flow_sheet.eluent.flow_rate', Q)
process.add_event('eluent_off', 'flow_sheet.eluent.flow_rate', 0.0)
process.add_duration('feed_duration')
process.add_event_dependency('eluent_on', ['feed_off'])
process.add_event_dependency('eluent_off', ['feed_on'])
process.add_event_dependency('feed_off', ['feed_on','feed_duration'], [1,1])
process.cycle_time = 1000
process.feed_duration.time = 60
print('hi 3')
# --------------------------------------------------------
# Run CADET
# --------------------------------------------------------
print(binding_model.n_binding_sites)
print(binding_model.n_bound_states)
if __name__ == "__main__":
from CADETProcess.simulator import Cadet
print('hi 4')
process_simulator = Cadet()
print('hi 5')
simulation_results = process_simulator.simulate(process)
print('hi 6')
# --------------------------------------------------------
# Extract outlet solution
# --------------------------------------------------------
outlet_solution = simulation_results.solution.column.outlet
solid_solution = simulation_results.solution.column.solid
bound_solution = simulation_results.solution.column.bound
q = bound_solution.solution
#q_store.append(q)
t_full = outlet_solution.time
c = outlet_solution.solution
c_store.append(c)
bound_solution = simulation_results.solution.column.bound
q_store.append(bound_solution.solution)
c_store = np.array(c_store)
#q_store = np.array(q_store)
print("DATA GENERATED\n")
# ============================================================
# Plot outlet concentration
# ============================================================
plt.figure(figsize=(8,6))
for i in range(len(C_in)):
c = c_store[i]
cA = c[:,0]
cB = c[:,1]
plt.plot(t_full, cA, label=f"A dataset {i}")
plt.plot(t_full, cB, '--', label=f"B dataset {i}")
plt.xlabel("Time (s)")
plt.ylabel("Outlet concentration")
plt.title("Outlet Chromatograms (Bi-Langmuir)")
plt.legend()
plt.grid()
plt.show()
# ============================================================
# Plot outlet concentration
# ============================================================
fig, ax = plt.subplots(3, 2, figsize=(10,8), sharex=True)
for i in range(len(C_in)):
c = c_store[i]
cA = c[:,0]
cB = c[:,1]
# A plots (left column)
ax[i,0].plot(t_full, cA)
ax[i,0].set_title(f"Dataset {i} - A")
ax[i,0].set_ylabel("Concentration")
ax[i,0].grid()
# B plots (right column)
ax[i,1].plot(t_full, cB)
ax[i,1].set_title(f"Dataset {i} - B")
ax[i,1].grid()
# x labels only on bottom row
ax[2,0].set_xlabel("Time (s)")
ax[2,1].set_xlabel("Time (s)")
plt.suptitle("Outlet Chromatograms (Bi-Langmuir)")
plt.tight_layout()
plt.show()
CODE 2 generates:
Am I setting something up wrong that may cause these negative concentrations?
Many thanks!

