Skip to main content

oxilean_std/denotational_semantics/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::{BTreeMap, HashMap};
6
7use super::functions::*;
8
9/// A move in an arena.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct ArenaMove {
12    /// Unique identifier.
13    pub id: usize,
14    /// Polarity.
15    pub polarity: Polarity,
16    /// Initial move flag (no enabler).
17    pub initial: bool,
18}
19/// A partial order on a finite set represented as an adjacency relation.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct FinitePartialOrder {
22    /// Number of elements (indexed 0..n).
23    pub n: usize,
24    /// Relation: `leq[i][j]` is true iff i ≤ j.
25    pub leq: Vec<Vec<bool>>,
26}
27impl FinitePartialOrder {
28    /// Create a discrete partial order (only reflexive pairs).
29    pub fn discrete(n: usize) -> Self {
30        let leq = (0..n).map(|i| (0..n).map(|j| i == j).collect()).collect();
31        FinitePartialOrder { n, leq }
32    }
33    /// Create the flat order: ⊥ ≤ everything.
34    pub fn flat(n: usize) -> Self {
35        let k = n + 1;
36        let leq = (0..k)
37            .map(|i| (0..k).map(|j| i == j || i == 0).collect())
38            .collect();
39        FinitePartialOrder { n: k, leq }
40    }
41    /// Check whether the order satisfies reflexivity.
42    pub fn is_reflexive(&self) -> bool {
43        (0..self.n).all(|i| self.leq[i][i])
44    }
45    /// Check whether the order satisfies transitivity.
46    pub fn is_transitive(&self) -> bool {
47        for i in 0..self.n {
48            for j in 0..self.n {
49                for k in 0..self.n {
50                    if self.leq[i][j] && self.leq[j][k] && !self.leq[i][k] {
51                        return false;
52                    }
53                }
54            }
55        }
56        true
57    }
58    /// Check whether the order satisfies antisymmetry.
59    pub fn is_antisymmetric(&self) -> bool {
60        for i in 0..self.n {
61            for j in 0..self.n {
62                if i != j && self.leq[i][j] && self.leq[j][i] {
63                    return false;
64                }
65            }
66        }
67        true
68    }
69    /// Check that this is a valid partial order.
70    pub fn is_valid(&self) -> bool {
71        self.is_reflexive() && self.is_transitive() && self.is_antisymmetric()
72    }
73    /// Compute the least upper bound of two elements, if it exists.
74    pub fn lub(&self, a: usize, b: usize) -> Option<usize> {
75        let ubs: Vec<usize> = (0..self.n)
76            .filter(|&x| self.leq[a][x] && self.leq[b][x])
77            .collect();
78        ubs.iter()
79            .find(|&&x| ubs.iter().all(|&y| self.leq[x][y]))
80            .copied()
81    }
82    /// Compute the greatest lower bound of two elements, if it exists.
83    pub fn glb(&self, a: usize, b: usize) -> Option<usize> {
84        let lbs: Vec<usize> = (0..self.n)
85            .filter(|&x| self.leq[x][a] && self.leq[x][b])
86            .collect();
87        lbs.iter()
88            .find(|&&x| lbs.iter().all(|&y| self.leq[y][x]))
89            .copied()
90    }
91}
92/// A concrete trace over an action type `A`.
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct Trace<A: Clone + Eq> {
95    /// The sequence of actions.
96    pub actions: Vec<A>,
97}
98impl<A: Clone + Eq> Trace<A> {
99    /// Create an empty trace.
100    pub fn empty() -> Self {
101        Trace { actions: vec![] }
102    }
103    /// Create a trace from a vector of actions.
104    pub fn new(actions: Vec<A>) -> Self {
105        Trace { actions }
106    }
107    /// Append one action at the end.
108    pub fn extend(&self, a: A) -> Self {
109        let mut acts = self.actions.clone();
110        acts.push(a);
111        Trace { actions: acts }
112    }
113    /// Concatenate two traces.
114    pub fn concat(&self, other: &Self) -> Self {
115        let mut acts = self.actions.clone();
116        acts.extend_from_slice(&other.actions);
117        Trace { actions: acts }
118    }
119    /// Check whether `self` is a prefix of `other`.
120    pub fn is_prefix_of(&self, other: &Self) -> bool {
121        other.actions.starts_with(&self.actions)
122    }
123    /// Return the length of the trace.
124    pub fn len(&self) -> usize {
125        self.actions.len()
126    }
127    /// Check whether the trace is empty.
128    pub fn is_empty(&self) -> bool {
129        self.actions.is_empty()
130    }
131}
132/// A finite model of a partial combinatory algebra pre-populated with K, S, I.
133#[derive(Debug, Clone)]
134pub struct KleenePCA {
135    /// Registered elements by name -> index.
136    pub combinators: HashMap<String, usize>,
137    /// Application table: (f, x) -> result.
138    pub apply_table: HashMap<(usize, usize), usize>,
139    next_idx: usize,
140}
141impl KleenePCA {
142    /// Create with K(0), S(1), I(2) and K-partial applications K*a (indices 3-5).
143    pub fn with_ks() -> Self {
144        let mut pca = KleenePCA {
145            combinators: HashMap::new(),
146            apply_table: HashMap::new(),
147            next_idx: 6,
148        };
149        pca.combinators.insert("K".to_string(), 0);
150        pca.combinators.insert("S".to_string(), 1);
151        pca.combinators.insert("I".to_string(), 2);
152        for a in 0usize..3 {
153            let ka = 3 + a;
154            pca.apply_table.insert((0, a), ka);
155            for b in 0usize..3 {
156                pca.apply_table.insert((ka, b), a);
157            }
158        }
159        for a in 0usize..3 {
160            pca.apply_table.insert((2, a), a);
161        }
162        pca
163    }
164    /// Look up a named element's index.
165    pub fn lookup(&self, name: &str) -> Option<usize> {
166        self.combinators.get(name).copied()
167    }
168    /// Apply element `f` to element `x`.
169    pub fn apply(&self, f: usize, x: usize) -> Option<usize> {
170        self.apply_table.get(&(f, x)).copied()
171    }
172    /// Add a fresh named element, returning its index.
173    pub fn add_element(&mut self, name: impl Into<String>) -> usize {
174        let idx = self.next_idx;
175        self.combinators.insert(name.into(), idx);
176        self.next_idx += 1;
177        idx
178    }
179    /// Define an application rule: f * x = result.
180    pub fn define_app(&mut self, f: usize, x: usize, result: usize) {
181        self.apply_table.insert((f, x), result);
182    }
183    /// Verify K * a * b = a for a, b in 0..3.
184    pub fn check_k_law(&self) -> bool {
185        let k = match self.lookup("K") {
186            Some(v) => v,
187            None => return false,
188        };
189        for a in 0usize..3 {
190            let ka = match self.apply(k, a) {
191                Some(v) => v,
192                None => return false,
193            };
194            for b in 0usize..3 {
195                match self.apply(ka, b) {
196                    Some(r) if r == a => {}
197                    _ => return false,
198                }
199            }
200        }
201        true
202    }
203    /// Verify I * a = a for a in 0..3.
204    pub fn check_i_law(&self) -> bool {
205        let i = match self.lookup("I") {
206            Some(v) => v,
207            None => return false,
208        };
209        (0..3).all(|a| self.apply(i, a) == Some(a))
210    }
211}
212/// Polarity of a move: Opponent (O) or Proponent (P).
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub enum Polarity {
215    /// Opponent move.
216    O,
217    /// Proponent move.
218    P,
219}
220/// A finite approximation to a bilimit: a chain of finite CPOs.
221#[derive(Debug, Clone)]
222pub struct BilimitApprox {
223    /// Levels in the chain.
224    pub levels: Vec<BilimitStep>,
225}
226impl BilimitApprox {
227    /// Create a bilimit approximation from a sequence of steps.
228    pub fn new(levels: Vec<BilimitStep>) -> Self {
229        BilimitApprox { levels }
230    }
231    /// The depth of the approximation.
232    pub fn depth(&self) -> usize {
233        self.levels.len()
234    }
235    /// Project an element from level `l` all the way down to level 0.
236    pub fn project_to_base(&self, mut x: usize, l: usize) -> usize {
237        for step in self.levels.iter().take(l).rev() {
238            x = step.project(x);
239        }
240        x
241    }
242}
243/// A binary logical relation over a finite domain, indexed by type codes.
244#[derive(Debug, Clone)]
245pub struct LogicalRelation {
246    /// For each type code, the set of related pairs (i, j).
247    pub pairs: HashMap<String, Vec<(usize, usize)>>,
248}
249impl LogicalRelation {
250    /// Create an empty logical relation.
251    pub fn new() -> Self {
252        LogicalRelation {
253            pairs: HashMap::new(),
254        }
255    }
256    /// Add a related pair (a, b) for type `ty_code`.
257    pub fn add(&mut self, ty_code: impl Into<String>, a: usize, b: usize) {
258        self.pairs.entry(ty_code.into()).or_default().push((a, b));
259    }
260    /// Check whether (a, b) is related at type `ty_code`.
261    pub fn relates(&self, ty_code: &str, a: usize, b: usize) -> bool {
262        self.pairs
263            .get(ty_code)
264            .map_or(false, |v| v.contains(&(a, b)))
265    }
266    /// Check reflexivity for all elements 0..domain_size.
267    pub fn is_reflexive_on(&self, ty_code: &str, domain_size: usize) -> bool {
268        (0..domain_size).all(|a| self.relates(ty_code, a, a))
269    }
270    /// Check symmetry for a given type code.
271    pub fn is_symmetric_on(&self, ty_code: &str) -> bool {
272        if let Some(pairs) = self.pairs.get(ty_code) {
273            pairs.iter().all(|&(a, b)| pairs.contains(&(b, a)))
274        } else {
275            true
276        }
277    }
278    /// Number of related pairs for a type code.
279    pub fn size(&self, ty_code: &str) -> usize {
280        self.pairs.get(ty_code).map_or(0, |v| v.len())
281    }
282}
283/// A simple lambda-calculus term (de Bruijn) for CPS transformation.
284#[derive(Debug, Clone, PartialEq, Eq)]
285pub enum LambdaTerm {
286    /// Variable (de Bruijn index).
287    Var(usize),
288    /// Lambda abstraction.
289    Lam(Box<LambdaTerm>),
290    /// Application.
291    App(Box<LambdaTerm>, Box<LambdaTerm>),
292    /// Named constant.
293    Const(String),
294}
295impl LambdaTerm {
296    /// Create a variable.
297    pub fn var(n: usize) -> Self {
298        LambdaTerm::Var(n)
299    }
300    /// Create a lambda abstraction.
301    pub fn lam(body: LambdaTerm) -> Self {
302        LambdaTerm::Lam(Box::new(body))
303    }
304    /// Create an application.
305    pub fn app(f: LambdaTerm, x: LambdaTerm) -> Self {
306        LambdaTerm::App(Box::new(f), Box::new(x))
307    }
308    /// Create a named constant.
309    pub fn cst(s: impl Into<String>) -> Self {
310        LambdaTerm::Const(s.into())
311    }
312    /// Term size (number of constructors).
313    pub fn size(&self) -> usize {
314        match self {
315            LambdaTerm::Var(_) | LambdaTerm::Const(_) => 1,
316            LambdaTerm::Lam(b) => 1 + b.size(),
317            LambdaTerm::App(f, x) => 1 + f.size() + x.size(),
318        }
319    }
320    /// Check whether de Bruijn index `target` is free at binding depth `depth`.
321    pub fn has_free_var(&self, depth: usize, target: usize) -> bool {
322        match self {
323            LambdaTerm::Var(n) => *n == target + depth,
324            LambdaTerm::Const(_) => false,
325            LambdaTerm::Lam(b) => b.has_free_var(depth + 1, target),
326            LambdaTerm::App(f, x) => f.has_free_var(depth, target) || x.has_free_var(depth, target),
327        }
328    }
329}
330/// A monadic interpreter using Option to model partial PCF evaluation.
331#[derive(Debug, Clone, PartialEq, Eq)]
332pub struct MaybeInterp {
333    /// Maximum evaluation fuel.
334    pub fuel: u64,
335}
336impl MaybeInterp {
337    /// Create a new interpreter.
338    pub fn new(fuel: u64) -> Self {
339        MaybeInterp { fuel }
340    }
341    /// Monadic unit: wrap a value.
342    pub fn ret(v: PCFValue) -> Option<PCFValue> {
343        Some(v)
344    }
345    /// Monadic bind.
346    pub fn bind<F>(m: Option<PCFValue>, f: F) -> Option<PCFValue>
347    where
348        F: FnOnce(PCFValue) -> Option<PCFValue>,
349    {
350        m.and_then(f)
351    }
352    /// Evaluate a PCF term; return None if it diverges.
353    pub fn eval(&self, term: &PCFTerm) -> Option<PCFValue> {
354        let v = pcf_eval(term, self.fuel);
355        if v.is_bottom() {
356            None
357        } else {
358            Some(v)
359        }
360    }
361    /// Guard: None if condition is false.
362    pub fn guard(cond: bool) -> Option<()> {
363        if cond {
364            Some(())
365        } else {
366            None
367        }
368    }
369    /// Sequence two evaluations, discarding the first result.
370    pub fn seq(&self, t1: &PCFTerm, t2: &PCFTerm) -> Option<PCFValue> {
371        Self::bind(self.eval(t1), |_| self.eval(t2))
372    }
373    /// Map over a monadic value.
374    pub fn map<F>(m: Option<PCFValue>, f: F) -> Option<PCFValue>
375    where
376        F: FnOnce(PCFValue) -> PCFValue,
377    {
378        m.map(f)
379    }
380}
381/// A Scott-open set on a finite flat domain {bottom=0, 1, ..., domain_size-1}.
382#[derive(Debug, Clone, PartialEq, Eq)]
383pub struct ScottOpen {
384    /// Total domain size (bottom at index 0).
385    pub domain_size: usize,
386    /// Elements in the open set (sorted, all > 0).
387    pub elements: Vec<usize>,
388}
389impl ScottOpen {
390    /// Create a Scott-open set (bottom is automatically excluded).
391    pub fn new(domain_size: usize, elems: impl IntoIterator<Item = usize>) -> Self {
392        let mut v: Vec<usize> = elems
393            .into_iter()
394            .filter(|&x| x > 0 && x < domain_size)
395            .collect();
396        v.sort_unstable();
397        v.dedup();
398        ScottOpen {
399            domain_size,
400            elements: v,
401        }
402    }
403    /// The whole domain minus bottom is Scott-open.
404    pub fn top(domain_size: usize) -> Self {
405        ScottOpen {
406            domain_size,
407            elements: (1..domain_size).collect(),
408        }
409    }
410    /// The empty set is Scott-open.
411    pub fn empty(domain_size: usize) -> Self {
412        ScottOpen {
413            domain_size,
414            elements: vec![],
415        }
416    }
417    /// Test membership.
418    pub fn contains(&self, x: usize) -> bool {
419        self.elements.binary_search(&x).is_ok()
420    }
421    /// Union (stays Scott-open).
422    pub fn union(&self, other: &Self) -> Self {
423        let mut v = self.elements.clone();
424        for &x in &other.elements {
425            if !self.contains(x) {
426                v.push(x);
427            }
428        }
429        v.sort_unstable();
430        ScottOpen {
431            domain_size: self.domain_size,
432            elements: v,
433        }
434    }
435    /// Intersection (stays Scott-open).
436    pub fn intersection(&self, other: &Self) -> Self {
437        let v: Vec<usize> = self
438            .elements
439            .iter()
440            .filter(|&&x| other.contains(x))
441            .copied()
442            .collect();
443        ScottOpen {
444            domain_size: self.domain_size,
445            elements: v,
446        }
447    }
448    /// Verify Scott-openness: bottom must not be in the set.
449    pub fn is_scott_open(&self) -> bool {
450        !self.contains(0)
451    }
452    /// Characteristic membership function.
453    pub fn characteristic(&self, x: usize) -> bool {
454        self.contains(x)
455    }
456}
457/// A game arena: a set of moves with a polarity function and enabling relation.
458#[derive(Debug, Clone)]
459pub struct GameArena {
460    /// The moves in this arena.
461    pub moves: Vec<ArenaMove>,
462    /// Enabling relation: `enables[i]` = set of moves that move `i` enables.
463    pub enables: HashMap<usize, Vec<usize>>,
464}
465impl GameArena {
466    /// Create an arena with given moves.
467    pub fn new(moves: Vec<ArenaMove>) -> Self {
468        GameArena {
469            moves,
470            enables: HashMap::new(),
471        }
472    }
473    /// Add an enabling: move with id `from` enables move with id `to`.
474    pub fn add_enables(&mut self, from: usize, to: usize) {
475        self.enables.entry(from).or_default().push(to);
476    }
477    /// Return all initial (O) moves.
478    pub fn initial_moves(&self) -> Vec<&ArenaMove> {
479        self.moves.iter().filter(|m| m.initial).collect()
480    }
481}
482/// The result of evaluating a PCF term: either a value or divergence (⊥).
483#[derive(Debug, Clone, PartialEq, Eq)]
484pub enum PCFValue {
485    /// A natural number value.
486    Num(u64),
487    /// A Boolean value.
488    Bool(bool),
489    /// Divergence (bottom element ⊥).
490    Bottom,
491    /// A closure (lambda body + captured environment depth).
492    Closure(Box<PCFTerm>, usize),
493}
494impl PCFValue {
495    /// Check whether this value is bottom.
496    pub fn is_bottom(&self) -> bool {
497        matches!(self, PCFValue::Bottom)
498    }
499    /// Check whether this value is a natural number.
500    pub fn is_nat(&self) -> bool {
501        matches!(self, PCFValue::Num(_))
502    }
503}
504/// A monotone endofunction on `usize` values (simulates a Scott-continuous map
505/// on a finite CPO where elements are represented as `usize` indices).
506#[derive(Clone)]
507pub struct MonotoneMap {
508    /// The underlying function table: `table[i] = f(i)`.
509    pub table: Vec<usize>,
510}
511impl MonotoneMap {
512    /// Create from an explicit table.
513    pub fn new(table: Vec<usize>) -> Self {
514        MonotoneMap { table }
515    }
516    /// Apply the function.
517    pub fn apply(&self, x: usize) -> usize {
518        self.table[x]
519    }
520    /// Check monotonicity with respect to a given partial order.
521    pub fn is_monotone(&self, po: &FinitePartialOrder) -> bool {
522        for i in 0..po.n.min(self.table.len()) {
523            for j in 0..po.n.min(self.table.len()) {
524                if po.leq[i][j] {
525                    let fi = self.table[i];
526                    let fj = self.table[j];
527                    if fi < po.n && fj < po.n && !po.leq[fi][fj] {
528                        return false;
529                    }
530                }
531            }
532        }
533        true
534    }
535}
536/// A PCF term (surface AST).
537#[derive(Debug, Clone, PartialEq, Eq)]
538pub enum PCFTerm {
539    /// Variable (de Bruijn index).
540    Var(usize),
541    /// Zero : Nat.
542    Zero,
543    /// True : Bool.
544    True,
545    /// False : Bool.
546    False,
547    /// Successor.
548    Succ(Box<PCFTerm>),
549    /// Predecessor (Pred(Zero) = Zero by convention).
550    Pred(Box<PCFTerm>),
551    /// IsZero test.
552    IsZero(Box<PCFTerm>),
553    /// λ-abstraction.
554    Lam(Box<PCFTerm>),
555    /// Application.
556    App(Box<PCFTerm>, Box<PCFTerm>),
557    /// Fixed-point combinator Y.
558    Fix(Box<PCFTerm>),
559    /// Conditional.
560    If(Box<PCFTerm>, Box<PCFTerm>, Box<PCFTerm>),
561}
562impl PCFTerm {
563    /// Return a human-readable label for the outermost constructor.
564    pub fn label(&self) -> &'static str {
565        match self {
566            PCFTerm::Var(_) => "Var",
567            PCFTerm::Zero => "Zero",
568            PCFTerm::True => "True",
569            PCFTerm::False => "False",
570            PCFTerm::Succ(_) => "Succ",
571            PCFTerm::Pred(_) => "Pred",
572            PCFTerm::IsZero(_) => "IsZero",
573            PCFTerm::Lam(_) => "Lam",
574            PCFTerm::App(_, _) => "App",
575            PCFTerm::Fix(_) => "Fix",
576            PCFTerm::If(_, _, _) => "If",
577        }
578    }
579}
580/// A step in building a bilimit: a finite CPO together with the sequence of
581/// projection maps from each level to the previous.
582#[derive(Debug, Clone)]
583pub struct BilimitStep {
584    /// The size of the CPO at this step.
585    pub size: usize,
586    /// The projection table `proj[x] = p(x)` mapping this level to the previous.
587    pub proj: Vec<usize>,
588}
589impl BilimitStep {
590    /// Create a bilimit step.
591    pub fn new(size: usize, proj: Vec<usize>) -> Self {
592        BilimitStep { size, proj }
593    }
594    /// Apply the projection.
595    pub fn project(&self, x: usize) -> usize {
596        self.proj.get(x).copied().unwrap_or(0)
597    }
598}
599/// A PCF type.
600#[derive(Debug, Clone, PartialEq, Eq)]
601pub enum PCFType {
602    /// The type of natural numbers.
603    Nat,
604    /// The type of Booleans.
605    Bool,
606    /// Function type.
607    Arrow(Box<PCFType>, Box<PCFType>),
608}
609impl PCFType {
610    /// Construct a function type τ₁ → τ₂.
611    pub fn arrow(t1: PCFType, t2: PCFType) -> Self {
612        PCFType::Arrow(Box::new(t1), Box::new(t2))
613    }
614    /// Return the string name of the type.
615    pub fn name(&self) -> String {
616        match self {
617            PCFType::Nat => "Nat".to_string(),
618            PCFType::Bool => "Bool".to_string(),
619            PCFType::Arrow(a, b) => format!("({} → {})", a.name(), b.name()),
620        }
621    }
622}
623/// A finite sub-probability distribution over a set of elements.
624#[derive(Debug, Clone)]
625pub struct FiniteValuation {
626    /// Probability mass for each element (indexed by element key).
627    pub masses: BTreeMap<String, f64>,
628}
629impl FiniteValuation {
630    /// Create an empty valuation (mass 0 everywhere).
631    pub fn empty() -> Self {
632        FiniteValuation {
633            masses: BTreeMap::new(),
634        }
635    }
636    /// Create a Dirac (point-mass) valuation at `x`.
637    pub fn dirac(x: impl Into<String>) -> Self {
638        let mut m = BTreeMap::new();
639        m.insert(x.into(), 1.0);
640        FiniteValuation { masses: m }
641    }
642    /// Total mass.
643    pub fn total_mass(&self) -> f64 {
644        self.masses.values().sum()
645    }
646    /// Check whether this is a sub-probability distribution (total mass ≤ 1).
647    pub fn is_sub_probability(&self) -> bool {
648        self.total_mass() <= 1.0 + 1e-9
649    }
650    /// Convex combination: (1 − p) * self + p * other.
651    pub fn mix(&self, other: &Self, p: f64) -> Self {
652        let mut m = BTreeMap::new();
653        for (k, v) in &self.masses {
654            *m.entry(k.clone()).or_insert(0.0) += (1.0 - p) * v;
655        }
656        for (k, v) in &other.masses {
657            *m.entry(k.clone()).or_insert(0.0) += p * v;
658        }
659        FiniteValuation { masses: m }
660    }
661    /// Stochastic order: self ≤ other iff for every "up-set" U,
662    /// self(U) ≤ other(U).  For a flat order this means self(x) ≤ other(x)
663    /// for every x (since singletons are up-sets).
664    pub fn stochastic_leq(&self, other: &Self) -> bool {
665        for (k, v) in &self.masses {
666            let w = other.masses.get(k).copied().unwrap_or(0.0);
667            if *v > w + 1e-9 {
668                return false;
669            }
670        }
671        true
672    }
673}
674/// A lower (Hoare) power domain element: a nonempty downward-closed set of
675/// `usize` values with a Hoare ordering.
676#[derive(Debug, Clone, PartialEq, Eq)]
677pub struct HoareElement {
678    /// The underlying finite set (kept sorted for canonical representation).
679    pub elems: Vec<usize>,
680}
681impl HoareElement {
682    /// Create from an iterator, removing duplicates and sorting.
683    pub fn new(iter: impl IntoIterator<Item = usize>) -> Self {
684        let mut v: Vec<usize> = iter.into_iter().collect();
685        v.sort_unstable();
686        v.dedup();
687        HoareElement { elems: v }
688    }
689    /// The Hoare (subset) ordering: `self ≤ other` iff `self ⊆ other`.
690    pub fn hoare_leq(&self, other: &Self) -> bool {
691        self.elems.iter().all(|x| other.elems.contains(x))
692    }
693    /// Union (join in Hoare order).
694    pub fn union(&self, other: &Self) -> Self {
695        let mut v = self.elems.clone();
696        for x in &other.elems {
697            if !v.contains(x) {
698                v.push(*x);
699            }
700        }
701        v.sort_unstable();
702        HoareElement { elems: v }
703    }
704}
705/// An innocent strategy: a prefix-closed set of P-views (encoded as sequences of
706/// move ids) along with the P-response for each view.
707#[derive(Debug, Clone)]
708pub struct InnocentStrategy {
709    /// Map from P-view (sequence of move ids) → P-move id chosen in response.
710    pub responses: HashMap<Vec<usize>, usize>,
711}
712impl InnocentStrategy {
713    /// Create an empty strategy.
714    pub fn new() -> Self {
715        InnocentStrategy {
716            responses: HashMap::new(),
717        }
718    }
719    /// Add a response: given the current P-view `view`, respond with `p_move`.
720    pub fn add_response(&mut self, view: Vec<usize>, p_move: usize) {
721        self.responses.insert(view, p_move);
722    }
723    /// Look up the response for a given P-view.
724    pub fn respond(&self, view: &[usize]) -> Option<usize> {
725        self.responses.get(view).copied()
726    }
727    /// Number of defined responses.
728    pub fn size(&self) -> usize {
729        self.responses.len()
730    }
731}