use std::cmp::Ordering;
use opendp_derive::bootstrap;
use crate::{
core::{Function, StabilityMap, Transformation},
domains::{AtomDomain, VectorDomain},
error::Fallible,
metrics::{AbsoluteDistance, IntDistance, SymmetricDistance},
traits::{Integer, SaturatingAdd},
};
#[cfg(feature = "ffi")]
mod ffi;
#[doc(hidden)]
pub trait SplitSatSum: Sized {
#[allow(clippy::ptr_arg)]
fn split_sat_sum(v: &Vec<Self>) -> Self;
}
macro_rules! impl_unsigned_int_split_sat_sum {
($($ty:ty)+) => ($(impl SplitSatSum for $ty {
fn split_sat_sum(v: &Vec<Self>) -> Self {
v.iter().fold(0, |sum, v| sum.saturating_add(*v))
}
})+);
}
macro_rules! impl___signed_int_split_sat_sum {
($($ty:ty)+) => ($(impl SplitSatSum for $ty {
fn split_sat_sum(v: &Vec<Self>) -> Self {
let (neg, pos) = v.iter().fold((0, 0), |(neg, pos), v| {
match v.cmp(&0) {
Ordering::Less => (neg.saturating_add(&v), pos),
Ordering::Greater => (neg, pos.saturating_add(&v)),
Ordering::Equal => (neg, pos),
}
});
neg.saturating_add(pos)
}
})+);
}
impl_unsigned_int_split_sat_sum! { u8 u16 u32 u64 u128 usize }
impl___signed_int_split_sat_sum! { i8 i16 i32 i64 i128 isize }
#[bootstrap(features("contrib"), generics(T(example = "$get_first(bounds)")))]
pub fn make_bounded_int_split_sum<T>(
bounds: (T, T),
) -> Fallible<
Transformation<
VectorDomain<AtomDomain<T>>,
SymmetricDistance,
AtomDomain<T>,
AbsoluteDistance<T>,
>,
>
where
T: Integer + SplitSatSum,
{
let (lower, upper) = bounds.clone();
Transformation::new(
VectorDomain::new(AtomDomain::new_closed(bounds)?),
SymmetricDistance,
AtomDomain::default(),
AbsoluteDistance::default(),
Function::new(|arg: &Vec<T>| T::split_sat_sum(arg)),
StabilityMap::new_from_constant(lower.alerting_abs()?.total_max(upper)?),
)
}
#[bootstrap(features("contrib"), generics(T(example = "$get_first(bounds)")))]
pub fn make_sized_bounded_int_split_sum<T>(
size: usize,
bounds: (T, T),
) -> Fallible<
Transformation<
VectorDomain<AtomDomain<T>>,
SymmetricDistance,
AtomDomain<T>,
AbsoluteDistance<T>,
>,
>
where
T: Integer + SplitSatSum,
{
let (lower, upper) = bounds.clone();
let range = upper.inf_sub(&lower)?;
Transformation::new(
VectorDomain::new(AtomDomain::new_closed(bounds)?).with_size(size),
SymmetricDistance,
AtomDomain::default(),
AbsoluteDistance::default(),
Function::new(|arg: &Vec<T>| T::split_sat_sum(arg)),
StabilityMap::new_fallible(
move |d_in: &IntDistance| T::inf_cast(d_in / 2).and_then(|d_in| d_in.inf_mul(&range)),
),
)
}
#[cfg(test)]
mod test;