"""
pyEQL Solute class.
This file contains functions and methods for managing properties of
individual solutes. The Solute class contains methods for accessing
ONLY those properties that DO NOT depend on solution composition.
Solute properties such as activity coefficient or concentration
that do depend on compsition are accessed via Solution class methods.
:copyright: 2013-2024 by Ryan S. Kingsbury
:license: LGPL, see LICENSE for more details.
"""
from __future__ import annotations
import logging
import warnings
from dataclasses import asdict, dataclass, field
from typing import Literal
import numpy as np
from pymatgen.core.ion import Ion
from pyEQL.utils import standardize_formula
logger = logging.getLogger(__name__)
[docs]
@dataclass
class Datum:
"""Document containing data for a single computed or experimental property."""
value: str
reference: str | None = None
data_type: Literal["computed", "experimental", "fitted", "unknown"] = "unknown"
@property
def magnitude(self):
"""Return the numerical value of a Datum."""
return float(self.value.split(" ")[0])
@property
def unit(self):
"""Return the unit of a Datum."""
return self.value.split(" ")[-1]
@property
def uncertainty(self):
"""Return the uncertainty of a Datum."""
if len(self.value.split(" ")) > 3:
return float(self.value.split(" ")[2])
return np.nan
[docs]
def as_dict(self):
"""Return a dictionary representation of the Datum."""
return dict(asdict(self).items())
[docs]
@dataclass
class Solute:
"""
represent each chemical species as an object containing its formal charge,
transport numbers, concentration, activity, etc.
Args:
formula: Chemical formula for the solute. Charged species must contain a + or - and (for polyvalent solutes)
a number representing the net charge (e.g. 'SO4-2').
"""
formula: str
charge: int
molecular_weight: str
elements: list
chemsys: str
pmg_ion: Ion
formula_html: str
formula_latex: str
formula_hill: str
formula_pretty: str
oxi_state_guesses: dict[str, float]
n_atoms: int
n_elements: int
size: dict = field(
default_factory=lambda: {
"radius_ionic": None,
"radius_hydrated": None,
"radius_vdw": None,
"molar_volume": None,
}
)
thermo: dict = field(default_factory=lambda: {"ΔG_hydration": None, "ΔG_formation": None})
transport: dict = field(default_factory=lambda: {"diffusion_coefficient": None})
model_parameters: dict = field(
default_factory=lambda: {
"activity_pitzer": {"Beta0": None, "Beta1": None, "Beta2": None, "Cphi": None, "Max_C": None},
"molar_volume_pitzer": {
"Beta0": None,
"Beta1": None,
"Beta2": None,
"Cphi": None,
"V_o": None,
"Max_C": None,
},
"viscosity_jones_dole": {"B": None},
"diffusion_temp_smolyakov": {"a1": None, "a2": None, "d": None},
}
)
[docs]
def as_dict(self):
"""Return a dictionary representation of the Solute."""
return dict(asdict(self).items())
# set output of the print() statement
def __str__(self):
return (
"Species "
+ str(self.formula)
+ " MW="
+ str(self.mw)
+ " Formal Charge="
+ str(self.charge)
+ " Amount= "
+ str(self.moles)
)