1use std::f32::consts::LN_2;
2
3#[derive(Debug, Clone)]
4pub struct RaceResult<D = ()> {
5 pub driver: D,
6 pub finish_rank: u32,
7 pub start_irating: u32,
8 pub started: bool,
9}
10
11impl<D> From<(D, u32, u32, bool)> for RaceResult<D> {
12 fn from(value: (D, u32, u32, bool)) -> Self {
13 RaceResult {
14 driver: value.0,
15 finish_rank: value.1,
16 start_irating: value.2,
17 started: value.3,
18 }
19 }
20}
21
22#[derive(Debug, Clone)]
23pub struct CalculationResult<D = ()> {
24 pub race_result: RaceResult<D>,
25 pub irating_change: f32,
26 pub new_irating: u32,
27}
28
29pub fn calculate<D>(race_results: Vec<RaceResult<D>>) -> Vec<CalculationResult<D>> {
30 let br1 = 1600. / LN_2;
31
32 let num_registrations = race_results.len();
33 let num_starters = race_results.iter().filter(|result| result.started).count();
34 let num_non_starters = num_registrations - num_starters;
35
36 let chances: Vec<Vec<f32>> = race_results
37 .iter()
38 .map(|result| result.start_irating as f32)
39 .map(|a| {
40 race_results
41 .iter()
42 .map(|result| result.start_irating as f32)
43 .map(|b| chance(a, b, br1))
44 .collect()
45 })
46 .collect();
47
48 let expected_scores: Vec<f32> = chances
57 .iter()
58 .map(|chances| chances.iter().sum::<f32>() - 0.5)
59 .collect();
60
61 let fudge_factors: Vec<f32> = race_results
62 .iter()
63 .map(|result| match result.started {
64 false => 0.,
65 true => {
66 let x = num_registrations as f32 - num_non_starters as f32 / 2.;
67 (x / 2. - result.finish_rank as f32) / 100.
68 }
69 })
70 .collect();
71
72 let changes_starters: Vec<Option<f32>> = race_results
73 .iter()
74 .zip(expected_scores.iter())
75 .zip(fudge_factors.iter())
76 .map(
77 |((result, expected_score), fudge_factor)| match result.started {
78 false => None,
79 true => Some(
80 (num_registrations as f32
81 - result.finish_rank as f32
82 - expected_score
83 - fudge_factor)
84 * 200.
85 / num_starters as f32,
86 ),
87 },
88 )
89 .collect();
90
91 let sum_changes_starters: f32 = changes_starters.iter().filter_map(Option::as_ref).sum();
92
93 let expected_score_non_starters: Vec<Option<f32>> = race_results
94 .iter()
95 .zip(expected_scores.iter())
96 .map(|(result, expected_score)| (!result.started).then_some(*expected_score))
97 .collect();
98
99 let sum_expected_score_non_starters: f32 = expected_score_non_starters
100 .iter()
101 .filter_map(Option::as_ref)
102 .sum();
103
104 let changes_non_starters: Vec<Option<f32>> = expected_score_non_starters
105 .iter()
106 .map(|expected_score| {
107 expected_score.map(|expected_score| {
108 -sum_changes_starters / num_non_starters as f32 * expected_score
109 / (sum_expected_score_non_starters / num_non_starters as f32)
110 })
111 })
113 .collect();
114
115 let changes: Vec<f32> = changes_starters
116 .iter()
117 .zip(changes_non_starters.iter())
118 .map(|change| match change {
119 (Some(change), None) => *change,
120 (None, Some(change)) => *change,
121 (_, _) => panic!(),
122 })
123 .collect();
124
125 race_results
126 .into_iter()
127 .zip(changes.iter())
128 .map(|(result, change)| {
129 let new_irating = (result.start_irating as f32 + change).round() as u32;
130 CalculationResult {
131 race_result: result,
132 irating_change: *change,
133 new_irating,
134 }
135 })
136 .collect()
137}
138
139fn chance(a: f32, b: f32, factor: f32) -> f32 {
140 (1. - (-a / factor).exp()) * (-b / factor).exp()
141 / ((1. - (-b / factor).exp()) * (-a / factor).exp()
142 + (1. - (-a / factor).exp()) * (-b / factor).exp())
143}
144
145#[cfg(test)]
146mod tests {
147 #[test]
148 fn it_works() {
149 let race_results = vec![
150 ("Driver 1", 1, 7526, true).into(),
151 ("Driver 2", 2, 5982, true).into(),
152 ("Driver 3", 3, 5463, true).into(),
153 ("Driver 4", 4, 4279, true).into(),
154 ("Driver 5", 5, 4137, true).into(),
155 ("Driver 6", 6, 4044, true).into(),
156 ("Driver 7", 7, 3891, true).into(),
157 ("Driver 8", 8, 3612, true).into(),
158 ("Driver 9", 9, 3147, true).into(),
159 ("Driver 10", 10, 2823, true).into(),
160 ("Driver 11", 11, 2715, true).into(),
161 ("Driver 12", 12, 2603, true).into(),
162 ("Driver 13", 13, 2512, true).into(),
163 ("Driver 14", 14, 2352, false).into(),
164 ("Driver 15", 15, 2227, true).into(),
165 ("Driver 16", 16, 2195, true).into(),
166 ("Driver 17", 17, 2166, true).into(),
167 ("Driver 18", 18, 2089, true).into(),
168 ("Driver 19", 19, 1773, true).into(),
169 ("Driver 20", 20, 1772, true).into(),
170 ("Driver 21", 21, 1752, true).into(),
171 ("Driver 22", 22, 1748, true).into(),
172 ("Driver 23", 23, 1705, true).into(),
173 ("Driver 24", 24, 1662, true).into(),
174 ("Driver 25", 25, 1622, true).into(),
175 ("Driver 26", 26, 1537, true).into(),
176 ("Driver 27", 27, 1464, true).into(),
177 ("Driver 28", 28, 1203, true).into(),
178 ];
179
180 let result = super::calculate(race_results);
181 insta::assert_debug_snapshot!(result);
182 }
183}