Source code for mutwo.common_generators.koenig

"""Algorithms which are related to German-Dutch composer G. M. Koenig."""

import numpy as np  # type: ignore
import ranges  # type: ignore

from mutwo import common_utilities
from mutwo import core_events

__all__ = ("Tendency",)


[docs]class Tendency(object): """Tendency offers an interface for dynamically changing minima / maxima areas. :param minima_curve: The curve which describes the smallest allowed value over the time axis. :type minima_curve: core_events.Envelope :param maxima_curve: The curve which describes the biggest allowed value over the time axis. :type maxima_curve: core_events.Envelope :param random_seed: The random seed which shall be set. :type random_seed: int The class is based on Gottfried Michael Koenigs algorithm of "Tendenz-Masken" in his program "Projekt 2" where those minima / maxima areas represent probability fields. **Example:** >>> import core_events >>> from mutwo.generators import koenig >>> minima_curve = core_events.Envelope.from_points((0, 0), (1, 1), (2, 0)) >>> maxima_curve = core_events.Envelope.from_points((0, 1), (1, 2), (2, 3)) >>> my_tendency = koenig.Tendency(minima_curve, maxima_curve) >>> my_tendency.value_at(0.5) 0.6456692551041303 >>> my_tendency.value_at(0.5) 0.9549270045140213 """ def __init__( self, minima_curve: core_events.Envelope, maxima_curve: core_events.Envelope, random_seed: int = 100, ): self._assert_curves_are_valid(minima_curve, maxima_curve) self._minima_curve = minima_curve self._maxima_curve = maxima_curve self._random = np.random.default_rng(random_seed) def __repr__(self) -> str: return f"{type(self).__name__}({self.minima_curve}, {self.maxima_curve})" @staticmethod def _assert_curves_are_valid( minima_curve: core_events.Envelope, maxima_curve: core_events.Envelope ): """Helper method that asserts the curves are a valid min/max pair.""" # make sure both curves have equal duration if minima_curve.duration != maxima_curve.duration: raise common_utilities.UnequalEnvelopeDurationError( minima_curve, maxima_curve ) # It would be better if we could compare all local extrema. # But there is no public function in core_events.Envelope yet. # Even if there would be a function to find local extrema it # is uncertain if this would be faster. # We want to make sure that at any time point minima_curve # < maxima_curve. point_to_compare_tuple = ( minima_curve.absolute_time_tuple + maxima_curve.absolute_time_tuple + (0, minima_curve.duration) ) for time in point_to_compare_tuple: if not minima_curve.value_at(time) < maxima_curve.value_at(time): raise common_utilities.InvalidMinimaCurveAndMaximaCurveCombination( f"At time '{time}' 'minima_curve' isn't smaller " "than 'maxima_curve'!" ) @property def minima_curve(self) -> core_events.Envelope: return self._minima_curve @minima_curve.setter def minima_curve(self, minima_curve: core_events.Envelope) -> core_events.Envelope: self._assert_curves_are_valid(minima_curve, self.maxima_curve) self._minima_curve = minima_curve @property def maxima_curve(self) -> core_events.Envelope: return self._maxima_curve @maxima_curve.setter def maxima_curve(self, maxima_curve: core_events.Envelope) -> core_events.Envelope: self._assert_curves_are_valid(self.minima_curve, maxima_curve) self._maxima_curve = maxima_curve
[docs] def range_at(self, time: float) -> ranges.Range: """Get minima / maxima range at requested time.""" return ranges.Range( self.minima_curve.value_at(time), self.maxima_curve.value_at(time) )
[docs] def value_at(self, time: float) -> float: """Get value at requested time.""" range_at_time = self.range_at(time) return self._random.uniform(range_at_time.start, range_at_time.end)