Skip to content

Commit ca20bbc

Browse files
committed
Using python-class-identifier everywhere
This paramter to loading/saving functions allows the caller to change the identifier used to mark python classes in dumped files.
1 parent c81a663 commit ca20bbc

31 files changed

+447
-168
lines changed

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ dependencies = [
4444
"pytest-runner>=6.0.1",
4545
"pandas>=2.2.3",
4646
"scipy>=1.14.1",
47-
"numpy>=2.1.3",
47+
"numpy>=2.0.0",
4848
]
4949

5050
[build-system]

src/negmas/events.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Implements Event management"""
2+
23
from __future__ import annotations
34
import json
45
import random
@@ -11,7 +12,7 @@
1112
from negmas import warnings
1213

1314
from .outcomes import Issue
14-
from .serialization import serialize
15+
from .serialization import PYTHON_CLASS_IDENTIFIER, serialize
1516
from .types import NamedObject
1617

1718
__all__ = [
@@ -98,7 +99,12 @@ def __init__(self, file_name: str | Path, types: list[str] | None = None):
9899
def reset_timer(self):
99100
self._start = time.perf_counter()
100101

101-
def on_event(self, event: Event, sender: EventSource):
102+
def on_event(
103+
self,
104+
event: Event,
105+
sender: EventSource,
106+
python_class_identifier=PYTHON_CLASS_IDENTIFIER,
107+
):
102108
if not self._file_name:
103109
return
104110
if self._types is not None and event.type not in self._types:
@@ -122,7 +128,11 @@ def _simplify(x):
122128
# return _simplify(myvars(x))
123129

124130
try:
125-
sid = sender.id if hasattr(sender, "id") else serialize(sender) # type: ignore
131+
sid = (
132+
sender.id
133+
if hasattr(sender, "id")
134+
else serialize(sender, python_class_identifier=python_class_identifier)
135+
) # type: ignore
126136
d = dict(
127137
sender=sid,
128138
time=time.perf_counter() - self._start,

src/negmas/gb/negotiators/base.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
TState = TypeVar("TState", bound=MechanismState)
2323

2424

25+
def none_return():
26+
return None
27+
28+
2529
class GBNegotiator(Negotiator[GBNMI, GBState], Generic[TNMI, TState]):
2630
"""
2731
Base class for all GB negotiators.
@@ -64,7 +68,7 @@ def __init__(
6468
self.add_capabilities({"respond": True, "propose": True, "max-proposals": 1})
6569
self.__end_negotiation = False
6670
self.__received_offer: dict[str | None, Outcome | None] = defaultdict(
67-
lambda: None
71+
none_return
6872
)
6973

7074
@abstractmethod

src/negmas/gb/negotiators/utilbased.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def utility_range_to_propose(self, state) -> tuple[float, float]:
5858
def utility_range_to_accept(self, state) -> tuple[float, float]:
5959
...
6060

61-
def respond(self, state, source: str | None = None):
61+
def respond(self, state, source: str | None = None) -> ResponseType:
6262
offer = state.current_offer # type: ignore
6363
if self._selector:
6464
self._selector.before_responding(state, offer, source)

src/negmas/genius/negotiator.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -463,8 +463,11 @@ def on_negotiation_start(self, state: MechanismState) -> None:
463463
)
464464
)
465465
n_rounds = info.n_steps
466-
if n_rounds is not None and info.one_offer_per_step:
467-
n_rounds = int(math.ceil(float(n_rounds) / info.n_negotiators))
466+
try:
467+
if n_rounds is not None and info.one_offer_per_step:
468+
n_rounds = int(math.ceil(float(n_rounds) / info.n_negotiators))
469+
except Exception:
470+
pass
468471
n_steps = -1 if n_rounds is None or math.isinf(n_rounds) else int(n_rounds)
469472
n_seconds = (
470473
-1

src/negmas/inout.py

+29-7
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,9 @@ def calc_extra_stats(
452452
bilateral_frontier_outcomes=fo,
453453
)
454454

455-
def serialize(self) -> dict[str, Any]:
455+
def serialize(
456+
self, python_class_identifier=PYTHON_CLASS_IDENTIFIER
457+
) -> dict[str, Any]:
456458
"""
457459
Converts the current scenario into a serializable dict.
458460
@@ -507,11 +509,24 @@ def adjust(
507509
return d
508510

509511
domain = adjust(
510-
serialize(self.outcome_space, shorten_type_field=True, add_type_field=True),
512+
serialize(
513+
self.outcome_space,
514+
shorten_type_field=True,
515+
add_type_field=True,
516+
python_class_identifier=python_class_identifier,
517+
),
511518
"domain",
512519
)
513520
ufuns = [
514-
adjust(serialize(u, shorten_type_field=True, add_type_field=True), i)
521+
adjust(
522+
serialize(
523+
u,
524+
shorten_type_field=True,
525+
add_type_field=True,
526+
python_class_identifier=python_class_identifier,
527+
),
528+
i,
529+
)
515530
for i, u in enumerate(self.ufuns)
516531
]
517532
return dict(domain=domain, ufuns=ufuns)
@@ -532,13 +547,19 @@ def to_json(self, folder: Path | str) -> None:
532547
"""
533548
self.dumpas(folder, "json")
534549

535-
def dumpas(self, folder: Path | str, type="yml", compact: bool = False) -> None:
550+
def dumpas(
551+
self,
552+
folder: Path | str,
553+
type="yml",
554+
compact: bool = False,
555+
python_class_identifier=PYTHON_CLASS_IDENTIFIER,
556+
) -> None:
536557
"""
537558
Dumps the scenario in the given file format.
538559
"""
539560
folder = Path(folder)
540561
folder.mkdir(parents=True, exist_ok=True)
541-
serialized = self.serialize()
562+
serialized = self.serialize(python_class_identifier=python_class_identifier)
542563
dump(serialized["domain"], folder / f"{serialized['domain']['name']}.{type}")
543564
for u in serialized["ufuns"]:
544565
dump(u, folder / f"{u['name']}.{type}", sort_keys=True, compact=compact)
@@ -666,6 +687,7 @@ def from_yaml_files(
666687
ignore_discount=False,
667688
ignore_reserved=False,
668689
safe_parsing=True,
690+
python_class_identifier="type",
669691
) -> Scenario | None:
670692
_ = safe_parsing # yaml parsing is always safe
671693

@@ -677,13 +699,13 @@ def adjust_type(d: dict, base: str = "negmas", domain=None) -> dict:
677699

678700
os = deserialize(
679701
adjust_type(load(domain)),
680-
python_class_identifier="type",
681702
base_module="negmas",
703+
python_class_identifier=python_class_identifier,
682704
)
683705
utils = tuple(
684706
deserialize(
685707
adjust_type(load(fname), domain=os),
686-
python_class_identifier="type",
708+
python_class_identifier=python_class_identifier,
687709
base_module="negmas",
688710
)
689711
for fname in ufuns

src/negmas/mechanisms.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1355,7 +1355,7 @@ def runall(
13551355
raise NotImplementedError()
13561356
else:
13571357
raise ValueError(
1358-
f"method {method} is unknown. Acceptable options are serial, threads, processes"
1358+
f"method {method} is unknown. Acceptable options are ordered, sequential, threads, processes"
13591359
)
13601360
return states
13611361

src/negmas/outcomes/base_issue.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -261,23 +261,30 @@ def rand_valid(self):
261261
return self.rand()
262262

263263
@classmethod
264-
def from_dict(cls, d):
264+
def from_dict(cls, d, python_class_identifier=PYTHON_CLASS_IDENTIFIER):
265265
"""
266266
Constructs an issue from a dict generated using `to_dict()`
267267
"""
268268
if isinstance(d, cls):
269269
return d
270-
d.pop(PYTHON_CLASS_IDENTIFIER, None)
271-
d["values"] = deserialize(d["values"])
270+
d.pop(python_class_identifier, None)
271+
d["values"] = deserialize(
272+
d["values"], python_class_identifier=python_class_identifier
273+
)
272274
return cls(values=d.get("values", None), name=d.get("name", None))
273275

274-
def to_dict(self):
276+
def to_dict(self, python_class_identifier=PYTHON_CLASS_IDENTIFIER):
275277
"""
276278
Converts the issue to a dictionary from which it can be constructed again using `Issue.from_dict()`
277279
"""
278-
d = {PYTHON_CLASS_IDENTIFIER: get_full_type_name(type(self))}
280+
d = {python_class_identifier: get_full_type_name(type(self))}
279281
return dict(
280-
**d, values=serialize(self.values), name=self.name, n_values=self._n_values
282+
**d,
283+
values=serialize(
284+
self.values, python_class_identifier=python_class_identifier
285+
),
286+
name=self.name,
287+
n_values=self._n_values,
281288
)
282289

283290
@abstractmethod

src/negmas/outcomes/continuous_issue.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from negmas.outcomes.base_issue import Issue
1010
from negmas.outcomes.range_issue import RangeIssue
11+
from negmas.serialization import PYTHON_CLASS_IDENTIFIER
1112

1213
from .common import DEFAULT_LEVELS
1314

@@ -30,8 +31,8 @@ def _to_xml_str(self, indx):
3031
f' <range lowerbound="{self._values[0]}" upperbound="{self._values[1]}"></range>\n </issue>\n'
3132
)
3233

33-
def to_dict(self):
34-
d = super().to_dict()
34+
def to_dict(self, python_class_identifier=PYTHON_CLASS_IDENTIFIER):
35+
d = super().to_dict(python_class_identifier=python_class_identifier)
3536
d["n_levels"] = self._n_levels
3637
return d
3738

src/negmas/outcomes/optional_issue.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -75,24 +75,26 @@ def rand(self) -> int | float | str | None:
7575
return self.base.rand()
7676

7777
@classmethod
78-
def from_dict(cls, d):
78+
def from_dict(cls, d, python_class_identifier=PYTHON_CLASS_IDENTIFIER):
7979
"""
8080
Constructs an issue from a dict generated using `to_dict()`
8181
"""
8282
if isinstance(d, cls):
8383
return d
84-
d.pop(PYTHON_CLASS_IDENTIFIER, None)
85-
d["base"] = deserialize(d["base"])
84+
d.pop(python_class_identifier, None)
85+
d["base"] = deserialize(
86+
d["base"], python_class_identifier=python_class_identifier
87+
)
8688
return cls(base=d.get("base", None), name=d.get("name", None))
8789

88-
def to_dict(self):
90+
def to_dict(self, python_class_identifier=PYTHON_CLASS_IDENTIFIER):
8991
"""
9092
Converts the issue to a dictionary from which it can be constructed again using `Issue.from_dict()`
9193
"""
92-
d = {PYTHON_CLASS_IDENTIFIER: get_full_type_name(type(self))}
94+
d = {python_class_identifier: get_full_type_name(type(self))}
9395
return dict(
9496
**d,
95-
base=serialize(self.base),
97+
base=serialize(self.base, python_class_identifier=python_class_identifier),
9698
name=self.name,
9799
n_values=self.cardinality + 1,
98100
)

src/negmas/outcomes/outcome_space.py

+18-5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ def update(self):
103103
class EnumeratingOutcomeSpace(DiscreteOutcomeSpace, OSWithValidity):
104104
"""An outcome space representing the enumeration of some outcomes. No issues defined"""
105105

106+
name: str | None = field(eq=False, default=None)
107+
106108
def invalidate(self, outcome: Outcome) -> None:
107109
"""Indicates that the outcome is invalid"""
108110
self.invalid.add(outcome)
@@ -266,6 +268,11 @@ def __attrs_post_init__(self):
266268
if not self.name:
267269
object.__setattr__(self, "name", unique_name("os", add_time=False, sep=""))
268270

271+
def __mul__(self, other: CartesianOutcomeSpace) -> CartesianOutcomeSpace:
272+
issues = list(self.issues) + list(other.issues)
273+
name = f"{self.name}*{other.name}"
274+
return CartesianOutcomeSpace(tuple(issues), name=name)
275+
269276
def contains_issue(self, x: Issue) -> bool:
270277
"""Cheks that the given issue is in the tuple of issues constituting the outcome space (i.e. it is one of its dimensions)"""
271278
return x in self.issues
@@ -299,13 +306,19 @@ def contains_os(self, x: OutcomeSpace) -> bool:
299306
)
300307
return all(self.is_valid(_) for _ in x.enumerate()) # type: ignore If we are here, we know that x is finite
301308

302-
def to_dict(self):
303-
d = {PYTHON_CLASS_IDENTIFIER: get_full_type_name(type(self))}
304-
return dict(**d, name=self.name, issues=serialize(self.issues))
309+
def to_dict(self, python_class_identifier=PYTHON_CLASS_IDENTIFIER):
310+
d = {python_class_identifier: get_full_type_name(type(self))}
311+
return dict(
312+
**d,
313+
name=self.name,
314+
issues=serialize(
315+
self.issues, python_class_identifier=python_class_identifier
316+
),
317+
)
305318

306319
@classmethod
307-
def from_dict(cls, d):
308-
return cls(**deserialize(d)) # type: ignore
320+
def from_dict(cls, d, python_class_identifier=PYTHON_CLASS_IDENTIFIER):
321+
return cls(**deserialize(d, python_class_identifier=python_class_identifier)) # type: ignore
309322

310323
@property
311324
def issue_names(self) -> list[str]:

src/negmas/outcomes/protocols.py

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
runtime_checkable,
99
)
1010

11+
1112
if TYPE_CHECKING:
1213
from .base_issue import DiscreteIssue, Issue
1314
from .common import Outcome

src/negmas/preferences/base_ufun.py

+16-9
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def __init__(
6262
self.reserved_value = reserved_value
6363
self._cached_inverse: InverseUFun | None = None
6464
self._cached_inverse_type: type[InverseUFun] | None = None
65-
self.__invalid_value = invalid_value
65+
self._invalid_value = invalid_value
6666

6767
@abstractmethod
6868
def eval(self, offer: Outcome) -> Value:
@@ -465,20 +465,27 @@ def to_prob(self) -> ProbUtilityFunction:
465465

466466
return ProbAdapter(self)
467467

468-
def to_dict(self) -> dict[str, Any]:
469-
d = {PYTHON_CLASS_IDENTIFIER: get_full_type_name(type(self))}
468+
def to_dict(
469+
self, python_class_identifier=PYTHON_CLASS_IDENTIFIER
470+
) -> dict[str, Any]:
471+
d = {python_class_identifier: get_full_type_name(type(self))}
470472
return dict(
471473
**d,
472-
outcome_space=serialize(self.outcome_space),
474+
outcome_space=serialize(
475+
self.outcome_space, python_class_identifier=python_class_identifier
476+
),
473477
reserved_value=self.reserved_value,
474478
name=self.name,
475479
id=self.id,
476480
)
477481

478482
@classmethod
479-
def from_dict(cls, d):
480-
d.pop(PYTHON_CLASS_IDENTIFIER, None)
481-
d["outcome_space"] = deserialize(d.get("outcome_space", None))
483+
def from_dict(cls, d, python_class_identifier=PYTHON_CLASS_IDENTIFIER):
484+
d.pop(python_class_identifier, None)
485+
d["outcome_space"] = deserialize(
486+
d.get("outcome_space", None),
487+
python_class_identifier=python_class_identifier,
488+
)
482489
return cls(**d)
483490

484491
def sample_outcome_with_utility(
@@ -1118,11 +1125,11 @@ def __call__(self, offer: Outcome | None) -> Value:
11181125
if offer is None:
11191126
return self.reserved_value # type: ignore I know that concrete subclasses will be returning the correct type
11201127
if (
1121-
self.__invalid_value is not None
1128+
self._invalid_value is not None
11221129
and self.outcome_space
11231130
and offer not in self.outcome_space
11241131
):
1125-
return self.__invalid_value
1132+
return self._invalid_value
11261133
return self.eval(offer)
11271134

11281135

0 commit comments

Comments
 (0)