1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! # Little Sorry
//!
//! Regret minimization algorithms for finding Nash equilibrium strategies
//! in imperfect-information games.
//!
//! ## Available Algorithms
//!
//! All algorithms implement the [`RegretMinimizer`] trait:
//!
//! | Type | Algorithm | Key Property |
//! |------|-----------|--------------|
//! | [`CfrPlusRegretMatcher`] | CFR+ | Regret clipping at zero |
//! | [`DiscountedRegretMatcher`] | DCFR | Configurable time-based discounting |
//! | [`DcfrPlusRegretMatcher`] | DCFR+ | DCFR discounting + CFR+ clipping |
//! | [`LinearCfrRegretMatcher`] | Linear CFR | Linear time-weighted regrets |
//! | [`PcfrPlusRegretMatcher`] | PCFR+ | Predictive future regret estimates |
//! | [`PdcfrPlusRegretMatcher`] | PDCFR+ | DCFR+ discounting + predictive updates |
//!
//! ## Quick Start
//!
//! ```
//! use little_sorry::{CfrPlusRegretMatcher, RegretMinimizer};
//!
//! let mut matcher = CfrPlusRegretMatcher::new(3);
//! for _ in 0..1000 {
//! matcher.update_regret(&[1.0, -0.5, 0.2]);
//! }
//! let strategy = matcher.best_weight();
//! assert!((strategy.iter().sum::<f32>() - 1.0).abs() < 1e-6);
//! ```
//!
//! ## Batched, storage-generic matchers
//!
//! For large or concurrent solves, [`BatchedMatcher`] owns many information sets
//! ("rows") that advance on one shared iteration clock, so a rule's
//! time-dependent factors are computed once per batch rather than once per row.
//! It is generic over both the update rule (one of [`Dcfr`], [`DcfrPlus`],
//! [`LinearCfr`], [`PcfrPlus`], [`PdcfrPlus`]) and the cell backend ([`Local`]
//! for zero-overhead single-threaded use, [`Atomic`] for lock-free concurrent
//! updates through a shared reference). Swapping either is a one-type change.
//!
//! The solved average strategy reads out the same way for every rule and can be
//! exported compactly with [`quantize_dist`] / [`dequantize_dist`]:
//!
//! ```
//! use little_sorry::{BatchedMatcher, Dcfr, DiscountParams, Local};
//! use little_sorry::{dequantize_dist, quantize_dist};
//!
//! // One node owning 8 abstraction classes over 3 actions.
//! let node = BatchedMatcher::<Dcfr, Local>::new(8, 3, DiscountParams::RECOMMENDED);
//! let mut expected = [0.0; 8];
//! for _ in 0..1000 {
//! // The caller supplies values from whatever layout it holds.
//! node.update_batch(|action, _row| [1.0, -0.5, 0.2][action], &mut expected);
//! }
//!
//! let mut probs = [0.0; 3];
//! node.average_into(0, &mut probs); // normalized average strategy, any rule
//! let codes = quantize_dist::<u16>(&probs); // compact on-disk form
//! let reloaded = dequantize_dist::<u16>(&codes); // decodes AND renormalizes
//! assert!((reloaded.iter().sum::<f32>() - 1.0).abs() < 1e-6);
//! ```
pub use CfrPlusRegretMatcher;
pub use DiscountedRegretMatcher;
pub use DcfrPlusRegretMatcher;
pub use DiscountParams;
pub use LinearCfrRegretMatcher;
pub use PcfrPlusRegretMatcher;
pub use PdcfrPlusRegretMatcher;
pub use RegretMinimizer;
// Batched, storage-generic machinery.
pub use BatchedMatcher;
pub use ;
pub use ;
pub use ;
pub use UpdateRule;
// Re-export for backwards compatibility
pub use CfrPlusRegretMatcher as RegretMatcher;