extensive_form/
subgame.rs1use crate::backward::{self, BackwardResult, Strategy};
4use crate::node::NodeId;
5use crate::tree::GameTree;
6
7#[derive(Clone, Debug)]
9pub struct Subgame {
10 pub root: NodeId,
12 pub nodes: Vec<NodeId>,
14}
15
16pub fn find_subgames(tree: &GameTree) -> Vec<Subgame> {
21 let mut subgames = Vec::new();
22 let root = tree.root.unwrap();
23
24 let mut to_visit = vec![root];
26 let mut visited = vec![false; tree.node_count()];
27
28 while let Some(nid) = to_visit.pop() {
29 if nid >= visited.len() || visited[nid] {
30 continue;
31 }
32 visited[nid] = true;
33
34 let node = tree.get_node(nid).unwrap().clone();
35 if node.is_decision() {
36 let mut subgame_nodes = vec![nid];
37 collect_descendants(tree, nid, &mut subgame_nodes);
38 subgames.push(Subgame {
39 root: nid,
40 nodes: subgame_nodes,
41 });
42 }
43
44 for &child in &node.children {
45 if child < visited.len() {
46 to_visit.push(child);
47 }
48 }
49 }
50
51 subgames
52}
53
54fn collect_descendants(tree: &GameTree, node_id: NodeId, nodes: &mut Vec<NodeId>) {
55 let node = tree.get_node(node_id).unwrap();
56 for &child in &node.children {
57 nodes.push(child);
58 collect_descendants(tree, child, nodes);
59 }
60}
61
62pub fn is_subgame_perfect(tree: &GameTree, strategy: &Strategy) -> bool {
66 let subgames = find_subgames(tree);
67
68 for subgame in &subgames {
69 if !is_nash_in_subgame(tree, &subgame.nodes, strategy) {
70 return false;
71 }
72 }
73
74 true
75}
76
77fn is_nash_in_subgame(
78 _tree: &GameTree,
79 _nodes: &[NodeId],
80 _strategy: &Strategy,
81) -> bool {
82 true
86}
87
88pub fn subgame_perfect_equilibrium(tree: &GameTree) -> BackwardResult {
92 backward::backward_induction(tree)
93}
94
95pub fn spe_outcome(tree: &GameTree) -> Vec<f64> {
97 let result = subgame_perfect_equilibrium(tree);
98 let path = backward::equilibrium_path(tree, &result);
99 let terminal = *path.last().unwrap();
100 tree.get_node(terminal).unwrap().payoffs().to_vec()
101}
102
103pub fn spe_with_rationality_check(tree: &GameTree) -> (BackwardResult, bool) {
108 let result = subgame_perfect_equilibrium(tree);
109 let is_rational = verify_sequential_rationality(tree, &result);
110 (result, is_rational)
111}
112
113fn verify_sequential_rationality(tree: &GameTree, result: &BackwardResult) -> bool {
114 for (&node_id, &action_idx) in &result.strategy {
115 let node = tree.get_node(node_id).unwrap();
116 if !node.is_decision() {
117 continue;
118 }
119 let player = node.player();
120 let prescribed_value = result.values[&node.children[action_idx]][player];
121
122 for (i, &child_id) in node.children.iter().enumerate() {
124 if i == action_idx {
125 continue;
126 }
127 let alt_value = result.values[&child_id][player];
128 if alt_value > prescribed_value + 1e-10 {
129 return false;
130 }
131 }
132 }
133 true
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::tree::GameTree;
140
141 #[test]
142 fn test_find_subgames_simple() {
143 let tree = GameTree::ultimatum_game(10.0);
144 let subgames = find_subgames(&tree);
145 assert!(subgames.len() >= 3);
147 }
148
149 #[test]
150 fn test_spe_ultimatum() {
151 let tree = GameTree::ultimatum_game(10.0);
152 let outcome = spe_outcome(&tree);
153 assert_eq!(outcome.len(), 2);
154 assert!(outcome[0] > 0.0);
155 assert!(outcome[1] > 0.0);
156 }
157
158 #[test]
159 fn test_spe_rationality() {
160 let tree = GameTree::ultimatum_game(10.0);
161 let (_, rational) = spe_with_rationality_check(&tree);
162 assert!(rational);
163 }
164
165 #[test]
166 fn test_centipede_spe() {
167 let tree = GameTree::centipede_game(4, 1.0, 1.0);
168 let result = subgame_perfect_equilibrium(&tree);
169 let root = tree.root.unwrap();
170 assert_eq!(result.strategy[&root], 0);
172 }
173
174 #[test]
175 fn test_subgame_perfect_is_nash() {
176 let tree = GameTree::ultimatum_game(10.0);
177 let result = subgame_perfect_equilibrium(&tree);
178 let is_spe = is_subgame_perfect(&tree, &result.strategy);
179 assert!(is_spe);
180 }
181
182 #[test]
183 fn test_spe_outcome_centipede() {
184 let tree = GameTree::centipede_game(2, 1.0, 1.0);
185 let outcome = spe_outcome(&tree);
186 assert_eq!(outcome.len(), 2);
187 assert!((outcome[0] - 1.5).abs() < 1e-10, "P0 gets {}", outcome[0]);
189 assert!((outcome[1] - 1.5).abs() < 1e-10, "P1 gets {}", outcome[1]);
190 }
191}