Skip to content

Commit

Permalink
Initial work on StackVec
Browse files Browse the repository at this point in the history
  • Loading branch information
ekoutanov committed Dec 23, 2023
1 parent 932ef71 commit 845ebc5
Show file tree
Hide file tree
Showing 10 changed files with 455 additions and 70 deletions.
4 changes: 2 additions & 2 deletions brumby-soccer/benches/cri_interval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| run(18, u16::MAX));
});

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

c.bench_function("cri_interval_90_min_1e-6_max_8_goals", |b| {
Expand Down
14 changes: 11 additions & 3 deletions brumby-soccer/src/bin/soc_prices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use tracing::{debug, info};
use brumby::hash_lookup::HashLookup;
use brumby::market::{Market, OverroundMethod, PriceBounds};
use brumby::tables;
use brumby::timed::Timed;
use brumby_soccer::data::{download_by_id, ContestSummary, SoccerFeedId};
use brumby_soccer::domain::{Offer, OfferType, OutcomeType};
use brumby_soccer::fit::{ErrorType, FittingErrors};
Expand All @@ -26,12 +27,10 @@ use brumby_soccer::{fit, model, print};

const OVERROUND_METHOD: OverroundMethod = OverroundMethod::OddsRatio;
const SINGLE_PRICE_BOUNDS: PriceBounds = 1.01..=301.0;
const FIRST_GOALSCORER_BOOKSUM: f64 = 1.5;
const INTERVALS: u8 = 18;
const INCREMENTAL_OVERROUND: f64 = 0.01;
// const MAX_TOTAL_GOALS_HALF: u16 = 4;
const MAX_TOTAL_GOALS: u16 = 8;
const GOALSCORER_MIN_PROB: f64 = 0.0;
// const ERROR_TYPE: ErrorType = ErrorType::SquaredRelative;

#[derive(Debug, clap::Parser, Clone)]
Expand Down Expand Up @@ -142,7 +141,16 @@ async fn main() -> Result<(), Box<dyn Error>> {
overround: offer.market.overround.clone(),
})
.collect::<Vec<_>>();
model.derive(&stubs, &SINGLE_PRICE_BOUNDS)?;

let Timed {
value: cache_stats,
elapsed,
} = model.derive(&stubs, &SINGLE_PRICE_BOUNDS)?;
debug!(
"derivation took {elapsed:?} for {} offers ({} outcomes), {cache_stats:?}",
stubs.len(),
stubs.iter().map(|stub| stub.outcomes.len()).sum::<usize>()
);

{
let table = Table::default().with_rows({
Expand Down
6 changes: 0 additions & 6 deletions brumby-soccer/src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,6 @@ impl OutcomeType {
}
}

// #[derive(Debug, Error)]
// pub enum OfferSubsetError {
// #[error("no outcomes remaining")]
// NoOutcomesRemaining
// }

#[derive(Debug)]
pub struct Offer {
pub offer_type: OfferType,
Expand Down
12 changes: 10 additions & 2 deletions brumby-soccer/src/domain/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl Offer {
&self.market.probs,
&self.offer_type,
)?;
self.offer_type.validate(&self.outcomes)?;
self.offer_type.validate_outcomes(&self.outcomes)?;
match self.offer_type {
OfferType::TotalGoals(_, _) => total_goals::validate_probs(&self.offer_type, &self.market.probs),
OfferType::HeadToHead(_) => head_to_head::validate_probs(&self.offer_type, &self.market.probs),
Expand Down Expand Up @@ -80,13 +80,21 @@ pub enum InvalidOutcome {
}

impl OfferType {
pub fn validate(&self, outcomes: &HashLookup<OutcomeType>) -> Result<(), InvalidOutcome> {
pub fn validate_outcomes(&self, outcomes: &HashLookup<OutcomeType>) -> Result<(), InvalidOutcome> {
match self {
OfferType::TotalGoals(_, _) => total_goals::validate_outcomes(self, outcomes),
OfferType::HeadToHead(_) => head_to_head::validate_outcomes(self, outcomes),
_ => Ok(()),
}
}

pub fn validate_outcome(&self, outcome: &OutcomeType) -> Result<(), InvalidOutcome> {
match self {
OfferType::TotalGoals(_, _) => total_goals::validate_outcome(self, outcome),
OfferType::HeadToHead(_) => head_to_head::validate_outcome(self, outcome),
_ => Ok(()),
}
}
}

#[derive(Debug, Error)]
Expand Down
41 changes: 23 additions & 18 deletions brumby-soccer/src/domain/error/head_to_head.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
use brumby::hash_lookup::HashLookup;

use crate::domain::{error, OfferType, OutcomeType, Side};
use crate::domain::error::{InvalidOffer, InvalidOutcome};
use crate::domain::error::{ExtraneousOutcome, InvalidOffer, InvalidOutcome};

pub fn validate_outcomes(
pub(crate) fn validate_outcomes(
offer_type: &OfferType,
outcomes: &HashLookup<OutcomeType>,
) -> Result<(), InvalidOutcome> {
match offer_type {
OfferType::HeadToHead(_) => {
error::OutcomesCompleteAssertion {
outcomes: &valid_outcomes(),
}
.check(outcomes, offer_type)?;
Ok(())
}
_ => unreachable!(),
error::OutcomesCompleteAssertion {
outcomes: &valid_outcomes(),
}
.check(outcomes, offer_type)?;
Ok(())
}

pub fn validate_probs(offer_type: &OfferType, probs: &[f64]) -> Result<(), InvalidOffer> {
match offer_type {
OfferType::HeadToHead(_) => {
error::BooksumAssertion::with_default_tolerance(1.0..=1.0).check(probs, offer_type)?;
Ok(())
}
_ => unreachable!(),
pub(crate) fn validate_outcome(
offer_type: &OfferType,
outcome: &OutcomeType,
) -> Result<(), InvalidOutcome> {
let valid_outcomes = valid_outcomes();
if valid_outcomes.contains(outcome) {
Ok(())
} else {
Err(InvalidOutcome::ExtraneousOutcome(ExtraneousOutcome {
outcome_type: outcome.clone(),
offer_type: offer_type.clone(),
}))
}
}

pub(crate) fn validate_probs(offer_type: &OfferType, probs: &[f64]) -> Result<(), InvalidOffer> {
error::BooksumAssertion::with_default_tolerance(1.0..=1.0).check(probs, offer_type)?;
Ok(())
}

fn valid_outcomes() -> [OutcomeType; 3] {
[
OutcomeType::Win(Side::Home),
Expand Down
33 changes: 23 additions & 10 deletions brumby-soccer/src/domain/error/total_goals.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use brumby::hash_lookup::HashLookup;

use crate::domain::error::{InvalidOffer, InvalidOutcome};
use crate::domain::{error, OfferType, OutcomeType, Over};
use crate::domain::error::{ExtraneousOutcome, InvalidOffer, InvalidOutcome};

pub fn validate_outcomes(
pub(crate) fn validate_outcomes(
offer_type: &OfferType,
outcomes: &HashLookup<OutcomeType>,
) -> Result<(), InvalidOutcome> {
Expand All @@ -19,7 +19,27 @@ pub fn validate_outcomes(
}
}

pub fn validate_probs(offer_type: &OfferType, probs: &[f64]) -> Result<(), InvalidOffer> {
pub(crate) fn validate_outcome(
offer_type: &OfferType,
outcome: &OutcomeType,
) -> Result<(), InvalidOutcome> {
match offer_type {
OfferType::TotalGoals(_, over) => {
let valid_outcomes = valid_outcomes(over);
if valid_outcomes.contains(outcome) {
Ok(())
} else {
Err(InvalidOutcome::ExtraneousOutcome(ExtraneousOutcome {
outcome_type: outcome.clone(),
offer_type: offer_type.clone(),
}))
}
}
_ => unreachable!(),
}
}

pub(crate) fn validate_probs(offer_type: &OfferType, probs: &[f64]) -> Result<(), InvalidOffer> {
match offer_type {
OfferType::TotalGoals(_, _) => {
error::BooksumAssertion::with_default_tolerance(1.0..=1.0).check(probs, offer_type)?;
Expand All @@ -29,13 +49,6 @@ pub fn validate_probs(offer_type: &OfferType, probs: &[f64]) -> Result<(), Inval
}
}

// pub fn create_outcomes(offer_type: &OfferType) -> [OutcomeType; 2] {
// match offer_type {
// OfferType::TotalGoals(_, over) => _create_outcomes(over),
// _ => unreachable!(),
// }
// }

fn valid_outcomes(over: &Over) -> [OutcomeType; 2] {
[
OutcomeType::Over(over.0), OutcomeType::Under(over.0 + 1),
Expand Down
28 changes: 27 additions & 1 deletion brumby-soccer/src/interval.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::ops::Range;
use std::ops::{Add, AddAssign, Range};

use bincode::Encode;
use rustc_hash::FxHashMap;
Expand Down Expand Up @@ -133,6 +133,32 @@ impl Default for Expansions {
}
}

// impl Add for Expansions {
// type Output = Expansions;
//
// fn add(self, rhs: Self) -> Self::Output {
// Self {
// ht_score: self.ht_score || rhs.ht_score,
// ft_score: self.ft_score || rhs.ft_score,
// max_player_goals: u8::max(self.max_player_goals, rhs.max_player_goals),
// player_split_goal_stats: self.player_split_goal_stats || rhs.player_split_goal_stats,
// max_player_assists: u8::max(self.max_player_assists, rhs.max_player_assists),
// first_goalscorer: self.first_goalscorer || rhs.first_goalscorer,
// }
// }
// }

impl AddAssign for Expansions {
fn add_assign(&mut self, rhs: Self) {
self.ht_score |= rhs.ht_score;
self.ft_score |= rhs.ft_score;
self.max_player_goals = u8::max(self.max_player_goals, rhs.max_player_goals);
self.player_split_goal_stats |= rhs.player_split_goal_stats;
self.max_player_assists = u8::max(self.max_player_assists, rhs.max_player_assists);
self.first_goalscorer |= rhs.first_goalscorer;
}
}

#[derive(Debug, Clone, Encode)]
pub struct PruneThresholds {
pub max_total_goals: u16,
Expand Down
65 changes: 38 additions & 27 deletions brumby-soccer/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use brumby::capture::Capture;
use brumby::hash_lookup::HashLookup;
use brumby::market::{Market, Overround, PriceBounds};
use brumby::probs::SliceExt;
use brumby::timed::Timed;

use crate::domain::error::{InvalidOffer, InvalidOutcome, MissingOutcome, UnvalidatedOffer};
use crate::domain::{Offer, OfferCategory, OfferType, OutcomeType, Over, Period, Player};
Expand Down Expand Up @@ -178,33 +179,26 @@ impl Model {
&mut self,
stubs: &[Stub],
price_bounds: &PriceBounds,
) -> Result<(), DerivationError> {
let start = Instant::now();
let mut cache = FxHashMap::default();
let mut cache_stats = CacheStats::default();
for stub in stubs {
debug!(
"deriving {:?} ({} outcomes)",
stub.offer_type,
stub.outcomes.len()
);
stub.offer_type.validate(&stub.outcomes)?;
let start = Instant::now();
let (offer, derive_cache_stats) = self.derive_offer(stub, price_bounds, &mut cache)?;
debug!("... took {:?}, {derive_cache_stats:?}", start.elapsed());
cache_stats += derive_cache_stats;
self.offers.insert(offer.offer_type.clone(), offer);
}
let elapsed = start.elapsed();
debug!(
"derivation took {elapsed:?} for {} offers ({} outcomes), {cache_stats:?}",
self.offers.len(),
self.offers
.values()
.map(|offer| offer.outcomes.len())
.sum::<usize>()
);
Ok(())
) -> Result<Timed<CacheStats>, DerivationError> {
Timed::result(|| {
let mut cache = FxHashMap::default();
let mut cache_stats = CacheStats::default();
for stub in stubs {
debug!(
"deriving {:?} ({} outcomes)",
stub.offer_type,
stub.outcomes.len()
);
stub.offer_type.validate_outcomes(&stub.outcomes)?;
let start = Instant::now();
let (offer, derive_cache_stats) =
self.derive_offer(stub, price_bounds, &mut cache)?;
debug!("... took {:?}, {derive_cache_stats:?}", start.elapsed());
cache_stats += derive_cache_stats;
self.offers.insert(offer.offer_type.clone(), offer);
}
Ok(cache_stats)
})
}

pub fn offers(&self) -> &FxHashMap<OfferType, Offer> {
Expand Down Expand Up @@ -313,6 +307,23 @@ impl Model {
Ok((offer, cache_stats))
}

pub fn derive_price(selections: &[(OfferType, OutcomeType)]) -> Result<(), ()> {
todo!()
}

fn collect_requirements(
offer_type: &OfferType,
outcome: &OutcomeType,
agg_reqs: &mut Expansions,
agg_player_probs: &mut FxHashMap<Player, PlayerProbs>,
) -> Result<(), DerivationError> {
offer_type.validate_outcome(outcome)?;
let reqs = requirements(offer_type);

// *agg_reqs = *agg_reqs + reqs;
todo!()
}

fn ensure_team_requirements(&self, reqs: &Expansions) -> Result<(), UnmetRequirement> {
if reqs.requires_team_goal_probs() {
self.require_team_goal_probs()?;
Expand Down
3 changes: 2 additions & 1 deletion brumby/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ pub mod opt;
pub mod poisson;
pub mod probs;
pub mod tables;
pub mod selection;
pub mod timed;
pub mod selection;
pub mod stack_vec;

#[cfg(test)]
pub(crate) mod testing;
Expand Down
Loading

0 comments on commit 845ebc5

Please sign in to comment.