Skip to content

Commit 19eee21

Browse files
authored
Merge pull request #29 from columbia/pds-refactor
Remove the abstract PDS trait
2 parents ca8dd07 + 878596d commit 19eee21

10 files changed

Lines changed: 75 additions & 114 deletions

File tree

.github/workflows/rust.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ jobs:
2424
run: |
2525
cargo test --package pdslib --test ara_demo -- --nocapture
2626
cargo test --package pdslib --test simple_events_demo -- --nocapture
27-
# - name: Run clippy
28-
# run: cargo clippy --tests -- -D warnings
27+
- name: Run clippy
28+
run: cargo clippy --tests -- -D warnings

src/budget/hashmap_filter_storage.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ use std::{collections::HashMap, marker::PhantomData};
22

33
use anyhow::Context;
44

5-
use crate::budget::traits::{Budget, Filter, FilterStorage};
6-
7-
use super::traits::FilterStatus;
5+
use crate::budget::traits::{Budget, Filter, FilterStatus, FilterStorage};
86

97
/// Simple implementation of FilterStorage using a HashMap.
108
/// Works for any Filter that implements the Filter trait.

src/budget/pure_dp_filter.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
pub use crate::budget::traits::{Budget, Filter};
2-
3-
use super::traits::FilterStatus;
1+
use crate::budget::traits::{Budget, Filter, FilterStatus};
42

53
/// A simple floating-point budget for pure differential privacy, with support
64
/// for infinite budget
@@ -87,11 +85,15 @@ mod tests {
8785
let mut filter =
8886
PureDPBudgetFilter::new(PureDPBudget::Epsilon(1.0)).unwrap();
8987
assert_eq!(
90-
filter.check_and_consume(&PureDPBudget::Epsilon(0.5)).unwrap(),
88+
filter
89+
.check_and_consume(&PureDPBudget::Epsilon(0.5))
90+
.unwrap(),
9191
FilterStatus::Continue
9292
);
9393
assert_eq!(
94-
filter.check_and_consume(&PureDPBudget::Epsilon(0.6)).unwrap(),
94+
filter
95+
.check_and_consume(&PureDPBudget::Epsilon(0.6))
96+
.unwrap(),
9597
FilterStatus::OutOfBudget
9698
);
9799
}

src/budget/traits.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ pub trait Filter<T: Budget> {
1414

1515
/// Tries to consume a given budget from the filter.
1616
/// In the formalism from https://arxiv.org/abs/1605.08294, Ok(()) corresponds to CONTINUE, and Err(FilterError::OutOfBudget) corresponds to HALT.
17-
fn check_and_consume(&mut self, budget: &T) -> Result<FilterStatus, Self::Error>;
17+
fn check_and_consume(
18+
&mut self,
19+
budget: &T,
20+
) -> Result<FilterStatus, Self::Error>;
1821

1922
/// [Experimental] Gets the remaining budget for this filter.
2023
/// WARNING: this method is for local visualization only.

src/pds/epoch_pds.rs

Lines changed: 46 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
use std::collections::HashMap;
22

3-
use crate::budget::pure_dp_filter::PureDPBudget;
4-
use crate::budget::traits::FilterStatus;
5-
use crate::budget::traits::FilterStorage;
6-
use crate::events::traits::RelevantEventSelector;
7-
use crate::events::traits::{EpochEvents, EpochId, Event, EventStorage};
8-
use crate::mechanisms::{NoiseScale, NormType};
9-
use crate::pds::traits::PrivateDataService;
10-
use crate::queries::traits::{
11-
EpochReportRequest, PassivePrivacyLossRequest, ReportRequest,
3+
use crate::{
4+
budget::{
5+
pure_dp_filter::PureDPBudget,
6+
traits::{FilterStatus, FilterStorage},
7+
},
8+
events::traits::{
9+
EpochEvents, EpochId, Event, EventStorage, RelevantEventSelector,
10+
},
11+
mechanisms::{NoiseScale, NormType},
12+
queries::traits::{
13+
EpochReportRequest, PassivePrivacyLossRequest, ReportRequest,
14+
},
1215
};
1316

14-
/// Epoch-based private data service implementation, using generic filter
15-
/// storage and event storage interfaces. We might want other implementations
16-
/// eventually, but at first this implementation should cover most use cases,
17-
/// as we can swap the types of events, filters and queries.
18-
pub struct EpochPrivateDataServiceImpl<
17+
/// Epoch-based private data service, using generic filter
18+
/// storage and event storage interfaces.
19+
///
20+
/// TODO(https://github.com/columbia/pdslib/issues/18): handle multiple queriers
21+
/// instead of assuming that there is a single querier and using filter_id =
22+
/// epoch_id
23+
pub struct EpochPrivateDataService<
1924
FS: FilterStorage,
2025
ES: EventStorage,
2126
Q: EpochReportRequest,
@@ -31,20 +36,17 @@ pub struct EpochPrivateDataServiceImpl<
3136
pub epoch_capacity: FS::Budget,
3237

3338
/// Type of accepted queries.
34-
pub _phantom: std::marker::PhantomData<Q>,
39+
pub _phantom_request: std::marker::PhantomData<Q>,
3540

3641
/// Type of errors.
3742
pub _phantom_error: std::marker::PhantomData<ERR>,
3843
}
3944

40-
/// Implements the generic PDS interface for the epoch-based PDS.
45+
/// API for the epoch-based PDS.
4146
///
4247
/// TODO(https://github.com/columbia/pdslib/issues/21): support more than PureDP
43-
/// TODO(https://github.com/columbia/pdslib/issues/18): handle multiple queriers
44-
/// instead of assuming that there is a single querier and using filter_id =
45-
/// epoch_id
46-
impl<EI, E, EE, RES, FS, ES, Q, ERR> PrivateDataService
47-
for EpochPrivateDataServiceImpl<FS, ES, Q, ERR>
48+
/// TODO(https://github.com/columbia/pdslib/issues/22): simplify trait bounds?
49+
impl<EI, E, EE, RES, FS, ES, Q, ERR> EpochPrivateDataService<FS, ES, Q, ERR>
4850
where
4951
EI: EpochId,
5052
E: Event<EpochId = EI>,
@@ -59,25 +61,22 @@ where
5961
>,
6062
ERR: From<FS::Error> + From<ES::Error>,
6163
{
62-
type Event = E;
63-
type Request = Q;
64-
type PassivePrivacyLossRequest =
65-
PassivePrivacyLossRequest<EI, PureDPBudget>;
66-
type Error = ERR;
67-
68-
fn register_event(&mut self, event: E) -> Result<(), Self::Error> {
64+
/// Registers a new event.
65+
pub fn register_event(&mut self, event: E) -> Result<(), ERR> {
6966
println!("Registering event {:?}", event);
7067
self.event_storage.add_event(event)?;
7168
Ok(())
7269
}
7370

71+
/// Computes a report for the given report request.
7472
/// This function follows `compute_attribution_report` from the Cookie
7573
/// Monster Algorithm (https://arxiv.org/pdf/2405.16719, Code Listing 1)
76-
fn compute_report(
74+
pub fn compute_report(
7775
&mut self,
7876
request: Q,
79-
) -> Result<<Q as ReportRequest>::Report, Self::Error> {
77+
) -> Result<<Q as ReportRequest>::Report, ERR> {
8078
println!("Computing report for request {:?}", request);
79+
8180
// Collect events from event storage. If an epoch has no relevant
8281
// events, don't add it to the mapping.
8382
let mut relevant_events_per_epoch: HashMap<EI, EE> = HashMap::new();
@@ -118,7 +117,8 @@ where
118117
// TODO(https://github.com/columbia/pdslib/issues/18): handle multiple queriers.
119118
self.initialize_filter_if_necessary(&epoch_id)?;
120119

121-
// Step 3. Try to consume budget from current epoch, drop events if OOB.
120+
// Step 3. Try to consume budget from current epoch, drop events if
121+
// OOB.
122122
match self
123123
.filter_storage
124124
.check_and_consume(&epoch_id, &individual_privacy_loss)
@@ -143,10 +143,16 @@ where
143143
Ok(filtered_report)
144144
}
145145

146-
fn account_for_passive_privacy_loss(
146+
/// [Experimental] Accounts for passive privacy loss. Can fail if the
147+
/// implementation has an error, but failure must not leak the state of
148+
/// the filters.
149+
///
150+
/// TODO(https://github.com/columbia/pdslib/issues/16): what are the semantics of passive loss queries that go over the filter
151+
/// capacity?
152+
pub fn account_for_passive_privacy_loss(
147153
&mut self,
148-
request: Self::PassivePrivacyLossRequest,
149-
) -> Result<FilterStatus, Self::Error> {
154+
request: PassivePrivacyLossRequest<EI, PureDPBudget>,
155+
) -> Result<FilterStatus, ERR> {
150156
// For each epoch, try to consume the privacy budget.
151157
for epoch_id in request.epoch_ids {
152158
self.initialize_filter_if_necessary(&epoch_id)?;
@@ -165,19 +171,7 @@ where
165171
}
166172
Ok(FilterStatus::Continue)
167173
}
168-
}
169174

170-
/// Utility methods for the epoch-based PDS implementation.
171-
impl<EI, E, EE, FS, ES, Q, ERR> EpochPrivateDataServiceImpl<FS, ES, Q, ERR>
172-
where
173-
EI: EpochId,
174-
E: Event<EpochId = EI>,
175-
EE: EpochEvents,
176-
FS: FilterStorage<FilterId = EI>,
177-
ES: EventStorage<Event = E, EpochEvents = EE>,
178-
Q: EpochReportRequest<EpochId = EI, EpochEvents = EE>,
179-
ERR: From<FS::Error> + From<ES::Error>,
180-
{
181175
fn initialize_filter_if_necessary(
182176
&mut self,
183177
epoch_id: &EI,
@@ -244,8 +238,8 @@ where
244238
return PureDPBudget::Infinite;
245239
}
246240

247-
// In Cookie Monster, we have `query_global_sensitivity` / `requested_epsilon` instead
248-
// of just `noise_scale`.
241+
// In Cookie Monster, we have `query_global_sensitivity` /
242+
// `requested_epsilon` instead of just `noise_scale`.
249243
// TODO(https://github.com/columbia/pdslib/issues/23): potentially use two parameters
250244
// instead of a single `noise_scale`.
251245
PureDPBudget::Epsilon(individual_sensitivity / noise_scale)
@@ -276,11 +270,13 @@ mod tests {
276270
> = HashMapFilterStorage::new();
277271
let events = HashMapEventStorage::new();
278272

279-
let mut pds = EpochPrivateDataServiceImpl {
273+
let mut pds = EpochPrivateDataService {
280274
filter_storage: filters,
281275
event_storage: events,
282276
epoch_capacity: PureDPBudget::Epsilon(3.0),
283-
_phantom: std::marker::PhantomData::<SimpleLastTouchHistogramRequest>,
277+
_phantom_request: std::marker::PhantomData::<
278+
SimpleLastTouchHistogramRequest,
279+
>,
284280
_phantom_error: std::marker::PhantomData::<anyhow::Error>,
285281
};
286282

src/pds/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
pub mod epoch_pds;
2-
pub mod traits;

src/pds/traits.rs

Lines changed: 0 additions & 39 deletions
This file was deleted.

tests/ara_demo.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use pdslib::{
66
pure_dp_filter::{PureDPBudget, PureDPBudgetFilter},
77
},
88
events::{ara_event::AraEvent, hashmap_event_storage::HashMapEventStorage},
9-
pds::{epoch_pds::EpochPrivateDataServiceImpl, traits::PrivateDataService},
9+
pds::epoch_pds::EpochPrivateDataService,
1010
queries::ara_histogram::{AraHistogramRequest, AraRelevantEventSelector},
1111
};
1212

@@ -17,11 +17,11 @@ fn main() {
1717
let filters: HashMapFilterStorage<usize, PureDPBudgetFilter, PureDPBudget> =
1818
HashMapFilterStorage::new();
1919

20-
let mut pds = EpochPrivateDataServiceImpl {
20+
let mut pds = EpochPrivateDataService {
2121
filter_storage: filters,
2222
event_storage: events,
2323
epoch_capacity: PureDPBudget::Epsilon(3.0),
24-
_phantom: std::marker::PhantomData::<AraHistogramRequest>,
24+
_phantom_request: std::marker::PhantomData::<AraHistogramRequest>,
2525
_phantom_error: std::marker::PhantomData::<anyhow::Error>,
2626
};
2727

tests/ppa_workflow.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use pdslib::{
66
events::{
77
hashmap_event_storage::HashMapEventStorage, simple_event::SimpleEvent,
88
},
9-
pds::{epoch_pds::EpochPrivateDataServiceImpl, traits::PrivateDataService},
9+
pds::epoch_pds::EpochPrivateDataService,
1010
queries::simple_last_touch_histogram::SimpleLastTouchHistogramRequest,
1111
};
1212

@@ -21,11 +21,13 @@ fn main() {
2121
let filters: HashMapFilterStorage<usize, PureDPBudgetFilter, PureDPBudget> =
2222
HashMapFilterStorage::new();
2323

24-
let mut pds = EpochPrivateDataServiceImpl {
24+
let mut pds = EpochPrivateDataService {
2525
filter_storage: filters,
2626
event_storage: events,
2727
epoch_capacity: PureDPBudget::Epsilon(3.0),
28-
_phantom: std::marker::PhantomData::<SimpleLastTouchHistogramRequest>,
28+
_phantom_request: std::marker::PhantomData::<
29+
SimpleLastTouchHistogramRequest,
30+
>,
2931
_phantom_error: std::marker::PhantomData::<anyhow::Error>,
3032
};
3133

tests/simple_events_demo.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use pdslib::{
66
events::{
77
hashmap_event_storage::HashMapEventStorage, simple_event::SimpleEvent,
88
},
9-
pds::{epoch_pds::EpochPrivateDataServiceImpl, traits::PrivateDataService},
9+
pds::epoch_pds::EpochPrivateDataService,
1010
queries::simple_last_touch_histogram::SimpleLastTouchHistogramRequest,
1111
};
1212

@@ -16,11 +16,13 @@ fn main() {
1616
let filters: HashMapFilterStorage<usize, PureDPBudgetFilter, PureDPBudget> =
1717
HashMapFilterStorage::new();
1818

19-
let mut pds = EpochPrivateDataServiceImpl {
19+
let mut pds = EpochPrivateDataService {
2020
filter_storage: filters,
2121
event_storage: events,
2222
epoch_capacity: PureDPBudget::Epsilon(3.0),
23-
_phantom: std::marker::PhantomData::<SimpleLastTouchHistogramRequest>,
23+
_phantom_request: std::marker::PhantomData::<
24+
SimpleLastTouchHistogramRequest,
25+
>,
2426
_phantom_error: std::marker::PhantomData::<anyhow::Error>,
2527
};
2628

@@ -58,7 +60,6 @@ fn main() {
5860
assert_eq!(report.bin_value, bucket);
5961

6062
// Test having multiple events in one epoch
61-
println!("");
6263
pds.register_event(event2.clone()).unwrap();
6364

6465
let report_request2 = SimpleLastTouchHistogramRequest {
@@ -88,7 +89,6 @@ fn main() {
8889
assert_eq!(report2.bin_value, bucket2);
8990

9091
// Test request for epoch empty yet.
91-
println!("");
9292
let report_request3_empty = SimpleLastTouchHistogramRequest {
9393
epoch_start: 3, // Epoch 3 not created yet.
9494
epoch_end: 3, // Epoch 3 not created yet.
@@ -100,7 +100,6 @@ fn main() {
100100
assert_eq!(report3_empty.bin_value, None);
101101

102102
// Test restricting attributable_value
103-
println!("");
104103
pds.register_event(event4.clone()).unwrap();
105104
let report_request3_over_budget = SimpleLastTouchHistogramRequest {
106105
epoch_start: 1,
@@ -109,7 +108,8 @@ fn main() {
109108
laplace_noise_scale: 1.0,
110109
is_relevant_event: always_relevant_event,
111110
};
112-
let report3_over_budget = pds.compute_report(report_request3_over_budget).unwrap();
111+
let report3_over_budget =
112+
pds.compute_report(report_request3_over_budget).unwrap();
113113
assert_eq!(report3_over_budget.bin_value, None);
114114

115115
// This tests the case where we meet the first event in epoch 3, below the

0 commit comments

Comments
 (0)