Skip to main content

opendp/transformations/sum/int/checked/
mod.rs

1use std::iter::Sum;
2
3use opendp_derive::bootstrap;
4
5use super::can_int_sum_overflow;
6use crate::{
7    core::{Function, StabilityMap, Transformation},
8    domains::{AtomDomain, VectorDomain},
9    error::Fallible,
10    metrics::{AbsoluteDistance, IntDistance, SymmetricDistance},
11    traits::Integer,
12};
13
14#[cfg(feature = "ffi")]
15mod ffi;
16
17#[bootstrap(features("contrib"), generics(T(example = "$get_first(bounds)")))]
18/// Make a Transformation that computes the sum of bounded ints.
19/// The effective range is reduced, as (bounds * size) must not overflow.
20///
21/// # Citations
22/// * [CSVW22 Widespread Underestimation of Sensitivity...](https://arxiv.org/pdf/2207.10635.pdf)
23/// * [DMNS06 Calibrating Noise to Sensitivity in Private Data Analysis](https://people.csail.mit.edu/asmith/PS/sensitivity-tcc-final.pdf)
24///
25/// # Arguments
26/// * `size` - Number of records in input data.
27/// * `bounds` - Tuple of lower and upper bounds for data in the input domain.
28///
29/// # Generics
30/// * `T` - Atomic Input Type and Output Type
31pub fn make_sized_bounded_int_checked_sum<T: Integer>(
32    size: usize,
33    bounds: (T, T),
34) -> Fallible<
35    Transformation<
36        VectorDomain<AtomDomain<T>>,
37        SymmetricDistance,
38        AtomDomain<T>,
39        AbsoluteDistance<T>,
40    >,
41>
42where
43    for<'a> T: Sum<&'a T>,
44{
45    if can_int_sum_overflow(size, bounds.clone()) {
46        return fallible!(
47            MakeTransformation,
48            "potential for overflow when computing function. You could resolve this by choosing tighter clipping bounds or by using a data type with greater bit-depth."
49        );
50    }
51
52    let (lower, upper) = bounds.clone();
53    let range = upper.inf_sub(&lower)?;
54    Transformation::new(
55        VectorDomain::new(AtomDomain::new_closed(bounds)?).with_size(size),
56        SymmetricDistance,
57        AtomDomain::default(),
58        AbsoluteDistance::default(),
59        Function::new(|arg: &Vec<T>| arg.iter().sum()),
60        StabilityMap::new_fallible(
61            // If d_in is odd, we still only consider databases with (d_in - 1) / 2 substitutions,
62            //    so floor division is acceptable
63            move |d_in: &IntDistance| T::inf_cast(d_in / 2).and_then(|d_in| d_in.inf_mul(&range)),
64        ),
65    )
66}
67
68#[cfg(test)]
69mod test;