use opendp_derive::bootstrap;
use crate::{
core::{Function, StabilityMap, Transformation},
domains::{AtomDomain, VectorDomain},
error::Fallible,
metrics::{AbsoluteDistance, IntDistance, SymmetricDistance},
traits::{Integer, Number},
};
#[cfg(feature = "ffi")]
mod ffi;
#[bootstrap(features("contrib"), generics(T(example = "$get_first(bounds)")))]
pub fn make_bounded_int_monotonic_sum<T: Integer>(
bounds: (T, T),
) -> Fallible<
Transformation<
VectorDomain<AtomDomain<T>>,
SymmetricDistance,
AtomDomain<T>,
AbsoluteDistance<T>,
>,
> {
if !signs_agree(bounds) {
return fallible!(
MakeTransformation,
"monotonic summation requires bounds to share the same sign"
);
}
let (lower, upper) = bounds.clone();
Transformation::new(
VectorDomain::new(AtomDomain::new_closed(bounds)?),
SymmetricDistance,
AtomDomain::default(),
AbsoluteDistance::default(),
Function::new(|arg: &Vec<T>| arg.iter().fold(T::zero(), |sum, v| sum.saturating_add(v))),
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_monotonic_sum<T: Integer>(
size: usize,
bounds: (T, T),
) -> Fallible<
Transformation<
VectorDomain<AtomDomain<T>>,
SymmetricDistance,
AtomDomain<T>,
AbsoluteDistance<T>,
>,
> {
if !signs_agree(bounds) {
return fallible!(
MakeTransformation,
"monotonic summation requires bounds to share the same sign"
);
}
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>| arg.iter().fold(T::zero(), |sum, v| sum.saturating_add(v))),
StabilityMap::new_fallible(
move |d_in: &IntDistance| T::inf_cast(d_in / 2).and_then(|d_in| d_in.inf_mul(&range)),
),
)
}
pub(crate) fn signs_agree<T: Number>((a, b): (T, T)) -> bool {
let _0 = T::zero();
a == _0 || b == _0 || (a > _0) == (b > _0)
}
#[cfg(test)]
mod test;