Activity Example#
This notebook demonstrates the Binary Activity Theory (BAT) model application, crucial for calculating the activity of water and organic compounds in mixtures and understanding phase separation. This model, as detailed in Gorkowski, K., Preston, T. C., & Zuend, A. (2019), provides critical insights into aerosol particle behavior, essential in environmental and climate change research.
Reference: Gorkowski, K., Preston, T. C., & Zuend, A. (2019). Relative-humidity-dependent organic aerosol thermodynamics Via an efficient reduced-complexity model. Atmospheric Chemistry and Physics https://doi.org/10.5194/acp-19-13383-2019
import numpy as np # For numerical operations
import matplotlib.pyplot as plt # For plotting graphs
# Specific functions from the particula package for activity calculations
from particula.activity import binary_activity, phase_separation, species_density
Activity Calculation#
Define the parameters required by the activity module to calculate the activity of water and organic compounds in a mixture, as well as phase separation. These parameters include organic mole fraction, density, molecular weight ratio [water/organic], and the density of the organic compound. Using these parameters helps in accurately modeling the behavior of aerosol particles in various environmental conditions.
# Define a range of organic mole fractions for the calculation
organic_mole_fraction = np.linspace(0.001, 1, 1000)
# Define other necessary parameters
oxygen2carbon = 0.225 # Oxygen to carbon ratio
molar_mass_ratio = 18.016 / 100 # Water to organic molecular weight ratio
density = species_density.organic_density_estimate(
18.016 / molar_mass_ratio,
oxygen2carbon) # Estimate of organic compound density
# Calculate activity coefficients using the binary_activity function
activity_water, activity_organic, mass_water, mass_organic, gamma_water, gamma_organic = \
binary_activity.activity_coefficients(
molar_mass_ratio,
organic_mole_fraction,
oxygen2carbon,
density,
functional_group=None)
Plotting the Activity and Phase Separation#
Here we plot the activity of water and the organic compound as a function of the organic mole fraction. Visualizing these activities helps in identifying phase separation or miscibility gaps, crucial for understanding the behavior of aerosols under different environmental conditions. Phase separation is indicated by activities greater than 1.0 or non-monotonic behavior in the activity curve, as shown below.
fig, ax = plt.subplots()
ax.plot(
1 - organic_mole_fraction,
activity_water,
label="water",
linestyle='dashed'
)
ax.plot(
1 - organic_mole_fraction,
activity_organic,
label="organic",
)
ax.set_ylim()
ax.set_xlabel("water mole fraction")
ax.set_ylabel("activity")
ax.legend()
plt.show()
fig, ax = plt.subplots()
ax.plot(
1 - organic_mole_fraction,
gamma_water,
label="water",
linestyle='dashed'
)
ax.plot(
1 - organic_mole_fraction,
gamma_organic,
label="organic",
)
ax.set_ylim()
ax.set_xlabel("water mole fraction")
ax.set_ylabel("activity coefficient")
ax.legend()
plt.show()
\( q^\alpha \)#
The \(q^\alpha\) parameter signifies the transition from an organic-rich phase to a water-rich phase. This transition is crucial for understanding the phase behavior of aerosol particles. It can be calculated using the particula.activity.phase_separation
function. The plot below illustrates \(q^\alpha\) based on the activity calculations performed earlier.
# Finding phase separation points and calculating q_alpha
phase_sep_aw = phase_separation.find_phase_separation(
activity_water, activity_organic)
q_alpha = phase_separation.q_alpha(
seperation_activity=phase_sep_aw['upper_seperation'],
activities=activity_water)
# Plotting q_alpha
fig, ax = plt.subplots()
plt.plot(activity_water, q_alpha)
plt.xlabel('Water Activity')
plt.ylabel('$q^{\\alpha}$ [Organic Rich to Water Rich]')
plt.show()
Water Activity Focus#
In atmospheric aerosol modeling, water activity is often a more critical parameter than mole fraction. This is because water activity is typically a controllable or known variable in atmospheric conditions, unlike the exact mole fractions in a solution. To correlate water activity with the mole fraction required to achieve it, we utilize functions from the particula.activity
module.
# select the water activity desired
water_activity_desired = np.linspace(0.5, 1, 100)
oxygen2carbon = 0.25
# calculate the mass fraction of water in the alpha and beta phases
# for each water activity
alpha, beta, q_alpha = binary_activity.fixed_water_activity(
water_activity=water_activity_desired,
molar_mass_ratio=molar_mass_ratio,
oxygen2carbon=oxygen2carbon,
density=density
)
# plot the results vs water activity
fig, ax = plt.subplots()
ax.plot(
water_activity_desired,
alpha[2],
label="alpha phase mass fraction water",
)
ax.plot(
water_activity_desired,
q_alpha,
label="q_alpha",
)
if beta is not None:
ax.plot(
water_activity_desired,
beta[2],
label="beta phase mass fraction water",
)
ax.set_ylim()
ax.set_xlabel("water activity (Relative Humidity/100)")
ax.set_ylabel("mass fraction of water")
plt.legend()
plt.show()
Higher Oxygen to Carbon Ratios#
Higher oxygen to carbon ratios in the mixture tend to inhibit phase separation. The following analysis demonstrates this effect. This observation is crucial in predicting the behavior of aerosol particles under varying chemical compositions (more or less ‘aged’).
# select the water activity desired
water_activity_desired = np.linspace(0.5, 1, 100)
# select the oxygen to carbon ratio
oxygen2carbon = 0.6
# calculate the mass fraction of water in the alpha and beta phases
# for each water activity
alpha, beta, q_alpha = binary_activity.fixed_water_activity(
water_activity=water_activity_desired,
molar_mass_ratio=molar_mass_ratio,
oxygen2carbon=oxygen2carbon,
density=density
)
# plot the results vs water activity
fig, ax = plt.subplots()
ax.plot(
water_activity_desired,
alpha[2],
label="alpha phase mass fraction water",
)
ax.plot(
water_activity_desired,
q_alpha,
label="q_alpha",
)
if beta is not None:
ax.plot(
water_activity_desired,
beta[2],
label="beta phase mass fraction water",
)
ax.set_ylim()
ax.set_xlabel("water activity (Relative Humidity/100)")
ax.set_ylabel("mass fraction of water")
plt.legend()
plt.show()
Summary#
This notebook demonstrates how to use the activity module for calculating the activity of water and organic compounds in a mixture and assessing phase separation. The insights gained are vital for applications in aerosol thermodynamics, cloud condensation nuclei, and cloud microphysics.
This is an implementation of the Binary Activity Theory (BAT) model developed in Gorkowski, K., Preston, T. C., & Zuend, A. (2019).
Further Documentation#
For more in-depth understanding and additional functionalities, refer to the documentation, or call the help
function on any of the functions in the particula.activity
module.
help(binary_activity)
Help on module particula.activity.binary_activity in particula.activity:
NAME
particula.activity.binary_activity - Binary activity coefficient model for organic-water mixtures.
DESCRIPTION
Gorkowski, K., Preston, T. C., & Zuend, A. (2019).
Relative-humidity-dependent organic aerosol thermodynamics
Via an efficient reduced-complexity model.
Atmospheric Chemistry and Physics
https://doi.org/10.5194/acp-19-13383-2019
FUNCTIONS
activity_coefficients(molar_mass_ratio: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], organic_mole_fraction: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], oxygen2carbon: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], density: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], functional_group=None) -> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray]
Calculate the activity coefficients for water and organic matter in
organic-water mixtures.
Args:
- molar_mass_ratio: Ratio of the molecular weight of water to the
molecular weight of organic matter.
- organic_mole_fraction: Molar fraction of organic matter in the
mixture.
- oxygen2carbon: Oxygen to carbon ratio in the organic compound.
- density: Density of the mixture.
- functional_group: Optional functional group(s) of the organic
compound, if applicable.
Returns:
A tuple containing the activity of water, activity
of organic matter, mass fraction of water, and mass
fraction of organic matter, gamma_water (activity coefficient),
and gamma_organic (activity coefficient).
bat_blending_weights(molar_mass_ratio: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], oxygen2carbon: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]]) -> numpy.ndarray
Function to estimate the blending weights for the BAT model.
Args:
- molar_mass_ratio: The molar mass ratio of water to organic
matter.
- oxygen2carbon: The oxygen to carbon ratio.
Returns:
- blending_weights : List of blending weights for the BAT model
in the low, mid, and high oxygen2carbon regions.
biphasic_water_activity_point(oxygen2carbon: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], hydrogen2carbon: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], molar_mass_ratio: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], functional_group: Union[list[str], str, NoneType] = None) -> numpy.ndarray
This function computes the biphasic to single phase
water activity (RH*100).
Args:
- oxygen2carbon: The oxygen to carbon ratio.
- hydrogen2carbon: The hydrogen to carbon ratio.
- molar_mass_ratio: The molar mass ratio of water to organic
matter.
- functional_group: Optional functional group(s) of the organic
compound, if applicable.
Returns:
- np.array: The RH cross point array.
coefficients_c(molar_mass_ratio: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], oxygen2carbon: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], fit_values: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]]) -> numpy.ndarray
Coefficients for activity model, see Gorkowski (2019). equation S1 S2.
Args:
- molar_mass_ratio: The molar mass ratio of water to organic
matter.
- oxygen2carbon: The oxygen to carbon ratio.
- fit_values: The fit values for the activity model.
Returns:
- np.ndarray: The coefficients for the activity model.
convert_to_oh_equivalent(oxygen2carbon: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], molar_mass_ratio: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], functional_group: Union[list[str], str, NoneType] = None) -> Tuple[numpy.ndarray, numpy.ndarray]
just a pass through now, but will
add the oh equivalent conversion
fixed_water_activity(water_activity: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], molar_mass_ratio: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], oxygen2carbon: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], density: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]]) -> Tuple
Calculate the activity coefficients of water and organic matter in
organic-water mixtures.
This function assumes a fixed water activity value (e.g., RH = 75%
corresponds to 0.75 water activity in equilibrium).
It calculates the activity coefficients for different phases and
determines phase separations if they occur.
Parameters:
water_activity (ArrayLike): An array of water activity values.
molar_mass_ratio (ArrayLike): Array of molar mass ratios of the components.
oxygen2carbon (ArrayLike): Array of oxygen-to-carbon ratios.
density (ArrayLike): Array of densities of the mixture.
Returns:
Tuple: A tuple containing the activity coefficients for alpha and beta
phases, and the alpha phase mole fraction.
If no phase separation occurs, the beta phase values are None.
gibbs_mix_weight(molar_mass_ratio: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], organic_mole_fraction: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], oxygen2carbon: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], density: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], functional_group: Optional[str] = None) -> Tuple[numpy.ndarray, numpy.ndarray]
Gibbs free energy of mixing, see Gorkowski (2019), with weighted
oxygen2carbon regions. Only can run one compound at a time.
Args:
- molar_mass_ratio: The molar mass ratio of water to organic
matter.
- organic_mole_fraction: The fraction of organic matter.
- oxygen2carbon: The oxygen to carbon ratio.
- density: The density of the mixture.
- functional_group: Optional functional group(s) of the organic
compound, if applicable.
Returns:
- gibbs_mix : Gibbs energy of mixing (including 1/RT)
- derivative_gibbs : derivative of Gibbs energy with respect to
- mole fraction of organics (includes 1/RT)
gibbs_of_mixing(molar_mass_ratio: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], organic_mole_fraction: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], oxygen2carbon: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], density: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], fit_dict: dict) -> Tuple[numpy.ndarray, numpy.ndarray]
Calculate the Gibbs free energy of mixing for a binary mixture.
Args:
- molar_mass_ratio: The molar mass ratio of water to organic
matter.
- organic_mole_fraction: The fraction of organic matter.
- oxygen2carbon: The oxygen to carbon ratio.
- density: The density of the mixture.
- fit_dict: A dictionary of fit values for the low oxygen2carbon region
Returns:
Tuple[np.ndarray, np.ndarray]: A tuple containing the Gibbs free
energy of mixing and its derivative.
DATA
ArrayLike = typing.Union[numpy._typing._array_like._Supports...ng.Unio...
FIT_HIGH = {'a1': [5.92155, -2.528295, -3.883017, -7.898128], 'a2': [-...
FIT_LOW = {'a1': [7.089476, -7.71186, -38.85941, -100.0], 'a2': [-0.62...
FIT_MID = {'a1': [5.872214, -4.535007, -5.129327, -28.09232], 'a2': [-...
INTERPOLATE_WATER_FIT = 500
LOWEST_ORGANIC_MOLE_FRACTION = 1e-12
Optional = typing.Optional
Optional[X] is equivalent to Union[X, None].
Tuple = typing.Tuple
Deprecated alias to builtins.tuple.
Tuple[X, Y] is the cross-product type of X and Y.
Example: Tuple[T1, T2] is a tuple of two elements corresponding
to type variables T1 and T2. Tuple[int, float, str] is a tuple
of an int, a float and a string.
To specify a variable-length tuple of homogeneous type, use Tuple[T, ...].
Union = typing.Union
Union type; Union[X, Y] means either X or Y.
On Python 3.10 and higher, the | operator
can also be used to denote unions;
X | Y means the same thing to the type checker as Union[X, Y].
To define a union, use e.g. Union[int, str]. Details:
- The arguments must be types and there must be at least one.
- None as an argument is a special case and is replaced by
type(None).
- Unions of unions are flattened, e.g.::
assert Union[Union[int, str], float] == Union[int, str, float]
- Unions of a single argument vanish, e.g.::
assert Union[int] == int # The constructor actually returns int
- Redundant arguments are skipped, e.g.::
assert Union[int, str, int] == Union[int, str]
- When comparing unions, the argument order is ignored, e.g.::
assert Union[int, str] == Union[str, int]
- You cannot subclass or instantiate a union.
- You can use Optional[X] as a shorthand for Union[X, None].
FILE
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/particula/activity/binary_activity.py