Source code for pyetsimul.simulation.composed_variation

"""Composed parameter variations for complex experiment designs."""

import math
from collections.abc import Generator, Iterable
from typing import Any

from .core import ParameterVariation


[docs] class ComposedVariation(ParameterVariation): """Combines multiple parameter variations into a single experiment."""
[docs] def __init__(self, variations: list[ParameterVariation], param_name: str = "composed") -> None: """Initialize composed variation. Args: variations: List of parameter variations to combine param_name: Name for the composed parameter """ super().__init__(param_name) self.variations = variations if not variations: raise ValueError("Must provide at least one variation")
@property def description(self) -> str: """Get a description of the composed variation.""" names = [v.__class__.__name__ for v in self.variations] return f"Composed({', '.join(names)})" def __len__(self) -> int: """Return the total number of combinations (Cartesian product).""" if not self.variations: return 0 return math.prod(len(v) for v in self.variations)
[docs] def generate_values(self) -> Iterable[dict[str, Any]]: """Generate Cartesian product of all variation values using a generator.""" if not self.variations: return all_values = [(v.param_name, v.generate_values()) for v in self.variations] yield from self._generate_combinations(all_values, {}, 0)
[docs] def describe(self) -> str: """Return a human-readable description of the composed variations.""" descriptions = [var.describe() for var in self.variations] return f"Combined: {' + '.join(descriptions)}"
def _generate_combinations( self, all_values: list[tuple[str, list[Any]]], current_combo: dict[str, Any], index: int ) -> Generator[dict[str, Any], None, None]: """Recursively generate all combinations.""" if index == len(all_values): yield current_combo.copy() return param_name, values = all_values[index] for value in values: current_combo[param_name] = value yield from self._generate_combinations(all_values, current_combo, index + 1) del current_combo[param_name]
[docs] class SequentialVariation(ParameterVariation): """Applies variations sequentially rather than in combination."""
[docs] def __init__(self, variations: list[ParameterVariation], param_name: str = "sequential") -> None: """Initialize sequential variation. Args: variations: List of parameter variations to apply sequentially param_name: Name for the sequential parameter """ super().__init__(param_name) self.variations = variations if not variations: raise ValueError("Must provide at least one variation")
[docs] def describe(self) -> str: """Return a human-readable description of the sequential variations.""" descriptions = [var.describe() for var in self.variations] return f"Sequential: {' → '.join(descriptions)}"
def __len__(self) -> int: """Return the total number of values (sum of lengths).""" return sum(len(v) for v in self.variations)
[docs] def generate_values(self) -> Iterable[dict[str, Any]]: """Generate sequential values from all variations.""" for i, variation in enumerate(self.variations): for value in variation.generate_values(): yield {"variation_index": i, "variation_name": variation.param_name, "value": value}