Skip to content

Commit

Permalink
Configurable expansions
Browse files Browse the repository at this point in the history
  • Loading branch information
ekoutanov committed Dec 8, 2023
1 parent cc08062 commit e07e69f
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 28 deletions.
7 changes: 4 additions & 3 deletions brumby-soccer/benches/cri_interval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ fn criterion_benchmark(c: &mut Criterion) {
max_total_goals,
min_prob: 1e-6,
},
expansions: Default::default(),
},
0..intervals,
)
Expand All @@ -25,15 +26,15 @@ fn criterion_benchmark(c: &mut Criterion) {
// sanity check
assert_eq!(65, run(4, u16::MAX));

c.bench_function("cri_interval_18_unbounded", |b| {
c.bench_function("cri_interval_18_min_1e-6", |b| {
b.iter(|| run(18, u16::MAX));
});

c.bench_function("cri_interval_90_unbounded", |b| {
c.bench_function("cri_interval_90_min_1e-6", |b| {
b.iter(|| run(90, u16::MAX));
});

c.bench_function("cri_interval_90_max_8", |b| {
c.bench_function("cri_interval_90_min_1e-6_max_8_goals", |b| {
b.iter(|| run(90, 8));
});
}
Expand Down
1 change: 1 addition & 0 deletions brumby-soccer/benches/cri_isolate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fn criterion_benchmark(c: &mut Criterion) {
max_total_goals,
min_prob: 1e-6,
},
expansions: Default::default(),
},
0..intervals,
)
Expand Down
34 changes: 26 additions & 8 deletions brumby-soccer/src/bin/soc_prices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use brumby::{factorial, poisson};
use brumby_soccer::{scoregrid};
use brumby_soccer::domain::{OfferType, OutcomeType, Over, Period, Player, Side};
use brumby_soccer::domain::Player::Named;
use brumby_soccer::interval::{explore, IntervalConfig, isolate, PruneThresholds, ScoringProbs};
use brumby_soccer::interval::{Expansions, explore, IntervalConfig, isolate, PruneThresholds, ScoringProbs};
use brumby::linear::matrix::Matrix;
use brumby::market::{Market, Overround, OverroundMethod, PriceBounds};
use brumby::opt::{
Expand All @@ -35,7 +35,7 @@ const FIRST_GOALSCORER_BOOKSUM: f64 = 1.0;
const INTERVALS: usize = 18;
const MAX_TOTAL_GOALS_HALF: u16 = 4;
const MAX_TOTAL_GOALS_FULL: u16 = 8;
const GOALSCORER_MIN_PROB: f64 = 1e-4;
const GOALSCORER_MIN_PROB: f64 = 0.0;
const ERROR_TYPE: ErrorType = ErrorType::SquaredRelative;

#[derive(Debug, clap::Parser, Clone)]
Expand All @@ -49,8 +49,8 @@ struct Args {
download: Option<String>,

/// print player markets
#[clap(short = 'p', long = "players")]
print_players: bool,
#[clap(long = "player-goals")]
player_goals: bool,
}
impl Args {
fn validate(&self) -> anyhow::Result<()> {
Expand Down Expand Up @@ -178,8 +178,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
adj_optimal_h2[i] = orig_h2 / (avg_h1_h2 / ft);
}
println!("adjusted optimal_h1={adj_optimal_h1:?}, optimal_h2={adj_optimal_h2:?}");
// let optimal_h1 = h1_search_outcome.optimal_values;
// let optimal_h2 = h2_search_outcome.optimal_values;
// let adj_optimal_h1 = h1_search_outcome.optimal_values;
// let adj_optimal_h2 = h2_search_outcome.optimal_values;

// let ft_gamma_sum = ft_search_outcome.optimal_values.sum();
// h1_search_outcome.optimal_values.normalise(ft_gamma_sum * 1.0);
Expand Down Expand Up @@ -418,6 +418,12 @@ async fn main() -> Result<(), Box<dyn Error>> {
max_total_goals: MAX_TOTAL_GOALS_FULL,
min_prob: GOALSCORER_MIN_PROB,
},
expansions: Expansions {
ft_score: false,
player_stats: false,
player_split_stats: false,
first_goalscorer: true,
}
},
0..INTERVALS as u8,
);
Expand All @@ -444,7 +450,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
),
};

if args.print_players {
if args.player_goals {
println!(
"sample first goalscorer σ={:.3}",
implied_booksum(&first_gs.market.prices)
Expand Down Expand Up @@ -476,6 +482,12 @@ async fn main() -> Result<(), Box<dyn Error>> {
max_total_goals: MAX_TOTAL_GOALS_FULL,
min_prob: GOALSCORER_MIN_PROB,
},
expansions: Expansions {
ft_score: false,
player_stats: true,
player_split_stats: false,
first_goalscorer: false,
}
},
0..INTERVALS as u8,
);
Expand Down Expand Up @@ -508,7 +520,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
),
};

if args.print_players {
if args.player_goals {
println!(
"sample anytime goalscorer σ={:.3}",
implied_booksum(&anytime_gs.market.prices)
Expand Down Expand Up @@ -725,6 +737,12 @@ fn fit_first_goalscorer(
max_total_goals: MAX_TOTAL_GOALS_FULL,
min_prob: GOALSCORER_MIN_PROB,
},
expansions: Expansions {
ft_score: false,
player_stats: false,
player_split_stats: false,
first_goalscorer: true,
}
},
0..INTERVALS as u8,
);
Expand Down
80 changes: 65 additions & 15 deletions brumby-soccer/src/interval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,32 @@ impl<'a> From<&'a [f64]> for ScoringProbs {
}
}

#[derive(Debug)]
pub struct Expansions {
pub ft_score: bool,
pub player_stats: bool,
pub player_split_stats: bool,
pub first_goalscorer: bool,
}
impl Expansions {
fn validate(&self) {
if self.player_split_stats {
assert!(self.player_stats, "cannot expand player split stats without player stats");
}
assert!(self.ft_score || self.player_stats || self.first_goalscorer, "at least one expansion must be enabled")
}
}

impl Default for Expansions {
fn default() -> Self {
Self {
ft_score: true,
player_stats: true,
player_split_stats: true,
first_goalscorer: true,
}
}
}

#[derive(Debug)]
pub struct PruneThresholds {
Expand All @@ -56,8 +82,9 @@ pub struct IntervalConfig {
pub intervals: u8,
pub h1_probs: ScoringProbs,
pub h2_probs: ScoringProbs,
pub prune_thresholds: PruneThresholds,
pub players: Vec<(Player, f64)>,
pub prune_thresholds: PruneThresholds,
pub expansions: Expansions,
}

#[derive(Debug)]
Expand Down Expand Up @@ -92,6 +119,8 @@ enum Half {
}

pub fn explore(config: &IntervalConfig, explore_intervals: Range<u8>) -> Exploration {
config.expansions.validate();

let mut player_lookup = Vec::with_capacity(config.players.len() + 1);
let mut home_scorers = Vec::with_capacity(config.players.len() + 1);
let mut away_scorers = Vec::with_capacity(config.players.len() + 1);
Expand Down Expand Up @@ -150,6 +179,7 @@ pub fn explore(config: &IntervalConfig, explore_intervals: Range<u8>) -> Explora
prob: neither_prob,
};
merge(
&config.expansions,
&half,
&current_prospect,
current_prob,
Expand All @@ -168,6 +198,7 @@ pub fn explore(config: &IntervalConfig, explore_intervals: Range<u8>) -> Explora
prob: params.home_prob * player_prob,
};
merge(
&config.expansions,
&half,
&current_prospect,
current_prob,
Expand All @@ -185,6 +216,7 @@ pub fn explore(config: &IntervalConfig, explore_intervals: Range<u8>) -> Explora
prob: params.away_prob * player_prob,
};
merge(
&config.expansions,
&half,
&current_prospect,
current_prob,
Expand Down Expand Up @@ -212,6 +244,7 @@ pub fn explore(config: &IntervalConfig, explore_intervals: Range<u8>) -> Explora
* 0.5,
};
merge(
&config.expansions,
&half,
&current_prospect,
current_prob,
Expand All @@ -238,6 +271,7 @@ pub fn explore(config: &IntervalConfig, explore_intervals: Range<u8>) -> Explora

#[inline]
fn merge(
expansions: &Expansions,
half: &Half,
current_prospect: &Prospect,
current_prob: f64,
Expand All @@ -247,24 +281,40 @@ fn merge(
let merged_prob = current_prob * partial.prob;
let mut merged = current_prospect.clone();
if let Some(scorer) = partial.home_scorer {
let half_stats = match half {
Half::First => &mut merged.stats[scorer].h1,
Half::Second => &mut merged.stats[scorer].h2,
};
half_stats.goals += 1;
merged.score.home += 1;
if merged.first_scorer.is_none() && partial.first_scoring_side.unwrap() == &Side::Home {
if expansions.player_split_stats {
let split_stats = match half {
Half::First => &mut merged.stats[scorer].h1,
Half::Second => &mut merged.stats[scorer].h2,
};
split_stats.goals += 1;
} else if expansions.player_stats {
merged.stats[scorer].h2.goals += 1;
}

if expansions.ft_score {
merged.score.home += 1;
}

if expansions.first_goalscorer && merged.first_scorer.is_none() && partial.first_scoring_side.unwrap() == &Side::Home {
merged.first_scorer = Some(scorer);
}
}
if let Some(scorer) = partial.away_scorer {
let half_stats = match half {
Half::First => &mut merged.stats[scorer].h1,
Half::Second => &mut merged.stats[scorer].h2,
};
half_stats.goals += 1;
merged.score.away += 1;
if merged.first_scorer.is_none() && partial.first_scoring_side.unwrap() == &Side::Away {
if expansions.player_split_stats {
let split_stats = match half {
Half::First => &mut merged.stats[scorer].h1,
Half::Second => &mut merged.stats[scorer].h2,
};
split_stats.goals += 1;
} else if expansions.player_stats {
merged.stats[scorer].h2.goals += 1;
}

if expansions.ft_score {
merged.score.away += 1;
}

if expansions.first_goalscorer && merged.first_scorer.is_none() && partial.first_scoring_side.unwrap() == &Side::Away {
merged.first_scorer = Some(scorer);
}
}
Expand Down
9 changes: 8 additions & 1 deletion brumby-soccer/src/interval/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fn explore_2x2() {
max_total_goals: u16::MAX,
min_prob: 0.0,
},
expansions: Default::default(),
},
0..2,
);
Expand Down Expand Up @@ -203,11 +204,12 @@ fn explore_2x2_pruned_2_goals() {
intervals: 2,
h1_probs: ScoringProbs { home_prob: 0.25, away_prob: 0.25, common_prob: 0.25 },
h2_probs: ScoringProbs { home_prob: 0.25, away_prob: 0.25, common_prob: 0.25 },
players: vec![],
prune_thresholds: PruneThresholds {
max_total_goals: 2,
min_prob: 0.0,
},
players: vec![],
expansions: Default::default(),
},
0..2,
);
Expand Down Expand Up @@ -327,6 +329,7 @@ fn explore_3x3() {
max_total_goals: u16::MAX,
min_prob: 0.0,
},
expansions: Default::default(),
},
0..3,
);
Expand All @@ -348,6 +351,7 @@ fn explore_4x4() {
max_total_goals: u16::MAX,
min_prob: 0.0,
},
expansions: Default::default(),
},
0..4,
);
Expand All @@ -370,6 +374,7 @@ fn explore_1x1_player() {
max_total_goals: u16::MAX,
min_prob: 0.0,
},
expansions: Default::default(),
},
0..1,
);
Expand Down Expand Up @@ -519,6 +524,7 @@ fn explore_2x2_player() {
max_total_goals: u16::MAX,
min_prob: 0.0,
},
expansions: Default::default(),
},
0..2,
);
Expand Down Expand Up @@ -564,6 +570,7 @@ fn explore_2x2_player_asymmetric() {
max_total_goals: u16::MAX,
min_prob: 0.0,
},
expansions: Default::default(),
},
0..2,
);
Expand Down
8 changes: 7 additions & 1 deletion brumby-soccer/src/scoregrid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use brumby::multinomial::binomial;

use brumby::comb::{count_permutations, pick};
use crate::domain::{OutcomeType, Score, Side};
use crate::interval::{explore, IntervalConfig, PruneThresholds, ScoringProbs};
use crate::interval::{Expansions, explore, IntervalConfig, PruneThresholds, ScoringProbs};
use brumby::linear::matrix::Matrix;
use brumby::multinomial::bivariate_binomial;
use brumby::probs::SliceExt;
Expand Down Expand Up @@ -185,6 +185,12 @@ pub fn from_interval(
max_total_goals,
min_prob: 0.0,
},
expansions: Expansions {
ft_score: true,
player_stats: false,
player_split_stats: false,
first_goalscorer: false,
}
},
explore_intervals,
);
Expand Down

0 comments on commit e07e69f

Please sign in to comment.