from __future__ import annotations
import math
import numbers
import random
from typing import Generator
import numpy as np
from negmas.outcomes.base_issue import Issue
from negmas.outcomes.range_issue import RangeIssue
from negmas.serialization import PYTHON_CLASS_IDENTIFIER
from .common import DEFAULT_LEVELS
__all__ = ["ContinuousIssue"]
[docs]
class ContinuousIssue(RangeIssue):
"""
A `RangeIssue` representing a continous range of real numbers with finite limits.
"""
def __init__(self, values, name=None, n_levels=DEFAULT_LEVELS) -> None:
super().__init__(values, name=name)
self._n_levels = n_levels
self.delta = (self.max_value - self.min_value) / self._n_levels
def _to_xml_str(self, indx):
return (
f' <issue etype="real" index="{indx + 1}" name="{self.name}" type="real" vtype="real">\n'
f' <range lowerbound="{self._values[0]}" upperbound="{self._values[1]}"></range>\n </issue>\n'
)
[docs]
def to_dict(self, python_class_identifier=PYTHON_CLASS_IDENTIFIER):
d = super().to_dict(python_class_identifier=python_class_identifier)
d["n_levels"] = self._n_levels
return d
@property
def cardinality(self) -> int | float:
return float("inf")
@property
def type(self) -> str:
return "continuous"
[docs]
def is_continuous(self) -> bool:
return True
[docs]
def is_uncountable(self) -> bool:
return True
[docs]
def rand(self) -> float:
"""Picks a random valid value."""
return random.random() * (self._values[1] - self._values[0]) + self._values[0] # type: ignore
[docs]
def ordered_value_generator(
self,
n: int | float | None = DEFAULT_LEVELS,
grid=True,
compact=False,
endpoints=True,
) -> Generator[float, None, None]:
if n is None or not math.isfinite(n):
raise ValueError(f"Cannot generate {n} values from issue: {self}")
n = int(n)
if grid:
yield from np.linspace(
self._values[0], self._values[1], num=n, endpoint=endpoints
).tolist()
return
if endpoints:
yield from (
[self._values[0]]
+ (
(self._values[1] - self._values[0]) * np.random.rand(n - 2)
+ self._values[0]
).tolist()
+ [self._values[1]]
)
yield from (
(self._values[1] - self._values[0]) * np.random.rand(n) + self._values[0]
).tolist()
[docs]
def value_generator(
self,
n: int | float | None = DEFAULT_LEVELS,
grid=True,
compact=False,
endpoints=True,
) -> Generator[float, None, None]:
return self.ordered_value_generator(n, grid, compact, endpoints)
[docs]
def rand_outcomes(
self, n: int, with_replacement=False, fail_if_not_enough=False
) -> list[float]:
if with_replacement:
return (
np.random.rand(n) * (self._values[1] - self._values[0])
+ self._values[0]
).tolist()
return np.linspace(
self._values[0], self._values[1], num=n, endpoint=True
).tolist()
[docs]
def rand_invalid(self):
"""Pick a random *invalid* value"""
return (
random.random() * (self.max_value - self.min_value) + self.max_value * 1.1
)
@property
def all(self):
raise ValueError("Cannot enumerate all values of a continuous issue")
[docs]
def value_at(self, index: int):
v = self.min_value + self.delta * index
if v > self.max_value:
return IndexError(index)
return v
[docs]
def contains(self, issue: Issue) -> bool:
"""Checks weather this issue contains the input issue (i.e. every value in the input issue is in this issue)"""
return (
issubclass(issue.value_type, numbers.Real)
and issue.min_value >= self.min_value
and issue.max_value <= self.max_value
)