1use crate::discount::DiscountParams;
27use crate::probability::normalize_inplace;
28use crate::regret_minimizer::regret_match;
29use crate::update_rule::UpdateRule;
30
31#[derive(Clone, Copy, Debug, PartialEq)]
36pub struct PlusDiscount {
37 pub alpha: f32,
39 pub gamma: f32,
41}
42
43fn plus_strategy_discount(t: usize, gamma: f32) -> f32 {
47 if t > 1 {
48 ((t - 1) as f32 / t as f32).powf(gamma)
49 } else {
50 0.0
51 }
52}
53
54fn predicted_strategy(regret: &[f32], last_inst: &[f32], regret_discount: f32, out: &mut [f32]) {
58 for ((o, &r), &m) in out.iter_mut().zip(regret).zip(last_inst) {
59 *o = (r * regret_discount + m).max(0.0);
60 }
61 normalize_inplace(out);
62}
63
64pub struct Dcfr;
70
71pub struct DcfrStep {
73 positive: f32,
74 negative: f32,
75 strategy: f32,
76}
77
78impl UpdateRule for Dcfr {
79 type Params = DiscountParams;
80 type Step = DcfrStep;
81 const LANES: usize = 2;
82
83 fn step(p: &Self::Params, t: usize) -> Self::Step {
84 DcfrStep {
85 positive: DiscountParams::discount_factor(t, p.alpha),
86 negative: DiscountParams::discount_factor(t, p.beta),
87 strategy: (t as f32 / (t as f32 + 1.0)).powf(p.gamma),
88 }
89 }
90
91 fn strategy_from_lanes(_: &Self::Params, regret: &[f32], _: &[f32], _: f32, out: &mut [f32]) {
92 regret_match(regret, out);
93 }
94
95 fn pre_discount(_: &Self::Step) -> f32 {
96 0.0
97 }
98 fn post_discount(_: &Self::Step) -> f32 {
99 0.0
100 }
101
102 fn accumulate_regret(s: &Self::Step, old: f32, reward: f32, expected: f32) -> f32 {
103 let d = if old > 0.0 { s.positive } else { s.negative };
104 old * d + (reward - expected)
105 }
106
107 fn strategy_accumulation(s: &Self::Step) -> (f32, f32) {
108 (s.strategy, 1.0)
109 }
110
111 fn regret_weight_step(s: &Self::Step, old_w: f32) -> f32 {
112 old_w * s.positive + 1.0
113 }
114 fn regret_weight_total(_: &Self::Params, _t: usize, accum_w: f32) -> f32 {
115 accum_w
116 }
117}
118
119pub struct DcfrPlus;
126
127pub struct PlusStep {
129 regret: f32,
130 strategy: f32,
131}
132
133impl DcfrPlus {
134 pub const RECOMMENDED: PlusDiscount = PlusDiscount {
136 alpha: 1.5,
137 gamma: 4.0,
138 };
139}
140
141impl UpdateRule for DcfrPlus {
142 type Params = PlusDiscount;
143 type Step = PlusStep;
144 const LANES: usize = 2;
145
146 fn step(p: &Self::Params, t: usize) -> Self::Step {
147 PlusStep {
148 regret: if t > 1 {
151 DiscountParams::discount_factor(t - 1, p.alpha)
152 } else {
153 0.0
154 },
155 strategy: plus_strategy_discount(t, p.gamma),
156 }
157 }
158
159 fn strategy_from_lanes(_: &Self::Params, regret: &[f32], _: &[f32], _: f32, out: &mut [f32]) {
160 regret_match(regret, out);
161 }
162
163 fn pre_discount(_: &Self::Step) -> f32 {
164 0.0
165 }
166 fn post_discount(_: &Self::Step) -> f32 {
167 0.0
168 }
169
170 fn accumulate_regret(s: &Self::Step, old: f32, reward: f32, expected: f32) -> f32 {
171 (old * s.regret + reward - expected).max(0.0)
173 }
174
175 fn strategy_accumulation(s: &Self::Step) -> (f32, f32) {
176 (s.strategy, 1.0)
177 }
178
179 fn regret_weight_step(s: &Self::Step, old_w: f32) -> f32 {
180 old_w * s.regret + 1.0
181 }
182 fn regret_weight_total(_: &Self::Params, _t: usize, accum_w: f32) -> f32 {
183 accum_w
184 }
185}
186
187pub struct LinearCfr;
194
195pub struct LinearStep {
197 t: f32,
198}
199
200impl UpdateRule for LinearCfr {
201 type Params = ();
202 type Step = LinearStep;
203 const LANES: usize = 2;
204
205 fn step(_: &Self::Params, t: usize) -> Self::Step {
206 LinearStep { t: t as f32 }
207 }
208
209 fn strategy_from_lanes(_: &Self::Params, regret: &[f32], _: &[f32], _: f32, out: &mut [f32]) {
210 regret_match(regret, out);
211 }
212
213 fn pre_discount(_: &Self::Step) -> f32 {
214 0.0
215 }
216 fn post_discount(_: &Self::Step) -> f32 {
217 0.0
218 }
219
220 fn accumulate_regret(s: &Self::Step, old: f32, reward: f32, expected: f32) -> f32 {
221 old + s.t * (reward - expected)
222 }
223
224 fn strategy_accumulation(s: &Self::Step) -> (f32, f32) {
225 (1.0, s.t)
226 }
227
228 fn regret_weight_step(_: &Self::Step, old_w: f32) -> f32 {
229 old_w }
231 fn regret_weight_total(_: &Self::Params, t: usize, _accum_w: f32) -> f32 {
232 let t = t as f32;
234 t * (t + 1.0) / 2.0
235 }
236}
237
238pub struct PcfrPlus;
244
245pub struct PcfrPlusStep {
248 quadratic: f32,
249}
250
251impl UpdateRule for PcfrPlus {
252 type Params = ();
253 type Step = PcfrPlusStep;
254 const LANES: usize = 3;
255
256 fn step(_: &Self::Params, t: usize) -> Self::Step {
257 PcfrPlusStep {
258 quadratic: (t * t) as f32,
259 }
260 }
261
262 fn strategy_from_lanes(
263 _: &Self::Params,
264 regret: &[f32],
265 last: &[f32],
266 d: f32,
267 out: &mut [f32],
268 ) {
269 predicted_strategy(regret, last, d, out);
270 }
271
272 fn pre_discount(_: &Self::Step) -> f32 {
273 1.0 }
275 fn post_discount(_: &Self::Step) -> f32 {
276 1.0
277 }
278
279 fn accumulate_regret(_: &Self::Step, old: f32, reward: f32, expected: f32) -> f32 {
280 (old + (reward - expected)).max(0.0)
281 }
282
283 fn strategy_accumulation(s: &Self::Step) -> (f32, f32) {
284 (1.0, s.quadratic)
285 }
286
287 fn regret_weight_step(_: &Self::Step, old_w: f32) -> f32 {
288 old_w }
290 fn regret_weight_total(_: &Self::Params, t: usize, _accum_w: f32) -> f32 {
291 t as f32
292 }
293}
294
295pub struct PdcfrPlus;
302
303pub struct PdcfrPlusStep {
305 previous: f32,
308 current: f32,
310 strategy: f32,
311}
312
313impl PdcfrPlus {
314 pub const RECOMMENDED: PlusDiscount = PlusDiscount {
316 alpha: 2.3,
317 gamma: 5.0,
318 };
319}
320
321impl UpdateRule for PdcfrPlus {
322 type Params = PlusDiscount;
323 type Step = PdcfrPlusStep;
324 const LANES: usize = 3;
325
326 fn step(p: &Self::Params, t: usize) -> Self::Step {
327 PdcfrPlusStep {
328 previous: if t > 1 {
329 DiscountParams::discount_factor(t - 1, p.alpha)
330 } else {
331 0.0
332 },
333 current: DiscountParams::discount_factor(t, p.alpha),
334 strategy: plus_strategy_discount(t, p.gamma),
335 }
336 }
337
338 fn strategy_from_lanes(
339 _: &Self::Params,
340 regret: &[f32],
341 last: &[f32],
342 d: f32,
343 out: &mut [f32],
344 ) {
345 predicted_strategy(regret, last, d, out);
346 }
347
348 fn pre_discount(s: &Self::Step) -> f32 {
349 s.previous
350 }
351 fn post_discount(s: &Self::Step) -> f32 {
352 s.current
353 }
354
355 fn accumulate_regret(s: &Self::Step, old: f32, reward: f32, expected: f32) -> f32 {
356 (old * s.previous + (reward - expected)).max(0.0)
358 }
359
360 fn strategy_accumulation(s: &Self::Step) -> (f32, f32) {
361 (s.strategy, 1.0)
362 }
363
364 fn regret_weight_step(s: &Self::Step, old_w: f32) -> f32 {
365 old_w * s.previous + 1.0
366 }
367 fn regret_weight_total(_: &Self::Params, _t: usize, accum_w: f32) -> f32 {
368 accum_w
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use crate::discount::DiscountParams;
376 use crate::update_rule::UpdateRule;
377
378 #[test]
381 fn dcfr_mirrors_scalar() {
382 let p = DiscountParams::RECOMMENDED;
383 let s = Dcfr::step(&p, 3);
384 let pos = DiscountParams::discount_factor(3, p.alpha);
385 let neg = DiscountParams::discount_factor(3, p.beta);
386 let strat = (3.0f32 / 4.0).powf(p.gamma);
387
388 assert_eq!(
391 Dcfr::accumulate_regret(&s, 2.0, 5.0, 1.0),
392 2.0 * pos + (5.0 - 1.0)
393 );
394 assert_eq!(
395 Dcfr::accumulate_regret(&s, -2.0, 5.0, 1.0),
396 -2.0 * neg + (5.0 - 1.0)
397 );
398 assert_eq!(Dcfr::strategy_accumulation(&s), (strat, 1.0));
399 assert_eq!(Dcfr::regret_weight_step(&s, 4.0), 4.0 * pos + 1.0);
400 assert_eq!(Dcfr::regret_weight_total(&p, 7, 9.5), 9.5);
401 assert_eq!(Dcfr::pre_discount(&s), 0.0);
402 assert_eq!(Dcfr::LANES, 2);
403 }
404
405 #[test]
408 fn dcfr_plus_mirrors_scalar() {
409 let p = DcfrPlus::RECOMMENDED;
410 let s = DcfrPlus::step(&p, 3);
411 let prev = DiscountParams::discount_factor(2, p.alpha);
412 let strat = (2.0f32 / 3.0).powf(p.gamma);
413
414 assert_eq!(
416 DcfrPlus::accumulate_regret(&s, 2.0, 5.0, 1.0),
417 (2.0 * prev + 5.0 - 1.0).max(0.0)
418 );
419 assert_eq!(DcfrPlus::accumulate_regret(&s, -10.0, 0.0, 1.0), 0.0); assert_eq!(DcfrPlus::strategy_accumulation(&s), (strat, 1.0));
421 assert_eq!(DcfrPlus::regret_weight_step(&s, 4.0), 4.0 * prev + 1.0);
422 assert_eq!(DcfrPlus::pre_discount(&s), 0.0); assert_eq!(DcfrPlus::LANES, 2);
424 }
425
426 #[test]
427 fn dcfr_plus_first_iteration_has_no_history() {
428 let s = DcfrPlus::step(&DcfrPlus::RECOMMENDED, 1);
429 assert_eq!(
431 DcfrPlus::accumulate_regret(&s, 9.0, 2.0, 0.5),
432 (2.0f32 - 0.5).max(0.0)
433 );
434 assert_eq!(DcfrPlus::strategy_accumulation(&s).0, 0.0);
435 }
436
437 #[test]
440 fn linear_cfr_mirrors_scalar() {
441 let s = LinearCfr::step(&(), 4);
442 assert_eq!(
444 LinearCfr::accumulate_regret(&s, 3.0, 5.0, 1.0),
445 3.0 + 4.0 * (5.0 - 1.0)
446 );
447 assert_eq!(LinearCfr::strategy_accumulation(&s), (1.0, 4.0));
448 assert_eq!(LinearCfr::regret_weight_total(&(), 4, 0.0), 4.0 * 5.0 / 2.0);
449 assert_eq!(LinearCfr::pre_discount(&s), 0.0);
450 assert_eq!(LinearCfr::LANES, 2);
451 }
452
453 #[test]
456 fn pcfr_plus_mirrors_scalar() {
457 let s = PcfrPlus::step(&(), 3);
458 assert_eq!(
460 PcfrPlus::accumulate_regret(&s, 1.0, 4.0, 1.0),
461 (1.0f32 + (4.0 - 1.0)).max(0.0)
462 );
463 assert_eq!(PcfrPlus::accumulate_regret(&s, 0.0, 0.0, 5.0), 0.0); assert_eq!(PcfrPlus::strategy_accumulation(&s), (1.0, 9.0)); assert_eq!(PcfrPlus::regret_weight_total(&(), 3, 0.0), 3.0); assert_eq!(PcfrPlus::pre_discount(&s), 1.0);
468 assert_eq!(PcfrPlus::post_discount(&s), 1.0);
469 assert_eq!(PcfrPlus::LANES, 3);
470
471 let mut out = [0.0f32; 2];
473 PcfrPlus::strategy_from_lanes(&(), &[1.0, 0.0], &[0.0, 1.0], 1.0, &mut out);
474 assert!((out[0] - 0.5).abs() < 1e-6 && (out[1] - 0.5).abs() < 1e-6);
475 }
476
477 #[test]
480 fn pdcfr_plus_mirrors_scalar() {
481 let p = PdcfrPlus::RECOMMENDED; let s = PdcfrPlus::step(&p, 3);
483 let prev = DiscountParams::discount_factor(2, p.alpha);
484 let curr = DiscountParams::discount_factor(3, p.alpha);
485 let strat = (2.0f32 / 3.0).powf(p.gamma);
486
487 assert_eq!(
489 PdcfrPlus::accumulate_regret(&s, 2.0, 5.0, 1.0),
490 (2.0 * prev + (5.0 - 1.0)).max(0.0)
491 );
492 assert_eq!(PdcfrPlus::strategy_accumulation(&s), (strat, 1.0));
493 assert_eq!(PdcfrPlus::regret_weight_step(&s, 4.0), 4.0 * prev + 1.0);
494 assert_eq!(PdcfrPlus::pre_discount(&s), prev);
496 assert_eq!(PdcfrPlus::post_discount(&s), curr);
497 assert_eq!(PdcfrPlus::LANES, 3);
498
499 let mut out = [0.0f32; 2];
501 PdcfrPlus::strategy_from_lanes(&p, &[1.0, 1.0], &[0.0, 0.0], curr, &mut out);
502 assert!((out[0] - 0.5).abs() < 1e-6 && (out[1] - 0.5).abs() < 1e-6);
503 }
504}