Source code for ogstools.materiallib.core.media
# Copyright (c) 2012-2025, OpenGeoSys Community (http://www.opengeosys.org)
# Distributed under a Modified BSD License.
# See accompanying file LICENSE.txt or
# http://www.opengeosys.org/project/license
#
import logging
from collections.abc import Iterator
from ogstools.materiallib.core.material_manager import MaterialManager
from ogstools.materiallib.core.medium import Medium
logger = logging.getLogger(__name__)
[docs]
class MediaSet:
"""
Represents a collection of Medium objects (solids or fluids) for a given process.
MediaSet are constructed from a filtered MaterialManager, i.e. after process
schemas, subdomain assignments, and fluid materials have already been applied.
Provides:
- Dictionary-like access (`__getitem__`, `keys()`, `values()`, `items()`).
- Iteration over all Medium instances.
- Lookup by name or by material ID.
Notes
-----
This class requires that the input MaterialManager has already been
filtered for a specific process (``filtered_db.process`` must not be None).
"""
[docs]
def __init__(self, filtered_db: MaterialManager):
"""
Create a MediaSet collection from a filtered MaterialManager.
Parameters
----------
filtered_db : MaterialManager
A MaterialManager instance that has been filtered for a
specific process and contains subdomain IDs and fluids.
Raises
------
ValueError
If `filtered_db.process` is None (i.e. unfiltered manager),
or if any Medium fails validation.
"""
if filtered_db.process is None:
logger.error(
"Cannot construct MediaSet: MaterialManager has no process."
)
msg = (
"MediaSet can only be created from a filtered MaterialManager."
)
raise ValueError(msg)
self.process = filtered_db.process
self._media: list[Medium] = []
self._name_map: dict[str, Medium] = {}
for name, mat_id in filtered_db.subdomain_ids.items():
mat = filtered_db.materials_db[name]
medium = Medium(
material_id=mat_id,
material=mat,
name=name,
fluids=filtered_db.fluids,
process=self.process,
)
logger.debug("Created Medium '%s' (ID=%d)", name, mat_id)
self._media.append(medium)
self._name_map[name] = medium
if not self.validate_medium(medium):
msg = f"Medium '{name}' (ID={mat_id}) is invalid."
logger.error(msg)
raise ValueError(msg)
def __iter__(self) -> Iterator[Medium]:
"""Iterate over all Medium objects."""
return iter(self._media)
[docs]
def __getitem__(self, key: str) -> Medium:
"""Retrieve a Medium by its subdomain name."""
return self._name_map[key]
def __len__(self) -> int:
"""Return the number of Medium objects."""
return len(self._media)
def __contains__(self, name: str) -> bool:
"""Return True if a Medium with the given name exists."""
return name in self._name_map
[docs]
def keys(self) -> list[str]:
"""Return the list of subdomain names (keys)."""
return list(self._name_map.keys())
[docs]
def values(self) -> list[Medium]:
"""Return the list of Medium objects (values)."""
return list(self._name_map.values())
[docs]
def items(self) -> list[tuple[str, Medium]]:
"""Return (name, Medium) pairs like dict.items()."""
return list(self._name_map.items())
[docs]
def to_dict(self) -> dict[str, Medium]:
"""Return the mapping of subdomain names to Medium objects."""
return dict(self._name_map)
[docs]
def get_by_id(self, material_id: int) -> Medium | None:
"""
Lookup a Medium by its material ID.
Parameters
----------
material_id : int
The material_id assigned to a subdomain.
Returns
-------
Medium | None
The Medium object with the given ID, or None if not found.
"""
return next(
(m for m in self._media if m.material_id == material_id), None
)
[docs]
@classmethod
def from_project(cls, prj: str, process: str) -> "MediaSet":
"""
Reconstruct a Media collection from an OGS6py Project.
Parameters
----------
prj : Project
An OGS6py Project instance containing <media> definitions.
process : str
The process type to which these media belong.
Raises
------
NotImplementedError
This functionality is not implemented yet.
"""
logger.info("Attempted to parse Media from Project (not implemented).")
_ = prj, process # prevent unused arg warnings
msg = "from_prj() not implemented yet."
raise NotImplementedError(msg)
[docs]
def validate(self) -> bool:
"""
Validate all Medium objects.
Returns
-------
bool
True if all Medium objects are valid, otherwise raises ValueError.
"""
logger.debug("Validating %d media objects...", len(self._media))
for medium in self._media:
medium.validate()
return True
[docs]
def validate_medium(self, medium: Medium) -> bool:
"""
Validate a single Medium.
Parameters
----------
medium : Medium
The Medium object to validate.
Returns
-------
bool
True if valid.
Raises
------
ValueError
If the Medium fails validation.
"""
if not medium.validate():
msg = f"Medium '{medium.name}' failed validation."
logger.error(msg)
raise ValueError(msg)
return True
# -----------------------
# Representation
# -----------------------
def __repr__(self) -> str:
"""Return a human-readable string representation of this MediaSet collection."""
lines = [f"<MediaSet with {len(self)} entries>"]
for medium in self._media:
for line in repr(medium).splitlines():
lines.append(" " + line)
return "\n".join(lines)