use crate::discount::DiscountParams;
use crate::probability::normalize_inplace;
use crate::regret_minimizer::regret_match;
use crate::update_rule::UpdateRule;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PlusDiscount {
pub alpha: f32,
pub gamma: f32,
}
fn plus_strategy_discount(t: usize, gamma: f32) -> f32 {
if t > 1 {
((t - 1) as f32 / t as f32).powf(gamma)
} else {
0.0
}
}
fn predicted_strategy(regret: &[f32], last_inst: &[f32], regret_discount: f32, out: &mut [f32]) {
for ((o, &r), &m) in out.iter_mut().zip(regret).zip(last_inst) {
*o = (r * regret_discount + m).max(0.0);
}
normalize_inplace(out);
}
pub struct Dcfr;
pub struct DcfrStep {
positive: f32,
negative: f32,
strategy: f32,
}
impl UpdateRule for Dcfr {
type Params = DiscountParams;
type Step = DcfrStep;
const LANES: usize = 2;
fn step(p: &Self::Params, t: usize) -> Self::Step {
DcfrStep {
positive: DiscountParams::discount_factor(t, p.alpha),
negative: DiscountParams::discount_factor(t, p.beta),
strategy: (t as f32 / (t as f32 + 1.0)).powf(p.gamma),
}
}
fn strategy_from_lanes(_: &Self::Params, regret: &[f32], _: &[f32], _: f32, out: &mut [f32]) {
regret_match(regret, out);
}
fn pre_discount(_: &Self::Step) -> f32 {
0.0
}
fn post_discount(_: &Self::Step) -> f32 {
0.0
}
fn accumulate_regret(s: &Self::Step, old: f32, reward: f32, expected: f32) -> f32 {
let d = if old > 0.0 { s.positive } else { s.negative };
old * d + (reward - expected)
}
fn strategy_accumulation(s: &Self::Step) -> (f32, f32) {
(s.strategy, 1.0)
}
fn regret_weight_step(s: &Self::Step, old_w: f32) -> f32 {
old_w * s.positive + 1.0
}
fn regret_weight_total(_: &Self::Params, _t: usize, accum_w: f32) -> f32 {
accum_w
}
}
pub struct DcfrPlus;
pub struct PlusStep {
regret: f32,
strategy: f32,
}
impl DcfrPlus {
pub const RECOMMENDED: PlusDiscount = PlusDiscount {
alpha: 1.5,
gamma: 4.0,
};
}
impl UpdateRule for DcfrPlus {
type Params = PlusDiscount;
type Step = PlusStep;
const LANES: usize = 2;
fn step(p: &Self::Params, t: usize) -> Self::Step {
PlusStep {
regret: if t > 1 {
DiscountParams::discount_factor(t - 1, p.alpha)
} else {
0.0
},
strategy: plus_strategy_discount(t, p.gamma),
}
}
fn strategy_from_lanes(_: &Self::Params, regret: &[f32], _: &[f32], _: f32, out: &mut [f32]) {
regret_match(regret, out);
}
fn pre_discount(_: &Self::Step) -> f32 {
0.0
}
fn post_discount(_: &Self::Step) -> f32 {
0.0
}
fn accumulate_regret(s: &Self::Step, old: f32, reward: f32, expected: f32) -> f32 {
(old * s.regret + reward - expected).max(0.0)
}
fn strategy_accumulation(s: &Self::Step) -> (f32, f32) {
(s.strategy, 1.0)
}
fn regret_weight_step(s: &Self::Step, old_w: f32) -> f32 {
old_w * s.regret + 1.0
}
fn regret_weight_total(_: &Self::Params, _t: usize, accum_w: f32) -> f32 {
accum_w
}
}
pub struct LinearCfr;
pub struct LinearStep {
t: f32,
}
impl UpdateRule for LinearCfr {
type Params = ();
type Step = LinearStep;
const LANES: usize = 2;
fn step(_: &Self::Params, t: usize) -> Self::Step {
LinearStep { t: t as f32 }
}
fn strategy_from_lanes(_: &Self::Params, regret: &[f32], _: &[f32], _: f32, out: &mut [f32]) {
regret_match(regret, out);
}
fn pre_discount(_: &Self::Step) -> f32 {
0.0
}
fn post_discount(_: &Self::Step) -> f32 {
0.0
}
fn accumulate_regret(s: &Self::Step, old: f32, reward: f32, expected: f32) -> f32 {
old + s.t * (reward - expected)
}
fn strategy_accumulation(s: &Self::Step) -> (f32, f32) {
(1.0, s.t)
}
fn regret_weight_step(_: &Self::Step, old_w: f32) -> f32 {
old_w }
fn regret_weight_total(_: &Self::Params, t: usize, _accum_w: f32) -> f32 {
let t = t as f32;
t * (t + 1.0) / 2.0
}
}
pub struct PcfrPlus;
pub struct PcfrPlusStep {
quadratic: f32,
}
impl UpdateRule for PcfrPlus {
type Params = ();
type Step = PcfrPlusStep;
const LANES: usize = 3;
fn step(_: &Self::Params, t: usize) -> Self::Step {
PcfrPlusStep {
quadratic: (t * t) as f32,
}
}
fn strategy_from_lanes(
_: &Self::Params,
regret: &[f32],
last: &[f32],
d: f32,
out: &mut [f32],
) {
predicted_strategy(regret, last, d, out);
}
fn pre_discount(_: &Self::Step) -> f32 {
1.0 }
fn post_discount(_: &Self::Step) -> f32 {
1.0
}
fn accumulate_regret(_: &Self::Step, old: f32, reward: f32, expected: f32) -> f32 {
(old + (reward - expected)).max(0.0)
}
fn strategy_accumulation(s: &Self::Step) -> (f32, f32) {
(1.0, s.quadratic)
}
fn regret_weight_step(_: &Self::Step, old_w: f32) -> f32 {
old_w }
fn regret_weight_total(_: &Self::Params, t: usize, _accum_w: f32) -> f32 {
t as f32
}
}
pub struct PdcfrPlus;
pub struct PdcfrPlusStep {
previous: f32,
current: f32,
strategy: f32,
}
impl PdcfrPlus {
pub const RECOMMENDED: PlusDiscount = PlusDiscount {
alpha: 2.3,
gamma: 5.0,
};
}
impl UpdateRule for PdcfrPlus {
type Params = PlusDiscount;
type Step = PdcfrPlusStep;
const LANES: usize = 3;
fn step(p: &Self::Params, t: usize) -> Self::Step {
PdcfrPlusStep {
previous: if t > 1 {
DiscountParams::discount_factor(t - 1, p.alpha)
} else {
0.0
},
current: DiscountParams::discount_factor(t, p.alpha),
strategy: plus_strategy_discount(t, p.gamma),
}
}
fn strategy_from_lanes(
_: &Self::Params,
regret: &[f32],
last: &[f32],
d: f32,
out: &mut [f32],
) {
predicted_strategy(regret, last, d, out);
}
fn pre_discount(s: &Self::Step) -> f32 {
s.previous
}
fn post_discount(s: &Self::Step) -> f32 {
s.current
}
fn accumulate_regret(s: &Self::Step, old: f32, reward: f32, expected: f32) -> f32 {
(old * s.previous + (reward - expected)).max(0.0)
}
fn strategy_accumulation(s: &Self::Step) -> (f32, f32) {
(s.strategy, 1.0)
}
fn regret_weight_step(s: &Self::Step, old_w: f32) -> f32 {
old_w * s.previous + 1.0
}
fn regret_weight_total(_: &Self::Params, _t: usize, accum_w: f32) -> f32 {
accum_w
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::discount::DiscountParams;
use crate::update_rule::UpdateRule;
#[test]
fn dcfr_mirrors_scalar() {
let p = DiscountParams::RECOMMENDED;
let s = Dcfr::step(&p, 3);
let pos = DiscountParams::discount_factor(3, p.alpha);
let neg = DiscountParams::discount_factor(3, p.beta);
let strat = (3.0f32 / 4.0).powf(p.gamma);
assert_eq!(
Dcfr::accumulate_regret(&s, 2.0, 5.0, 1.0),
2.0 * pos + (5.0 - 1.0)
);
assert_eq!(
Dcfr::accumulate_regret(&s, -2.0, 5.0, 1.0),
-2.0 * neg + (5.0 - 1.0)
);
assert_eq!(Dcfr::strategy_accumulation(&s), (strat, 1.0));
assert_eq!(Dcfr::regret_weight_step(&s, 4.0), 4.0 * pos + 1.0);
assert_eq!(Dcfr::regret_weight_total(&p, 7, 9.5), 9.5);
assert_eq!(Dcfr::pre_discount(&s), 0.0);
assert_eq!(Dcfr::LANES, 2);
}
#[test]
fn dcfr_plus_mirrors_scalar() {
let p = DcfrPlus::RECOMMENDED;
let s = DcfrPlus::step(&p, 3);
let prev = DiscountParams::discount_factor(2, p.alpha);
let strat = (2.0f32 / 3.0).powf(p.gamma);
assert_eq!(
DcfrPlus::accumulate_regret(&s, 2.0, 5.0, 1.0),
(2.0 * prev + 5.0 - 1.0).max(0.0)
);
assert_eq!(DcfrPlus::accumulate_regret(&s, -10.0, 0.0, 1.0), 0.0); assert_eq!(DcfrPlus::strategy_accumulation(&s), (strat, 1.0));
assert_eq!(DcfrPlus::regret_weight_step(&s, 4.0), 4.0 * prev + 1.0);
assert_eq!(DcfrPlus::pre_discount(&s), 0.0); assert_eq!(DcfrPlus::LANES, 2);
}
#[test]
fn dcfr_plus_first_iteration_has_no_history() {
let s = DcfrPlus::step(&DcfrPlus::RECOMMENDED, 1);
assert_eq!(
DcfrPlus::accumulate_regret(&s, 9.0, 2.0, 0.5),
(2.0f32 - 0.5).max(0.0)
);
assert_eq!(DcfrPlus::strategy_accumulation(&s).0, 0.0);
}
#[test]
fn linear_cfr_mirrors_scalar() {
let s = LinearCfr::step(&(), 4);
assert_eq!(
LinearCfr::accumulate_regret(&s, 3.0, 5.0, 1.0),
3.0 + 4.0 * (5.0 - 1.0)
);
assert_eq!(LinearCfr::strategy_accumulation(&s), (1.0, 4.0));
assert_eq!(LinearCfr::regret_weight_total(&(), 4, 0.0), 4.0 * 5.0 / 2.0);
assert_eq!(LinearCfr::pre_discount(&s), 0.0);
assert_eq!(LinearCfr::LANES, 2);
}
#[test]
fn pcfr_plus_mirrors_scalar() {
let s = PcfrPlus::step(&(), 3);
assert_eq!(
PcfrPlus::accumulate_regret(&s, 1.0, 4.0, 1.0),
(1.0f32 + (4.0 - 1.0)).max(0.0)
);
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);
assert_eq!(PcfrPlus::post_discount(&s), 1.0);
assert_eq!(PcfrPlus::LANES, 3);
let mut out = [0.0f32; 2];
PcfrPlus::strategy_from_lanes(&(), &[1.0, 0.0], &[0.0, 1.0], 1.0, &mut out);
assert!((out[0] - 0.5).abs() < 1e-6 && (out[1] - 0.5).abs() < 1e-6);
}
#[test]
fn pdcfr_plus_mirrors_scalar() {
let p = PdcfrPlus::RECOMMENDED; let s = PdcfrPlus::step(&p, 3);
let prev = DiscountParams::discount_factor(2, p.alpha);
let curr = DiscountParams::discount_factor(3, p.alpha);
let strat = (2.0f32 / 3.0).powf(p.gamma);
assert_eq!(
PdcfrPlus::accumulate_regret(&s, 2.0, 5.0, 1.0),
(2.0 * prev + (5.0 - 1.0)).max(0.0)
);
assert_eq!(PdcfrPlus::strategy_accumulation(&s), (strat, 1.0));
assert_eq!(PdcfrPlus::regret_weight_step(&s, 4.0), 4.0 * prev + 1.0);
assert_eq!(PdcfrPlus::pre_discount(&s), prev);
assert_eq!(PdcfrPlus::post_discount(&s), curr);
assert_eq!(PdcfrPlus::LANES, 3);
let mut out = [0.0f32; 2];
PdcfrPlus::strategy_from_lanes(&p, &[1.0, 1.0], &[0.0, 0.0], curr, &mut out);
assert!((out[0] - 0.5).abs() < 1e-6 && (out[1] - 0.5).abs() < 1e-6);
}
}