Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/rtichoke/processing/evaluation_semantics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Internal semantic metadata for model/population evaluations.

This module does not change public inputs or rendered grouping. It records the
semantic information that can be known from the existing input shapes while
preserving ``reference_group`` as the compatibility grouping key.
"""

from dataclasses import dataclass
from typing import Dict, Mapping, Optional, Union

import numpy as np

_SHARED_POPULATION = "__shared_population__"


@dataclass(frozen=True)
class _EvaluationMetadata:
"""Semantic identity available for one compatibility reference group."""

reference_group: str
evaluation: str
model: Optional[str]
population: str


def _build_evaluation_metadata(
probs: Mapping[str, np.ndarray],
reals: Union[np.ndarray, Dict[str, np.ndarray]],
times: Union[np.ndarray, Dict[str, np.ndarray]],
) -> dict[str, _EvaluationMetadata]:
"""Describe evaluations without changing existing grouping behavior.

With shared outcome/time arrays, probability keys identify models evaluated
in one shared population. With keyed outcome/time dictionaries, keys identify
distinct evaluation populations, but the current API does not separately
encode model identity; that field is therefore left unknown rather than
inferred from a generic group label.
"""
keyed_population = isinstance(reals, dict) or isinstance(times, dict)

if keyed_population:
return {
group: _EvaluationMetadata(
reference_group=group,
evaluation=group,
model=None,
population=group,
)
for group in probs
}

return {
group: _EvaluationMetadata(
reference_group=group,
evaluation=group,
model=group,
population=_SHARED_POPULATION,
)
for group in probs
}
63 changes: 63 additions & 0 deletions tests/test_evaluation_semantics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import numpy as np

from rtichoke.processing.evaluation_semantics import (
_SHARED_POPULATION,
_build_evaluation_metadata,
)


def test_shared_outcomes_identify_models_in_one_population():
probs = {
"Model A": np.array([0.1, 0.9]),
"Model B": np.array([0.2, 0.8]),
}
reals = np.array([0, 1])
times = np.array([5.0, 10.0])

metadata = _build_evaluation_metadata(probs, reals, times)

assert set(metadata) == set(probs)
assert metadata["Model A"].reference_group == "Model A"
assert metadata["Model A"].evaluation == "Model A"
assert metadata["Model A"].model == "Model A"
assert metadata["Model A"].population == _SHARED_POPULATION
assert metadata["Model B"].model == "Model B"
assert metadata["Model B"].population == _SHARED_POPULATION


def test_keyed_outcomes_identify_populations_without_guessing_model_identity():
probs = {
"Population A": np.array([0.1, 0.9]),
"Population B": np.array([0.2, 0.8]),
}
reals = {
"Population A": np.array([0, 1]),
"Population B": np.array([1, 0]),
}
times = {
"Population A": np.array([5.0, 10.0]),
"Population B": np.array([4.0, 9.0]),
}

metadata = _build_evaluation_metadata(probs, reals, times)

assert metadata["Population A"].reference_group == "Population A"
assert metadata["Population A"].evaluation == "Population A"
assert metadata["Population A"].model is None
assert metadata["Population A"].population == "Population A"
assert metadata["Population B"].model is None
assert metadata["Population B"].population == "Population B"


def test_paired_labels_remain_compatibility_evaluation_labels():
pair = "Model A @ Population A"
probs = {pair: np.array([0.1, 0.9])}
reals = {pair: np.array([0, 1])}
times = {pair: np.array([5.0, 10.0])}

metadata = _build_evaluation_metadata(probs, reals, times)[pair]

assert metadata.reference_group == pair
assert metadata.evaluation == pair
assert metadata.population == pair
assert metadata.model is None
Loading