Source code for citoolkit.improvisers.classic_ci

""" Contains the ClassicCI class, which acts as an improviser
for the Classic CI problem.
"""

from __future__ import annotations
from typing import Tuple

import random

from citoolkit.improvisers.improviser import Improviser, InfeasibleImproviserError
from citoolkit.specifications.spec import Spec

[docs]class ClassicCI(Improviser): """ An improviser for the "Control Improvisation" problem. :param hard_constraint: A specification that must accept all improvisations :param soft_constraint: A specification that must accept improvisations with probability 1 - epsilon. :param length_bounds: A tuple containing lower and upper bounds on the length of a generated word. :param epsilon: The allowed tolerance with which we can not satisfy the soft constraint. :param prob_bounds: A tuple containing lower and upper bounds on the probability with which we can generate a word. :raises ValueError: If passed parameters are not of the correct type. :raises InfeasibleImproviserError: If the resulting improvisation problem is not feasible. """ def __init__(self, hard_constraint: Spec, soft_constraint: Spec, length_bounds: Tuple[int, int], \ epsilon: float, prob_bounds: Tuple[float, float]) -> None: # Checks that parameters are well formed if not isinstance(hard_constraint, Spec): raise ValueError("The hard_constraint parameter must be a member of the Spec class.") if not isinstance(soft_constraint, Spec): raise ValueError("The soft_constraint parameter must be a member of the Spec class.") if (len(length_bounds) != 2) or (length_bounds[0] < 0) or (length_bounds[0] > length_bounds[1]): raise ValueError("The length_bounds parameter should contain two integers, with 0 <= length_bounds[0] <= length_bounds[1].") if epsilon < 0 or epsilon > 1: raise ValueError("The epsilon parameter should be between 0 and 1 inclusive.") if (len(prob_bounds) != 2) or (prob_bounds[0] < 0) or (prob_bounds[0] > prob_bounds[1]) or (prob_bounds[1] > 1): raise ValueError("The prob_bounds parameter should contain two floats, with 0 <= prob_bounds[0] <= prob_bounds[1] <= 1.") # Initializes improviser values. In this case i refers to I\A instead of I self.length_bounds = length_bounds self.i_spec = hard_constraint - soft_constraint self.a_spec = hard_constraint & soft_constraint i_size = self.i_spec.language_size(*self.length_bounds) a_size = self.a_spec.language_size(*self.length_bounds) min_prob, max_prob = prob_bounds self.i_prob = max(1 - max_prob * a_size, min_prob * i_size) self.a_prob = 1 - self.i_prob # Checks that improviser is feasible. If not raise an InfeasibleImproviserError. if (i_size + a_size) < (1/max_prob) or (min_prob != 0 and (i_size + a_size) > (1/min_prob)): if min_prob == 0: inv_min_prob = float("inf") else: inv_min_prob = 1/min_prob raise InfeasibleImproviserError("Violation of condition 1/max_prob <= (i_size + a_size) <= 1/min_prob. Instead, " \ + str(1/max_prob) + " <= " + str(i_size + a_size) + " <= " + str(inv_min_prob)) if (1 - epsilon)/max_prob > a_size: raise InfeasibleImproviserError("Violation of condition (1 - epsilon)/max_prob <= a_size. Instead, " \ + str((1 - epsilon)/max_prob) + " <= " + str(a_size)) if min_prob != 0 and epsilon/min_prob < i_size: raise InfeasibleImproviserError("Violation of condition epsilon/min_prob >= i_size. Instead, " \ + str(epsilon/max_prob) + " >= " + str(i_size))
[docs] def improvise(self) -> Tuple[str,...]: """ Improvise a single word. :returns: A single improvised word. """ spec_choice = random.random() if spec_choice < self.i_prob: return self.i_spec.sample(*self.length_bounds) else: return self.a_spec.sample(*self.length_bounds)