diff --git a/src/bindings/PyDP/algorithms/bounded_functions.cpp b/src/bindings/PyDP/algorithms/bounded_functions.cpp index 6798c93c..874d7f9c 100644 --- a/src/bindings/PyDP/algorithms/bounded_functions.cpp +++ b/src/bindings/PyDP/algorithms/bounded_functions.cpp @@ -22,67 +22,7 @@ namespace dp = differential_privacy; template void declareBoundedAlgorithm(py::module& m) { using builder = typename dp::python::AlgorithmBuilder; - py::class_ bld(m, builder().get_algorithm_name().c_str()); - bld.attr("__module__") = "pydp"; - bld.def(py::init([](double epsilon, T lower_bound, T upper_bound, int l0_sensitivity, - int linf_sensitivity) { - py::print("Building with bounds"); - return builder().Build(epsilon, lower_bound, upper_bound, l0_sensitivity, - linf_sensitivity); - }), - py::arg("epsilon"), py::arg("lower_bound"), py::arg("upper_bound"), - py::arg("l0_sensitivity") = 1, py::arg("linf_sensitivity") = 1); - - bld.def(py::init([](double epsilon, int l0_sensitivity, int linf_sensitivity) { - py::print("Building without bounds"); - return builder().Build(epsilon, nullopt, nullopt, l0_sensitivity, - linf_sensitivity); - }), - py::arg("epsilon"), py::arg("l0_sensitivity") = 1, - py::arg("linf_sensitivity") = 1); - - bld.def_property_readonly("epsilon", [](Algorithm& obj) { return obj.GetEpsilon(); }); - - bld.def("privacy_budget_left", - [](Algorithm& obj) { return obj.RemainingPrivacyBudget(); }); - - bld.def("add_entry", [](Algorithm& obj, T& v) { - obj.AddEntry(v); - }); - - bld.def("add_entries", [](Algorithm& obj, std::vector& v) { - obj.AddEntries(v.begin(), v.end()); - }); - - bld.def("partial_result", [](Algorithm& obj) { - auto result = obj.PartialResult(); - - if (!result.ok()) { - throw std::runtime_error(result.status().error_message()); - } - return dp::GetValue(result.ValueOrDie()); - }); - - bld.def("partial_result", [](Algorithm& obj, double privacy_budget) { - if (privacy_budget > obj.RemainingPrivacyBudget()){ - throw std::runtime_error("Privacy budget requeted exceeds set privacy budget"); - } - auto result = obj.PartialResult(privacy_budget); - - if (!result.ok()) { - throw std::runtime_error(result.status().error_message()); - } - return dp::GetValue(result.ValueOrDie()); - }); - - bld.def("result", [](Algorithm& obj, std::vector& v) { - auto result = obj.Result(v.begin(), v.end()); - - if (!result.ok()) { - throw std::runtime_error(result.status().error_message()); - } - return dp::GetValue(result.ValueOrDie()); - }); + builder().declare(m); } void init_algorithms_bounded_functions(py::module& m) { diff --git a/src/bindings/PyDP/algorithms/count.cpp b/src/bindings/PyDP/algorithms/count.cpp index 75d81b69..554fc030 100644 --- a/src/bindings/PyDP/algorithms/count.cpp +++ b/src/bindings/PyDP/algorithms/count.cpp @@ -12,46 +12,13 @@ using namespace std; namespace py = pybind11; namespace dp = differential_privacy; -template -void declareCount(py::module& m, string const& suffix) { - using builder = typename dp::python::AlgorithmBuilder>; - - py::class_> count(m, ("Count" + suffix).c_str()); - count.attr("__module__") = "pydp"; - count.def(py::init([](double epsilon) { return builder().Build(epsilon); })) - .def("add_entry", &dp::Count::AddEntry) - .def("add_entries", - [](dp::Count& obj, std::vector& v) { - return obj.AddEntries(v.begin(), v.end()); - }) - // TODO: port ConfidenceInterval and Summary - //.def("noise_confidence_interval", &dp::Count::NoiseConfidenceInterval) - //.def("serialize", &dp::Count::Serialize) - //.def("merge", &dp::Count::Merge) - .def("memory_used", &dp::Count::MemoryUsed) - .def_property_readonly("epsilon", - [](dp::Count& obj) { return obj.GetEpsilon(); }) - .def("result", - [](dp::Count& obj, std::vector& v) { - auto result = obj.Result(v.begin(), v.end()); - - if (!result.ok()) { - throw std::runtime_error(result.status().error_message()); - } - - return dp::GetValue(result.ValueOrDie()); - }) - .def("partial_result", - [](dp::Count& obj) { - return dp::GetValue(obj.PartialResult().ValueOrDie()); - }) - - .def("partial_result", [](dp::Count& obj, double privacy_budget) { - return dp::GetValue(obj.PartialResult(privacy_budget).ValueOrDie()); - }); +template +void declareAlgorithm(py::module& m) { + using builder = typename dp::python::AlgorithmBuilder; + builder().declare(m); } void init_algorithms_count(py::module& m) { - declareCount(m, "Int"); - declareCount(m, "Double"); + declareAlgorithm>(m); + declareAlgorithm>(m); } diff --git a/src/bindings/PyDP/pydp_lib/algorithm_builder.hpp b/src/bindings/PyDP/pydp_lib/algorithm_builder.hpp index 32139ff7..8c27d294 100644 --- a/src/bindings/PyDP/pydp_lib/algorithm_builder.hpp +++ b/src/bindings/PyDP/pydp_lib/algorithm_builder.hpp @@ -6,10 +6,12 @@ #include "algorithms/bounded-standard-deviation.h" #include "algorithms/bounded-sum.h" #include "algorithms/bounded-variance.h" +#include "algorithms/count.h" #include "algorithms/numerical-mechanisms.h" #include "base/statusor.h" namespace dp = differential_privacy; +namespace py = pybind11; namespace differential_privacy { namespace python { @@ -25,7 +27,8 @@ constexpr bool is_bounded_algorithm() { template class AlgorithmBuilder { public: - std::unique_ptr Build(double epsilon, + std::unique_ptr build(double epsilon, + std::optional delta = std::nullopt, std::optional lower_bound = std::nullopt, std::optional upper_bound = std::nullopt, std::optional l0_sensitivity = std::nullopt, @@ -34,6 +37,7 @@ class AlgorithmBuilder { builder.SetEpsilon(epsilon); + if (delta.has_value()) builder.SetDelta(delta.value()); if (l0_sensitivity.has_value()) builder.SetMaxPartitionsContributed(l0_sensitivity.value()); if (linf_sensitivity.has_value()) @@ -58,12 +62,120 @@ class AlgorithmBuilder { {typeid(dp::BoundedMean), "BoundedMean"}, {typeid(dp::BoundedSum), "BoundedSum"}, {typeid(dp::BoundedStandardDeviation), "BoundedStandardDeviation"}, - {typeid(dp::BoundedVariance), "BoundedVariance"}}; + {typeid(dp::BoundedVariance), "BoundedVariance"}, + {typeid(dp::Count), "Count"}}; std::string get_algorithm_name() { // Set the suffix string return (algorithm_to_name[typeid(Algorithm)] + type_to_name[typeid(T)]); } + + void declare(py::module& m) { + py::class_ pyself(m, get_algorithm_name().c_str()); + + pyself.attr("__module__") = "pydp"; + + // Constructors + if constexpr (is_bounded_algorithm()) { + // Explicit bounds constructor + pyself.def( + py::init([this](double epsilon, double delta, T lower_bound, T upper_bound, + int l0_sensitivity, int linf_sensitivity) { + return this->build(epsilon, delta, lower_bound, upper_bound, l0_sensitivity, + linf_sensitivity); + }), + py::arg("epsilon"), py::arg("delta") = 0, py::arg("lower_bound"), + py::arg("upper_bound"), py::arg("l0_sensitivity") = 1, + py::arg("linf_sensitivity") = 1); + } + + // No bounds constructor + pyself.def(py::init([this](double epsilon, double delta, int l0_sensitivity, + int linf_sensitivity) { + return this->build(epsilon, delta, std::nullopt /*lower_bound*/, + std::nullopt /*upper_bound*/, l0_sensitivity, + linf_sensitivity); + }), + py::arg("epsilon"), py::arg("delta") = 0, py::arg("l0_sensitivity") = 1, + py::arg("linf_sensitivity") = 1); + + // Getters + pyself.def_property_readonly("epsilon", &Algorithm::GetEpsilon); + + pyself.def("privacy_budget_left", &Algorithm::RemainingPrivacyBudget); + + pyself.def("consume_privacy_budget", &Algorithm::ConsumePrivacyBudget); + + pyself.def("memory_used", &Algorithm::MemoryUsed); + + // Input data + pyself.def("add_entries", [](Algorithm& pythis, std::vector& v) { + pythis.AddEntries(v.begin(), v.end()); + }); + + pyself.def("add_entry", &Algorithm::AddEntry); + + // Compute results + pyself.def("result", [](Algorithm& pythis, std::vector& v) { + auto result = pythis.Result(v.begin(), v.end()); + + if (!result.ok()) { + throw std::runtime_error(result.status().error_message()); + } + + return dp::GetValue(result.ValueOrDie()); + }); + + pyself.def("partial_result", [](Algorithm& pythis) { + auto result = pythis.PartialResult(); + + if (!result.ok()) { + throw std::runtime_error(result.status().error_message()); + } + + return dp::GetValue(result.ValueOrDie()); + }); + + pyself.def("partial_result", [](Algorithm& pythis, double privacy_budget) { + if (privacy_budget > pythis.RemainingPrivacyBudget()) { + throw std::runtime_error("Privacy budget requeted exceeds set privacy budget"); + } + + auto result = pythis.PartialResult(privacy_budget); + + if (!result.ok()) { + throw std::runtime_error(result.status().error_message()); + } + + return dp::GetValue(result.ValueOrDie()); + }); + + pyself.def("partial_result", [](Algorithm& pythis, double privacy_budget, + double noise_interval_level) { + if (privacy_budget > pythis.RemainingPrivacyBudget()) { + throw std::runtime_error("Privacy budget requeted exceeds set privacy budget"); + } + + auto result = pythis.PartialResult(privacy_budget, noise_interval_level); + + if (!result.ok()) { + throw std::runtime_error(result.status().error_message()); + } + + return dp::GetValue(result.ValueOrDie()); + }); + + // Other methods + pyself.def("consume_privacy_budget", &Algorithm::ConsumePrivacyBudget); + + pyself.def("reset", &Algorithm::Reset); + + pyself.def("serialize", &Algorithm::Serialize); + + pyself.def("merge", &Algorithm::Merge); + + pyself.def("noise_confidence_interval", &Algorithm::NoiseConfidenceInterval); + } }; } // namespace python