prospicio_prob/
sampled.rs1use prospicio_core::{Error, Result};
4
5use crate::distortion::Distortion;
6use crate::distribution::Distribution;
7use crate::risk::{tvar_sorted, var_sorted};
8
9pub trait Empirical: Distribution {
18 fn draws(&self) -> &[f64];
22
23 fn sorted(&self) -> &[f64];
25
26 fn mean_of(&self, f: impl Fn(f64) -> f64) -> f64 {
36 let draws = self.draws();
37 draws.iter().map(|&x| f(x)).sum::<f64>() / draws.len() as f64
38 }
39
40 fn var(&self, p: f64) -> Result<f64> {
42 var_sorted(self.sorted(), p)
43 }
44
45 fn tvar(&self, p: f64) -> Result<f64> {
47 tvar_sorted(self.sorted(), p)
48 }
49
50 fn distortion(&self, d: &Distortion) -> f64 {
61 d.apply_sorted(self.sorted())
62 }
63}
64
65#[derive(Debug, Clone, PartialEq)]
84pub struct Sampled {
85 draws: Vec<f64>,
86 sorted: Vec<f64>,
87 mean: f64,
88 variance: f64,
89}
90
91impl Sampled {
92 pub fn new(draws: Vec<f64>) -> Result<Self> {
96 if draws.is_empty() {
97 return Err(Error::InvalidParameter {
98 name: "draws",
99 value: 0.0,
100 reason: "must not be empty",
101 });
102 }
103 if let Some(&bad) = draws.iter().find(|x| !x.is_finite()) {
104 return Err(Error::InvalidParameter {
105 name: "draws",
106 value: bad,
107 reason: "must all be finite",
108 });
109 }
110 let mut sorted = draws.clone();
111 sorted.sort_by(f64::total_cmp);
112 let n = draws.len() as f64;
113 let mean = draws.iter().sum::<f64>() / n;
114 let variance = draws.iter().map(|x| (x - mean) * (x - mean)).sum::<f64>() / n;
116 Ok(Self {
117 draws,
118 sorted,
119 mean,
120 variance,
121 })
122 }
123
124 pub fn len(&self) -> usize {
126 self.draws.len()
127 }
128
129 pub fn is_empty(&self) -> bool {
131 false
132 }
133
134 pub fn into_draws(self) -> Vec<f64> {
136 self.draws
137 }
138}
139
140impl Distribution for Sampled {
141 fn mean(&self) -> f64 {
142 self.mean
143 }
144
145 fn variance(&self) -> f64 {
146 self.variance
147 }
148
149 fn cdf(&self, x: f64) -> f64 {
150 self.sorted.partition_point(|&d| d <= x) as f64 / self.sorted.len() as f64
151 }
152
153 fn quantile(&self, p: f64) -> Result<f64> {
154 var_sorted(&self.sorted, p)
155 }
156}
157
158impl Empirical for Sampled {
159 fn draws(&self) -> &[f64] {
160 &self.draws
161 }
162
163 fn sorted(&self) -> &[f64] {
164 &self.sorted
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use prospicio_core::StreamRng;
172
173 fn sampled(x: &[f64]) -> Sampled {
174 Sampled::new(x.to_vec()).unwrap()
175 }
176
177 #[test]
178 fn rejects_empty_and_non_finite() {
179 assert!(Sampled::new(vec![]).is_err());
180 assert!(Sampled::new(vec![1.0, f64::NAN]).is_err());
181 assert!(Sampled::new(vec![f64::INFINITY]).is_err());
182 }
183
184 #[test]
185 fn keeps_simulation_order() {
186 let s = sampled(&[3.0, 1.0, 2.0]);
187 assert_eq!(s.draws(), [3.0, 1.0, 2.0]);
188 assert_eq!(s.sorted(), [1.0, 2.0, 3.0]);
189 assert_eq!(s.len(), 3);
190 }
191
192 #[test]
193 fn moments_are_those_of_the_draws() {
194 let s = sampled(&[2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]);
195 assert_eq!(s.mean(), 5.0);
196 assert_eq!(s.variance(), 4.0);
197 assert_eq!(s.std_dev(), 2.0);
198 }
199
200 #[test]
201 fn variance_survives_a_large_mean() {
202 let s = sampled(&[1e9 + 1.0, 1e9 + 2.0, 1e9 + 3.0]);
203 assert!((s.variance() - 2.0 / 3.0).abs() < 1e-6);
204 }
205
206 #[test]
207 fn cdf_and_quantile_are_inverse() {
208 let s = sampled(&[40.0, 10.0, 30.0, 20.0]);
209 assert_eq!(s.cdf(5.0), 0.0);
210 assert_eq!(s.cdf(10.0), 0.25);
211 assert_eq!(s.cdf(25.0), 0.5);
212 assert_eq!(s.cdf(40.0), 1.0);
213 for x in [10.0, 20.0, 30.0, 40.0] {
214 assert_eq!(s.quantile(s.cdf(x)), Ok(x));
215 }
216 assert!(s.quantile(1.5).is_err());
217 }
218
219 #[test]
220 fn sample_resamples_the_draws() {
221 let s = sampled(&[1.0, 2.0, 3.0]);
222 let r = s.sample(&mut StreamRng::new(9, 0), 1000);
223 assert!(r.iter().all(|x| [1.0, 2.0, 3.0].contains(x)));
224 assert_eq!(r, s.sample(&mut StreamRng::new(9, 0), 1000));
225 }
226
227 #[test]
228 fn lognormal_tail_measures_converge() {
229 use crate::Lognormal;
230 use prospicio_math::special::norm_cdf;
231
232 let d = Lognormal::new(0.0, 0.5).unwrap();
233 let s = Sampled::new(d.sample(&mut StreamRng::new(1, 0), 400_000)).unwrap();
234 let p = 0.99;
235 let q = d.quantile(p).unwrap();
236 let z = (q.ln() - d.meanlog()) / d.sdlog();
238 let tvar = d.mean() * norm_cdf(d.sdlog() - z) / (1.0 - p);
239 let var_err = (s.var(p).unwrap() - q).abs() / q;
240 let tvar_err = (s.tvar(p).unwrap() - tvar).abs() / tvar;
241 assert!(var_err < 0.01, "VaR relative error {var_err}");
242 assert!(tvar_err < 0.01, "TVaR relative error {tvar_err}");
243 }
244}