Source code for ogstools.materiallib.distributions
# SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from dataclasses import asdict, dataclass, fields
from typing import Any
[docs]
@dataclass(frozen=True)
class NormalDistribution:
mean: float
stddev: float
[docs]
@dataclass(frozen=True)
class LogNormalDistribution:
mean: float
stddev: float
Distribution = (
UniformDistribution
| NormalDistribution
| LogUniformDistribution
| LogNormalDistribution
)
_DISTRIBUTION_TYPES: dict[str, type[Distribution]] = {
"uniform": UniformDistribution,
"normal": NormalDistribution,
"loguniform": LogUniformDistribution,
"lognormal": LogNormalDistribution,
}
def _distribution_fields(distribution_type: str) -> tuple[str, ...]:
cls = _DISTRIBUTION_TYPES[distribution_type]
return tuple(field.name for field in fields(cls))
def _validate_distribution_mapping(data: dict[str, Any]) -> str:
type_ = data.get("type")
if not isinstance(type_, str):
msg = "Distribution mapping must define a string 'type'."
raise ValueError(msg)
if type_ not in _DISTRIBUTION_TYPES:
known_types = ", ".join(sorted(_DISTRIBUTION_TYPES))
msg = (
f"Unknown distribution type '{type_}'. "
f"Known types are: {known_types}."
)
raise ValueError(msg)
expected_fields = _distribution_fields(type_)
allowed_keys = {"type", *expected_fields}
unknown_keys = set(data) - allowed_keys
if unknown_keys:
msg = (
f"Distribution of type '{type_}' contains unknown key(s): "
f"{', '.join(sorted(unknown_keys))}."
)
raise ValueError(msg)
missing_keys = set(expected_fields) - set(data)
if missing_keys:
msg = (
f"Distribution of type '{type_}' is missing required key(s): "
f"{', '.join(sorted(missing_keys))}."
)
raise ValueError(msg)
return type_
[docs]
def parse_distribution(data: dict[str, Any]) -> Distribution:
type_ = _validate_distribution_mapping(data)
cls = _DISTRIBUTION_TYPES[type_]
kwargs = {name: data[name] for name in _distribution_fields(type_)}
return cls(**kwargs)
[docs]
def serialize_distribution(distribution: Distribution) -> dict[str, Any]:
for type_name, cls in _DISTRIBUTION_TYPES.items():
if isinstance(distribution, cls):
return {"type": type_name, **asdict(distribution)}
msg = f"Unsupported distribution object of type {type(distribution)!r}."
raise TypeError(msg)