Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/nashpy/algorithms/support_enumeration.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,5 @@ def support_enumeration(
An even number of ({}) equilibria was returned. This
indicates that the game is degenerate. Consider using another algorithm
to investigate.
""".format(
count
)
""".format(count)
warnings.warn(warning, RuntimeWarning)
10 changes: 10 additions & 0 deletions src/nashpy/algorithms/vertex_enumeration.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ def vertex_enumeration(
if np.min(B) < 0:
B = B + abs(np.min(B))

# The best response polytope is {x >= 0, Mx <= 1}, so its vertex coordinates
# scale as 1 / payoff. With large payoffs every vertex falls below the
# absolute tolerance used to discard the origin and the algorithm returns
# nothing. Nash equilibria are invariant under a positive rescaling of each
# player's payoffs, so normalise here to keep the polytope well conditioned.
if np.max(A) > 0:
A = A / np.max(A)
if np.max(B) > 0:
B = B / np.max(B)

number_of_row_strategies, row_dimension = A.shape
max_label = number_of_row_strategies + row_dimension
full_labels = set(range(max_label))
Expand Down
4 changes: 1 addition & 3 deletions src/nashpy/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ def __repr__(self) -> str:
{}

Column player:
{}""".format(
tpe, *self.payoff_matrices
)
{}""".format(tpe, *self.payoff_matrices)

def __getitem__(self, key: Any) -> npt.NDArray:
row_strategy, column_strategy = key
Expand Down
2 changes: 1 addition & 1 deletion src/nashpy/learning/imitation_dynamics.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def imitation_dynamics(
iterations=1000,
random_seed=None,
threshold=0.5,
) -> Generator[Tuple[float, float], Any, None]:
) -> Generator[Tuple[npt.NDArray, npt.NDArray], Any, None]:
"""
Simulate the imitation dynamics for a given game represented by payoff matrices A and B.

Expand Down
34 changes: 34 additions & 0 deletions tests/unit/test_vertex_enumeration.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,37 @@ def test_with_negative_utilities(self):
equilibrium = next(vertex_enumeration(A, B))
for strategy, expected_strategy in zip(equilibrium, expected_equilibrium):
assert all(np.isclose(strategy, expected_strategy)), strategy

def test_with_large_utilities(self):
"""
Nash equilibria are invariant under a positive rescaling of payoffs, so
scaling a game up must not change the equilibria that are found.

Regression test: previously the vertices of the best response polytope
fell below the absolute tolerance used to discard the origin, and the
algorithm silently returned no equilibria.
"""
A = np.array([[3, 0], [0, 2]])
B = np.array([[2, 0], [0, 3]])

expected_equilibria = sorted(
[
(np.array([1, 0]), np.array([1, 0])),
(np.array([3 / 5, 2 / 5]), np.array([2 / 5, 3 / 5])),
(np.array([0, 1]), np.array([0, 1])),
],
key=lambda a: list(np.round(a[0], 4)),
)

for scale in (1, 10**6, 10**9, 10**12):
equilibria = sorted(
vertex_enumeration(A * scale, B * scale),
key=lambda a: list(np.round(a[0], 4)),
)
assert len(equilibria) == 3, (scale, len(equilibria))
for equilibrium, expected in zip(equilibria, expected_equilibria):
for strategy, expected_strategy in zip(equilibrium, expected):
assert all(np.isclose(strategy, expected_strategy)), (
scale,
strategy,
)
Loading