├── .env ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── diffpriv_laplace ├── __init__.py ├── anonymizer │ ├── __init__.py │ ├── base.py │ ├── count.py │ ├── counting.py │ ├── max.py │ ├── mean.py │ ├── median.py │ ├── min.py │ ├── proportion.py │ ├── sum.py │ └── variance.py ├── exceptions.py ├── global_sensitivity │ ├── __init__.py │ ├── base.py │ ├── count.py │ ├── counting.py │ ├── max.py │ ├── mean.py │ ├── median.py │ ├── min.py │ ├── proportion.py │ ├── sum.py │ └── variance.py ├── laplace_mechanism.py ├── laplace_sanitizer.py ├── query │ ├── __init__.py │ ├── parallel_statistics.py │ └── sequential_statistics.py ├── statistics.py └── version.py ├── docs ├── .nojekyll ├── _static │ ├── ajax-loader.gif │ ├── alabaster.css │ ├── basic.css │ ├── comment-bright.png │ ├── comment-close.png │ ├── comment.png │ ├── custom.css │ ├── doctools.js │ ├── down-pressed.png │ ├── down.png │ ├── file.png │ ├── jquery-3.1.0.js │ ├── jquery.js │ ├── minus.png │ ├── plus.png │ ├── pygments.css │ ├── searchtools.js │ ├── underscore-1.3.1.js │ ├── underscore.js │ ├── up-pressed.png │ ├── up.png │ └── websupport.js ├── genindex.html ├── index.html ├── search.html └── searchindex.js ├── docs_src ├── Makefile ├── conf.py └── index.rst ├── requirements.txt ├── setup.py ├── tests ├── __init__.py └── unit │ ├── __init__.py │ ├── anonymizer │ ├── __init__.py │ ├── test_anonymizer.py │ ├── test_count.py │ ├── test_counting.py │ ├── test_max.py │ ├── test_mean.py │ ├── test_median.py │ ├── test_min.py │ ├── test_proportion.py │ ├── test_sum.py │ └── test_variance.py │ ├── global_sensitivity │ ├── __init__.py │ ├── test_count.py │ ├── test_counting.py │ ├── test_global_sensitivity.py │ ├── test_max.py │ ├── test_mean.py │ ├── test_median.py │ ├── test_min.py │ ├── test_proportion.py │ ├── test_sum.py │ └── test_variance.py │ ├── query │ ├── __init__.py │ ├── test_parallel_statistics.py │ └── test_sequential_statistics.py │ ├── test_laplace_mechanism.py │ ├── test_laplace_sanitizer.py │ └── test_statistics.py └── tox.ini /.env: -------------------------------------------------------------------------------- 1 | export PATH="$HOME/bin:$PATH" 2 | 3 | "$@" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # .env 2 | 3 | .DS_store 4 | 5 | *.pyc 6 | __pycache__ 7 | 8 | *.egg 9 | *.egg-info 10 | 11 | venv 12 | 13 | .cache/* 14 | .coverage 15 | .tox/* 16 | 17 | docs/.buildinfo 18 | docs/.doctrees 19 | docs/objects.inv 20 | 21 | dist -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.5 2 | 3 | - Added `postprocess` parameter (enabled by default) to `DiffPrivStatistics.count` and `DiffPrivStatistics.proportion` which enforces that the returned anonymized values are within their corresponding ranges: `[0, n]` and `[0.0, 1.0]` respectively. 4 | - Simplified exception names: 5 | - `DiffPrivStatisticsInvalidDimensions` => `DiffPrivInvalidDimensions` 6 | - `DiffPrivStatisticsSizeMismatch` => `DiffPrivSizeMismatch` 7 | - Added support to allow skipping calculation of statistics for a given data slice by using a kind value of `None` instead of a defined `DiffPrivStatisticKind` value. 8 | - Added Laplace sanitizer `DiffPrivLaplaceSanitizer`. 9 | 10 | ## 1.0.4 11 | 12 | - Added sequential (`DiffPrivSequentialStatisticsQuery`) and parallel (`DiffPrivParallelStatisticsQuery`) composition query functionality. 13 | 14 | ## 1.0.3 15 | 16 | - Added support for calculating data anonymized statistics through `DiffPrivStatistics`. 17 | 18 | ## 1.0.2 19 | 20 | - Added Laplace mechanism anonymization for operations: 21 | - `min` 22 | - `max` 23 | - `median` 24 | - `sum` 25 | 26 | ## 1.0.1 27 | 28 | - Added baseline Laplace mechanism anonymization for operations: 29 | - `count` 30 | - `mean` 31 | - `proportion` 32 | - `variance` 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2020 Elmar Langholz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!make 2 | 3 | # Clean pyc 4 | .PHONY: clean-pyc 5 | clean-pyc: 6 | find . -name "*.pyc" -exec rm -rf {} + 7 | 8 | # Run lint 9 | .PHONY: lint 10 | lint: 11 | flake8 diffpriv_laplace tests 12 | 13 | # Execute lint and tests 14 | .PHONY: test 15 | test: lint 16 | ./.env tox 17 | 18 | # Setup dependencies 19 | .PHONY: setup 20 | setup: 21 | pip install -r requirements.txt 22 | 23 | # Build the docs 24 | .PHONY: docs  25 | docs: 26 | -rm -r docs/ 27 | sphinx-build docs_src/ docs/ -b html 28 | touch docs/.nojekyll 29 | 30 | # Clean the /dist directory for a new publish 31 | .PHONY: package-clean 32 | package-clean: 33 | @rm -rf dist/* 34 | 35 | # Build a new package into the /dist directory 36 | .PHONY: package-build 37 | package-build: 38 | python setup.py sdist 39 | 40 | # Test new package before publish 41 | .PHONY: package-check 42 | package-check: 43 | twine check dist/* 44 | 45 | # Publish the new /dist package to Pypi 46 | .PHONY: package-publish 47 | package-publish: 48 | twine upload dist/* 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | diff-priv-laplace-python 2 | ========================= 3 | 4 | Implementation of [differential privacy][1] value anonymization using [Laplace mechanism][2]. 5 | 6 | ## Table of Contents 7 | 8 | - [diff-priv-laplace-python](#diff-priv-laplace-python) 9 | * [Install](#install) 10 | * [Documentation](#documentation) 11 | + [Privacy budget](#privacy-budget) 12 | + [Sequential composition](#sequential-composition) 13 | + [Parallel composition](#parallel-composition) 14 | + [Laplace sanitizer](#laplace-sanitizer) 15 | + [Python docs](#python-docs) 16 | * [Examples](#examples) 17 | + [Sequential composite queries](#sequential-composite-queries) 18 | + [Parallel composite queries](#parallel-composite-queries) 19 | + [Laplace sanitizer application](#laplace-sanitizer-queries) 20 | + [Statistics](#statistics) 21 | + [Laplace mechanism](#laplace-mechanism) 22 | * [License](#license) 23 | 24 | ## Install 25 | 26 | ```console 27 | $ pip install diff-priv-laplace-python 28 | ``` 29 | 30 | ## Documentation 31 | 32 | The Laplace mechanism consists of adding noise, generated through the [Laplace distribution](https://en.wikipedia.org/wiki/Laplace_distribution) and the privacy budget, to a value. The derived value is said to be "anonymized" if the privacy budget used is good enough. 33 | 34 | ### Privacy budget 35 | 36 | The privacy budget `epsilon` defines how much privacy protection to apply. 37 | 38 | - If `epsilon` is large less noise will be added, therefore more information leakage exists so less privacy protection will be present. 39 | - If `epsilon` is small (must be larger than zero) more noise will be added, therefore less information leakage so more privacy protection will be present. 40 | 41 | ### Sequential composition 42 | 43 | When using a data set one tends to issue multiple statistical queries such that one output might be correlated with another. In doing so we reveal more, therefore leak more information so less privacy protection will be present. In order to address this problem, we divide the provided privacy budget into the amount of queries performed creating a query privacy budget. This query privacy budget is then used for each statistic query in order to strengthen the privacy protection. 44 | 45 | ### Parallel composition 46 | 47 | Unlike the pessimistic approach of sequential composition, when using disjoint data sets we assume there isn't any correlation between statistical queries. Therefore, if we have a privacy budget for each query we choose the maximum one and use it for all queries. 48 | 49 | ### Laplace sanitizer 50 | 51 | The Laplace sanitizer is an extension to the Laplace mechanism that is usable if it's possible to decompose categorical data into disjoint/independent subsets (e.g. a histogram or a contingency table). Under these circumstances it's possible to use parallel composition statistical queries. 52 | 53 | ### Python docs 54 | 55 | For a complete API documentation checkout the [python docs][3]. 56 | 57 | ## Examples 58 | 59 | ### Sequential composite queries 60 | 61 | #### Perform mean and variance statistic queries for single data slice 62 | 63 | ```python 64 | import numpy as np 65 | from diffpriv_laplace import DiffPrivSequentialStatisticsQuery, DiffPrivStatisticKind 66 | 67 | 68 | epsilon = 0.1 69 | data = np.array(list(range(0, 20)) + [100.0]) 70 | kinds = DiffPrivStatisticKind.mean | DiffPrivStatisticKind.variance 71 | results = DiffPrivSequentialStatisticsQuery.query(data, kinds, epsilon) 72 | ``` 73 | 74 | #### Perform mean and variance statistic queries for multiple data slices 75 | 76 | ```python 77 | import numpy as np 78 | from diffpriv_laplace import DiffPrivSequentialStatisticsQuery, DiffPrivStatisticKind 79 | 80 | 81 | epsilon = 0.1 82 | data = np.array([list(range(0, 20)) + [100.0]] * 3) 83 | kinds = [DiffPrivStatisticKind.mean | DiffPrivStatisticKind.variance] * 3 84 | results = DiffPrivSequentialStatisticsQuery.query(data, kinds, epsilon, axis=1) 85 | ``` 86 | 87 | ### Parallel composite queries 88 | 89 | #### Perform mean and variance statistic queries for single data slice 90 | 91 | ```python 92 | import numpy as np 93 | from diffpriv_laplace import DiffPrivParallelStatisticsQuery, DiffPrivStatisticKind 94 | 95 | 96 | epsilon = 0.1 97 | data = np.array(list(range(0, 20)) + [100.0]) 98 | kinds = DiffPrivStatisticKind.mean | DiffPrivStatisticKind.variance 99 | results = DiffPrivParallelStatisticsQuery.query(data, kinds, epsilon) 100 | ``` 101 | 102 | #### Perform mean and variance statistic queries for multiple data slices 103 | 104 | ```python 105 | import numpy as np 106 | from diffpriv_laplace import DiffPrivParallelStatisticsQuery, DiffPrivStatisticKind 107 | 108 | 109 | epsilon = 0.1 110 | data = np.array([list(range(0, 20)) + [100.0]] * 3) 111 | kinds = [DiffPrivStatisticKind.mean | DiffPrivStatisticKind.variance] * 3 112 | results = DiffPrivParallelStatisticsQuery.query(data, kinds, epsilon, axis=1) 113 | ``` 114 | 115 | ### Laplace sanitizer queries 116 | 117 | #### Perform categorical anonymized count 118 | 119 | ```python 120 | import numpy as np 121 | from diffpriv_laplace import DiffPrivLaplaceSanitizer 122 | 123 | 124 | epsilon = 0.1 125 | data = np.array([0.01, -0.01, 0.03, -0.001, 0.1] * 2) 126 | 127 | def selector_positive(data): 128 | return data >= 0.0 129 | 130 | def selector_negative(data): 131 | return data < 0.0 132 | 133 | selectors = [selector_positive, selector_negative] 134 | value = DiffPrivLaplaceSanitizer.count(data, selectors, epsilon) 135 | ``` 136 | 137 | ### Statistics 138 | 139 | The anonymized statistics. 140 | 141 | #### Perform anonymized count 142 | 143 | ```python 144 | import numpy as np 145 | from diffpriv_laplace import DiffPrivStatistics 146 | 147 | 148 | epsilon = 0.1 149 | data = np.array([True, False, True, False, True] * 2) 150 | value = DiffPrivStatistics.count(data, epsilon) 151 | ``` 152 | 153 | #### Perform anonymized count with condition function 154 | 155 | ```python 156 | import numpy as np 157 | from diffpriv_laplace import DiffPrivStatistics 158 | 159 | 160 | epsilon = 0.1 161 | data = np.array([0.01, -0.01, 0.03, -0.001, 0.1] * 2) 162 | 163 | def condition(data): 164 | return data >= 0.0 165 | 166 | value = DiffPrivStatistics.count(data, epsilon, condition=condition) 167 | ``` 168 | 169 | #### Perform anonymized min 170 | 171 | ```python 172 | import numpy as np 173 | from diffpriv_laplace import DiffPrivStatistics 174 | 175 | 176 | epsilon = 0.1 177 | data = np.array(list(range(1, 101))) 178 | value = DiffPrivStatistics.min(data, epsilon) 179 | ``` 180 | 181 | #### Perform anonymized max 182 | 183 | ```python 184 | import numpy as np 185 | from diffpriv_laplace import DiffPrivStatistics 186 | 187 | 188 | epsilon = 0.1 189 | data = np.array(list(range(1, 101))) 190 | value = DiffPrivStatistics.max(data, epsilon) 191 | ``` 192 | 193 | #### Perform anonymized median 194 | 195 | ```python 196 | import numpy as np 197 | from diffpriv_laplace import DiffPrivStatistics 198 | 199 | 200 | epsilon = 0.1 201 | data = np.array(list(range(1, 101))) 202 | value = DiffPrivStatistics.median(data, epsilon) 203 | ``` 204 | 205 | #### Perform anonymized proportion 206 | 207 | ```python 208 | import numpy as np 209 | from diffpriv_laplace import DiffPrivStatistics 210 | 211 | 212 | epsilon = 0.1 213 | data = np.array([True, False, True, False, True] * 2) 214 | value = DiffPrivStatistics.proportion(data, epsilon) 215 | ``` 216 | 217 | #### Perform anonymized proportion with condition function 218 | 219 | ```python 220 | import numpy as np 221 | from diffpriv_laplace import DiffPrivStatistics 222 | 223 | 224 | epsilon = 0.1 225 | data = np.array([0.01, -0.01, 0.03, -0.001, 0.1] * 2) 226 | 227 | def condition(data): 228 | return data >= 0.0 229 | 230 | value = DiffPrivStatistics.proportion(data, epsilon, condition=condition) 231 | ``` 232 | 233 | #### Perform anonymized sum 234 | 235 | ```python 236 | import numpy as np 237 | from diffpriv_laplace import DiffPrivStatistics 238 | 239 | 240 | epsilon = 0.1 241 | data = np.array(list(range(1, 101))) 242 | value = DiffPrivStatistics.sum(data, epsilon) 243 | ``` 244 | 245 | #### Perform anonymized mean 246 | 247 | ```python 248 | import numpy as np 249 | from diffpriv_laplace import DiffPrivStatistics 250 | 251 | 252 | epsilon = 0.1 253 | data = np.array(list(range(1, 101))) 254 | value = DiffPrivStatistics.mean(data, epsilon) 255 | ``` 256 | 257 | #### Perform anonymized variance 258 | 259 | ```python 260 | import numpy as np 261 | from diffpriv_laplace import DiffPrivStatistics 262 | 263 | 264 | epsilon = 0.1 265 | data = np.array(list(range(1, 101))) 266 | value = DiffPrivStatistics.variance(data, epsilon) 267 | ``` 268 | 269 | ### Laplace mechanism 270 | 271 | The core Laplace mechanism used to construct the anonymized statistics. 272 | 273 | #### Create an instance with a defined privacy budget 274 | 275 | ```python 276 | from diffpriv_laplace import DiffPrivLaplaceMechanism 277 | 278 | 279 | epsilon = 0.1 280 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 281 | ``` 282 | 283 | #### Anonymize a count value 284 | 285 | ```python 286 | from diffpriv_laplace import DiffPrivLaplaceMechanism 287 | 288 | 289 | epsilon = 0.1 290 | value = 32.0 291 | 292 | # Using the class method 293 | anonymized = DiffPrivLaplaceMechanism.anonymize_count_with_budget(value, epsilon) 294 | 295 | # Using an instance 296 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 297 | anonymized = anonymizer.anonymize_count(value) 298 | ``` 299 | 300 | #### Anonymize a min value 301 | 302 | ```python 303 | from diffpriv_laplace import DiffPrivLaplaceMechanism 304 | 305 | 306 | epsilon = 0.1 307 | value = 32.0 308 | 309 | # Using the class method 310 | anonymized = DiffPrivLaplaceMechanism.anonymize_min_with_budget(value, epsilon) 311 | 312 | # Using an instance 313 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 314 | anonymized = anonymizer.anonymize_min(value) 315 | ``` 316 | 317 | #### Anonymize a max value 318 | 319 | ```python 320 | from diffpriv_laplace import DiffPrivLaplaceMechanism 321 | 322 | 323 | epsilon = 0.1 324 | value = 32.0 325 | 326 | # Using the class method 327 | anonymized = DiffPrivLaplaceMechanism.anonymize_max_with_budget(value, epsilon) 328 | 329 | # Using an instance 330 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 331 | anonymized = anonymizer.anonymize_max(value) 332 | ``` 333 | 334 | #### Anonymize a median value 335 | 336 | ```python 337 | from diffpriv_laplace import DiffPrivLaplaceMechanism 338 | 339 | 340 | epsilon = 0.1 341 | value = 32.0 342 | 343 | # Using the class method 344 | anonymized = DiffPrivLaplaceMechanism.anonymize_median_with_budget(value, epsilon) 345 | 346 | # Using an instance 347 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 348 | anonymized = anonymizer.anonymize_median(value) 349 | ``` 350 | 351 | #### Anonymize a proportion value 352 | 353 | ```python 354 | from diffpriv_laplace import DiffPrivLaplaceMechanism 355 | 356 | 357 | epsilon = 0.1 358 | n = 50.0 359 | value = 32.0 360 | 361 | # Using the class method 362 | anonymized = DiffPrivLaplaceMechanism.anonymize_proportion_with_budget(value, n, epsilon) 363 | 364 | # Using an instance 365 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 366 | anonymized = anonymizer.anonymize_proportion(value, n) 367 | ``` 368 | 369 | #### Anonymize a sum value 370 | 371 | ```python 372 | from diffpriv_laplace import DiffPrivLaplaceMechanism 373 | 374 | 375 | epsilon = 0.1 376 | lower = 0.1 377 | upper = 100.3 378 | value = 32.0 379 | 380 | # Using the class method 381 | anonymized = DiffPrivLaplaceMechanism.anonymize_sum_with_budget(value, lower, upper, epsilon) 382 | 383 | # Using an instance 384 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 385 | anonymized = anonymizer.anonymize_sum(value, lower, upper) 386 | ``` 387 | 388 | #### Anonymize a mean value 389 | 390 | ```python 391 | from diffpriv_laplace import DiffPrivLaplaceMechanism 392 | 393 | 394 | epsilon = 0.1 395 | lower = 0.1 396 | upper = 100.3 397 | n = 50.0 398 | value = 32.0 399 | 400 | # Using the class method 401 | anonymized = DiffPrivLaplaceMechanism.anonymize_mean_with_budget(value, lower, upper, n, epsilon) 402 | 403 | # Using an instance 404 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 405 | anonymized = anonymizer.anonymize_mean(value, lower, upper, n) 406 | ``` 407 | 408 | #### Anonymize a variance value 409 | 410 | ```python 411 | from diffpriv_laplace import DiffPrivLaplaceMechanism 412 | 413 | 414 | epsilon = 0.1 415 | lower = 0.1 416 | upper = 100.3 417 | n = 50.0 418 | value = 32.0 419 | 420 | # Using the class method 421 | anonymized = DiffPrivLaplaceMechanism.anonymize_variance_with_budget(value, lower, upper, n, epsilon) 422 | 423 | # Using an instance 424 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 425 | anonymized = anonymizer.anonymize_variance(value, lower, upper, n) 426 | ``` 427 | 428 | ## Known Issues 429 | 430 | Please open an [issue][4] for anything not on this list! 431 | 432 | ## License 433 | 434 | [MIT][5] 435 | 436 | [1]: https://en.wikipedia.org/wiki/Differential_privacy 437 | [2]: https://en.wikipedia.org/wiki/Differential_privacy#The_Laplace_mechanism 438 | [3]: https://aleph-research.github.io/diff-priv-laplace-python/ 439 | [4]: https://github.com/aleph-research/diff-priv-laplace-python/issues/new 440 | [5]: https://github.com/plaid/aleph-research/diff-priv-laplace-python/blob/master/LICENSE 441 | -------------------------------------------------------------------------------- /diffpriv_laplace/__init__.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.laplace_mechanism import DiffPrivLaplaceMechanism 2 | from diffpriv_laplace.statistics import DiffPrivStatistics, DiffPrivStatisticKind 3 | from diffpriv_laplace.query.sequential_statistics import ( 4 | DiffPrivSequentialStatisticsQuery, 5 | ) 6 | from diffpriv_laplace.query.parallel_statistics import DiffPrivParallelStatisticsQuery 7 | from diffpriv_laplace.laplace_sanitizer import DiffPrivLaplaceSanitizer 8 | from diffpriv_laplace.version import __version__ 9 | 10 | __all__ = [ 11 | "DiffPrivLaplaceMechanism", 12 | "DiffPrivStatistics", 13 | "DiffPrivStatisticKind", 14 | "DiffPrivSequentialStatisticsQuery", 15 | "DiffPrivParallelStatisticsQuery", 16 | "DiffPrivLaplaceSanitizer", 17 | "__version__", 18 | ] 19 | -------------------------------------------------------------------------------- /diffpriv_laplace/anonymizer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/diffpriv_laplace/anonymizer/__init__.py -------------------------------------------------------------------------------- /diffpriv_laplace/anonymizer/base.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class DiffPrivAnonymizer(object): 5 | @classmethod 6 | def calculate_scale(cls, gs, epsilon): 7 | scale = gs.value / epsilon 8 | return scale 9 | 10 | def __init__(self, gs, epsilon): 11 | super().__init__() 12 | self.__gs = gs 13 | self.__epsilon = float(epsilon) 14 | self.__scale = self.calculate_scale(self.__gs, self.__epsilon) 15 | 16 | def apply(self, values, size=None): 17 | samples = np.random.laplace(loc=values, scale=self.__scale, size=size) 18 | return samples 19 | 20 | @property 21 | def scale(self): 22 | return self.__scale 23 | 24 | @property 25 | def global_sensitivity(self): 26 | return self.__gs 27 | 28 | @property 29 | def epsilon(self): 30 | return self.__epsilon 31 | -------------------------------------------------------------------------------- /diffpriv_laplace/anonymizer/count.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.count import CountGlobalSensitivity 2 | from diffpriv_laplace.anonymizer.base import DiffPrivAnonymizer 3 | 4 | 5 | class DiffPrivCountAnonymizer(DiffPrivAnonymizer): 6 | def __init__(self, epsilon): 7 | gs = CountGlobalSensitivity() 8 | super().__init__(gs, epsilon) 9 | -------------------------------------------------------------------------------- /diffpriv_laplace/anonymizer/counting.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.anonymizer.count import DiffPrivCountAnonymizer 2 | 3 | 4 | class DiffPrivCountingAnonymizer(DiffPrivCountAnonymizer): 5 | def __init__(self, epsilon): 6 | super().__init__(epsilon) 7 | -------------------------------------------------------------------------------- /diffpriv_laplace/anonymizer/max.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.max import MaxGlobalSensitivity 2 | from diffpriv_laplace.anonymizer.base import DiffPrivAnonymizer 3 | 4 | 5 | class DiffPrivMaxAnonymizer(DiffPrivAnonymizer): 6 | def __init__(self, epsilon): 7 | gs = MaxGlobalSensitivity() 8 | super().__init__(gs, epsilon) 9 | -------------------------------------------------------------------------------- /diffpriv_laplace/anonymizer/mean.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.mean import MeanGlobalSensitivity 2 | from diffpriv_laplace.anonymizer.base import DiffPrivAnonymizer 3 | 4 | 5 | class DiffPrivMeanAnonymizer(DiffPrivAnonymizer): 6 | def __init__(self, epsilon, lower, upper, n): 7 | gs = MeanGlobalSensitivity(lower, upper, n) 8 | super().__init__(gs, epsilon) 9 | 10 | @property 11 | def lower(self): 12 | return self.global_sensitivity.lower 13 | 14 | @property 15 | def upper(self): 16 | return self.global_sensitivity.upper 17 | 18 | @property 19 | def n(self): 20 | return self.global_sensitivity.n 21 | -------------------------------------------------------------------------------- /diffpriv_laplace/anonymizer/median.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.median import MedianGlobalSensitivity 2 | from diffpriv_laplace.anonymizer.base import DiffPrivAnonymizer 3 | 4 | 5 | class DiffPrivMedianAnonymizer(DiffPrivAnonymizer): 6 | def __init__(self, epsilon): 7 | gs = MedianGlobalSensitivity() 8 | super().__init__(gs, epsilon) 9 | -------------------------------------------------------------------------------- /diffpriv_laplace/anonymizer/min.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.min import MinGlobalSensitivity 2 | from diffpriv_laplace.anonymizer.base import DiffPrivAnonymizer 3 | 4 | 5 | class DiffPrivMinAnonymizer(DiffPrivAnonymizer): 6 | def __init__(self, epsilon): 7 | gs = MinGlobalSensitivity() 8 | super().__init__(gs, epsilon) 9 | -------------------------------------------------------------------------------- /diffpriv_laplace/anonymizer/proportion.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.proportion import ProportionGlobalSensitivity 2 | from diffpriv_laplace.anonymizer.base import DiffPrivAnonymizer 3 | 4 | 5 | class DiffPrivProportionAnonymizer(DiffPrivAnonymizer): 6 | def __init__(self, epsilon, n): 7 | gs = ProportionGlobalSensitivity(n) 8 | super().__init__(gs, epsilon) 9 | 10 | @property 11 | def n(self): 12 | return self.global_sensitivity.n 13 | -------------------------------------------------------------------------------- /diffpriv_laplace/anonymizer/sum.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.sum import SumGlobalSensitivity 2 | from diffpriv_laplace.anonymizer.base import DiffPrivAnonymizer 3 | 4 | 5 | class DiffPrivSumAnonymizer(DiffPrivAnonymizer): 6 | def __init__(self, epsilon, lower, upper): 7 | gs = SumGlobalSensitivity(lower, upper) 8 | super().__init__(gs, epsilon) 9 | 10 | @property 11 | def lower(self): 12 | return self.global_sensitivity.lower 13 | 14 | @property 15 | def upper(self): 16 | return self.global_sensitivity.upper 17 | -------------------------------------------------------------------------------- /diffpriv_laplace/anonymizer/variance.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.variance import VarianceGlobalSensitivity 2 | from diffpriv_laplace.anonymizer.base import DiffPrivAnonymizer 3 | 4 | 5 | class DiffPrivVarianceAnonymizer(DiffPrivAnonymizer): 6 | def __init__(self, epsilon, lower, upper, n): 7 | gs = VarianceGlobalSensitivity(lower, upper, n) 8 | super().__init__(gs, epsilon) 9 | 10 | @property 11 | def lower(self): 12 | return self.global_sensitivity.lower 13 | 14 | @property 15 | def upper(self): 16 | return self.global_sensitivity.upper 17 | 18 | @property 19 | def n(self): 20 | return self.global_sensitivity.n 21 | -------------------------------------------------------------------------------- /diffpriv_laplace/exceptions.py: -------------------------------------------------------------------------------- 1 | class DiffPrivInvalidDimensions(Exception): 2 | pass 3 | 4 | 5 | class DiffPrivSizeMismatch(Exception): 6 | pass 7 | 8 | 9 | class DiffPrivInvalidDecomposition(Exception): 10 | pass 11 | -------------------------------------------------------------------------------- /diffpriv_laplace/global_sensitivity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/diffpriv_laplace/global_sensitivity/__init__.py -------------------------------------------------------------------------------- /diffpriv_laplace/global_sensitivity/base.py: -------------------------------------------------------------------------------- 1 | class GlobalSensitivity(object): 2 | def __init__(self, value): 3 | super().__init__() 4 | self.value = value 5 | 6 | @property 7 | def value(self): 8 | return self.__value 9 | 10 | @value.setter 11 | def value(self, value): 12 | self.__value = value 13 | -------------------------------------------------------------------------------- /diffpriv_laplace/global_sensitivity/count.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.base import GlobalSensitivity 2 | 3 | 4 | class CountGlobalSensitivity(GlobalSensitivity): 5 | def __init__(self): 6 | super().__init__(1.0) 7 | -------------------------------------------------------------------------------- /diffpriv_laplace/global_sensitivity/counting.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.count import CountGlobalSensitivity 2 | 3 | 4 | class CountingGlobalSensitivity(CountGlobalSensitivity): 5 | def __init__(self): 6 | super().__init__() 7 | -------------------------------------------------------------------------------- /diffpriv_laplace/global_sensitivity/max.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.base import GlobalSensitivity 2 | 3 | 4 | class MaxGlobalSensitivity(GlobalSensitivity): 5 | def __init__(self): 6 | super().__init__(1.0) 7 | -------------------------------------------------------------------------------- /diffpriv_laplace/global_sensitivity/mean.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.base import GlobalSensitivity 2 | 3 | 4 | class MeanGlobalSensitivity(GlobalSensitivity): 5 | @classmethod 6 | def calculate_value(cls, lower, upper, n): 7 | value = (upper - lower) / n 8 | return value 9 | 10 | def __init__(self, lower, upper, n): 11 | self.__lower = float(lower) 12 | self.__upper = float(upper) 13 | self.__n = float(n) 14 | super().__init__(self.calculate_value(self.__lower, self.__upper, self.__n)) 15 | 16 | @property 17 | def lower(self): 18 | return self.__lower 19 | 20 | @property 21 | def upper(self): 22 | return self.__upper 23 | 24 | @property 25 | def n(self): 26 | return self.__n 27 | -------------------------------------------------------------------------------- /diffpriv_laplace/global_sensitivity/median.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.base import GlobalSensitivity 2 | 3 | 4 | class MedianGlobalSensitivity(GlobalSensitivity): 5 | def __init__(self): 6 | super().__init__(1.0) 7 | -------------------------------------------------------------------------------- /diffpriv_laplace/global_sensitivity/min.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.base import GlobalSensitivity 2 | 3 | 4 | class MinGlobalSensitivity(GlobalSensitivity): 5 | def __init__(self): 6 | super().__init__(1.0) 7 | -------------------------------------------------------------------------------- /diffpriv_laplace/global_sensitivity/proportion.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.global_sensitivity.base import GlobalSensitivity 2 | 3 | 4 | class ProportionGlobalSensitivity(GlobalSensitivity): 5 | @classmethod 6 | def calculate_value(cls, n): 7 | value = 1.0 / n 8 | return value 9 | 10 | def __init__(self, n): 11 | self.__n = float(n) 12 | super().__init__(self.calculate_value(self.__n)) 13 | 14 | @property 15 | def n(self): 16 | return self.__n 17 | -------------------------------------------------------------------------------- /diffpriv_laplace/global_sensitivity/sum.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from diffpriv_laplace.global_sensitivity.base import GlobalSensitivity 3 | 4 | 5 | class SumGlobalSensitivity(GlobalSensitivity): 6 | @classmethod 7 | def calculate_value(cls, lower, upper): 8 | value = np.max([np.abs(upper), np.abs(lower)]) 9 | return value 10 | 11 | def __init__(self, lower, upper): 12 | self.__lower = float(lower) 13 | self.__upper = float(upper) 14 | super().__init__(self.calculate_value(self.__lower, self.__upper)) 15 | 16 | @property 17 | def lower(self): 18 | return self.__lower 19 | 20 | @property 21 | def upper(self): 22 | return self.__upper 23 | -------------------------------------------------------------------------------- /diffpriv_laplace/global_sensitivity/variance.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from diffpriv_laplace.global_sensitivity.base import GlobalSensitivity 3 | 4 | 5 | class VarianceGlobalSensitivity(GlobalSensitivity): 6 | @classmethod 7 | def calculate_value(cls, lower, upper, n): 8 | value = np.square(upper - lower) / n 9 | return value 10 | 11 | def __init__(self, lower, upper, n): 12 | self.__lower = float(lower) 13 | self.__upper = float(upper) 14 | self.__n = float(n) 15 | super().__init__(self.calculate_value(self.__lower, self.__upper, self.__n)) 16 | 17 | @property 18 | def lower(self): 19 | return self.__lower 20 | 21 | @property 22 | def upper(self): 23 | return self.__upper 24 | 25 | @property 26 | def n(self): 27 | return self.__n 28 | -------------------------------------------------------------------------------- /diffpriv_laplace/laplace_sanitizer.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from diffpriv_laplace.statistics import DiffPrivStatisticKind 3 | from diffpriv_laplace.query.parallel_statistics import DiffPrivParallelStatisticsQuery 4 | from diffpriv_laplace.exceptions import ( 5 | DiffPrivInvalidDimensions, 6 | DiffPrivSizeMismatch, 7 | DiffPrivInvalidDecomposition, 8 | ) 9 | 10 | 11 | class DiffPrivLaplaceSanitizer(object): 12 | """ 13 | Differential privacy Laplace sanitizer. 14 | """ 15 | 16 | @classmethod 17 | def decompose_data_slice(cls, data, selectors): 18 | """ 19 | Decomposes a data slice into a category subsets of independent/disjoint data 20 | derived from the provided `selectors`. 21 | 22 | Parameters 23 | ---------- 24 | data : list|ndarray 25 | The data slice to split. 26 | selectors : list 27 | The list of selector functions to apply to the data such that the data 28 | slice is decomposed into n subsets of the same size, where n is the number 29 | of decomposing functions. Each selector function should return a boolean 30 | `ndarray` or `list` where the value is `True` if it belongs to that category 31 | and `False` otherwise. 32 | 33 | Returns 34 | ------- 35 | ndarray 36 | The decomposed data slice. 37 | 38 | Raises 39 | ------ 40 | DiffPrivInvalidDecomposition 41 | The exception is raised when there are overlapping elements in the 42 | decomposed data slice subsets meaning that there is at least one element 43 | which is present in other subsets. This means that the data slice subsets 44 | are not independent/disjoint with respect to each other which is a 45 | requirement for the Laplace sanitizer. 46 | 47 | """ 48 | n = len(data) 49 | selectors_len = len(selectors) 50 | decomposed = np.array([None] * selectors_len * n) 51 | decomposed = decomposed.reshape((selectors_len, n)) 52 | for index in range(selectors_len): 53 | select = selectors[index] 54 | subset = select(data) 55 | decomposed[index] = np.array(subset) 56 | 57 | if any(decomposed.sum(axis=0) != 1): 58 | raise DiffPrivInvalidDecomposition( 59 | "The constructed subsets are not independent! decomposed = {}".format( 60 | decomposed 61 | ) 62 | ) 63 | 64 | return decomposed 65 | 66 | @classmethod 67 | def constrain_anonymized_counts(cls, counts, n): 68 | """ 69 | Constrain the anonymized counts by a defined quantity. 70 | 71 | Parameters 72 | ---------- 73 | counts : ndarray 74 | The anonymized counts. 75 | n : float 76 | The quantity to constrain the values by. 77 | 78 | Returns 79 | ------- 80 | ndarray 81 | The constrained anonymized counts. 82 | 83 | """ 84 | constrained = (counts / np.sum(counts)) * n 85 | constrained = np.round(constrained) 86 | return constrained 87 | 88 | @classmethod 89 | def count(cls, data, selectors, epsilon, axis=None, postprocess=True): 90 | """ 91 | Performs Laplace sanitizer counting by selecting the data into 92 | independent/disjoint data slice subsets and then applying parallel composition 93 | on each using the defined privacy budget which should correspond to the 94 | maximum budget of all the count queries. This process creates a histogram 95 | for each data slice where the counts correspond to the frequency for each 96 | category. 97 | 98 | Parameters 99 | ---------- 100 | data : list|ndarray 101 | The data to retrieve the anonymized statistic value(s) from. 102 | selectors : list 103 | The list of lists of selector functions to apply to the data such that 104 | each collection of functions in index i is applied to its corresponding 105 | data slice i. If an entry is is `None`, no selecting is performed. 106 | epsilon : list 107 | The privacy budget. 108 | [axis] : int|tuple 109 | Axis or tuple of axes along which to obtain the anonymized statistic 110 | value(s). 111 | [postprocess] : bool 112 | Indicates whether or not to constrain the anonymized count values. 113 | 114 | Returns 115 | ------- 116 | ndarray 117 | The list of anonymized statistics requested subject to the decomposed 118 | data. 119 | 120 | Raises 121 | ------ 122 | DiffPrivInvalidDimensions 123 | The exception is raised when the data dimension is invalid. 124 | DiffPrivSizeMismatch 125 | The exception is raised when the length of the `selectors` list is of 126 | different size than the amount of data slices in `data` defined 127 | through `axis`. 128 | DiffPrivInvalidDecomposition 129 | The exception is raised when there are overlapping elements in the 130 | decomposed data slice subsets meaning that there is at least one element 131 | which is present in other subsets. This means that the data slice subsets 132 | are not independent/disjoint with respect to each other which is a 133 | requirement for the Laplace sanitizer. 134 | 135 | """ 136 | if np.ndim(data) == 1: 137 | data = np.array([data]) 138 | axis = 1 139 | 140 | data_dim = np.ndim(data) 141 | if data_dim != 2: 142 | raise DiffPrivInvalidDimensions( 143 | "Invalid data dimension: {}".format(data_dim) 144 | ) 145 | 146 | if np.ndim(selectors) == 1: 147 | selectors = [selectors] 148 | 149 | shape = np.shape(data) 150 | iter_axis = (axis if axis else 0) - 1 151 | n = shape[iter_axis] 152 | selectors_len = len(selectors) 153 | if n != selectors_len: 154 | raise DiffPrivSizeMismatch( 155 | "Data slices and selectors have different sizes! [{} != {}]".format( 156 | n, selectors_len 157 | ) 158 | ) 159 | 160 | data_slice_len = np.size(data, axis=axis) 161 | results = [None] * selectors_len 162 | for index in range(0, selectors_len): 163 | data_slice_selectors = selectors[index] 164 | if data_slice_selectors and isinstance(data_slice_selectors, list): 165 | data_slice = np.take(data, [index], axis=iter_axis) 166 | data_slice = np.ravel(data_slice) 167 | decomposed = cls.decompose_data_slice(data_slice, data_slice_selectors) 168 | kind = [DiffPrivStatisticKind.count] * len(data_slice_selectors) 169 | result = DiffPrivParallelStatisticsQuery.query( 170 | decomposed, kind, epsilon, axis=1 171 | ) 172 | counts = np.array( 173 | [value[DiffPrivStatisticKind.count] for value in result] 174 | ) 175 | if postprocess: 176 | counts = cls.constrain_anonymized_counts(counts, data_slice_len) 177 | 178 | results[index] = counts 179 | 180 | return results 181 | -------------------------------------------------------------------------------- /diffpriv_laplace/query/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/diffpriv_laplace/query/__init__.py -------------------------------------------------------------------------------- /diffpriv_laplace/query/parallel_statistics.py: -------------------------------------------------------------------------------- 1 | from diffpriv_laplace.statistics import DiffPrivStatistics 2 | 3 | 4 | class DiffPrivParallelStatisticsQuery(object): 5 | """ 6 | The parallel composition statistics query class. 7 | """ 8 | 9 | @classmethod 10 | def query(cls, data, kinds, epsilon, axis=None): 11 | """ 12 | Performs parallel composition by decomposing a multiple statistic queries 13 | into sub-queries (each subset assigned to each data slice) which use the 14 | defined privacy budget which should correspond to the maximum budget of 15 | the queries. 16 | 17 | Parameters 18 | ---------- 19 | data : list|ndarray 20 | The data to retrieve the anonymized statistic value(s) from. 21 | kinds : DiffPrivStatisticKind|list 22 | The kind of statistics to perform on each data slice. If a `None` value 23 | is provided the corresponding statistics calculation for the data slice 24 | is skipped. 25 | epsilon : list 26 | The privacy budget. 27 | [axis] : int|tuple 28 | Axis or tuple of axes along which to obtain the anonymized statistic 29 | value(s). 30 | 31 | Returns 32 | ------- 33 | list 34 | The list of anonymized statistics requested. 35 | 36 | Raises 37 | ------ 38 | DiffPrivInvalidDimensions 39 | The exception is raised when the data dimension is invalid. 40 | DiffPrivSizeMismatch 41 | The exception is raised when the length of the `kind` list is of 42 | different size than the amount of data slices in `data` defined 43 | through `axis`. 44 | 45 | """ 46 | results = DiffPrivStatistics.apply_kind_on_data_slice( 47 | data, kinds, epsilon, axis=axis 48 | ) 49 | return results 50 | -------------------------------------------------------------------------------- /diffpriv_laplace/query/sequential_statistics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from diffpriv_laplace.statistics import DiffPrivStatistics 3 | 4 | 5 | class DiffPrivSequentialStatisticsQuery(object): 6 | """ 7 | The sequential composition statistics query class. 8 | """ 9 | 10 | @classmethod 11 | def query_count(cls, kinds): 12 | """ 13 | Determines the amount of queries to perform. 14 | 15 | Parameters 16 | ---------- 17 | kinds : list 18 | The list of `DiffPrivStatisticKind` to calculate the queries from. 19 | 20 | Returns 21 | ------- 22 | float 23 | The amount of queries to perform. 24 | 25 | """ 26 | query_count = np.sum([kind.size for kind in kinds]) 27 | return float(query_count) 28 | 29 | @classmethod 30 | def calculate_query_epsilon(cls, kinds, epsilon): 31 | """ 32 | Calculates the privacy budget to use for each query. 33 | 34 | Parameters 35 | ---------- 36 | kinds : list 37 | The list of `DiffPrivStatisticKind` to calculate the queries from. 38 | epsilon : float 39 | The privacy budget to calculate the query privacy budget from. 40 | 41 | Returns 42 | ------- 43 | float 44 | The calculated privacy budget to use for each query. 45 | 46 | """ 47 | query_count = cls.query_count(kinds) 48 | query_epsilon = np.divide(epsilon, query_count) 49 | return query_epsilon 50 | 51 | @classmethod 52 | def query(cls, data, kinds, epsilon, axis=None): 53 | """ 54 | Performs sequential composition by decomposing a multiple statistic queries 55 | into sub-queries (each subset assigned to each data slice) which use a 56 | portion of the defined privacy budget. 57 | 58 | Parameters 59 | ---------- 60 | data : list|ndarray 61 | The data to retrieve the anonymized statistic value(s) from. 62 | kinds : DiffPrivStatisticKind|list 63 | The kind of statistics to perform on each data slice. If a `None` value 64 | is provided the corresponding statistics calculation for the data slice 65 | is skipped. 66 | epsilon : float 67 | The privacy budget. 68 | [axis] : int|tuple 69 | Axis or tuple of axes along which to obtain the anonymized statistic 70 | value(s). 71 | 72 | Returns 73 | ------- 74 | list 75 | The list of anonymized statistics requested. 76 | 77 | Raises 78 | ------ 79 | DiffPrivInvalidDimensions 80 | The exception is raised when the data dimension is invalid. 81 | DiffPrivSizeMismatch 82 | The exception is raised when the length of the `kinds` list is of 83 | different size than the amount of data slices in `data` defined 84 | through `axis`. 85 | 86 | """ 87 | query_epsilon = ( 88 | cls.calculate_query_epsilon(kinds, epsilon) 89 | if isinstance(kinds, list) 90 | else epsilon 91 | ) 92 | results = DiffPrivStatistics.apply_kind_on_data_slice( 93 | data, kinds, query_epsilon, axis=axis 94 | ) 95 | return results 96 | -------------------------------------------------------------------------------- /diffpriv_laplace/statistics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from enum import Flag, auto 3 | from diffpriv_laplace import DiffPrivLaplaceMechanism 4 | from diffpriv_laplace.exceptions import DiffPrivInvalidDimensions, DiffPrivSizeMismatch 5 | 6 | 7 | class DiffPrivStatisticKind(Flag): 8 | """ 9 | The kind flag representing each statistic operation. 10 | """ 11 | 12 | count = auto() 13 | min = auto() 14 | max = auto() 15 | median = auto() 16 | proportion = auto() 17 | sum = auto() 18 | mean = auto() 19 | variance = auto() 20 | all = count | min | max | median | proportion | sum | mean | variance 21 | 22 | @property 23 | def size(self): 24 | """ 25 | Calculates the amount of statistic kind in the flag. 26 | 27 | Returns 28 | ------- 29 | int 30 | The total amount of statistic kind in the flag. 31 | 32 | """ 33 | value = 0 34 | if self == DiffPrivStatisticKind.all: 35 | value = len(DiffPrivStatisticKind) - 1 36 | else: 37 | for kind in DiffPrivStatisticKind: 38 | if kind != self.all and bool(self & kind): 39 | value = value + 1 40 | 41 | return value 42 | 43 | 44 | class DiffPrivStatistics(object): 45 | """ 46 | Differential privacy Laplace mechanism statistics. 47 | """ 48 | 49 | @classmethod 50 | def count(cls, data, epsilon, condition=None, axis=None, postprocess=True): 51 | """ 52 | Performs the count operation and anonymizes the value(s) using the provided 53 | privacy budget. 54 | 55 | The operation counts non-zero or non-False values. When a condition 56 | function is provided, it is invoked so that the data can be transformed 57 | before counting. 58 | 59 | Parameters 60 | ---------- 61 | data : list|ndarray 62 | The data to retrieve the count(s) from. 63 | epsilon : float 64 | The privacy budget. 65 | [condition] : function 66 | A condition function that receives the data as a parameter and returns 67 | a list or ndarray of the same size in which for each element is either 68 | a zero or False when it should not be counted and non-zero or True when 69 | it should. 70 | [axis] : int|tuple 71 | Axis or tuple of axes along which to count non-zeros or non-False value(s). 72 | [postprocess] : bool 73 | Indicates whether or not to post-process the count values so that the 74 | returned value is rounded and within the [0, n] range, where n is the total 75 | number of observations. 76 | 77 | Returns 78 | ------- 79 | float|ndarray 80 | The anonymized count(s). 81 | 82 | """ 83 | if condition: 84 | data = condition(data) 85 | 86 | value = np.count_nonzero(data, axis=axis) 87 | anonymized = DiffPrivLaplaceMechanism.anonymize_count_with_budget( 88 | value, epsilon 89 | ) 90 | 91 | if postprocess: 92 | n = np.size(data, axis=axis) 93 | anonymized = np.clip(anonymized, 0.0, n) 94 | anonymized = np.round(anonymized) 95 | 96 | return anonymized 97 | 98 | @classmethod 99 | def min(cls, data, epsilon, axis=None): 100 | """ 101 | Performs the min operation and anonymizes the value(s) using the provided 102 | privacy budget. 103 | 104 | Parameters 105 | ---------- 106 | data : list|ndarray 107 | The data to retrieve the min value(s) from. 108 | epsilon : float 109 | The privacy budget. 110 | [axis] : int|tuple 111 | Axis or tuple of axes along which to obtain the min value(s). 112 | 113 | Returns 114 | ------- 115 | float|ndarray 116 | The anonymized min value(s). 117 | 118 | """ 119 | value = np.min(data, axis=axis) 120 | anonymized = DiffPrivLaplaceMechanism.anonymize_min_with_budget(value, epsilon) 121 | return anonymized 122 | 123 | @classmethod 124 | def max(cls, data, epsilon, axis=None): 125 | """ 126 | Performs the max operation and anonymizes the value(s) using the provided 127 | privacy budget. 128 | 129 | Parameters 130 | ---------- 131 | data : list|ndarray 132 | The data to retrieve the max value(s) from. 133 | epsilon : float 134 | The privacy budget. 135 | [axis] : int|tuple 136 | Axis or tuple of axes along which to obtain the max value(s). 137 | 138 | Returns 139 | ------- 140 | float|ndarray 141 | The anonymized max value(s). 142 | 143 | """ 144 | value = np.max(data, axis=axis) 145 | anonymized = DiffPrivLaplaceMechanism.anonymize_max_with_budget(value, epsilon) 146 | return anonymized 147 | 148 | @classmethod 149 | def median(cls, data, epsilon, axis=None): 150 | """ 151 | Performs the median operation and anonymizes the value(s) using the provided 152 | privacy budget. 153 | 154 | Parameters 155 | ---------- 156 | data : list|ndarray 157 | The data to retrieve the median value(s) from. 158 | epsilon : float 159 | The privacy budget. 160 | [axis] : int|tuple 161 | Axis or tuple of axes along which to obtain the median value(s). 162 | 163 | Returns 164 | ------- 165 | float|ndarray 166 | The anonymized median value(s). 167 | 168 | """ 169 | value = np.median(data, axis=axis) 170 | anonymized = DiffPrivLaplaceMechanism.anonymize_median_with_budget( 171 | value, epsilon 172 | ) 173 | return anonymized 174 | 175 | @classmethod 176 | def proportion(cls, data, epsilon, condition=None, axis=None, postprocess=True): 177 | """ 178 | Performs the proportion operation and anonymizes the value(s) using the 179 | provided privacy budget. 180 | 181 | The operation counts non-zero or non-False values. When a condition 182 | function is provided, it is invoked so that the data can be transformed 183 | before counting. Afterwords, the proportion is calculated using the total 184 | amount of values. 185 | 186 | Parameters 187 | ---------- 188 | data : list|ndarray 189 | The data to retrieve the proportion(s) from. 190 | epsilon : float 191 | The privacy budget. 192 | [condition] : function 193 | A condition function that receives the data as a parameter and returns 194 | a list or ndarray of the same size in which for each element is either 195 | a zero or False when it should not be counted and non-zero or True when 196 | it should. 197 | [axis] : int|tuple 198 | Axis or tuple of axes along which to count non-zeros or non-False value(s). 199 | [postprocess] : bool 200 | Indicates whether or not to post-process the proportion values so that the 201 | returned value is within the [0.0, 1.0] range. 202 | 203 | Returns 204 | ------- 205 | float|ndarray 206 | The anonymized count(s). 207 | 208 | """ 209 | n = np.size(data, axis=axis) 210 | if condition: 211 | data = condition(data) 212 | 213 | value = np.count_nonzero(data, axis=axis) 214 | value = np.divide(value, n) 215 | anonymized = DiffPrivLaplaceMechanism.anonymize_proportion_with_budget( 216 | value, n, epsilon 217 | ) 218 | 219 | if postprocess: 220 | anonymized = np.clip(anonymized, 0.0, 1.0) 221 | 222 | return anonymized 223 | 224 | @classmethod 225 | def sum(cls, data, epsilon, axis=None): 226 | """ 227 | Performs the sum operation and anonymizes the value(s) using the provided 228 | privacy budget. 229 | 230 | Parameters 231 | ---------- 232 | data : list|ndarray 233 | The data to retrieve the sum value(s) from. 234 | epsilon : float 235 | The privacy budget. 236 | [axis] : int|tuple 237 | Axis or tuple of axes along which to obtain the sum value(s). 238 | 239 | Returns 240 | ------- 241 | float|ndarray 242 | The anonymized sum value(s). 243 | 244 | """ 245 | lower = np.min(data, axis=axis) 246 | upper = np.max(data, axis=axis) 247 | value = np.sum(data, axis=axis) 248 | if np.isscalar(lower): 249 | anonymized = DiffPrivLaplaceMechanism.anonymize_sum_with_budget( 250 | value, lower, upper, epsilon 251 | ) 252 | else: 253 | size = lower.size 254 | anonymized = np.zeros(size) 255 | for index in range(0, size): 256 | anonymized[index] = DiffPrivLaplaceMechanism.anonymize_sum_with_budget( 257 | value[index], lower[index], upper[index], epsilon 258 | ) 259 | 260 | return anonymized 261 | 262 | @classmethod 263 | def mean(cls, data, epsilon, axis=None): 264 | """ 265 | Performs the mean operation and anonymizes the value(s) using the provided 266 | privacy budget. 267 | 268 | Parameters 269 | ---------- 270 | data : list|ndarray 271 | The data to retrieve the mean value(s) from. 272 | epsilon : float 273 | The privacy budget. 274 | [axis] : int|tuple 275 | Axis or tuple of axes along which to obtain the mean value(s). 276 | 277 | Returns 278 | ------- 279 | float|ndarray 280 | The anonymized mean value(s). 281 | 282 | """ 283 | n = np.size(data, axis=axis) 284 | lower = np.min(data, axis=axis) 285 | upper = np.max(data, axis=axis) 286 | value = np.mean(data, axis=axis) 287 | if np.isscalar(lower): 288 | anonymized = DiffPrivLaplaceMechanism.anonymize_mean_with_budget( 289 | value, lower, upper, n, epsilon 290 | ) 291 | else: 292 | size = lower.size 293 | anonymized = np.zeros(size) 294 | for index in range(0, size): 295 | mean_value = DiffPrivLaplaceMechanism.anonymize_mean_with_budget( 296 | value[index], lower[index], upper[index], n, epsilon 297 | ) 298 | anonymized[index] = mean_value 299 | 300 | return anonymized 301 | 302 | @classmethod 303 | def variance(cls, data, epsilon, axis=None): 304 | """ 305 | Performs the variance operation and anonymizes the value(s) using the provided 306 | privacy budget. 307 | 308 | Parameters 309 | ---------- 310 | data : list|ndarray 311 | The data to retrieve the variance value(s) from. 312 | epsilon : float 313 | The privacy budget. 314 | [axis] : int|tuple 315 | Axis or tuple of axes along which to obtain the variance value(s). 316 | 317 | Returns 318 | ------- 319 | float|ndarray 320 | The anonymized variance value(s). 321 | 322 | """ 323 | n = np.size(data, axis=axis) 324 | lower = np.min(data, axis=axis) 325 | upper = np.max(data, axis=axis) 326 | value = np.var(data, axis=axis) 327 | if np.isscalar(lower): 328 | anonymized = DiffPrivLaplaceMechanism.anonymize_variance_with_budget( 329 | value, lower, upper, n, epsilon 330 | ) 331 | else: 332 | size = lower.size 333 | anonymized = np.zeros(size) 334 | for index in range(0, size): 335 | var_value = DiffPrivLaplaceMechanism.anonymize_variance_with_budget( 336 | value[index], lower[index], upper[index], n, epsilon 337 | ) 338 | anonymized[index] = var_value 339 | 340 | return anonymized 341 | 342 | @classmethod 343 | def calculate_data_slice_statistics(cls, data_slice, kind, epsilon): 344 | """ 345 | Calculates the statistics for a given data slice using a provided 346 | privacy budget. 347 | 348 | Parameters 349 | ---------- 350 | data : ndarray 351 | The data slice to calculate the anonymized statistic(s) for. 352 | kind : DiffPrivStatisticKind 353 | The kind of statistics to perform on the data slice. 354 | epsilon : float 355 | The privacy budget. 356 | 357 | Returns 358 | ------- 359 | dict 360 | The dictionary of calculated statistics where keys are of type 361 | `DiffPrivStatisticKind` and values are of type `float`. 362 | 363 | """ 364 | stats = {} 365 | if bool(kind & DiffPrivStatisticKind.count): 366 | value = cls.count(data_slice, epsilon) 367 | stats[DiffPrivStatisticKind.count] = value 368 | 369 | if bool(kind & DiffPrivStatisticKind.min): 370 | value = cls.min(data_slice, epsilon) 371 | stats[DiffPrivStatisticKind.min] = value 372 | 373 | if bool(kind & DiffPrivStatisticKind.max): 374 | value = cls.max(data_slice, epsilon) 375 | stats[DiffPrivStatisticKind.max] = value 376 | 377 | if bool(kind & DiffPrivStatisticKind.median): 378 | value = cls.median(data_slice, epsilon) 379 | stats[DiffPrivStatisticKind.median] = value 380 | 381 | if bool(kind & DiffPrivStatisticKind.proportion): 382 | value = cls.proportion(data_slice, epsilon) 383 | stats[DiffPrivStatisticKind.proportion] = value 384 | 385 | if bool(kind & DiffPrivStatisticKind.sum): 386 | value = cls.sum(data_slice, epsilon) 387 | stats[DiffPrivStatisticKind.sum] = value 388 | 389 | if bool(kind & DiffPrivStatisticKind.mean): 390 | value = cls.mean(data_slice, epsilon) 391 | stats[DiffPrivStatisticKind.mean] = value 392 | 393 | if bool(kind & DiffPrivStatisticKind.variance): 394 | value = cls.variance(data_slice, epsilon) 395 | stats[DiffPrivStatisticKind.variance] = value 396 | 397 | return stats 398 | 399 | @classmethod 400 | def apply_kind_on_data_slice(cls, data, kind, epsilon, axis=None): 401 | """ 402 | Performs the statistic operations for its corresponding data slice using the 403 | provided privacy budget. The statistics defined in `kind` at index i is only 404 | applied to data slice i. Therefore, the length of the `kind` list should be 405 | the same as the amount of data slices that are derived from `axis`. 406 | 407 | Parameters 408 | ---------- 409 | data : list|ndarray 410 | The data to retrieve the anonymized statistic(s) from. 411 | kind : DiffPrivStatisticKind|list 412 | The kind of statistics to perform on each data slice. If a `None` value 413 | is provided the corresponding statistics calculation for the data slice 414 | is skipped. 415 | epsilon : float 416 | The privacy budget. 417 | [axis] : int|tuple 418 | Axis or tuple of axes along which to obtain the anonymized statistic 419 | value(s). 420 | 421 | Returns 422 | ------- 423 | list 424 | The list of anonymized statistics requested and calculated for each data 425 | slice. 426 | 427 | Raises 428 | ------ 429 | DiffPrivInvalidDimensions 430 | The exception is raised when the data dimension is invalid. 431 | DiffPrivSizeMismatch 432 | The exception is raised when the length of the `kind` list is of different 433 | size than the amount of data slices in `data` defined through `axis`. 434 | 435 | """ 436 | kinds = kind 437 | if not isinstance(kind, list): 438 | kinds = [kind] 439 | 440 | if np.ndim(data) == 1: 441 | data = np.array([data]) 442 | axis = 1 443 | 444 | data_dim = np.ndim(data) 445 | if data_dim != 2: 446 | raise DiffPrivInvalidDimensions( 447 | "Invalid data dimension: {}".format(data_dim) 448 | ) 449 | 450 | shape = np.shape(data) 451 | iter_axis = (axis if axis else 0) - 1 452 | n = shape[iter_axis] 453 | kind_len = len(kinds) 454 | if n != kind_len: 455 | raise DiffPrivSizeMismatch( 456 | "Data slices and kind have different sizes! [{} != {}]".format( 457 | n, kind_len 458 | ) 459 | ) 460 | 461 | results = [None] * kind_len 462 | for index in range(0, kind_len): 463 | kind = kinds[index] 464 | if kind: 465 | data_slice = np.take(data, [index], axis=iter_axis) 466 | stats = cls.calculate_data_slice_statistics(data_slice, kind, epsilon) 467 | results[index] = stats 468 | 469 | return results 470 | -------------------------------------------------------------------------------- /diffpriv_laplace/version.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.5" 2 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/.nojekyll -------------------------------------------------------------------------------- /docs/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/_static/alabaster.css: -------------------------------------------------------------------------------- 1 | @import url("basic.css"); 2 | 3 | /* -- page layout ----------------------------------------------------------- */ 4 | 5 | body { 6 | font-family: Georgia, serif; 7 | font-size: 17px; 8 | background-color: #fff; 9 | color: #000; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | 15 | div.document { 16 | width: 940px; 17 | margin: 30px auto 0 auto; 18 | } 19 | 20 | div.documentwrapper { 21 | float: left; 22 | width: 100%; 23 | } 24 | 25 | div.bodywrapper { 26 | margin: 0 0 0 220px; 27 | } 28 | 29 | div.sphinxsidebar { 30 | width: 220px; 31 | font-size: 14px; 32 | line-height: 1.5; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #fff; 41 | color: #3E4349; 42 | padding: 0 30px 0 30px; 43 | } 44 | 45 | div.body > .section { 46 | text-align: left; 47 | } 48 | 49 | div.footer { 50 | width: 940px; 51 | margin: 20px auto 30px auto; 52 | font-size: 14px; 53 | color: #888; 54 | text-align: right; 55 | } 56 | 57 | div.footer a { 58 | color: #888; 59 | } 60 | 61 | p.caption { 62 | font-family: inherit; 63 | font-size: inherit; 64 | } 65 | 66 | 67 | div.relations { 68 | display: none; 69 | } 70 | 71 | 72 | div.sphinxsidebar a { 73 | color: #444; 74 | text-decoration: none; 75 | border-bottom: 1px dotted #999; 76 | } 77 | 78 | div.sphinxsidebar a:hover { 79 | border-bottom: 1px solid #999; 80 | } 81 | 82 | div.sphinxsidebarwrapper { 83 | padding: 18px 10px; 84 | } 85 | 86 | div.sphinxsidebarwrapper p.logo { 87 | padding: 0; 88 | margin: -10px 0 0 0px; 89 | text-align: center; 90 | } 91 | 92 | div.sphinxsidebarwrapper h1.logo { 93 | margin-top: -10px; 94 | text-align: center; 95 | margin-bottom: 5px; 96 | text-align: left; 97 | } 98 | 99 | div.sphinxsidebarwrapper h1.logo-name { 100 | margin-top: 0px; 101 | } 102 | 103 | div.sphinxsidebarwrapper p.blurb { 104 | margin-top: 0; 105 | font-style: normal; 106 | } 107 | 108 | div.sphinxsidebar h3, 109 | div.sphinxsidebar h4 { 110 | font-family: Georgia, serif; 111 | color: #444; 112 | font-size: 24px; 113 | font-weight: normal; 114 | margin: 0 0 5px 0; 115 | padding: 0; 116 | } 117 | 118 | div.sphinxsidebar h4 { 119 | font-size: 20px; 120 | } 121 | 122 | div.sphinxsidebar h3 a { 123 | color: #444; 124 | } 125 | 126 | div.sphinxsidebar p.logo a, 127 | div.sphinxsidebar h3 a, 128 | div.sphinxsidebar p.logo a:hover, 129 | div.sphinxsidebar h3 a:hover { 130 | border: none; 131 | } 132 | 133 | div.sphinxsidebar p { 134 | color: #555; 135 | margin: 10px 0; 136 | } 137 | 138 | div.sphinxsidebar ul { 139 | margin: 10px 0; 140 | padding: 0; 141 | color: #000; 142 | } 143 | 144 | div.sphinxsidebar ul li.toctree-l1 > a { 145 | font-size: 120%; 146 | } 147 | 148 | div.sphinxsidebar ul li.toctree-l2 > a { 149 | font-size: 110%; 150 | } 151 | 152 | div.sphinxsidebar input { 153 | border: 1px solid #CCC; 154 | font-family: Georgia, serif; 155 | font-size: 1em; 156 | } 157 | 158 | div.sphinxsidebar hr { 159 | border: none; 160 | height: 1px; 161 | color: #AAA; 162 | background: #AAA; 163 | 164 | text-align: left; 165 | margin-left: 0; 166 | width: 50%; 167 | } 168 | 169 | div.sphinxsidebar .badge { 170 | border-bottom: none; 171 | } 172 | 173 | div.sphinxsidebar .badge:hover { 174 | border-bottom: none; 175 | } 176 | 177 | /* To address an issue with donation coming after search */ 178 | div.sphinxsidebar h3.donation { 179 | margin-top: 10px; 180 | } 181 | 182 | /* -- body styles ----------------------------------------------------------- */ 183 | 184 | a { 185 | color: #004B6B; 186 | text-decoration: underline; 187 | } 188 | 189 | a:hover { 190 | color: #6D4100; 191 | text-decoration: underline; 192 | } 193 | 194 | div.body h1, 195 | div.body h2, 196 | div.body h3, 197 | div.body h4, 198 | div.body h5, 199 | div.body h6 { 200 | font-family: Georgia, serif; 201 | font-weight: normal; 202 | margin: 30px 0px 10px 0px; 203 | padding: 0; 204 | } 205 | 206 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 207 | div.body h2 { font-size: 180%; } 208 | div.body h3 { font-size: 150%; } 209 | div.body h4 { font-size: 130%; } 210 | div.body h5 { font-size: 100%; } 211 | div.body h6 { font-size: 100%; } 212 | 213 | a.headerlink { 214 | color: #DDD; 215 | padding: 0 4px; 216 | text-decoration: none; 217 | } 218 | 219 | a.headerlink:hover { 220 | color: #444; 221 | background: #EAEAEA; 222 | } 223 | 224 | div.body p, div.body dd, div.body li { 225 | line-height: 1.4em; 226 | } 227 | 228 | div.admonition { 229 | margin: 20px 0px; 230 | padding: 10px 30px; 231 | background-color: #EEE; 232 | border: 1px solid #CCC; 233 | } 234 | 235 | div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { 236 | background-color: #FBFBFB; 237 | border-bottom: 1px solid #fafafa; 238 | } 239 | 240 | div.admonition p.admonition-title { 241 | font-family: Georgia, serif; 242 | font-weight: normal; 243 | font-size: 24px; 244 | margin: 0 0 10px 0; 245 | padding: 0; 246 | line-height: 1; 247 | } 248 | 249 | div.admonition p.last { 250 | margin-bottom: 0; 251 | } 252 | 253 | div.highlight { 254 | background-color: #fff; 255 | } 256 | 257 | dt:target, .highlight { 258 | background: #FAF3E8; 259 | } 260 | 261 | div.warning { 262 | background-color: #FCC; 263 | border: 1px solid #FAA; 264 | } 265 | 266 | div.danger { 267 | background-color: #FCC; 268 | border: 1px solid #FAA; 269 | -moz-box-shadow: 2px 2px 4px #D52C2C; 270 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 271 | box-shadow: 2px 2px 4px #D52C2C; 272 | } 273 | 274 | div.error { 275 | background-color: #FCC; 276 | border: 1px solid #FAA; 277 | -moz-box-shadow: 2px 2px 4px #D52C2C; 278 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 279 | box-shadow: 2px 2px 4px #D52C2C; 280 | } 281 | 282 | div.caution { 283 | background-color: #FCC; 284 | border: 1px solid #FAA; 285 | } 286 | 287 | div.attention { 288 | background-color: #FCC; 289 | border: 1px solid #FAA; 290 | } 291 | 292 | div.important { 293 | background-color: #EEE; 294 | border: 1px solid #CCC; 295 | } 296 | 297 | div.note { 298 | background-color: #EEE; 299 | border: 1px solid #CCC; 300 | } 301 | 302 | div.tip { 303 | background-color: #EEE; 304 | border: 1px solid #CCC; 305 | } 306 | 307 | div.hint { 308 | background-color: #EEE; 309 | border: 1px solid #CCC; 310 | } 311 | 312 | div.seealso { 313 | background-color: #EEE; 314 | border: 1px solid #CCC; 315 | } 316 | 317 | div.topic { 318 | background-color: #EEE; 319 | } 320 | 321 | p.admonition-title { 322 | display: inline; 323 | } 324 | 325 | p.admonition-title:after { 326 | content: ":"; 327 | } 328 | 329 | pre, tt, code { 330 | font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 331 | font-size: 0.9em; 332 | } 333 | 334 | .hll { 335 | background-color: #FFC; 336 | margin: 0 -12px; 337 | padding: 0 12px; 338 | display: block; 339 | } 340 | 341 | img.screenshot { 342 | } 343 | 344 | tt.descname, tt.descclassname, code.descname, code.descclassname { 345 | font-size: 0.95em; 346 | } 347 | 348 | tt.descname, code.descname { 349 | padding-right: 0.08em; 350 | } 351 | 352 | img.screenshot { 353 | -moz-box-shadow: 2px 2px 4px #EEE; 354 | -webkit-box-shadow: 2px 2px 4px #EEE; 355 | box-shadow: 2px 2px 4px #EEE; 356 | } 357 | 358 | table.docutils { 359 | border: 1px solid #888; 360 | -moz-box-shadow: 2px 2px 4px #EEE; 361 | -webkit-box-shadow: 2px 2px 4px #EEE; 362 | box-shadow: 2px 2px 4px #EEE; 363 | } 364 | 365 | table.docutils td, table.docutils th { 366 | border: 1px solid #888; 367 | padding: 0.25em 0.7em; 368 | } 369 | 370 | table.field-list, table.footnote { 371 | border: none; 372 | -moz-box-shadow: none; 373 | -webkit-box-shadow: none; 374 | box-shadow: none; 375 | } 376 | 377 | table.footnote { 378 | margin: 15px 0; 379 | width: 100%; 380 | border: 1px solid #EEE; 381 | background: #FDFDFD; 382 | font-size: 0.9em; 383 | } 384 | 385 | table.footnote + table.footnote { 386 | margin-top: -15px; 387 | border-top: none; 388 | } 389 | 390 | table.field-list th { 391 | padding: 0 0.8em 0 0; 392 | } 393 | 394 | table.field-list td { 395 | padding: 0; 396 | } 397 | 398 | table.field-list p { 399 | margin-bottom: 0.8em; 400 | } 401 | 402 | /* Cloned from 403 | * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 404 | */ 405 | .field-name { 406 | -moz-hyphens: manual; 407 | -ms-hyphens: manual; 408 | -webkit-hyphens: manual; 409 | hyphens: manual; 410 | } 411 | 412 | table.footnote td.label { 413 | width: .1px; 414 | padding: 0.3em 0 0.3em 0.5em; 415 | } 416 | 417 | table.footnote td { 418 | padding: 0.3em 0.5em; 419 | } 420 | 421 | dl { 422 | margin: 0; 423 | padding: 0; 424 | } 425 | 426 | dl dd { 427 | margin-left: 30px; 428 | } 429 | 430 | blockquote { 431 | margin: 0 0 0 30px; 432 | padding: 0; 433 | } 434 | 435 | ul, ol { 436 | /* Matches the 30px from the narrow-screen "li > ul" selector below */ 437 | margin: 10px 0 10px 30px; 438 | padding: 0; 439 | } 440 | 441 | pre { 442 | background: #EEE; 443 | padding: 7px 30px; 444 | margin: 15px 0px; 445 | line-height: 1.3em; 446 | } 447 | 448 | div.viewcode-block:target { 449 | background: #ffd; 450 | } 451 | 452 | dl pre, blockquote pre, li pre { 453 | margin-left: 0; 454 | padding-left: 30px; 455 | } 456 | 457 | tt, code { 458 | background-color: #ecf0f3; 459 | color: #222; 460 | /* padding: 1px 2px; */ 461 | } 462 | 463 | tt.xref, code.xref, a tt { 464 | background-color: #FBFBFB; 465 | border-bottom: 1px solid #fff; 466 | } 467 | 468 | a.reference { 469 | text-decoration: none; 470 | border-bottom: 1px dotted #004B6B; 471 | } 472 | 473 | /* Don't put an underline on images */ 474 | a.image-reference, a.image-reference:hover { 475 | border-bottom: none; 476 | } 477 | 478 | a.reference:hover { 479 | border-bottom: 1px solid #6D4100; 480 | } 481 | 482 | a.footnote-reference { 483 | text-decoration: none; 484 | font-size: 0.7em; 485 | vertical-align: top; 486 | border-bottom: 1px dotted #004B6B; 487 | } 488 | 489 | a.footnote-reference:hover { 490 | border-bottom: 1px solid #6D4100; 491 | } 492 | 493 | a:hover tt, a:hover code { 494 | background: #EEE; 495 | } 496 | 497 | 498 | @media screen and (max-width: 870px) { 499 | 500 | div.sphinxsidebar { 501 | display: none; 502 | } 503 | 504 | div.document { 505 | width: 100%; 506 | 507 | } 508 | 509 | div.documentwrapper { 510 | margin-left: 0; 511 | margin-top: 0; 512 | margin-right: 0; 513 | margin-bottom: 0; 514 | } 515 | 516 | div.bodywrapper { 517 | margin-top: 0; 518 | margin-right: 0; 519 | margin-bottom: 0; 520 | margin-left: 0; 521 | } 522 | 523 | ul { 524 | margin-left: 0; 525 | } 526 | 527 | li > ul { 528 | /* Matches the 30px from the "ul, ol" selector above */ 529 | margin-left: 30px; 530 | } 531 | 532 | .document { 533 | width: auto; 534 | } 535 | 536 | .footer { 537 | width: auto; 538 | } 539 | 540 | .bodywrapper { 541 | margin: 0; 542 | } 543 | 544 | .footer { 545 | width: auto; 546 | } 547 | 548 | .github { 549 | display: none; 550 | } 551 | 552 | 553 | 554 | } 555 | 556 | 557 | 558 | @media screen and (max-width: 875px) { 559 | 560 | body { 561 | margin: 0; 562 | padding: 20px 30px; 563 | } 564 | 565 | div.documentwrapper { 566 | float: none; 567 | background: #fff; 568 | } 569 | 570 | div.sphinxsidebar { 571 | display: block; 572 | float: none; 573 | width: 102.5%; 574 | margin: 50px -30px -20px -30px; 575 | padding: 10px 20px; 576 | background: #333; 577 | color: #FFF; 578 | } 579 | 580 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 581 | div.sphinxsidebar h3 a { 582 | color: #fff; 583 | } 584 | 585 | div.sphinxsidebar a { 586 | color: #AAA; 587 | } 588 | 589 | div.sphinxsidebar p.logo { 590 | display: none; 591 | } 592 | 593 | div.document { 594 | width: 100%; 595 | margin: 0; 596 | } 597 | 598 | div.footer { 599 | display: none; 600 | } 601 | 602 | div.bodywrapper { 603 | margin: 0; 604 | } 605 | 606 | div.body { 607 | min-height: 0; 608 | padding: 0; 609 | } 610 | 611 | .rtd_doc_footer { 612 | display: none; 613 | } 614 | 615 | .document { 616 | width: auto; 617 | } 618 | 619 | .footer { 620 | width: auto; 621 | } 622 | 623 | .footer { 624 | width: auto; 625 | } 626 | 627 | .github { 628 | display: none; 629 | } 630 | } 631 | 632 | 633 | /* misc. */ 634 | 635 | .revsys-inline { 636 | display: none!important; 637 | } 638 | 639 | /* Make nested-list/multi-paragraph items look better in Releases changelog 640 | * pages. Without this, docutils' magical list fuckery causes inconsistent 641 | * formatting between different release sub-lists. 642 | */ 643 | div#changelog > div.section > ul > li > p:only-child { 644 | margin-bottom: 0; 645 | } 646 | 647 | /* Hide fugly table cell borders in ..bibliography:: directive output */ 648 | table.docutils.citation, table.docutils.citation td, table.docutils.citation th { 649 | border: none; 650 | /* Below needed in some edge cases; if not applied, bottom shadows appear */ 651 | -moz-box-shadow: none; 652 | -webkit-box-shadow: none; 653 | box-shadow: none; 654 | } 655 | 656 | 657 | /* relbar */ 658 | 659 | .related { 660 | line-height: 30px; 661 | width: 100%; 662 | font-size: 0.9rem; 663 | } 664 | 665 | .related.top { 666 | border-bottom: 1px solid #EEE; 667 | margin-bottom: 20px; 668 | } 669 | 670 | .related.bottom { 671 | border-top: 1px solid #EEE; 672 | } 673 | 674 | .related ul { 675 | padding: 0; 676 | margin: 0; 677 | list-style: none; 678 | } 679 | 680 | .related li { 681 | display: inline; 682 | } 683 | 684 | nav#rellinks { 685 | float: right; 686 | } 687 | 688 | nav#rellinks li+li:before { 689 | content: "|"; 690 | } 691 | 692 | nav#breadcrumbs li+li:before { 693 | content: "\00BB"; 694 | } 695 | 696 | /* Hide certain items when printing */ 697 | @media print { 698 | div.related { 699 | display: none; 700 | } 701 | } -------------------------------------------------------------------------------- /docs/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | /* -- relbar ---------------------------------------------------------------- */ 19 | 20 | div.related { 21 | width: 100%; 22 | font-size: 90%; 23 | } 24 | 25 | div.related h3 { 26 | display: none; 27 | } 28 | 29 | div.related ul { 30 | margin: 0; 31 | padding: 0 0 0 10px; 32 | list-style: none; 33 | } 34 | 35 | div.related li { 36 | display: inline; 37 | } 38 | 39 | div.related li.right { 40 | float: right; 41 | margin-right: 5px; 42 | } 43 | 44 | /* -- sidebar --------------------------------------------------------------- */ 45 | 46 | div.sphinxsidebarwrapper { 47 | padding: 10px 5px 0 10px; 48 | } 49 | 50 | div.sphinxsidebar { 51 | float: left; 52 | width: 230px; 53 | margin-left: -100%; 54 | font-size: 90%; 55 | word-wrap: break-word; 56 | overflow-wrap : break-word; 57 | } 58 | 59 | div.sphinxsidebar ul { 60 | list-style: none; 61 | } 62 | 63 | div.sphinxsidebar ul ul, 64 | div.sphinxsidebar ul.want-points { 65 | margin-left: 20px; 66 | list-style: square; 67 | } 68 | 69 | div.sphinxsidebar ul ul { 70 | margin-top: 0; 71 | margin-bottom: 0; 72 | } 73 | 74 | div.sphinxsidebar form { 75 | margin-top: 10px; 76 | } 77 | 78 | div.sphinxsidebar input { 79 | border: 1px solid #98dbcc; 80 | font-family: sans-serif; 81 | font-size: 1em; 82 | } 83 | 84 | div.sphinxsidebar #searchbox input[type="text"] { 85 | width: 170px; 86 | } 87 | 88 | img { 89 | border: 0; 90 | max-width: 100%; 91 | } 92 | 93 | /* -- search page ----------------------------------------------------------- */ 94 | 95 | ul.search { 96 | margin: 10px 0 0 20px; 97 | padding: 0; 98 | } 99 | 100 | ul.search li { 101 | padding: 5px 0 5px 20px; 102 | background-image: url(file.png); 103 | background-repeat: no-repeat; 104 | background-position: 0 7px; 105 | } 106 | 107 | ul.search li a { 108 | font-weight: bold; 109 | } 110 | 111 | ul.search li div.context { 112 | color: #888; 113 | margin: 2px 0 0 30px; 114 | text-align: left; 115 | } 116 | 117 | ul.keywordmatches li.goodmatch a { 118 | font-weight: bold; 119 | } 120 | 121 | /* -- index page ------------------------------------------------------------ */ 122 | 123 | table.contentstable { 124 | width: 90%; 125 | margin-left: auto; 126 | margin-right: auto; 127 | } 128 | 129 | table.contentstable p.biglink { 130 | line-height: 150%; 131 | } 132 | 133 | a.biglink { 134 | font-size: 1.3em; 135 | } 136 | 137 | span.linkdescr { 138 | font-style: italic; 139 | padding-top: 5px; 140 | font-size: 90%; 141 | } 142 | 143 | /* -- general index --------------------------------------------------------- */ 144 | 145 | table.indextable { 146 | width: 100%; 147 | } 148 | 149 | table.indextable td { 150 | text-align: left; 151 | vertical-align: top; 152 | } 153 | 154 | table.indextable ul { 155 | margin-top: 0; 156 | margin-bottom: 0; 157 | list-style-type: none; 158 | } 159 | 160 | table.indextable > tbody > tr > td > ul { 161 | padding-left: 0em; 162 | } 163 | 164 | table.indextable tr.pcap { 165 | height: 10px; 166 | } 167 | 168 | table.indextable tr.cap { 169 | margin-top: 10px; 170 | background-color: #f2f2f2; 171 | } 172 | 173 | img.toggler { 174 | margin-right: 3px; 175 | margin-top: 3px; 176 | cursor: pointer; 177 | } 178 | 179 | div.modindex-jumpbox { 180 | border-top: 1px solid #ddd; 181 | border-bottom: 1px solid #ddd; 182 | margin: 1em 0 1em 0; 183 | padding: 0.4em; 184 | } 185 | 186 | div.genindex-jumpbox { 187 | border-top: 1px solid #ddd; 188 | border-bottom: 1px solid #ddd; 189 | margin: 1em 0 1em 0; 190 | padding: 0.4em; 191 | } 192 | 193 | /* -- domain module index --------------------------------------------------- */ 194 | 195 | table.modindextable td { 196 | padding: 2px; 197 | border-collapse: collapse; 198 | } 199 | 200 | /* -- general body styles --------------------------------------------------- */ 201 | 202 | div.body p, div.body dd, div.body li, div.body blockquote { 203 | -moz-hyphens: auto; 204 | -ms-hyphens: auto; 205 | -webkit-hyphens: auto; 206 | hyphens: auto; 207 | } 208 | 209 | a.headerlink { 210 | visibility: hidden; 211 | } 212 | 213 | h1:hover > a.headerlink, 214 | h2:hover > a.headerlink, 215 | h3:hover > a.headerlink, 216 | h4:hover > a.headerlink, 217 | h5:hover > a.headerlink, 218 | h6:hover > a.headerlink, 219 | dt:hover > a.headerlink, 220 | caption:hover > a.headerlink, 221 | p.caption:hover > a.headerlink, 222 | div.code-block-caption:hover > a.headerlink { 223 | visibility: visible; 224 | } 225 | 226 | div.body p.caption { 227 | text-align: inherit; 228 | } 229 | 230 | div.body td { 231 | text-align: left; 232 | } 233 | 234 | .first { 235 | margin-top: 0 !important; 236 | } 237 | 238 | p.rubric { 239 | margin-top: 30px; 240 | font-weight: bold; 241 | } 242 | 243 | img.align-left, .figure.align-left, object.align-left { 244 | clear: left; 245 | float: left; 246 | margin-right: 1em; 247 | } 248 | 249 | img.align-right, .figure.align-right, object.align-right { 250 | clear: right; 251 | float: right; 252 | margin-left: 1em; 253 | } 254 | 255 | img.align-center, .figure.align-center, object.align-center { 256 | display: block; 257 | margin-left: auto; 258 | margin-right: auto; 259 | } 260 | 261 | .align-left { 262 | text-align: left; 263 | } 264 | 265 | .align-center { 266 | text-align: center; 267 | } 268 | 269 | .align-right { 270 | text-align: right; 271 | } 272 | 273 | /* -- sidebars -------------------------------------------------------------- */ 274 | 275 | div.sidebar { 276 | margin: 0 0 0.5em 1em; 277 | border: 1px solid #ddb; 278 | padding: 7px 7px 0 7px; 279 | background-color: #ffe; 280 | width: 40%; 281 | float: right; 282 | } 283 | 284 | p.sidebar-title { 285 | font-weight: bold; 286 | } 287 | 288 | /* -- topics ---------------------------------------------------------------- */ 289 | 290 | div.topic { 291 | border: 1px solid #ccc; 292 | padding: 7px 7px 0 7px; 293 | margin: 10px 0 10px 0; 294 | } 295 | 296 | p.topic-title { 297 | font-size: 1.1em; 298 | font-weight: bold; 299 | margin-top: 10px; 300 | } 301 | 302 | /* -- admonitions ----------------------------------------------------------- */ 303 | 304 | div.admonition { 305 | margin-top: 10px; 306 | margin-bottom: 10px; 307 | padding: 7px; 308 | } 309 | 310 | div.admonition dt { 311 | font-weight: bold; 312 | } 313 | 314 | div.admonition dl { 315 | margin-bottom: 0; 316 | } 317 | 318 | p.admonition-title { 319 | margin: 0px 10px 5px 0px; 320 | font-weight: bold; 321 | } 322 | 323 | div.body p.centered { 324 | text-align: center; 325 | margin-top: 25px; 326 | } 327 | 328 | /* -- tables ---------------------------------------------------------------- */ 329 | 330 | table.docutils { 331 | border: 0; 332 | border-collapse: collapse; 333 | } 334 | 335 | table caption span.caption-number { 336 | font-style: italic; 337 | } 338 | 339 | table caption span.caption-text { 340 | } 341 | 342 | table.docutils td, table.docutils th { 343 | padding: 1px 8px 1px 5px; 344 | border-top: 0; 345 | border-left: 0; 346 | border-right: 0; 347 | border-bottom: 1px solid #aaa; 348 | } 349 | 350 | table.footnote td, table.footnote th { 351 | border: 0 !important; 352 | } 353 | 354 | th { 355 | text-align: left; 356 | padding-right: 5px; 357 | } 358 | 359 | table.citation { 360 | border-left: solid 1px gray; 361 | margin-left: 1px; 362 | } 363 | 364 | table.citation td { 365 | border-bottom: none; 366 | } 367 | 368 | /* -- figures --------------------------------------------------------------- */ 369 | 370 | div.figure { 371 | margin: 0.5em; 372 | padding: 0.5em; 373 | } 374 | 375 | div.figure p.caption { 376 | padding: 0.3em; 377 | } 378 | 379 | div.figure p.caption span.caption-number { 380 | font-style: italic; 381 | } 382 | 383 | div.figure p.caption span.caption-text { 384 | } 385 | 386 | /* -- field list styles ----------------------------------------------------- */ 387 | 388 | table.field-list td, table.field-list th { 389 | border: 0 !important; 390 | } 391 | 392 | .field-list ul { 393 | margin: 0; 394 | padding-left: 1em; 395 | } 396 | 397 | .field-list p { 398 | margin: 0; 399 | } 400 | 401 | /* -- other body styles ----------------------------------------------------- */ 402 | 403 | ol.arabic { 404 | list-style: decimal; 405 | } 406 | 407 | ol.loweralpha { 408 | list-style: lower-alpha; 409 | } 410 | 411 | ol.upperalpha { 412 | list-style: upper-alpha; 413 | } 414 | 415 | ol.lowerroman { 416 | list-style: lower-roman; 417 | } 418 | 419 | ol.upperroman { 420 | list-style: upper-roman; 421 | } 422 | 423 | dl { 424 | margin-bottom: 15px; 425 | } 426 | 427 | dd p { 428 | margin-top: 0px; 429 | } 430 | 431 | dd ul, dd table { 432 | margin-bottom: 10px; 433 | } 434 | 435 | dd { 436 | margin-top: 3px; 437 | margin-bottom: 10px; 438 | margin-left: 30px; 439 | } 440 | 441 | dt:target, .highlighted { 442 | background-color: #fbe54e; 443 | } 444 | 445 | dl.glossary dt { 446 | font-weight: bold; 447 | font-size: 1.1em; 448 | } 449 | 450 | .optional { 451 | font-size: 1.3em; 452 | } 453 | 454 | .sig-paren { 455 | font-size: larger; 456 | } 457 | 458 | .versionmodified { 459 | font-style: italic; 460 | } 461 | 462 | .system-message { 463 | background-color: #fda; 464 | padding: 5px; 465 | border: 3px solid red; 466 | } 467 | 468 | .footnote:target { 469 | background-color: #ffa; 470 | } 471 | 472 | .line-block { 473 | display: block; 474 | margin-top: 1em; 475 | margin-bottom: 1em; 476 | } 477 | 478 | .line-block .line-block { 479 | margin-top: 0; 480 | margin-bottom: 0; 481 | margin-left: 1.5em; 482 | } 483 | 484 | .guilabel, .menuselection { 485 | font-family: sans-serif; 486 | } 487 | 488 | .accelerator { 489 | text-decoration: underline; 490 | } 491 | 492 | .classifier { 493 | font-style: oblique; 494 | } 495 | 496 | abbr, acronym { 497 | border-bottom: dotted 1px; 498 | cursor: help; 499 | } 500 | 501 | /* -- code displays --------------------------------------------------------- */ 502 | 503 | pre { 504 | overflow: auto; 505 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 506 | } 507 | 508 | span.pre { 509 | -moz-hyphens: none; 510 | -ms-hyphens: none; 511 | -webkit-hyphens: none; 512 | hyphens: none; 513 | } 514 | 515 | td.linenos pre { 516 | padding: 5px 0px; 517 | border: 0; 518 | background-color: transparent; 519 | color: #aaa; 520 | } 521 | 522 | table.highlighttable { 523 | margin-left: 0.5em; 524 | } 525 | 526 | table.highlighttable td { 527 | padding: 0 0.5em 0 0.5em; 528 | } 529 | 530 | div.code-block-caption { 531 | padding: 2px 5px; 532 | font-size: small; 533 | } 534 | 535 | div.code-block-caption code { 536 | background-color: transparent; 537 | } 538 | 539 | div.code-block-caption + div > div.highlight > pre { 540 | margin-top: 0; 541 | } 542 | 543 | div.code-block-caption span.caption-number { 544 | padding: 0.1em 0.3em; 545 | font-style: italic; 546 | } 547 | 548 | div.code-block-caption span.caption-text { 549 | } 550 | 551 | div.literal-block-wrapper { 552 | padding: 1em 1em 0; 553 | } 554 | 555 | div.literal-block-wrapper div.highlight { 556 | margin: 0; 557 | } 558 | 559 | code.descname { 560 | background-color: transparent; 561 | font-weight: bold; 562 | font-size: 1.2em; 563 | } 564 | 565 | code.descclassname { 566 | background-color: transparent; 567 | } 568 | 569 | code.xref, a code { 570 | background-color: transparent; 571 | font-weight: bold; 572 | } 573 | 574 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 575 | background-color: transparent; 576 | } 577 | 578 | .viewcode-link { 579 | float: right; 580 | } 581 | 582 | .viewcode-back { 583 | float: right; 584 | font-family: sans-serif; 585 | } 586 | 587 | div.viewcode-block:target { 588 | margin: -1px -10px; 589 | padding: 0 10px; 590 | } 591 | 592 | /* -- math display ---------------------------------------------------------- */ 593 | 594 | img.math { 595 | vertical-align: middle; 596 | } 597 | 598 | div.body div.math p { 599 | text-align: center; 600 | } 601 | 602 | span.eqno { 603 | float: right; 604 | } 605 | 606 | span.eqno a.headerlink { 607 | position: relative; 608 | left: 0px; 609 | z-index: 1; 610 | } 611 | 612 | div.math:hover a.headerlink { 613 | visibility: visible; 614 | } 615 | 616 | /* -- printout stylesheet --------------------------------------------------- */ 617 | 618 | @media print { 619 | div.document, 620 | div.documentwrapper, 621 | div.bodywrapper { 622 | margin: 0 !important; 623 | width: 100%; 624 | } 625 | 626 | div.sphinxsidebar, 627 | div.related, 628 | div.footer, 629 | #top-link { 630 | display: none; 631 | } 632 | } -------------------------------------------------------------------------------- /docs/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/_static/comment-close.png -------------------------------------------------------------------------------- /docs/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/_static/comment.png -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* This file intentionally left blank. */ 2 | -------------------------------------------------------------------------------- /docs/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /* 95 | * backward compatibility for jQuery.browser 96 | * This will be supported until firefox bug is fixed. 97 | */ 98 | if (!jQuery.browser) { 99 | jQuery.uaMatch = function(ua) { 100 | ua = ua.toLowerCase(); 101 | 102 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 103 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 104 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 105 | /(msie) ([\w.]+)/.exec(ua) || 106 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 107 | []; 108 | 109 | return { 110 | browser: match[ 1 ] || "", 111 | version: match[ 2 ] || "0" 112 | }; 113 | }; 114 | jQuery.browser = {}; 115 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 116 | } 117 | 118 | /** 119 | * Small JavaScript module for the documentation. 120 | */ 121 | var Documentation = { 122 | 123 | init : function() { 124 | this.fixFirefoxAnchorBug(); 125 | this.highlightSearchWords(); 126 | this.initIndexTable(); 127 | 128 | }, 129 | 130 | /** 131 | * i18n support 132 | */ 133 | TRANSLATIONS : {}, 134 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 135 | LOCALE : 'unknown', 136 | 137 | // gettext and ngettext don't access this so that the functions 138 | // can safely bound to a different name (_ = Documentation.gettext) 139 | gettext : function(string) { 140 | var translated = Documentation.TRANSLATIONS[string]; 141 | if (typeof translated == 'undefined') 142 | return string; 143 | return (typeof translated == 'string') ? translated : translated[0]; 144 | }, 145 | 146 | ngettext : function(singular, plural, n) { 147 | var translated = Documentation.TRANSLATIONS[singular]; 148 | if (typeof translated == 'undefined') 149 | return (n == 1) ? singular : plural; 150 | return translated[Documentation.PLURALEXPR(n)]; 151 | }, 152 | 153 | addTranslations : function(catalog) { 154 | for (var key in catalog.messages) 155 | this.TRANSLATIONS[key] = catalog.messages[key]; 156 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 157 | this.LOCALE = catalog.locale; 158 | }, 159 | 160 | /** 161 | * add context elements like header anchor links 162 | */ 163 | addContextElements : function() { 164 | $('div[id] > :header:first').each(function() { 165 | $('\u00B6'). 166 | attr('href', '#' + this.id). 167 | attr('title', _('Permalink to this headline')). 168 | appendTo(this); 169 | }); 170 | $('dt[id]').each(function() { 171 | $('\u00B6'). 172 | attr('href', '#' + this.id). 173 | attr('title', _('Permalink to this definition')). 174 | appendTo(this); 175 | }); 176 | }, 177 | 178 | /** 179 | * workaround a firefox stupidity 180 | * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 181 | */ 182 | fixFirefoxAnchorBug : function() { 183 | if (document.location.hash) 184 | window.setTimeout(function() { 185 | document.location.href += ''; 186 | }, 10); 187 | }, 188 | 189 | /** 190 | * highlight the search words provided in the url in the text 191 | */ 192 | highlightSearchWords : function() { 193 | var params = $.getQueryParameters(); 194 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 195 | if (terms.length) { 196 | var body = $('div.body'); 197 | if (!body.length) { 198 | body = $('body'); 199 | } 200 | window.setTimeout(function() { 201 | $.each(terms, function() { 202 | body.highlightText(this.toLowerCase(), 'highlighted'); 203 | }); 204 | }, 10); 205 | $('') 207 | .appendTo($('#searchbox')); 208 | } 209 | }, 210 | 211 | /** 212 | * init the domain index toggle buttons 213 | */ 214 | initIndexTable : function() { 215 | var togglers = $('img.toggler').click(function() { 216 | var src = $(this).attr('src'); 217 | var idnum = $(this).attr('id').substr(7); 218 | $('tr.cg-' + idnum).toggle(); 219 | if (src.substr(-9) == 'minus.png') 220 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 221 | else 222 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 223 | }).css('display', ''); 224 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 225 | togglers.click(); 226 | } 227 | }, 228 | 229 | /** 230 | * helper function to hide the search marks again 231 | */ 232 | hideSearchWords : function() { 233 | $('#searchbox .highlight-link').fadeOut(300); 234 | $('span.highlighted').removeClass('highlighted'); 235 | }, 236 | 237 | /** 238 | * make the url absolute 239 | */ 240 | makeURL : function(relativeURL) { 241 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 242 | }, 243 | 244 | /** 245 | * get the current relative url 246 | */ 247 | getCurrentURL : function() { 248 | var path = document.location.pathname; 249 | var parts = path.split(/\//); 250 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 251 | if (this == '..') 252 | parts.pop(); 253 | }); 254 | var url = parts.join('/'); 255 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 256 | }, 257 | 258 | initOnKeyListeners: function() { 259 | $(document).keyup(function(event) { 260 | var activeElementType = document.activeElement.tagName; 261 | // don't navigate when in search box or textarea 262 | if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { 263 | switch (event.keyCode) { 264 | case 37: // left 265 | var prevHref = $('link[rel="prev"]').prop('href'); 266 | if (prevHref) { 267 | window.location.href = prevHref; 268 | return false; 269 | } 270 | case 39: // right 271 | var nextHref = $('link[rel="next"]').prop('href'); 272 | if (nextHref) { 273 | window.location.href = nextHref; 274 | return false; 275 | } 276 | } 277 | } 278 | }); 279 | } 280 | }; 281 | 282 | // quick alias for translations 283 | _ = Documentation.gettext; 284 | 285 | $(document).ready(function() { 286 | Documentation.init(); 287 | }); -------------------------------------------------------------------------------- /docs/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/_static/down.png -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/_static/minus.png -------------------------------------------------------------------------------- /docs/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/_static/plus.png -------------------------------------------------------------------------------- /docs/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ 8 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 9 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 10 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ 11 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 12 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 13 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 14 | .highlight .ge { font-style: italic } /* Generic.Emph */ 15 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 16 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 17 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 18 | .highlight .go { color: #333333 } /* Generic.Output */ 19 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 20 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 21 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 22 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 23 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 24 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 25 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 26 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 27 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 28 | .highlight .kt { color: #902000 } /* Keyword.Type */ 29 | .highlight .m { color: #208050 } /* Literal.Number */ 30 | .highlight .s { color: #4070a0 } /* Literal.String */ 31 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 32 | .highlight .nb { color: #007020 } /* Name.Builtin */ 33 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 34 | .highlight .no { color: #60add5 } /* Name.Constant */ 35 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 36 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 37 | .highlight .ne { color: #007020 } /* Name.Exception */ 38 | .highlight .nf { color: #06287e } /* Name.Function */ 39 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 40 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 41 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 42 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 43 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 44 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 45 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */ 46 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 47 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 48 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 49 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 50 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */ 51 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 52 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 53 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ 54 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 55 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 56 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 57 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 58 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 59 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 60 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 61 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 62 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 63 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 64 | .highlight .fm { color: #06287e } /* Name.Function.Magic */ 65 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 66 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 67 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 68 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ 69 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/_static/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.1 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, 10 | h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= 11 | b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== 12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= 13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= 14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, 17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; 24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, 25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; 26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; 27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), 28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ 29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= 30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= 31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /docs/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/docs/_static/up.png -------------------------------------------------------------------------------- /docs/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Index — Differential Privacy Laplace Python 1.0.5 documentation 11 | 12 | 13 | 14 | 15 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 | 44 | 45 |
46 | 47 | 48 |

Index

49 | 50 |
51 | _ 52 | | A 53 | | C 54 | | D 55 | | E 56 | | M 57 | | P 58 | | Q 59 | | S 60 | | V 61 | 62 |
63 |

_

64 | 65 | 69 | 73 |
74 | 75 |

A

76 | 77 | 95 | 115 |
116 | 117 |

C

118 | 119 | 137 | 153 |
154 | 155 |

D

156 | 157 | 165 | 173 |
174 | 175 |

E

176 | 177 | 181 |
182 | 183 |

M

184 | 185 | 191 | 197 |
198 | 199 |

P

200 | 201 | 205 |
206 | 207 |

Q

208 | 209 | 217 | 221 |
222 | 223 |

S

224 | 225 | 229 |
230 | 231 |

V

232 | 233 | 237 |
238 | 239 | 240 | 241 |
242 | 243 |
244 |
245 | 268 |
269 |
270 | 278 | 279 | 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /docs/search.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Search — Differential Privacy Laplace Python 1.0.5 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 |
49 |
50 | 51 | 52 |
53 | 54 |

Search

55 |
56 | 57 |

58 | Please activate JavaScript to enable the search 59 | functionality. 60 |

61 |
62 |

63 | From here you can search these documents. Enter your search 64 | words into the box below and click "search". Note that the search 65 | function will automatically search for all of the words. Pages 66 | containing fewer words won't appear in the result list. 67 |

68 |
69 | 70 | 71 | 72 |
73 | 74 |
75 | 76 |
77 | 78 |
79 | 80 |
81 |
82 | 92 |
93 |
94 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /docs/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({docnames:["index"],envversion:51,filenames:["index.rst"],objects:{"diffpriv_laplace.DiffPrivLaplaceMechanism":{__init__:[0,1,1,""],__weakref__:[0,2,1,""],anonymize_count:[0,1,1,""],anonymize_count_with_budget:[0,3,1,""],anonymize_max:[0,1,1,""],anonymize_max_with_budget:[0,3,1,""],anonymize_mean:[0,1,1,""],anonymize_mean_with_budget:[0,3,1,""],anonymize_median:[0,1,1,""],anonymize_median_with_budget:[0,3,1,""],anonymize_min:[0,1,1,""],anonymize_min_with_budget:[0,3,1,""],anonymize_proportion:[0,1,1,""],anonymize_proportion_with_budget:[0,3,1,""],anonymize_sum:[0,1,1,""],anonymize_sum_with_budget:[0,3,1,""],anonymize_variance:[0,1,1,""],anonymize_variance_with_budget:[0,3,1,""],create_count_anonymizer:[0,3,1,""],create_counting_anonymizer:[0,3,1,""],create_max_anonymizer:[0,3,1,""],create_mean_anonymizer:[0,3,1,""],create_median_anonymizer:[0,3,1,""],create_min_anonymizer:[0,3,1,""],create_proportion_anonymizer:[0,3,1,""],create_sum_anonymizer:[0,3,1,""],create_variance_anonymizer:[0,3,1,""],epsilon:[0,2,1,""]},"diffpriv_laplace.DiffPrivLaplaceSanitizer":{constrain_anonymized_counts:[0,3,1,""],count:[0,3,1,""],decompose_data_slice:[0,3,1,""]},"diffpriv_laplace.DiffPrivParallelStatisticsQuery":{query:[0,3,1,""]},"diffpriv_laplace.DiffPrivSequentialStatisticsQuery":{calculate_query_epsilon:[0,3,1,""],query:[0,3,1,""],query_count:[0,3,1,""]},"diffpriv_laplace.DiffPrivStatistics":{apply_kind_on_data_slice:[0,3,1,""],calculate_data_slice_statistics:[0,3,1,""],count:[0,3,1,""],max:[0,3,1,""],mean:[0,3,1,""],median:[0,3,1,""],min:[0,3,1,""],proportion:[0,3,1,""],sum:[0,3,1,""],variance:[0,3,1,""]},diffpriv_laplace:{DiffPrivLaplaceMechanism:[0,0,1,""],DiffPrivLaplaceSanitizer:[0,0,1,""],DiffPrivParallelStatisticsQuery:[0,0,1,""],DiffPrivSequentialStatisticsQuery:[0,0,1,""],DiffPrivStatistics:[0,0,1,""]}},objnames:{"0":["py","class","Python class"],"1":["py","method","Python method"],"2":["py","attribute","Python attribute"],"3":["py","classmethod","Python class method"]},objtypes:{"0":"py:class","1":"py:method","2":"py:attribute","3":"py:classmethod"},terms:{"boolean":0,"class":0,"float":0,"function":0,"import":0,"int":0,"return":0,"true":0,The:0,Using:0,__init__:0,__weakref__:0,added:0,adding:0,address:0,afterword:0,all:0,along:0,amount:0,ani:0,anonym:0,anonymize_count:0,anonymize_count_with_budget:0,anonymize_max:0,anonymize_max_with_budget:0,anonymize_mean:0,anonymize_mean_with_budget:0,anonymize_median:0,anonymize_median_with_budget:0,anonymize_min:0,anonymize_min_with_budget:0,anonymize_proport:0,anonymize_proportion_with_budget:0,anonymize_sum:0,anonymize_sum_with_budget:0,anonymize_vari:0,anonymize_variance_with_budget:0,anoth:0,appli:0,apply_kind_on_data_slic:0,approach:0,arrai:0,assign:0,assum:0,axes:0,axi:0,befor:0,belong:0,between:0,bool:0,bound:0,calcul:0,calculate_data_slice_statist:0,calculate_query_epsilon:0,can:0,categor:0,categori:0,choos:0,circumst:0,classmethod:0,collect:0,condit:0,consist:0,constrain:0,constrain_anonymized_count:0,conting:0,correl:0,correspond:0,count:0,creat:0,create_count_anonym:0,create_counting_anonym:0,create_max_anonym:0,create_mean_anonym:0,create_median_anonym:0,create_min_anonym:0,create_proportion_anonym:0,create_sum_anonym:0,create_variance_anonym:0,data:0,data_slic:0,decompos:0,decompose_data_slic:0,def:0,defin:0,deriv:0,determin:0,dict:0,dictionari:0,differ:0,diffpriv_laplac:0,diffprivcountanonym:0,diffprivcountinganonym:0,diffprivinvaliddecomposit:0,diffprivinvaliddimens:0,diffprivlaplacemechan:0,diffprivlaplacesanit:0,diffprivmaxanonym:0,diffprivmeananonym:0,diffprivmediananonym:0,diffprivminanonym:0,diffprivparallelstatisticsqueri:0,diffprivproportionanonym:0,diffprivsequentialstatisticsqueri:0,diffprivsizemismatch:0,diffprivstatist:0,diffprivstatistickind:0,diffprivsumanonym:0,diffprivvarianceanonym:0,dimens:0,disjoint:0,distribut:0,divid:0,doing:0,each:0,either:0,element:0,enough:0,entri:0,epsilon:0,exampl:0,except:0,exist:0,extens:0,fals:0,frequenc:0,from:0,gener:0,given:0,good:0,have:0,histogram:0,how:0,independ:0,indic:0,inform:0,initi:0,instanc:0,invalid:0,invok:0,isn:0,issu:0,its:0,kei:0,kind:0,larg:0,larger:0,leak:0,leakag:0,least:0,length:0,less:0,list:0,lower:0,mani:0,max:0,maximum:0,mean:0,median:0,method:0,might:0,min:0,more:0,much:0,multipl:0,must:0,ndarrai:0,nois:0,non:0,none:0,number:0,numpi:0,object:0,observ:0,obtain:0,one:0,onli:0,oper:0,order:0,other:0,otherwis:0,output:0,overlap:0,paramet:0,perform:0,pessimist:0,portion:0,possibl:0,post:0,postprocess:0,present:0,problem:0,process:0,proport:0,protect:0,provid:0,quantiti:0,queri:0,query_count:0,rais:0,rang:0,receiv:0,refer:0,request:0,requir:0,respect:0,result:0,retriev:0,reveal:0,round:0,said:0,same:0,select:0,selector:0,selector_neg:0,selector_posit:0,set:0,shape:0,should:0,singl:0,size:0,skip:0,slice:0,small:0,split:0,strengthen:0,sub:0,subject:0,subset:0,sum:0,tabl:0,tend:0,than:0,therefor:0,thi:0,through:0,total:0,transform:0,tupl:0,type:0,under:0,unlik:0,upper:0,usabl:0,use:0,used:0,valu:0,varianc:0,weak:0,when:0,where:0,whether:0,which:0,within:0,zero:0},titles:["Differential privacy using Laplace mechanism documentation"],titleterms:{budget:0,composit:0,differenti:0,document:0,index:0,laplac:0,mechan:0,parallel:0,privaci:0,sanit:0,sequenti:0,statist:0,using:0}}) -------------------------------------------------------------------------------- /docs_src/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = DiffPrivLaplace 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs_src/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Differential Privacy Laplace documentation build configuration file, 5 | # created by sphinx-quickstart on Sun Mar 22 14:51:19 2020. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | 20 | import os 21 | import sys 22 | 23 | sys.path.insert(0, os.path.abspath("..")) 24 | import diffpriv_laplace # noqa: E402 25 | 26 | # -- General configuration ------------------------------------------------ 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | # 30 | # needs_sphinx = '1.0' 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon"] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ["_templates"] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = ".rst" 45 | 46 | # The master toctree document. 47 | master_doc = "index" 48 | 49 | # General information about the project. 50 | project = "Differential Privacy Laplace Python" 51 | copyright = "2020, Elmar Langholz" 52 | author = "Elmar Langholz" 53 | 54 | # The version info for the project you're documenting, acts as replacement for 55 | # |version| and |release|, also used in various other places throughout the 56 | # built documents. 57 | # 58 | # The short X.Y version. 59 | version = diffpriv_laplace.version.__version__ 60 | # The full version, including alpha/beta/rc tags. 61 | release = diffpriv_laplace.version.__version__ 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This patterns also effect to html_static_path and html_extra_path 73 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = "sphinx" 77 | 78 | # If true, `todo` and `todoList` produce output, else they produce nothing. 79 | todo_include_todos = False 80 | 81 | 82 | # -- Options for HTML output ---------------------------------------------- 83 | 84 | # The theme to use for HTML and HTML Help pages. See the documentation for 85 | # a list of builtin themes. 86 | # 87 | html_theme = "alabaster" 88 | 89 | # Theme options are theme-specific and customize the look and feel of a theme 90 | # further. For a list of options available for each theme, see the 91 | # documentation. 92 | # 93 | # html_theme_options = {} 94 | 95 | # Add any paths that contain custom static files (such as style sheets) here, 96 | # relative to this directory. They are copied after the builtin static files, 97 | # so a file named "default.css" will overwrite the builtin "default.css". 98 | html_static_path = ["_static"] 99 | 100 | 101 | # -- Options for HTMLHelp output ------------------------------------------ 102 | 103 | # Output file base name for HTML help builder. 104 | htmlhelp_basename = "DiffPrivLaplaceDoc" 105 | html_copy_source = False 106 | 107 | 108 | # -- Options for LaTeX output --------------------------------------------- 109 | 110 | latex_elements = { 111 | # The paper size ('letterpaper' or 'a4paper'). 112 | # 113 | # 'papersize': 'letterpaper', 114 | # The font size ('10pt', '11pt' or '12pt'). 115 | # 116 | # 'pointsize': '10pt', 117 | # Additional stuff for the LaTeX preamble. 118 | # 119 | # 'preamble': '', 120 | # Latex figure (float) alignment 121 | # 122 | # 'figure_align': 'htbp', 123 | } 124 | 125 | # Grouping the document tree into LaTeX files. List of tuples 126 | # (source start file, target name, title, 127 | # author, documentclass [howto, manual, or own class]). 128 | latex_documents = [ 129 | ( 130 | master_doc, 131 | "DiffPrivLaplace.tex", 132 | "Differential Privacy Laplace Documentation", 133 | "Elmar Langholz", 134 | "manual", 135 | ), 136 | ] 137 | 138 | 139 | # -- Options for manual page output --------------------------------------- 140 | 141 | # One entry per manual page. List of tuples 142 | # (source start file, name, description, authors, manual section). 143 | man_pages = [ 144 | ( 145 | master_doc, 146 | "diffpriv_laplace", 147 | "Differential Privacy Laplace Documentation", 148 | [author], 149 | 1, 150 | ) 151 | ] 152 | 153 | 154 | # -- Options for Texinfo output ------------------------------------------- 155 | 156 | # Grouping the document tree into Texinfo files. List of tuples 157 | # (source start file, target name, title, author, 158 | # dir menu entry, description, category) 159 | texinfo_documents = [ 160 | ( 161 | master_doc, 162 | "DiffPrivLaplace", 163 | "Differential Privacy Laplace Documentation", 164 | author, 165 | "DiffPrivLaplace", 166 | "One line description of project.", 167 | "Miscellaneous", 168 | ), 169 | ] 170 | -------------------------------------------------------------------------------- /docs_src/index.rst: -------------------------------------------------------------------------------- 1 | Differential privacy using Laplace mechanism documentation 2 | ========================================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | The Laplace mechanism consists of adding noise, generated through the Laplace distribution 9 | and the privacy budget, to a value. The derived value is said to be "anonymized" if the 10 | privacy budget used is good enough. 11 | 12 | Privacy budget 13 | -------------- 14 | 15 | The privacy budget ``epsilon`` defines how much privacy protection to apply. 16 | 17 | - If ``epsilon`` is large less noise will be added and therefore more information leakage exists, so less privacy protection will be present. 18 | - If ``epsilon`` is small (must be larger than zero) more noise will be added and therefore less information leakage exists, so more privacy protection will be present. 19 | 20 | Sequential composition 21 | ---------------------- 22 | 23 | When using a data set one tends to issue multiple statistical queries such that one output 24 | might be correlated with another. In doing so we reveal more, therefore leak more information 25 | so less privacy protection will be present. In order to address this problem, we divide the 26 | provided privacy budget into the amount of queries performed creating a query privacy budget. 27 | This query privacy budget is then used for each statistic query in order to strengthen the 28 | privacy protection. 29 | 30 | .. currentmodule:: diffpriv_laplace 31 | 32 | .. autoclass:: DiffPrivSequentialStatisticsQuery 33 | :members: 34 | 35 | Examples: 36 | 37 | - Perform mean and variance statistic queries for single data slice: 38 | 39 | .. code-block:: python 40 | 41 | import numpy as np 42 | from diffpriv_laplace import DiffPrivSequentialStatisticsQuery, DiffPrivStatisticKind 43 | 44 | 45 | epsilon = 0.1 46 | data = np.array(list(range(0, 20)) + [100.0]) 47 | kinds = DiffPrivStatisticKind.mean | DiffPrivStatisticKind.variance 48 | results = DiffPrivSequentialStatisticsQuery.query(data, kinds, epsilon) 49 | 50 | - Perform mean and variance statistic queries for multiple data slices: 51 | 52 | .. code-block:: python 53 | 54 | import numpy as np 55 | from diffpriv_laplace import DiffPrivSequentialStatisticsQuery, DiffPrivStatisticKind 56 | 57 | 58 | epsilon = 0.1 59 | data = np.array([list(range(0, 20)) + [100.0]] * 3) 60 | kinds = [DiffPrivStatisticKind.mean | DiffPrivStatisticKind.variance] * 3 61 | results = DiffPrivSequentialStatisticsQuery.query(data, kinds, epsilon, axis=1) 62 | 63 | Parallel composition 64 | -------------------- 65 | 66 | Unlike the pessimistic approach of sequential composition, when using disjoint data sets we 67 | assume there isn't any correlation between statistical queries. Therefore, if we have a privacy 68 | budget for each query we choose the maximum one and use it for all queries. 69 | 70 | .. currentmodule:: diffpriv_laplace 71 | 72 | .. autoclass:: DiffPrivParallelStatisticsQuery 73 | :members: 74 | 75 | Examples: 76 | 77 | - Perform mean and variance statistic queries for single data slice: 78 | 79 | .. code-block:: python 80 | 81 | import numpy as np 82 | from diffpriv_laplace import DiffPrivParallelStatisticsQuery, DiffPrivStatisticKind 83 | 84 | 85 | epsilon = 0.1 86 | data = np.array(list(range(0, 20)) + [100.0]) 87 | kinds = DiffPrivStatisticKind.mean | DiffPrivStatisticKind.variance 88 | results = DiffPrivParallelStatisticsQuery.query(data, kinds, epsilon) 89 | 90 | - Perform mean and variance statistic queries for multiple data slices: 91 | 92 | .. code-block:: python 93 | 94 | import numpy as np 95 | from diffpriv_laplace import DiffPrivParallelStatisticsQuery, DiffPrivStatisticKind 96 | 97 | 98 | epsilon = 0.1 99 | data = np.array([list(range(0, 20)) + [100.0]] * 3) 100 | kinds = [DiffPrivStatisticKind.mean | DiffPrivStatisticKind.variance] * 3 101 | results = DiffPrivParallelStatisticsQuery.query(data, kinds, epsilon, axis=1) 102 | 103 | 104 | Laplace sanitizer 105 | ----------------- 106 | 107 | The Laplace sanitizer is an extension to the Laplace mechanism that is usable if it's possible 108 | to decompose categorical data into disjoint/independent subsets (e.g. a histogram or a 109 | contingency table). Under these circumstances it's possible to use parallel composition statistical 110 | queries. 111 | 112 | .. currentmodule:: diffpriv_laplace 113 | 114 | .. autoclass:: DiffPrivLaplaceSanitizer 115 | :members: 116 | 117 | Examples: 118 | 119 | - Perform categorical anonymized count: 120 | 121 | .. code-block:: python 122 | 123 | import numpy as np 124 | from diffpriv_laplace import DiffPrivLaplaceSanitizer 125 | 126 | 127 | epsilon = 0.1 128 | data = np.array([0.01, -0.01, 0.03, -0.001, 0.1] * 2) 129 | 130 | def selector_positive(data): 131 | return data >= 0.0 132 | 133 | def selector_negative(data): 134 | return data < 0.0 135 | 136 | selectors = [selector_positive, selector_negative] 137 | value = DiffPrivLaplaceSanitizer.count(data, selectors, epsilon) 138 | 139 | Statistics 140 | ---------- 141 | 142 | .. currentmodule:: diffpriv_laplace 143 | 144 | .. autoclass:: DiffPrivStatistics 145 | :members: 146 | 147 | Examples: 148 | 149 | - Perform anonymized count: 150 | 151 | .. code-block:: python 152 | 153 | import numpy as np 154 | from diffpriv_laplace import DiffPrivStatistics 155 | 156 | 157 | epsilon = 0.1 158 | data = np.array([True, False, True, False, True] * 2) 159 | value = DiffPrivStatistics.count(data, epsilon) 160 | 161 | - Perform anonymized count with condition function: 162 | 163 | .. code-block:: python 164 | 165 | import numpy as np 166 | from diffpriv_laplace import DiffPrivStatistics 167 | 168 | 169 | epsilon = 0.1 170 | data = np.array([0.01, -0.01, 0.03, -0.001, 0.1] * 2) 171 | 172 | def condition(data): 173 | return data >= 0.0 174 | 175 | value = DiffPrivStatistics.count(data, epsilon, condition=condition) 176 | 177 | - Perform anonymized min: 178 | 179 | .. code-block:: python 180 | 181 | import numpy as np 182 | from diffpriv_laplace import DiffPrivStatistics 183 | 184 | 185 | epsilon = 0.1 186 | data = np.array(list(range(1, 101))) 187 | value = DiffPrivStatistics.min(data, epsilon) 188 | 189 | - Perform anonymized max: 190 | 191 | .. code-block:: python 192 | 193 | import numpy as np 194 | from diffpriv_laplace import DiffPrivStatistics 195 | 196 | 197 | epsilon = 0.1 198 | data = np.array(list(range(1, 101))) 199 | value = DiffPrivStatistics.max(data, epsilon) 200 | 201 | - Perform anonymized median: 202 | 203 | .. code-block:: python 204 | 205 | import numpy as np 206 | from diffpriv_laplace import DiffPrivStatistics 207 | 208 | 209 | epsilon = 0.1 210 | data = np.array(list(range(1, 101))) 211 | value = DiffPrivStatistics.median(data, epsilon) 212 | 213 | - Perform anonymized proportion: 214 | 215 | .. code-block:: python 216 | 217 | import numpy as np 218 | from diffpriv_laplace import DiffPrivStatistics 219 | 220 | 221 | epsilon = 0.1 222 | data = np.array([True, False, True, False, True] * 2) 223 | value = DiffPrivStatistics.proportion(data, epsilon) 224 | 225 | - Perform anonymized proportion with condition function: 226 | 227 | .. code-block:: python 228 | 229 | import numpy as np 230 | from diffpriv_laplace import DiffPrivStatistics 231 | 232 | 233 | epsilon = 0.1 234 | data = np.array([0.01, -0.01, 0.03, -0.001, 0.1] * 2) 235 | 236 | def condition(data): 237 | return data >= 0.0 238 | 239 | value = DiffPrivStatistics.proportion(data, epsilon, condition=condition) 240 | 241 | - Perform anonymized sum: 242 | 243 | .. code-block:: python 244 | 245 | import numpy as np 246 | from diffpriv_laplace import DiffPrivStatistics 247 | 248 | 249 | epsilon = 0.1 250 | data = np.array(list(range(1, 101))) 251 | value = DiffPrivStatistics.sum(data, epsilon) 252 | 253 | - Perform anonymized mean: 254 | 255 | .. code-block:: python 256 | 257 | import numpy as np 258 | from diffpriv_laplace import DiffPrivStatistics 259 | 260 | 261 | epsilon = 0.1 262 | data = np.array(list(range(1, 101))) 263 | value = DiffPrivStatistics.mean(data, epsilon) 264 | 265 | - Perform anonymized variance: 266 | 267 | .. code-block:: python 268 | 269 | import numpy as np 270 | from diffpriv_laplace import DiffPrivStatistics 271 | 272 | 273 | epsilon = 0.1 274 | data = np.array(list(range(1, 101))) 275 | value = DiffPrivStatistics.variance(data, epsilon) 276 | 277 | Laplace mechanism 278 | ----------------- 279 | 280 | .. currentmodule:: diffpriv_laplace 281 | 282 | .. autoclass:: DiffPrivLaplaceMechanism 283 | :members: 284 | :special-members: 285 | 286 | Examples: 287 | 288 | - Create an instance with a defined privacy budget: 289 | 290 | .. code-block:: python 291 | 292 | from diffpriv_laplace import DiffPrivLaplaceMechanism 293 | 294 | 295 | epsilon = 0.1 296 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 297 | 298 | - Anonymize a count value: 299 | 300 | .. code-block:: python 301 | 302 | from diffpriv_laplace import DiffPrivLaplaceMechanism 303 | 304 | 305 | epsilon = 0.1 306 | value = 32.0 307 | 308 | # Using the class method 309 | anonymized = DiffPrivLaplaceMechanism.anonymize_count_with_budget(value, epsilon) 310 | 311 | # Using an instance 312 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 313 | anonymized = anonymizer.anonymize_count(value) 314 | 315 | 316 | - Anonymize a min value: 317 | 318 | .. code-block:: python 319 | 320 | from diffpriv_laplace import DiffPrivLaplaceMechanism 321 | 322 | 323 | epsilon = 0.1 324 | value = 32.0 325 | 326 | # Using the class method 327 | anonymized = DiffPrivLaplaceMechanism.anonymize_min_with_budget(value, epsilon) 328 | 329 | # Using an instance 330 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 331 | anonymized = anonymizer.anonymize_min(value) 332 | 333 | - Anonymize a max value: 334 | 335 | .. code-block:: python 336 | 337 | from diffpriv_laplace import DiffPrivLaplaceMechanism 338 | 339 | 340 | epsilon = 0.1 341 | value = 32.0 342 | 343 | # Using the class method 344 | anonymized = DiffPrivLaplaceMechanism.anonymize_max_with_budget(value, epsilon) 345 | 346 | # Using an instance 347 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 348 | anonymized = anonymizer.anonymize_max(value) 349 | 350 | -Anonymize a median value: 351 | 352 | .. code-block:: python 353 | 354 | from diffpriv_laplace import DiffPrivLaplaceMechanism 355 | 356 | 357 | epsilon = 0.1 358 | value = 32.0 359 | 360 | # Using the class method 361 | anonymized = DiffPrivLaplaceMechanism.anonymize_median_with_budget(value, epsilon) 362 | 363 | # Using an instance 364 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 365 | anonymized = anonymizer.anonymize_median(value) 366 | 367 | - Anonymize a proportion value: 368 | 369 | .. code-block:: python 370 | 371 | from diffpriv_laplace import DiffPrivLaplaceMechanism 372 | 373 | 374 | epsilon = 0.1 375 | n = 50.0 376 | value = 32.0 377 | 378 | # Using the class method 379 | anonymized = DiffPrivLaplaceMechanism.anonymize_proportion_with_budget(value, n, epsilon) 380 | 381 | # Using an instance 382 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 383 | anonymized = anonymizer.anonymize_proportion(value, n) 384 | 385 | - Anonymize a sum value: 386 | 387 | .. code-block:: python 388 | 389 | from diffpriv_laplace import DiffPrivLaplaceMechanism 390 | 391 | 392 | epsilon = 0.1 393 | lower = 0.1 394 | upper = 100.3 395 | value = 32.0 396 | 397 | # Using the class method 398 | anonymized = DiffPrivLaplaceMechanism.anonymize_sum_with_budget(value, lower, upper, epsilon) 399 | 400 | # Using an instance 401 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 402 | anonymized = anonymizer.anonymize_sum(value, lower, upper) 403 | 404 | - Anonymize a mean value: 405 | 406 | .. code-block:: python 407 | 408 | from diffpriv_laplace import DiffPrivLaplaceMechanism 409 | 410 | 411 | epsilon = 0.1 412 | lower = 0.1 413 | upper = 100.3 414 | n = 50.0 415 | value = 32.0 416 | 417 | # Using the class method 418 | anonymized = DiffPrivLaplaceMechanism.anonymize_mean_with_budget(value, lower, upper, n, epsilon) 419 | 420 | # Using an instance 421 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 422 | anonymized = anonymizer.anonymize_mean(value, lower, upper, n) 423 | 424 | 425 | - Anonymize a variance value: 426 | 427 | .. code-block:: python 428 | 429 | from diffpriv_laplace import DiffPrivLaplaceMechanism 430 | 431 | 432 | epsilon = 0.1 433 | lower = 0.1 434 | upper = 100.3 435 | n = 50.0 436 | value = 32.0 437 | 438 | # Using the class method 439 | anonymized = DiffPrivLaplaceMechanism.anonymize_variance_with_budget(value, lower, upper, n, epsilon) 440 | 441 | # Using an instance 442 | anonymizer = DiffPrivLaplaceMechanism(epsilon) 443 | anonymized = anonymizer.anonymize_variance(value, lower, upper, n) 444 | 445 | Index 446 | ===== 447 | 448 | * :ref:`genindex` 449 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ##### Dependencies 2 | numpy==1.22.0 3 | 4 | ##### DEV Dependencies 5 | cov-core==1.7 6 | coverage==3.7.1 7 | flake8-black==0.1.1 8 | mock==1.0.1 9 | py==1.10.0 10 | pytest==2.9.2 11 | pytest-cov==1.6 12 | sphinx==1.5.2 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from setuptools import setup, find_packages 3 | 4 | with open("diffpriv_laplace/version.py", "r") as f: 5 | version = re.search( 6 | r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE 7 | ).group(1) 8 | 9 | if not version: 10 | raise RuntimeError("Cannot find version information") 11 | 12 | url = "https://github.com/aleph-research/diff-priv-laplace-python" 13 | 14 | setup( 15 | name="diff-priv-laplace-python", 16 | version=version, 17 | description="Python library for Laplace differential privacy", 18 | long_description="", 19 | keywords="laplace, differential, privacy", 20 | author="Elmar Langholz", 21 | author_email="langholz@gmail.com", 22 | url=url, 23 | download_url="{}/tarball/v{}".format(url, version), 24 | license="MIT", 25 | packages=find_packages(exclude="tests"), 26 | package_data={"README": ["README.md"]}, 27 | install_requires=["numpy>=1.18.2"], 28 | zip_safe=False, 29 | include_package_data=True, 30 | classifiers=[ 31 | "Programming Language :: Python", 32 | "Programming Language :: Python :: 3.6", 33 | "Programming Language :: Python :: 3.7", 34 | "Topic :: Software Development :: Libraries :: Python Modules", 35 | ], 36 | ) 37 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/tests/__init__.py -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/anonymizer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/tests/unit/anonymizer/__init__.py -------------------------------------------------------------------------------- /tests/unit/anonymizer/test_anonymizer.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import mock 3 | import numpy as np 4 | from diffpriv_laplace.anonymizer.base import DiffPrivAnonymizer 5 | 6 | 7 | class TestDiffPrivAnonymizer(unittest.TestCase): 8 | def setUp(self): 9 | pass 10 | 11 | def tearDown(self): 12 | pass 13 | 14 | def set_seed(self): 15 | np.random.seed(31337) 16 | 17 | def create_mock_gs(self): 18 | gs = mock.MagicMock() 19 | type(gs).value = mock.PropertyMock(return_value=1.0) 20 | return gs 21 | 22 | def test_global_sensitivity_getter(self): 23 | gs = self.create_mock_gs() 24 | epsilon = 1.0 25 | anonymizer = DiffPrivAnonymizer(gs, epsilon) 26 | self.assertEqual(anonymizer.global_sensitivity, gs) 27 | self.assertEqual(anonymizer.global_sensitivity.value, gs.value) 28 | 29 | def test_epsilon_getter(self): 30 | gs = self.create_mock_gs() 31 | epsilon = 1.0 32 | anonymizer = DiffPrivAnonymizer(gs, epsilon) 33 | self.assertEqual(anonymizer.epsilon, epsilon) 34 | 35 | def test_scale_getter(self): 36 | gs = self.create_mock_gs() 37 | epsilon = 1.0 38 | scale = gs.value / epsilon 39 | anonymizer = DiffPrivAnonymizer(gs, epsilon) 40 | self.assertEqual(anonymizer.scale, scale) 41 | 42 | def test_apply_single_one(self): 43 | expected_value = 87.58645513850368 44 | gs = self.create_mock_gs() 45 | epsilon = 1.0 46 | anonymizer = DiffPrivAnonymizer(gs, epsilon) 47 | self.set_seed() 48 | value = anonymizer.apply(87.0) 49 | np.testing.assert_almost_equal(value, expected_value) 50 | 51 | def test_apply_single_many(self): 52 | expected_values = np.array([87.5864551, 89.701297, 86.4519884]) 53 | gs = self.create_mock_gs() 54 | epsilon = 1.0 55 | anonymizer = DiffPrivAnonymizer(gs, epsilon) 56 | self.set_seed() 57 | values = anonymizer.apply(87.0, size=3) 58 | np.testing.assert_almost_equal(values, expected_values) 59 | 60 | def test_apply_multiple(self): 61 | expected_values = np.array([87.5864551, 437.701297]) 62 | gs = self.create_mock_gs() 63 | epsilon = 1.0 64 | anonymizer = DiffPrivAnonymizer(gs, epsilon) 65 | self.set_seed() 66 | values = anonymizer.apply([87.0, 435.0]) 67 | np.testing.assert_almost_equal(values, expected_values) 68 | -------------------------------------------------------------------------------- /tests/unit/anonymizer/test_count.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace.anonymizer.count import DiffPrivCountAnonymizer 4 | 5 | 6 | class TestDiffPrivCountAnonymizer(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def set_seed(self): 14 | np.random.seed(31337) 15 | 16 | def test_global_sensitivity_getter(self): 17 | epsilon = 1.0 18 | anonymizer = DiffPrivCountAnonymizer(epsilon) 19 | self.assertEqual(anonymizer.global_sensitivity.value, 1.0) 20 | 21 | def test_epsilon_getter(self): 22 | epsilon = 1.0 23 | anonymizer = DiffPrivCountAnonymizer(epsilon) 24 | self.assertEqual(anonymizer.epsilon, epsilon) 25 | 26 | def test_scale_getter(self): 27 | epsilon = 1.0 28 | scale = 1.0 / epsilon 29 | anonymizer = DiffPrivCountAnonymizer(epsilon) 30 | self.assertEqual(anonymizer.scale, scale) 31 | 32 | def test_apply_single(self): 33 | expected_value = 87.58645513850368 34 | epsilon = 1.0 35 | anonymizer = DiffPrivCountAnonymizer(epsilon) 36 | self.set_seed() 37 | value = anonymizer.apply(87.0) 38 | np.testing.assert_almost_equal(value, expected_value) 39 | 40 | def test_apply_single_many(self): 41 | expected_values = np.array([87.5864551, 89.701297, 86.4519884]) 42 | epsilon = 1.0 43 | anonymizer = DiffPrivCountAnonymizer(epsilon) 44 | self.set_seed() 45 | values = anonymizer.apply(87.0, size=3) 46 | np.testing.assert_almost_equal(values, expected_values) 47 | 48 | def test_apply_multiple(self): 49 | expected_values = np.array([87.5864551, 437.701297]) 50 | epsilon = 1.0 51 | anonymizer = DiffPrivCountAnonymizer(epsilon) 52 | self.set_seed() 53 | values = anonymizer.apply([87.0, 435.0]) 54 | np.testing.assert_almost_equal(values, expected_values) 55 | -------------------------------------------------------------------------------- /tests/unit/anonymizer/test_counting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace.anonymizer.counting import DiffPrivCountingAnonymizer 4 | 5 | 6 | class TestDiffPrivCountingAnonymizer(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def set_seed(self): 14 | np.random.seed(31337) 15 | 16 | def test_global_sensitivity_getter(self): 17 | epsilon = 1.0 18 | anonymizer = DiffPrivCountingAnonymizer(epsilon) 19 | self.assertEqual(anonymizer.global_sensitivity.value, 1.0) 20 | 21 | def test_epsilon_getter(self): 22 | epsilon = 1.0 23 | anonymizer = DiffPrivCountingAnonymizer(epsilon) 24 | self.assertEqual(anonymizer.epsilon, epsilon) 25 | 26 | def test_scale_getter(self): 27 | epsilon = 1.0 28 | scale = 1.0 / epsilon 29 | anonymizer = DiffPrivCountingAnonymizer(epsilon) 30 | self.assertEqual(anonymizer.scale, scale) 31 | 32 | def test_apply_single(self): 33 | expected_value = 87.58645513850368 34 | epsilon = 1.0 35 | anonymizer = DiffPrivCountingAnonymizer(epsilon) 36 | self.set_seed() 37 | value = anonymizer.apply(87.0) 38 | np.testing.assert_almost_equal(value, expected_value) 39 | 40 | def test_apply_single_many(self): 41 | expected_values = np.array([87.5864551, 89.701297, 86.4519884]) 42 | epsilon = 1.0 43 | anonymizer = DiffPrivCountingAnonymizer(epsilon) 44 | self.set_seed() 45 | values = anonymizer.apply(87.0, size=3) 46 | np.testing.assert_almost_equal(values, expected_values) 47 | 48 | def test_apply_multiple(self): 49 | expected_values = np.array([87.5864551, 437.701297]) 50 | epsilon = 1.0 51 | anonymizer = DiffPrivCountingAnonymizer(epsilon) 52 | self.set_seed() 53 | values = anonymizer.apply([87.0, 435.0]) 54 | np.testing.assert_almost_equal(values, expected_values) 55 | -------------------------------------------------------------------------------- /tests/unit/anonymizer/test_max.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace.anonymizer.max import DiffPrivMaxAnonymizer 4 | 5 | 6 | class TestDiffPrivMaxAnonymizer(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def set_seed(self): 14 | np.random.seed(31337) 15 | 16 | def test_global_sensitivity_getter(self): 17 | epsilon = 1.0 18 | anonymizer = DiffPrivMaxAnonymizer(epsilon) 19 | self.assertEqual(anonymizer.global_sensitivity.value, 1.0) 20 | 21 | def test_epsilon_getter(self): 22 | epsilon = 1.0 23 | anonymizer = DiffPrivMaxAnonymizer(epsilon) 24 | self.assertEqual(anonymizer.epsilon, epsilon) 25 | 26 | def test_scale_getter(self): 27 | epsilon = 1.0 28 | scale = 1.0 / epsilon 29 | anonymizer = DiffPrivMaxAnonymizer(epsilon) 30 | self.assertEqual(anonymizer.scale, scale) 31 | 32 | def test_apply_single(self): 33 | expected_value = 87.58645513850368 34 | epsilon = 1.0 35 | anonymizer = DiffPrivMaxAnonymizer(epsilon) 36 | self.set_seed() 37 | value = anonymizer.apply(87.0) 38 | np.testing.assert_almost_equal(value, expected_value) 39 | 40 | def test_apply_single_many(self): 41 | expected_values = np.array([87.5864551, 89.701297, 86.4519884]) 42 | epsilon = 1.0 43 | anonymizer = DiffPrivMaxAnonymizer(epsilon) 44 | self.set_seed() 45 | values = anonymizer.apply(87.0, size=3) 46 | np.testing.assert_almost_equal(values, expected_values) 47 | 48 | def test_apply_multiple(self): 49 | expected_values = np.array([87.5864551, 437.701297]) 50 | epsilon = 1.0 51 | anonymizer = DiffPrivMaxAnonymizer(epsilon) 52 | self.set_seed() 53 | values = anonymizer.apply([87.0, 435.0]) 54 | np.testing.assert_almost_equal(values, expected_values) 55 | -------------------------------------------------------------------------------- /tests/unit/anonymizer/test_mean.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace.anonymizer.mean import DiffPrivMeanAnonymizer 4 | 5 | 6 | class TestDiffPrivMeanAnonymizer(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def set_seed(self): 14 | np.random.seed(31337) 15 | 16 | def test_length_getter(self): 17 | lower = 10.0 18 | upper = 99.0 19 | n = 100.0 20 | epsilon = 1.0 21 | anonymizer = DiffPrivMeanAnonymizer(epsilon, lower, upper, n) 22 | self.assertEqual(anonymizer.n, n) 23 | 24 | def test_lower_getter(self): 25 | lower = 10.0 26 | upper = 99.0 27 | n = 100.0 28 | epsilon = 1.0 29 | anonymizer = DiffPrivMeanAnonymizer(epsilon, lower, upper, n) 30 | self.assertEqual(anonymizer.lower, lower) 31 | 32 | def test_upper_getter(self): 33 | lower = 10.0 34 | upper = 99.0 35 | n = 100.0 36 | epsilon = 1.0 37 | anonymizer = DiffPrivMeanAnonymizer(epsilon, lower, upper, n) 38 | self.assertEqual(anonymizer.upper, upper) 39 | 40 | def test_global_sensitivity_getter(self): 41 | lower = 10.0 42 | upper = 99.0 43 | n = 100.0 44 | epsilon = 1.0 45 | gs_value = (upper - lower) / n 46 | anonymizer = DiffPrivMeanAnonymizer(epsilon, lower, upper, n) 47 | np.testing.assert_almost_equal(anonymizer.global_sensitivity.value, gs_value) 48 | 49 | def test_epsilon_getter(self): 50 | lower = 10.0 51 | upper = 99.0 52 | n = 100.0 53 | epsilon = 1.0 54 | anonymizer = DiffPrivMeanAnonymizer(epsilon, lower, upper, n) 55 | self.assertEqual(anonymizer.epsilon, epsilon) 56 | 57 | def test_scale_getter(self): 58 | lower = 10.0 59 | upper = 99.0 60 | n = 100.0 61 | epsilon = 1.0 62 | gs_value = (upper - lower) / n 63 | scale = gs_value / epsilon 64 | anonymizer = DiffPrivMeanAnonymizer(epsilon, lower, upper, n) 65 | self.assertEqual(anonymizer.scale, scale) 66 | 67 | def test_apply_single(self): 68 | expected_value = 87.52194507326827 69 | lower = 10.0 70 | upper = 99.0 71 | n = 100.0 72 | epsilon = 1.0 73 | anonymizer = DiffPrivMeanAnonymizer(epsilon, lower, upper, n) 74 | self.set_seed() 75 | value = anonymizer.apply(87.0) 76 | np.testing.assert_almost_equal(value, expected_value) 77 | 78 | def test_apply_single_many(self): 79 | expected_values = np.array([87.5219451, 89.4041544, 86.5122696]) 80 | lower = 10.0 81 | upper = 99.0 82 | n = 100.0 83 | epsilon = 1.0 84 | anonymizer = DiffPrivMeanAnonymizer(epsilon, lower, upper, n) 85 | self.set_seed() 86 | values = anonymizer.apply(87.0, size=3) 87 | np.testing.assert_almost_equal(values, expected_values) 88 | 89 | def test_apply_multiple(self): 90 | expected_values = np.array([87.5219451, 437.4041544]) 91 | lower = 10.0 92 | upper = 99.0 93 | n = 100.0 94 | epsilon = 1.0 95 | anonymizer = DiffPrivMeanAnonymizer(epsilon, lower, upper, n) 96 | self.set_seed() 97 | values = anonymizer.apply([87.0, 435.0]) 98 | np.testing.assert_almost_equal(values, expected_values) 99 | -------------------------------------------------------------------------------- /tests/unit/anonymizer/test_median.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace.anonymizer.median import DiffPrivMedianAnonymizer 4 | 5 | 6 | class TestDiffPrivMedianAnonymizer(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def set_seed(self): 14 | np.random.seed(31337) 15 | 16 | def test_global_sensitivity_getter(self): 17 | epsilon = 1.0 18 | anonymizer = DiffPrivMedianAnonymizer(epsilon) 19 | self.assertEqual(anonymizer.global_sensitivity.value, 1.0) 20 | 21 | def test_epsilon_getter(self): 22 | epsilon = 1.0 23 | anonymizer = DiffPrivMedianAnonymizer(epsilon) 24 | self.assertEqual(anonymizer.epsilon, epsilon) 25 | 26 | def test_scale_getter(self): 27 | epsilon = 1.0 28 | scale = 1.0 / epsilon 29 | anonymizer = DiffPrivMedianAnonymizer(epsilon) 30 | self.assertEqual(anonymizer.scale, scale) 31 | 32 | def test_apply_single(self): 33 | expected_value = 87.58645513850368 34 | epsilon = 1.0 35 | anonymizer = DiffPrivMedianAnonymizer(epsilon) 36 | self.set_seed() 37 | value = anonymizer.apply(87.0) 38 | np.testing.assert_almost_equal(value, expected_value) 39 | 40 | def test_apply_single_many(self): 41 | expected_values = np.array([87.5864551, 89.701297, 86.4519884]) 42 | epsilon = 1.0 43 | anonymizer = DiffPrivMedianAnonymizer(epsilon) 44 | self.set_seed() 45 | values = anonymizer.apply(87.0, size=3) 46 | np.testing.assert_almost_equal(values, expected_values) 47 | 48 | def test_apply_multiple(self): 49 | expected_values = np.array([87.5864551, 437.701297]) 50 | epsilon = 1.0 51 | anonymizer = DiffPrivMedianAnonymizer(epsilon) 52 | self.set_seed() 53 | values = anonymizer.apply([87.0, 435.0]) 54 | np.testing.assert_almost_equal(values, expected_values) 55 | -------------------------------------------------------------------------------- /tests/unit/anonymizer/test_min.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace.anonymizer.min import DiffPrivMinAnonymizer 4 | 5 | 6 | class TestDiffPrivMinAnonymizer(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def set_seed(self): 14 | np.random.seed(31337) 15 | 16 | def test_global_sensitivity_getter(self): 17 | epsilon = 1.0 18 | anonymizer = DiffPrivMinAnonymizer(epsilon) 19 | self.assertEqual(anonymizer.global_sensitivity.value, 1.0) 20 | 21 | def test_epsilon_getter(self): 22 | epsilon = 1.0 23 | anonymizer = DiffPrivMinAnonymizer(epsilon) 24 | self.assertEqual(anonymizer.epsilon, epsilon) 25 | 26 | def test_scale_getter(self): 27 | epsilon = 1.0 28 | scale = 1.0 / epsilon 29 | anonymizer = DiffPrivMinAnonymizer(epsilon) 30 | self.assertEqual(anonymizer.scale, scale) 31 | 32 | def test_apply_single(self): 33 | expected_value = 87.58645513850368 34 | epsilon = 1.0 35 | anonymizer = DiffPrivMinAnonymizer(epsilon) 36 | self.set_seed() 37 | value = anonymizer.apply(87.0) 38 | np.testing.assert_almost_equal(value, expected_value) 39 | 40 | def test_apply_single_many(self): 41 | expected_values = np.array([87.5864551, 89.701297, 86.4519884]) 42 | epsilon = 1.0 43 | anonymizer = DiffPrivMinAnonymizer(epsilon) 44 | self.set_seed() 45 | values = anonymizer.apply(87.0, size=3) 46 | np.testing.assert_almost_equal(values, expected_values) 47 | 48 | def test_apply_multiple(self): 49 | expected_values = np.array([87.5864551, 437.701297]) 50 | epsilon = 1.0 51 | anonymizer = DiffPrivMinAnonymizer(epsilon) 52 | self.set_seed() 53 | values = anonymizer.apply([87.0, 435.0]) 54 | np.testing.assert_almost_equal(values, expected_values) 55 | -------------------------------------------------------------------------------- /tests/unit/anonymizer/test_proportion.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace.anonymizer.proportion import DiffPrivProportionAnonymizer 4 | 5 | 6 | class TestDiffPrivProportionAnonymizer(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def set_seed(self): 14 | np.random.seed(31337) 15 | 16 | def test_length_getter(self): 17 | n = 10.0 18 | epsilon = 1.0 19 | anonymizer = DiffPrivProportionAnonymizer(epsilon, n) 20 | self.assertEqual(anonymizer.n, n) 21 | 22 | def test_global_sensitivity_getter(self): 23 | n = 10.0 24 | epsilon = 1.0 25 | gs_value = 1.0 / n 26 | anonymizer = DiffPrivProportionAnonymizer(epsilon, n) 27 | np.testing.assert_almost_equal(anonymizer.global_sensitivity.value, gs_value) 28 | 29 | def test_epsilon_getter(self): 30 | n = 10.0 31 | epsilon = 1.0 32 | anonymizer = DiffPrivProportionAnonymizer(epsilon, n) 33 | self.assertEqual(anonymizer.epsilon, epsilon) 34 | 35 | def test_scale_getter(self): 36 | n = 10.0 37 | gs_value = 1.0 / n 38 | epsilon = 1.0 39 | scale = gs_value / epsilon 40 | anonymizer = DiffPrivProportionAnonymizer(epsilon, n) 41 | self.assertEqual(anonymizer.scale, scale) 42 | 43 | def test_apply_single(self): 44 | expected_value = 87.05864551385037 45 | n = 10.0 46 | epsilon = 1.0 47 | anonymizer = DiffPrivProportionAnonymizer(epsilon, n) 48 | self.set_seed() 49 | value = anonymizer.apply(87.0) 50 | np.testing.assert_almost_equal(value, expected_value) 51 | 52 | def test_apply_single_many(self): 53 | expected_values = np.array([87.0586455, 87.2701297, 86.9451988]) 54 | n = 10.0 55 | epsilon = 1.0 56 | anonymizer = DiffPrivProportionAnonymizer(epsilon, n) 57 | self.set_seed() 58 | values = anonymizer.apply(87.0, size=3) 59 | np.testing.assert_almost_equal(values, expected_values) 60 | 61 | def test_apply_multiple(self): 62 | expected_values = np.array([87.0586455, 435.2701297]) 63 | n = 10.0 64 | epsilon = 1.0 65 | anonymizer = DiffPrivProportionAnonymizer(epsilon, n) 66 | self.set_seed() 67 | values = anonymizer.apply([87.0, 435.0]) 68 | np.testing.assert_almost_equal(values, expected_values) 69 | -------------------------------------------------------------------------------- /tests/unit/anonymizer/test_sum.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace.anonymizer.sum import DiffPrivSumAnonymizer 4 | 5 | 6 | class TestDiffPrivSumAnonymizer(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def set_seed(self): 14 | np.random.seed(31337) 15 | 16 | def test_lower_getter(self): 17 | lower = 10.0 18 | upper = 99.0 19 | epsilon = 1.0 20 | anonymizer = DiffPrivSumAnonymizer(epsilon, lower, upper) 21 | self.assertEqual(anonymizer.lower, lower) 22 | 23 | def test_upper_getter(self): 24 | lower = 10.0 25 | upper = 99.0 26 | epsilon = 1.0 27 | anonymizer = DiffPrivSumAnonymizer(epsilon, lower, upper) 28 | self.assertEqual(anonymizer.upper, upper) 29 | 30 | def test_global_sensitivity_getter(self): 31 | lower = 10.0 32 | upper = 99.0 33 | epsilon = 1.0 34 | gs_value = np.max([np.abs(lower), np.abs(upper)]) 35 | anonymizer = DiffPrivSumAnonymizer(epsilon, lower, upper) 36 | np.testing.assert_almost_equal(anonymizer.global_sensitivity.value, gs_value) 37 | 38 | def test_epsilon_getter(self): 39 | lower = 10.0 40 | upper = 99.0 41 | epsilon = 1.0 42 | anonymizer = DiffPrivSumAnonymizer(epsilon, lower, upper) 43 | self.assertEqual(anonymizer.epsilon, epsilon) 44 | 45 | def test_scale_getter(self): 46 | lower = 10.0 47 | upper = 99.0 48 | epsilon = 1.0 49 | gs_value = np.max([np.abs(lower), np.abs(upper)]) 50 | scale = gs_value / epsilon 51 | anonymizer = DiffPrivSumAnonymizer(epsilon, lower, upper) 52 | self.assertEqual(anonymizer.scale, scale) 53 | 54 | def test_apply_single(self): 55 | expected_value = 145.05905871186388 56 | lower = 10.0 57 | upper = 99.0 58 | epsilon = 1.0 59 | anonymizer = DiffPrivSumAnonymizer(epsilon, lower, upper) 60 | self.set_seed() 61 | value = anonymizer.apply(87.0) 62 | np.testing.assert_almost_equal(value, expected_value) 63 | 64 | def test_apply_single_many(self): 65 | expected_values = np.array([145.0590587, 354.4284063, 32.746848]) 66 | lower = 10.0 67 | upper = 99.0 68 | epsilon = 1.0 69 | anonymizer = DiffPrivSumAnonymizer(epsilon, lower, upper) 70 | self.set_seed() 71 | values = anonymizer.apply(87.0, size=3) 72 | np.testing.assert_almost_equal(values, expected_values) 73 | 74 | def test_apply_multiple(self): 75 | expected_values = np.array([145.0590587, 702.4284063]) 76 | lower = 10.0 77 | upper = 99.0 78 | epsilon = 1.0 79 | anonymizer = DiffPrivSumAnonymizer(epsilon, lower, upper) 80 | self.set_seed() 81 | values = anonymizer.apply([87.0, 435.0]) 82 | np.testing.assert_almost_equal(values, expected_values) 83 | -------------------------------------------------------------------------------- /tests/unit/anonymizer/test_variance.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace.anonymizer.variance import DiffPrivVarianceAnonymizer 4 | 5 | 6 | class TestDiffPrivVarianceAnonymizer(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def set_seed(self): 14 | np.random.seed(31337) 15 | 16 | def test_length_getter(self): 17 | lower = 10.0 18 | upper = 99.0 19 | n = 100.0 20 | epsilon = 1.0 21 | anonymizer = DiffPrivVarianceAnonymizer(epsilon, lower, upper, n) 22 | self.assertEqual(anonymizer.n, n) 23 | 24 | def test_lower_getter(self): 25 | lower = 10.0 26 | upper = 99.0 27 | n = 100.0 28 | epsilon = 1.0 29 | anonymizer = DiffPrivVarianceAnonymizer(epsilon, lower, upper, n) 30 | self.assertEqual(anonymizer.lower, lower) 31 | 32 | def test_upper_getter(self): 33 | lower = 10.0 34 | upper = 99.0 35 | n = 100.0 36 | epsilon = 1.0 37 | anonymizer = DiffPrivVarianceAnonymizer(epsilon, lower, upper, n) 38 | self.assertEqual(anonymizer.upper, upper) 39 | 40 | def test_global_sensitivity_getter(self): 41 | lower = 10.0 42 | upper = 99.0 43 | n = 100.0 44 | epsilon = 1.0 45 | gs_value = np.square(upper - lower) / n 46 | anonymizer = DiffPrivVarianceAnonymizer(epsilon, lower, upper, n) 47 | np.testing.assert_almost_equal(anonymizer.global_sensitivity.value, gs_value) 48 | 49 | def test_epsilon_getter(self): 50 | lower = 10.0 51 | upper = 99.0 52 | n = 100.0 53 | epsilon = 1.0 54 | anonymizer = DiffPrivVarianceAnonymizer(epsilon, lower, upper, n) 55 | self.assertEqual(anonymizer.epsilon, epsilon) 56 | 57 | def test_scale_getter(self): 58 | lower = 10.0 59 | upper = 99.0 60 | n = 100.0 61 | epsilon = 1.0 62 | gs_value = np.square(upper - lower) / n 63 | scale = gs_value / epsilon 64 | anonymizer = DiffPrivVarianceAnonymizer(epsilon, lower, upper, n) 65 | self.assertEqual(anonymizer.scale, scale) 66 | 67 | def test_apply_single(self): 68 | expected_value = 133.45311152087612 69 | lower = 10.0 70 | upper = 99.0 71 | n = 100.0 72 | epsilon = 1.0 73 | anonymizer = DiffPrivVarianceAnonymizer(epsilon, lower, upper, n) 74 | self.set_seed() 75 | value = anonymizer.apply(87.0) 76 | np.testing.assert_almost_equal(value, expected_value) 77 | 78 | def test_apply_single_many(self): 79 | expected_values = np.array([133.4531115, 300.969738, 43.5919983]) 80 | lower = 10.0 81 | upper = 99.0 82 | n = 100.0 83 | epsilon = 1.0 84 | anonymizer = DiffPrivVarianceAnonymizer(epsilon, lower, upper, n) 85 | self.set_seed() 86 | values = anonymizer.apply(87.0, size=3) 87 | np.testing.assert_almost_equal(values, expected_values) 88 | 89 | def test_apply_multiple(self): 90 | expected_values = np.array([133.4531115, 648.969738]) 91 | lower = 10.0 92 | upper = 99.0 93 | n = 100.0 94 | epsilon = 1.0 95 | anonymizer = DiffPrivVarianceAnonymizer(epsilon, lower, upper, n) 96 | self.set_seed() 97 | values = anonymizer.apply([87.0, 435.0]) 98 | np.testing.assert_almost_equal(values, expected_values) 99 | -------------------------------------------------------------------------------- /tests/unit/global_sensitivity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/tests/unit/global_sensitivity/__init__.py -------------------------------------------------------------------------------- /tests/unit/global_sensitivity/test_count.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from diffpriv_laplace.global_sensitivity.count import CountGlobalSensitivity 3 | 4 | 5 | class TestCountGlobalSensitivity(unittest.TestCase): 6 | def setUp(self): 7 | pass 8 | 9 | def tearDown(self): 10 | pass 11 | 12 | def test_value(self): 13 | gs = CountGlobalSensitivity() 14 | self.assertEqual(gs.value, 1.0) 15 | -------------------------------------------------------------------------------- /tests/unit/global_sensitivity/test_counting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from diffpriv_laplace.global_sensitivity.counting import CountingGlobalSensitivity 3 | 4 | 5 | class TestCountingGlobalSensitivity(unittest.TestCase): 6 | def setUp(self): 7 | pass 8 | 9 | def tearDown(self): 10 | pass 11 | 12 | def test_value(self): 13 | gs = CountingGlobalSensitivity() 14 | self.assertEqual(gs.value, 1.0) 15 | -------------------------------------------------------------------------------- /tests/unit/global_sensitivity/test_global_sensitivity.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from diffpriv_laplace.global_sensitivity.base import GlobalSensitivity 3 | 4 | 5 | class TestGlobalSensitivity(unittest.TestCase): 6 | def setUp(self): 7 | pass 8 | 9 | def tearDown(self): 10 | pass 11 | 12 | def test_getter(self): 13 | value = 5 14 | gs = GlobalSensitivity(value) 15 | self.assertEqual(gs.value, value) 16 | 17 | def test_setter(self): 18 | value = 5 19 | gs = GlobalSensitivity(value) 20 | new_value = 6 21 | gs.value = new_value 22 | self.assertEqual(gs.value, new_value) 23 | -------------------------------------------------------------------------------- /tests/unit/global_sensitivity/test_max.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from diffpriv_laplace.global_sensitivity.max import MaxGlobalSensitivity 3 | 4 | 5 | class TestMaxGlobalSensitivity(unittest.TestCase): 6 | def setUp(self): 7 | pass 8 | 9 | def tearDown(self): 10 | pass 11 | 12 | def test_value(self): 13 | gs = MaxGlobalSensitivity() 14 | self.assertEqual(gs.value, 1.0) 15 | -------------------------------------------------------------------------------- /tests/unit/global_sensitivity/test_mean.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from diffpriv_laplace.global_sensitivity.mean import MeanGlobalSensitivity 3 | 4 | 5 | class TestMeanGlobalSensitivity(unittest.TestCase): 6 | def setUp(self): 7 | pass 8 | 9 | def tearDown(self): 10 | pass 11 | 12 | def test_value(self): 13 | lower = 10.0 14 | upper = 99.0 15 | n = 100.0 16 | value = (upper - lower) / n 17 | gs = MeanGlobalSensitivity(lower, upper, n) 18 | self.assertEqual(gs.value, value) 19 | 20 | def test_length_getter(self): 21 | lower = 10.0 22 | upper = 99.0 23 | n = 100.0 24 | gs = MeanGlobalSensitivity(lower, upper, n) 25 | self.assertEqual(gs.n, n) 26 | 27 | def test_lower_getter(self): 28 | lower = 10.0 29 | upper = 99.0 30 | n = 100.0 31 | gs = MeanGlobalSensitivity(lower, upper, n) 32 | self.assertEqual(gs.lower, lower) 33 | 34 | def test_upper_getter(self): 35 | lower = 10.0 36 | upper = 99.0 37 | n = 100.0 38 | gs = MeanGlobalSensitivity(lower, upper, n) 39 | self.assertEqual(gs.upper, upper) 40 | -------------------------------------------------------------------------------- /tests/unit/global_sensitivity/test_median.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from diffpriv_laplace.global_sensitivity.median import MedianGlobalSensitivity 3 | 4 | 5 | class TestMedianGlobalSensitivity(unittest.TestCase): 6 | def setUp(self): 7 | pass 8 | 9 | def tearDown(self): 10 | pass 11 | 12 | def test_value(self): 13 | gs = MedianGlobalSensitivity() 14 | self.assertEqual(gs.value, 1.0) 15 | -------------------------------------------------------------------------------- /tests/unit/global_sensitivity/test_min.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from diffpriv_laplace.global_sensitivity.min import MinGlobalSensitivity 3 | 4 | 5 | class TestMinGlobalSensitivity(unittest.TestCase): 6 | def setUp(self): 7 | pass 8 | 9 | def tearDown(self): 10 | pass 11 | 12 | def test_value(self): 13 | gs = MinGlobalSensitivity() 14 | self.assertEqual(gs.value, 1.0) 15 | -------------------------------------------------------------------------------- /tests/unit/global_sensitivity/test_proportion.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from diffpriv_laplace.global_sensitivity.proportion import ProportionGlobalSensitivity 3 | 4 | 5 | class TestProportionGlobalSensitivity(unittest.TestCase): 6 | def setUp(self): 7 | pass 8 | 9 | def tearDown(self): 10 | pass 11 | 12 | def test_value(self): 13 | n = 100.0 14 | value = 1.0 / n 15 | gs = ProportionGlobalSensitivity(n) 16 | self.assertEqual(gs.value, value) 17 | 18 | def test_length_getter(self): 19 | n = 100.0 20 | gs = ProportionGlobalSensitivity(n) 21 | self.assertEqual(gs.n, n) 22 | -------------------------------------------------------------------------------- /tests/unit/global_sensitivity/test_sum.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace.global_sensitivity.sum import SumGlobalSensitivity 4 | 5 | 6 | class TestSumGlobalSensitivity(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def test_value(self): 14 | lower = 10.0 15 | upper = 99.0 16 | value = np.max([np.abs(lower), np.abs(upper)]) 17 | gs = SumGlobalSensitivity(lower, upper) 18 | self.assertEqual(gs.value, value) 19 | 20 | def test_lower_getter(self): 21 | lower = 10.0 22 | upper = 99.0 23 | gs = SumGlobalSensitivity(lower, upper) 24 | self.assertEqual(gs.lower, lower) 25 | 26 | def test_upper_getter(self): 27 | lower = 10.0 28 | upper = 99.0 29 | gs = SumGlobalSensitivity(lower, upper) 30 | self.assertEqual(gs.upper, upper) 31 | -------------------------------------------------------------------------------- /tests/unit/global_sensitivity/test_variance.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace.global_sensitivity.variance import VarianceGlobalSensitivity 4 | 5 | 6 | class TestDiffPrivQuery(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def test_value(self): 14 | lower = 10.0 15 | upper = 99.0 16 | n = 100.0 17 | value = np.square(upper - lower) / n 18 | gs = VarianceGlobalSensitivity(lower, upper, n) 19 | self.assertEqual(gs.value, value) 20 | 21 | def test_length_getter(self): 22 | lower = 10.0 23 | upper = 99.0 24 | n = 100.0 25 | gs = VarianceGlobalSensitivity(lower, upper, n) 26 | self.assertEqual(gs.n, n) 27 | 28 | def test_lower_getter(self): 29 | lower = 10.0 30 | upper = 99.0 31 | n = 100.0 32 | gs = VarianceGlobalSensitivity(lower, upper, n) 33 | self.assertEqual(gs.lower, lower) 34 | 35 | def test_upper_getter(self): 36 | lower = 10.0 37 | upper = 99.0 38 | n = 100.0 39 | gs = VarianceGlobalSensitivity(lower, upper, n) 40 | self.assertEqual(gs.upper, upper) 41 | -------------------------------------------------------------------------------- /tests/unit/query/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleph-research/diff-priv-laplace-python/6bbdd57a8f6b588994f94dadacc4664b148db042/tests/unit/query/__init__.py -------------------------------------------------------------------------------- /tests/unit/query/test_parallel_statistics.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace import DiffPrivParallelStatisticsQuery, DiffPrivStatisticKind 4 | 5 | 6 | class TestDiffPrivParallelStatisticsQuery(unittest.TestCase): 7 | epsilon = 100000000 8 | decimal_places = 2 9 | 10 | def setUp(self): 11 | pass 12 | 13 | def tearDown(self): 14 | pass 15 | 16 | def set_seed(self): 17 | np.random.seed(31337) 18 | 19 | def calculate_stats(self, data, axis=None): 20 | stats = { 21 | DiffPrivStatisticKind.count: np.count_nonzero(data, axis=axis), 22 | DiffPrivStatisticKind.min: np.min(data, axis=axis), 23 | DiffPrivStatisticKind.max: np.max(data, axis=axis), 24 | DiffPrivStatisticKind.median: np.median(data, axis=axis), 25 | DiffPrivStatisticKind.proportion: np.divide( 26 | np.count_nonzero(data, axis=axis), np.size(data, axis=axis) 27 | ), 28 | DiffPrivStatisticKind.sum: np.sum(data, axis=axis), 29 | DiffPrivStatisticKind.mean: np.mean(data, axis=axis), 30 | DiffPrivStatisticKind.variance: np.var(data, axis=axis), 31 | } 32 | return stats 33 | 34 | def test_query_single(self): 35 | data = np.array(list(range(0, 20)) + [100.0]) 36 | kinds = DiffPrivStatisticKind.all 37 | expected_results = [self.calculate_stats(data)] 38 | self.set_seed() 39 | results = DiffPrivParallelStatisticsQuery.query(data, kinds, self.epsilon) 40 | self.assertEqual(len(results), len(expected_results)) 41 | for index in range(len(expected_results)): 42 | result = results[index] 43 | expected_result = expected_results[index] 44 | self.assertEqual(len(result), len(expected_result)) 45 | for key, value in expected_result.items(): 46 | value = result[key] 47 | expected_value = expected_result[key] 48 | self.assertAlmostEqual(value, expected_value, self.decimal_places) 49 | 50 | def test_query_single_with_axis_0(self): 51 | data = np.array(list(range(0, 20)) + [100.0]) 52 | kinds = DiffPrivStatisticKind.all 53 | expected_results = [self.calculate_stats(data)] 54 | self.set_seed() 55 | results = DiffPrivParallelStatisticsQuery.query( 56 | data, kinds, self.epsilon, axis=0 57 | ) 58 | self.assertEqual(len(results), len(expected_results)) 59 | for index in range(len(expected_results)): 60 | result = results[index] 61 | expected_result = expected_results[index] 62 | self.assertEqual(len(result), len(expected_result)) 63 | for key, value in expected_result.items(): 64 | value = result[key] 65 | expected_value = expected_result[key] 66 | self.assertAlmostEqual(value, expected_value, self.decimal_places) 67 | 68 | def test_query_single_with_axis_1(self): 69 | data = np.array(list(range(0, 20)) + [100.0]) 70 | kinds = DiffPrivStatisticKind.all 71 | expected_results = [self.calculate_stats(data)] 72 | data = np.transpose(data) 73 | self.set_seed() 74 | results = DiffPrivParallelStatisticsQuery.query( 75 | data, kinds, self.epsilon, axis=1 76 | ) 77 | self.assertEqual(len(results), len(expected_results)) 78 | for index in range(len(expected_results)): 79 | result = results[index] 80 | expected_result = expected_results[index] 81 | self.assertEqual(len(result), len(expected_result)) 82 | for key, value in expected_result.items(): 83 | value = result[key] 84 | expected_value = expected_result[key] 85 | self.assertAlmostEqual(value, expected_value, self.decimal_places) 86 | 87 | def test_query_multiple_axis_0(self): 88 | data = np.array([list(range(0, 20)) + [100.0]] * 3) 89 | kinds = [DiffPrivStatisticKind.all] * 3 90 | expected_results = [ 91 | self.calculate_stats(data[0]), 92 | self.calculate_stats(data[1]), 93 | self.calculate_stats(data[2]), 94 | ] 95 | data = np.transpose(data) 96 | self.set_seed() 97 | results = DiffPrivParallelStatisticsQuery.query( 98 | data, kinds, self.epsilon, axis=0 99 | ) 100 | self.assertEqual(len(results), len(expected_results)) 101 | for index in range(len(expected_results)): 102 | result = results[index] 103 | expected_result = expected_results[index] 104 | self.assertEqual(len(result), len(expected_result)) 105 | for key, value in expected_result.items(): 106 | value = result[key] 107 | expected_value = expected_result[key] 108 | self.assertAlmostEqual(value, expected_value, self.decimal_places) 109 | 110 | def test_query_multiple_axis_1(self): 111 | data = np.array([list(range(0, 20)) + [100.0]] * 3) 112 | kinds = [DiffPrivStatisticKind.all] * 3 113 | expected_results = [ 114 | self.calculate_stats(data[0]), 115 | self.calculate_stats(data[1]), 116 | self.calculate_stats(data[2]), 117 | ] 118 | self.set_seed() 119 | results = DiffPrivParallelStatisticsQuery.query( 120 | data, kinds, self.epsilon, axis=1 121 | ) 122 | self.assertEqual(len(results), len(expected_results)) 123 | for index in range(len(expected_results)): 124 | result = results[index] 125 | expected_result = expected_results[index] 126 | self.assertEqual(len(result), len(expected_result)) 127 | for key, value in expected_result.items(): 128 | value = result[key] 129 | expected_value = expected_result[key] 130 | self.assertAlmostEqual(value, expected_value, self.decimal_places) 131 | -------------------------------------------------------------------------------- /tests/unit/query/test_sequential_statistics.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace import DiffPrivSequentialStatisticsQuery, DiffPrivStatisticKind 4 | 5 | 6 | class TestDiffPrivSequentialStatisticsQuery(unittest.TestCase): 7 | epsilon = 100000000 8 | decimal_places = 2 9 | 10 | def setUp(self): 11 | pass 12 | 13 | def tearDown(self): 14 | pass 15 | 16 | def set_seed(self): 17 | np.random.seed(31337) 18 | 19 | def test_query_count(self): 20 | kinds = [ 21 | DiffPrivStatisticKind.count, 22 | DiffPrivStatisticKind.count | DiffPrivStatisticKind.max, 23 | DiffPrivStatisticKind.count 24 | | DiffPrivStatisticKind.max 25 | | DiffPrivStatisticKind.min, 26 | DiffPrivStatisticKind.count 27 | | DiffPrivStatisticKind.max 28 | | DiffPrivStatisticKind.min 29 | | DiffPrivStatisticKind.sum, 30 | DiffPrivStatisticKind.count 31 | | DiffPrivStatisticKind.max 32 | | DiffPrivStatisticKind.min 33 | | DiffPrivStatisticKind.sum 34 | | DiffPrivStatisticKind.mean, 35 | DiffPrivStatisticKind.count 36 | | DiffPrivStatisticKind.max 37 | | DiffPrivStatisticKind.min 38 | | DiffPrivStatisticKind.sum 39 | | DiffPrivStatisticKind.mean 40 | | DiffPrivStatisticKind.median, 41 | DiffPrivStatisticKind.count 42 | | DiffPrivStatisticKind.max 43 | | DiffPrivStatisticKind.min 44 | | DiffPrivStatisticKind.sum 45 | | DiffPrivStatisticKind.mean 46 | | DiffPrivStatisticKind.median 47 | | DiffPrivStatisticKind.proportion, 48 | DiffPrivStatisticKind.count 49 | | DiffPrivStatisticKind.max 50 | | DiffPrivStatisticKind.min 51 | | DiffPrivStatisticKind.sum 52 | | DiffPrivStatisticKind.mean 53 | | DiffPrivStatisticKind.median 54 | | DiffPrivStatisticKind.proportion 55 | | DiffPrivStatisticKind.variance, 56 | ] 57 | query_count = DiffPrivSequentialStatisticsQuery.query_count(kinds) 58 | self.assertAlmostEqual(query_count, np.sum(list(range(1, 9)))) 59 | 60 | def test_calculate_query_epsilon(self): 61 | kinds = [ 62 | DiffPrivStatisticKind.count, 63 | DiffPrivStatisticKind.count | DiffPrivStatisticKind.max, 64 | DiffPrivStatisticKind.count 65 | | DiffPrivStatisticKind.max 66 | | DiffPrivStatisticKind.min, 67 | DiffPrivStatisticKind.count 68 | | DiffPrivStatisticKind.max 69 | | DiffPrivStatisticKind.min 70 | | DiffPrivStatisticKind.sum, 71 | DiffPrivStatisticKind.count 72 | | DiffPrivStatisticKind.max 73 | | DiffPrivStatisticKind.min 74 | | DiffPrivStatisticKind.sum 75 | | DiffPrivStatisticKind.mean, 76 | DiffPrivStatisticKind.count 77 | | DiffPrivStatisticKind.max 78 | | DiffPrivStatisticKind.min 79 | | DiffPrivStatisticKind.sum 80 | | DiffPrivStatisticKind.mean 81 | | DiffPrivStatisticKind.median, 82 | DiffPrivStatisticKind.count 83 | | DiffPrivStatisticKind.max 84 | | DiffPrivStatisticKind.min 85 | | DiffPrivStatisticKind.sum 86 | | DiffPrivStatisticKind.mean 87 | | DiffPrivStatisticKind.median 88 | | DiffPrivStatisticKind.proportion, 89 | DiffPrivStatisticKind.count 90 | | DiffPrivStatisticKind.max 91 | | DiffPrivStatisticKind.min 92 | | DiffPrivStatisticKind.sum 93 | | DiffPrivStatisticKind.mean 94 | | DiffPrivStatisticKind.median 95 | | DiffPrivStatisticKind.proportion 96 | | DiffPrivStatisticKind.variance, 97 | ] 98 | epsilon = 360.0 99 | expected_value = 10.0 100 | value = DiffPrivSequentialStatisticsQuery.calculate_query_epsilon( 101 | kinds, epsilon 102 | ) 103 | self.assertAlmostEqual(value, expected_value) 104 | 105 | def calculate_stats(self, data, axis=None): 106 | stats = { 107 | DiffPrivStatisticKind.count: np.count_nonzero(data, axis=axis), 108 | DiffPrivStatisticKind.min: np.min(data, axis=axis), 109 | DiffPrivStatisticKind.max: np.max(data, axis=axis), 110 | DiffPrivStatisticKind.median: np.median(data, axis=axis), 111 | DiffPrivStatisticKind.proportion: np.divide( 112 | np.count_nonzero(data, axis=axis), np.size(data, axis=axis) 113 | ), 114 | DiffPrivStatisticKind.sum: np.sum(data, axis=axis), 115 | DiffPrivStatisticKind.mean: np.mean(data, axis=axis), 116 | DiffPrivStatisticKind.variance: np.var(data, axis=axis), 117 | } 118 | return stats 119 | 120 | def test_query_single(self): 121 | data = np.array(list(range(0, 20)) + [100.0]) 122 | kinds = DiffPrivStatisticKind.all 123 | expected_results = [self.calculate_stats(data)] 124 | self.set_seed() 125 | results = DiffPrivSequentialStatisticsQuery.query(data, kinds, self.epsilon) 126 | self.assertEqual(len(results), len(expected_results)) 127 | for index in range(len(expected_results)): 128 | result = results[index] 129 | expected_result = expected_results[index] 130 | self.assertEqual(len(result), len(expected_result)) 131 | for key, value in expected_result.items(): 132 | value = result[key] 133 | expected_value = expected_result[key] 134 | self.assertAlmostEqual(value, expected_value, self.decimal_places) 135 | 136 | def test_query_single_with_axis_0(self): 137 | data = np.array(list(range(0, 20)) + [100.0]) 138 | kinds = DiffPrivStatisticKind.all 139 | expected_results = [self.calculate_stats(data)] 140 | self.set_seed() 141 | results = DiffPrivSequentialStatisticsQuery.query( 142 | data, kinds, self.epsilon, axis=0 143 | ) 144 | self.assertEqual(len(results), len(expected_results)) 145 | for index in range(len(expected_results)): 146 | result = results[index] 147 | expected_result = expected_results[index] 148 | self.assertEqual(len(result), len(expected_result)) 149 | for key, value in expected_result.items(): 150 | value = result[key] 151 | expected_value = expected_result[key] 152 | self.assertAlmostEqual(value, expected_value, self.decimal_places) 153 | 154 | def test_query_single_with_axis_1(self): 155 | data = np.array(list(range(0, 20)) + [100.0]) 156 | kinds = DiffPrivStatisticKind.all 157 | expected_results = [self.calculate_stats(data)] 158 | data = np.transpose(data) 159 | self.set_seed() 160 | results = DiffPrivSequentialStatisticsQuery.query( 161 | data, kinds, self.epsilon, axis=1 162 | ) 163 | self.assertEqual(len(results), len(expected_results)) 164 | for index in range(len(expected_results)): 165 | result = results[index] 166 | expected_result = expected_results[index] 167 | self.assertEqual(len(result), len(expected_result)) 168 | for key, value in expected_result.items(): 169 | value = result[key] 170 | expected_value = expected_result[key] 171 | self.assertAlmostEqual(value, expected_value, self.decimal_places) 172 | 173 | def test_query_multiple_axis_0(self): 174 | data = np.array([list(range(0, 20)) + [100.0]] * 3) 175 | kinds = [DiffPrivStatisticKind.all] * 3 176 | expected_results = [ 177 | self.calculate_stats(data[0]), 178 | self.calculate_stats(data[1]), 179 | self.calculate_stats(data[2]), 180 | ] 181 | data = np.transpose(data) 182 | self.set_seed() 183 | results = DiffPrivSequentialStatisticsQuery.query( 184 | data, kinds, self.epsilon, axis=0 185 | ) 186 | self.assertEqual(len(results), len(expected_results)) 187 | for index in range(len(expected_results)): 188 | result = results[index] 189 | expected_result = expected_results[index] 190 | self.assertEqual(len(result), len(expected_result)) 191 | for key, value in expected_result.items(): 192 | value = result[key] 193 | expected_value = expected_result[key] 194 | self.assertAlmostEqual(value, expected_value, self.decimal_places) 195 | 196 | def test_query_multiple_axis_1(self): 197 | data = np.array([list(range(0, 20)) + [100.0]] * 3) 198 | kinds = [DiffPrivStatisticKind.all] * 3 199 | expected_results = [ 200 | self.calculate_stats(data[0]), 201 | self.calculate_stats(data[1]), 202 | self.calculate_stats(data[2]), 203 | ] 204 | self.set_seed() 205 | results = DiffPrivSequentialStatisticsQuery.query( 206 | data, kinds, self.epsilon, axis=1 207 | ) 208 | self.assertEqual(len(results), len(expected_results)) 209 | for index in range(len(expected_results)): 210 | result = results[index] 211 | expected_result = expected_results[index] 212 | self.assertEqual(len(result), len(expected_result)) 213 | for key, value in expected_result.items(): 214 | value = result[key] 215 | expected_value = expected_result[key] 216 | self.assertAlmostEqual(value, expected_value, self.decimal_places) 217 | -------------------------------------------------------------------------------- /tests/unit/test_laplace_sanitizer.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from diffpriv_laplace import DiffPrivLaplaceSanitizer 4 | from diffpriv_laplace.exceptions import ( 5 | DiffPrivInvalidDecomposition, 6 | DiffPrivInvalidDimensions, 7 | DiffPrivSizeMismatch, 8 | ) 9 | 10 | 11 | class TestDiffPrivLaplaceSanitizer(unittest.TestCase): 12 | epsilon = 1000000 13 | decimal_places = 2 14 | 15 | def setUp(self): 16 | pass 17 | 18 | def tearDown(self): 19 | pass 20 | 21 | def set_seed(self): 22 | np.random.seed(31337) 23 | 24 | def test_decompose_data_slice_two_selectors(self): 25 | data = np.array([0.1, -0.1, 0.1, 0.1] * 10) 26 | expected_result = np.array( 27 | [data >= 0, data < 0], 28 | ) 29 | 30 | def selector_positive(data): 31 | return data >= 0 32 | 33 | def selector_negative(data): 34 | return data < 0 35 | 36 | selectors = [ 37 | selector_positive, 38 | selector_negative, 39 | ] 40 | 41 | result = DiffPrivLaplaceSanitizer.decompose_data_slice(data, selectors) 42 | self.assertEqual(len(result), len(selectors)) 43 | for index in range(len(expected_result)): 44 | np.testing.assert_almost_equal(result[index], expected_result[index]) 45 | 46 | def test_decompose_data_slice_three_selectors(self): 47 | data = np.array([0.1, -0.1, 0.01, -0.01, 0.1, 0.1] * 10) 48 | expected_result = np.array( 49 | [(data > -0.1) & (data < 0.1), data >= 0.1, data <= -0.1], 50 | ) 51 | 52 | def selector_near_zero(data): 53 | return (data > -0.1) & (data < 0.1) 54 | 55 | def selector_positive(data): 56 | return data >= 0.1 57 | 58 | def selector_negative(data): 59 | return data <= -0.1 60 | 61 | selectors = [ 62 | selector_near_zero, 63 | selector_positive, 64 | selector_negative, 65 | ] 66 | 67 | result = DiffPrivLaplaceSanitizer.decompose_data_slice(data, selectors) 68 | self.assertEqual(len(result), len(selectors)) 69 | for index in range(len(expected_result)): 70 | np.testing.assert_almost_equal(result[index], expected_result[index]) 71 | 72 | def test_decompose_data_slice_invalid_decomposition_error(self): 73 | data = np.array([0.1, -0.1, 0.01, 0.0, -0.01, 0.1, 0.1] * 10) 74 | 75 | def selector_positive(data): 76 | return data != 0.0 77 | 78 | def selector_negative(data): 79 | return data != 0.0 80 | 81 | selectors = [ 82 | selector_positive, 83 | selector_negative, 84 | ] 85 | 86 | with self.assertRaises(DiffPrivInvalidDecomposition): 87 | DiffPrivLaplaceSanitizer.decompose_data_slice(data, selectors) 88 | 89 | def test_count_example(self): 90 | epsilon = 0.1 91 | data = np.array([0.01, -0.01, 0.03, -0.001, 0.1] * 2) 92 | expected_value = np.array([np.array([5.0, 5.0])]) 93 | 94 | def selector_positive(data): 95 | return data >= 0.0 96 | 97 | def selector_negative(data): 98 | return data < 0.0 99 | 100 | selectors = [selector_positive, selector_negative] 101 | self.set_seed() 102 | value = DiffPrivLaplaceSanitizer.count(data, selectors, epsilon) 103 | np.testing.assert_almost_equal(value, expected_value) 104 | 105 | def test_count_invalid_dimension_error(self): 106 | data = np.array([np.array([np.array([0.1, -0.1, 0.1, 0.1] * 10)])]) 107 | 108 | def selector_positive(data): 109 | return data >= 0 110 | 111 | def selector_negative(data): 112 | return data < 0 113 | 114 | selectors = [ 115 | selector_positive, 116 | selector_negative, 117 | ] 118 | 119 | self.set_seed() 120 | with self.assertRaises(DiffPrivInvalidDimensions): 121 | DiffPrivLaplaceSanitizer.count(data, selectors, self.epsilon) 122 | 123 | def test_count_size_mismatch_error(self): 124 | data = np.array( 125 | [np.array([0.1, -0.1, 0.1, 0.1] * 10), np.array([0.1, -0.1, 0.1, 0.1] * 10)] 126 | ) 127 | selectors = [[]] 128 | 129 | self.set_seed() 130 | with self.assertRaises(DiffPrivSizeMismatch): 131 | DiffPrivLaplaceSanitizer.count(data, selectors, self.epsilon) 132 | 133 | def test_count_single_no_postprocess(self): 134 | data = np.array([0.1, -0.1, 0.1, 0.1] * 10) 135 | postprocess = False 136 | expected_result = [np.array([np.sum(data >= 0), np.sum(data < 0)])] 137 | 138 | def selector_positive(data): 139 | return data >= 0 140 | 141 | def selector_negative(data): 142 | return data < 0 143 | 144 | selectors = [ 145 | selector_positive, 146 | selector_negative, 147 | ] 148 | 149 | self.set_seed() 150 | results = DiffPrivLaplaceSanitizer.count( 151 | data, selectors, self.epsilon, postprocess=postprocess 152 | ) 153 | 154 | for index in range(len(expected_result)): 155 | np.testing.assert_almost_equal(results[index], expected_result[index]) 156 | 157 | def test_count_single_no_postprocess_with_axis_0(self): 158 | data = np.array([0.1, -0.1, 0.1, 0.1] * 10) 159 | postprocess = False 160 | expected_result = [np.array([np.sum(data >= 0), np.sum(data < 0)])] 161 | 162 | def selector_positive(data): 163 | return data >= 0 164 | 165 | def selector_negative(data): 166 | return data < 0 167 | 168 | selectors = [ 169 | selector_positive, 170 | selector_negative, 171 | ] 172 | 173 | self.set_seed() 174 | results = DiffPrivLaplaceSanitizer.count( 175 | data, selectors, self.epsilon, axis=0, postprocess=postprocess 176 | ) 177 | 178 | for index in range(len(expected_result)): 179 | np.testing.assert_almost_equal(results[index], expected_result[index]) 180 | 181 | def test_count_single_no_postprocess_with_axis_1(self): 182 | data = np.array([0.1, -0.1, 0.1, 0.1] * 10) 183 | postprocess = False 184 | expected_result = [np.array([np.sum(data >= 0), np.sum(data < 0)])] 185 | 186 | def selector_positive(data): 187 | return data >= 0 188 | 189 | def selector_negative(data): 190 | return data < 0 191 | 192 | selectors = [ 193 | selector_positive, 194 | selector_negative, 195 | ] 196 | 197 | data = data.transpose() 198 | self.set_seed() 199 | results = DiffPrivLaplaceSanitizer.count( 200 | data, selectors, self.epsilon, axis=1, postprocess=postprocess 201 | ) 202 | 203 | for index in range(len(expected_result)): 204 | np.testing.assert_almost_equal(results[index], expected_result[index]) 205 | 206 | def test_count_single_with_postprocess(self): 207 | data = np.array([0.1, -0.1, 0.1, 0.1] * 10) 208 | postprocess = True 209 | expected_result = [np.array([np.sum(data >= 0), np.sum(data < 0)])] 210 | 211 | def selector_positive(data): 212 | return data >= 0 213 | 214 | def selector_negative(data): 215 | return data < 0 216 | 217 | selectors = [ 218 | selector_positive, 219 | selector_negative, 220 | ] 221 | 222 | self.set_seed() 223 | results = DiffPrivLaplaceSanitizer.count( 224 | data, selectors, self.epsilon, postprocess=postprocess 225 | ) 226 | 227 | for index in range(len(expected_result)): 228 | np.testing.assert_almost_equal(results[index], expected_result[index]) 229 | 230 | def test_count_single_with_postprocess_with_axis_0(self): 231 | data = np.array([0.1, -0.1, 0.1, 0.1] * 10) 232 | postprocess = True 233 | expected_result = [np.array([np.sum(data >= 0), np.sum(data < 0)])] 234 | 235 | def selector_positive(data): 236 | return data >= 0 237 | 238 | def selector_negative(data): 239 | return data < 0 240 | 241 | selectors = [ 242 | selector_positive, 243 | selector_negative, 244 | ] 245 | 246 | self.set_seed() 247 | results = DiffPrivLaplaceSanitizer.count( 248 | data, selectors, self.epsilon, axis=0, postprocess=postprocess 249 | ) 250 | 251 | for index in range(len(expected_result)): 252 | np.testing.assert_almost_equal(results[index], expected_result[index]) 253 | 254 | def test_count_single_with_postprocess_with_axis_1(self): 255 | data = np.array([0.1, -0.1, 0.1, 0.1] * 10) 256 | postprocess = True 257 | expected_result = [np.array([np.sum(data >= 0), np.sum(data < 0)])] 258 | 259 | def selector_positive(data): 260 | return data >= 0 261 | 262 | def selector_negative(data): 263 | return data < 0 264 | 265 | selectors = [ 266 | selector_positive, 267 | selector_negative, 268 | ] 269 | 270 | data = data.transpose() 271 | self.set_seed() 272 | results = DiffPrivLaplaceSanitizer.count( 273 | data, selectors, self.epsilon, axis=1, postprocess=postprocess 274 | ) 275 | 276 | for index in range(len(expected_result)): 277 | np.testing.assert_almost_equal(results[index], expected_result[index]) 278 | 279 | def test_count_multiple_no_postprocess_with_axis_0(self): 280 | data = np.array( 281 | [ 282 | np.array([0.1, -0.1, 0.1, 0.1] * 10), 283 | np.array([0.1, -0.1, 0.1, 0.1] * 10), 284 | ] 285 | ) 286 | postprocess = False 287 | expected_result = [ 288 | np.array([np.sum(data[0] >= 0), np.sum(data[0] < 0)]), 289 | np.array([np.sum(data[1] >= 0), np.sum(data[1] < 0)]), 290 | ] 291 | 292 | def selector_positive(data): 293 | return data >= 0 294 | 295 | def selector_negative(data): 296 | return data < 0 297 | 298 | selectors = [ 299 | [selector_positive, selector_negative], 300 | [selector_positive, selector_negative], 301 | ] 302 | 303 | data = np.transpose(data) 304 | self.set_seed() 305 | results = DiffPrivLaplaceSanitizer.count( 306 | data, selectors, self.epsilon, axis=0, postprocess=postprocess 307 | ) 308 | 309 | for index in range(len(expected_result)): 310 | np.testing.assert_almost_equal(results[index], expected_result[index]) 311 | 312 | def test_count_multiple_no_postprocess_with_axis_1(self): 313 | data = np.array( 314 | [ 315 | np.array([0.1, -0.1, 0.1, 0.1] * 10), 316 | np.array([0.1, -0.1, 0.1, 0.1] * 10), 317 | ] 318 | ) 319 | postprocess = False 320 | expected_result = [ 321 | np.array([np.sum(data[0] >= 0), np.sum(data[0] < 0)]), 322 | np.array([np.sum(data[1] >= 0), np.sum(data[1] < 0)]), 323 | ] 324 | 325 | def selector_positive(data): 326 | return data >= 0 327 | 328 | def selector_negative(data): 329 | return data < 0 330 | 331 | selectors = [ 332 | [selector_positive, selector_negative], 333 | [selector_positive, selector_negative], 334 | ] 335 | 336 | self.set_seed() 337 | results = DiffPrivLaplaceSanitizer.count( 338 | data, selectors, self.epsilon, axis=1, postprocess=postprocess 339 | ) 340 | 341 | for index in range(len(expected_result)): 342 | np.testing.assert_almost_equal(results[index], expected_result[index]) 343 | 344 | def test_count_multiple_with_postprocess_with_axis_0(self): 345 | data = np.array( 346 | [ 347 | np.array([0.1, -0.1, 0.1, 0.1] * 10), 348 | np.array([0.1, -0.1, 0.1, 0.1] * 10), 349 | ] 350 | ) 351 | postprocess = False 352 | expected_result = [ 353 | np.array([np.sum(data[0] >= 0), np.sum(data[0] < 0)]), 354 | np.array([np.sum(data[1] >= 0), np.sum(data[1] < 0)]), 355 | ] 356 | 357 | def selector_positive(data): 358 | return data >= 0 359 | 360 | def selector_negative(data): 361 | return data < 0 362 | 363 | selectors = [ 364 | [selector_positive, selector_negative], 365 | [selector_positive, selector_negative], 366 | ] 367 | 368 | data = np.transpose(data) 369 | self.set_seed() 370 | results = DiffPrivLaplaceSanitizer.count( 371 | data, selectors, self.epsilon, axis=0, postprocess=postprocess 372 | ) 373 | 374 | for index in range(len(expected_result)): 375 | np.testing.assert_almost_equal(results[index], expected_result[index]) 376 | 377 | def test_count_multiple_with_postprocess_with_axis_1(self): 378 | data = np.array( 379 | [ 380 | np.array([0.1, -0.1, 0.1, 0.1] * 10), 381 | np.array([0.1, -0.1, 0.1, 0.1] * 10), 382 | ] 383 | ) 384 | postprocess = True 385 | expected_result = [ 386 | np.array([np.sum(data[0] >= 0), np.sum(data[0] < 0)]), 387 | np.array([np.sum(data[1] >= 0), np.sum(data[1] < 0)]), 388 | ] 389 | 390 | def selector_positive(data): 391 | return data >= 0 392 | 393 | def selector_negative(data): 394 | return data < 0 395 | 396 | selectors = [ 397 | [selector_positive, selector_negative], 398 | [selector_positive, selector_negative], 399 | ] 400 | 401 | self.set_seed() 402 | results = DiffPrivLaplaceSanitizer.count( 403 | data, selectors, self.epsilon, axis=1, postprocess=postprocess 404 | ) 405 | 406 | for index in range(len(expected_result)): 407 | np.testing.assert_almost_equal(results[index], expected_result[index]) 408 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36 3 | 4 | [flake8] 5 | # Recommend matching the black line length (default 88), 6 | # rather than using the flake8 default of 79: 7 | max-line-length = 88 8 | extend-ignore = 9 | # See https://github.com/PyCQA/pycodestyle/issues/373 10 | E203, 11 | 12 | [testenv] 13 | passenv = * 14 | deps= 15 | -rrequirements.txt 16 | commands= 17 | coverage run --source=diffpriv_laplace/,tests/*,tests/*/* -m pytest -vv --strict tests/ 18 | coverage report -m --show-missing --fail-under 96 19 | flake8 --exclude=.git,__pycache__,.tox,venv . 20 | --------------------------------------------------------------------------------