8.7.3. Soluble surfactants and surfactant isotherms

Soluble surfactants are allowed to move from the liquid bulk phase to the interface and vice versa. Hence, in order for a surfactants to be soluble, we must have it in the liquid phase as well as surfactant concentration at the interface. In fact, the SurfactantProperties class we have used so far to define insoluble surfactants inherits from the PureLiquidProperties class, i.e. each surfactant is automatically also a pure liquid and can hence be mixed with other liquids. However, before doing so, we must at least set the molar_mass so that the mole fractions in the liquid mixture can be calculated. This is e.g. relevant for Raoult’s law for the evaporation (cf. (8.4)).

# Register an soluble surfactant
@MaterialProperties.register()
class MySolubleSurfactant(SurfactantProperties):  # It is automatically also a pure liquid
    name = "my_soluble_surfactant"

    def __init__(self):
        super(MySolubleSurfactant, self).__init__()
        self.molar_mass = 100 * gram / mol  # required so that we can mix it with other liquids
        self.surface_diffusivity = 0.5e-9 * meter ** 2 / second  # default surface diffusivity

Since the surfactant is now also in the liquid phase, we must define the properties of the bulk liquid mixture we want to use. In particular, the presence of the surfactant could influence the dynamic_viscosity or mass_density. However, for low concentrations, it is reasonable to disregard this effect and just copy the values of e.g. pure water:

# Define how the liquid mixture should behave in the bulk
@MaterialProperties.register()
class MixLiquidWaterMySolubleSurfactant(MixtureLiquidProperties):
    components = {"water", "my_soluble_surfactant"}

    def __init__(self, pure_props):
        super(MixLiquidWaterMySolubleSurfactant, self).__init__(pure_props)
        # Copy the relevant properties from the water. We assume that the surfactant concentration is small
        # so that all properties are close to these of water
        self.mass_density = self.pure_properties["water"].mass_density
        self.dynamic_viscosity = self.pure_properties["water"].dynamic_viscosity
        self.default_surface_tension["gas"] = self.pure_properties["water"].default_surface_tension["gas"]
        # However, we must set a diffusivity
        self.set_diffusion_coefficient(1e-9 * meter ** 2 / second)

However, we must specify the diffusivity in the bulk. This may be different from the diffusivity at the interface.

Of course, also the properties of the interface are relevant, i.e. how the surfactant influences the surface tension. For soluble surfactants, there is another relevant property to set, namely how the surfactant moves between the bulk and the interface. Therefore, the surfactant transport equation (8.11) is augmented by a sink/source term \(S_\Gamma\):

(8.13)\[\partial_t \Gamma+\nabla_S\cdot\left(\vec{u}_\text{P}\Gamma\right)=\nabla_S\cdot\left(D_S\nabla_S \Gamma\right)+S_\Gamma\]

\(S_\Gamma\) is now the flux (in \(\:\mathrm{mol}/\mathrm{m^2} \cdot \mathrm{s}\)) from the bulk to the interface. This flux is constituted by adsorption of surfactants to the interface (positive contribution to \(S_\Gamma\)) and desorption from the interface to the bulk (negative contribution to \(S_\Gamma\)). Of course, this transfer has to be compensated by the bulk in order to conserve the total mass of the surfactants, i.e. the sum in the liquid bulk and the interface. The molar flux \(S_\Gamma\) can be converted to a mass flux by multiplying it with the molar mass \(M\) of the surfactant and this flux can be applied as Neumann condition, i.e. as diffusive mass flux, for the corresponding compositional advection-diffusion equation (8.1). It does not contribute to the mass transfer flux rate \(j_\alpha\), though, since the surfactant does not cross the interface. Of course, all this is subject to a few assumptions, since a molecule requires volume in the bulk phase, but will occupy zero volume at the interface. The flux \(S_\Gamma\) is automatically considered in the MultiComponentNavierStokesInterface, so there is nothing to be done.

For the adsorption/desorption rates, there are plenty of models in the literature. To that end, pyoomph offers the most common surfactant isotherms in the module pyoomph.materials.surfactant_isotherms. The isotherms are usually expressed in terms of the surface concentration \(\Gamma\) and the molar concentration \(C\) in the bulk, where the latter can be calculated from the bulk mass fraction \(w\) via \(C=\rho w/M\). The molar concentrations can be accessed in pyoomph via the prefix "molarconc_", e.g. var("molarconc_my_soluble_surfactant"). All surfactant isoterms contain expressions for the adsorption flux \(S_\Gamma^\text{ads}\), \(S_\Gamma^\text{des}\) and the surface pressure \(\Pi\), where the latter is just the decrease of the surface tension due to the presence of the surfactant, i.e. \(\sigma=\sigma_0-\Pi\). The total flux is just \(S_\Gamma=S_\Gamma^\text{ads}-S_\Gamma^\text{des}\). The equilibrium relation between \(C\) and \(\Gamma\), where the surfactants in the bulk and the interface are at equilibrium, is given by \(S_\Gamma=0\), i.e. \(S_\Gamma^\text{ads}=S_\Gamma^\text{des}\). These are listed for all predefined isotherms in Table 8.1.

Table 8.1 Predefined surfactant isotherms stating the adsorption and desorption rates and the surface pressure.

isotherm

\(S_\Gamma^\text{ads}\)

\(S_\Gamma^\text{des}\)

\(\Pi\)

HenryIsotherm

\(k_\text{ads}C\)

\(k_\text{des}\Gamma\)

\(RT\Gamma\)

LangmuirIsotherm

\(k_\text{ads}C\frac{\Gamma_\infty-\Gamma}{\Gamma_\infty}\)

\(k_\text{des}\Gamma\)

\(-RT\Gamma_\infty\ln\left(1-\frac{\Gamma}{\Gamma_\infty}\right)\)

VolmerIsotherm

\(k_\text{ads}C\frac{\Gamma_\infty-\Gamma}{\Gamma_\infty}\)

\(k_\text{des}\Gamma\exp\left(\frac{\Gamma}{\Gamma_\infty-\Gamma}\right)\)

\(\frac{RT\Gamma_\infty}{1-\Gamma\Gamma_\infty}\)

FrumkinIsotherm

\(k_\text{ads}C\frac{\Gamma_\infty-\Gamma}{\Gamma_\infty}\)

\(k_\text{des}\Gamma\exp\left(-\frac{\beta\Gamma}{RT}\right)\)

\(-RT \Gamma_\infty \ln(1 - \frac{\Gamma}{\Gamma_\infty})\)

VanDerWaalsIsotherm

\(k_\text{ads}C\frac{\Gamma_\infty - \Gamma}{\Gamma_\infty}\)

\(k_\text{des}\Gamma\exp\left(\frac{\Gamma}{\Gamma_\infty-\Gamma} -\frac{\beta\Gamma}{RT}\right)\)

\(\frac{RT\Gamma}{1 - \Gamma/\Gamma_\infty}-\frac{\beta\Gamma^2}{2}\)

To constuct an isotherm, we just have to pass the surfactant name and the parameters k_ads and k_des, as well as potential further parameters GammaInfty and beta to the constructor. Sometimes in the literature, you will find a value \(K\), which is just \(K=k_\text{ads}/k_\text{des}\). Moreover, some literature define \(k_\text{ads}\) as product of \(k_\text{ads}\Gamma_\infty\). Here, the convention was chosen that \(k_\text{ads}\) always has the units \(\:\mathrm{m}/\mathrm{s}\), whereas \(k_\text{des}\) has always the unit \(1/\:\mathrm{s}\). If required for the isotherm, the infinity concentration \(\Gamma_\infty\) has the unit \(\:\mathrm{mol}/\mathrm{m^2}\) and the interaction parameter \(\beta\) is associated with the units \(\:\mathrm{m^4}/(\mathrm{mol^2} \cdot \mathrm{s^2})\). Hence, when using values from the literature, always make sure that you cast the isotherms and parameters accordingly.

The typical time scale of the surfactant equilibration is given by both \(k_\text{ads}\) and \(k_\text{des}\), whereas the ratio of these and the further parameters control the equilibrium and the surface tension reduction.

To use the isotherms on an interface, we just construct it and apply the its method apply_on_interface(). This will set the surface_tension of this liquid-gas interface to the passed pure_surface_tension minus the surface pressure \(\Pi\). Furthermore, it will set the transfer rate \(S_\Gamma\) according to the particular isotherm. \(S_\Gamma\) can alternatively be set by hand with the surfactant_adsorption_rate dict:

@MaterialProperties.register()
class InterfaceWaterMySolubleSurfactantVSGas(DefaultLiquidGasInterface):
    liquid_components = {"water", "my_soluble_surfactant"}  # Water and the surfactant are in the liquid phase
    # gas_components = {"air","water"} # do not specify any particular gas phase here: Hold for all gas mixtures
    surfactants = {"my_soluble_surfactant"}  # The soluble surfactant may also be on the interface

    def __init__(self, phaseA, phaseB, surfactants):
        super(InterfaceWaterMySolubleSurfactantVSGas, self).__init__(phaseA, phaseB, surfactants)
        # Create a LangmuirIsotherm for my_soluble_surfactant
        isotherm = LangmuirIsotherm("my_soluble_surfactant", k_ads=5e-6 * meter / second, k_des=9.5 / second,
                                    GammaInfty=5 * micro * mol / meter ** 2)
        # And apply it to this interface. This will modify self.surface_tension by substracting the surface pressure
        # and furthermore it will set self.surfactant_adsorption_rate["my_soluble_surfactant"] to the total ad-/desorption flux
        isotherm.apply_on_interface(self, pure_surface_tension=self.surface_tension,min_surface_tension=20*milli*newton/meter)

Since some isotherms have an unbounded surface pressure, the surface tension might become negative once the surfactant concentration exceeds the validity range of the isotherm. Therefore, you can pass a min_surface_tension to the apply_on_interface() call to make sure the surface tension never becomes negative. This can help to prevent crashes of the simulation, when the surfactant leaves the valid bounds.

As for the insoluble surfactants, the interface properties of for an interface with soluble surfactants is obtained by get_interface_properties(). However, in order for the surfactant to be indeed soluble, the surfactant must be present in both the liquid bulk properties and the interface surfactants.

# For soluble surfactants, we also must have it in the bulk (potentially at zero concentration)
liquid = Mixture(get_pure_liquid("water")+0.001*get_pure_liquid("my_soluble_surfactant"))
gas = get_pure_gas("air")
# Dict stating the initial surface concentration
surfactants = {"my_soluble_surfactant": 1 * micro * mol / meter ** 2}

# Getting interface properties with surfactants.
# For a soluble surfactant, it must be present in both the liquid phase and in the surfactants dict
# Any of them may be present at zero concentration, but it must be specified to be present at all
interface = get_interface_properties(liquid, gas, surfactants=surfactants)