Chromatography: An Inside Study

Introduction

This model studies the inner workings of a chromatography column that can be used to separate different chemicals, based on properties such as size and charge. As the analytes flow through the column, they do so with a variable flow rate. This notebook demonstrates how to use the Wolfram Language to discretize the partial differential equations of a chromatography column and translate the equations to Modelica.

Explore the available scenarios by evaluating each cell in order. Evaluation is done by selecting a cell and pressing Shift+Enter.

In order to get the full experience of this example, you need the following:

Wolfram System Modeler; a free trial download is available at www.wolfram.com/system-modeler

The model that came in the same bundle as this notebook

For more information about Wolfram System Modeler, visit www.wolfram.com/system-modeler.

Initialize

Define the model and component names:

reaction="Chromatography.Components.LactoseBreakdown";
sampleInjector="Chromatography.Components.PartialSampleInjector";
chromatographyColumn="Chromatography.Components.PartialChromatographyColumn";
spectrophotometer="Chromatography.Components.PartialSpectrophotometer";

Load the model:

Import[FileNameJoin[{NotebookDirectory[],"Chromatography.mo"}]];

The Model

Model Description

A chromatography column can be used to separate chemicals in order to purify them in large quantities. Separation on smaller analyte volumes is also performed to detect and quantify the constituent parts of a chemical solution.

The column can be divided into a liquid—or mobile—and a solid—or stationary—phase. The liquid phase contains the analytes that are to be separated. The analytes interact with the solid phase in the column, which retains the particles and influences their flow rate. This can be modeled as an equilibrium for the analytes, where they move from the liquid phase to being fixed inside the solid phase. This model assumes noncompetitive binding and linear equilibrium for all the analytes.

Sample Injector

The chemicals that will be separated in this model will be from the breakdown of lactose in enteric bacteria. This has been modeled using the free BioChem Modelica library:

SystemModel[reaction,{"Diagram",Frame-> False}]

Specify the name of the chemicals that are to be injected into the column:

analytes={"galactose","glucose","lactose"};
nrOfComponents= Length[analytes];

Each chemical will have a real-valued output that corresponds to the concentration of the chemical at any given moment:

outputs=Table[Symbol["y"<>ToString[i]],{i,1,nrOfComponents}];

A partial model has been created within the Modelica package that came together with this notebook. As it is not desirable to have a continuous stream of analyte going into the column, the partial model contains a pulse signal that specifies when and for how long to add analyte to the column. Inside this partial model, the reaction of interest can be inserted and connected to the outputs.

injector=CreateSystemModel["Injector",
MapIndexed[StringJoin["y",ToString[#2[[1]]]][t]==QuantityVariable["reaction."<>#1<>".c"][t]*QuantityVariable["switch.y"][t]&,analytes],t,
Join[{"reaction"∈reaction},Map[#∈"Modelica.Blocks.Interfaces.RealOutput"&,outputs]],
<|"ExtendsModels"-> sampleInjector|>]

Chromatography Column

The column can be spatially discretized into equally spaced parts using finite differences. The number of spatial parts and the length of the column in centimeters need to be specified:

columnLength=30;
Nz=10;
dz=columnLength/Nz;

With noncompetitive binding and linear equilibrium, each chemical component will have two states within each spatial part. The liquid state is the concentration of the compound that is free to flow and diffuse through the column and moves toward equilibrium with the solid state. The solid state is not free to flow or diffuse and is therefore fixed within each spatial part. The liquid states will be denoted L (Subscript[n, m]), where n is the compound number and m is the number of the spatial part. The solid state will similarly be denoted S (Subscript[n, m]).

The following equation describes the movement of the liquid states:

eqLiquid[i_,j_]:=
(Subscript[lComp, j])'[t]==
Di((Subscript[lComp, j+1])[t]-2(Subscript[lComp, j])[t]+(Subscript[lComp, j-1])[t])/dz^2-q((Subscript[lComp, j+1])[t]-(Subscript[lComp, j-1])[t])/(2 dz voidFraction columnArea)-(Subscript[k, i]) ((Subscript[KEq, i])(Subscript[lComp, j])[t]-(Subscript[sComp, j])[t]) VARatio(1-voidFraction) /voidFraction /.
{lComp->Symbol["L"<>ToString[i]],
sComp->Symbol["S"<>ToString[i]]}

Here, (Subscript[k, i]) is the rate coefficient for liquid-solid equilibrium for the (i^th) compound, while (Subscript[KEq, i]) is the equilibrium constant. (Subscript[D, i]) is the diffusivity of the column, q is the flow rate in ml/s, and VARatio is the volume-to-area ratio of the solid-phase particles.

Two boundary conditions also need to be specified for the liquid states:

eqLiquid[i_,1]:=
(Subscript[lComp, 1])'[t]==
Di((Subscript[lComp, 2])[t]-(Subscript[lComp, 1])[t])/dz^2-q((Subscript[lComp, 2])[t]+(Subscript[lComp, 1])[t]-2(Subscript[lComp, 0])[t])/(2 dz voidFraction columnArea)-(Subscript[k, i]) ((Subscript[KEq, i])(Subscript[lComp, 1])[t]-(Subscript[sComp, 1])[t]) VARatio(1-voidFraction) /voidFraction /.
{lComp->Symbol["L"<>ToString[i]],
sComp->Symbol["S"<>ToString[i]]}
eqLiquid[i_,Nz]:=
(Subscript[lComp, j])'[t]==
Di((Subscript[lComp, Nz-1])[t]-(Subscript[lComp, Nz])[t])/dz^2-q((Subscript[lComp, Nz])[t]-(Subscript[lComp, Nz-1])[t])/(dz voidFraction columnArea)-(Subscript[k, i]) ((Subscript[KEq, i])(Subscript[lComp, Nz])[t]-(Subscript[sComp, Nz])[t]) VARatio(1-voidFraction) /voidFraction /.
{lComp->Symbol["L"<>ToString[i]],
sComp->Symbol["S"<>ToString[i]]}

Equations are also needed to describe the states of the solid compounds:

eqSolid[i_,j_]:=(Subscript[sComp, j])'[t]==(Subscript[k, i])VARatio ((Subscript[KEq, i])(Subscript[lComp, j])[t]-(Subscript[sComp, j])[t]) /.
{lComp->Symbol["L"<>ToString[i]],
sComp->Symbol["S"<>ToString[i]]}

Each component will be connected to a real-valued input, coming from the sample injector, and an output at the far side of the column:

inputs=Table[Symbol["u"<>ToString[i]],{i,1,nrOfComponents}];
outputs=Table[Symbol["y"<>ToString[i]],{i,1,nrOfComponents}];

The liquid state of each component at the beginning of the column and at the end of the column will be equal to their inputs and outputs, respectively:

Table[(Subscript[Symbol["L"<>ToString[i]], 0])[t_]=inputs[[i]][t],{i,1,nrOfComponents}];
eqOutput =Table[Symbol["y"<>ToString[i]][t]==(Subscript[Symbol["L"<>ToString[i]], Nz])[t],{i,1,nrOfComponents}];

Together, these form the equations and initial conditions needed for the chromatography column model:

eqns=Join[Flatten[Table[{eqLiquid[i,j],eqSolid[i,j]},{i,1,nrOfComponents},{j,1,Nz}]],eqOutput];
init=Flatten[Table[{(Subscript[Symbol["L"<>ToString[i]], j])-> 0,(Subscript[Symbol["S"<>ToString[i]], j])-> 0},{i,1,nrOfComponents},{j,1,Nz}]];

All equations are inserted into a Modelica model using CreateSystemModel:

column=CreateSystemModel["Column",
eqns,t,
Join[Map[#∈"Modelica.Blocks.Interfaces.RealInput"&,inputs],Map[#∈"Modelica.Blocks.Interfaces.RealOutput"&,outputs]],
<|"ExtendsModels"-> chromatographyColumn,"InitialValues"->init|>]

Spectrophotometer

To analyze the outgoing stream of chemicals, one would typically use some instrument that detects specific properties of the chemicals. One such instrument is a spectrophotometer, which detects the refractive index of an analyte at given wavelengths. The amount of light reflection will depend on the path length b, the concentrations of the chemical compounds in the analyte, and the specific molar absorption of each compound (Subscript[ϵ, i]).

Using the outgoing compounds from the chromatography column as the basis for the concentration, a model can be constructed using CreateSystemModel:

photometer=CreateSystemModel["Photometer",
{y[t]/b==Plus@@(Table[inputs[[i]][t] (Subscript[ϵ, i]),{i,1,nrOfComponents}])},t,
Join[Map[#∈"Modelica.Blocks.Interfaces.RealInput"&,inputs],{y∈"Modelica.Blocks.Interfaces.RealOutput"}],
<|"ExtendsModels"->spectrophotometer|>]

Simulation

Model Construction

All components that are needed have now been constructed and can be connected using ConnectSystemModelComponents:

analysis=ConnectSystemModelComponents["Analysis",
{"sampleInjector"∈injector,
"chromatographyColumn"∈column,
"spectrophotometer"∈photometer},
Join[
Table["sampleInjector.y"<>ToString[i]-> "chromatographyColumn.u"<>ToString[i],{i,1,nrOfComponents}],
Table["chromatographyColumn.y"<>ToString[i]-> "spectrophotometer.u"<>ToString[i],{i,1,nrOfComponents}]]
];
analysis[{"Diagram",Frame-> False,ImageSize-> 570}]

Simulation

Before simulation, all parameters need to have their values specified:

sampleInjectorParameters={
"sampleInjector.startTime"->20,
"sampleInjector.injectionVolume"->0.01,
"sampleInjector.q"->0.04,
"sampleInjector.timeBetweenSamples"->23*60
};
columnParameters={
"chromatographyColumn.Di"->1.21 10^-5,
"chromatographyColumn.columnArea"->4 10^-1Pi,
"chromatographyColumn.VARatio"->80 ,
"chromatographyColumn.q"->0.04,
"chromatographyColumn.voidFraction"-> 0.4
};
equilibriumParameters={
"chromatographyColumn.KEq_1"-> 1.21,
"chromatographyColumn.KEq_2"-> 0.53,
"chromatographyColumn.KEq_3"-> 2.3,
"chromatographyColumn.k_1"-> 1.21 10^-3,
"chromatographyColumn.k_2"-> 1.01 10^-3,
"chromatographyColumn.k_3"-> 1.54 10^-3
};
spectrophotometerParameters={
"spectrophotometer.epsilon_1"->0.49 10^3,
"spectrophotometer.epsilon_2"->0.13 10^3,
"spectrophotometer.epsilon_3"->0.54 10^3,
"spectrophotometer.b"->0.01
};

With the SetSystemModel function, the model parameter values can be set using the Wolfram Language. Alternatively, parameter values could have been set as an option to the simulation:

SetSystemModel[analysis,
<|
"ParameterValues"->Join[
sampleInjectorParameters,
columnParameters,
equilibriumParameters,
spectrophotometerParameters] 
|>]

With everything in place, the model can be simulated:

simTime=60*60;
sim=SystemModelSimulate[analysis,{0,simTime}];

Analysis

Using the Wolfram Language, the discretized spatial points can be plotted in a dynamic graph where you can adjust the time with a slider. Also plotted is a graph that shows the simulated readings from the spectrophotometer, together with the different components that make up the chromatogram:

DynamicModule[{
simTime=simTime,
colResult=Map[Map[sim[#][Range[0,simTime,10]]&,#]&,Table["chromatographyColumn.L"<>ToString[i]<>"_"<>ToString[j],{i,1,nrOfComponents},{j,1,Nz}]],
specResult=Join[{Table[0,{i,0,simTime,10}]},Table[sim["spectrophotometer.u"<>ToString[i]][Range[0,simTime,10]],{i,1,nrOfComponents}]]
},
Manipulate[
Column[{
ListPlot[Table[{Range[0,columnLength-columnLength/Nz,columnLength/Nz],colResult[[j,All,t]]}\[Transpose],{j,1,nrOfComponents}],Joined->True,PlotRange->{All,{0,Max[colResult]}},ImageSize->Medium,
Frame->{{True,False},{True,False}},FrameLabel->{"Column length [cm]","Concentration [g/l]"},
PlotLegends->Placed[Capitalize[analytes],Below]],
ListPlot[Table[{Range[0,(1/60)*simTime,(1/60)*10],Sum[specResult[[j]],{j,i}]}\[Transpose],{i,1,nrOfComponents+1}],Joined->True,PlotRange->{All,{0,Max[Sum[specResult[[j]],{j,4}]]}},ImageSize->Medium,
Epilog->Line[{{(1/60)*t*10,0},{(1/60)*t*10,99}}],Frame->{{True,False},{True,False}},
PlotStyle->{None,None,None,{Black,Thin}},
Filling->{1->{2},2->{3},3->{4}},
FrameLabel->{"Time [min]","Relative Absorbance"}]}],
{t,1,simTime/10,1}],
SaveDefinitions->True]

As can be seen in the plot, the separation between the different components is not complete. Depending on the purpose for the separation, adjustments could be made to the length of the column or the flow rate to get a better separation. The analytes from the first injection have not completely been eluted before the first analytes from the second injection reach the end of the column. This causes additional impurities that could be eliminated by increasing the time between injections.

Terms and Conditions of Use

This domain example is an informational resource made freely available by Wolfram Research.

Use of This Example

• You may not use this example for any purpose that is unlawful or dangerous.
• You assume total responsibility and risk for your use of this example.
• You may not present this example with any alteration to its trade dress, trademarks, or design.

License

All content in this bundle is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. By accessing the content or using it in any way, you accept and agree to be bound by the terms of this license. If you do not agree to these Terms of Use, you may not use this content. Wolfram Research reserves the right to change, modify, add to, or remove portions of these Terms of Use at any time without notice. Please refer back to www.wolfram.com for the latest Terms of Use.

A summary of the licensing terms can be found at:
http://creativecommons.org/licenses/by-nc-sa/3.0

The full legal code can be found at: 
http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode