Skip to content

Commit

Permalink
Making mechanisms and worlds generics
Browse files Browse the repository at this point in the history
  • Loading branch information
yasserfarouk committed Feb 20, 2024
1 parent 98c00f0 commit e63cb2d
Show file tree
Hide file tree
Showing 26 changed files with 474 additions and 361 deletions.
62 changes: 35 additions & 27 deletions negmas/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"AgentMechanismInterface",
"TraceElement",
"DEFAULT_JAVA_PORT",
"MechanismAction",
]

DEFAULT_JAVA_PORT = 25337
Expand Down Expand Up @@ -304,12 +305,10 @@ class NegotiatorMechanismInterface:
"""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"""
mechanism: Mechanism
_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"""
one_offer_per_step: bool = False
"""If true, a step should be atomic with only one action from one negotiator"""

# def __copy__(self):
# return NegotiatorMechanismInterface(**vars(self))
Expand Down Expand Up @@ -344,16 +343,16 @@ def discrete_outcome_space(
"""
Returns a stable discrete version of the given outcome-space
"""
return self.mechanism.discrete_outcome_space(levels, max_cardinality)
return self._mechanism.discrete_outcome_space(levels, max_cardinality)

@property
def params(self):
"""Returns the parameters used to initialize the mechanism."""
return self.mechanism.params
return self._mechanism.params

def random_outcome(self) -> Outcome:
"""A single random outcome."""
return self.mechanism.random_outcome()
return self._mechanism.random_outcome()

def random_outcomes(self, n: int = 1) -> list[Outcome]:
"""
Expand All @@ -367,7 +366,7 @@ def random_outcomes(self, n: int = 1) -> list[Outcome]:
list[Outcome]: list of `n` or less outcomes
"""
return self.mechanism.random_outcomes(n=n)
return self._mechanism.random_outcomes(n=n)

def discrete_outcomes(
self, max_cardinality: int | float = float("inf")
Expand All @@ -383,11 +382,11 @@ def discrete_outcomes(
list[Outcome]: list of `n` or less outcomes
"""
return self.mechanism.discrete_outcomes(max_cardinality=max_cardinality)
return self._mechanism.discrete_outcomes(max_cardinality=max_cardinality)

@property
def issues(self) -> tuple[Issue, ...]:
os = self.mechanism.outcome_space
os = self._mechanism.outcome_space
if hasattr(os, "issues"):
return os.issues # type: ignore I am just checking that the attribute issues exists
raise ValueError(
Expand All @@ -400,14 +399,14 @@ def outcomes(self) -> Iterable[Outcome] | None:
from negmas.outcomes.protocols import DiscreteOutcomeSpace

return (
self.mechanism.outcome_space.enumerate()
if isinstance(self.mechanism.outcome_space, DiscreteOutcomeSpace)
self._mechanism.outcome_space.enumerate()
if isinstance(self._mechanism.outcome_space, DiscreteOutcomeSpace)
else None
)

@property
def participants(self) -> list[NegotiatorInfo]:
return self.mechanism.participants
return self._mechanism.participants

@property
def state(self) -> MechanismState:
Expand All @@ -420,7 +419,7 @@ def state(self) -> MechanismState:
protocol by accessing this property.
"""
return self.mechanism.state
return self._mechanism.state

@property
def requirements(self) -> dict:
Expand All @@ -430,7 +429,7 @@ def requirements(self) -> dict:
Returns:
- A dict of str/Any pairs giving the requirements
"""
return self.mechanism.requirements
return self._mechanism.requirements

@property
def n_negotiators(self) -> int:
Expand All @@ -440,20 +439,25 @@ def n_negotiators(self) -> int:
@property
def genius_negotiator_ids(self) -> list[str]:
"""Gets the Java IDs of all negotiators (if the negotiator is not a GeniusNegotiator, its normal ID is returned)"""
return self.mechanism.genius_negotiator_ids
return self._mechanism.genius_negotiator_ids

def genius_id(self, id: str | None) -> str | None:
"""Gets the Genius ID corresponding to the given negotiator if known otherwise its normal ID"""
return self.mechanism.genius_id(id)
return self._mechanism.genius_id(id)

@property
def mechanism_id(self) -> str:
"""Gets the ID of the mechanism"""
return self._mechanism.id

@property
def negotiator_ids(self) -> list[str]:
"""Gets the IDs of all negotiators"""
return self.mechanism.negotiator_ids
return self._mechanism.negotiator_ids

def negotiator_index(self, source: str) -> int:
"""Returns the negotiator index for the given negotiator. Raises an exception if not found"""
indx = self.mechanism.negotiator_index(source)
indx = self._mechanism.negotiator_index(source)
if indx is None:
raise ValueError(f"No known index for negotiator {source}")
return indx
Expand All @@ -466,7 +470,7 @@ def negotiator_index(self, source: str) -> int:
@property
def agent_ids(self) -> list[str]:
"""Gets the IDs of all agents owning all negotiators"""
return self.mechanism.agent_ids
return self._mechanism.agent_ids

# @property
# def agent_names(self) -> list[str]:
Expand All @@ -489,23 +493,23 @@ def __getitem__(self, item):

def log_info(self, nid: str, data: dict[str, Any]) -> None:
"""Logs at info level"""
self.mechanism.log(nid, level="info", data=data)
self._mechanism.log(nid, level="info", data=data)

def log_debug(self, nid: str, data: dict[str, Any]) -> None:
"""Logs at debug level"""
self.mechanism.log(nid, level="debug", data=data)
self._mechanism.log(nid, level="debug", data=data)

def log_warning(self, nid: str, data: dict[str, Any]) -> None:
"""Logs at warning level"""
self.mechanism.log(nid, level="warning", data=data)
self._mechanism.log(nid, level="warning", data=data)

def log_error(self, nid: str, data: dict[str, Any]) -> None:
"""Logs at error level"""
self.mechanism.log(nid, level="error", data=data)
self._mechanism.log(nid, level="error", data=data)

def log_critical(self, nid: str, data: dict[str, Any]) -> None:
"""Logs at critical level"""
self.mechanism.log(nid, level="critical", data=data)
self._mechanism.log(nid, level="critical", data=data)


TraceElement = namedtuple(
Expand All @@ -518,8 +522,12 @@ def log_critical(self, nid: str, data: dict[str, Any]) -> None:
"""A **depricated** alias for `NegotiatorMechanismInterface`"""


Action = Any
"""Defines a negotiation action"""
class MechanismAction:
"""Defines a negotiation action"""

ReactiveStrategy = Mapping[MechanismState, Action] | Callable[[MechanismState], Action]

ReactiveStrategy = (
Mapping[MechanismState, MechanismAction]
| Callable[[MechanismState], MechanismAction]
)
"""Defines a negotiation strategy as a mapping from a mechanism state to an action"""
30 changes: 18 additions & 12 deletions negmas/concurrent/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from collections import defaultdict, namedtuple
from dataclasses import dataclass

from ..common import NegotiatorMechanismInterface
from ..mechanisms import Mechanism, MechanismState, MechanismStepResult
from ..common import NegotiatorMechanismInterface, MechanismAction, MechanismState
from ..mechanisms import Mechanism, MechanismStepResult
from ..negotiators import Negotiator
from ..outcomes import Outcome
from ..preferences import Preferences
Expand Down Expand Up @@ -35,7 +35,7 @@ class Offer:
"""Defines an agreement for a multi-channel mechanism"""


class ChainAMI(NegotiatorMechanismInterface):
class ChainNMI(NegotiatorMechanismInterface):
def __init__(
self,
*args,
Expand All @@ -58,7 +58,7 @@ class ChainNegotiator(Negotiator, ABC):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._nmi: ChainAMI
self._nmi: ChainNMI
self.__level = -1

def join(
Expand Down Expand Up @@ -159,7 +159,7 @@ def confirm(self, left: bool) -> bool:
Returns:
"""
return self.nmi.confirm(left) # type: ignore
return self.nmi.confirm(left) #

@abstractmethod
def on_acceptance(self, state: MechanismState, offer: Offer) -> Offer:
Expand Down Expand Up @@ -210,9 +210,13 @@ def respond(
"""


class ChainNegotiationsMechanism(Mechanism):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class ChainNegotiationsMechanism(
Mechanism[MechanismState, MechanismAction, ChainNMI, ChainNegotiator]
):
def __init__(self, initial_state: MechanismState | None = None, *args, **kwargs):
super().__init__(
initial_state if initial_state else MechanismState() * args, **kwargs
)
self.__chain: list[ChainNegotiator | None] = []
self.__next_agent = 0
self.__last_proposal: Offer | None = None
Expand All @@ -233,7 +237,7 @@ def _get_ami(
Returns:
"""
return ChainAMI(
return ChainNMI(
id=self.id,
n_outcomes=self.nmi.n_outcomes,
issues=self.nmi.outcome_space,
Expand All @@ -245,7 +249,7 @@ def _get_ami(
max_n_agents=self.nmi.max_n_agents,
annotation=self.nmi.annotation,
parent=self,
negotiator=negotiator, # type: ignore
negotiator=negotiator, #
level=int(role) + 1,
)

Expand Down Expand Up @@ -386,7 +390,9 @@ def on_confirm(self, level: int, left: bool) -> bool:
return True


class MultiChainNegotiationsMechanism(Mechanism):
class MultiChainNegotiationsMechanism(
Mechanism[ChainNMI, MechanismState, MechanismAction, MultiChainNegotiator]
):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__chain: list[list[MultiChainNegotiator]] = []
Expand All @@ -413,7 +419,7 @@ def _get_ami(
Returns:
"""
return ChainAMI(
return ChainNMI(
id=self.id,
n_outcomes=self.nmi.n_outcomes,
issues=self.nmi.outcome_space,
Expand Down
20 changes: 13 additions & 7 deletions negmas/ga.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from attrs import define, field

from negmas.common import Action
from negmas.common import MechanismAction, NegotiatorMechanismInterface
from negmas.negotiators.simple import SorterNegotiator

from .mechanisms import Mechanism, MechanismState, MechanismStepResult
from .outcomes import Outcome
Expand All @@ -18,7 +19,9 @@ class GAState(MechanismState):
dominant_outcomes: list[Outcome] = field(default=list)


class GAMechanism(Mechanism):
class GAMechanism(
Mechanism[NegotiatorMechanismInterface, GAState, MechanismAction, SorterNegotiator]
):
"""Naive GA-based mechanism that assume multi-issue discrete domains.
Args:
Expand All @@ -32,11 +35,14 @@ def generate(self, n: int) -> list[Outcome]:
return self.random_outcomes(n)

def __init__(
self, *args, n_population: int = 100, mutate_rate: float = 0.1, **kwargs
self,
initial_state: GAState | None = None,
*args,
n_population: int = 100,
mutate_rate: float = 0.1,
**kwargs,
):
kwargs["initial_state"] = GAState()
super().__init__(*args, **kwargs)
self._current_state: GAState
super().__init__(initial_state if initial_state else GAState(), *args, **kwargs)

self.n_population = n_population

Expand Down Expand Up @@ -117,7 +123,7 @@ def update_dominant_outcomes(self):
self._current_state.dominant_outcomes = self.dominant_outcomes # type: ignore

def __call__( # type: ignore
self, state: GAState, action: Action | None = None
self, state: GAState, action: MechanismAction | None = None
) -> MechanismStepResult:
self.update_ranks()
self.update_dominant_outcomes()
Expand Down
10 changes: 5 additions & 5 deletions negmas/gb/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from attrs import asdict, define, field

from negmas.common import MechanismState, NegotiatorMechanismInterface
from negmas.common import MechanismState, NegotiatorMechanismInterface, MechanismAction
from negmas.outcomes import Outcome

if TYPE_CHECKING:
Expand Down Expand Up @@ -119,7 +119,7 @@ def get_offer(state: GBState, source: str | None) -> Outcome | None:
return None


GBAction = dict[str, Outcome | None]
"""
An action for a GB mechanism is a mapping from one or more thread IDs to outcomes to offer.
"""
class GBAction(Outcome, MechanismAction):
"""
An action for a GB mechanism is a mapping from one or more thread IDs to outcomes to offer.
"""
Loading

0 comments on commit e63cb2d

Please sign in to comment.