noregret 0.0.0.dev6__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.
Files changed (46) hide show
  1. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/PKG-INFO +6 -6
  2. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/README.rst +5 -5
  3. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/__init__.py +8 -6
  4. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/__init__.py +6 -4
  5. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/black_box.py +2 -2
  6. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/extensive_form/__init__.py +2 -0
  7. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/extensive_form/games.py +131 -1
  8. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/__init__.py +2 -0
  9. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/games.py +16 -0
  10. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/solvers/regret_minimization.py +25 -15
  11. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/tests/test_games.py +13 -3
  12. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/tests/test_linear_programming.py +2 -2
  13. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/tests/test_regret_minimization.py +27 -5
  14. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret.egg-info/PKG-INFO +6 -6
  15. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret.egg-info/SOURCES.txt +0 -1
  16. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/setup.py +1 -1
  17. noregret-0.0.0.dev6/noregret/games/utilities.py +0 -141
  18. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/LICENSE +0 -0
  19. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/games.py +0 -0
  20. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/multilinear.py +0 -0
  21. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/assurance-game.json +0 -0
  22. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/battle-of-the-sexes.json +0 -0
  23. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/chicken.json +0 -0
  24. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/gift-exchange-game.json +0 -0
  25. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/matching-pennies.json +0 -0
  26. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/prisoners-dilemma.json +0 -0
  27. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/pure-coordination.json +0 -0
  28. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-scissors-plus.json +0 -0
  29. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-scissors.json +0 -0
  30. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-superscissors.json +0 -0
  31. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/games/normal_form/stag-hunt.json +0 -0
  32. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/kernels.py +0 -0
  33. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/regret_minimizers/__init__.py +4 -4
  34. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/regret_minimizers/probability_simplices.py +0 -0
  35. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/regret_minimizers/regret_minimizers.py +0 -0
  36. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/regret_minimizers/sequence_form_polytopes.py +0 -0
  37. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/sequence_form_polytopes.py +0 -0
  38. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/solvers/__init__.py +0 -0
  39. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/solvers/linear_programming.py +0 -0
  40. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/tests/__init__.py +0 -0
  41. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/tests/test_sequence_form_polytopes.py +0 -0
  42. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret/utilities.py +0 -0
  43. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret.egg-info/dependency_links.txt +0 -0
  44. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret.egg-info/requires.txt +0 -0
  45. {noregret-0.0.0.dev6 → noregret-0.0.0.dev7}/noregret.egg-info/top_level.txt +0 -0
  46. {noregret-0.0.0.dev6 → 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.dev6
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
@@ -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.from_open_spiel('kuhn_poker')),
98
- 'Leduc poker': nr.to_efg(KERNEL, nr.from_open_spiel('leduc_poker')),
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.from_open_spiel('liars_dice'))
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.from_open_spiel('kuhn_poker')),
224
- 'Leduc poker': nr.to_efg(KERNEL, nr.from_open_spiel('leduc_poker')),
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
 
@@ -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.from_open_spiel('kuhn_poker')),
48
- 'Leduc poker': nr.to_efg(KERNEL, nr.from_open_spiel('leduc_poker')),
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.from_open_spiel('liars_dice'))
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.from_open_spiel('kuhn_poker')),
174
- 'Leduc poker': nr.to_efg(KERNEL, nr.from_open_spiel('leduc_poker')),
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
- to_extensive_form,
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 = to_extensive_form
113
- """Alias for :func:`noregret.to_extensive_form`."""
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
- 'from_open_spiel',
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
- 'to_extensive_form',
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, from_open_spiel
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
- 'to_extensive_form',
52
+ 'to_extensive_form_game',
51
53
  'TwoPlayerExtensiveFormGame',
52
54
  'TwoPlayerGame',
53
55
  'TwoPlayerMultilinearGame',
@@ -171,7 +171,7 @@ class _OpenSpielBlackBoxGame(BlackBoxGame):
171
171
  def player(self, node):
172
172
  i = node.current_player()
173
173
 
174
- return None if i == -1 else i
174
+ return None if i < 0 else i
175
175
 
176
176
  def utility(self, node, player):
177
177
  return node.player_reward(player)
@@ -189,7 +189,7 @@ class _OpenSpielBlackBoxGame(BlackBoxGame):
189
189
  return [p for _, p in node.chance_outcomes()]
190
190
 
191
191
 
192
- def from_open_spiel(game):
192
+ def open_spiel_game(game):
193
193
  """Load a game from OpenSpiel.
194
194
 
195
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
@@ -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)
@@ -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',
@@ -115,6 +115,22 @@ class TwoPlayerZeroSumNormalFormGame(
115
115
  return u, neg_v
116
116
 
117
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
+
118
134
  def _2p_nfg(name, kernel):
119
135
  with open(Path(__file__).parent / f'{name}.json', 'rb') as file:
120
136
  return TwoPlayerNormalFormGame.loads(kernel, file.read())
@@ -62,27 +62,32 @@ def regret_minimization(
62
62
  for R in regret_minimizers:
63
63
  s.append(R.output(prediction))
64
64
 
65
- for i in iterations:
65
+ for t in iterations:
66
66
  if alternation:
67
- for j, R in enumerate(regret_minimizers):
68
- R.observe(game.utility(j, *s[:j], *s[j + 1:]))
67
+ for i, R in enumerate(regret_minimizers):
68
+ R.observe(game.utility(i, *s[:i], *s[i + 1:]))
69
69
 
70
- s[j] = R.output(prediction)
70
+ s[i] = R.output(prediction)
71
71
  else:
72
72
  U = game.utilities(*s)
73
73
 
74
- for j, (R, u) in enumerate(zip(regret_minimizers, U)):
74
+ for i, (R, u) in enumerate(zip(regret_minimizers, U)):
75
75
  R.observe(u)
76
76
 
77
- s[j] = R.output(prediction)
77
+ s[i] = R.output(prediction)
78
78
 
79
- if not checkpoints or i in checkpoints:
79
+ if not checkpoints or t in checkpoints:
80
80
  if update is not None:
81
- update()
81
+ status = update()
82
+ else:
83
+ status = False
82
84
 
83
85
  if (
84
- target_exploitability is not None
85
- and exploitability() < target_exploitability
86
+ status
87
+ or (
88
+ target_exploitability is not None
89
+ and exploitability() < target_exploitability
90
+ )
86
91
  ):
87
92
  break
88
93
 
@@ -138,18 +143,23 @@ def symmetric_regret_minimization(
138
143
 
139
144
  s_neg_1 = [R.output(prediction)] * (game.player_count - 1)
140
145
 
141
- for i in iterations:
146
+ for t in iterations:
142
147
  R.observe(game.utility(0, *s_neg_1))
143
148
 
144
149
  s_neg_1 = [R.output(prediction)] * (game.player_count - 1)
145
150
 
146
- if not checkpoints or i in checkpoints:
151
+ if not checkpoints or t in checkpoints:
147
152
  if update is not None:
148
- update()
153
+ status = update()
154
+ else:
155
+ status = False
149
156
 
150
157
  if (
151
- target_exploitability is not None
152
- and exploitability() < target_exploitability
158
+ status
159
+ or (
160
+ target_exploitability is not None
161
+ and exploitability() < target_exploitability
162
+ )
153
163
  ):
154
164
  break
155
165
 
@@ -85,6 +85,16 @@ class NormalFormGameTestCase(GameTestCaseMixin, TestCase):
85
85
  self.assertTrue((game.payoffs == game2.payoffs).all())
86
86
  self.assertEqual(game.actions, game2.actions)
87
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
+
88
98
 
89
99
  class ExtensiveFormGameTestCase(GameTestCaseMixin, TestCase):
90
100
  KERNEL = nr.FloatingPointKernel()
@@ -93,8 +103,8 @@ class ExtensiveFormGameTestCase(GameTestCaseMixin, TestCase):
93
103
  nr.to_efg(nr.RockPaperScissors(KERNEL)),
94
104
  nr.to_efg(nr.RockPaperScissorsPlus(KERNEL)),
95
105
  nr.to_efg(nr.RockPaperSuperscissors(KERNEL)),
96
- nr.to_efg(KERNEL, nr.from_open_spiel('kuhn_poker')),
97
- nr.to_efg(KERNEL, nr.from_open_spiel('leduc_poker')),
106
+ nr.to_efg(KERNEL, nr.open_spiel_game('kuhn_poker')),
107
+ nr.to_efg(KERNEL, nr.open_spiel_game('leduc_poker')),
98
108
  )
99
109
 
100
110
  def uniform_strategy_profile(self, game):
@@ -134,7 +144,7 @@ class ExtensiveFormGameTestCase(GameTestCaseMixin, TestCase):
134
144
 
135
145
 
136
146
  class BlackBoxGameTestCase(TestCase):
137
- GAMES = nr.from_open_spiel('kuhn_poker'), nr.from_open_spiel('leduc_poker')
147
+ GAMES = nr.open_spiel_game('kuhn_poker'), nr.open_spiel_game('leduc_poker')
138
148
 
139
149
  def test_actions_and_children(self):
140
150
  for game in self.GAMES:
@@ -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.from_open_spiel('kuhn_poker')), -1 / 18),
18
- (nr.to_efg(KERNEL, nr.from_open_spiel('leduc_poker')), -0.08560642408),
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.from_open_spiel('kuhn_poker')), -1 / 18),
114
- (nr.to_efg(KERNEL, nr.from_open_spiel('leduc_poker')), -0.08560642408),
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.from_open_spiel('kuhn_poker')),
161
- nr.to_efg(KERNEL, nr.from_open_spiel('leduc_poker')),
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 = 6
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.dev6
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
@@ -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.from_open_spiel('kuhn_poker')),
98
- 'Leduc poker': nr.to_efg(KERNEL, nr.from_open_spiel('leduc_poker')),
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.from_open_spiel('liars_dice'))
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.from_open_spiel('kuhn_poker')),
224
- 'Leduc poker': nr.to_efg(KERNEL, nr.from_open_spiel('leduc_poker')),
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
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
4
4
 
5
5
  setup(
6
6
  name='noregret',
7
- version='0.0.0.dev6',
7
+ version='0.0.0.dev7',
8
8
  description='No-regret learning dynamics',
9
9
  long_description=open('README.rst').read(),
10
10
  long_description_content_type='text/x-rst',
@@ -1,141 +0,0 @@
1
- """Module for utilities."""
2
- from collections import defaultdict
3
- from functools import partial, singledispatch
4
- from itertools import starmap
5
-
6
- from ordered_set import OrderedSet
7
- from scipy.sparse import lil_array
8
-
9
- from noregret.games.black_box import BlackBoxGame
10
- from noregret.games.extensive_form.games import (
11
- ExtensiveFormGame,
12
- TwoPlayerExtensiveFormGame,
13
- TwoPlayerZeroSumExtensiveFormGame,
14
- )
15
- from noregret.games.games import Game
16
- from noregret.games.normal_form.games import (
17
- NormalFormGame,
18
- TwoPlayerNormalFormGame,
19
- TwoPlayerZeroSumNormalFormGame,
20
- )
21
- from noregret.sequence_form_polytopes import SequenceFormPolytope
22
-
23
-
24
- def _nfg2efg(kernel, game, decision_points=str):
25
- np = kernel.numpy
26
- scipy = kernel.scipy
27
- dtype = kernel.data_type
28
-
29
- if isinstance(game, TwoPlayerZeroSumNormalFormGame):
30
- type_ = TwoPlayerZeroSumExtensiveFormGame
31
- elif isinstance(game, TwoPlayerNormalFormGame):
32
- type_ = TwoPlayerExtensiveFormGame
33
- else:
34
- type_ = ExtensiveFormGame
35
-
36
- d = game.dimensions
37
-
38
- if isinstance(game, TwoPlayerZeroSumNormalFormGame):
39
- payoffs = np.zeros(tuple(n + 1 for n in d), dtype)
40
- payoffs[tuple(slice(1, None) for _ in d)] = game.payoffs
41
- else:
42
- payoffs = np.zeros((game.player_count, *(n + 1 for n in d)), dtype)
43
- payoffs[:, *(slice(1, None) for _ in d)] = game.payoffs
44
-
45
- payoffs = scipy.sparse.csr_array(payoffs)
46
- sfps = []
47
-
48
- for i, A_j in enumerate(game.actions):
49
- j = decision_points(i)
50
- sfp = SequenceFormPolytope(kernel, {j: A_j}, {j: None})
51
-
52
- sfps.append(sfp)
53
-
54
- sfps = tuple(sfps)
55
-
56
- return type_(kernel, payoffs, sfps)
57
-
58
-
59
- def _bbg2efg(kernel, game):
60
- scipy = kernel.scipy
61
- dtype = kernel.data_type
62
- P = range(game.player_count)
63
- A_js = [defaultdict(OrderedSet) for _ in P]
64
- p_js = [{} for _ in P]
65
- raw_payoffs = [defaultdict(int) for _ in P]
66
-
67
- def dfs(h, p, seqs, us):
68
- A_j, h_primes = game.actions_and_children(h)
69
- i = game.player(h)
70
- us = us.copy()
71
-
72
- for i_prime, v in enumerate(game.utilities(h)):
73
- us[i_prime] += v
74
-
75
- if not A_j:
76
- seqs = tuple(seqs)
77
-
78
- for i_prime, u in enumerate(us):
79
- raw_payoffs[i_prime][seqs] += p * u
80
- elif i is None:
81
- p_primes = game.chance_probabilities(h)
82
-
83
- for h_prime, p_prime in zip(h_primes, p_primes):
84
- dfs(h_prime, p_prime * p, seqs, us)
85
- else:
86
- j = game.information_set(h)
87
- p_j = seqs[i]
88
- p_js[i][j] = p_j
89
-
90
- for a, h_prime in zip(A_j, h_primes):
91
- next_seqs = seqs.copy()
92
- next_seqs[i] = j, a
93
-
94
- A_js[i][j].add(a)
95
- dfs(h_prime, p, next_seqs, us)
96
-
97
- dfs(game.root_node, 1, [None for _ in P], [0 for _ in P])
98
-
99
- SFP = partial(SequenceFormPolytope, kernel)
100
- sfps = tuple(starmap(SFP, zip(A_js, p_js)))
101
- dimensions = tuple(sfp.column_count for sfp in sfps)
102
-
103
- if game.is_two_player and game.is_zero_sum:
104
- type_ = TwoPlayerZeroSumExtensiveFormGame
105
- payoffs = lil_array(dimensions, dtype=dtype)
106
-
107
- for seqs, u in raw_payoffs[0].items():
108
- indices = []
109
-
110
- for sfp, seq in zip(sfps, seqs):
111
- indices.append(sfp.column(seq))
112
-
113
- payoffs[tuple(indices)] = u
114
-
115
- payoffs = scipy.sparse.csr_array(payoffs)
116
- else:
117
- raise NotImplementedError
118
-
119
- return type_(kernel, payoffs, sfps)
120
-
121
-
122
- @singledispatch
123
- def to_extensive_form(kernel, game):
124
- """Convert a given game to an extensive-form game.
125
-
126
- :param game: Game.
127
- :return: Extensive-form game.
128
- """
129
- if isinstance(game, NormalFormGame):
130
- game = _nfg2efg(kernel, game)
131
- elif isinstance(game, BlackBoxGame):
132
- game = _bbg2efg(kernel, game)
133
- else:
134
- raise ValueError('unknown game')
135
-
136
- return game
137
-
138
-
139
- @to_extensive_form.register
140
- def _(game: Game):
141
- return to_extensive_form(game.kernel, game)
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,
File without changes