Skip to main content

bicmath_statistics/
lib.rs

1//! Statistics module: descriptive statistics, probability distributions,
2//! statistical inference, and stratified experiment analysis.
3
4pub mod bayesian;
5pub mod causal;
6pub mod common;
7pub mod continuous_distributions;
8pub mod descriptive;
9pub mod discrete_distributions;
10pub mod distributions;
11pub mod inference;
12pub mod mathfn;
13pub mod regression;
14pub mod sequential;
15pub mod stratified;
16pub mod testing;
17
18use std::sync::Arc;
19
20use bicmath_core::contract::{Function, Module, ModuleDescriptor};
21use bicmath_core::number::NumericMode;
22
23/// Build the statistics module with all of its registered functions.
24pub fn module() -> Module {
25    let mut functions: Vec<Arc<dyn Function>> = Vec::new();
26    functions.extend(descriptive::functions());
27    functions.extend(distributions::functions());
28    functions.extend(discrete_distributions::functions());
29    functions.extend(continuous_distributions::functions());
30    functions.extend(inference::functions());
31    functions.extend(regression::functions());
32    functions.extend(testing::functions());
33    functions.extend(stratified::functions());
34    functions.extend(sequential::functions());
35    functions.extend(bayesian::functions());
36    functions.extend(causal::functions());
37    let descriptor = ModuleDescriptor::new(
38        "statistics",
39        "Statistics",
40        "1.0.0",
41        "Descriptive statistics, probability distributions, inference, and stratified \
42         experiment analysis.",
43    )
44    .with_capabilities(vec![
45        "exact_descriptive_statistics",
46        "probability_distributions",
47        "confidence_intervals",
48        "sample_size_planning",
49        "regression_models",
50        "hypothesis_testing",
51        "equivalence_testing",
52        "stratified_experiments",
53        "sequential_inference",
54        "confidence_sequences",
55        "bayesian_conjugate_updates",
56        "causal_estimators",
57    ])
58    .with_dependencies(vec!["core"])
59    .with_modes(vec![
60        NumericMode::Exact,
61        NumericMode::Auto,
62        NumericMode::Scientific,
63    ])
64    .with_source("crates/bicmath-statistics");
65    Module::new(descriptor, functions)
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use bicmath_core::context::ExecContext;
72    use bicmath_core::contract::{Args, ExampleExpectation, Function};
73    use std::collections::{BTreeMap, BTreeSet};
74
75    fn context_for(function: &dyn Function) -> ExecContext {
76        if function.descriptor().modes.contains(&NumericMode::Exact) {
77            // Exact-capable functions declare examples in their exact form.
78            ExecContext::exact()
79        } else {
80            ExecContext::scientific()
81        }
82    }
83
84    #[test]
85    fn descriptor_contract_is_complete() {
86        let module = module();
87        assert_eq!(module.descriptor.id, "statistics");
88        assert_eq!(module.descriptor.version, "1.0.0");
89        assert!(
90            module.functions.len() >= 38,
91            "expected all statistics functions"
92        );
93        let mut ids = BTreeSet::new();
94        for function in &module.functions {
95            let descriptor = function.descriptor();
96            assert!(
97                descriptor.id.starts_with("statistics."),
98                "bad id {}",
99                descriptor.id
100            );
101            assert_eq!(descriptor.module, "statistics");
102            assert_eq!(descriptor.version, "1.0.0");
103            assert!(
104                descriptor
105                    .method_ref
106                    .starts_with("docs/methods/statistics.md#"),
107                "bad method_ref {}",
108                descriptor.method_ref
109            );
110            assert!(
111                !descriptor.examples.is_empty(),
112                "function {} has no executable example",
113                descriptor.id
114            );
115            assert!(ids.insert(descriptor.id.clone()), "duplicate id");
116        }
117    }
118
119    #[test]
120    fn every_example_executes_and_matches() {
121        for function in module().functions {
122            let ctx = context_for(function.as_ref());
123            for example in &function.descriptor().examples {
124                let mut values = BTreeMap::new();
125                for (name, raw) in &example.arguments {
126                    let parameter = function
127                        .descriptor()
128                        .parameter(name)
129                        .unwrap_or_else(|| panic!("example parameter {name} is not declared"));
130                    parameter
131                        .schema
132                        .validate(raw, name, &ctx.limits)
133                        .unwrap_or_else(|error| {
134                            panic!(
135                                "example {:?} of {} has an invalid {name}: {error}",
136                                example.title,
137                                function.descriptor().id
138                            )
139                        });
140                    values.insert(name.clone(), raw.clone());
141                }
142                let result = function.invoke(&Args::new(values), &ctx);
143                match &example.expected {
144                    Some(ExampleExpectation::Value(expected)) => {
145                        let outcome = result.unwrap_or_else(|error| {
146                            panic!(
147                                "example {:?} of {} failed: {error}",
148                                example.title,
149                                function.descriptor().id
150                            )
151                        });
152                        assert_eq!(
153                            &outcome.value,
154                            expected,
155                            "example {:?} of {} produced an unexpected value",
156                            example.title,
157                            function.descriptor().id
158                        );
159                    }
160                    Some(ExampleExpectation::Error(code)) => {
161                        let error = match result {
162                            Err(error) => error,
163                            Ok(outcome) => panic!(
164                                "example {:?} of {} expected {code:?}, produced {:?}",
165                                example.title,
166                                function.descriptor().id,
167                                outcome.value
168                            ),
169                        };
170                        assert_eq!(
171                            error.code,
172                            *code,
173                            "example {:?} of {} produced the wrong error: {}",
174                            example.title,
175                            function.descriptor().id,
176                            error.message
177                        );
178                    }
179                    Some(ExampleExpectation::Contains(text)) => {
180                        let outcome = result.unwrap_or_else(|error| {
181                            panic!(
182                                "example {:?} of {} failed: {error}",
183                                example.title,
184                                function.descriptor().id
185                            )
186                        });
187                        assert!(
188                            format!("{:?}", outcome.value).contains(text),
189                            "example {:?} of {} does not contain {text:?}",
190                            example.title,
191                            function.descriptor().id
192                        );
193                    }
194                    None => {
195                        // No expectation: the example only has to be invocable.
196                        let _ = result;
197                    }
198                }
199            }
200        }
201    }
202}