Skip to main content

extensive_form/
information.rs

1//! Information sets for modeling imperfect information.
2
3use crate::node::NodeId;
4use crate::tree::GameTree;
5use std::collections::HashMap;
6
7/// An information set groups nodes that a player cannot distinguish between.
8#[derive(Clone, Debug)]
9pub struct InformationSet {
10    /// Unique identifier for this information set.
11    pub id: usize,
12    /// The player who owns this information set.
13    pub player: usize,
14    /// The nodes in this information set.
15    pub nodes: Vec<NodeId>,
16    /// Available actions (shared across all nodes in the set).
17    pub actions: Vec<String>,
18}
19
20/// Collection of all information sets in a game.
21#[derive(Clone, Debug, Default)]
22pub struct InformationStructure {
23    /// Information sets indexed by their ID.
24    pub sets: Vec<InformationSet>,
25    /// Map from node ID to information set ID.
26    pub node_to_set: HashMap<NodeId, usize>,
27}
28
29impl InformationStructure {
30    /// Create a new empty information structure.
31    pub fn new() -> Self {
32        Self::default()
33    }
34
35    /// Add an information set.
36    pub fn add_set(
37        &mut self,
38        player: usize,
39        nodes: Vec<NodeId>,
40        actions: Vec<String>,
41    ) -> usize {
42        let id = self.sets.len();
43        for &node in &nodes {
44            self.node_to_set.insert(node, id);
45        }
46        self.sets.push(InformationSet {
47            id,
48            player,
49            nodes,
50            actions,
51        });
52        id
53    }
54
55    /// Get the information set for a node.
56    pub fn set_for_node(&self, node: NodeId) -> Option<&InformationSet> {
57        self.node_to_set.get(&node).map(|&id| &self.sets[id])
58    }
59
60    /// Check if two nodes are in the same information set.
61    pub fn same_set(&self, a: NodeId, b: NodeId) -> bool {
62        match (self.node_to_set.get(&a), self.node_to_set.get(&b)) {
63            (Some(sa), Some(sb)) => sa == sb,
64            _ => false,
65        }
66    }
67
68    /// Validate that an information structure is consistent with a game tree.
69    ///
70    /// Checks that:
71    /// - All nodes in a set belong to the same player
72    /// - All nodes in a set have the same number of actions
73    /// - No node appears in multiple sets
74    pub fn validate(&self, tree: &GameTree) -> Result<(), String> {
75        for info_set in &self.sets {
76            // Check all nodes exist and belong to same player
77            let mut player = None;
78            for &nid in &info_set.nodes {
79                let node = tree.get_node(nid)
80                    .ok_or_else(|| format!("Node {} not found", nid))?;
81                match player {
82                    None => player = Some(node.player()),
83                    Some(p) => {
84                        if node.player() != p {
85                            return Err(format!(
86                                "Info set {} has nodes with different players", info_set.id
87                            ));
88                        }
89                    }
90                }
91            }
92
93            // Check action counts match
94            for &nid in &info_set.nodes {
95                let node = tree.get_node(nid).unwrap();
96                if node.actions.len() != info_set.actions.len() {
97                    return Err(format!(
98                        "Node {} has {} actions but info set has {}",
99                        nid, node.actions.len(), info_set.actions.len()
100                    ));
101                }
102            }
103        }
104        Ok(())
105    }
106}
107
108/// Build a simultaneous-move game as an extensive-form game with information sets.
109///
110/// Player 0 moves first, but player 1 cannot observe the move (they are in one info set).
111pub fn simultaneous_game_info_sets(n_actions_p0: usize, n_actions_p1: usize) -> InformationStructure {
112    let mut info = InformationStructure::new();
113    // All P1 decision nodes go in one information set
114    let p1_nodes: Vec<NodeId> = (1..=n_actions_p0).collect();
115    let p1_actions: Vec<String> = (0..n_actions_p1).map(|i| format!("A{}", i)).collect();
116    info.add_set(1, p1_nodes, p1_actions);
117    info
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use crate::tree::GameTree;
124
125    #[test]
126    fn test_info_set_creation() {
127        let mut info = InformationStructure::new();
128        let id = info.add_set(0, vec![0, 1], vec!["L".into(), "R".into()]);
129        assert_eq!(id, 0);
130        assert_eq!(info.sets.len(), 1);
131    }
132
133    #[test]
134    fn test_same_set() {
135        let mut info = InformationStructure::new();
136        info.add_set(0, vec![0, 1, 2], vec!["L".into(), "R".into()]);
137        assert!(info.same_set(0, 1));
138        assert!(info.same_set(0, 2));
139        assert!(!info.same_set(0, 3));
140    }
141
142    #[test]
143    fn test_set_for_node() {
144        let mut info = InformationStructure::new();
145        info.add_set(0, vec![0, 1], vec!["L".into(), "R".into()]);
146        let s = info.set_for_node(0).unwrap();
147        assert_eq!(s.player, 0);
148        assert!(info.set_for_node(99).is_none());
149    }
150
151    #[test]
152    fn test_validate_consistent() {
153        let mut tree = GameTree::new("Test");
154        let root = tree.add_decision(0, "P0");
155        let n0 = tree.add_decision(1, "P1-a");
156        let n1 = tree.add_decision(1, "P1-b");
157        let t0 = tree.add_terminal(vec![1.0, 2.0], "End0");
158        let t1 = tree.add_terminal(vec![3.0, 4.0], "End1");
159        let t2 = tree.add_terminal(vec![5.0, 6.0], "End2");
160        let t3 = tree.add_terminal(vec![7.0, 8.0], "End3");
161        tree.add_action(root, "L", n0);
162        tree.add_action(root, "R", n1);
163        tree.add_action(n0, "U", t0);
164        tree.add_action(n0, "D", t1);
165        tree.add_action(n1, "U", t2);
166        tree.add_action(n1, "D", t3);
167
168        let mut info = InformationStructure::new();
169        info.add_set(1, vec![n0, n1], vec!["U".into(), "D".into()]);
170        assert!(info.validate(&tree).is_ok());
171    }
172}