prommis.nanofiltration.multi_component_diafiltration#

Multi-Component Diafiltration Unit Model#

Author: Molly Dougher

This membrane unit model is for the multi-component diafiltration of a multi-salt system with a common anion. Currently, the model and property packages support one, two, and three salt systems; however, the model can be extended to \(n\) salts by supplying the appropriate properties and arguments (see below). The membrane is designed for use in a diafiltration cascade, i.e., the model represents one spiral-wound membrane module piece within a cascade of several membranes.

Configuration Arguments#

The Multi-Component Diafiltration unit model requires a property package that provides the valency (\(z_i\)), infinite dilution diffusion coefficient (\(D_i\)) in \(\mathrm{mm}^2 \, \mathrm{h}^{-1}\), thermodynamic reflection coefficient (\(\sigma_i\)), partition coefficients (\(H_{i,r}\) and \(H_{i,p}\)) at the retentate-membrane and membrane-permeate interfaces, and number of dissolved species (\(n_i\)) for each ion \(i\) in solution. When used in a flowsheet, the user can provide separate property packages for the feed and product streams.

There are four required arguments:

  1. cation_list (list of cations present in the system)

    default=["Li", "Co"]

  2. anion_list (list of anions present in the system)

    default=["Cl"]

  3. NFE_module_length (the desired number of finite elements across the width of the membrane (i.e., the module length))

  4. NFE_membrane_thickness (the desired number of finite elements across the thickness of the membrane)

Degrees of Freedom#

The Multi-Component Diafiltration unit model has \(5+2n\) degrees of freedom, where \(n\) is the number of cations in the system:

  1. the length of the membrane module (total_module_length)

  2. the length of the membrane (total_membrane_length)

  3. the pressure applied to the membrane system (applied_pressure)

  4. the volumetric flow rate of the feed (feed_flow_volume)

  5. the cation concentration in the feed (feed_conc_mol_comp[t,k])

  6. the volumetric flow rate of the diafiltrate (diafiltrate_flow_volume)

  7. the cation concentration in the diafiltrate (diafiltrate_conc_mol_comp[t,k])

Model Structure#

There are three phases in the Multi-Component Diafiltration model: the retentate, the membrane, and the permeate. The retentate and the permeate are only discretized with respect to \(x\) (parallel to the membrane surface), while the membrane is discretized with respect to both \(x\) and \(z\) (perpendicular to the membrane surface). The resulting system of partial differential algebraic equations is solved by discretizing with the backward finite difference method.

Assumptions#

The membrane module dimensions, maximum applied pressure, and inlet flow rates assume that one tube (one instance of this model) consists of 4 NF270-440 membranes in series.

The partitioning relationships, which describe how the solutes transition (partition) across the solution-membrane interfaces, are derived assuming Donnan equilibrium. The partitioning coefficients incorporate both steric and Donnan effects.

The default value for the membrane’s surface charge (\(-44 \, \mathrm{mM}\)), was calculated using zeta potential measurements for NF270 membranes. (See this reference). Currently, the default property package only supports negatively charged membranes.

The membrane is assumed to be \(100 \, \mathrm{nm}\) thick.

The default value for the membrane permeability (\(0.01 \, \mathrm{m} \, \mathrm{h}^{-1} \, \mathrm{bar}^{-1}\)) is based off of parameter estimation results from this reference for NF270 membranes.

The formation of a boundary layer at the membrane surface due to concentration polarization is neglected for mathematical simplicity.

The dominating transport mechanism within the bulk/retentate solution is convection in the \(x\)-direction (parallel to the membrane surface). The dominating transport mechanism within the permeate solution is convection in the \(z\)-direction (perpendicular to the membrane surface).

The transport mechanisms modeled within the membrane are convection, diffusion, and electromigration. Diffusion within the membrane that is normal to the pore walls is ignored, meaning the concentration gradient of ion \(i\) within the membrane only has a \(z\)-component (perpendicular to the membrane surface).

Sets#

The Multi-Component Diafiltration model defines the following discrete sets for solutes and cations in the system, respectively:

\[\mathcal{I}=\{\mathrm{cation_1, cation_2, ..., cation_n, anion}\}\]
\[\mathcal{K}=\{\mathrm{cation_1, cation_2, ..., cation_n}\}\]

where \(n\) is the desired number of cations.

There are 2 continuous sets for each length dimension: dimensionless_module_length (in the \(x\)-direction parallel to the membrane surface) and dimensionless_membrane_thickness (in the \(z\)-direction perpendicular to the membrane surface). \(x\) and \(z\) are non-dimensionalized (denoted as \(\bar{x}\) and \(\bar{z}\), respectively) using the module length (\(w\)) and membrane thickness (\(l\)), respectively, to improve numerical stability.

\[\bar{x} \in \mathbb{R} \| 0 \leq \bar{x} \leq 1\]
\[\bar{z} \in \mathbb{R} \| 0 \leq \bar{z} \leq 1\]

Some variables have a time domain to be compatible with the property package, even though this is not a dynamic model. Thus, the following set is defined for time.

\[t \in [0]\]

Default Model Parameters#

The Multi-Component Diafiltration model has the following parameters.

Parameter

Description

Name

Default Value

Units

\(\epsilon\)

numerical tolerance for zero values

numerical_zero_tolerance

1e-10

\(l\)

thickness of the membrane

total_membrane_thickness

1e-07

\(\mathrm{m}\)

\(L_p\)

hydraulic permeability of the membrane

membrane_permeability

0.01

\(\mathrm{m} \, \mathrm{h}^{-1} \, \mathrm{bar}^{-1}\)

\(T\)

temperature of the system

temperature

298

\(\mathrm{K}\)

\(\chi\)

concentration of surface charge on the membrane

membrane_fixed_charge

-140

\(\mathrm{mol} \, \mathrm{m}^{-3}\)

Variables#

The Multi-Component Diafiltration model adds the following variables.

Variable

Description

Name

Units

Indexed over

\(c_{i,d}\)

ion concentration in the diafiltrate

diafiltrate_conc_mol_comp

\(\mathrm{mol} \, \mathrm{m}^{-3}\)

\(t\) and \(i \in \mathcal{I}\)

\(c_{i,f}\)

ion concentration in the feed

feed_conc_mol_comp

\(\mathrm{mol} \, \mathrm{m}^{-3}\)

\(t\) and \(i \in \mathcal{I}\)

\(c_{i,m}\)

ion concentration in the membrane

membrane_conc_mol_comp

\(\mathrm{mol} \, \mathrm{m}^{-3}\)

\(t\), \(\bar{x}\), \(\bar{z}\), amd \(i \in \mathcal{I}\)

\(c_{i,p}\)

ion concentration in the permeate

permeate_conc_mol_comp

\(\mathrm{mol} \, \mathrm{m}^{-3}\)

\(t\), \(\bar{x}\), amd \(i \in \mathcal{I}\)

\(c_{i,r}\)

ion concentration in the retentate

retentate_conc_mol_comp

\(\mathrm{mol} \, \mathrm{m}^{-3}\)

\(t\), \(\bar{x}\), amd \(i \in \mathcal{I}\)

\(\tilde{D}\)

diffusion & convection coefficient denominator in the membrane

membrane_D_tilde

\(\mathrm{mm}^2 \, \mathrm{h}^{-1} \, \mathrm{mol} \, \mathrm{m}^{-3}\)

\(t\), \(\bar{x}\), and \(\bar{z}\)

\(D_{kj}^{bilinear}\)

bilinear cross-diffusion coefficient in the membrane

membrane_cross_diffusion_coefficient_bilinear

\(\mathrm{mm}^4 \, \mathrm{h}^{-2} \, \mathrm{mol} \, \mathrm{m}^{-3}\)

\(t\), \(\bar{x}\), \(\bar{z}\), \(k \in \mathcal{K}\), and \(j \in \mathcal{K}\)

\(\alpha_k^{bilinear}\)

bilinear convection coefficient in the membrane

membrane_convection_coefficient_bilinear

\(\mathrm{mm}^2 \, \mathrm{h}^{-1} \, \mathrm{mol} \, \mathrm{m}^{-3}\)

\(t\), \(\bar{x}\), \(\bar{z}\), and \(k \in \mathcal{K}\)

\(D_{kj}\)

cross-diffusion coefficient in the membrane

membrane_cross_diffusion_coefficient

\(\mathrm{mm}^2 \, \mathrm{h}^{-1}\)

\(t\), \(\bar{x}\), \(\bar{z}\), \(k \in \mathcal{K}\), and \(j \in \mathcal{K}\)

\(\alpha_k\)

convection coefficient in the membrane

membrnane_convection_coefficient

\(\mathrm{dimensionless}\)

\(t\), \(\bar{x}\), \(\bar{z}\), and \(k \in \mathcal{K}\)

\(j_i\)

molar flux of ions across the membrane

molar_ion_flux

\(\mathrm{mol} \, \mathrm{m}^{-2} \, \mathrm{h}^{-1}\)

\(t\), \(\bar{x}\), amd \(i \in \mathcal{I}\)

\(J_w\)

water flux across the membrane

volume_flux_water

\(\mathrm{m}^3 \, \mathrm{m}^{-2} \, \mathrm{h}^{-1}\)

\(t\) and \(\bar{x}\)

\(L\)

length of the membrane

total_membrane_length

\(\mathrm{m}\)

\(\Delta \pi\)

osmotic pressure of feed-side fluid

osmotic_pressure

\(\mathrm{bar}\)

\(t\) and \(\bar{x}\)

\(\Delta P\)

applied pressure to the membrane

applied_pressure

\(\mathrm{bar}\)

\(t\)

\(q_d\)

volumetric flow rate of the diafiltrate

diafiltrate_flow_volume

\(\mathrm{m}^3 \, \mathrm{h}^{-1}\)

\(t\)

\(q_f\)

volumetric flow rate of the feed

feed_flow_volume

\(\mathrm{m}^3 \, \mathrm{h}^{-1}\)

\(t\)

\(q_p\)

volumetric flow rate of the permeate

permeate_flow_volume

\(\mathrm{m}^3 \, \mathrm{h}^{-1}\)

\(t\) and \(\bar{x}\)

\(q_r\)

volumetric flow rate of the retentate

retentate_flow_volume

\(\mathrm{m}^3 \, \mathrm{h}^{-1}\)

\(t\) and \(\bar{x}\)

\(w\)

length of the membrane module

total_module_length

\(\mathrm{m}\)

Derivative Variables#

The Multi-Component Diafiltration model adds the following derivative variables.

Variable

Description

Name

Units

Indexed over

\(\frac{\mathrm{d}c_{k,r}}{\mathrm{d}\bar{x}}\)

ion concentration gradient in the retentate

d_retentate_conc_mass_comp_dx

\(\mathrm{kg} \, \mathrm{m}^{-3}\)

\(t\), \(\bar{x}\), and \(k \in \mathcal{K}\)

\(\frac{\mathrm{d}q_r}{\mathrm{d}\bar{x}}\)

retentate flow rate gradient

d_retentate_flow_volume_dx

\(\mathrm{m}^3 \, \mathrm{h}^{-1}\)

\(t\) and \(\bar{x}\)

\(\frac{\partial c_{k,m}}{\partial \bar{z}}\)

ion concentration gradient in the membrane

d_membrane_conc_mass_comp_dz

\(\mathrm{kg} \, \mathrm{m}^{-3}\)

\(t\), \(\bar{x}\), \(\bar{z}\), and \(k \in \mathcal{K}\)

Constraints#

Differential mole balances:

\[\frac{\mathrm{d}q_r(\bar{x})}{\mathrm{d}\bar{x}} = - J_w(\bar{x}) wL \qquad \forall \, \bar{x} \in (0, 1]\]
\[q_r(\bar{x}) \frac{\mathrm{d}c_{k,r}(\bar{x})}{\mathrm{d}\bar{x}} = wL (J_w(\bar{x}) c_{k,r}(\bar{x}) - j_{k}(\bar{x})) \qquad \forall \, \bar{x} \in (0, 1], \, k \in \mathcal{K}\]

Bulk flux balances:

\[q_p(\bar{x}) = \bar{x} wL J_w(\bar{x}) \qquad \forall \, \bar{x} \in (0, 1]\]
\[j_{k}(\bar{x}) = c_{k,p}(\bar{x}) J_w(\bar{x}) \qquad \forall \, \bar{x} \in (0, 1], \, k \in \mathcal{K}\]

Overall water flux through the membrane:

\[J_w (\bar{x}) = L_p (\Delta P - \Delta \pi (\bar{x})) \qquad \forall \, \bar{x} \in (0, 1]\]
\[\Delta \pi (\bar{x}) = \mathrm{R} \mathrm{T} \sum_{i \in \mathcal{I}} n_i \sigma_i (c_{i,r}(\bar{x})-c_{i,p}(\bar{x})) \qquad \forall \, \bar{x} \in (0, 1]\]

Cation flux through the membrane:

Derived from the extended Nernst-Planck equation

\[j_k(\bar{x}) = \alpha_k(\bar{x},\bar{z}) c_{k,m}(\bar{x},\bar{z}) J_w(\bar{x}) + \frac{1}{l} \sum_{j \in \mathcal{K}} \left(D_{kj} (\hat{x},\hat{z}) \nabla c_{j,m} (\hat{x},\hat{z}) \right) \qquad \forall \, \bar{x} \in (0, 1], \, k \in \mathcal{K}\]

where

\[\alpha_k(\bar{x},\bar{z}) = 1 + \dfrac{z_k D_k \chi}{\tilde{D} (\hat{x},\hat{z})}\]
\[\begin{split}D_{kj}(\bar{x},\bar{z}) = \begin{cases} \dfrac{(z_k z_j D_k D_j - z_k z_j D_k D_a)c_{k,m} (\hat{x},\hat{z})}{\tilde{D} (\hat{x},\hat{z})},& \text{if } k \neq j \\ \dfrac{\sum_{t \in \mathcal{C}} \left((z_t z_a D_k D_a - \beta_{kt})c_{t,m} (\hat{x},\hat{z}) \right) + z_a D_k D_a \chi}{\tilde{D} (\hat{x},\hat{z})} ,& \text{if } k=j \\ \end{cases}\end{split}\]
\[\begin{split}\beta_{kt} = \begin{cases} z_t^2 D_t D_k ,& \text{if } k\neq t \\ z_t^2 D_t D_a ,& \text{if } k=t \\ \end{cases}\end{split}\]
\[\tilde{D} (\hat{x},\hat{z}) = \sum_{j \in \mathcal{K}} \left((z_j^2 D_j - z_j z_a D_a)c_{j,m} (\hat{x},\hat{z}) \right) - z_a D_a \chi\]
\[\nabla c_{k,m} (\hat{x},\hat{z})= \dfrac{\partial c_{k,m}(\hat{x},\hat{z})}{\partial \hat{z}}\]

where the subscript \(a\) represents the anion in solution.

The diffusion and convection coefficients are reformulated to bilinear constraints:

\[\alpha_k^{bilinear}(\bar{x},\bar{z}) = \alpha_k(\bar{x},\bar{z}) \tilde{D}(\bar{x},\bar{z}) = \tilde{D}(\bar{x},\bar{z}) + z_k D_k \chi\]
\[D_{kj}^{bilinear}(\bar{x},\bar{z}) = D_{kj}(\bar{x},\bar{z}) \tilde{D}(\bar{x},\bar{z})\]

Note that the single solute diffusion coefficients are provided in \(\mathrm{mm}^2\ \, \mathrm{h}^{-1}\) to improve numerical stability, but the diffusion coefficients in the Nernst-Planck equations must be converted to \(\mathrm{m}^2\ \, \mathrm{h}^{-1}\).

No applied potential on the system:

\[0 = \sum_{i \in \mathcal{I}} z_i j_i(\bar{x}) \qquad \forall \, \bar{x} \in (0, 1]\]

Electroneutrality:

\[0 = \sum_{i \in \mathcal{I}} z_i c_{i,r}(\bar{x})\]
\[0 = \chi + \sum_{i \in \mathcal{I}} z_i c_{i,m}(\bar{x},\bar{z}) \qquad \forall \, \bar{x} \in (0, 1]\]
\[0 = \sum_{i \in \mathcal{I}} z_i c_{i,p}(\bar{x}) \qquad \forall \, \bar{x} \in (0, 1]\]

Partitioning:

At the the retentate-membrane interface:

\[H_k^{-z_a} H_a^{z_k} = \left(\frac{c_{k,m} (\hat{x},\hat{z}=0)}{c_{k,r} (\hat{x})}\right)^{-z_a} \left(\frac{c_{a,m} (\hat{x},\hat{z}=0)}{c_{a,r}(\hat{x})}\right)^{z_k} \qquad \forall \, \bar{x} \in (0, 1], \, k \in \mathcal{K}\]

At the membrane-permeate interface:

\[H_k^{-z_a} H_a^{z_k} = \left(\frac{c_{k,m} (\hat{x},\hat{z}=1)}{c_{k,p} (\hat{x})}\right)^{-z_a} \left(\frac{c_{a,m} (\hat{x},\hat{z}=1)}{c_{a,p}(\hat{x})}\right)^{z_k} \qquad \forall \, \bar{x} \in (0, 1], \, k \in \mathcal{K}\]

Boundary conditions:

\[q_r(\bar{x}=0) = q_f + q_d\]
\[c_{k,r}(\bar{x}=0) = \frac{q_f c_{k,f} + q_d c_{k,d}}{q_f + q_d} \qquad \forall \, k \in \mathcal{K}\]
\[c_{k,m} (\bar{x}=0,\bar{z}) = 0 \qquad \forall \, \bar{z}, \, k \in \mathcal{K}\]

The following constraints (which are expected to be zero) are enforced to improve numerical stability (with the appropriate constraints deactivated as described above):

\[q_p(\bar{x}=0) = \epsilon\]
\[c_{i,p}(\bar{x}=0) = \epsilon \qquad \forall \, i \in \mathcal{I}\]
\[\frac{\mathrm{d}q_r(\bar{x})}{\mathrm{d}\bar{x}}(\bar{x}=0)=\epsilon\]
\[\frac{\mathrm{d}c_{k,r}(\bar{x})}{\mathrm{d}\bar{x}}(\bar{x}=0)=\epsilon \qquad \forall \, k \in \mathcal{K}\]
\[J_w(\bar{x}=0) = \epsilon\]
\[j_i(\bar{x}=0) = \epsilon \qquad \forall \, i \in \mathcal{I}\]
class prommis.nanofiltration.multi_component_diafiltration.MultiComponentDiafiltration(*args, **kwds)#
Parameters:
  • rule (function) – A rule function or None. Default rule calls build().

  • concrete (bool) – If True, make this a toplevel model. Default - False.

  • ctype (class) –

    Pyomo ctype of the block. Default - pyomo.environ.Block

    Config args

    dynamic

    Indicates whether this model will be dynamic or not, default = useDefault. Valid values: { useDefault - get flag from parent (default = False), True - set as a dynamic model, False - set as a steady-state model.}

    has_holdup

    Indicates whether holdup terms should be constructed or not. Must be True if dynamic = True, default - False. Valid values: { useDefault - get flag from parent (default = False), True - construct holdup terms, False - do not construct holdup terms}

    property_package

    Property parameter object used to define property calculations, default - useDefault. Valid values: { useDefault - use default package from parent model or flowsheet, PhysicalParameterObject - a PhysicalParameterBlock object.}

    property_package_args

    A ConfigBlock with arguments to be passed to a property block(s) and used when constructing these, default - None. Valid values: {see property package for documentation}

    cation_list

    List of cations present in the system

    anion_list

    List of anions present in the system

    NFE_module_length

    Number of discretization points in the x-direction (across module length)

    NFE_membrane_thickness

    Number of discretization points in the z-direction (across membrane thickness)

  • initialize (dict) – ProcessBlockData config for individual elements. Keys are BlockData indexes and values are dictionaries with config arguments as keys.

  • idx_map (function) – Function to take the index of a BlockData element and return the index in the initialize dict from which to read arguments. This can be provided to override the default behavior of matching the BlockData index exactly to the index in initialize.

Returns:

(MultiComponentDiafiltration) New instance

class prommis.nanofiltration.multi_component_diafiltration.MultiComponentDiafiltrationData(component)[source]#

Multi-Component Diafiltration Unit Model Class.

add_constraints()[source]#

Adds model constraints for the multi-component diafiltration unit model.

add_mutable_parameters()[source]#

Adds default parameters for the multi-component diafiltration unit model.

Values can be changed by the user during implementation.

Assumes membrane thickness of 100 nm.

Membrane permeability and fixed charged are estimated from: Liu, Xinhong, et al. (2025) https://doi.org/10.1021/acs.iecr.4c04763

add_scaling_factors()[source]#

Assigns scaling factors to certain variables and constraints to improve solver performance.

add_variables()[source]#

Adds variables for the multi-component diafiltration unit model.

Membrane module dimensions and maximum flowrate (17 m3/h) are estimated from NF270-440 modules.

Assumes 4 modules in series.

build()[source]#

Build method for the multi-component diafiltration unit model.

deactivate_unnecessary_objects()[source]#

Deactivates variables and constraints not needed in the multi-component diafiltration unit model.

default_initializer#

alias of MultiComponentDiafiltrationInitializer

class prommis.nanofiltration.multi_component_diafiltration.MultiComponentDiafiltrationInitializer(**kwargs)[source]#

Multi-Component Diafiltration Initializer Class.

constraint_tolerance

Tolerance for checking constraint convergence

output_level

Set output level for logging messages

block_solver

Solver to use for NxN blocks

block_solver_options

Dict of options to use to set solver.options.

block_solver_options
tol

Convergence tolerance for block solver

max_iter

Iteration limit for block solver

block_solver_writer_config

Dict of writer_config arguments to pass to block solver

block_solver_writer_config
linear_presolve

Whether to use linear presolver with block solver

scale_model

Whether to apply model scaling with block solver

block_solver_call_options

Dict of arguments to be passed as part of the solver.solve call, such as tee=True.

calculate_variable_options

Dict of options to pass to calc_var_kwds argument in solve_strongly_connected_components method.

skip_final_solve

Skip solving the block after block triangularization finishes.This solve may be necessary to decrease the scaled constraint residuals below the specified tolerance because, until Pyomo issue #3785 is addressed, solve_strongly_connected_components does not take scaling into account.

initialization_routine(model)[source]#

Initializes the retentate and permeate streams, membrane concentration, and un-initialized derivative variables.

Note: derivative variables are initialized to an arbitrary value.

Method then calls the block triangularization initializer method.