#[cfg(feature = "ffi")]
mod ffi;
use opendp_derive::bootstrap;
use crate::core::{Domain, Function, MetricSpace, StabilityMap, Transformation};
use crate::domains::{AtomDomain, OptionDomain, VectorDomain};
use crate::error::Fallible;
use crate::metrics::EventLevelMetric;
use crate::traits::samplers::GeneratorOpenDP;
use crate::traits::{CheckAtom, CheckNull, Float, HasNull};
use crate::transformations::make_row_by_row;
use rand::distributions::{Distribution, Uniform, uniform::SampleUniform};
#[bootstrap(
features("contrib"),
generics(M(suppress), TA(suppress)),
derived_types(TA = "$get_atom(get_type(input_domain))")
)]
pub fn make_impute_uniform_float<M, TA>(
input_domain: VectorDomain<AtomDomain<TA>>,
input_metric: M,
bounds: (TA, TA),
) -> Fallible<Transformation<VectorDomain<AtomDomain<TA>>, M, VectorDomain<AtomDomain<TA>>, M>>
where
TA: Float + SampleUniform,
M: EventLevelMetric,
(VectorDomain<AtomDomain<TA>>, M): MetricSpace,
{
let (lower, upper) = bounds;
if lower.is_nan() {
return fallible!(MakeTransformation, "lower may not be nan");
}
if upper.is_nan() {
return fallible!(MakeTransformation, "upper may not be nan");
}
if lower >= upper {
return fallible!(MakeTransformation, "lower must be smaller than upper");
}
make_row_by_row(
input_domain,
input_metric,
AtomDomain::new_non_nan(),
move |v| {
if v.is_null() {
let mut rng = GeneratorOpenDP::new();
let sample = Uniform::from(lower..upper).sample(&mut rng);
rng.error.map(|_| sample).unwrap_or(lower)
} else {
*v
}
},
)
}
pub trait ImputeConstantDomain: Domain {
type Imputed;
fn impute_constant<'a>(
default: &'a Self::Carrier,
constant: &'a Self::Imputed,
) -> &'a Self::Imputed;
}
impl<T: CheckAtom> ImputeConstantDomain for OptionDomain<AtomDomain<T>> {
type Imputed = T;
fn impute_constant<'a>(
default: &'a Self::Carrier,
constant: &'a Self::Imputed,
) -> &'a Self::Imputed {
default.as_ref().unwrap_or(constant)
}
}
impl<T: CheckAtom + HasNull> ImputeConstantDomain for AtomDomain<T> {
type Imputed = Self::Carrier;
fn impute_constant<'a>(
default: &'a Self::Carrier,
constant: &'a Self::Imputed,
) -> &'a Self::Imputed {
if default.is_null() { constant } else { default }
}
}
#[bootstrap(
features("contrib"),
arguments(constant(
rust_type = "$get_atom(get_type(input_domain))",
c_type = "AnyObject *"
)),
generics(DIA(suppress), M(suppress))
)]
pub fn make_impute_constant<DIA, M>(
input_domain: VectorDomain<DIA>,
input_metric: M,
constant: DIA::Imputed,
) -> Fallible<Transformation<VectorDomain<DIA>, M, VectorDomain<AtomDomain<DIA::Imputed>>, M>>
where
DIA: ImputeConstantDomain + Default,
DIA::Imputed: 'static + Clone + CheckAtom,
DIA::Carrier: 'static,
M: EventLevelMetric,
(VectorDomain<DIA>, M): MetricSpace,
(VectorDomain<AtomDomain<DIA::Imputed>>, M): MetricSpace,
{
let output_atom_domain = AtomDomain::new_non_nan();
if !output_atom_domain.member(&constant)? {
return fallible!(MakeTransformation, "Constant may not be null.");
}
make_row_by_row(input_domain, input_metric, output_atom_domain, move |v| {
DIA::impute_constant(v, &constant).clone()
})
}
pub trait DropNullDomain: Domain {
type Imputed;
fn option(value: &Self::Carrier) -> Option<Self::Imputed>;
}
impl<T: CheckAtom + Clone> DropNullDomain for OptionDomain<AtomDomain<T>> {
type Imputed = T;
fn option(value: &Self::Carrier) -> Option<T> {
if value.is_null() { None } else { value.clone() }
}
}
impl<T: CheckAtom + HasNull + Clone> DropNullDomain for AtomDomain<T> {
type Imputed = T;
fn option(value: &Self::Carrier) -> Option<T> {
if value.is_null() {
None
} else {
Some(value.clone())
}
}
}
#[bootstrap(features("contrib"), generics(DIA(suppress), M(suppress)))]
pub fn make_drop_null<M, DIA>(
input_domain: VectorDomain<DIA>,
input_metric: M,
) -> Fallible<Transformation<VectorDomain<DIA>, M, VectorDomain<AtomDomain<DIA::Imputed>>, M>>
where
DIA: DropNullDomain + Default,
DIA::Imputed: CheckAtom,
M: EventLevelMetric,
(VectorDomain<DIA>, M): MetricSpace,
(VectorDomain<AtomDomain<DIA::Imputed>>, M): MetricSpace,
{
Transformation::new(
input_domain,
input_metric.clone(),
VectorDomain::new(AtomDomain::new_non_nan()),
input_metric,
Function::new(|arg: &Vec<DIA::Carrier>| arg.iter().filter_map(DIA::option).collect()),
StabilityMap::new_from_constant(1),
)
}
#[cfg(test)]
mod test;