#[cfg(feature = "ffi")]
mod ffi;
use num::{Float as _, One, Zero};
use opendp_derive::bootstrap;
use crate::core::{Function, StabilityMap, Transformation};
use crate::domains::{AtomDomain, VectorDomain};
use crate::error::Fallible;
use crate::metrics::{AbsoluteDistance, SymmetricDistance};
use crate::traits::{ExactIntCast, Float, InfAdd, InfCast, InfDiv, InfMul, InfSub};
use super::UncheckedSum;
#[bootstrap(
features("contrib"),
arguments(bounds(rust_type = "(T, T)")),
generics(S(default = "Pairwise<T>")),
derived_types(T = "$get_atom(get_type(input_domain))")
)]
pub fn make_sum_of_squared_deviations<S>(
input_domain: VectorDomain<AtomDomain<S::Item>>,
input_metric: SymmetricDistance,
) -> Fallible<
Transformation<
VectorDomain<AtomDomain<S::Item>>,
SymmetricDistance,
AtomDomain<S::Item>,
AbsoluteDistance<S::Item>,
>,
>
where
S: UncheckedSum,
S::Item: 'static + Float,
{
let size = (input_domain.size).ok_or_else(|| {
err!(
MakeTransformation,
"dataset size must be known. Either specify size in the input domain or use make_resize"
)
})?;
let bounds = (input_domain.element_domain.get_closed_bounds())?;
if size == 0 {
return fallible!(MakeTransformation, "size must be greater than zero");
}
let size_ = S::Item::exact_int_cast(size)?;
let (lower, upper) = bounds;
let _1 = S::Item::one();
let mean_error = S::error(size, lower, upper)?.inf_div(&size_)?;
let (lower, upper) = (lower.neg_inf_sub(&mean_error)?, upper.inf_add(&mean_error)?);
let range = upper.inf_sub(&lower)?;
let sensitivity = range
.inf_mul(&range)?
.inf_mul(&size_.inf_sub(&_1)?)?
.inf_div(&size_)?;
let relaxation = S::relaxation(size, S::Item::zero(), range.inf_mul(&range)?)?;
lower.inf_mul(&size_)?;
upper.inf_mul(&size_)?;
range.inf_mul(&range)?.inf_mul(&size_)?;
Transformation::new(
input_domain,
input_metric,
AtomDomain::new_non_nan(),
AbsoluteDistance::default(),
Function::new(move |arg: &Vec<S::Item>| {
let mean = S::unchecked_sum(arg) / size_;
S::unchecked_sum(
&arg.iter()
.map(|v| (*v - mean).powi(2))
.collect::<Vec<S::Item>>(),
)
}),
StabilityMap::new_fallible(move |d_in| {
S::Item::inf_cast(d_in / 2)?
.inf_mul(&sensitivity)?
.inf_add(&relaxation)
}),
)
}
#[cfg(test)]
mod test;