pyoe2_craftpath/calc/statistics/analyzers/
common_analyzer_utils.rs

1use anyhow::Result;
2use rayon::{
3    iter::{IntoParallelIterator, ParallelIterator},
4    slice::ParallelSliceMut,
5};
6use tracing::instrument;
7
8use crate::{
9    api::{
10        calculator::{Calculator, GroupRoute},
11        currency::CraftCurrencyList,
12        provider::{item_info::ItemInfoProvider, market_prices::MarketPriceProvider},
13        types::THashMap,
14    },
15    calc::statistics::{
16        analyzers::collectors::{
17            currency_groups::group_chance_memory_efficient_collector::CurrencyGroupChanceMemoryEfficientCollector,
18            utils::{
19                statistic_analyzer_currency_grouped_collector::calculate_currency_groups,
20                statistic_analyzer_currency_grouped_memory_efficient_collector::calculate_currency_groups_memory_efficient,
21            },
22        },
23        helpers::{
24            RouteChance, RouteCustomWeight, StatisticAnalyzerCurrencyGroupCollectorTrait,
25            SubpathAmount,
26        },
27    },
28    utils::float_compare,
29};
30
31#[instrument(skip_all)]
32pub fn get_grouped_statistic_memory_efficient<T: StatisticAnalyzerCurrencyGroupCollectorTrait>(
33    lower_is_better: bool,
34    calculator: &Calculator,
35    item_provider: &ItemInfoProvider,
36    market_provider: &MarketPriceProvider,
37    max_ram_in_bytes: u64,
38) -> Result<Vec<GroupRoute>> {
39    let res: THashMap<
40        Vec<&CraftCurrencyList>,
41        (
42            RouteChance,
43            RouteCustomWeight,
44            SubpathAmount,
45            Vec<RouteChance>,
46        ),
47    > = calculate_currency_groups_memory_efficient::<CurrencyGroupChanceMemoryEfficientCollector>(
48        calculator,
49        item_provider,
50        market_provider,
51        max_ram_in_bytes,
52    )?;
53
54    let mut data: Vec<GroupRoute> = res
55        .into_par_iter()
56        .map(|(k, v)| {
57            let key_owned: Vec<CraftCurrencyList> = k.into_iter().cloned().collect();
58            let chance: RouteChance = v.0;
59            let weight: RouteCustomWeight = v.1;
60            let subpaths_amount: SubpathAmount = v.2;
61            let subpaths: Vec<RouteChance> = v.3;
62
63            GroupRoute {
64                group: key_owned,
65                weight: weight,
66                amount_subpaths: subpaths_amount,
67                unique_route_weights: vec![subpaths],
68                chance,
69            }
70        })
71        .collect();
72
73    if lower_is_better {
74        data.par_sort_unstable_by(|a, b| {
75            float_compare::cmp_f64(*a.weight.get_raw_value(), *b.weight.get_raw_value()).then(
76                float_compare::cmp_f64(*a.chance.get_raw_value(), *b.chance.get_raw_value()),
77            )
78        });
79    } else {
80        data.par_sort_unstable_by(|a, b| {
81            float_compare::cmp_f64(*b.weight.get_raw_value(), *a.weight.get_raw_value()).then(
82                float_compare::cmp_f64(*a.chance.get_raw_value(), *b.chance.get_raw_value()),
83            )
84        });
85    }
86
87    Ok(data)
88}
89
90#[instrument(skip_all)]
91pub fn get_grouped_statistic<T: StatisticAnalyzerCurrencyGroupCollectorTrait>(
92    lower_is_better: bool,
93    calculator: &Calculator,
94    item_provider: &ItemInfoProvider,
95    market_provider: &MarketPriceProvider,
96    max_ram_in_bytes: u64,
97) -> Result<Vec<GroupRoute>> {
98    let res: THashMap<Vec<&CraftCurrencyList>, Vec<Vec<RouteChance>>> =
99        calculate_currency_groups::<T>(
100            calculator,
101            item_provider,
102            market_provider,
103            max_ram_in_bytes,
104        )?;
105
106    let mut data: Vec<GroupRoute> = res
107        .into_par_iter()
108        .map(|(k, v)| {
109            let chance = T::calculate_group_chance(&v);
110            let weight = T::calculate_group_weight(&k, &v);
111
112            GroupRoute {
113                group: k.into_iter().cloned().collect(),
114                weight,
115                amount_subpaths: SubpathAmount::from(v.len() as u32),
116                unique_route_weights: v,
117                chance,
118            }
119        })
120        .collect();
121
122    if lower_is_better {
123        data.par_sort_unstable_by(|a, b| {
124            float_compare::cmp_f64(*a.weight.get_raw_value(), *b.weight.get_raw_value()).then(
125                float_compare::cmp_f64(*a.chance.get_raw_value(), *b.chance.get_raw_value()),
126            )
127        });
128    } else {
129        data.par_sort_unstable_by(|a, b| {
130            float_compare::cmp_f64(*b.weight.get_raw_value(), *a.weight.get_raw_value()).then(
131                float_compare::cmp_f64(*a.chance.get_raw_value(), *b.chance.get_raw_value()),
132            )
133        });
134    }
135
136    Ok(data)
137}
138
139#[macro_export]
140macro_rules! impl_common_group_analyzer_methods {
141    () => {
142        fn calculate_chance_for_group_step_index(
143            &self,
144            group_routes: &Vec<Vec<crate::calc::statistics::helpers::RouteChance>>,
145            subpath_amount: crate::calc::statistics::helpers::SubpathAmount,
146            index: usize,
147        ) -> crate::calc::statistics::helpers::RouteChance {
148            use crate::calc::statistics::helpers::RouteChance;
149
150            // Sum all values found at this index across group routes
151            let total: f64 = group_routes
152                .iter()
153                .filter_map(|gr| gr.get(index))
154                .map(|rc| rc.get_raw_value())
155                .sum();
156
157            // Convert subpath_amount to f64
158            let denom = (*subpath_amount.get_raw_value()) as f64;
159
160            let value = if denom > 0.0 { total / denom } else { 0.0 };
161
162            RouteChance::new(value.clamp(0.0, 1.0))
163        }
164
165        fn calculate_cost_per_craft(
166            &self,
167            currency: &Vec<crate::api::currency::CraftCurrencyList>,
168            item_info: &crate::api::provider::item_info::ItemInfoProvider,
169            market_provider: &crate::api::provider::market_prices::MarketPriceProvider,
170        ) -> crate::api::provider::market_prices::PriceInDivines {
171            let pc = crate::api::provider::market_prices::PriceInDivines::new(
172                currency.iter().fold(0.0_f64, |a, b| {
173                    a + b.list.iter().fold(0.0_f64, |a, b| {
174                        a + market_provider
175                            .try_lookup_currency_in_divines_default_if_fail(b, item_info)
176                            .get_divine_value()
177                    })
178                }),
179            );
180
181            pc
182        }
183
184        fn calculate_tries_needed_for_60_percent(
185            &self,
186            group_route: &crate::api::calculator::GroupRoute,
187        ) -> u64 {
188            let tries_for_60 = ((((1.0_f64 - 0.6_f64).ln()
189                / (1.0_f64 - group_route.chance.get_raw_value()).ln())
190            .ceil()) as u64)
191                .max(1);
192
193            tries_for_60
194        }
195    };
196}
197
198#[macro_export]
199macro_rules! impl_common_unique_path_analyzer_methods {
200    () => {
201        fn calculate_tries_needed_for_60_percent(
202            &self,
203            route: &crate::api::calculator::ItemRoute,
204        ) -> u64 {
205            let tries_for_60_percent = ((((1.0_f64 - 0.6_f64).ln()
206                / (1.0_f64 - route.chance.get_raw_value()).ln())
207            .ceil()) as u64)
208                .max(1);
209
210            tries_for_60_percent
211        }
212
213        fn calculate_cost_per_craft(
214            &self,
215            currency: &Vec<crate::api::currency::CraftCurrencyList>,
216            item_info: &crate::api::provider::item_info::ItemInfoProvider,
217            market_provider: &crate::api::provider::market_prices::MarketPriceProvider,
218        ) -> crate::api::provider::market_prices::PriceInDivines {
219            crate::api::provider::market_prices::PriceInDivines::new(currency.iter().fold(
220                0.0_f64,
221                |a, b| {
222                    a + b.list.iter().fold(0.0_f64, |a, b| {
223                        a + market_provider
224                            .try_lookup_currency_in_divines_default_if_fail(b, item_info)
225                            .get_divine_value()
226                    })
227                },
228            ))
229        }
230    };
231}