Source code for negmas.negotiators.helpers

from __future__ import annotations
import math
from abc import abstractmethod
from typing import Literal, Protocol, runtime_checkable

__all__ = ["TimeCurve", "Aspiration", "PolyAspiration", "ExpAspiration"]


[docs] @runtime_checkable class TimeCurve(Protocol): """ Models a time-curve mapping relative timge (going from 0.0 to 1.0) to a utility range to use """
[docs] @abstractmethod def utility_range(self, t: float) -> tuple[float, float]: pass
[docs] @runtime_checkable class Aspiration(TimeCurve, Protocol): """ A monotonically decreasing time-curve """
[docs] @abstractmethod def utility_at(self, t: float) -> float: pass
[docs] def utility_range(self, t: float) -> tuple[float, float]: return self.utility_at(t), 1.0
[docs] class ExpAspiration(Aspiration): """ An exponential conceding curve Args: max_aspiration: The aspiration level to start from (usually 1.0) aspiration_type: The aspiration type. Can be a string ("boulware", "linear", "conceder") or a number giving the exponent of the aspiration curve. """ def __init__( self, max_aspiration: float, aspiration_type: Literal["boulware"] | Literal["conceder"] | Literal["linear"] | float, ): self.max_aspiration = max_aspiration self.aspiration_type = aspiration_type self.exponent = 1.0 if isinstance(aspiration_type, int): self.exponent = float(aspiration_type) elif isinstance(aspiration_type, float): self.exponent = aspiration_type elif aspiration_type == "boulware": self.exponent = 0.125 elif aspiration_type == "linear": self.exponent = 0.725 elif aspiration_type == "conceder": self.exponent = 4.0 else: raise ValueError(f"Unknown aspiration type {aspiration_type}") self._denominator = math.exp(1) - 1
[docs] def utility_at(self, t: float) -> float: """ The aspiration level Args: t: relative time (a number between zero and one) Returns: aspiration level """ if t is None: raise ValueError( "Aspiration negotiators cannot be used in negotiations with no time or #steps limit!!" ) return ( self.max_aspiration * (math.exp(math.pow(1 - t, self.exponent)) - 1) / self._denominator )
[docs] class PolyAspiration(Aspiration): """ A polynomially conceding curve Args: max_aspiration: The aspiration level to start from (usually 1.0) aspiration_type: The aspiration type. Can be a string ("boulware", "linear", "conceder") or a number giving the exponent of the aspiration curve. """ def __init__( self, max_aspiration: float, aspiration_type: Literal["boulware"] | Literal["conceder"] | Literal["linear"] | Literal["hardheaded"] | float, ): self.max_aspiration = max_aspiration self.aspiration_type = aspiration_type self.exponent = 1.0 if isinstance(aspiration_type, int): self.exponent = float(aspiration_type) elif isinstance(aspiration_type, float): self.exponent = aspiration_type elif aspiration_type == "boulware": self.exponent = 4.0 elif aspiration_type == "linear": self.exponent = 1.0 elif aspiration_type == "conceder": self.exponent = 0.25 elif aspiration_type == "hardheaded": self.exponent = float("inf") else: raise ValueError(f"Unknown aspiration type {aspiration_type}")
[docs] def utility_at(self, t: float) -> float: """ The aspiration level Args: t: relative time (a number between zero and one) Returns: aspiration level """ if t is None: raise ValueError( "Aspiration negotiators cannot be used in negotiations with no time or #steps limit!!" ) return self.max_aspiration * (1.0 - math.pow(t, self.exponent))