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.
Files changed (47) hide show
  1. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/PKG-INFO +7 -7
  2. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/README.rst +6 -6
  3. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/__init__.py +8 -6
  4. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/__init__.py +6 -4
  5. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/black_box.py +8 -10
  6. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/extensive_form/__init__.py +2 -0
  7. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/extensive_form/games.py +133 -3
  8. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/games.py +6 -4
  9. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/multilinear.py +1 -7
  10. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/__init__.py +2 -0
  11. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/games.py +21 -2
  12. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/sequence_form_polytopes.py +2 -4
  13. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/solvers/regret_minimization.py +28 -15
  14. noregret-0.0.0.dev7/noregret/tests/test_games.py +192 -0
  15. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/tests/test_linear_programming.py +2 -2
  16. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/tests/test_regret_minimization.py +27 -5
  17. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret.egg-info/PKG-INFO +7 -7
  18. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret.egg-info/SOURCES.txt +0 -1
  19. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/setup.py +1 -1
  20. noregret-0.0.0.dev5/noregret/games/utilities.py +0 -140
  21. noregret-0.0.0.dev5/noregret/tests/test_games.py +0 -62
  22. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/LICENSE +0 -0
  23. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/assurance-game.json +0 -0
  24. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/battle-of-the-sexes.json +0 -0
  25. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/chicken.json +0 -0
  26. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/gift-exchange-game.json +0 -0
  27. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/matching-pennies.json +0 -0
  28. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/prisoners-dilemma.json +0 -0
  29. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/pure-coordination.json +0 -0
  30. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-scissors-plus.json +0 -0
  31. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-scissors.json +0 -0
  32. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/rock-paper-superscissors.json +0 -0
  33. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/games/normal_form/stag-hunt.json +0 -0
  34. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/kernels.py +0 -0
  35. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/regret_minimizers/__init__.py +4 -4
  36. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/regret_minimizers/probability_simplices.py +0 -0
  37. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/regret_minimizers/regret_minimizers.py +0 -0
  38. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/regret_minimizers/sequence_form_polytopes.py +0 -0
  39. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/solvers/__init__.py +0 -0
  40. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/solvers/linear_programming.py +0 -0
  41. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/tests/__init__.py +0 -0
  42. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/tests/test_sequence_form_polytopes.py +0 -0
  43. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret/utilities.py +0 -0
  44. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret.egg-info/dependency_links.txt +0 -0
  45. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret.egg-info/requires.txt +0 -0
  46. {noregret-0.0.0.dev5 → noregret-0.0.0.dev7}/noregret.egg-info/top_level.txt +0 -0
  47. {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.dev5
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 91% code coverage.
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.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
 
@@ -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 91% code coverage.
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.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',
@@ -85,11 +85,11 @@ class BlackBoxGame(ABC):
85
85
  """
86
86
 
87
87
  @abstractmethod
88
- def utility(self, player, node):
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
- P = range(self.player_count)
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(node, a) for a in A))
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 == -1 else i
174
+ return None if i < 0 else i
177
175
 
178
- def utility(self, player, node):
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 from_open_spiel(game):
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.row_sequence_form_polytopes.best_response_value(u)
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.column_sequence_form_polytopes.best_response_value(v)
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
- assert (best_response_values >= expected_utilities).all()
105
+ for u, u_prime in zip(best_response_values, expected_utilities):
106
+ assert u >= u_prime
105
107
 
106
- nash_gap = (best_response_values - expected_utilities).sum()
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 != (self.row_dimension, self.column_dimension):
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 row_best_response_value(self, player, column_strategy):
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, player, row_strategy):
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 L_R, L_A, L_B, L_C_B in zip(
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 L_R, L_A, L_B, L_C_B in zip(
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 i in iterations:
65
+ for t in iterations:
63
66
  if alternation:
64
- for j, R in enumerate(regret_minimizers):
65
- 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:]))
66
69
 
67
- s[j] = R.output(prediction)
70
+ s[i] = R.output(prediction)
68
71
  else:
69
72
  U = game.utilities(*s)
70
73
 
71
- for j, (R, u) in enumerate(zip(regret_minimizers, U)):
74
+ for i, (R, u) in enumerate(zip(regret_minimizers, U)):
72
75
  R.observe(u)
73
76
 
74
- s[j] = R.output(prediction)
77
+ s[i] = R.output(prediction)
75
78
 
76
- if not checkpoints or i in checkpoints:
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
- target_exploitability is not None
82
- and exploitability() < target_exploitability
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 i in iterations:
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 i in checkpoints:
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
- target_exploitability is not None
149
- and exploitability() < target_exploitability
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.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.dev5
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 91% code coverage.
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.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.dev5',
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,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
@@ -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