noregret 0.0.0.dev5__tar.gz → 0.0.0.dev7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/PKG-INFO +7 -7
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/README.rst +6 -6
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/__init__.py +8 -6
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/__init__.py +6 -4
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/black_box.py +8 -10
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/extensive_form/__init__.py +2 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/extensive_form/games.py +133 -3
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/games.py +6 -4
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/multilinear.py +1 -7
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/__init__.py +2 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/games.py +21 -2
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/sequence_form_polytopes.py +2 -4
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/solvers/regret_minimization.py +28 -15
- noregret-0.0.0.dev7/noregret/tests/test_games.py +192 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/tests/test_linear_programming.py +2 -2
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/tests/test_regret_minimization.py +27 -5
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret.egg-info/PKG-INFO +7 -7
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret.egg-info/SOURCES.txt +0 -1
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/setup.py +1 -1
- noregret-0.0.0.dev5/noregret/games/utilities.py +0 -140
- noregret-0.0.0.dev5/noregret/tests/test_games.py +0 -62
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/LICENSE +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/assurance-game.json +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/battle-of-the-sexes.json +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/chicken.json +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/gift-exchange-game.json +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/matching-pennies.json +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/prisoners-dilemma.json +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/pure-coordination.json +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-scissors-plus.json +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-scissors.json +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-superscissors.json +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/stag-hunt.json +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/kernels.py +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/regret_minimizers/__init__.py +4 -4
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/regret_minimizers/probability_simplices.py +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/regret_minimizers/regret_minimizers.py +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/regret_minimizers/sequence_form_polytopes.py +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/solvers/__init__.py +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/solvers/linear_programming.py +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/tests/__init__.py +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/tests/test_sequence_form_polytopes.py +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/utilities.py +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret.egg-info/dependency_links.txt +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret.egg-info/requires.txt +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret.egg-info/top_level.txt +0 -0
- {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: noregret
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev7
|
|
4
4
|
Summary: No-regret learning dynamics
|
|
5
5
|
Home-page: https://github.com/uoftcprg/noregret
|
|
6
6
|
Author: Universal, Open, Free, and Transparent Computer Poker Research Group
|
|
@@ -52,7 +52,7 @@ Dynamic: summary
|
|
|
52
52
|
NoRegret
|
|
53
53
|
========
|
|
54
54
|
|
|
55
|
-
NoRegret is an open-source software library for no-regret learning dynamics and computational game solving, developed by the Universal, Open, Free, and Transparent Computer Poker Research Group. NoRegret implements an extensive array of regret minimizers and game solvers, and also supports GPU-acceleration. The library can be used in a variety of use cases, from solving games to conducting research in online convex optimization. NoRegret's reliability has been established through extensive doctests and unit tests, achieving
|
|
55
|
+
NoRegret is an open-source software library for no-regret learning dynamics and computational game solving, developed by the Universal, Open, Free, and Transparent Computer Poker Research Group. NoRegret implements an extensive array of regret minimizers and game solvers, and also supports GPU-acceleration. The library can be used in a variety of use cases, from solving games to conducting research in online convex optimization. NoRegret's reliability has been established through extensive doctests and unit tests, achieving 95% code coverage.
|
|
56
56
|
|
|
57
57
|
Features
|
|
58
58
|
--------
|
|
@@ -94,8 +94,8 @@ The code snippet below demonstrates how one can solve games via regret minimizat
|
|
|
94
94
|
KERNEL = nr.FloatingPointKernel()
|
|
95
95
|
GAMES = {
|
|
96
96
|
'Rock paper superscissors': nr.to_efg(nr.RockPaperSuperscissors(KERNEL)),
|
|
97
|
-
'Kuhn poker': nr.to_efg(KERNEL, nr.
|
|
98
|
-
'Leduc poker': nr.to_efg(KERNEL, nr.
|
|
97
|
+
'Kuhn poker': nr.to_efg(KERNEL, nr.open_spiel_game('kuhn_poker')),
|
|
98
|
+
'Leduc poker': nr.to_efg(KERNEL, nr.open_spiel_game('leduc_poker')),
|
|
99
99
|
}
|
|
100
100
|
PARAMETERS = {
|
|
101
101
|
'CFR': (nr.CFR, False, False),
|
|
@@ -180,7 +180,7 @@ The code snippet below demonstrates how one can solve games while leveraging GPU
|
|
|
180
180
|
import noregret as nr
|
|
181
181
|
|
|
182
182
|
KERNEL = nr.CUDAKernel()
|
|
183
|
-
GAME = nr.to_efg(KERNEL, nr.
|
|
183
|
+
GAME = nr.to_efg(KERNEL, nr.open_spiel_game('liars_dice'))
|
|
184
184
|
PARAMETERS = nr.CFR, True, False
|
|
185
185
|
|
|
186
186
|
|
|
@@ -220,8 +220,8 @@ The code snippet below demonstrates how one can solve games via linear programmi
|
|
|
220
220
|
KERNEL = nr.FloatingPointKernel()
|
|
221
221
|
GAMES = {
|
|
222
222
|
'Rock paper superscissors': nr.RockPaperSuperscissors(KERNEL),
|
|
223
|
-
'Kuhn poker': nr.to_efg(KERNEL, nr.
|
|
224
|
-
'Leduc poker': nr.to_efg(KERNEL, nr.
|
|
223
|
+
'Kuhn poker': nr.to_efg(KERNEL, nr.open_spiel_game('kuhn_poker')),
|
|
224
|
+
'Leduc poker': nr.to_efg(KERNEL, nr.open_spiel_game('leduc_poker')),
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
NoRegret
|
|
3
3
|
========
|
|
4
4
|
|
|
5
|
-
NoRegret is an open-source software library for no-regret learning dynamics and computational game solving, developed by the Universal, Open, Free, and Transparent Computer Poker Research Group. NoRegret implements an extensive array of regret minimizers and game solvers, and also supports GPU-acceleration. The library can be used in a variety of use cases, from solving games to conducting research in online convex optimization. NoRegret's reliability has been established through extensive doctests and unit tests, achieving
|
|
5
|
+
NoRegret is an open-source software library for no-regret learning dynamics and computational game solving, developed by the Universal, Open, Free, and Transparent Computer Poker Research Group. NoRegret implements an extensive array of regret minimizers and game solvers, and also supports GPU-acceleration. The library can be used in a variety of use cases, from solving games to conducting research in online convex optimization. NoRegret's reliability has been established through extensive doctests and unit tests, achieving 95% code coverage.
|
|
6
6
|
|
|
7
7
|
Features
|
|
8
8
|
--------
|
|
@@ -44,8 +44,8 @@ The code snippet below demonstrates how one can solve games via regret minimizat
|
|
|
44
44
|
KERNEL = nr.FloatingPointKernel()
|
|
45
45
|
GAMES = {
|
|
46
46
|
'Rock paper superscissors': nr.to_efg(nr.RockPaperSuperscissors(KERNEL)),
|
|
47
|
-
'Kuhn poker': nr.to_efg(KERNEL, nr.
|
|
48
|
-
'Leduc poker': nr.to_efg(KERNEL, nr.
|
|
47
|
+
'Kuhn poker': nr.to_efg(KERNEL, nr.open_spiel_game('kuhn_poker')),
|
|
48
|
+
'Leduc poker': nr.to_efg(KERNEL, nr.open_spiel_game('leduc_poker')),
|
|
49
49
|
}
|
|
50
50
|
PARAMETERS = {
|
|
51
51
|
'CFR': (nr.CFR, False, False),
|
|
@@ -130,7 +130,7 @@ The code snippet below demonstrates how one can solve games while leveraging GPU
|
|
|
130
130
|
import noregret as nr
|
|
131
131
|
|
|
132
132
|
KERNEL = nr.CUDAKernel()
|
|
133
|
-
GAME = nr.to_efg(KERNEL, nr.
|
|
133
|
+
GAME = nr.to_efg(KERNEL, nr.open_spiel_game('liars_dice'))
|
|
134
134
|
PARAMETERS = nr.CFR, True, False
|
|
135
135
|
|
|
136
136
|
|
|
@@ -170,8 +170,8 @@ The code snippet below demonstrates how one can solve games via linear programmi
|
|
|
170
170
|
KERNEL = nr.FloatingPointKernel()
|
|
171
171
|
GAMES = {
|
|
172
172
|
'Rock paper superscissors': nr.RockPaperSuperscissors(KERNEL),
|
|
173
|
-
'Kuhn poker': nr.to_efg(KERNEL, nr.
|
|
174
|
-
'Leduc poker': nr.to_efg(KERNEL, nr.
|
|
173
|
+
'Kuhn poker': nr.to_efg(KERNEL, nr.open_spiel_game('kuhn_poker')),
|
|
174
|
+
'Leduc poker': nr.to_efg(KERNEL, nr.open_spiel_game('leduc_poker')),
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
|
|
@@ -5,19 +5,20 @@ from noregret.games import (
|
|
|
5
5
|
BlackBoxGame,
|
|
6
6
|
Chicken,
|
|
7
7
|
ExtensiveFormGame,
|
|
8
|
-
from_open_spiel,
|
|
9
8
|
Game,
|
|
10
9
|
GiftExchangeGame,
|
|
11
10
|
MatchingPennies,
|
|
11
|
+
matrix_game,
|
|
12
12
|
MultilinearGame,
|
|
13
13
|
NormalFormGame,
|
|
14
|
+
open_spiel_game,
|
|
14
15
|
PrisonersDilemma,
|
|
15
16
|
PureCoordination,
|
|
16
17
|
RockPaperScissors,
|
|
17
18
|
RockPaperScissorsPlus,
|
|
18
19
|
RockPaperSuperscissors,
|
|
19
20
|
StagHunt,
|
|
20
|
-
|
|
21
|
+
to_extensive_form_game,
|
|
21
22
|
TwoPlayerExtensiveFormGame,
|
|
22
23
|
TwoPlayerGame,
|
|
23
24
|
TwoPlayerMultilinearGame,
|
|
@@ -109,8 +110,8 @@ rm = regret_minimization
|
|
|
109
110
|
"""Alias for :func:`noregret.regret_minimization`."""
|
|
110
111
|
symmetric_rm = symmetric_regret_minimization
|
|
111
112
|
"""Alias for :func:`noregret.symmetric_regret_minimization`."""
|
|
112
|
-
to_efg =
|
|
113
|
-
"""Alias for :func:`noregret.
|
|
113
|
+
to_efg = to_extensive_form_game
|
|
114
|
+
"""Alias for :func:`noregret.to_extensive_form_game`."""
|
|
114
115
|
|
|
115
116
|
__all__ = (
|
|
116
117
|
'AssuranceGame',
|
|
@@ -138,7 +139,7 @@ __all__ = (
|
|
|
138
139
|
'ExtensiveFormGame',
|
|
139
140
|
'FloatingPointKernel',
|
|
140
141
|
'FollowTheRegularizedLeader',
|
|
141
|
-
'
|
|
142
|
+
'open_spiel_game',
|
|
142
143
|
'FTRL',
|
|
143
144
|
'Game',
|
|
144
145
|
'GiftExchangeGame',
|
|
@@ -148,6 +149,7 @@ __all__ = (
|
|
|
148
149
|
'linear_programming',
|
|
149
150
|
'lp',
|
|
150
151
|
'MatchingPennies',
|
|
152
|
+
'matrix_game',
|
|
151
153
|
'MD',
|
|
152
154
|
'MirrorDescent',
|
|
153
155
|
'MultilinearGame',
|
|
@@ -181,7 +183,7 @@ __all__ = (
|
|
|
181
183
|
'symmetric_regret_minimization',
|
|
182
184
|
'symmetric_rm',
|
|
183
185
|
'to_efg',
|
|
184
|
-
'
|
|
186
|
+
'to_extensive_form_game',
|
|
185
187
|
'tuple_or_none',
|
|
186
188
|
'TwoPlayerExtensiveFormGame',
|
|
187
189
|
'TwoPlayerGame',
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Module for games."""
|
|
2
|
-
from noregret.games.black_box import BlackBoxGame,
|
|
2
|
+
from noregret.games.black_box import BlackBoxGame, open_spiel_game
|
|
3
3
|
from noregret.games.extensive_form import (
|
|
4
4
|
ExtensiveFormGame,
|
|
5
|
+
to_extensive_form_game,
|
|
5
6
|
TwoPlayerExtensiveFormGame,
|
|
6
7
|
TwoPlayerZeroSumExtensiveFormGame,
|
|
7
8
|
)
|
|
@@ -17,6 +18,7 @@ from noregret.games.normal_form import (
|
|
|
17
18
|
Chicken,
|
|
18
19
|
GiftExchangeGame,
|
|
19
20
|
MatchingPennies,
|
|
21
|
+
matrix_game,
|
|
20
22
|
NormalFormGame,
|
|
21
23
|
PrisonersDilemma,
|
|
22
24
|
PureCoordination,
|
|
@@ -27,7 +29,6 @@ from noregret.games.normal_form import (
|
|
|
27
29
|
TwoPlayerNormalFormGame,
|
|
28
30
|
TwoPlayerZeroSumNormalFormGame,
|
|
29
31
|
)
|
|
30
|
-
from noregret.games.utilities import to_extensive_form
|
|
31
32
|
|
|
32
33
|
__all__ = (
|
|
33
34
|
'AssuranceGame',
|
|
@@ -35,19 +36,20 @@ __all__ = (
|
|
|
35
36
|
'BlackBoxGame',
|
|
36
37
|
'Chicken',
|
|
37
38
|
'ExtensiveFormGame',
|
|
38
|
-
'from_open_spiel',
|
|
39
39
|
'Game',
|
|
40
40
|
'GiftExchangeGame',
|
|
41
41
|
'MatchingPennies',
|
|
42
|
+
'matrix_game',
|
|
42
43
|
'MultilinearGame',
|
|
43
44
|
'NormalFormGame',
|
|
45
|
+
'open_spiel_game',
|
|
44
46
|
'PrisonersDilemma',
|
|
45
47
|
'PureCoordination',
|
|
46
48
|
'RockPaperScissors',
|
|
47
49
|
'RockPaperScissorsPlus',
|
|
48
50
|
'RockPaperSuperscissors',
|
|
49
51
|
'StagHunt',
|
|
50
|
-
'
|
|
52
|
+
'to_extensive_form_game',
|
|
51
53
|
'TwoPlayerExtensiveFormGame',
|
|
52
54
|
'TwoPlayerGame',
|
|
53
55
|
'TwoPlayerMultilinearGame',
|
|
@@ -85,11 +85,11 @@ class BlackBoxGame(ABC):
|
|
|
85
85
|
"""
|
|
86
86
|
|
|
87
87
|
@abstractmethod
|
|
88
|
-
def utility(self,
|
|
88
|
+
def utility(self, node, player):
|
|
89
89
|
"""Return the utility given a player and a node.
|
|
90
90
|
|
|
91
|
-
:param player: Player.
|
|
92
91
|
:param node: Node.
|
|
92
|
+
:param player: Player.
|
|
93
93
|
:return: Utility.
|
|
94
94
|
"""
|
|
95
95
|
|
|
@@ -99,9 +99,7 @@ class BlackBoxGame(ABC):
|
|
|
99
99
|
:param node: Node.
|
|
100
100
|
:return: Utilities.
|
|
101
101
|
"""
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return list(map(self.utility(i, node) for i in P))
|
|
102
|
+
return list(map(partial(self.utility, node), range(self.player_count)))
|
|
105
103
|
|
|
106
104
|
@abstractmethod
|
|
107
105
|
def information_set(self, node):
|
|
@@ -128,7 +126,7 @@ class BlackBoxGame(ABC):
|
|
|
128
126
|
"""
|
|
129
127
|
A = self.actions(node)
|
|
130
128
|
|
|
131
|
-
return list(map(self.chance_probability
|
|
129
|
+
return list(map(partial(self.chance_probability, node), A))
|
|
132
130
|
|
|
133
131
|
|
|
134
132
|
@dataclass
|
|
@@ -168,14 +166,14 @@ class _OpenSpielBlackBoxGame(BlackBoxGame):
|
|
|
168
166
|
actions.append(node.action_to_string(a))
|
|
169
167
|
children.append(node.child(a))
|
|
170
168
|
|
|
171
|
-
return actions, children
|
|
169
|
+
return OrderedSet(actions), children
|
|
172
170
|
|
|
173
171
|
def player(self, node):
|
|
174
172
|
i = node.current_player()
|
|
175
173
|
|
|
176
|
-
return None if i
|
|
174
|
+
return None if i < 0 else i
|
|
177
175
|
|
|
178
|
-
def utility(self,
|
|
176
|
+
def utility(self, node, player):
|
|
179
177
|
return node.player_reward(player)
|
|
180
178
|
|
|
181
179
|
def utilities(self, node):
|
|
@@ -191,7 +189,7 @@ class _OpenSpielBlackBoxGame(BlackBoxGame):
|
|
|
191
189
|
return [p for _, p in node.chance_outcomes()]
|
|
192
190
|
|
|
193
191
|
|
|
194
|
-
def
|
|
192
|
+
def open_spiel_game(game):
|
|
195
193
|
"""Load a game from OpenSpiel.
|
|
196
194
|
|
|
197
195
|
:param game: Game in OpenSpiel.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Module for extensive-form games (EFGs)."""
|
|
2
2
|
from noregret.games.extensive_form.games import (
|
|
3
3
|
ExtensiveFormGame,
|
|
4
|
+
to_extensive_form_game,
|
|
4
5
|
TwoPlayerExtensiveFormGame,
|
|
5
6
|
TwoPlayerZeroSumExtensiveFormGame,
|
|
6
7
|
)
|
|
7
8
|
|
|
8
9
|
__all__ = (
|
|
9
10
|
'ExtensiveFormGame',
|
|
11
|
+
'to_extensive_form_game',
|
|
10
12
|
'TwoPlayerExtensiveFormGame',
|
|
11
13
|
'TwoPlayerZeroSumExtensiveFormGame',
|
|
12
14
|
)
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
"""Module for extensive-form games (EFGs)."""
|
|
2
|
+
from collections import defaultdict
|
|
2
3
|
from dataclasses import dataclass
|
|
4
|
+
from functools import partial, singledispatch
|
|
3
5
|
from io import BytesIO
|
|
6
|
+
from itertools import starmap
|
|
4
7
|
|
|
5
8
|
from ordered_set import OrderedSet
|
|
6
9
|
from orjson import dumps, loads
|
|
7
|
-
from scipy.sparse import load_npz, save_npz
|
|
10
|
+
from scipy.sparse import lil_array, load_npz, save_npz
|
|
8
11
|
|
|
12
|
+
from noregret.games.black_box import BlackBoxGame
|
|
13
|
+
from noregret.games.games import Game
|
|
9
14
|
from noregret.games.multilinear import (
|
|
10
15
|
MultilinearGame,
|
|
11
16
|
TwoPlayerMultilinearGame,
|
|
12
17
|
TwoPlayerZeroSumMultilinearGame,
|
|
13
18
|
)
|
|
19
|
+
from noregret.games.normal_form.games import (
|
|
20
|
+
NormalFormGame,
|
|
21
|
+
TwoPlayerNormalFormGame,
|
|
22
|
+
TwoPlayerZeroSumNormalFormGame,
|
|
23
|
+
)
|
|
14
24
|
from noregret.kernels import Serializable
|
|
15
25
|
from noregret.sequence_form_polytopes import SequenceFormPolytope
|
|
16
26
|
from noregret.utilities import tuple_or_none
|
|
@@ -122,12 +132,12 @@ class TwoPlayerExtensiveFormGame(TwoPlayerMultilinearGame, ExtensiveFormGame):
|
|
|
122
132
|
def row_best_response_value(self, column_strategy):
|
|
123
133
|
u = self.row_utility(column_strategy)
|
|
124
134
|
|
|
125
|
-
return self.
|
|
135
|
+
return self.row_sequence_form_polytope.best_response_value(u)
|
|
126
136
|
|
|
127
137
|
def column_best_response_value(self, row_strategy):
|
|
128
138
|
v = self.column_utility(row_strategy)
|
|
129
139
|
|
|
130
|
-
return self.
|
|
140
|
+
return self.column_sequence_form_polytope.best_response_value(v)
|
|
131
141
|
|
|
132
142
|
|
|
133
143
|
@dataclass
|
|
@@ -143,3 +153,123 @@ class TwoPlayerZeroSumExtensiveFormGame(
|
|
|
143
153
|
neg_v = self.column_sequence_form_polytope.worst_response_value(neg_v)
|
|
144
154
|
|
|
145
155
|
return u, neg_v
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _nfg2efg(kernel, game, decision_points='p{}'.format):
|
|
159
|
+
np = kernel.numpy
|
|
160
|
+
scipy = kernel.scipy
|
|
161
|
+
dtype = kernel.data_type
|
|
162
|
+
|
|
163
|
+
if isinstance(game, TwoPlayerZeroSumNormalFormGame):
|
|
164
|
+
type_ = TwoPlayerZeroSumExtensiveFormGame
|
|
165
|
+
elif isinstance(game, TwoPlayerNormalFormGame):
|
|
166
|
+
type_ = TwoPlayerExtensiveFormGame
|
|
167
|
+
else:
|
|
168
|
+
type_ = ExtensiveFormGame
|
|
169
|
+
|
|
170
|
+
d = game.dimensions
|
|
171
|
+
|
|
172
|
+
if isinstance(game, TwoPlayerZeroSumNormalFormGame):
|
|
173
|
+
payoffs = np.zeros(tuple(n + 1 for n in d), dtype)
|
|
174
|
+
payoffs[tuple(slice(1, None) for _ in d)] = game.payoffs
|
|
175
|
+
else:
|
|
176
|
+
payoffs = np.zeros((game.player_count, *(n + 1 for n in d)), dtype)
|
|
177
|
+
payoffs[:, *(slice(1, None) for _ in d)] = game.payoffs
|
|
178
|
+
|
|
179
|
+
payoffs = scipy.sparse.csr_array(payoffs)
|
|
180
|
+
sfps = []
|
|
181
|
+
|
|
182
|
+
for i, A_j in enumerate(game.actions):
|
|
183
|
+
j = decision_points(i)
|
|
184
|
+
sfp = SequenceFormPolytope(kernel, {j: A_j}, {j: None})
|
|
185
|
+
|
|
186
|
+
sfps.append(sfp)
|
|
187
|
+
|
|
188
|
+
sfps = tuple(sfps)
|
|
189
|
+
|
|
190
|
+
return type_(kernel, payoffs, sfps)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _bbg2efg(kernel, game):
|
|
194
|
+
scipy = kernel.scipy
|
|
195
|
+
dtype = kernel.data_type
|
|
196
|
+
P = range(game.player_count)
|
|
197
|
+
A_js = [defaultdict(OrderedSet) for _ in P]
|
|
198
|
+
p_js = [{} for _ in P]
|
|
199
|
+
raw_payoffs = [defaultdict(int) for _ in P]
|
|
200
|
+
|
|
201
|
+
def dfs(h, p, seqs, us):
|
|
202
|
+
A_j, h_primes = game.actions_and_children(h)
|
|
203
|
+
i = game.player(h)
|
|
204
|
+
us = us.copy()
|
|
205
|
+
|
|
206
|
+
for i_prime, v in enumerate(game.utilities(h)):
|
|
207
|
+
us[i_prime] += v
|
|
208
|
+
|
|
209
|
+
if not A_j:
|
|
210
|
+
seqs = tuple(seqs)
|
|
211
|
+
|
|
212
|
+
for i_prime, u in enumerate(us):
|
|
213
|
+
raw_payoffs[i_prime][seqs] += p * u
|
|
214
|
+
elif i is None:
|
|
215
|
+
p_primes = game.chance_probabilities(h)
|
|
216
|
+
|
|
217
|
+
for h_prime, p_prime in zip(h_primes, p_primes):
|
|
218
|
+
dfs(h_prime, p_prime * p, seqs, us)
|
|
219
|
+
else:
|
|
220
|
+
j = game.information_set(h)
|
|
221
|
+
p_j = seqs[i]
|
|
222
|
+
p_js[i][j] = p_j
|
|
223
|
+
|
|
224
|
+
for a, h_prime in zip(A_j, h_primes):
|
|
225
|
+
next_seqs = seqs.copy()
|
|
226
|
+
next_seqs[i] = j, a
|
|
227
|
+
|
|
228
|
+
A_js[i][j].add(a)
|
|
229
|
+
dfs(h_prime, p, next_seqs, us)
|
|
230
|
+
|
|
231
|
+
dfs(game.root_node, 1, [None for _ in P], [0 for _ in P])
|
|
232
|
+
|
|
233
|
+
SFP = partial(SequenceFormPolytope, kernel)
|
|
234
|
+
sfps = tuple(starmap(SFP, zip(A_js, p_js)))
|
|
235
|
+
dimensions = tuple(sfp.column_count for sfp in sfps)
|
|
236
|
+
|
|
237
|
+
if game.is_two_player and game.is_zero_sum:
|
|
238
|
+
type_ = TwoPlayerZeroSumExtensiveFormGame
|
|
239
|
+
payoffs = lil_array(dimensions, dtype=dtype)
|
|
240
|
+
|
|
241
|
+
for seqs, u in raw_payoffs[0].items():
|
|
242
|
+
indices = []
|
|
243
|
+
|
|
244
|
+
for sfp, seq in zip(sfps, seqs):
|
|
245
|
+
indices.append(sfp.column(seq))
|
|
246
|
+
|
|
247
|
+
payoffs[tuple(indices)] = u
|
|
248
|
+
|
|
249
|
+
payoffs = scipy.sparse.csr_array(payoffs)
|
|
250
|
+
else:
|
|
251
|
+
raise NotImplementedError
|
|
252
|
+
|
|
253
|
+
return type_(kernel, payoffs, sfps)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@singledispatch
|
|
257
|
+
def to_extensive_form_game(kernel, game):
|
|
258
|
+
"""Convert a given game to an extensive-form game.
|
|
259
|
+
|
|
260
|
+
:param game: Game.
|
|
261
|
+
:return: Extensive-form game.
|
|
262
|
+
"""
|
|
263
|
+
if isinstance(game, NormalFormGame):
|
|
264
|
+
game = _nfg2efg(kernel, game)
|
|
265
|
+
elif isinstance(game, BlackBoxGame):
|
|
266
|
+
game = _bbg2efg(kernel, game)
|
|
267
|
+
else:
|
|
268
|
+
raise ValueError('unknown game')
|
|
269
|
+
|
|
270
|
+
return game
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@to_extensive_form_game.register
|
|
274
|
+
def _(game: Game):
|
|
275
|
+
return to_extensive_form_game(game.kernel, game)
|
|
@@ -98,12 +98,14 @@ class Game(ABC):
|
|
|
98
98
|
:param strategy_profile: Strategy profile.
|
|
99
99
|
:return: Nash gap.
|
|
100
100
|
"""
|
|
101
|
-
expected_utilities = self.expected_utilities(strategy_profile)
|
|
102
|
-
best_response_values = self.best_response_values(strategy_profile)
|
|
101
|
+
expected_utilities = self.expected_utilities(*strategy_profile)
|
|
102
|
+
best_response_values = self.best_response_values(*strategy_profile)
|
|
103
|
+
nash_gap = 0
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
for u, u_prime in zip(best_response_values, expected_utilities):
|
|
106
|
+
assert u >= u_prime
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
nash_gap += u - u_prime
|
|
107
109
|
|
|
108
110
|
return nash_gap
|
|
109
111
|
|
|
@@ -122,12 +122,6 @@ class TwoPlayerMultilinearGame(TwoPlayerGame, MultilinearGame, ABC):
|
|
|
122
122
|
def expected_column_utility(self, row_strategy, column_strategy):
|
|
123
123
|
return row_strategy @ self.column_payoffs @ column_strategy
|
|
124
124
|
|
|
125
|
-
def expected_utility(self, player, row_strategy, column_strategy):
|
|
126
|
-
return row_strategy @ self.payoffs[player] @ column_strategy
|
|
127
|
-
|
|
128
|
-
def expected_utilities(self, row_strategy, column_strategy):
|
|
129
|
-
return row_strategy @ self.payoffs @ column_strategy
|
|
130
|
-
|
|
131
125
|
|
|
132
126
|
@dataclass
|
|
133
127
|
class TwoPlayerZeroSumMultilinearGame(
|
|
@@ -144,7 +138,7 @@ class TwoPlayerZeroSumMultilinearGame(
|
|
|
144
138
|
def __post_init__(self):
|
|
145
139
|
super(MultilinearGame, self).__post_init__()
|
|
146
140
|
|
|
147
|
-
if self.payoffs.shape !=
|
|
141
|
+
if self.payoffs.shape != self.dimensions:
|
|
148
142
|
raise ValueError('inconsistent dimensions')
|
|
149
143
|
|
|
150
144
|
@property
|
|
@@ -5,6 +5,7 @@ from noregret.games.normal_form.games import (
|
|
|
5
5
|
Chicken,
|
|
6
6
|
GiftExchangeGame,
|
|
7
7
|
MatchingPennies,
|
|
8
|
+
matrix_game,
|
|
8
9
|
NormalFormGame,
|
|
9
10
|
PrisonersDilemma,
|
|
10
11
|
PureCoordination,
|
|
@@ -22,6 +23,7 @@ __all__ = (
|
|
|
22
23
|
'Chicken',
|
|
23
24
|
'GiftExchangeGame',
|
|
24
25
|
'MatchingPennies',
|
|
26
|
+
'matrix_game',
|
|
25
27
|
'NormalFormGame',
|
|
26
28
|
'PrisonersDilemma',
|
|
27
29
|
'PureCoordination',
|
|
@@ -90,10 +90,13 @@ class TwoPlayerNormalFormGame(TwoPlayerMultilinearGame, NormalFormGame):
|
|
|
90
90
|
"""
|
|
91
91
|
return len(self.column_actions)
|
|
92
92
|
|
|
93
|
-
def
|
|
93
|
+
def expected_utilities(self, row_strategy, column_strategy):
|
|
94
|
+
return row_strategy @ self.payoffs @ column_strategy
|
|
95
|
+
|
|
96
|
+
def row_best_response_value(self, column_strategy):
|
|
94
97
|
return self.row_utility(column_strategy).max()
|
|
95
98
|
|
|
96
|
-
def column_best_response_value(self,
|
|
99
|
+
def column_best_response_value(self, row_strategy):
|
|
97
100
|
return self.column_utility(row_strategy).max()
|
|
98
101
|
|
|
99
102
|
|
|
@@ -112,6 +115,22 @@ class TwoPlayerZeroSumNormalFormGame(
|
|
|
112
115
|
return u, neg_v
|
|
113
116
|
|
|
114
117
|
|
|
118
|
+
def matrix_game(kernel, matrix):
|
|
119
|
+
"""Create a matrix game.
|
|
120
|
+
|
|
121
|
+
:param kernel: Kernel.
|
|
122
|
+
:param matrix: Matrix.
|
|
123
|
+
:return: Game.
|
|
124
|
+
"""
|
|
125
|
+
R, C = matrix.shape
|
|
126
|
+
actions = (
|
|
127
|
+
OrderedSet(map('r{}'.format, range(R))),
|
|
128
|
+
OrderedSet(map('c{}'.format, range(C))),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return TwoPlayerZeroSumNormalFormGame(kernel, matrix, actions)
|
|
132
|
+
|
|
133
|
+
|
|
115
134
|
def _2p_nfg(name, kernel):
|
|
116
135
|
with open(Path(__file__).parent / f'{name}.json', 'rb') as file:
|
|
117
136
|
return TwoPlayerNormalFormGame.loads(kernel, file.read())
|
|
@@ -303,8 +303,7 @@ class SequenceFormPolytope:
|
|
|
303
303
|
|
|
304
304
|
u = utility.copy()
|
|
305
305
|
|
|
306
|
-
for
|
|
307
|
-
self._L_R[::-1],
|
|
306
|
+
for L_A, L_B, L_C_B in zip(
|
|
308
307
|
self._L_A[::-1],
|
|
309
308
|
self._L_B[::-1],
|
|
310
309
|
self._L_C_B2[::-1],
|
|
@@ -328,8 +327,7 @@ class SequenceFormPolytope:
|
|
|
328
327
|
|
|
329
328
|
u = utility.copy()
|
|
330
329
|
|
|
331
|
-
for
|
|
332
|
-
self._L_R[::-1],
|
|
330
|
+
for L_A, L_B, L_C_B in zip(
|
|
333
331
|
self._L_A[::-1],
|
|
334
332
|
self._L_B[::-1],
|
|
335
333
|
self._L_C_B2[::-1],
|
|
@@ -31,6 +31,9 @@ def regret_minimization(
|
|
|
31
31
|
"""
|
|
32
32
|
np = game.kernel.numpy
|
|
33
33
|
|
|
34
|
+
if len(regret_minimizers) != game.player_count:
|
|
35
|
+
raise ValueError('inconsistent number of regret minimizers')
|
|
36
|
+
|
|
34
37
|
def average_strategy_profile():
|
|
35
38
|
average_strategy_profile = []
|
|
36
39
|
|
|
@@ -59,27 +62,32 @@ def regret_minimization(
|
|
|
59
62
|
for R in regret_minimizers:
|
|
60
63
|
s.append(R.output(prediction))
|
|
61
64
|
|
|
62
|
-
for
|
|
65
|
+
for t in iterations:
|
|
63
66
|
if alternation:
|
|
64
|
-
for
|
|
65
|
-
R.observe(game.utility(
|
|
67
|
+
for i, R in enumerate(regret_minimizers):
|
|
68
|
+
R.observe(game.utility(i, *s[:i], *s[i + 1:]))
|
|
66
69
|
|
|
67
|
-
s[
|
|
70
|
+
s[i] = R.output(prediction)
|
|
68
71
|
else:
|
|
69
72
|
U = game.utilities(*s)
|
|
70
73
|
|
|
71
|
-
for
|
|
74
|
+
for i, (R, u) in enumerate(zip(regret_minimizers, U)):
|
|
72
75
|
R.observe(u)
|
|
73
76
|
|
|
74
|
-
s[
|
|
77
|
+
s[i] = R.output(prediction)
|
|
75
78
|
|
|
76
|
-
if not checkpoints or
|
|
79
|
+
if not checkpoints or t in checkpoints:
|
|
77
80
|
if update is not None:
|
|
78
|
-
update()
|
|
81
|
+
status = update()
|
|
82
|
+
else:
|
|
83
|
+
status = False
|
|
79
84
|
|
|
80
85
|
if (
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
status
|
|
87
|
+
or (
|
|
88
|
+
target_exploitability is not None
|
|
89
|
+
and exploitability() < target_exploitability
|
|
90
|
+
)
|
|
83
91
|
):
|
|
84
92
|
break
|
|
85
93
|
|
|
@@ -135,18 +143,23 @@ def symmetric_regret_minimization(
|
|
|
135
143
|
|
|
136
144
|
s_neg_1 = [R.output(prediction)] * (game.player_count - 1)
|
|
137
145
|
|
|
138
|
-
for
|
|
146
|
+
for t in iterations:
|
|
139
147
|
R.observe(game.utility(0, *s_neg_1))
|
|
140
148
|
|
|
141
149
|
s_neg_1 = [R.output(prediction)] * (game.player_count - 1)
|
|
142
150
|
|
|
143
|
-
if not checkpoints or
|
|
151
|
+
if not checkpoints or t in checkpoints:
|
|
144
152
|
if update is not None:
|
|
145
|
-
update()
|
|
153
|
+
status = update()
|
|
154
|
+
else:
|
|
155
|
+
status = False
|
|
146
156
|
|
|
147
157
|
if (
|
|
148
|
-
|
|
149
|
-
|
|
158
|
+
status
|
|
159
|
+
or (
|
|
160
|
+
target_exploitability is not None
|
|
161
|
+
and exploitability() < target_exploitability
|
|
162
|
+
)
|
|
150
163
|
):
|
|
151
164
|
break
|
|
152
165
|
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from unittest import main, TestCase
|
|
3
|
+
|
|
4
|
+
import noregret as nr
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GameTestCaseMixin(ABC):
|
|
8
|
+
@abstractmethod
|
|
9
|
+
def uniform_strategy_profile(self, game):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
def test_equivalence(self):
|
|
13
|
+
np = self.KERNEL.numpy
|
|
14
|
+
|
|
15
|
+
for game in self.GAMES:
|
|
16
|
+
x, y = self.uniform_strategy_profile(game)
|
|
17
|
+
|
|
18
|
+
self.assertEqual(
|
|
19
|
+
nr.MultilinearGame.dimensions.fget(game),
|
|
20
|
+
game.dimensions,
|
|
21
|
+
)
|
|
22
|
+
np.testing.assert_allclose(
|
|
23
|
+
nr.Game.utilities(game, x, y),
|
|
24
|
+
game.utilities(x, y),
|
|
25
|
+
)
|
|
26
|
+
np.testing.assert_allclose(
|
|
27
|
+
nr.Game.expected_utilities(game, x, y),
|
|
28
|
+
game.expected_utilities(x, y),
|
|
29
|
+
)
|
|
30
|
+
np.testing.assert_allclose(
|
|
31
|
+
nr.Game.best_response_values(game, x, y),
|
|
32
|
+
game.best_response_values(x, y),
|
|
33
|
+
)
|
|
34
|
+
np.testing.assert_allclose(
|
|
35
|
+
nr.Game.nash_gap(game, x, y),
|
|
36
|
+
game.nash_gap(x, y),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class NormalFormGameTestCase(GameTestCaseMixin, TestCase):
|
|
41
|
+
KERNEL = nr.FloatingPointKernel()
|
|
42
|
+
GAMES = (
|
|
43
|
+
nr.AssuranceGame(KERNEL),
|
|
44
|
+
nr.BattleOfTheSexes(KERNEL),
|
|
45
|
+
nr.Chicken(KERNEL),
|
|
46
|
+
nr.GiftExchangeGame(KERNEL),
|
|
47
|
+
nr.MatchingPennies(KERNEL),
|
|
48
|
+
nr.PrisonersDilemma(KERNEL),
|
|
49
|
+
nr.PureCoordination(KERNEL),
|
|
50
|
+
nr.RockPaperScissors(KERNEL),
|
|
51
|
+
nr.RockPaperScissorsPlus(KERNEL),
|
|
52
|
+
nr.RockPaperSuperscissors(KERNEL),
|
|
53
|
+
nr.StagHunt(KERNEL),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def uniform_strategy_profile(self, game):
|
|
57
|
+
np = self.KERNEL.numpy
|
|
58
|
+
dtype = self.KERNEL.data_type
|
|
59
|
+
|
|
60
|
+
for n in game.dimensions:
|
|
61
|
+
yield np.full(n, 1 / n, dtype)
|
|
62
|
+
|
|
63
|
+
def test_best_response_value(self):
|
|
64
|
+
np = self.KERNEL.numpy
|
|
65
|
+
|
|
66
|
+
for game in self.GAMES:
|
|
67
|
+
x, y = self.uniform_strategy_profile(game)
|
|
68
|
+
|
|
69
|
+
np.testing.assert_allclose(
|
|
70
|
+
nr.NFG.best_response_value(game, 0, y),
|
|
71
|
+
game.best_response_value(0, y),
|
|
72
|
+
)
|
|
73
|
+
np.testing.assert_allclose(
|
|
74
|
+
nr.NFG.best_response_value(game, 1, x),
|
|
75
|
+
game.best_response_value(1, x),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def test_serialization(self):
|
|
79
|
+
for game in self.GAMES:
|
|
80
|
+
raw_game = game.dumps()
|
|
81
|
+
game2 = type(game).loads(self.KERNEL, raw_game)
|
|
82
|
+
raw_game2 = game2.dumps()
|
|
83
|
+
|
|
84
|
+
self.assertEqual(raw_game, raw_game2)
|
|
85
|
+
self.assertTrue((game.payoffs == game2.payoffs).all())
|
|
86
|
+
self.assertEqual(game.actions, game2.actions)
|
|
87
|
+
|
|
88
|
+
def test_matrix_game(self):
|
|
89
|
+
np = self.KERNEL.numpy
|
|
90
|
+
dtype = self.KERNEL.data_type
|
|
91
|
+
A = np.array([[3, 0, -3], [0, 3, -4], [0, 0, 1]], dtype)
|
|
92
|
+
game = nr.matrix_game(self.KERNEL, A)
|
|
93
|
+
x, y = nr.linear_programming(game)
|
|
94
|
+
v = game.expected_row_utility(x, y)
|
|
95
|
+
|
|
96
|
+
self.assertAlmostEqual(v, 0.25)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ExtensiveFormGameTestCase(GameTestCaseMixin, TestCase):
|
|
100
|
+
KERNEL = nr.FloatingPointKernel()
|
|
101
|
+
GAMES = (
|
|
102
|
+
nr.to_efg(nr.MatchingPennies(KERNEL)),
|
|
103
|
+
nr.to_efg(nr.RockPaperScissors(KERNEL)),
|
|
104
|
+
nr.to_efg(nr.RockPaperScissorsPlus(KERNEL)),
|
|
105
|
+
nr.to_efg(nr.RockPaperSuperscissors(KERNEL)),
|
|
106
|
+
nr.to_efg(KERNEL, nr.open_spiel_game('kuhn_poker')),
|
|
107
|
+
nr.to_efg(KERNEL, nr.open_spiel_game('leduc_poker')),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def uniform_strategy_profile(self, game):
|
|
111
|
+
for sfp in game.sequence_form_polytopes:
|
|
112
|
+
yield sfp.to_sequence_form(sfp.behavioral_form_uniform_strategy)
|
|
113
|
+
|
|
114
|
+
def test_best_response_value(self):
|
|
115
|
+
np = self.KERNEL.numpy
|
|
116
|
+
|
|
117
|
+
for game in self.GAMES:
|
|
118
|
+
x, y = self.uniform_strategy_profile(game)
|
|
119
|
+
|
|
120
|
+
np.testing.assert_allclose(
|
|
121
|
+
nr.EFG.best_response_value(game, 0, y),
|
|
122
|
+
game.best_response_value(0, y),
|
|
123
|
+
)
|
|
124
|
+
np.testing.assert_allclose(
|
|
125
|
+
nr.EFG.best_response_value(game, 1, x),
|
|
126
|
+
game.best_response_value(1, x),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def test_serialization(self):
|
|
130
|
+
for game in self.GAMES:
|
|
131
|
+
raw_game = game.dumps()
|
|
132
|
+
game2 = type(game).loads(self.KERNEL, raw_game)
|
|
133
|
+
raw_game2 = game2.dumps()
|
|
134
|
+
|
|
135
|
+
self.assertEqual(raw_game, raw_game2)
|
|
136
|
+
self.assertFalse((game.payoffs != game2.payoffs).count_nonzero())
|
|
137
|
+
|
|
138
|
+
for sfp, sfp2 in zip(
|
|
139
|
+
game.sequence_form_polytopes,
|
|
140
|
+
game2.sequence_form_polytopes,
|
|
141
|
+
):
|
|
142
|
+
self.assertEqual(sfp.actions, sfp2.actions)
|
|
143
|
+
self.assertEqual(sfp.parent_sequences, sfp2.parent_sequences)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class BlackBoxGameTestCase(TestCase):
|
|
147
|
+
GAMES = nr.open_spiel_game('kuhn_poker'), nr.open_spiel_game('leduc_poker')
|
|
148
|
+
|
|
149
|
+
def test_actions_and_children(self):
|
|
150
|
+
for game in self.GAMES:
|
|
151
|
+
h = game.root_node
|
|
152
|
+
A = game.actions(h)
|
|
153
|
+
children = list(map(str, game.children(h)))
|
|
154
|
+
A_children = game.actions_and_children(h)
|
|
155
|
+
|
|
156
|
+
self.assertIsInstance(A_children, tuple)
|
|
157
|
+
self.assertEqual(len(A_children), 2)
|
|
158
|
+
|
|
159
|
+
A_children = A_children[0], list(map(str, A_children[1]))
|
|
160
|
+
|
|
161
|
+
self.assertEqual((A, children), A_children)
|
|
162
|
+
|
|
163
|
+
children2 = list(map(str, nr.BlackBoxGame.children(game, h)))
|
|
164
|
+
A_children2 = nr.BlackBoxGame.actions_and_children(game, h)
|
|
165
|
+
|
|
166
|
+
self.assertIsInstance(A_children, tuple)
|
|
167
|
+
self.assertEqual(len(A_children), 2)
|
|
168
|
+
|
|
169
|
+
A_children2 = A_children2[0], list(map(str, A_children2[1]))
|
|
170
|
+
|
|
171
|
+
self.assertEqual((A, children2), A_children2)
|
|
172
|
+
self.assertEqual(A_children, A_children2)
|
|
173
|
+
|
|
174
|
+
def test_utilities(self):
|
|
175
|
+
for game in self.GAMES:
|
|
176
|
+
h = game.root_node
|
|
177
|
+
us = game.utilities(h)
|
|
178
|
+
us2 = nr.BlackBoxGame.utilities(game, h)
|
|
179
|
+
|
|
180
|
+
self.assertEqual(us, us2)
|
|
181
|
+
|
|
182
|
+
def test_chance_probabilities(self):
|
|
183
|
+
for game in self.GAMES:
|
|
184
|
+
h = game.root_node
|
|
185
|
+
ps = game.chance_probabilities(h)
|
|
186
|
+
ps2 = nr.BlackBoxGame.chance_probabilities(game, h)
|
|
187
|
+
|
|
188
|
+
self.assertEqual(ps, ps2)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == '__main__':
|
|
192
|
+
main() # pragma: no cover
|
|
@@ -14,8 +14,8 @@ class LinearProgrammingTestCase(TestCase):
|
|
|
14
14
|
(nr.to_efg(nr.RockPaperScissors(KERNEL)), 0),
|
|
15
15
|
(nr.to_efg(nr.RockPaperScissorsPlus(KERNEL)), 0),
|
|
16
16
|
(nr.to_efg(nr.RockPaperSuperscissors(KERNEL)), 0),
|
|
17
|
-
(nr.to_efg(KERNEL, nr.
|
|
18
|
-
(nr.to_efg(KERNEL, nr.
|
|
17
|
+
(nr.to_efg(KERNEL, nr.open_spiel_game('kuhn_poker')), -1 / 18),
|
|
18
|
+
(nr.to_efg(KERNEL, nr.open_spiel_game('leduc_poker')), -0.08560642408),
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
def test_linear_programming(self):
|
|
@@ -110,8 +110,8 @@ class SequenceFormPolytopeRegretMinimizationTestCase(TestCase):
|
|
|
110
110
|
(nr.to_efg(nr.RockPaperScissors(KERNEL)), 0),
|
|
111
111
|
(nr.to_efg(nr.RockPaperScissorsPlus(KERNEL)), 0),
|
|
112
112
|
(nr.to_efg(nr.RockPaperSuperscissors(KERNEL)), 0),
|
|
113
|
-
(nr.to_efg(KERNEL, nr.
|
|
114
|
-
(nr.to_efg(KERNEL, nr.
|
|
113
|
+
(nr.to_efg(KERNEL, nr.open_spiel_game('kuhn_poker')), -1 / 18),
|
|
114
|
+
(nr.to_efg(KERNEL, nr.open_spiel_game('leduc_poker')), -0.08560642408),
|
|
115
115
|
)
|
|
116
116
|
REGRET_MINIMIZION_PARAMETERS = (
|
|
117
117
|
(partial(nr.CFR, KERNEL), False, False),
|
|
@@ -157,10 +157,10 @@ class SequenceFormPolytopeRegretMinimization2TestCase(TestCase):
|
|
|
157
157
|
nr.to_efg(nr.RockPaperScissors(KERNEL)),
|
|
158
158
|
nr.to_efg(nr.RockPaperScissorsPlus(KERNEL)),
|
|
159
159
|
nr.to_efg(nr.RockPaperSuperscissors(KERNEL)),
|
|
160
|
-
nr.to_efg(KERNEL, nr.
|
|
161
|
-
nr.to_efg(KERNEL, nr.
|
|
160
|
+
nr.to_efg(KERNEL, nr.open_spiel_game('kuhn_poker')),
|
|
161
|
+
nr.to_efg(KERNEL, nr.open_spiel_game('leduc_poker')),
|
|
162
162
|
)
|
|
163
|
-
PLACES =
|
|
163
|
+
PLACES = 2
|
|
164
164
|
|
|
165
165
|
def test_equivalence(self):
|
|
166
166
|
for game in self.GAMES:
|
|
@@ -186,6 +186,28 @@ class SequenceFormPolytopeRegretMinimization2TestCase(TestCase):
|
|
|
186
186
|
self.assertAlmostEqual(e, e2, self.PLACES)
|
|
187
187
|
self.assertAlmostEqual(v, v2, self.PLACES)
|
|
188
188
|
|
|
189
|
+
x_bar, y_bar = nr.regret_minimization(
|
|
190
|
+
game,
|
|
191
|
+
nr.CFR(self.KERNEL, game.row_sequence_form_polytope),
|
|
192
|
+
nr.CFR(self.KERNEL, game.column_sequence_form_polytope),
|
|
193
|
+
prediction=True,
|
|
194
|
+
progress_bar=False,
|
|
195
|
+
)
|
|
196
|
+
e = game.exploitability(x_bar, y_bar)
|
|
197
|
+
v = game.expected_row_utility(x_bar, y_bar)
|
|
198
|
+
x_bar2, y_bar2 = nr.regret_minimization(
|
|
199
|
+
game,
|
|
200
|
+
nr.CFR2(self.KERNEL, game.row_sequence_form_polytope),
|
|
201
|
+
nr.CFR2(self.KERNEL, game.column_sequence_form_polytope),
|
|
202
|
+
prediction=True,
|
|
203
|
+
progress_bar=False,
|
|
204
|
+
)
|
|
205
|
+
e2 = game.exploitability(x_bar2, y_bar2)
|
|
206
|
+
v2 = game.expected_row_utility(x_bar2, y_bar2)
|
|
207
|
+
|
|
208
|
+
self.assertAlmostEqual(e, e2, self.PLACES)
|
|
209
|
+
self.assertAlmostEqual(v, v2, self.PLACES)
|
|
210
|
+
|
|
189
211
|
|
|
190
212
|
if __name__ == '__main__':
|
|
191
213
|
main() # pragma: no cover
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: noregret
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev7
|
|
4
4
|
Summary: No-regret learning dynamics
|
|
5
5
|
Home-page: https://github.com/uoftcprg/noregret
|
|
6
6
|
Author: Universal, Open, Free, and Transparent Computer Poker Research Group
|
|
@@ -52,7 +52,7 @@ Dynamic: summary
|
|
|
52
52
|
NoRegret
|
|
53
53
|
========
|
|
54
54
|
|
|
55
|
-
NoRegret is an open-source software library for no-regret learning dynamics and computational game solving, developed by the Universal, Open, Free, and Transparent Computer Poker Research Group. NoRegret implements an extensive array of regret minimizers and game solvers, and also supports GPU-acceleration. The library can be used in a variety of use cases, from solving games to conducting research in online convex optimization. NoRegret's reliability has been established through extensive doctests and unit tests, achieving
|
|
55
|
+
NoRegret is an open-source software library for no-regret learning dynamics and computational game solving, developed by the Universal, Open, Free, and Transparent Computer Poker Research Group. NoRegret implements an extensive array of regret minimizers and game solvers, and also supports GPU-acceleration. The library can be used in a variety of use cases, from solving games to conducting research in online convex optimization. NoRegret's reliability has been established through extensive doctests and unit tests, achieving 95% code coverage.
|
|
56
56
|
|
|
57
57
|
Features
|
|
58
58
|
--------
|
|
@@ -94,8 +94,8 @@ The code snippet below demonstrates how one can solve games via regret minimizat
|
|
|
94
94
|
KERNEL = nr.FloatingPointKernel()
|
|
95
95
|
GAMES = {
|
|
96
96
|
'Rock paper superscissors': nr.to_efg(nr.RockPaperSuperscissors(KERNEL)),
|
|
97
|
-
'Kuhn poker': nr.to_efg(KERNEL, nr.
|
|
98
|
-
'Leduc poker': nr.to_efg(KERNEL, nr.
|
|
97
|
+
'Kuhn poker': nr.to_efg(KERNEL, nr.open_spiel_game('kuhn_poker')),
|
|
98
|
+
'Leduc poker': nr.to_efg(KERNEL, nr.open_spiel_game('leduc_poker')),
|
|
99
99
|
}
|
|
100
100
|
PARAMETERS = {
|
|
101
101
|
'CFR': (nr.CFR, False, False),
|
|
@@ -180,7 +180,7 @@ The code snippet below demonstrates how one can solve games while leveraging GPU
|
|
|
180
180
|
import noregret as nr
|
|
181
181
|
|
|
182
182
|
KERNEL = nr.CUDAKernel()
|
|
183
|
-
GAME = nr.to_efg(KERNEL, nr.
|
|
183
|
+
GAME = nr.to_efg(KERNEL, nr.open_spiel_game('liars_dice'))
|
|
184
184
|
PARAMETERS = nr.CFR, True, False
|
|
185
185
|
|
|
186
186
|
|
|
@@ -220,8 +220,8 @@ The code snippet below demonstrates how one can solve games via linear programmi
|
|
|
220
220
|
KERNEL = nr.FloatingPointKernel()
|
|
221
221
|
GAMES = {
|
|
222
222
|
'Rock paper superscissors': nr.RockPaperSuperscissors(KERNEL),
|
|
223
|
-
'Kuhn poker': nr.to_efg(KERNEL, nr.
|
|
224
|
-
'Leduc poker': nr.to_efg(KERNEL, nr.
|
|
223
|
+
'Kuhn poker': nr.to_efg(KERNEL, nr.open_spiel_game('kuhn_poker')),
|
|
224
|
+
'Leduc poker': nr.to_efg(KERNEL, nr.open_spiel_game('leduc_poker')),
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
|
|
@@ -14,7 +14,6 @@ noregret/games/__init__.py
|
|
|
14
14
|
noregret/games/black_box.py
|
|
15
15
|
noregret/games/games.py
|
|
16
16
|
noregret/games/multilinear.py
|
|
17
|
-
noregret/games/utilities.py
|
|
18
17
|
noregret/games/extensive_form/__init__.py
|
|
19
18
|
noregret/games/extensive_form/games.py
|
|
20
19
|
noregret/games/normal_form/__init__.py
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
from collections import defaultdict
|
|
2
|
-
from functools import partial, singledispatch
|
|
3
|
-
from itertools import starmap
|
|
4
|
-
|
|
5
|
-
from ordered_set import OrderedSet
|
|
6
|
-
from scipy.sparse import lil_array
|
|
7
|
-
|
|
8
|
-
from noregret.games.black_box import BlackBoxGame
|
|
9
|
-
from noregret.games.extensive_form.games import (
|
|
10
|
-
ExtensiveFormGame,
|
|
11
|
-
TwoPlayerExtensiveFormGame,
|
|
12
|
-
TwoPlayerZeroSumExtensiveFormGame,
|
|
13
|
-
)
|
|
14
|
-
from noregret.games.games import Game
|
|
15
|
-
from noregret.games.normal_form.games import (
|
|
16
|
-
NormalFormGame,
|
|
17
|
-
TwoPlayerNormalFormGame,
|
|
18
|
-
TwoPlayerZeroSumNormalFormGame,
|
|
19
|
-
)
|
|
20
|
-
from noregret.sequence_form_polytopes import SequenceFormPolytope
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _nfg2efg(kernel, game, decision_points=str):
|
|
24
|
-
np = kernel.numpy
|
|
25
|
-
scipy = kernel.scipy
|
|
26
|
-
dtype = kernel.data_type
|
|
27
|
-
|
|
28
|
-
if isinstance(game, TwoPlayerZeroSumNormalFormGame):
|
|
29
|
-
type_ = TwoPlayerZeroSumExtensiveFormGame
|
|
30
|
-
elif isinstance(game, TwoPlayerNormalFormGame):
|
|
31
|
-
type_ = TwoPlayerExtensiveFormGame
|
|
32
|
-
else:
|
|
33
|
-
type_ = ExtensiveFormGame
|
|
34
|
-
|
|
35
|
-
d = game.dimensions
|
|
36
|
-
|
|
37
|
-
if isinstance(game, TwoPlayerZeroSumNormalFormGame):
|
|
38
|
-
payoffs = np.zeros(tuple(n + 1 for n in d), dtype)
|
|
39
|
-
payoffs[tuple(slice(1, None) for _ in d)] = game.payoffs
|
|
40
|
-
else:
|
|
41
|
-
payoffs = np.zeros((game.player_count, *(n + 1 for n in d)), dtype)
|
|
42
|
-
payoffs[:, *(slice(1, None) for _ in d)] = game.payoffs
|
|
43
|
-
|
|
44
|
-
payoffs = scipy.sparse.csr_array(payoffs)
|
|
45
|
-
sfps = []
|
|
46
|
-
|
|
47
|
-
for i, A_j in enumerate(game.actions):
|
|
48
|
-
j = decision_points(i)
|
|
49
|
-
sfp = SequenceFormPolytope(kernel, {j: A_j}, {j: None})
|
|
50
|
-
|
|
51
|
-
sfps.append(sfp)
|
|
52
|
-
|
|
53
|
-
sfps = tuple(sfps)
|
|
54
|
-
|
|
55
|
-
return type_(kernel, payoffs, sfps)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _bbg2efg(kernel, game):
|
|
59
|
-
scipy = kernel.scipy
|
|
60
|
-
dtype = kernel.data_type
|
|
61
|
-
P = range(game.player_count)
|
|
62
|
-
A_js = [defaultdict(OrderedSet) for _ in P]
|
|
63
|
-
p_js = [{} for _ in P]
|
|
64
|
-
raw_payoffs = [defaultdict(int) for _ in P]
|
|
65
|
-
|
|
66
|
-
def dfs(h, p, seqs, us):
|
|
67
|
-
A_j, h_primes = game.actions_and_children(h)
|
|
68
|
-
i = game.player(h)
|
|
69
|
-
us = us.copy()
|
|
70
|
-
|
|
71
|
-
for i_prime, v in enumerate(game.utilities(h)):
|
|
72
|
-
us[i_prime] += v
|
|
73
|
-
|
|
74
|
-
if not A_j:
|
|
75
|
-
seqs = tuple(seqs)
|
|
76
|
-
|
|
77
|
-
for i_prime, u in enumerate(us):
|
|
78
|
-
raw_payoffs[i_prime][seqs] += p * u
|
|
79
|
-
elif i is None:
|
|
80
|
-
p_primes = game.chance_probabilities(h)
|
|
81
|
-
|
|
82
|
-
for h_prime, p_prime in zip(h_primes, p_primes):
|
|
83
|
-
dfs(h_prime, p_prime * p, seqs, us)
|
|
84
|
-
else:
|
|
85
|
-
j = game.information_set(h)
|
|
86
|
-
p_j = seqs[i]
|
|
87
|
-
p_js[i][j] = p_j
|
|
88
|
-
|
|
89
|
-
for a, h_prime in zip(A_j, h_primes):
|
|
90
|
-
next_seqs = seqs.copy()
|
|
91
|
-
next_seqs[i] = j, a
|
|
92
|
-
|
|
93
|
-
A_js[i][j].add(a)
|
|
94
|
-
dfs(h_prime, p, next_seqs, us)
|
|
95
|
-
|
|
96
|
-
dfs(game.root_node, 1, [None for _ in P], [0 for _ in P])
|
|
97
|
-
|
|
98
|
-
SFP = partial(SequenceFormPolytope, kernel)
|
|
99
|
-
sfps = tuple(starmap(SFP, zip(A_js, p_js)))
|
|
100
|
-
dimensions = tuple(sfp.column_count for sfp in sfps)
|
|
101
|
-
|
|
102
|
-
if game.is_two_player and game.is_zero_sum:
|
|
103
|
-
type_ = TwoPlayerZeroSumExtensiveFormGame
|
|
104
|
-
payoffs = lil_array(dimensions, dtype=dtype)
|
|
105
|
-
|
|
106
|
-
for seqs, u in raw_payoffs[0].items():
|
|
107
|
-
indices = []
|
|
108
|
-
|
|
109
|
-
for sfp, seq in zip(sfps, seqs):
|
|
110
|
-
indices.append(sfp.column(seq))
|
|
111
|
-
|
|
112
|
-
payoffs[tuple(indices)] = u
|
|
113
|
-
|
|
114
|
-
payoffs = scipy.sparse.csr_array(payoffs)
|
|
115
|
-
else:
|
|
116
|
-
raise NotImplementedError
|
|
117
|
-
|
|
118
|
-
return type_(kernel, payoffs, sfps)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
@singledispatch
|
|
122
|
-
def to_extensive_form(kernel, game):
|
|
123
|
-
"""Convert a given game to an extensive-form game.
|
|
124
|
-
|
|
125
|
-
:param game: Game.
|
|
126
|
-
:return: Extensive-form game.
|
|
127
|
-
"""
|
|
128
|
-
if isinstance(game, NormalFormGame):
|
|
129
|
-
game = _nfg2efg(kernel, game)
|
|
130
|
-
elif isinstance(game, BlackBoxGame):
|
|
131
|
-
game = _bbg2efg(kernel, game)
|
|
132
|
-
else:
|
|
133
|
-
raise ValueError('unknown game')
|
|
134
|
-
|
|
135
|
-
return game
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
@to_extensive_form.register
|
|
139
|
-
def _(game: Game):
|
|
140
|
-
return to_extensive_form(game.kernel, game)
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
from unittest import main, TestCase
|
|
2
|
-
|
|
3
|
-
import noregret as nr
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class NormalFormGameTestCase(TestCase):
|
|
7
|
-
KERNEL = nr.FloatingPointKernel()
|
|
8
|
-
GAMES = (
|
|
9
|
-
nr.AssuranceGame(KERNEL),
|
|
10
|
-
nr.BattleOfTheSexes(KERNEL),
|
|
11
|
-
nr.Chicken(KERNEL),
|
|
12
|
-
nr.GiftExchangeGame(KERNEL),
|
|
13
|
-
nr.MatchingPennies(KERNEL),
|
|
14
|
-
nr.PrisonersDilemma(KERNEL),
|
|
15
|
-
nr.PureCoordination(KERNEL),
|
|
16
|
-
nr.RockPaperScissors(KERNEL),
|
|
17
|
-
nr.RockPaperScissorsPlus(KERNEL),
|
|
18
|
-
nr.RockPaperSuperscissors(KERNEL),
|
|
19
|
-
nr.StagHunt(KERNEL),
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
def test_serialization(self):
|
|
23
|
-
for game in self.GAMES:
|
|
24
|
-
raw_game = game.dumps()
|
|
25
|
-
game2 = type(game).loads(self.KERNEL, raw_game)
|
|
26
|
-
raw_game2 = game2.dumps()
|
|
27
|
-
|
|
28
|
-
self.assertEqual(raw_game, raw_game2)
|
|
29
|
-
self.assertTrue((game.payoffs == game2.payoffs).all())
|
|
30
|
-
self.assertEqual(game.actions, game2.actions)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class ExtensiveFormGameTestCase(TestCase):
|
|
34
|
-
KERNEL = nr.FloatingPointKernel()
|
|
35
|
-
GAMES = (
|
|
36
|
-
nr.to_efg(nr.MatchingPennies(KERNEL)),
|
|
37
|
-
nr.to_efg(nr.RockPaperScissors(KERNEL)),
|
|
38
|
-
nr.to_efg(nr.RockPaperScissorsPlus(KERNEL)),
|
|
39
|
-
nr.to_efg(nr.RockPaperSuperscissors(KERNEL)),
|
|
40
|
-
nr.to_efg(KERNEL, nr.from_open_spiel('kuhn_poker')),
|
|
41
|
-
nr.to_efg(KERNEL, nr.from_open_spiel('leduc_poker')),
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
def test_serialization(self):
|
|
45
|
-
for game in self.GAMES:
|
|
46
|
-
raw_game = game.dumps()
|
|
47
|
-
game2 = type(game).loads(self.KERNEL, raw_game)
|
|
48
|
-
raw_game2 = game2.dumps()
|
|
49
|
-
|
|
50
|
-
self.assertEqual(raw_game, raw_game2)
|
|
51
|
-
self.assertFalse((game.payoffs != game2.payoffs).count_nonzero())
|
|
52
|
-
|
|
53
|
-
for sfp, sfp2 in zip(
|
|
54
|
-
game.sequence_form_polytopes,
|
|
55
|
-
game2.sequence_form_polytopes,
|
|
56
|
-
):
|
|
57
|
-
self.assertEqual(sfp.actions, sfp2.actions)
|
|
58
|
-
self.assertEqual(sfp.parent_sequences, sfp2.parent_sequences)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if __name__ == '__main__':
|
|
62
|
-
main() # pragma: no cover
|
|
File without changes
|
|
File without changes
|
{noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/battle-of-the-sexes.json
RENAMED
|
File without changes
|
|
File without changes
|
{noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/gift-exchange-game.json
RENAMED
|
File without changes
|
{noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/matching-pennies.json
RENAMED
|
File without changes
|
{noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/prisoners-dilemma.json
RENAMED
|
File without changes
|
{noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/pure-coordination.json
RENAMED
|
File without changes
|
{noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-scissors-plus.json
RENAMED
|
File without changes
|
{noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-scissors.json
RENAMED
|
File without changes
|
{noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-superscissors.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
"""Module for regret minimizers."""
|
|
2
|
-
from noregret.regret_minimizers.regret_minimizers import (
|
|
3
|
-
RegretMinimizer,
|
|
4
|
-
SwapRegretMinimizer,
|
|
5
|
-
)
|
|
6
2
|
from noregret.regret_minimizers.probability_simplices import (
|
|
7
3
|
BlumMansour,
|
|
8
4
|
DiscountedRegretMatching,
|
|
@@ -17,6 +13,10 @@ from noregret.regret_minimizers.probability_simplices import (
|
|
|
17
13
|
RegretMatching,
|
|
18
14
|
RegretMatchingPlus,
|
|
19
15
|
)
|
|
16
|
+
from noregret.regret_minimizers.regret_minimizers import (
|
|
17
|
+
RegretMinimizer,
|
|
18
|
+
SwapRegretMinimizer,
|
|
19
|
+
)
|
|
20
20
|
from noregret.regret_minimizers.sequence_form_polytopes import (
|
|
21
21
|
CounterfactualRegretMinimization,
|
|
22
22
|
CounterfactualRegretMinimization2,
|
{noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/regret_minimizers/probability_simplices.py
RENAMED
|
File without changes
|
|
File without changes
|
{noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/regret_minimizers/sequence_form_polytopes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|