Source code for ogstools.materiallib.core.components
# SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
# SPDX-License-Identifier: BSD-3-Clause
from pathlib import Path
import yaml # type: ignore[import]
# from ogstools.definitions import MATERIALS_DIR
import ogstools.definitions as defs
from .component import Component
from .material import Material
from .property import MaterialProperty
[docs]
class Components:
[docs]
def __init__(
self,
phase_type: str,
gas_component: Material,
liquid_component: Material,
process: str,
Diffusion_coefficient: float | None = None,
data_dir: Path | str | None = None,
):
self.phase_type = phase_type
self.gas_properties: list[MaterialProperty] = gas_component.properties
self.liquid_properties: list[MaterialProperty] = (
liquid_component.properties
)
self.process = process
self.data_dir = (
Path(data_dir) if data_dir is not None else Path(defs.MATERIALS_DIR)
)
self.gas_component = gas_component
self.liquid_component = liquid_component
if self.phase_type == "AqueousLiquid":
gas_role = "Solute"
liquid_role = "Solvent"
elif self.phase_type == "Gas":
gas_role = "Carrier"
liquid_role = "Vapour"
else:
msg = f"Unsupported phase_type: {self.phase_type}"
raise ValueError(msg)
D = (
Diffusion_coefficient
if Diffusion_coefficient is not None
else self.get_binary_diffusion_coefficient(self.phase_type)
)
self.gas_component_obj = self._create_component(
self.gas_component, gas_role, D
)
self.liquid_component_obj = self._create_component(
self.liquid_component, liquid_role, D
)
[docs]
def get_binary_diffusion_coefficient(self, phase: str) -> float:
"""
Retrieves the binary diffusion coefficient D [m²/s] for a specific component pair
in either the liquid or gas phase.
In the liquid phase, this typically describes a gas (e.g. CO2) diffusing in a solvent
like water. In the gas phase, it typically describes a vapor (e.g. H2O) diffusing in a
carrier gas like CO₂.
Binary diffusion coefficients depend on a pair of species rather than a single
material, which is why they are treated differently from other material properties.
Parameters:
phase (str): Either "liquid" or "gas". Indicates the phase in which diffusion
occurs.
Returns:
float: Binary diffusion coefficient in [m²/s].
Raises:
ValueError: If the component pair is not found.
"""
if phase == "AqueousLiquid":
# Solute (gas component) diffuses in solvent (liquid component)
solvent = self.liquid_component.name
solute = self.gas_component.name
elif phase == "Gas":
# Solute (liquid component) evaporates into solvent (gas component)
solvent = self.gas_component.name
solute = self.liquid_component.name
else:
msg = f"Invalid phase '{phase}'. Must be 'AqueousLiquid' or 'Gas'."
raise ValueError(msg)
file_path = self._diffusion_coefficients_path()
with file_path.open() as f:
data = yaml.safe_load(f)
try:
return float(data[phase][solvent][solute])
except KeyError:
pass
try:
return float(data[phase][solute][solvent])
except KeyError as err:
msg = (
f"No {phase}-phase diffusion coefficient found for the pair "
f"'{solvent} / {solute}' in {file_path}"
)
raise ValueError(msg) from err
def _diffusion_coefficients_path(self) -> Path:
"""
Resolve the diffusion coefficients file within the configured data directory.
Prefers .yml but accepts .yaml as well so external libraries can supply either.
If not found in the configured data directory, fall back to the built-in
materials directory for backward compatibility.
"""
default_dir = Path(defs.MATERIALS_DIR)
data_dir = self.data_dir
names = ["diffusion_coefficients.yaml", "diffusion_coefficients.yml"]
for dir_ in [data_dir, default_dir]:
for name in names:
candidate = dir_ / name
if candidate.is_file():
return candidate
# Default to the configured data_dir .yml path for error reporting/opening.
return data_dir / names[-1]
def _create_component(
self, material: Material, role: str, D: float
) -> Component:
return Component(
material,
self.phase_type,
role,
self.process,
diffusion_coefficient=D,
)
# -----------------------
# Representation
# -----------------------
def __repr__(self) -> str:
lines = [f"<Components for phase '{self.phase_type}'>"]
for comp in [self.gas_component_obj, self.liquid_component_obj]:
for line in repr(comp).splitlines():
lines.append(" " + line)
return "\n".join(lines)