bench_diff 1.1.0

Reliably compare the latencies of two functions/closures.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
//! Module defining the key data structure produced by [`crate::bench_diff`].

use crate::{
    SummaryStats, Timing, new_timing,
    stats_types::{AltHyp, Ci, HypTestResult, PositionWrtCi},
    summary_stats,
};
use basic_stats::{
    aok::{AokBasicStats, AokFloat},
    core::{SampleMoments, sample_mean, sample_stdev},
    normal::{
        student_1samp_ci, student_1samp_t, student_1samp_test, welch_ci, welch_df, welch_t,
        welch_test,
    },
};
use hdrhistogram::Histogram;

#[cfg(feature = "_dev_support")]
use basic_stats::{binomial, wilcoxon::RankSum};

/// Contains the data resulting from a benchmark comparing two closures `f1` and `f2`.
///
/// It is returned by the core benchmarking functions in this library.
/// Its methods provide descriptive and inferential statistics about the latency samples of the two
/// benchmarked functions, individually and in relation to each other.
///
/// All statistics involving differences refer to a value for `f1` minus the corresponding
/// value for `f2`.
pub struct DiffOut {
    pub(super) hist_f1: Timing,
    pub(super) hist_f2: Timing,
    pub(super) hist_f1_lt_f2: Timing, //todo: replace with count, sum and sum of squares of ratios
    pub(super) count_f1_eq_f2: u64,
    pub(super) hist_f1_gt_f2: Timing, //todo: replace with count, sum and sum of squares of ratios
    pub(super) sum_f1: i64,
    pub(super) sum_f2: i64,
    pub(super) sum_ln_f1: f64,
    pub(super) sum2_ln_f1: f64,
    pub(super) sum_ln_f2: f64,
    pub(super) sum2_ln_f2: f64,
    pub(super) sum2_diff_f1_f2: i64,
    pub(super) sum2_diff_ln_f1_f2: f64,
}

impl DiffOut {
    /// Creates a new empty instance.
    pub(crate) fn new() -> Self {
        let hist_f1 = new_timing(20 * 1000 * 1000, 5);
        let hist_f2 = Histogram::<u64>::new_from(&hist_f1);
        let hist_f1_lt_f2 = Histogram::<u64>::new_from(&hist_f1);
        let count_f1_eq_f2 = 0;
        let hist_f1_gt_f2 = Histogram::<u64>::new_from(&hist_f1);
        let sum_f1 = 0;
        let sum_f2 = 0;
        let sum_ln_f1 = 0.;
        let sum2_ln_f1 = 0.;
        let sum_ln_f2 = 0.;
        let sum2_ln_f2 = 0.;
        let sum2_diff_f1_f2 = 0;
        let sum2_diff_ln_f1_f2 = 0.;

        Self {
            hist_f1,
            hist_f2,
            hist_f1_lt_f2,
            count_f1_eq_f2,
            hist_f1_gt_f2,
            sum_f1,
            sum_f2,
            sum_ln_f1,
            sum2_ln_f1,
            sum_ln_f2,
            sum2_ln_f2,
            sum2_diff_f1_f2,
            sum2_diff_ln_f1_f2,
        }
    }

    /// Number of observations (sample size) for a function, as an integer.
    ///
    /// It is the same value for `f1` and `f2`.
    #[inline(always)]
    pub fn n(&self) -> u64 {
        self.hist_f1.len()
    }

    /// Number of observations (sample size) for a function, as a floating point number.
    ///
    /// It is the same value for `f1` and `f2`.
    #[inline(always)]
    pub fn nf(&self) -> f64 {
        self.hist_f1.len() as f64
    }

    /// Summary descriptive statistics for `f1`.
    ///
    /// Includes sample size, mean, standard deviation, median, several percentiles, min, and max.
    pub fn summary_f1(&self) -> SummaryStats {
        summary_stats(&self.hist_f1)
    }

    /// Summary descriptive statistics for `f2`.
    ///
    /// Includes sample size, mean, standard deviation, median, several percentiles, min, and max.
    pub fn summary_f2(&self) -> SummaryStats {
        summary_stats(&self.hist_f2)
    }

    fn sum_diff_f1_f2(&self) -> f64 {
        (self.sum_f1 - self.sum_f2) as f64
    }

    fn sum_diff_ln_f1_f2(&self) -> f64 {
        self.sum_ln_f1 - self.sum_ln_f2
    }

    /// Mean of `f1`'s latencies.
    pub fn mean_f1(&self) -> f64 {
        self.summary_f1().mean
    }

    /// Mean of `f1`'s latencies.
    pub fn mean_f2(&self) -> f64 {
        self.summary_f2().mean
    }

    /// Median of `f1`'s latencies.
    pub fn median_f1(&self) -> f64 {
        self.summary_f1().median as f64
    }

    /// Median of `f2`'s latencies.
    pub fn median_f2(&self) -> f64 {
        self.summary_f2().median as f64
    }

    /// Difference between the median of `f1`'s latencies and the median of `f2`'s latencies.
    pub fn diff_medians_f1_f2(&self) -> f64 {
        self.median_f1() - self.median_f2()
    }

    /// Ratio of the median of `f1`'s latencies to the median of `f2`'s latencies.
    pub fn ratio_medians_f1_f2(&self) -> f64 {
        self.median_f1() / self.median_f2()
    }

    #[cfg(feature = "_dev_support")]
    /// Ratio of the minimum of `f1`'s latencies to the minimum of `f2`'s latencies.
    pub fn ratio_mins_f1_f2(&self) -> f64 {
        self.summary_f1().min as f64 / self.summary_f2().min as f64
    }

    /// Count of paired observations where `f1`'s latency is less than `f2`'s.
    pub fn count_f1_lt_f2(&self) -> u64 {
        self.hist_f1_lt_f2.len()
    }

    /// Count of paired observations where `f1`'s latency is equal to `f2`'s.
    pub fn count_f1_eq_f2(&self) -> u64 {
        self.count_f1_eq_f2
    }

    /// Count of paired observations where `f1`'s latency is greater than `f2`'s.
    pub fn count_f1_gt_f2(&self) -> u64 {
        self.hist_f1_gt_f2.len()
    }

    /// Mean of the natural logarithms of `f1`'s latencies.
    pub fn mean_ln_f1(&self) -> f64 {
        sample_mean(self.n(), self.sum_ln_f1).aok()
    }

    /// Standard deviation of the natural logarithms `f1`'s latecies.
    pub fn stdev_ln_f1(&self) -> f64 {
        sample_stdev(self.n(), self.sum_ln_f1, self.sum2_ln_f1).aok()
    }

    /// Mean of the natural logarithms of `f2`'s latencies.
    pub fn mean_ln_f2(&self) -> f64 {
        sample_mean(self.n(), self.sum_ln_f2).aok()
    }

    /// Standard deviation of the natural logarithms `f2`'s latecies.
    pub fn stdev_ln_f2(&self) -> f64 {
        sample_stdev(self.n(), self.sum_ln_f2, self.sum2_ln_f2).aok()
    }

    /// Mean of the differences between paired latencies of `f1` and `f2`.
    /// Equal to the difference between the mean of `f1`'s latencies and the mean of `f2`'s latencies.
    pub fn mean_diff_f1_f2(&self) -> f64 {
        sample_mean(self.n(), self.sum_diff_f1_f2()).aok()
    }

    /// Standard deviation of the differences between paired latencies of `f1` and `f2`.
    /// (*Not* the difference between the standard deviation of `f1`'s latencies and
    /// the standard deviation of`f2`'s latencies.)
    pub fn stdev_diff_f1_f2(&self) -> f64 {
        sample_stdev(self.n(), self.sum_diff_f1_f2(), self.sum2_diff_f1_f2 as f64).aok()
    }

    /// Mean of the differences between the natural logarithms of paired latencies of `f1` and `f2`.
    /// (Same as the difference between the mean of the natural logarithms of `f1`'s latencies and
    /// the mean of the natural logarithms of`f2`'s latencies.)
    pub fn mean_diff_ln_f1_f2(&self) -> f64 {
        sample_mean(self.n(), self.sum_diff_ln_f1_f2()).aok()
    }

    /// Standard deviation of the differences between the natural logarithms of paired latencies of `f1` and `f2`.
    /// (*Not* the difference between the standard deviation of the natural logarithms of `f1`'s latencies and
    /// the standard deviation of the natural logarithms of`f2`'s latencies.)
    pub fn stdev_diff_ln_f1_f2(&self) -> f64 {
        sample_stdev(self.n(), self.sum_diff_ln_f1_f2(), self.sum2_diff_ln_f1_f2).aok()
    }

    /// Estimated ratio of the median `f1` latency to the median `f2` latency,
    /// computed as the `exp()` of [`Self::mean_diff_ln_f1_f2`].
    pub fn ratio_medians_f1_f2_from_lns(&self) -> f64 {
        self.mean_diff_ln_f1_f2().exp()
    }

    #[cfg(feature = "_dev_support")]
    /// Proportion of paired observations where `f1`s latency is greater than `f2`s.
    pub fn prop_f1_gt_f2(&self) -> f64 {
        (self.count_f1_gt_f2() as f64) / self.nf()
    }

    #[cfg(feature = "_dev_support")]
    /// Confidence interval for the probability that `f1`s latency is greater than `f2`s
    /// in a paired observation (Wilson score interval without continuity correction).
    pub fn binomial_f1_gt_f2_ws_ci(&self, alpha: f64) -> Ci {
        use basic_stats::aok::AokBasicStats;

        let n = self.n();
        let n_s = self.count_f1_gt_f2();
        binomial::binomial_ws_ci(n, n_s, alpha).aok()
    }

    #[cfg(feature = "_dev_support")]
    /// Position of `value` with respect to the
    /// confidence interval for the probability that `f1`s latency is greater than `f2`s
    /// in a paired observation (Wilson score interval without continuity correction).
    pub fn binomial_value_position_wrt_f1_gt_f2_ws_ci(
        &self,
        value: f64,
        alpha: f64,
    ) -> PositionWrtCi {
        let ci = self.binomial_f1_gt_f2_ws_ci(alpha);
        ci.position_of(value)
    }

    #[cfg(feature = "_dev_support")]
    /// Statistical test of the hypothesis that
    /// the probability that `f1`s latency is greater than `f2`s (in a paired observation) is `p0`,
    /// with alternative hypothesis `alt_hyp` and confidence level `(1 - alpha)`.
    pub fn exact_binomial_f1_gt_f2_test(
        &self,
        p0: f64,
        alt_hyp: AltHyp,
        alpha: f64,
    ) -> HypTestResult {
        use basic_stats::aok::AokBasicStats;

        binomial::exact_binomial_test(self.n(), self.count_f1_gt_f2(), p0, alt_hyp, alpha).aok()
    }

    #[cfg(feature = "_dev_support")]
    /// Statistical test of the hypothesis that
    /// the probability that `f1`s latency is greater than `f2`s (in a paired observation) is `0.5`,
    /// with alternative hypothesis `alt_hyp` and confidence level `(1 - alpha)`.
    pub fn exact_binomial_f1_gt_f2_eq_half_test(
        &self,
        alt_hyp: AltHyp,
        alpha: f64,
    ) -> HypTestResult {
        self.exact_binomial_f1_gt_f2_test(0.5, alt_hyp, alpha)
    }

    /// Welch's t statistic for
    /// `mean(ln(latency(f1))) - mean(ln(latency(f2)))` (where `ln` is the natural logarithm).
    pub fn welch_ln_t(&self) -> f64 {
        let moments1 = SampleMoments::new(self.hist_f1.len(), self.sum_ln_f1, self.sum2_ln_f1);
        let moments2 = SampleMoments::new(self.hist_f2.len(), self.sum_ln_f2, self.sum2_ln_f2);
        welch_t(&moments1, &moments2).aok()
    }

    /// Degrees of freedom for Welch's t-test for
    /// `mean(ln(latency(f1))) - mean(ln(latency(f2)))` (where `ln` is the natural logarithm).
    pub fn welch_ln_df(&self) -> f64 {
        let moments1 = SampleMoments::new(self.hist_f1.len(), self.sum_ln_f1, self.sum2_ln_f1);
        let moments2 = SampleMoments::new(self.hist_f2.len(), self.sum_ln_f2, self.sum2_ln_f2);
        welch_df(&moments1, &moments2).aok()
    }

    /// Welch confidence interval for
    /// `mean(ln(latency(f1))) - mean(ln(latency(f2)))` (where `ln` is the natural logarithm),
    /// with confidence level `(1 - alpha)`.
    ///
    /// Assumes that both `latency(f1)` and `latency(f2)` are approximately log-normal.
    /// This assumption is widely supported by performance analysis theory and empirical data.
    ///
    /// This is also the confidence interval for the difference of medians of logarithms under the above assumption.
    pub fn welch_ln_ci(&self, alpha: f64) -> Ci {
        let moments1 = SampleMoments::new(self.hist_f1.len(), self.sum_ln_f1, self.sum2_ln_f1);
        let moments2 = SampleMoments::new(self.hist_f2.len(), self.sum_ln_f2, self.sum2_ln_f2);
        welch_ci(&moments1, &moments2, alpha).aok()
    }

    /// Welch confidence interval for
    /// `median(latency(f1)) / median(latency(f2))`,
    /// with confidence level `(1 - alpha)`.
    ///
    /// Assumes that both `latency(f1)` and `latency(f2)` are approximately log-normal.
    /// This assumption is widely supported by performance analysis theory and empirical data.
    pub fn welch_ratio_ci(&self, alpha: f64) -> Ci {
        let Ci(log_low, log_high) = self.welch_ln_ci(alpha);
        let low = log_low.exp();
        let high = log_high.exp();
        Ci(low, high)
    }

    /// Position of `value` with respect to the
    /// Welch confidence interval for
    /// `median(latency(f1)) / median(latency(f2))`,
    /// with confidence level `(1 - alpha)`.
    ///
    /// Assumes that both `latency(f1)` and `latency(f2)` are approximately log-normal.
    /// This assumption is widely supported by performance analysis theory and empirical data.
    pub fn welch_value_position_wrt_ratio_ci(&self, value: f64, alpha: f64) -> PositionWrtCi {
        let ci = self.welch_ratio_ci(alpha);
        ci.position_of(value)
    }

    /// Welch's test of the hypothesis that
    /// `median(latency(f1)) == median(latency(f2))`,
    /// with alternative hypothesis `alt_hyp` and confidence level `(1 - alpha)`.
    ///
    /// Assumes that both `latency(f1)` and `latency(f2)` are approximately log-normal.
    /// This assumption is widely supported by performance analysis theory and empirical data.
    pub fn welch_ln_test(&self, alt_hyp: AltHyp, alpha: f64) -> HypTestResult {
        let moments1 = SampleMoments::new(self.hist_f1.len(), self.sum_ln_f1, self.sum2_ln_f1);
        let moments2 = SampleMoments::new(self.hist_f2.len(), self.sum_ln_f2, self.sum2_ln_f2);
        welch_test(&moments1, &moments2, alt_hyp, alpha).aok()
    }

    #[cfg(feature = "_dev_support")]
    /// Student's one-sample t statistic for
    /// `mean(latency(f1) - latency(f2))`.
    pub fn student_diff_t(&self) -> f64 {
        let moments = SampleMoments::new(
            self.hist_f1.len(),
            self.sum_diff_f1_f2(),
            self.sum2_diff_f1_f2 as f64,
        );
        student_1samp_t(&moments, 0.).aok()
    }

    #[cfg(feature = "_dev_support")]
    /// Degrees of freedom for Student's one-sample t-test for
    /// `mean(latency(f1) - latency(f2))`.
    pub fn student_diff_df(&self) -> f64 {
        self.nf() - 1.
    }

    #[cfg(feature = "_dev_support")]
    /// Student's confidence interval for
    /// `mean(latency(f1) - latency(f2))`,
    /// with confidence level `(1 - alpha)`.
    ///
    /// Assumes that `latency(f1) - latency(f2)` is normally distributed. This assumption is *not* supported by
    /// performance analysis theory or empirical data.
    pub fn student_diff_ci(&self, alpha: f64) -> Ci {
        let moments = SampleMoments::new(
            self.hist_f1.len(),
            self.sum_diff_f1_f2(),
            self.sum2_diff_f1_f2 as f64,
        );
        student_1samp_ci(&moments, alpha).aok()
    }

    #[cfg(feature = "_dev_support")]
    /// Position of `value` with respect to
    /// Student's confidence interval for
    /// `mean(latency(f1) - latency(f2))`,
    /// with confidence level `(1 - alpha)`.
    ///
    /// Assumes that `latency(f1) - latency(f2)` is normally distributed. This assumption is *not* supported by
    /// performance analysis theory or empirical data.
    pub fn student_value_position_wrt_diff_ci(&self, value: f64, alpha: f64) -> PositionWrtCi {
        let ci = self.student_diff_ci(alpha);
        ci.position_of(value)
    }

    #[cfg(feature = "_dev_support")]
    /// Student's one-sample test of the hypothesis that
    /// `mean(latency(f1) - latency(f2)) == 0`,
    /// with alternative hypothesis `alt_hyp` and confidence level `(1 - alpha)`.
    ///
    /// Assumes that `latency(f1) - latency(f2)` is normally distributed. This assumption is *not* supported by
    /// performance analysis theory or empirical data.
    pub fn student_diff_test(&self, alt_hyp: AltHyp, alpha: f64) -> HypTestResult {
        let moments = SampleMoments::new(
            self.hist_f1.len(),
            self.sum_diff_f1_f2(),
            self.sum2_diff_f1_f2 as f64,
        );
        student_1samp_test(&moments, 0., alt_hyp, alpha).aok()
    }

    #[deprecated = "Use `welch_ln_t` instead"]
    /// Student's one-sample t statistic for
    /// `mean(ln(latency(f1)) - ln(latency(f2)))` (where `ln` is the natural logarithm).
    pub fn student_diff_ln_t(&self) -> f64 {
        let moments = SampleMoments::new(
            self.hist_f1.len(),
            self.sum_diff_ln_f1_f2(),
            self.sum2_diff_ln_f1_f2,
        );
        student_1samp_t(&moments, 0.).aok()
    }

    #[deprecated = "Use `welch_ln_df` instead"]
    /// Degrees of freedom for Student's one-sample t-test for
    /// `mean(ln(latency(f1)) - ln(latency(f2)))` (where `ln` is the natural logarithm).
    pub fn student_diff_ln_df(&self) -> f64 {
        self.nf() - 1.
    }

    #[deprecated = "Use `welch_ln_ci` instead"]
    /// Student's one-sample confidence interval for
    /// `mean(ln(latency(f1)) - ln(latency(f2)))` (where `ln` is the natural logarithm).
    /// with confidence level `(1 - alpha)`.
    ///
    /// Assumes that both `latency(f1)` and `latency(f2)` are approximately log-normal.
    /// This assumption is widely supported by performance analysis theory and empirical data.
    pub fn student_diff_ln_ci(&self, alpha: f64) -> Ci {
        let moments = SampleMoments::new(
            self.hist_f1.len(),
            self.sum_diff_ln_f1_f2(),
            self.sum2_diff_ln_f1_f2,
        );
        student_1samp_ci(&moments, alpha).aok()
    }

    #[deprecated = "Use `welch_ratio_ci` instead"]
    #[allow(deprecated)]
    /// Student's one-sample confidence interval for
    /// `median(latency(f1)) / median(latency(f2))`,
    /// with confidence level `(1 - alpha)`.
    ///
    /// Assumes that both `latency(f1)` and `latency(f2)` are approximately log-normal.
    /// This assumption is widely supported by performance analysis theory and empirical data.
    pub fn student_ratio_ci(&self, alpha: f64) -> Ci {
        let Ci(log_low, log_high) = self.student_diff_ln_ci(alpha);
        let low = log_low.exp();
        let high = log_high.exp();
        Ci(low, high)
    }

    #[deprecated = "Use `welch_value_position_wrt_ratio_ci` instead"]
    #[allow(deprecated)]
    /// Position of `value` with respect to
    /// Student's one-sample confidence interval for
    /// `median(latency(f1)) / median(latency(f2))`,
    /// with confidence level `(1 - alpha)`.
    ///
    /// Assumes that both `latency(f1)` and `latency(f2)` are approximately log-normal.
    /// This assumption is widely supported by performance analysis theory and empirical data.
    pub fn student_value_position_wrt_ratio_ci(&self, value: f64, alpha: f64) -> PositionWrtCi {
        let ci = self.student_ratio_ci(alpha);
        ci.position_of(value)
    }

    #[deprecated = "Use `welch_ln_test` instead"]
    /// Student's one-sample test of the hypothesis that
    /// `median(latency(f1)) == median(latency(f2))`,
    /// with alternative hypothesis `alt_hyp` and confidence level `(1 - alpha)`.
    ///
    /// Assumes that both `latency(f1)` and `latency(f2)` are approximately log-normal.
    /// This assumption is widely supported by performance analysis theory and empirical data.
    pub fn student_diff_ln_test(&self, alt_hyp: AltHyp, alpha: f64) -> HypTestResult {
        let moments = SampleMoments::new(
            self.hist_f1.len(),
            self.sum_diff_ln_f1_f2(),
            self.sum2_diff_ln_f1_f2,
        );
        student_1samp_test(&moments, 0., alt_hyp, alpha).aok()
    }

    #[cfg(feature = "_dev_support")]
    /// Wilcoxon rank sum struct.
    fn rank_sum(&self) -> RankSum {
        let iter_f1 = self.hist_f1.iter_recorded().map(|x| {
            let value = x.value_iterated_to();
            let count = x.count_at_value();
            (value as f64, count)
        });

        let iter_f2 = self.hist_f2.iter_recorded().map(|x| {
            let value = x.value_iterated_to();
            let count = x.count_at_value();
            (value as f64, count)
        });

        RankSum::from_iters_with_counts(iter_f1, iter_f2)
            .expect("data should be in strictly increasing order")
    }

    #[cfg(feature = "_dev_support")]
    /// Wilcoxon rank sum *W* statistic for `latency(f1)` and `latency(f2)`.
    pub fn wilcoxon_rank_sum_w(&self) -> f64 {
        self.rank_sum().w()
    }

    #[cfg(feature = "_dev_support")]
    /// Wilcoxon rank sum normal approximation *z* value for `latency(f1)` and `latency(f2)`.
    pub fn wilcoxon_rank_sum_z(&self) -> f64 {
        self.rank_sum().z().aok()
    }

    #[cfg(feature = "_dev_support")]
    /// Wilcoxon rank sum normal approximation *p* value for `latency(f1)` and `latency(f2)`.
    pub fn wilcoxon_rank_sum_p(&self, alt_hyp: AltHyp) -> f64 {
        self.rank_sum().z_p(alt_hyp).aok()
    }

    #[cfg(feature = "_dev_support")]
    /// Wilcoxon rank sum test for for `latency(f1)` and `latency(f2)`,
    /// with alternative hypothesis `alt_hyp` and confidence level `(1 - alpha)`.
    pub fn wilcoxon_rank_sum_test(&self, alt_hyp: AltHyp, alpha: f64) -> HypTestResult {
        self.rank_sum().z_test(alt_hyp, alpha).aok()
    }
}