"""Viscosity computation for various media"""
from __future__ import annotations
from typing import Literal
import warnings
import numpy as np
from ...warn import PipelineWarning
#: Dictionary with different names for one medium
SAME_MEDIA = {
"0.49% MC-PBS": ["0.49% MC-PBS",
"0.5% MC-PBS",
"0.50% MC-PBS",
"CellCarrier",
],
"0.59% MC-PBS": ["0.59% MC-PBS",
"0.6% MC-PBS",
"0.60% MC-PBS",
"CellCarrier B",
"CellCarrierB",
],
"0.83% MC-PBS": ["0.83% MC-PBS",
"0.8% MC-PBS",
"0.80% MC-PBS"],
"water": ["water"],
}
#: Many media names are actually shorthand for one medium
ALIAS_MEDIA = {}
for key in SAME_MEDIA:
for item in SAME_MEDIA[key]:
ALIAS_MEDIA[item] = key
ALIAS_MEDIA[item.lower()] = key # also support all-lower case
#: Media for which computation of viscosity is defined (has duplicate entries)
KNOWN_MEDIA = sorted(ALIAS_MEDIA.keys())
[docs]class TemperatureOutOfRangeWarning(PipelineWarning):
pass
[docs]def check_temperature(model: str,
temperature: float | np.array,
tmin: float,
tmax: float):
"""Raise a TemperatureOutOfRangeWarning if applicable"""
if np.min(temperature) < tmin or np.max(temperature) > tmax:
warnings.warn(
f"For the {model} model, the temperature should be "
f"in [{tmin}, {tmax}] degC! Got min/max of "
f"[{np.min(temperature):.1f}, {np.max(temperature):.1f}] degC.",
TemperatureOutOfRangeWarning)
[docs]def get_viscosity(medium: str = "0.49% MC-PBS",
channel_width: float = 20.0,
flow_rate: float = 0.16,
temperature: float | np.ndarray = 23.0,
model: Literal['herold-2017',
'herold-2017-fallback',
'buyukurganci-2022',
'kestin-1978'] = 'herold-2017-fallback'):
"""Returns the viscosity for RT-DC-specific media
Media that are not pure (e.g. ketchup or polymer solutions)
often exhibit a non-linear relationship between shear rate
(determined by the velocity profile) and shear stress
(determined by pressure differences). If the shear stress
grows non-linearly with the shear rate resulting in a slope
in log-log space that is less than one, then we are talking about
shear thinning. The viscosity is not a constant anymore (as it
is e.g. for water). At higher flow rates, the viscosity becomes
smaller, following a power law. Christoph Herold characterized
shear thinning for the CellCarrier media :cite:`Herold2017`.
The resulting formulae for computing the viscosities of these
media at different channel widths, flow rates, and temperatures,
are implemented here.
Parameters
----------
medium: str
The medium to compute the viscosity for; Valid values
are defined in :const:`KNOWN_MEDIA`.
channel_width: float
The channel width in µm
flow_rate: float
Flow rate in µL/s
temperature: float or ndarray
Temperature in °C
model: str
The model name to use for computing the medium viscosity.
For water, this value is ignored, as there is only the
'kestin-1978' model :cite:`Kestin_1978`. For MC-PBS media,
there are the 'herold-2017' model :cite:`Herold2017` and the
'buyukurganci-2022' model :cite:`Buyukurganci2022`.
Returns
-------
viscosity: float or ndarray
Viscosity in mPa*s
Notes
-----
- CellCarrier (0.49% MC-PBS) and CellCarrier B (0.59% MC-PBS) are
media designed for RT-DC experiments.
- A :class:`TemperatureOutOfRangeWarning` is issued if the
input temperature range exceeds the temperature ranges of
the models.
"""
# also support lower-case media and a space before the "B"
if medium not in KNOWN_MEDIA:
raise ValueError(f"Invalid medium: {medium}")
medium = ALIAS_MEDIA[medium]
if medium == "water":
# We ignore the `model`, because it's user convenient.
eta = get_viscosity_water_kestin_1978(temperature=temperature)
elif medium in ["0.49% MC-PBS", "0.59% MC-PBS", "0.83% MC-PBS"]:
kwargs = {"medium": medium,
"temperature": temperature,
"flow_rate": flow_rate,
"channel_width": channel_width}
# Let the user know that we have a new model in town.
if model == "herold-2017-fallback":
warnings.warn(
"dclab 0.48.0 introduced a more accurate model for computing "
"the MC-PBS viscosity. You are now using the old model "
"'herold-2017'. Unless you are reproducing an old analysis "
"pipeline, you should consider passing 'buyukurganci-2022' "
"as a viscosity model!",
DeprecationWarning)
model = "herold-2017"
if model == "herold-2017":
eta = get_viscosity_mc_pbs_herold_2017(**kwargs)
elif model == "buyukurganci-2022":
eta = get_viscosity_mc_pbs_buyukurganci_2022(**kwargs)
else:
raise NotImplementedError(f"Unknown model '{model}' for MC-PBS!")
else:
raise NotImplementedError(f"Unknown medium '{medium}'!")
return eta
[docs]def shear_rate_square_channel(flow_rate, channel_width, flow_index):
"""Returns The wall shear rate of a power law liquid in a squared channel.
Parameters
----------
flow_rate: float
Flow rate in µL/s
channel_width: float
The channel width in µm
flow_index: float
Flow behavior index aka the power law exponent of the shear thinning
Returns
-------
shear_rate: float
Shear rate in 1/s.
"""
# convert channel width to mm
channel_width = channel_width * 1e-3
return 8*flow_rate/(channel_width**3) * (0.6671 + 0.2121/flow_index)
[docs]def get_viscosity_mc_pbs_buyukurganci_2022(
medium: Literal["0.49% MC-PBS",
"0.59% MC-PBS",
"0.83% MC-PBS"] = "0.49% MC-PBS",
channel_width: float = 20.0,
flow_rate: float = 0.16,
temperature: float = 23.0):
"""Compute viscosity of MC-PBS according to :cite:`Buyukurganci2022`
This viscosity model was derived in :cite:`Buyukurganci2022`
and adapted for RT-DC in :cite:`Reichel2023`.
"""
check_temperature("'buyukurganci-2022' MC-PBS", temperature, 22, 37)
# material constants for temperature behavior of MC dissolved in PBS:
alpha = 0.00223
lambd = 3379.7
kelvin = temperature + 273.15
if medium == "0.49% MC-PBS":
a = 2.30e-6 # previously 2.23e-6, changed in Reichel2023 rev 2
beta = -0.0056
elif medium == "0.59% MC-PBS":
a = 5.70e-6
beta = -0.0744
elif medium == "0.83% MC-PBS":
a = 16.52e-6
beta = -0.1455
else:
raise NotImplementedError(
f"Medium {medium} not supported for model `buyukurganci-2022`!")
k = a * np.exp(lambd / kelvin)
n = alpha * kelvin + beta
shear_rate = shear_rate_square_channel(flow_rate, channel_width, n)
return k * shear_rate**(n - 1) * 1e3
[docs]def get_viscosity_mc_pbs_herold_2017(
medium: Literal["0.49% MC-PBS", "0.59% MC-PBS"] = "0.49% MC-PBS",
channel_width: float = 20.0,
flow_rate: float = 0.16,
temperature: float = 23.0):
r"""Compute viscosity of MC-PBS according to :cite:`Herold2017`
Note that all the factors in equation 5.2 in :cite:`Herold2017`
compute to 8, which is essentially what is implemented in
:func:`shear_rate_square_channel`:
.. math::
1.1856 \cdot 6 \cdot \frac{2}{3} \cdot \frac{1}{0.5928} = 8
"""
# see figure (9) in Herold arXiv:1704.00572 (2017)
check_temperature("'herold-2017' MC-PBS", temperature, 18, 26)
# convert flow_rate from µL/s to m³/s
# convert channel_width from µm to m
term1 = 1.1856 * 6 * flow_rate * 1e-9 / (channel_width * 1e-6)**3 * 2 / 3
if medium == "0.49% MC-PBS":
temp_corr = (temperature / 23.2)**-0.866
term2 = 0.6771 / 0.5928 + 0.2121 / (0.5928 * 0.677)
eta = 0.179 * (term1 * term2)**(0.677 - 1) * temp_corr * 1e3
elif medium == "0.59% MC-PBS":
temp_corr = (temperature / 23.6)**-0.866
term2 = 0.6771 / 0.5928 + 0.2121 / (0.5928 * 0.634)
eta = 0.360 * (term1 * term2)**(0.634 - 1) * temp_corr * 1e3
else:
raise NotImplementedError(
f"Medium {medium} not supported for model `herold-2017`!")
return eta
[docs]def get_viscosity_water_kestin_1978(temperature: float = 23.0):
"""Compute the viscosity of water according to :cite:`Kestin_1978`"""
# see equation (15) in Kestin et al, J. Phys. Chem. 7(3) 1978
check_temperature("'kestin-1978' water", temperature, 0, 40)
eta0 = 1.002 # [mPa s]
right = (20-temperature) / (temperature + 96) \
* (+ 1.2364
- 1.37e-3 * (20 - temperature)
+ 5.7e-6 * (20 - temperature)**2
)
eta = eta0 * 10**right
return eta