Skip to content

Commit

Permalink
quality of life improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
yasserfarouk committed Jan 24, 2025
1 parent 2db2382 commit c224450
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 121 deletions.
26 changes: 14 additions & 12 deletions src/negmas/gb/components/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@

@define
class GBComponent(Component):
_negotiator: GBNegotiator | None = field(default=None, kw_only=True)
_negotiator: GBNegotiator | None = field(default=None, kw_only=True) # type: ignore

def before_proposing(self, state: GBState):
def before_proposing(self, state: GBState, dest: str | None = None):
"""
Called before proposing
"""

def after_proposing(self, state: GBState, offer: Outcome | None):
def after_proposing(
self, state: GBState, offer: Outcome | None, dest: str | None = None
):
"""
Called after proposing
"""
Expand Down Expand Up @@ -190,7 +192,7 @@ class RejectionPolicy(AcceptancePolicy):
a: AcceptancePolicy

def __call__(
self, state: GBState, offer: Outcome | None, source: str
self, state: GBState, offer: Outcome | None, source: str | None
) -> ResponseType:
response = self.a(state, offer, source)
if response == ResponseType.ACCEPT_OFFER:
Expand All @@ -200,16 +202,16 @@ def __call__(

@define
class OfferingPolicy(GBComponent):
_current_offer: tuple[int, str, Outcome | None] = field(
init=False, default=(-1, None)
_current_offer: tuple[int, str | None, Outcome | None] = field(
init=False, default=(-1, None, None)
)

def propose(self, state: GBState, dest: str | None = None) -> Outcome | None:
"""Propose an offer or None to refuse.
Args:
state: `GBState` giving current state of the negotiation.
source: the thread in which I am supposed to offer.
dest: the thread in which I am supposed to offer.
Returns:
The outcome being proposed or None to refuse to propose
Expand All @@ -219,14 +221,14 @@ def propose(self, state: GBState, dest: str | None = None) -> Outcome | None:
- Caching is useful when the acceptance strategy calls the offering strategy
"""
source = self.negotiator.id
if self._current_offer[0] != state.step or self._current_offer[1] != source:
offer = self(state)
self._current_offer = (state.step, source, offer)
thread = self.negotiator.id
if self._current_offer[0] != state.step or self._current_offer[1] != thread:
offer = self(state, dest=dest)
self._current_offer = (state.step, thread, offer)
return self._current_offer[2]

@abstractmethod
def __call__(self, state: GBState) -> Outcome | None:
def __call__(self, state: GBState, dest: str | None = None) -> Outcome | None:
...

def __and__(self, s: OfferingPolicy):
Expand Down
37 changes: 21 additions & 16 deletions src/negmas/gb/components/offering.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@
"OfferBest",
"TFTOfferingPolicy",
"MiCROOfferingPolicy",
"TimeBasedOfferingStrategy",
"TimeBasedOfferingPolicy",
]


@define
class TimeBasedOfferingStrategy(OfferingPolicy):
class TimeBasedOfferingPolicy(OfferingPolicy):
curve: PolyAspiration = field(factory=lambda: PolyAspiration(1.0, "boulware"))
stochastic: bool = False

Expand All @@ -61,7 +61,7 @@ def on_preferences_changed(self, changes: list[PreferencesChange]):
)
self.sorter.init()

def __call__(self, state: GBState):
def __call__(self, state: GBState, dest: str | None = None):
assert self.negotiator.ufun is not None
asp = self.curve.utility_at(state.relative_time)
mn, mx = self.sorter.minmax()
Expand Down Expand Up @@ -128,7 +128,7 @@ def best_offer_so_far(self) -> Outcome | None:
def ready_to_concede(self) -> bool:
return len(self._sent) <= len(self._received)

def __call__(self, state: GBState) -> Outcome | None:
def __call__(self, state: GBState, dest: str | None = None) -> Outcome | None:
outcome = self.next_offer()
assert self.sorter
assert self.negotiator.ufun
Expand Down Expand Up @@ -180,7 +180,7 @@ def on_preferences_changed(self, changes: list[PreferencesChange]):
self.next_indx = 0
self._repeating = False

def __call__(self, state: GBState) -> Outcome | None:
def __call__(self, state: GBState, dest: str | None = None) -> Outcome | None:
if (
self._repeating
or not self.negotiator
Expand Down Expand Up @@ -253,7 +253,7 @@ def on_negotiation_start(self, state) -> None:
self._irrational_index = self.negotiator.nmi.n_outcomes - 1 # type: ignore
return super().on_negotiation_start(state)

def __call__(self, state: GBState) -> Outcome | None:
def __call__(self, state: GBState, dest: str | None = None) -> Outcome | None:
if not self.negotiator or not self.negotiator.ufun or not self.negotiator.nmi:
return self._last_offer
if self._repeating:
Expand Down Expand Up @@ -314,7 +314,7 @@ def on_preferences_changed(self, changes: list[PreferencesChange]):
super().on_preferences_changed(changes)
self.partner_ufun.on_preferences_changed(changes)

def __call__(self, state: GBState):
def __call__(self, state: GBState, dest: str | None = None):
if not self.negotiator or not self.negotiator.ufun:
return None
partner_u = (
Expand Down Expand Up @@ -358,7 +358,7 @@ def on_preferences_changed(self, changes: list[PreferencesChange]):
return
_, self._best = self.negotiator.ufun.extreme_outcomes()

def __call__(self, state: GBState) -> Outcome | None:
def __call__(self, state: GBState, dest: str | None = None) -> Outcome | None:
return self._best


Expand Down Expand Up @@ -393,7 +393,7 @@ def on_preferences_changed(self, changes: list[PreferencesChange]):
top_f = inverter.within_fractions((0.0, self.fraction))
self._top = list(set(top_k + top_f))

def __call__(self, state: GBState) -> Outcome | None:
def __call__(self, state: GBState, dest: str | None = None) -> Outcome | None:
if not self.negotiator or not self.negotiator.ufun:
return None
if self._top is None:
Expand All @@ -409,7 +409,7 @@ class NoneOfferingPolicy(OfferingPolicy):
Always offers `None` which means it never gets an agreement.
"""

def __call__(self, state: GBState) -> Outcome | None:
def __call__(self, state: GBState, dest: str | None = None) -> Outcome | None:
return None


Expand All @@ -419,7 +419,7 @@ class RandomOfferingPolicy(OfferingPolicy):
Always offers `None` which means it never gets an agreement.
"""

def __call__(self, state: GBState) -> Outcome | None:
def __call__(self, state: GBState, dest: str | None = None) -> Outcome | None:
if not self.negotiator or not self.negotiator.nmi:
return None
return self.negotiator.nmi.random_outcome()
Expand All @@ -435,7 +435,9 @@ class LimitedOutcomesOfferingPolicy(OfferingPolicy):
prob: list[float] | None = None
p_ending: float = 0.0

def __call__(self, state: GBState, retry=False) -> Outcome | None:
def _run(
self, state: GBState, dest: str | None = None, second_trial: bool = False
) -> Outcome | None:
if not self.negotiator or not self.negotiator.nmi:
return None
if random.random() < self.p_ending - 1e-7:
Expand All @@ -451,12 +453,15 @@ def __call__(self, state: GBState, retry=False) -> Outcome | None:
s += p
if r <= s:
return w
if retry:
if second_trial:
return None
if s > 0.999:
return self.outcomes[-1]
self.prob = [_ / s for _ in self.prob]
return self(state, True)
return self._run(state, dest, True)

def __call__(self, state: GBState, dest: str | None = None) -> Outcome | None:
return self._run(state, dest)


@define
Expand All @@ -467,7 +472,7 @@ class NegotiatorOfferingPolicy(OfferingPolicy):

proposer: GBNegotiator = field(kw_only=True)

def __call__(self, state: GBState) -> Outcome | None:
def __call__(self, state: GBState, dest: str | None = None) -> Outcome | None:
r = self.proposer.propose(state)
if isinstance(r, ExtendedOutcome):
return r.outcome
Expand Down Expand Up @@ -503,7 +508,7 @@ def decide(
Called to make a final decsision given the decisions of the stratgeis with indices `indices` (see `filter` for filtering rules)
"""

def __call__(self, state: GBState) -> Outcome | None:
def __call__(self, state: GBState, dest: str | None = None) -> Outcome | None:
selected, selected_indices = [], []
for i, s in enumerate(self.strategies):
response = s.propose(state)
Expand Down
22 changes: 10 additions & 12 deletions src/negmas/gb/negotiators/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,8 @@ def on_partner_ended(self, partner: str):
"""
Called when a partner ends the negotiation.
Note that the negotiator owning this component may never receive this
offer.
This is only receivd if the mechanism is sending notifications
Note that the negotiator owning this component may never receive this
offer. This is only received if the mechanism is sending notifications
on every offer.
"""

Expand All @@ -154,7 +153,7 @@ def __call__(self, state: SAOState, dest: str | None = None) -> SAOResponse:
Args:
state: `SAOState` giving current state of the negotiation.
partner: The partner to respond to with a counter offer.
dest: The partner to respond to with a counter offer (for AOP, SAOP, MAOP this can safely be ignored).
Returns:
Tuple[ResponseType, Outcome]: The response to the given offer with a counter offer if the response is REJECT
Expand All @@ -177,12 +176,14 @@ def __call__(self, state: SAOState, dest: str | None = None) -> SAOResponse:
ResponseType.REJECT_OFFER, proposal.outcome, proposal.data
)
return SAOResponse(ResponseType.REJECT_OFFER, proposal)
response = self.respond_(state=state)
response = self.respond_(
state=state, source=state.current_proposer if state.current_proposer else ""
)
if response == ResponseType.ACCEPT_OFFER:
return SAOResponse(response, offer)
if response != ResponseType.REJECT_OFFER:
return SAOResponse(response, None)
proposal = self.propose_(state=state)
proposal = self.propose_(state=state, dest=dest)
if isinstance(proposal, ExtendedOutcome):
return SAOResponse(
ResponseType.REJECT_OFFER, proposal.outcome, proposal.data
Expand All @@ -194,18 +195,15 @@ def propose_(
) -> Outcome | ExtendedOutcome | None:
if not self._capabilities["propose"] or self.__end_negotiation:
return None
return self.propose(state=self._gb_state_from_sao_state(state))
return self.propose(state=self._gb_state_from_sao_state(state), dest=dest)

def respond_(self, state: SAOState) -> ResponseType:
def respond_(self, state: SAOState, source: str | None = None) -> ResponseType:
offer = state.current_offer
if self.__end_negotiation:
return ResponseType.END_NEGOTIATION
self.__received_offer[state.current_proposer] = offer

return self.respond(
state=self._gb_state_from_sao_state(state),
source=state.current_proposer if state.current_proposer else "",
)
return self.respond(state=self._gb_state_from_sao_state(state), source=source)

def _gb_state_from_sao_state(self, state: SAOState) -> GBState:
if isinstance(state, GBState):
Expand Down
2 changes: 1 addition & 1 deletion src/negmas/gb/negotiators/limited.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def __init__(
if acceptance_probabilities is None:
acceptance_probabilities = [1.0] * len(acceptable_outcomes)
acceptance = LimitedOutcomesAcceptancePolicy(
prob=dict(zip(acceptable_outcomes, acceptance_probabilities)),
prob=dict(zip(acceptable_outcomes, acceptance_probabilities)), # type: ignore
p_ending=p_ending,
)
super().__init__(acceptance=acceptance, offering=offering, **kwargs)
Expand Down
12 changes: 6 additions & 6 deletions src/negmas/gb/negotiators/modular/boa.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@

if TYPE_CHECKING:
from ...components import GBComponent
from ...negotiators.base import GBNegotiator

__all__ = ["make_boa", "BOANegotiator"]


def make_boa(
acceptance: AcceptancePolicy | GBNegotiator | None = None,
offering: OfferingPolicy | GBNegotiator | None = None,
acceptance: AcceptancePolicy | None = None,
offering: OfferingPolicy | None = None,
model: Model | None = None,
extra_components: list[GBComponent] | None = None,
extra_component_names: list[str] | None = None,
Expand Down Expand Up @@ -52,14 +51,14 @@ class BOANegotiator(MAPNegotiator):
3. An `OfferingStrategy` That is used for generating offers.
For all callbacks, partner-model is called first, followed by the acceptance strategy followed by the offering strategy
For all callbacks, partner-model is called first, followed by the acceptance strategy followed by the offering strategy.
"""

def __init__(
*args,
acceptance: AcceptancePolicy | GBNegotiator | None = None,
offering: OfferingPolicy | GBNegotiator | None = None,
acceptance: AcceptancePolicy | None = None,
offering: OfferingPolicy | None = None,
model: Model | None = None,
extra_components: list[GBComponent] | None = None,
extra_component_names: list[str] | None = None,
Expand All @@ -73,5 +72,6 @@ def __init__(
extra_components=extra_components,
extra_component_names=extra_component_names,
type_name="BOANegotiator",
acceptance_first=False,
**kwargs,
)
Loading

0 comments on commit c224450

Please sign in to comment.