from __future__ import annotations
from typing import TYPE_CHECKING
from attrs import define, field
from negmas.preferences import RankOnlyUtilityFunction
from negmas.preferences.base_ufun import BaseUtilityFunction
from negmas.preferences.mixins import StationaryMixin
from ..base import GBComponent
if TYPE_CHECKING:
from negmas import PreferencesChange, Value
from negmas.outcomes import Outcome
__all__ = [
"UFunModel",
"FrequencyUFunModel",
"FrequencyLinearUFunModel",
"ZeroSumModel",
]
SENTINAL = object()
[docs]
class UFunModel(GBComponent, BaseUtilityFunction):
"""
A `SAOComponent` that can model the opponent's utility function.
Classes implementing this ufun-model, must implement the abstract `eval()`
method to return the utility value of an outcome. They can use any callbacks
available to `SAOComponent` to update the model.
"""
[docs]
@define
class ZeroSumModel(StationaryMixin, UFunModel):
"""
Assumes a zero-sum negotiation (i.e. $u_o$ = $-u_s$ )
Remarks:
- Because some negotiators do not work well with negative ufun values, we return (max - u(w)) instead of (- u(w))
"""
above_reserve: bool = True
rank_only: bool = False
_effective_ufun: BaseUtilityFunction = field(init=False, default=SENTINAL)
[docs]
def on_preferences_changed(self, changes: list[PreferencesChange]):
if not self.negotiator or not self.negotiator.ufun:
raise ValueError("Negotiator or ufun are not known")
self._effective_ufun = (
self.negotiator.ufun
if not self.rank_only
else RankOnlyUtilityFunction(self.negotiator.ufun)
)
[docs]
def eval(self, offer: Outcome) -> Value:
uo = self._effective_ufun.eval_normalized(offer, self.above_reserve) * -1 + 1.0
mn, mx = self._effective_ufun.minmax(above_reserve=False)
return uo * (mx - mn) + mn
[docs]
def eval_normalized(
self,
offer: Outcome | None,
above_reserve: bool = True,
expected_limits: bool = True,
) -> Value:
if offer is None:
return 0.0
return (
self._effective_ufun.eval_normalized(offer, above_reserve, expected_limits)
* -1
+ 1.0
)
[docs]
class FrequencyUFunModel(UFunModel):
"""
A `PartnerUfunModel` that uses a simple frequency-based model of the opponent offers.
"""
[docs]
def eval(self, offer: Outcome) -> Value:
raise NotImplementedError()
[docs]
class FrequencyLinearUFunModel(UFunModel):
"""
A `PartnerUfunModel` that uses a simple frequency-based model of the opponent offers assuming the ufun is `LinearAdditiveUtilityFunction` .
"""
[docs]
def eval(self, offer: Outcome) -> Value:
raise NotImplementedError()