game-resolver-cpp 0.1.0__tar.gz → 0.1.2__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.
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/.gitignore +9 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/CMakeLists.txt +86 -1
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/Makefile +5 -1
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/PKG-INFO +63 -5
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/README.md +207 -1
- game_resolver_cpp-0.1.2/assets/editor.css +644 -0
- game_resolver_cpp-0.1.2/assets/editor.html.in +110 -0
- game_resolver_cpp-0.1.2/assets/editor.js +2394 -0
- game_resolver_cpp-0.1.2/assets/tree_view.css +641 -0
- game_resolver_cpp-0.1.2/assets/tree_view.html.in +68 -0
- game_resolver_cpp-0.1.2/assets/tree_view.js +773 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/docs/README_for_pypi.md +62 -4
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/CMakeLists.txt +3 -0
- game_resolver_cpp-0.1.2/examples/centipede.cpp +78 -0
- game_resolver_cpp-0.1.2/examples/entry_deterrence.cpp +46 -0
- game_resolver_cpp-0.1.2/examples/extensive_battle_of_sex.cpp +72 -0
- game_resolver_cpp-0.1.2/include/game_resolver/extensive/editor.hpp +35 -0
- game_resolver_cpp-0.1.2/include/game_resolver/extensive/extensive_form_game.hpp +157 -0
- game_resolver_cpp-0.1.2/include/game_resolver/extensive/subgame_perfect_equilibrium.hpp +102 -0
- game_resolver_cpp-0.1.2/include/game_resolver/extensive/tree_view.hpp +71 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/pyproject.toml +4 -1
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/__init__.py +2 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/bayesian_nash_equilibrium.py +16 -1
- game_resolver_cpp-0.1.2/python/game_resolver_cpp/extensive_form_game.py +268 -0
- game_resolver_cpp-0.1.2/python/game_resolver_cpp/web.py +257 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/src/binding.cpp +172 -3
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/tests/test_compat.py +32 -0
- game_resolver_cpp-0.1.2/python/tests/test_extensive.py +181 -0
- game_resolver_cpp-0.1.2/python/tests/test_web.py +168 -0
- game_resolver_cpp-0.1.2/src/extensive/editor.cpp +54 -0
- game_resolver_cpp-0.1.2/src/extensive/extensive_form_game.cpp +332 -0
- game_resolver_cpp-0.1.2/src/extensive/html_util.cpp +114 -0
- game_resolver_cpp-0.1.2/src/extensive/html_util.hpp +36 -0
- game_resolver_cpp-0.1.2/src/extensive/subgame_perfect_equilibrium.cpp +390 -0
- game_resolver_cpp-0.1.2/src/extensive/tree_view.cpp +252 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/version.cpp +1 -1
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/CMakeLists.txt +4 -0
- game_resolver_cpp-0.1.2/tests/editor_test.cpp +93 -0
- game_resolver_cpp-0.1.2/tests/extensive_form_test.cpp +172 -0
- game_resolver_cpp-0.1.2/tests/extensive_games.hpp +185 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/smoke_test.cpp +1 -1
- game_resolver_cpp-0.1.2/tests/spne_test.cpp +215 -0
- game_resolver_cpp-0.1.2/tests/tree_view_test.cpp +160 -0
- game_resolver_cpp-0.1.2/third_party/d3-mini/LICENSE +13 -0
- game_resolver_cpp-0.1.2/third_party/d3-mini/LICENSE.d3-ease +28 -0
- game_resolver_cpp-0.1.2/third_party/d3-mini/README.md +36 -0
- game_resolver_cpp-0.1.2/third_party/d3-mini/d3-mini.min.js +1 -0
- game_resolver_cpp-0.1.2/third_party/d3-mini/entry.js +7 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/.clang-format +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/.clang-tidy +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/LICENSE +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/benchmarks/CMakeLists.txt +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/benchmarks/benchmark.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/cmake/game_resolverConfig.cmake.in +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/battle_of_sex.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/bayesian_battle_of_sex.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/cournot.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/hawk_dove.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/prisoners_dilemma.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/action.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/bayesian/bayesian_game.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/bayesian/bayesian_nash_equilibrium.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/bayesian/bayesian_player.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/error.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/game.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/game_resolver.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/nash_equilibrium.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/ndarray.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/payoff.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/player.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/solver/nash_solver.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/version.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/CMakeLists.txt +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/benchmarks/compare_with_python.py +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/bayesian_game.py +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/bayesian_player.py +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/game.py +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/nash_equilibrium.py +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/payoff.py +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/player.py +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/action.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/bayesian/bayesian_nash_equilibrium.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/bayesian/bayesian_player.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/nash_equilibrium.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/payoff.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/solver/nash_solver.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/action_test.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/bayesian_test.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/example_games.hpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/battle_of_sex.txt +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/bayesian_battle_of_sex.txt +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/cournot.txt +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/generate.py +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/hawk_dove.txt +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/prisoners_dilemma.txt +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/verify_exact.py +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden_test.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/nash_equilibrium_test.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/nash_solver_test.cpp +0 -0
- {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/ndarray_test.cpp +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
cmake_minimum_required(VERSION 3.21)
|
|
2
|
-
project(game_resolver_cpp VERSION 0.1.
|
|
2
|
+
project(game_resolver_cpp VERSION 0.1.2 LANGUAGES CXX)
|
|
3
3
|
|
|
4
4
|
option(GAME_RESOLVER_BUILD_TESTS "Build the test suite" ${PROJECT_IS_TOP_LEVEL})
|
|
5
5
|
option(GAME_RESOLVER_BUILD_EXAMPLES "Build the example programs" ${PROJECT_IS_TOP_LEVEL})
|
|
@@ -27,6 +27,84 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
|
|
27
27
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
|
|
28
28
|
endif()
|
|
29
29
|
|
|
30
|
+
# ------------------------------------------------------- 可視化アセットの埋め込み
|
|
31
|
+
# assets/ と third_party/d3-mini/ を C++ の生文字列リテラルにしたヘッダを configure 時に作る。
|
|
32
|
+
# こうしておくと tree_view.cpp が吐く HTML が 1 ファイルで完結し、実行時に何も探しに行かない。
|
|
33
|
+
#
|
|
34
|
+
# MSVC は 1 つの文字列リテラルを 65535 バイトまでしか許さないので、16 KB ずつに割って
|
|
35
|
+
# 配列にし、実行時に連結する(d3-mini.min.js が 57 KB あり、素直に書くと将来引っかかる)。
|
|
36
|
+
set(GR_ASSET_FILES
|
|
37
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.html.in
|
|
38
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.css
|
|
39
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.js
|
|
40
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.html.in
|
|
41
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.css
|
|
42
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.js
|
|
43
|
+
${CMAKE_CURRENT_SOURCE_DIR}/third_party/d3-mini/d3-mini.min.js
|
|
44
|
+
)
|
|
45
|
+
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${GR_ASSET_FILES})
|
|
46
|
+
|
|
47
|
+
function(gr_emit_asset out_var function_name path)
|
|
48
|
+
file(READ "${path}" _content)
|
|
49
|
+
string(FIND "${_content}" ")GRASSET\"" _clash)
|
|
50
|
+
if(NOT _clash EQUAL -1)
|
|
51
|
+
message(FATAL_ERROR "${path}: 生文字列リテラルの終端子 )GRASSET\" が中身に含まれています")
|
|
52
|
+
endif()
|
|
53
|
+
|
|
54
|
+
string(LENGTH "${_content}" _len)
|
|
55
|
+
set(_chunks "")
|
|
56
|
+
set(_at 0)
|
|
57
|
+
while(_at LESS _len)
|
|
58
|
+
math(EXPR _take "${_len} - ${_at}")
|
|
59
|
+
if(_take GREATER 16000)
|
|
60
|
+
set(_take 16000)
|
|
61
|
+
endif()
|
|
62
|
+
string(SUBSTRING "${_content}" ${_at} ${_take} _piece)
|
|
63
|
+
string(APPEND _chunks " R\"GRASSET(${_piece})GRASSET\",\n")
|
|
64
|
+
math(EXPR _at "${_at} + ${_take}")
|
|
65
|
+
endwhile()
|
|
66
|
+
|
|
67
|
+
string(APPEND ${out_var}
|
|
68
|
+
"inline std::string ${function_name}() {\n"
|
|
69
|
+
" static const char* const kParts[] = {\n${_chunks} };\n"
|
|
70
|
+
" return detail::join(kParts, sizeof(kParts) / sizeof(kParts[0]));\n"
|
|
71
|
+
"}\n\n")
|
|
72
|
+
set(${out_var} "${${out_var}}" PARENT_SCOPE)
|
|
73
|
+
endfunction()
|
|
74
|
+
|
|
75
|
+
set(GR_ASSET_BODY "")
|
|
76
|
+
gr_emit_asset(GR_ASSET_BODY tree_view_template ${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.html.in)
|
|
77
|
+
gr_emit_asset(GR_ASSET_BODY tree_view_css ${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.css)
|
|
78
|
+
gr_emit_asset(GR_ASSET_BODY tree_view_js ${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.js)
|
|
79
|
+
gr_emit_asset(GR_ASSET_BODY editor_template ${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.html.in)
|
|
80
|
+
gr_emit_asset(GR_ASSET_BODY editor_css ${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.css)
|
|
81
|
+
gr_emit_asset(GR_ASSET_BODY editor_js ${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.js)
|
|
82
|
+
gr_emit_asset(GR_ASSET_BODY d3_mini ${CMAKE_CURRENT_SOURCE_DIR}/third_party/d3-mini/d3-mini.min.js)
|
|
83
|
+
|
|
84
|
+
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/generated/game_resolver_assets.hpp
|
|
85
|
+
"// CMake が assets/ と third_party/d3-mini/ から生成する。直接編集しない。
|
|
86
|
+
// 元ファイルを直せば configure が走り直して作り直される。
|
|
87
|
+
#pragma once
|
|
88
|
+
|
|
89
|
+
#include <cstddef>
|
|
90
|
+
#include <string>
|
|
91
|
+
|
|
92
|
+
namespace game_resolver::assets {
|
|
93
|
+
namespace detail {
|
|
94
|
+
|
|
95
|
+
inline std::string join(const char* const* parts, std::size_t count) {
|
|
96
|
+
std::string out;
|
|
97
|
+
for (std::size_t i = 0; i < count; ++i) {
|
|
98
|
+
out += parts[i];
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
} // namespace detail
|
|
104
|
+
|
|
105
|
+
${GR_ASSET_BODY}} // namespace game_resolver::assets
|
|
106
|
+
")
|
|
107
|
+
|
|
30
108
|
# ---------------------------------------------------------------- library
|
|
31
109
|
add_library(game_resolver
|
|
32
110
|
src/version.cpp
|
|
@@ -36,6 +114,11 @@ add_library(game_resolver
|
|
|
36
114
|
src/solver/nash_solver.cpp
|
|
37
115
|
src/bayesian/bayesian_player.cpp
|
|
38
116
|
src/bayesian/bayesian_nash_equilibrium.cpp
|
|
117
|
+
src/extensive/extensive_form_game.cpp
|
|
118
|
+
src/extensive/subgame_perfect_equilibrium.cpp
|
|
119
|
+
src/extensive/tree_view.cpp
|
|
120
|
+
src/extensive/html_util.cpp
|
|
121
|
+
src/extensive/editor.cpp
|
|
39
122
|
)
|
|
40
123
|
add_library(game_resolver::game_resolver ALIAS game_resolver)
|
|
41
124
|
|
|
@@ -43,6 +126,8 @@ target_include_directories(game_resolver PUBLIC
|
|
|
43
126
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
44
127
|
$<INSTALL_INTERFACE:include>
|
|
45
128
|
)
|
|
129
|
+
# 生成した game_resolver_assets.hpp は tree_view.cpp だけが使う。公開ヘッダではない。
|
|
130
|
+
target_include_directories(game_resolver PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated)
|
|
46
131
|
target_compile_features(game_resolver PUBLIC cxx_std_20)
|
|
47
132
|
|
|
48
133
|
# 静的ライブラリを共有オブジェクトにリンクできるようにする。
|
|
@@ -4,7 +4,7 @@ BUILD_DIR ?= build
|
|
|
4
4
|
BUILD_TYPE ?= Release
|
|
5
5
|
GENERATOR ?= Ninja
|
|
6
6
|
|
|
7
|
-
.PHONY: all configure build test bench format format-check tidy clean python-install python-test python-bench
|
|
7
|
+
.PHONY: all configure build test bench format format-check tidy clean python-install python-test python-bench web
|
|
8
8
|
|
|
9
9
|
all: build
|
|
10
10
|
|
|
@@ -30,6 +30,10 @@ python-test:
|
|
|
30
30
|
python-bench:
|
|
31
31
|
python3 python/benchmarks/compare_with_python.py
|
|
32
32
|
|
|
33
|
+
# ブラウザでゲームツリーを描いて解く(python-install 済みであること)
|
|
34
|
+
web:
|
|
35
|
+
python3 -m game_resolver_cpp.web
|
|
36
|
+
|
|
33
37
|
# サニタイザ付きのデバッグビルドでテストする
|
|
34
38
|
test-asan:
|
|
35
39
|
$(CMAKE) -S . -B $(BUILD_DIR)-asan -G $(GENERATOR) -DCMAKE_BUILD_TYPE=Debug -DGAME_RESOLVER_SANITIZE=ON
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: game_resolver_cpp
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: C++ backed game equilibrium solver with the game_resolver interface
|
|
5
5
|
Keywords: game-theory,nash-equilibrium,bayesian-game
|
|
6
6
|
Author: nishino-lab
|
|
@@ -78,7 +78,11 @@ All four ways of defining payoffs work, exactly as in `game_resolver`:
|
|
|
78
78
|
|
|
79
79
|
Subclass `BayesianGame` and implement `posterior()`.
|
|
80
80
|
|
|
81
|
+
A Bayesian battle of the sexes. Each player has a type `A` / `B`, observes their own type,
|
|
82
|
+
and computes the expected payoff with the posterior over the other player's type.
|
|
83
|
+
|
|
81
84
|
```python
|
|
85
|
+
import numpy as np
|
|
82
86
|
from game_resolver_cpp.bayesian_game import BayesianGame
|
|
83
87
|
from game_resolver_cpp.bayesian_nash_equilibrium import BayesianNashEquilibrium
|
|
84
88
|
from game_resolver_cpp.bayesian_player import BayesianPlayer
|
|
@@ -97,14 +101,32 @@ class SampleGame(BayesianGame):
|
|
|
97
101
|
other = type_tuple[(player_id + 1) % 2]
|
|
98
102
|
return 1 / 4 if own == other else 3 / 4
|
|
99
103
|
|
|
100
|
-
# payoff functions take (own type, each player's action)
|
|
101
|
-
def male(self, type, male_action, female_action):
|
|
102
|
-
|
|
104
|
+
# payoff functions take (own type, each player's action), and must return a number
|
|
105
|
+
def male(self, type, male_action, female_action):
|
|
106
|
+
if male_action != female_action:
|
|
107
|
+
return 0.0
|
|
108
|
+
if type == "A":
|
|
109
|
+
return 2.0 if male_action == "Boxing" else 1.0
|
|
110
|
+
return 1.0 if male_action == "Boxing" else 2.0
|
|
111
|
+
|
|
112
|
+
def female(self, type, male_action, female_action):
|
|
113
|
+
if male_action != female_action:
|
|
114
|
+
return 0.0
|
|
115
|
+
if type == "A":
|
|
116
|
+
return 2.0 if female_action == "Boxing" else 1.0
|
|
117
|
+
return 1.0 if female_action == "Boxing" else 2.0
|
|
103
118
|
|
|
104
119
|
|
|
105
120
|
nash = BayesianNashEquilibrium(SampleGame()).get_nash_equilibrium()
|
|
121
|
+
print(nash["index"]) # [(0, 0), (1, 2), (2, 1), (3, 3)]
|
|
122
|
+
print(nash["profile"][0]) # [{'A': 'Boxing', 'B': 'Boxing'}, {'A': 'Boxing', 'B': 'Boxing'}]
|
|
123
|
+
print(nash["payoff"][0]) # [{'A': 2.0, 'B': 1.0}, {'A': 2.0, 'B': 1.0}]
|
|
106
124
|
```
|
|
107
125
|
|
|
126
|
+
`profile` and `payoff` are indexed by `[equilibrium][player][type]`. The types and actions
|
|
127
|
+
are handed back exactly as you passed them in, so with `np.array` they print as
|
|
128
|
+
`np.str_('A')`; pass plain lists if you want plain strings.
|
|
129
|
+
|
|
108
130
|
## What gets faster
|
|
109
131
|
|
|
110
132
|
Against the pure Python `game_resolver`, on an 11-core Apple M series machine.
|
|
@@ -136,10 +158,46 @@ an exact `==`, because an exact comparison makes genuine ties depend on rounding
|
|
|
136
158
|
Cournot example has three equilibria on its grid; the Python version reports one of them.
|
|
137
159
|
Pass `tolerance=0` to `NashEquilibrium` for the Python version's strict behaviour.
|
|
138
160
|
|
|
161
|
+
## Run an extensive form game
|
|
162
|
+
|
|
163
|
+
Build a game tree and get its pure strategy subgame perfect equilibria. Information sets
|
|
164
|
+
turn the same tree into a simultaneous move game.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from game_resolver_cpp.extensive_form_game import ExtensiveFormGame
|
|
168
|
+
|
|
169
|
+
game = ExtensiveFormGame()
|
|
170
|
+
entrant = game.add_player("Entrant")
|
|
171
|
+
incumbent = game.add_player("Incumbent")
|
|
172
|
+
|
|
173
|
+
enter = game.node(entrant, "E")
|
|
174
|
+
react = game.node(incumbent, "I")
|
|
175
|
+
game.edge(enter, game.terminal([0, 2], "out"), "Out")
|
|
176
|
+
game.edge(enter, react, "In")
|
|
177
|
+
game.edge(react, game.terminal([-1, -1], "fight"), "Fight")
|
|
178
|
+
game.edge(react, game.terminal([1, 1], "share"), "Accommodate")
|
|
179
|
+
game.auto_information_sets()
|
|
180
|
+
|
|
181
|
+
print(game.get_subgame_perfect_equilibrium())
|
|
182
|
+
# [{'payoff': [1.0, 1.0], 'strategy': {'E': 'In', 'I': 'Accommodate'},
|
|
183
|
+
# 'path': ['E', 'I', 'share'], 'outcome': 'share'}]
|
|
184
|
+
|
|
185
|
+
game.draw_gametree("entry.html") # self-contained page with the tree and the equilibria
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Or draw the tree in the browser and press ▶ 解く:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
python -m game_resolver_cpp.web
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The editor saves games as JSON, `ExtensiveFormGame.load()` reads them, and `game.edit()`
|
|
195
|
+
opens a game built in code in the editor.
|
|
196
|
+
|
|
139
197
|
## Not supported
|
|
140
198
|
|
|
141
199
|
- Mixed strategy Nash equilibria (the Python version does not have them either)
|
|
142
|
-
-
|
|
200
|
+
- Chance (Nature) nodes in extensive form games
|
|
143
201
|
|
|
144
202
|
## Links
|
|
145
203
|
|
|
@@ -235,6 +235,8 @@ class SampleGame : public BayesianGame {
|
|
|
235
235
|
};
|
|
236
236
|
```
|
|
237
237
|
|
|
238
|
+
The bodies left out here are in [examples/bayesian_battle_of_sex.cpp](examples/bayesian_battle_of_sex.cpp).
|
|
239
|
+
|
|
238
240
|
```cpp
|
|
239
241
|
SampleGame game;
|
|
240
242
|
BayesianNashEquilibrium equilibrium(game);
|
|
@@ -248,6 +250,140 @@ std::cout << nash << '\n';
|
|
|
248
250
|
|
|
249
251
|
`payoff` is the expected payoff indexed by `[equilibrium][player][type]`.
|
|
250
252
|
|
|
253
|
+
## Run an extensive form game
|
|
254
|
+
|
|
255
|
+
Build a game tree, get its pure strategy subgame perfect equilibria, and draw it.
|
|
256
|
+
|
|
257
|
+
```cpp
|
|
258
|
+
#include <iostream>
|
|
259
|
+
|
|
260
|
+
#include "game_resolver/extensive/subgame_perfect_equilibrium.hpp"
|
|
261
|
+
#include "game_resolver/extensive/tree_view.hpp"
|
|
262
|
+
|
|
263
|
+
using namespace game_resolver;
|
|
264
|
+
|
|
265
|
+
int main() {
|
|
266
|
+
ExtensiveFormGame game;
|
|
267
|
+
const int entrant = game.add_player("Entrant");
|
|
268
|
+
const int incumbent = game.add_player("Incumbent");
|
|
269
|
+
|
|
270
|
+
const NodeId enter = game.decision_node(entrant, "E");
|
|
271
|
+
const NodeId react = game.decision_node(incumbent, "I");
|
|
272
|
+
|
|
273
|
+
game.edge(enter, game.terminal_node({0.0, 2.0}, "out"), Action(std::string("Out")));
|
|
274
|
+
game.edge(enter, react, Action(std::string("In")));
|
|
275
|
+
game.edge(react, game.terminal_node({-1.0, -1.0}, "fight"), Action(std::string("Fight")));
|
|
276
|
+
game.edge(react, game.terminal_node({1.0, 1.0}, "share"), Action(std::string("Accommodate")));
|
|
277
|
+
|
|
278
|
+
game.auto_information_sets(); // perfect information: one node per information set
|
|
279
|
+
|
|
280
|
+
const SubgamePerfectEquilibrium equilibrium(game);
|
|
281
|
+
std::cout << describe(game, equilibrium.solve()) << '\n';
|
|
282
|
+
|
|
283
|
+
write_solved_tree_view(game, "entry.html", "Entry deterrence");
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
subgame perfect equilibria: 1
|
|
289
|
+
[0] payoff=(1, 1)
|
|
290
|
+
strategy: E=In, I=Accommodate
|
|
291
|
+
path: E -In-> I -Accommodate-> share
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`(Out, Fight)` is a Nash equilibrium of the normal form but not a subgame perfect one: once
|
|
295
|
+
the entrant is in, fighting pays -1 and accommodating pays 1, so the threat is never carried
|
|
296
|
+
out. Backward induction drops it.
|
|
297
|
+
|
|
298
|
+
### Imperfect information
|
|
299
|
+
|
|
300
|
+
Put several nodes in one information set and the player cannot tell them apart. The same
|
|
301
|
+
tree then describes a simultaneous move game:
|
|
302
|
+
|
|
303
|
+
```cpp
|
|
304
|
+
// instead of auto_information_sets()
|
|
305
|
+
game.information_set(her, {boxing, ballet}, {first}, "her");
|
|
306
|
+
game.information_set(him, {boxing, ballet}, {after_boxing, after_ballet}, "him (blind)");
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Every node in one information set must offer the same actions in the same order — otherwise
|
|
310
|
+
the player could tell the nodes apart by what they can do. `validate()` checks this.
|
|
311
|
+
|
|
312
|
+
There is one algorithm for both cases. Subgames are found and collapsed deepest first; each
|
|
313
|
+
is converted to its induced normal form and handed to the same `solve_pure_nash` the rest of
|
|
314
|
+
the library uses. Under perfect information every decision node is its own subgame and the
|
|
315
|
+
induced normal form degenerates to picking a maximum, which is exactly backward induction.
|
|
316
|
+
|
|
317
|
+
### The HTML view
|
|
318
|
+
|
|
319
|
+
`write_solved_tree_view` writes one self-contained file — no network access, nothing to
|
|
320
|
+
serve, just open it.
|
|
321
|
+
|
|
322
|
+
| | |
|
|
323
|
+
| --- | --- |
|
|
324
|
+
| Layout | tidy tree (Reingold-Tilford) via `d3-hierarchy`. Deterministic: the same game always draws the same picture |
|
|
325
|
+
| Payoffs | shown at the leaves, one colored dot per player next to the number |
|
|
326
|
+
| Information sets | non-singleton sets are joined by a dashed connector |
|
|
327
|
+
| Equilibria | listed in the sidebar; selecting one highlights its path |
|
|
328
|
+
| Off-path moves | drawn as dashed colored edges — a subgame perfect equilibrium fixes behaviour at information sets the path never reaches, and that is what separates it from a plain Nash equilibrium |
|
|
329
|
+
| Interaction | pan / zoom, click a node to collapse its subtree, hover for details, `f` to fit, `1`–`9` to pick an equilibrium |
|
|
330
|
+
| Size | about 97 KB, of which 57 KB is the embedded subset of D3 (see [third_party/d3-mini/](third_party/d3-mini/)) |
|
|
331
|
+
|
|
332
|
+
Player colors come from a palette validated for colorblind separation, but identity never
|
|
333
|
+
rests on color alone: each node shows the player number, and the legend is always visible.
|
|
334
|
+
Beyond three players the categorical hues cannot be kept pairwise distinguishable, so the
|
|
335
|
+
number and the legend are what carry identity.
|
|
336
|
+
|
|
337
|
+
Runnable versions of all of the above are in
|
|
338
|
+
[examples/entry_deterrence.cpp](examples/entry_deterrence.cpp),
|
|
339
|
+
[examples/extensive_battle_of_sex.cpp](examples/extensive_battle_of_sex.cpp) and
|
|
340
|
+
[examples/centipede.cpp](examples/centipede.cpp).
|
|
341
|
+
|
|
342
|
+
### Draw the tree in the browser instead
|
|
343
|
+
|
|
344
|
+
You do not have to write the tree as code. The package ships an editor that runs in the
|
|
345
|
+
browser: draw the tree, press **▶ 解く**, and the equilibria are computed by the same C++
|
|
346
|
+
solver and drawn on the tree.
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
pip install .
|
|
350
|
+
python -m game_resolver_cpp.web # or: game-resolver-web
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
This starts a local server on `http://127.0.0.1:8765/` and opens it. Nothing is installed
|
|
354
|
+
besides the package itself; the server is the standard library's `http.server`, and the
|
|
355
|
+
page is the same self-contained HTML the tree view uses.
|
|
356
|
+
|
|
357
|
+
| | |
|
|
358
|
+
| --- | --- |
|
|
359
|
+
| Building | click a node to edit it in the inspector; hover a node and press **+** (or `a`) to add a branch; each branch has a menu to make its child a terminal node or a decision node of some player |
|
|
360
|
+
| Information sets | select a decision node and tick the other nodes of the same player under 「同じ情報集合に入れる」. Only nodes with the same actions in the same order are offered, and a node cannot be joined with its own ancestor (that would break perfect recall). Adding, removing or renaming a branch on one member is applied to every member, so a set never goes out of sync |
|
|
361
|
+
| Solving | **▶ 解く** (`Ctrl+Enter`) lists the pure strategy subgame perfect equilibria; picking one highlights its path and shows off-path choices as dashed edges, as in the HTML view |
|
|
362
|
+
| Saving | **保存** downloads the game as JSON, **開く** reads it back, and edits are also kept in the browser's local storage so a reload does not lose them. `Ctrl+Z` undoes |
|
|
363
|
+
| Getting it into code | **Python** shows the `ExtensiveFormGame` code that builds the same tree; **HTML** downloads the solved tree view. `ExtensiveFormGame.load("game.json")` reads a saved file directly |
|
|
364
|
+
| Samples | 参入阻止 / 男女の争い(逐次・同時)/ ムカデゲーム, from the menu at the top |
|
|
365
|
+
|
|
366
|
+
The other direction works too: `game.edit()` on a game built in Python opens it in the
|
|
367
|
+
editor. See [Extensive form games from Python](#extensive-form-games-from-python).
|
|
368
|
+
|
|
369
|
+
From C++, `game_json()` / `tree_view_json()` in
|
|
370
|
+
[tree_view.hpp](include/game_resolver/extensive/tree_view.hpp) return the JSON the editor
|
|
371
|
+
and the view read, and `write_editor()` in [editor.hpp](include/game_resolver/extensive/editor.hpp)
|
|
372
|
+
writes the editor page itself. Without a server that page can edit and export but not solve;
|
|
373
|
+
the solving is done by the C++ library on the server side, not re-implemented in JavaScript.
|
|
374
|
+
|
|
375
|
+
### Limits
|
|
376
|
+
|
|
377
|
+
- **Pure strategies only**, same as the rest of the library. A game with no pure strategy
|
|
378
|
+
equilibrium returns an empty result rather than a mixed one.
|
|
379
|
+
- **Perfect recall is only partly checked.** `validate()` rejects an information set that
|
|
380
|
+
contains a node and one of its own descendants, which is the case that silently breaks the
|
|
381
|
+
conversion to normal form. It does not verify the full perfect recall condition.
|
|
382
|
+
- **No chance (Nature) nodes yet.**
|
|
383
|
+
- The induced normal form of a subgame has `Π_players Π_{their information sets} |actions|`
|
|
384
|
+
cells, which grows doubly exponentially. `SpneOptions::max_cells` (10,000,000 by default)
|
|
385
|
+
stops a mis-built game from quietly allocating gigabytes.
|
|
386
|
+
|
|
251
387
|
## Use it from Python
|
|
252
388
|
|
|
253
389
|
The package exposes the same interface as the Python `game_resolver`, so existing lab code
|
|
@@ -297,6 +433,55 @@ payoff.update(more_entries) # also works
|
|
|
297
433
|
Player(0, actions=actions, payoff=mapping) # a plain dict is accepted too
|
|
298
434
|
```
|
|
299
435
|
|
|
436
|
+
### Extensive form games from Python
|
|
437
|
+
|
|
438
|
+
`node` / `edge` / `information_set` / `draw_gametree` keep the names used by the prototype on
|
|
439
|
+
the Python side's `feature/extensive_form_game` branch. Terminal nodes carry payoffs, which
|
|
440
|
+
that branch did not have, and nodes are integer ids rather than objects.
|
|
441
|
+
|
|
442
|
+
```python
|
|
443
|
+
from game_resolver_cpp.extensive_form_game import ExtensiveFormGame
|
|
444
|
+
|
|
445
|
+
game = ExtensiveFormGame()
|
|
446
|
+
entrant = game.add_player("Entrant")
|
|
447
|
+
incumbent = game.add_player("Incumbent")
|
|
448
|
+
|
|
449
|
+
enter = game.node(entrant, "E")
|
|
450
|
+
react = game.node(incumbent, "I")
|
|
451
|
+
game.edge(enter, game.terminal([0, 2], "out"), "Out")
|
|
452
|
+
game.edge(enter, react, "In")
|
|
453
|
+
game.edge(react, game.terminal([-1, -1], "fight"), "Fight")
|
|
454
|
+
game.edge(react, game.terminal([1, 1], "share"), "Accommodate")
|
|
455
|
+
game.auto_information_sets()
|
|
456
|
+
|
|
457
|
+
for eq in game.get_subgame_perfect_equilibrium():
|
|
458
|
+
print(eq)
|
|
459
|
+
# {'payoff': [1.0, 1.0], 'strategy': {'E': 'In', 'I': 'Accommodate'},
|
|
460
|
+
# 'path': ['E', 'I', 'share'], 'outcome': 'share'}
|
|
461
|
+
|
|
462
|
+
game.draw_gametree("entry.html", "Entry deterrence")
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
`strategy` covers every information set, including ones the equilibrium path never reaches.
|
|
466
|
+
For imperfect information, pass several nodes to `information_set`:
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
game.information_set(him, ["Boxing", "Ballet"], after_boxing, after_ballet, label="him")
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
A game can be moved between code and the browser editor in both directions:
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
game.edit() # open this game in the editor (Ctrl+C to stop)
|
|
476
|
+
game = ExtensiveFormGame.load("entry.json") # read a file saved from the editor
|
|
477
|
+
game.save("entry.json") # the same format, from code
|
|
478
|
+
data = game.to_dict(); ExtensiveFormGame.from_dict(data)
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
The JSON is `{"players", "root", "nodes", "informationSets"}`; `from_dict` also accepts a
|
|
482
|
+
shorter form (player names as strings, no ids, only the non-singleton information sets),
|
|
483
|
+
which is what the editor writes.
|
|
484
|
+
|
|
300
485
|
### What actually gets faster
|
|
301
486
|
|
|
302
487
|
One run of `python3 python/benchmarks/compare_with_python.py` on an 11-core Apple M series
|
|
@@ -361,6 +546,26 @@ The reasoning behind each of these, and the alternatives that were measured and
|
|
|
361
546
|
| `BayesianNashEquilibrium(g)` + `initialize_payoff_matrices()` | `BayesianNashEquilibrium(g)` (built in the constructor) |
|
|
362
547
|
| strategy = `dict[type, action]` | strategy = `Strategy` (a vector of action indices, one per type) |
|
|
363
548
|
|
|
549
|
+
From the `feature/extensive_form_game` branch:
|
|
550
|
+
|
|
551
|
+
| Python prototype | C++ |
|
|
552
|
+
| --- | --- |
|
|
553
|
+
| `ExtensiveFormPlayer(id=0)` + `game.add(p)` | `game.add_player("name")` -> `int` |
|
|
554
|
+
| `game.node(player)` -> `Node` | `game.decision_node(player)` -> `NodeId` |
|
|
555
|
+
| — (terminals were invented at draw time) | `game.terminal_node({2.0, 1.0})` -> `NodeId` |
|
|
556
|
+
| `game.edge(parent, child, action)` | same, with `NodeId` |
|
|
557
|
+
| `game.information_set(player, actions, *nodes)` | `game.information_set(player, actions, {nodes})` |
|
|
558
|
+
| `player.setup_actions()` | not needed — the solver derives the induced normal form |
|
|
559
|
+
| `SubgamePerfectNashEquilibrium(g)` (an empty stub) | `SubgamePerfectEquilibrium(g).solve()` |
|
|
560
|
+
| `game.draw_gametree()` -> pyvis, physics layout | `write_solved_tree_view(g, path, title)` -> deterministic tidy tree |
|
|
561
|
+
|
|
562
|
+
What that branch had was the tree structure and the idea of taking the product of local
|
|
563
|
+
strategies to get the induced normal form. It had no payoffs on the tree
|
|
564
|
+
(`draw_gametree` carried a `# ToDo 利得ベクトルを書く!` where the payoff vector belongs and
|
|
565
|
+
manufactured terminal nodes from `local_actions` at draw time), and
|
|
566
|
+
`SubgamePerfectNashEquilibrium.initialize_payoff_matrices` was `pass`. The solver and the
|
|
567
|
+
HTML view here are new.
|
|
568
|
+
|
|
364
569
|
Deliberate differences:
|
|
365
570
|
|
|
366
571
|
- **The Type1 / Type2 distinction is gone from the C++ API.** In the Python version, whether
|
|
@@ -399,6 +604,7 @@ make test
|
|
|
399
604
|
| `make python-install` | `pip install ".[test]"` |
|
|
400
605
|
| `make python-test` | run the Python tests |
|
|
401
606
|
| `make python-bench` | compare against the Python version |
|
|
607
|
+
| `make web` | open the browser editor (needs `make python-install` first) |
|
|
402
608
|
|
|
403
609
|
Or drive CMake directly:
|
|
404
610
|
|
|
@@ -514,7 +720,7 @@ Two things are deliberately excluded:
|
|
|
514
720
|
## Not supported
|
|
515
721
|
|
|
516
722
|
- Mixed strategy Nash equilibria (the Python version does not have them either)
|
|
517
|
-
-
|
|
723
|
+
- Chance (Nature) nodes in extensive form games
|
|
518
724
|
- A Bayesian game's strategy space grows as `|actions|^|types|`. That blow-up is inherent,
|
|
519
725
|
so oversized settings are rejected up front.
|
|
520
726
|
|