Skip to content

Commit

Permalink
More consistent exception ignoring and naming
Browse files Browse the repository at this point in the history
- Base Mechanism class now can ignore exceptions in initialization of
  agents
- More consistent naming of method involving negotiators
  • Loading branch information
yasserfarouk committed Apr 1, 2024
1 parent 03d9cf0 commit b1305b8
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 78 deletions.
8 changes: 4 additions & 4 deletions negmas/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,13 @@ class NegotiatorMechanismInterface:
n_steps: int | None
"""The allowed number of steps for this negotiation. None indicates infinity"""
dynamic_entry: bool
"""Whether it is allowed for agents to enter/leave the negotiation after it starts"""
max_n_agents: int | None
"""Maximum allowed number of agents in the session. None indicates no limit"""
"""Whether it is allowed for negotiators to enter/leave the negotiation after it starts"""
max_n_negotiators: int | None
"""Maximum allowed number of negotiators in the session. None indicates no limit"""
_mechanism: Mechanism = field(alias="_mechanism")
"""A reference to the mechanism. MUST NEVER BE USED BY NEGOTIATORS. **must be treated as a private member**"""
annotation: dict[str, Any] = field(default=dict)
"""An arbitrary annotation as a `dict[str, Any]` that is always available for all agents"""
"""An arbitrary annotation as a `dict[str, Any]` that is always available for all negotiators"""

# def __copy__(self):
# return NegotiatorMechanismInterface(**vars(self))
Expand Down
2 changes: 1 addition & 1 deletion negmas/concurrent/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ def _get_ami(
step_time_limit=self.nmi.step_time_limit,
n_steps=self.nmi.n_steps,
dynamic_entry=self.nmi.dynamic_entry,
max_n_agents=self.nmi.max_n_agents,
max_n_agents=self.nmi.max_n_negotiators,
annotation=self.nmi.annotation,
parent=self,
negotiator=negotiator,
Expand Down
136 changes: 74 additions & 62 deletions negmas/mechanisms.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class MechanismStepResult(Generic[TState]):
completed: bool = True
"""Whether the current round is completed or not."""
broken: bool = False
"""True only if END_NEGOTIATION was selected by one agent."""
"""True only if END_NEGOTIATION was selected by one negotiator."""
timedout: bool = False
"""True if a timeout occurred."""
agreement: Outcome | None = None
Expand Down Expand Up @@ -120,8 +120,8 @@ class Mechanism(
pend: Probability of ending the negotiation at any step
pend_per_second: Probability of ending the negotiation every second
hidden_time_limit: Number of real seconds allowed but not visilbe to the negotiators
max_n_agents: Maximum allowed number of agents
dynamic_entry: Allow agents to enter/leave negotiations between rounds
max_n_negotiators: Maximum allowed number of negotiators.
dynamic_entry: Allow negotiators to enter/leave negotiations between rounds
cache_outcomes: If true, a list of all possible outcomes will be cached
max_cardinality: The maximum allowed number of outcomes in the cached set
annotation: Arbitrary annotation
Expand Down Expand Up @@ -154,7 +154,7 @@ def __init__(
step_time_limit: float | None = None,
negotiator_time_limit: float | None = None,
hidden_time_limit: float = float("inf"),
max_n_agents: int | None = None,
max_n_negotiators: int | None = None,
dynamic_entry=False,
annotation: dict[str, Any] | None = None,
nmi_factory: type[TNMI] = NegotiatorMechanismInterface,
Expand All @@ -170,13 +170,15 @@ def __init__(
id: str | None = None,
type_name: str | None = None,
verbosity: int = 0,
ignore_negotiator_exceptions=False,
):
check_one_and_only(outcome_space, issues, outcomes)
outcome_space = ensure_os(outcome_space, issues, outcomes)
self.__verbosity = verbosity
self._negotiator_logs: dict[str, list[dict[str, Any]]] = defaultdict(list)
super().__init__(name, id=id, type_name=type_name)

self.ignore_negotiator_exceptions = ignore_negotiator_exceptions
self._negotiator_times = defaultdict(float)
CheckpointMixin.checkpoint_init(
self,
Expand Down Expand Up @@ -220,7 +222,7 @@ def __init__(
step_time_limit=step_time_limit,
negotiator_time_limit=negotiator_time_limit,
dynamic_entry=dynamic_entry,
max_n_agents=max_n_agents,
max_n_negotiators=max_n_negotiators,
annotation=annotation if annotation is not None else dict(),
_mechanism=self,
)
Expand Down Expand Up @@ -248,8 +250,8 @@ def __init__(
self.__discrete_outcomes = None
self._extra_callbacks = extra_callbacks

self.agents_of_role = defaultdict(list)
self.role_of_agent = {}
self.negotiators_of_role = defaultdict(list)
self.role_of_negotiator = {}
# mechanisms do not differentiate between RANDOM_JAVA_PORT and ANY_JAVA_PORT.
# if either is given as the genius_port, it will fix a port and all negotiators
# that are not explicitly assigned to a port (by passing port>0 to them) will just
Expand Down Expand Up @@ -534,7 +536,7 @@ def expected_remaining_steps(self) -> int | None:
@property
def requirements(self):
"""A dictionary specifying the requirements that must be in the
capabilities of any agent to join the mechanism."""
capabilities of any negotiator to join the mechanism."""
return self._requirements

@requirements.setter
Expand Down Expand Up @@ -625,43 +627,40 @@ def is_satisfying(self, capabilities: dict) -> bool:

return True

def can_participate(self, agent: TNegotiator) -> bool:
"""Checks if the agent can participate in this type of negotiation in
def can_participate(self, negotiator: TNegotiator) -> bool:
"""Checks if the negotiator can participate in this type of negotiation in
general.
Args:
agent:
Returns:
bool: True if it can
bool: True if the negotiator can participate
Remarks:
The only reason this may return `False` is if the mechanism requires some requirements
that are not within the capabilities of the agent.
that are not within the capabilities of the negotiator.
When evaluating compatibility, the agent is considered incapable of participation if any
When evaluating compatibility, the negotiator is considered incapable of participation if any
of the following conditions hold:
* A mechanism requirement is not in the capabilities of the agent
* A mechanism requirement is in the capabilities of the agent by the values required for it
is not in the values announced by the agent.
* A mechanism requirement is not in the capabilities of the negotiator
* A mechanism requirement is in the capabilities of the negotiator by the values required for it
is not in the values announced by the negotiator.
An agent that lists a `None` value for a capability is announcing that it can work with all its
An negotiator that lists a `None` value for a capability is announcing that it can work with all its
values. On the other hand, a mechanism that lists a requirement as None announces that it accepts
any value for this requirement as long as it exist in the agent
any value for this requirement as long as it exist in the negotiator
"""
return self.is_satisfying(agent.capabilities)
return self.is_satisfying(negotiator.capabilities)

def can_accept_more_agents(self) -> bool:
def can_accept_more_negotiators(self) -> bool:
"""Whether the mechanism can **currently** accept more negotiators."""
return (
True
if self.nmi.max_n_agents is None or self._negotiators is None
else len(self._negotiators) < self.nmi.max_n_agents
if self.nmi.max_n_negotiators is None or self._negotiators is None
else len(self._negotiators) < self.nmi.max_n_negotiators
)

def can_enter(self, agent: TNegotiator) -> bool:
"""Whether the agent can enter the negotiation now."""
return self.can_accept_more_agents() and self.can_participate(agent)
def can_enter(self, negotiator: TNegotiator) -> bool:
"""Whether the negotiator can enter the negotiation now."""
return self.can_accept_more_negotiators() and self.can_participate(negotiator)

# def extra_state(self) -> dict[str, Any] | None:
# """Returns any extra state information to be kept in the `state` and `history` properties"""
Expand All @@ -687,25 +686,25 @@ def add(
role: str | None = None,
ufun: BaseUtilityFunction | None = None,
) -> bool | None:
"""Add an agent to the negotiation.
"""Add an negotiator to the negotiation.
Args:
negotiator: The agent to be added.
preferences: The utility function to use. If None, then the agent must already have a stored
negotiator: The negotiator to be added.
preferences: The utility function to use. If None, then the negotiator must already have a stored
utility function otherwise it will fail to enter the negotiation.
ufun: [depricated] same as preferences but must be a `UFun` object.
role: The role the agent plays in the negotiation mechanism. It is expected that mechanisms inheriting from
role: The role the negotiator plays in the negotiation mechanism. It is expected that mechanisms inheriting from
this class will check this parameter to ensure that the role is a valid role and is still possible for
negotiators to join on that role. Roles may include things like moderator, representative etc based
on the mechanism
Returns:
* True if the agent was added.
* False if the agent was already in the negotiation.
* None if the agent cannot be added. This can happen in the following cases:
* True if the negotiator was added.
* False if the negotiator was already in the negotiation.
* None if the negotiator cannot be added. This can happen in the following cases:
1. The capabilities of the negotiator do not match the requirements of the negotiation
2. The outcome-space of the negotiator's preferences do not contain the outcome-space of the negotiation
Expand Down Expand Up @@ -756,8 +755,8 @@ def add(
self._negotiator_map[negotiator.id] = negotiator
self._negotiator_index[negotiator.id] = len(self._negotiators) - 1
self._roles.append(role)
self.role_of_agent[negotiator.uuid] = role
self.agents_of_role[role].append(negotiator)
self.role_of_negotiator[negotiator.uuid] = role
self.negotiators_of_role[role].append(negotiator)
return True
return None

Expand All @@ -771,24 +770,37 @@ def get_negotiator_raise(self, source: str) -> Negotiator:
negotiation otherwise it raises an exception."""
return self._negotiator_map[source]

def can_leave(self, agent: Negotiator) -> bool:
"""Can the agent leave now?"""
def can_leave(self, negotiator: Negotiator) -> bool:
"""Can the negotiator leave now?"""
return (
True
if self.nmi.dynamic_entry
else not self.nmi.state.running and agent in self._negotiators
else not self.nmi.state.running and negotiator in self._negotiators
)

def remove(self, negotiator: Negotiator) -> bool | None:
"""Remove the agent from the negotiation.
def _call(self, negotiator: TNegotiator, callback: Callable, *args, **kwargs):
result = None
try:
result = callback(*args, **kwargs)
except Exception as e:
if self.ignore_negotiator_exceptions:
pass
else:
self.state.has_error = True
self.state.error_details = str(e)
self.state.erred_negotiator = negotiator.id if negotiator else ""
a = negotiator.owner if negotiator else None
self.state.erred_agent = a.id if a else ""
finally:
return result

Args:
agent:
def remove(self, negotiator: TNegotiator) -> bool | None:
"""Remove the negotiator from the negotiation.
Returns:
* True if the agent was removed.
* False if the agent was not in the negotiation already.
* None if the agent cannot be removed.
* True if the negotiator was removed.
* False if the negotiator was not in the negotiation already.
* None if the negotiator cannot be removed.
"""
if not self.can_leave(negotiator):
return False
Expand All @@ -801,7 +813,7 @@ def remove(self, negotiator: Negotiator) -> bool | None:
self._negotiator_map.pop(negotiator.id)
if self._extra_callbacks:
strt = time.perf_counter()
negotiator.on_leave(self.nmi.state)
self._call(negotiator, negotiator.on_leave, self.nmi.state)
self._negotiator_times[negotiator.id] += time.perf_counter() - strt
return True

Expand Down Expand Up @@ -914,8 +926,8 @@ def dynamic_entry(self):
return self.nmi.dynamic_entry

@property
def max_n_agents(self):
return self.nmi.max_n_agents
def max_n_negotiators(self):
return self.nmi.max_n_negotiators

@property
def state4history(self) -> Any:
Expand All @@ -942,7 +954,7 @@ def on_mechanism_error(self) -> None:
if self._extra_callbacks:
for a in self.negotiators:
strt = time.perf_counter()
a.on_mechanism_error(state=state)
self._call(a, a.on_mechanism_error, state=state)
self._negotiator_times[a.id] += time.perf_counter() - strt

def on_negotiation_end(self) -> None:
Expand All @@ -954,7 +966,7 @@ def on_negotiation_end(self) -> None:
state = self.state
for a in self.negotiators:
strt = time.perf_counter()
a._on_negotiation_end(state=state)
self._call(a, a._on_negotiation_end, state=state)
self._negotiator_times[a.id] += time.perf_counter() - strt
self.announce(
Event(
Expand Down Expand Up @@ -1112,7 +1124,7 @@ def step(self, action: dict[str, TAction] | None = None) -> TState:
return self.state
for a in self.negotiators:
strt = time.perf_counter()
a._on_negotiation_start(state=state)
self._call(a, a._on_negotiation_start, state=state)
self._negotiator_times[a.id] += time.perf_counter() - strt
self.announce(Event(type="negotiation_start", data=None))
else:
Expand All @@ -1133,10 +1145,10 @@ def step(self, action: dict[str, TAction] | None = None) -> TState:
# send round start only if the mechanism is not waiting for anyone
# TODO check this.
if not self._current_state.waiting and self._extra_callbacks:
for agent in self._negotiators:
for negotiator in self._negotiators:
strt = time.perf_counter()
agent.on_round_start(state=state)
self._negotiator_times[agent.id] += time.perf_counter() - strt
self._call(negotiator, negotiator.on_round_start, state=state)
self._negotiator_times[negotiator.id] += time.perf_counter() - strt

# run a round of the mechanism and get the new state
step_start = (
Expand Down Expand Up @@ -1198,10 +1210,10 @@ def step(self, action: dict[str, TAction] | None = None) -> TState:
if not self._current_state.waiting and result.completed:
state4history = self.state4history
if self._extra_callbacks:
for agent in self._negotiators:
for negotiator in self._negotiators:
strt = time.perf_counter()
agent.on_round_end(state=state)
self._negotiator_times[agent.id] += time.perf_counter() - strt
self._call(negotiator, negotiator.on_round_end, state=state)
self._negotiator_times[negotiator.id] += time.perf_counter() - strt
self._add_to_history(state4history)
# we only indicate a new step if no one is waiting
self._current_state.step += 1
Expand Down Expand Up @@ -1236,10 +1248,10 @@ def abort(self) -> TState:
state4history = self.state4history
self._current_state.running = False
if self._extra_callbacks:
for agent in self._negotiators:
for negotiator in self._negotiators:
strt = time.perf_counter()
agent.on_round_end(state=state)
self._negotiator_times[agent.id] += time.perf_counter() - strt
self._call(negotiator, negotiator.on_round_end, state=state)
self._negotiator_times[negotiator.id] += time.perf_counter() - strt
self._add_to_history(state4history)
self._current_state.step += 1
self.on_negotiation_end()
Expand Down
2 changes: 0 additions & 2 deletions negmas/sao/mechanism.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ def __init__(
check_offers=False,
enforce_issue_types=False,
cast_offers=False,
ignore_negotiator_exceptions=False,
offering_is_accepting=True,
allow_offering_just_rejected_outcome=True,
name: str | None = None,
Expand Down Expand Up @@ -159,7 +158,6 @@ def __init__(
] = allow_offering_just_rejected_outcome
self._n_max_waits = max_wait if max_wait is not None else float("inf")
self.params["max_wait"] = self._n_max_waits
self.ignore_negotiator_exceptions = ignore_negotiator_exceptions
self.allow_offering_just_rejected_outcome = allow_offering_just_rejected_outcome
self.end_negotiation_on_refusal_to_propose = end_on_no_response
self.check_offers = check_offers
Expand Down
6 changes: 3 additions & 3 deletions negmas/tests/test_protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,11 @@ def test_different_capability_types(mechanism):

def test_can_accept_more_agents():
mechanism = MyMechanism(max_n_agents=2)
assert mechanism.can_accept_more_agents() is True
assert mechanism.can_accept_more_negotiators() is True
mechanism.add(RandomNegotiator(), ufun=MappingUtilityFunction(lambda x: 5.0))
assert mechanism.can_accept_more_agents() is True
assert mechanism.can_accept_more_negotiators() is True
mechanism.add(RandomNegotiator(), ufun=MappingUtilityFunction(lambda x: 5.0))
assert mechanism.can_accept_more_agents() is False
assert mechanism.can_accept_more_negotiators() is False


def test_dynamic_entry(static_mechanism: Mechanism):
Expand Down
Loading

0 comments on commit b1305b8

Please sign in to comment.