Source code for negmas.outcomes.ordinal_issue

from __future__ import annotations
import random
from abc import ABC, abstractmethod
from typing import Any, Generator

from negmas import warnings
from negmas.helpers import unique_name
from negmas.helpers.numeric import is_float_type, is_int_type, sample
from negmas.outcomes.base_issue import DiscreteIssue, Issue

__all__ = ["OrdinalIssue", "DiscreteOrdinalIssue"]


def generate_values(n: int) -> list[str]:
    if n > 1000_000:
        warnings.warn(
            f"You are creating an OrdinalIssue with {n} items. This is too large."
            f"Consider using something like ContiguousIssue if possible",
            warnings.NegmasMemoryWarning,
        )
    width = len(str(n))
    return list(f"{_:0{width}d}" for _ in range(n))


[docs] class OrdinalIssue(Issue, ABC): """ An `Issue` that have some defined ordering of outcomes but not necessarily a meaningful difference function between its values. """
[docs] @abstractmethod def ordered_value_generator( # type: ignore self, n: int = 10, grid=True, compact=False, endpoints=True ) -> Generator[Any, None, None]: ...
[docs] class DiscreteOrdinalIssue(DiscreteIssue, OrdinalIssue): # type: ignore """ A `DiscreteIssue` that have some defined ordering of outcomes but not necessarily a meaningful difference function between its values. """ def __init__(self, values, name=None) -> None: """ `values` can be an integer and in this case, values will be strings """ super().__init__(values, name) if isinstance(values, int): values = generate_values(values) else: values = list(values) types = {type(_) for _ in values if _ is not None} if len(types) == 1: type_ = list(types)[0] elif all(is_int_type(_) for _ in types): type_ = int elif all(is_float_type(_) for _ in types): type_ = float else: raise TypeError( f"Found the following types in the list of values for an " f"ordinal issue ({types}). Can only have one type. Try " f"CategoricalIssue" ) self._value_type = type_ self._n_values = len(values) self.min_value, self.max_value = min(values), max(values) def _to_xml_str(self, indx): output = f' <issue etype="discrete" index="{indx + 1}" name="{self.name}" type="discrete" vtype="discrete">\n' for i, v in enumerate(self._values): output += f' <item index="{i + 1}" value="{v}" cost="0" description="{v}">\n </item>\n' output += " </issue>\n" return output @property def all(self) -> Generator[Any, None, None]: yield from self._values # type: ignore
[docs] def rand_invalid(self): # type: ignore """Pick a random *invalid* value""" if self.is_float(): return random.random() * self.max_value + self.max_value * 1.1 if self.is_integer(): return random.randint(self.max_value + 1, self.max_value * 2) return unique_name("") + str(random.choice(self._values)) + unique_name("")
[docs] def ordered_value_generator( # type: ignore self, n: int = 10, grid=True, compact=False, endpoints=True ) -> Generator[Any, None, None]: """ A generator that generates at most `n` values (in order) Remarks: - This function returns a generator for the case when the number of values is very large. - If you need a list then use something like: >>> from negmas.outcomes import make_issue >>> list(make_issue(5).ordered_value_generator()) [0, 1, 2, 3, 4] >>> list( ... int(10 * _) for _ in make_issue((0.0, 1.0)).ordered_value_generator(11) ... ) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] """ yield from ( self._values[_] for _ in sample( self.cardinality, n, grid=grid, compact=compact, endpoints=endpoints ) )