use opendp_derive::bootstrap;
use crate::{
core::{Function, StabilityMap, Transformation},
domains::{AtomDomain, VectorDomain},
error::Fallible,
metrics::{AbsoluteDistance, InsertDeleteDistance, IntDistance},
traits::{AlertingAbs, InfAdd, InfCast, InfMul, InfSub, ProductOrd},
};
use super::{Float, Pairwise, Sequential, SumRelaxation};
#[cfg(feature = "ffi")]
mod ffi;
#[bootstrap(
features("contrib"),
arguments(bounds(rust_type = "(T, T)")),
generics(S(default = "Pairwise<T>")),
derived_types(T = "$get_atom_or_infer(S, get_first(bounds))")
)]
pub fn make_bounded_float_ordered_sum<S>(
size_limit: usize,
bounds: (S::Item, S::Item),
) -> Fallible<
Transformation<
VectorDomain<AtomDomain<S::Item>>,
InsertDeleteDistance,
AtomDomain<S::Item>,
AbsoluteDistance<S::Item>,
>,
>
where
S: SaturatingSum,
S::Item: 'static + Float,
{
let (lower, upper) = bounds;
let ideal_sensitivity = upper
.inf_sub(&lower)?
.total_max(lower.alerting_abs()?.total_max(upper)?)?;
let relaxation = S::relaxation(size_limit, lower, upper)?;
Transformation::new(
VectorDomain::new(AtomDomain::new_closed(bounds)?),
InsertDeleteDistance,
AtomDomain::new_non_nan(),
AbsoluteDistance::default(),
Function::new(move |arg: &Vec<S::Item>| {
S::saturating_sum(&arg[..size_limit.min(arg.len())])
}),
StabilityMap::new_fallible(move |d_in: &IntDistance| {
S::Item::inf_cast(*d_in)?
.inf_mul(&ideal_sensitivity)?
.inf_add(&relaxation)
}),
)
}
#[bootstrap(
features("contrib"),
arguments(bounds(rust_type = "(T, T)")),
generics(S(default = "Pairwise<T>")),
derived_types(T = "$get_atom_or_infer(S, get_first(bounds))")
)]
pub fn make_sized_bounded_float_ordered_sum<S>(
size: usize,
bounds: (S::Item, S::Item),
) -> Fallible<
Transformation<
VectorDomain<AtomDomain<S::Item>>,
InsertDeleteDistance,
AtomDomain<S::Item>,
AbsoluteDistance<S::Item>,
>,
>
where
S: SaturatingSum,
S::Item: 'static + Float,
{
let (lower, upper) = bounds;
let ideal_sensitivity = upper.inf_sub(&lower)?;
let relaxation = S::relaxation(size, lower, upper)?;
Transformation::new(
VectorDomain::new(AtomDomain::new_closed(bounds)?).with_size(size),
InsertDeleteDistance,
AtomDomain::new_non_nan(),
AbsoluteDistance::default(),
Function::new(move |arg: &Vec<S::Item>| S::saturating_sum(arg)),
StabilityMap::new_fallible(move |d_in: &IntDistance| {
S::Item::inf_cast(d_in / 2)?
.inf_mul(&ideal_sensitivity)?
.inf_add(&relaxation)
}),
)
}
#[doc(hidden)]
pub trait SaturatingSum: SumRelaxation {
fn saturating_sum(arg: &[Self::Item]) -> Self::Item;
}
impl<T: Float> SaturatingSum for Sequential<T> {
fn saturating_sum(arg: &[T]) -> T {
arg.iter().fold(T::zero(), |sum, v| sum.saturating_add(v))
}
}
impl<T: Float> SaturatingSum for Pairwise<T> {
fn saturating_sum(arg: &[T]) -> T {
match arg.len() {
0 => T::zero(),
1 => arg[0],
n => {
let m = n / 2;
Self::saturating_sum(&arg[..m]).saturating_add(&Self::saturating_sum(&arg[m..]))
}
}
}
}
#[cfg(test)]
mod test;