use dashu::integer::IBig;
use opendp_derive::bootstrap;
use crate::{
core::{Function, StabilityMap, Transformation},
domains::AtomDomain,
error::Fallible,
metrics::AbsoluteDistance,
traits::Float,
};
#[cfg(feature = "ffi")]
mod ffi;
#[cfg(test)]
mod test;
#[bootstrap(
features("contrib"),
arguments(constant(c_type = "void *")),
generics(TA(suppress)),
derived_types(TA = "$get_atom(get_carrier_type(input_domain))")
)]
pub fn make_lipschitz_float_mul<TA: Float>(
input_domain: AtomDomain<TA>,
input_metric: AbsoluteDistance<TA>,
constant: TA,
bounds: (TA, TA),
) -> Fallible<
Transformation<AtomDomain<TA>, AbsoluteDistance<TA>, AtomDomain<TA>, AbsoluteDistance<TA>>,
> {
if input_domain.nan() {
return fallible!(MakeTransformation, "input_domain may not contain NaN.");
}
let _2 = TA::exact_int_cast(2)?;
let (lower, upper) = bounds;
let input_mag = lower.alerting_abs()?.total_max(upper)?;
let output_mag = input_mag.inf_mul(&constant.alerting_abs()?)?;
let max_exponent: IBig = output_mag.raw_exponent().into();
let max_unbiased_exponent = max_exponent - TA::EXPONENT_BIAS.into();
let output_ulp = _2.inf_powi(max_unbiased_exponent - TA::MANTISSA_BITS.into())?;
Transformation::new(
input_domain,
input_metric.clone(),
AtomDomain::new_non_nan(),
input_metric,
Function::new_fallible(move |arg: &TA| {
Ok(arg.total_clamp(lower, upper)?.saturating_mul(&constant))
}),
StabilityMap::new_fallible(move |d_in| {
constant.alerting_abs()?.inf_mul(d_in)?.inf_add(&output_ulp)
}),
)
}