Source code for pylimer_tools.calc.stoichiometric_relationships
"""
Module for calculating stoichiometric relationships in polymer networks.
In particular, it helps to work with the parameter `b2`, which is the mole fraction
of reactive sites in B2 among all reactive sites in a mixture of B1 and B2.
"""
import pint
from typing import Union, cast
import math
[docs]
def compute_number_fractions(
b2: float = 1.0,
) -> tuple[float, float]:
"""
Compute the number fractions of bifunctional and monofunctional strands.
:param b2: The mole fraction of reactive sites in B2 among all reactive sites in a mixture of B1 and B2
:return: A tuple containing the number fractions (x_bifunctional, x_monofunctional)
:rtype: tuple[float, float]
"""
assert 0.0 <= b2 <= 1.0, "b2 must be between 0 and 1."
n_total_strands = (
1e3 # irrelevant, cancels out, but makes the calculation easier to read
)
"""
b2 = (2 * n_bifunctional) / (n_monofunctional + 2 * n_bifunctional)
n_total = n_bifunctional + n_monofunctional
"""
n_bifunctional = -b2 * n_total_strands / (-2 + b2)
n_monofunctional = n_total_strands - n_bifunctional
assert math.isclose(
n_monofunctional,
2 * (-n_total_strands + b2 * n_total_strands) / (-2 + b2),
abs_tol=1e-6,
)
bifunctional_fraction = n_bifunctional / n_total_strands
monofunctional_fraction = n_monofunctional / n_total_strands
assert math.isclose(
bifunctional_fraction + monofunctional_fraction,
1.0,
abs_tol=1e-6,
)
return bifunctional_fraction, monofunctional_fraction
[docs]
def compute_strand_number_density(
mw_bifunctional: pint.Quantity,
density: pint.Quantity,
mw_monofunctional: Union[pint.Quantity, None] = None,
b2: float = 1.0,
) -> pint.Quantity:
"""
Compute the strand number density of the polymer network.
:param mw_bifunctional: The molecular weight of the bifunctional strands
:param density: The density of the polymer network
:param mw_monofunctional: The molecular weight of the monofunctional strands. None if not present
:param b2: The mole fraction of reactive sites in B2 among all reactive sites in a mixture of B1 and B2
:return: The strand number density
:rtype: pint.Quantity
"""
if (
mw_monofunctional is None
or not math.isfinite(mw_monofunctional.magnitude)
or math.isclose(mw_monofunctional.magnitude, 0.0)
):
assert b2 == 1.0, "If no monofunctional strands are present, b2 must be 1.0."
return density / mw_bifunctional
bifunctional_fraction, monofunctional_fraction = compute_number_fractions(
b2)
return cast(
pint.Quantity,
(
bifunctional_fraction * density / mw_bifunctional
+ monofunctional_fraction * density / mw_monofunctional
),
)
[docs]
def compute_weight_fractions(
mw_bifunctional: pint.Quantity,
mw_monofunctional: Union[pint.Quantity, None] = None,
b2: float = 1.0,
) -> tuple[float, float]:
"""
Compute the weight fractions of bifunctional and monofunctional strands.
Makes some assumptions, e.g. the same density for both types of strands.
:param mw_bifunctional: The molecular weight of the bifunctional strands
:param mw_monofunctional: The molecular weight of the monofunctional strands. None if not present
:param b2: The mole fraction of reactive sites in B2 among all reactive sites in a mixture of B1 and B2
:return: A tuple containing the weight fractions (w_bifunctional, w_monofunctional)
:rtype: tuple[float, float]
"""
if mw_monofunctional is None or not math.isfinite(
mw_monofunctional.magnitude):
assert b2 == 1.0, "If no monofunctional strands are present, b2 must be 1.0."
return 1.0, 0.0
assert 0.0 <= b2 <= 1.0, "b2 must be between 0 and 1."
bifunctional_fraction, monofunctional_fraction = compute_number_fractions(
b2)
mass_bifunctional = bifunctional_fraction * mw_bifunctional.magnitude
mass_monofunctional = monofunctional_fraction * mw_monofunctional.magnitude
total_mass = mass_bifunctional + mass_monofunctional
w_bifunctional = mass_bifunctional / total_mass
w_monofunctional = mass_monofunctional / total_mass
return w_bifunctional, w_monofunctional