1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
use crate::errors::*; use crate::{proto, base}; use crate::components::{Expandable, Report}; use crate::base::{NodeProperties, Value, Array, IndexKey}; use crate::utilities::json::{JSONRelease, value_to_json, privacy_usage_to_json, AlgorithmInfo}; use crate::utilities::{prepend, privacy::spread_privacy_usage, array::get_ith_column}; use indexmap::map::IndexMap; impl Expandable for proto::DpMedian { fn expand_component( &self, _privacy_definition: &Option<proto::PrivacyDefinition>, component: &proto::Component, _public_arguments: &IndexMap<IndexKey, &Value>, properties: &base::NodeProperties, component_id: u32, _maximum_id: u32, ) -> Result<base::ComponentExpansion> { let mut expansion = base::ComponentExpansion::default(); let mechanism = if self.mechanism.to_lowercase().as_str() == "automatic" { if properties.contains_key::<IndexKey>(&"candidates".into()) { "exponential" } else { "laplace" }.to_string() } else { self.mechanism.to_lowercase() }; expansion.computation_graph.insert(component_id, proto::Component { arguments: component.arguments.clone(), variant: Some(if mechanism == "gumbel" { proto::component::Variant::DpGumbelMedian(proto::DpGumbelMedian { privacy_usage: self.privacy_usage.clone() }) } else { proto::component::Variant::DpQuantile(proto::DpQuantile { alpha: 0.5, interpolation: self.interpolation.clone(), privacy_usage: self.privacy_usage.clone(), mechanism }) }), omit: component.omit, submission: component.submission, }); expansion.traversal.push(component_id); Ok(expansion) } } impl Report for proto::DpMedian { fn summarize( &self, node_id: u32, component: &proto::Component, _public_arguments: IndexMap<base::IndexKey, &Value>, properties: NodeProperties, release: &Value, variable_names: Option<&Vec<base::IndexKey>>, ) -> Result<Option<Vec<JSONRelease>>> { let data_property = properties.get::<base::IndexKey>(&"data".into()) .ok_or("data: missing")?.array() .map_err(prepend("data:"))?.clone(); let mut releases = Vec::new(); let minimums = data_property.lower_float().unwrap(); let maximums = data_property.upper_float().unwrap(); let num_columns = data_property.num_columns()?; let privacy_usages = spread_privacy_usage(&self.privacy_usage, num_columns as usize)?; for column_number in 0..(num_columns as usize) { let variable_name = variable_names .and_then(|names| names.get(column_number)).cloned() .unwrap_or_else(|| "[Unknown]".into()); releases.push(JSONRelease { description: "DP release information".to_string(), statistic: "DPMedian".to_string(), variables: serde_json::json!(variable_name.to_string()), release_info: match release.ref_array()? { Array::Float(v) => value_to_json(&get_ith_column(v, column_number)?.into())?, Array::Int(v) => value_to_json(&get_ith_column(v, column_number)?.into())?, _ => return Err("maximum must be numeric".into()) }, privacy_loss: privacy_usage_to_json(&privacy_usages[column_number].clone()), accuracy: None, submission: component.submission, node_id, postprocess: false, algorithm_info: AlgorithmInfo { name: "".to_string(), cite: "".to_string(), mechanism: self.mechanism.clone(), argument: serde_json::json!({ "constraint": { "lowerbound": minimums[column_number], "upperbound": maximums[column_number] } }), }, }); } Ok(Some(releases)) } }