From b0d7abcdf07ebbdb814e14fff4cd7455ea45a733 Mon Sep 17 00:00:00 2001 From: dfroelic Date: Tue, 11 Apr 2023 16:16:06 -0400 Subject: [PATCH 01/58] added README for MusCAT --- .../README.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md diff --git a/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md b/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md new file mode 100644 index 0000000..d8c60dc --- /dev/null +++ b/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md @@ -0,0 +1,21 @@ +# Tool Template + +**MusCAT: A multi-scale, hybrid federated system for privacy-preserving epidemic surveillance and risk prediction** + +**Primary Focus Area (select one):** De-identification + +**De-identification Keywords (select any relevant):** Differential Privacy, Multiparty Homomorphic Encryption, Machine Learning, Federated Learning + +**Brief Description:** +Our tool, MusCAT, is a multi-scale, hybrid federated system for privacy-preserving epidemic surveillance and risk prediction. +It combines differential privacy, multiparty homomorphic encryption, and federated learning to jointly analyze private data held by multiple federation units with formal privacy guarantees. +MusCAT came in second place as a solution in the [U.S. PETs Prize Challenge](https://www.drivendata.org/competitions/group/nist-federated-learning/). + +**Additional Notes:** Paper describing our solution: TODO + +**GitHub User Serving as POC (or Email Address):** @hhcho (hhcho@broadinstitute.org) + +**Affiliation/Organization(s) Contributing (if relevant):** Broad Institute, MIT, Harvard Business School, University of Texas, University of Toronto + + +**Tool Link:** https://github.com/hhcho/petchal \ No newline at end of file From 9f48e39cdaeffd464a27d813bbbd5ed284b246fb Mon Sep 17 00:00:00 2001 From: David Froelicher Date: Tue, 11 Apr 2023 16:26:40 -0400 Subject: [PATCH 02/58] Update README.md --- .../MusCAT-fed-secure-epidemic-surveil/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md b/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md index d8c60dc..fd16ead 100644 --- a/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md +++ b/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md @@ -1,6 +1,4 @@ -# Tool Template - -**MusCAT: A multi-scale, hybrid federated system for privacy-preserving epidemic surveillance and risk prediction** +# MusCAT: A multi-scale, hybrid federated system for privacy-preserving epidemic surveillance and risk prediction **Primary Focus Area (select one):** De-identification @@ -18,4 +16,4 @@ MusCAT came in second place as a solution in the [U.S. PETs Prize Challenge](htt **Affiliation/Organization(s) Contributing (if relevant):** Broad Institute, MIT, Harvard Business School, University of Texas, University of Toronto -**Tool Link:** https://github.com/hhcho/petchal \ No newline at end of file +**Tool Link:** https://github.com/hhcho/petchal From 65255135fac0e64c215bb1d1d987ae9b9c8a13f8 Mon Sep 17 00:00:00 2001 From: David Froelicher Date: Tue, 11 Apr 2023 17:06:35 -0400 Subject: [PATCH 03/58] Update README.md --- .../MusCAT-fed-secure-epidemic-surveil/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md b/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md index fd16ead..8c6e782 100644 --- a/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md +++ b/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md @@ -16,4 +16,4 @@ MusCAT came in second place as a solution in the [U.S. PETs Prize Challenge](htt **Affiliation/Organization(s) Contributing (if relevant):** Broad Institute, MIT, Harvard Business School, University of Texas, University of Toronto -**Tool Link:** https://github.com/hhcho/petchal +**Tool Link:** https://github.com/hhcho/muscat From b077b3789c97b1bb63f1c26bd6fc033884e796d3 Mon Sep 17 00:00:00 2001 From: David Froelicher Date: Wed, 12 Apr 2023 16:06:06 -0400 Subject: [PATCH 04/58] Update README.md --- .../MusCAT-fed-secure-epidemic-surveil/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md b/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md index 8c6e782..c8142c6 100644 --- a/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md +++ b/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md @@ -9,7 +9,7 @@ Our tool, MusCAT, is a multi-scale, hybrid federated system for privacy-preservi It combines differential privacy, multiparty homomorphic encryption, and federated learning to jointly analyze private data held by multiple federation units with formal privacy guarantees. MusCAT came in second place as a solution in the [U.S. PETs Prize Challenge](https://www.drivendata.org/competitions/group/nist-federated-learning/). -**Additional Notes:** Paper describing our solution: TODO +**Additional Notes:** White paper describing our solution: [here](https://www.dropbox.com/s/dzyc8himjtcu05j/PETsChallenge_MusCAT_Report.pdf?dl=0) **GitHub User Serving as POC (or Email Address):** @hhcho (hhcho@broadinstitute.org) From 30ce1902649213b7c8baf774fe4ee67661996e99 Mon Sep 17 00:00:00 2001 From: dfroelic Date: Wed, 12 Apr 2023 16:08:09 -0400 Subject: [PATCH 05/58] renamed folder --- .../README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/de-identification/{MusCAT-fed-secure-epidemic-surveil => MusCAT-fed-secure-epidemic-surveillance}/README.md (100%) diff --git a/tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md b/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md similarity index 100% rename from tools/de-identification/MusCAT-fed-secure-epidemic-surveil/README.md rename to tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md From 1f53e7965b5b20c67cec93a32ca4e19a28b60019 Mon Sep 17 00:00:00 2001 From: David Froelicher Date: Thu, 13 Apr 2023 16:07:27 -0400 Subject: [PATCH 06/58] Update global readme with our project in the list --- tools/de-identification/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/de-identification/README.md b/tools/de-identification/README.md index 9edb655..f7564f0 100644 --- a/tools/de-identification/README.md +++ b/tools/de-identification/README.md @@ -63,6 +63,12 @@ Contributions are listed in alphabetical order. **[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Google-DP-Lib)** | **[Link to Tool](https://github.com/google/differential-privacy)** +## MusCAT: A multi-scale, hybrid federated system for privacy-preserving epidemic surveillance and risk prediction + +**Keywords:** Differential Privacy, Multiparty Homomorphic Encryption, Machine Learning, Federated Learning + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance)** | **[Link to Tool]([https://github.com/columbia/pixeldp](https://github.com/hhcho/muscat))** + ## PixelDP **Keywords:** Differential Privacy, Verification of Algorithms, Machine Learning, Adversarial Examples From 4ccd017e0df5657172baea333a75e82ddd81a967 Mon Sep 17 00:00:00 2001 From: David Froelicher Date: Thu, 13 Apr 2023 16:10:25 -0400 Subject: [PATCH 07/58] Update README.md --- tools/de-identification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/de-identification/README.md b/tools/de-identification/README.md index f7564f0..6a208bc 100644 --- a/tools/de-identification/README.md +++ b/tools/de-identification/README.md @@ -67,7 +67,7 @@ Contributions are listed in alphabetical order. **Keywords:** Differential Privacy, Multiparty Homomorphic Encryption, Machine Learning, Federated Learning -**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance)** | **[Link to Tool]([https://github.com/columbia/pixeldp](https://github.com/hhcho/muscat))** +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance)** | **[Link to Tool](https://github.com/hhcho/muscat)** ## PixelDP From 5d0aaaaf7663cda84ea1b90674e1fc19232723e7 Mon Sep 17 00:00:00 2001 From: David Froelicher Date: Thu, 13 Apr 2023 16:52:06 -0400 Subject: [PATCH 08/58] Update README.md --- .../MusCAT-fed-secure-epidemic-surveillance/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md b/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md index c8142c6..b69f3c6 100644 --- a/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md +++ b/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md @@ -9,8 +9,6 @@ Our tool, MusCAT, is a multi-scale, hybrid federated system for privacy-preservi It combines differential privacy, multiparty homomorphic encryption, and federated learning to jointly analyze private data held by multiple federation units with formal privacy guarantees. MusCAT came in second place as a solution in the [U.S. PETs Prize Challenge](https://www.drivendata.org/competitions/group/nist-federated-learning/). -**Additional Notes:** White paper describing our solution: [here](https://www.dropbox.com/s/dzyc8himjtcu05j/PETsChallenge_MusCAT_Report.pdf?dl=0) - **GitHub User Serving as POC (or Email Address):** @hhcho (hhcho@broadinstitute.org) **Affiliation/Organization(s) Contributing (if relevant):** Broad Institute, MIT, Harvard Business School, University of Texas, University of Toronto From fa0911b3a7b6cb7ebd904fb28aaab27e3d3d5a2a Mon Sep 17 00:00:00 2001 From: Hoon Cho Date: Mon, 17 Apr 2023 15:14:02 -0400 Subject: [PATCH 09/58] Update README.md --- .../MusCAT-fed-secure-epidemic-surveillance/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md b/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md index b69f3c6..6556a3f 100644 --- a/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md +++ b/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md @@ -1,17 +1,18 @@ -# MusCAT: A multi-scale, hybrid federated system for privacy-preserving epidemic surveillance and risk prediction +# MusCAT **Primary Focus Area (select one):** De-identification **De-identification Keywords (select any relevant):** Differential Privacy, Multiparty Homomorphic Encryption, Machine Learning, Federated Learning **Brief Description:** -Our tool, MusCAT, is a multi-scale, hybrid federated system for privacy-preserving epidemic surveillance and risk prediction. +MusCAT is a multi-scale, hybrid federated system for privacy-preserving epidemic surveillance and risk prediction. It combines differential privacy, multiparty homomorphic encryption, and federated learning to jointly analyze private data held by multiple federation units with formal privacy guarantees. -MusCAT came in second place as a solution in the [U.S. PETs Prize Challenge](https://www.drivendata.org/competitions/group/nist-federated-learning/). +This software implements Team MusCAT's solution to the [U.S. PETs Prize Challenge](https://www.drivendata.org/competitions/group/nist-federated-learning/) (Pandemic Forecasting). +Team MusCAT won [first place](https://drivendata.co/blog/federated-learning-pets-prize-winners-phase1) for the white paper (Phase 1) and [second place](https://drivendata.co/blog/federated-learning-pets-prize-winners-phases-2-3) in the final stage (Phase 2) of the Challenge. -**GitHub User Serving as POC (or Email Address):** @hhcho (hhcho@broadinstitute.org) -**Affiliation/Organization(s) Contributing (if relevant):** Broad Institute, MIT, Harvard Business School, University of Texas, University of Toronto +**GitHub User Serving as POC (or Email Address):** @hhcho +**Affiliation/Organization(s) Contributing (if relevant):** Broad Institute, MIT, Harvard Business School, University of Texas, University of Toronto **Tool Link:** https://github.com/hhcho/muscat From 8ee9f3b0c4d6618df7082cbcb55add98df56f7f1 Mon Sep 17 00:00:00 2001 From: Hoon Cho Date: Mon, 17 Apr 2023 15:15:21 -0400 Subject: [PATCH 10/58] Update README.md --- tools/de-identification/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/de-identification/README.md b/tools/de-identification/README.md index 6a208bc..f80bd82 100644 --- a/tools/de-identification/README.md +++ b/tools/de-identification/README.md @@ -63,11 +63,11 @@ Contributions are listed in alphabetical order. **[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Google-DP-Lib)** | **[Link to Tool](https://github.com/google/differential-privacy)** -## MusCAT: A multi-scale, hybrid federated system for privacy-preserving epidemic surveillance and risk prediction +## MusCAT **Keywords:** Differential Privacy, Multiparty Homomorphic Encryption, Machine Learning, Federated Learning -**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance)** | **[Link to Tool](https://github.com/hhcho/muscat)** +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/MusCAT)** | **[Link to Tool](https://github.com/hhcho/muscat)** ## PixelDP From 4bb282b1e643a18165918fe4e4633c6395250a97 Mon Sep 17 00:00:00 2001 From: Hoon Cho Date: Mon, 17 Apr 2023 22:20:29 +0300 Subject: [PATCH 11/58] Rename --- .../{MusCAT-fed-secure-epidemic-surveillance => MusCAT}/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/de-identification/{MusCAT-fed-secure-epidemic-surveillance => MusCAT}/README.md (100%) diff --git a/tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md b/tools/de-identification/MusCAT/README.md similarity index 100% rename from tools/de-identification/MusCAT-fed-secure-epidemic-surveillance/README.md rename to tools/de-identification/MusCAT/README.md From ef77e8592fe71182d40db374100e9eaa27099955 Mon Sep 17 00:00:00 2001 From: David Froelicher Date: Tue, 18 Apr 2023 09:08:02 -0400 Subject: [PATCH 12/58] updated affiliation --- tools/de-identification/MusCAT/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/de-identification/MusCAT/README.md b/tools/de-identification/MusCAT/README.md index 6556a3f..6ab09b4 100644 --- a/tools/de-identification/MusCAT/README.md +++ b/tools/de-identification/MusCAT/README.md @@ -13,6 +13,6 @@ Team MusCAT won [first place](https://drivendata.co/blog/federated-learning-pets **GitHub User Serving as POC (or Email Address):** @hhcho -**Affiliation/Organization(s) Contributing (if relevant):** Broad Institute, MIT, Harvard Business School, University of Texas, University of Toronto +**Affiliation/Organization(s) Contributing (if relevant):** Broad Institute, MIT, Harvard Business School, UT Austin, University of Toronto **Tool Link:** https://github.com/hhcho/muscat From c3cbe955555a2bc1f3f1d224e631efd7d4de17c2 Mon Sep 17 00:00:00 2001 From: Peter Rindal Date: Thu, 20 Apr 2023 11:54:34 -0700 Subject: [PATCH 13/58] initial commit --- tools/de-identification/README.md | 6 + .../visa-pets-FL/LICENSE.txt | 408 ++++++++ .../de-identification/visa-pets-FL/README.md | 24 + .../centralized/DNNCentralizedModule.py | 204 ++++ .../visa-pets-FL/centralized/DataObjects.py | 82 ++ .../visa-pets-FL/centralized/DataPrepUtils.py | 387 +++++++ .../visa-pets-FL/centralized/NeuralNet.py | 28 + .../centralized/solution_centralized.py | 47 + .../visa-pets-FL/federated/DNN.py | 126 +++ .../visa-pets-FL/federated/DataObjects.py | 82 ++ .../visa-pets-FL/federated/DataPrepUtils.py | 454 +++++++++ .../visa-pets-FL/federated/NeuralNet.py | 28 + .../visa-pets-FL/federated/ot.py | 161 +++ .../federated/solution_federated.py | 830 +++++++++++++++ .../de-identification/visa-pets-FL/figure.png | Bin 0 -> 143190 bytes .../visa-pets-FL/ot_library/.gitignore | 227 +++++ .../ot_library/.vscode/launch.json | 16 + .../ot_library/.vscode/settings.json | 82 ++ .../visa-pets-FL/ot_library/CMakeLists.txt | 21 + .../visa-pets-FL/ot_library/CMakePresets.json | 82 ++ .../ot_library/cmake/Config.cmake.in | 13 + .../ot_library/cmake/buildOptions.cmake | 41 + .../ot_library/cmake/findDependancies.cmake | 66 ++ .../ot_library/cmake/install.cmake | 67 ++ .../ot_library/cmake/preamble.cmake | 87 ++ .../ot_library/drop-ot/CMakeLists.txt | 31 + .../visa-pets-FL/ot_library/drop-ot/Defines.h | 116 +++ .../ot_library/drop-ot/IknpOtExt.cpp | 443 ++++++++ .../ot_library/drop-ot/IknpOtExt.h | 237 +++++ .../ot_library/drop-ot/MasnyRindal.cpp | 224 +++++ .../ot_library/drop-ot/MasnyRindal.h | 183 ++++ .../visa-pets-FL/ot_library/drop-ot/Tools.cpp | 942 ++++++++++++++++++ .../visa-pets-FL/ot_library/drop-ot/Tools.h | 47 + .../ot_library/drop-ot/UnitTests.cpp | 11 + .../ot_library/drop-ot/UnitTests.h | 15 + .../ot_library/drop-ot/config.h.in | 18 + .../ot_library/frontend/CMakeLists.txt | 15 + .../visa-pets-FL/ot_library/frontend/main.cpp | 243 +++++ .../visa-pets-FL/ot_library/readme.md | 12 + .../ot_library/wrapper/CMakeLists.txt | 8 + .../ot_library/wrapper/CWrapper.cpp | 385 +++++++ .../visa-pets-FL/solution.md | 179 ++++ 42 files changed, 6678 insertions(+) create mode 100644 tools/de-identification/visa-pets-FL/LICENSE.txt create mode 100644 tools/de-identification/visa-pets-FL/README.md create mode 100644 tools/de-identification/visa-pets-FL/centralized/DNNCentralizedModule.py create mode 100644 tools/de-identification/visa-pets-FL/centralized/DataObjects.py create mode 100644 tools/de-identification/visa-pets-FL/centralized/DataPrepUtils.py create mode 100644 tools/de-identification/visa-pets-FL/centralized/NeuralNet.py create mode 100644 tools/de-identification/visa-pets-FL/centralized/solution_centralized.py create mode 100644 tools/de-identification/visa-pets-FL/federated/DNN.py create mode 100644 tools/de-identification/visa-pets-FL/federated/DataObjects.py create mode 100644 tools/de-identification/visa-pets-FL/federated/DataPrepUtils.py create mode 100644 tools/de-identification/visa-pets-FL/federated/NeuralNet.py create mode 100644 tools/de-identification/visa-pets-FL/federated/ot.py create mode 100644 tools/de-identification/visa-pets-FL/federated/solution_federated.py create mode 100644 tools/de-identification/visa-pets-FL/figure.png create mode 100644 tools/de-identification/visa-pets-FL/ot_library/.gitignore create mode 100644 tools/de-identification/visa-pets-FL/ot_library/.vscode/launch.json create mode 100644 tools/de-identification/visa-pets-FL/ot_library/.vscode/settings.json create mode 100644 tools/de-identification/visa-pets-FL/ot_library/CMakeLists.txt create mode 100644 tools/de-identification/visa-pets-FL/ot_library/CMakePresets.json create mode 100644 tools/de-identification/visa-pets-FL/ot_library/cmake/Config.cmake.in create mode 100644 tools/de-identification/visa-pets-FL/ot_library/cmake/buildOptions.cmake create mode 100644 tools/de-identification/visa-pets-FL/ot_library/cmake/findDependancies.cmake create mode 100644 tools/de-identification/visa-pets-FL/ot_library/cmake/install.cmake create mode 100644 tools/de-identification/visa-pets-FL/ot_library/cmake/preamble.cmake create mode 100644 tools/de-identification/visa-pets-FL/ot_library/drop-ot/CMakeLists.txt create mode 100644 tools/de-identification/visa-pets-FL/ot_library/drop-ot/Defines.h create mode 100644 tools/de-identification/visa-pets-FL/ot_library/drop-ot/IknpOtExt.cpp create mode 100644 tools/de-identification/visa-pets-FL/ot_library/drop-ot/IknpOtExt.h create mode 100644 tools/de-identification/visa-pets-FL/ot_library/drop-ot/MasnyRindal.cpp create mode 100644 tools/de-identification/visa-pets-FL/ot_library/drop-ot/MasnyRindal.h create mode 100644 tools/de-identification/visa-pets-FL/ot_library/drop-ot/Tools.cpp create mode 100644 tools/de-identification/visa-pets-FL/ot_library/drop-ot/Tools.h create mode 100644 tools/de-identification/visa-pets-FL/ot_library/drop-ot/UnitTests.cpp create mode 100644 tools/de-identification/visa-pets-FL/ot_library/drop-ot/UnitTests.h create mode 100644 tools/de-identification/visa-pets-FL/ot_library/drop-ot/config.h.in create mode 100644 tools/de-identification/visa-pets-FL/ot_library/frontend/CMakeLists.txt create mode 100644 tools/de-identification/visa-pets-FL/ot_library/frontend/main.cpp create mode 100644 tools/de-identification/visa-pets-FL/ot_library/readme.md create mode 100644 tools/de-identification/visa-pets-FL/ot_library/wrapper/CMakeLists.txt create mode 100644 tools/de-identification/visa-pets-FL/ot_library/wrapper/CWrapper.cpp create mode 100644 tools/de-identification/visa-pets-FL/solution.md diff --git a/tools/de-identification/README.md b/tools/de-identification/README.md index 9edb655..8fe3c56 100644 --- a/tools/de-identification/README.md +++ b/tools/de-identification/README.md @@ -80,3 +80,9 @@ Contributions are listed in alphabetical order. **Keywords:** Differential Privacy, Machine Learning **[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Private-Aggregation-of-Teacher-Ensembles-PATE)** | **[Link to Tool](https://github.com/tensorflow/privacy/tree/master/research)** + +## Visa Pets Federated Learning + +**Keywords:** Differential Privacy, Machine Learning, Privacy Preservering Computation, MPC + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/visa-pets-FL)** | **[Link to Tool](https://github.com/Visa-Research/visa-pets-FL)** diff --git a/tools/de-identification/visa-pets-FL/LICENSE.txt b/tools/de-identification/visa-pets-FL/LICENSE.txt new file mode 100644 index 0000000..fe463e0 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/LICENSE.txt @@ -0,0 +1,408 @@ +Attribution-NonCommercial 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + j. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + k. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + l. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/tools/de-identification/visa-pets-FL/README.md b/tools/de-identification/visa-pets-FL/README.md new file mode 100644 index 0000000..2a2c3a0 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/README.md @@ -0,0 +1,24 @@ +# Visa-Pets-FL + +Copyright 2023 Visa + +This code is meant only as a reference for the Privacy-enhancing technologies (PETs) challenge organized by NIST & NSF on behalf of the U.S. government. The code is specifically written for the competition's test harness. The current implementation would have to be modified to run in a real-world environment. This is not a Visa Product and Visa doesn't guarantee the code is maintained or bug free. + +Our solution folder consists of three components, a centralized solution, a federated solution, and the source code for an oblivious transfer shared library, which we use in our federated solution. We describe the files below: + +* *DataObjects.py:* Defines the train and test datasets +* *DataPrepUtils.py:* Data pre-processing code +* *DNNCentralizedModule.py:* The main centralized code logic +* *NeuralNet.py:* Defines the neural network configuration +* *solution_centralized.py:* The exposed APIs for the execution harness. After pre-processing the data, both fit() and predict() call the corresponding functions in the DNNCentralizedModule.py +* *DNN.py:* Helper functions for the training and inference flows, including flattening per-sample gradient updates into 1d tensors, masking gradients, converting gradients to and from integers. +* *libwrapper.so:* The shared library exposes the apis for us to do oblivious transfer, saving symmetric keys to disk and symmetric encryption & decryption process. +* *ot.py:* Loads and exposes the api of the oblivious transfer library. +* *solution_federated.py:* Our federated solution. The solution contains Client and Strategy functions for both the train and test phases: + +## Instructions + +Traverse into the ot_library & build the library based upon the readme file present inside the folder. The library would be created under ./out/build//wrapper/. Copy the dynamic library libwrapper into the federated folder. + +See the below repo for instructions on how to use the runtime container to run the solution: +https://github.com/drivendataorg/pets-prize-challenge-runtime/tree/main/runtime \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/centralized/DNNCentralizedModule.py b/tools/de-identification/visa-pets-FL/centralized/DNNCentralizedModule.py new file mode 100644 index 0000000..a2d135e --- /dev/null +++ b/tools/de-identification/visa-pets-FL/centralized/DNNCentralizedModule.py @@ -0,0 +1,204 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +from pathlib import Path +import numpy as np +import pandas as pd + +### Libraries for Algorithms +from sklearn.model_selection import train_test_split, KFold, ShuffleSplit, StratifiedKFold, StratifiedShuffleSplit +from sklearn.model_selection import cross_val_score +from sklearn import metrics +from sklearn.metrics import classification_report, confusion_matrix, accuracy_score +from sklearn.preprocessing import StandardScaler, MinMaxScaler + +import torch +from torch.utils.data import Dataset, DataLoader +from torch import nn +import torch.nn.functional as F +from src.NeuralNet import NeuralNetwork +from src.DataObjects import CustomDatasetTrain, CustomDatasetTest +import time + + +BATCH_SIZE_TRAIN = 4096 +USE_BANK_INFO = True +device = "cuda" +print("Using {} device".format(device)) + + +def get_flags(idx, X, BankFlagDict): + # Get bank flag by the status of the receiver bank. + # The 2nd and 4th item list in idx contains the bid, uid of the receiver bank. + bankflags = [BankFlagDict[item] if item in BankFlagDict else False for item in zip(idx[1], idx[3])] + bankflags = [int(item) for item in bankflags] + bankflags = np.array(bankflags).reshape([-1, 1]) + bankflags = torch.tensor(bankflags) + return torch.concat([X, bankflags], axis=1) + + +def norm_clipping(X, max_norm=1.0): + # Norm clipping + X_norm = torch.norm(X, dim=1) + X_norm = torch.clamp(X_norm/max_norm, min=1.0) + X_norm = X_norm.unsqueeze(1).repeat(1, (X.shape)[1]) + return X/X_norm + +def to_int(X, factor=1e6, dtype=torch.int32): + return (X*factor).to(dtype) + +def to_float(X, factor=1e6, dtype=torch.float): + return (X.to(dtype))/factor + +def get_aggregated_grads(X): + return torch.sum(X, axis=0) + +def train(dataloader, model, loss_fn, optimizer, bank_flag_dict=None): + size = len(dataloader.dataset) + model.train() + for batch, (idx, X, y) in enumerate(dataloader): + if USE_BANK_INFO: + X = get_flags(idx, X, bank_flag_dict) + X, y = X.to(device), y.to(device) + pred = model(X.float()) + loss =loss_fn(pred, y) + + optimizer.zero_grad() + loss.backward() + optimizer.step() + + if (batch%(len(dataloader)//10)) == 0: + loss, current = loss.item(), batch*len(X) + print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]") + return model + +def test(dataloader, model, loss_fn, bank_flag_dict=None): + size = len(dataloader.dataset) + num_batches = len(dataloader) + model.eval() + test_loss, correct = 0, 0 + with torch.no_grad(): + for idx, X in dataloader: + if USE_BANK_INFO: + X = get_flags(idx, X, bank_flag_dict) + X = X.to(device) + pred = model(X.float()) + probs = F.softmax(pred, dim=1) + + return probs + +import opacus +import time +from opacus.grad_sample import GradSampleModule +from opacus.grad_sample import register_grad_sampler +from sklearn import metrics + +def fit(processor_path, bank_path, model_path, final_model_name, n_epochs=1): + ### Setup Dataset Path + train_data_path = processor_path + "train_data_processor.csv" + train_label_path = processor_path + "train_label.csv" + bank_flag_dict_path = bank_path + "bank_flag_dict" + scaler_path = model_path + "scaler" + + # load bank flag dictionary + import pickle + with open(bank_flag_dict_path, "rb") as f: + BankFlagDict = pickle.load(f) + + dataset_train = CustomDatasetTrain([train_data_path, train_label_path, scaler_path]) + train_dataloader = DataLoader(dataset_train, batch_size=BATCH_SIZE_TRAIN, shuffle=True) + print("Done! Loading Payment Processor Training") + + ### Retrieve Input Dimension for Neural Nets + X = dataset_train.data + USE_BANK_INFO = True + INPUT_DIM = (X.shape)[1] + if USE_BANK_INFO: + INPUT_DIM +=1 + print("NN Input Dimension is {}".format(INPUT_DIM)) + + ### Specify GPU/CPU + device = "cuda" + print("Using {} device".format(device)) + model = NeuralNetwork(INPUT_DIM).to(device) + loss_fn = nn.CrossEntropyLoss() + + for t in range(n_epochs): + print(f"Epoch {t+1}\n-------------------------") + lr_init = (5e-2/8192)*BATCH_SIZE_TRAIN + lr = lr_init/np.sqrt(t+1) + optimizer = torch.optim.SGD(model.parameters(), lr=lr, weight_decay=5e-4) + t_start = time.time() + model = train(train_dataloader, model, loss_fn, optimizer, BankFlagDict) + t_end = time.time() + print("Training time", t_end - t_start) + + # test model save/load + modelname = model_path+"model"+str(t) + torch.save(model.state_dict(), modelname) + pred_model = NeuralNetwork(INPUT_DIM).to(device) + pred_model.load_state_dict(torch.load(modelname)) + pred_model.to(device) + + import gc + gc.collect() + + torch.save(model.state_dict(), final_model_name) + print("Done") + + +def predict(processor_path, bank_path, model_path, model_name, res_path, format_path): + ### Setup Dataset Path + test_data_path = processor_path + "test_data_processor.csv" + test_label_path = None + bank_flag_dict_path = bank_path + "bank_flag_dict" + scaler_path = model_path + "scaler" + # load bank flag dictionary + import pickle + with open(bank_flag_dict_path, "rb") as f: + BankFlagDict = pickle.load(f) + + dataset = CustomDatasetTest([test_data_path, test_label_path, scaler_path]) + test_dataloader = DataLoader(dataset, batch_size=len(dataset), shuffle=False) + print("Done! Loading Payment Processor Test") + + + ### Retrieve Input Dimension for Neural Nets + X = dataset.data + USE_BANK_INFO = True + INPUT_DIM = (X.shape)[1] + if USE_BANK_INFO: + INPUT_DIM +=1 + print("NN Input Dimension is {}".format(INPUT_DIM)) + + ### Specify GPU/CPU + device = "cuda" + print("Using {} device".format(device)) + + loss_fn = nn.CrossEntropyLoss() + + labelpath = test_data_path + label_df = pd.read_csv(labelpath) + + label_format = pd.read_csv(format_path) + + pred_model = NeuralNetwork(INPUT_DIM).to(device) + pred_model.load_state_dict(torch.load(model_name)) + pred_model.to(device) + pred_model.eval() + probs = test(test_dataloader, pred_model, loss_fn, BankFlagDict) + y_score = probs[:, 1].cpu().numpy().tolist() + label_df["Score"] = y_score + print(label_df.columns) + final_res = label_format.merge(label_df, on="MessageId", how="left") + print(label_df.columns, label_format.columns, final_res.columns) + final_res = final_res.fillna(1e-8) + final_res = final_res.rename(columns={"Score_y":"Score"}) + print(final_res.columns) + final_res[["MessageId", "Score"]].to_csv(res_path, index=False) + print("Done") + diff --git a/tools/de-identification/visa-pets-FL/centralized/DataObjects.py b/tools/de-identification/visa-pets-FL/centralized/DataObjects.py new file mode 100644 index 0000000..845f505 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/centralized/DataObjects.py @@ -0,0 +1,82 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import numpy as np +import pandas as pd +pd.set_option("display.max_columns", None) +from sklearn.preprocessing import StandardScaler, MinMaxScaler +import torch +from torch.utils.data import Dataset, DataLoader +import pickle + +class CustomDatasetTrain(Dataset): + def __init__(self, paths): + [train_data_path, train_label_path, scaler_path] = paths + raw_data = pd.read_csv(train_data_path, index_col="MessageId") + col = raw_data.columns + print(col) + + id_cols = ["Sender", "Receiver", "OrderingAccount", "BeneficiaryAccount"] + ids = raw_data[id_cols] + self.ids = ids.values.tolist() + data = raw_data.drop(columns=id_cols) + self.data = data.values + print(self.data.shape) + + scaler = StandardScaler() + scaler.fit(self.data) + self.data = scaler.transform(self.data) + with open(scaler_path, "wb") as f: + pickle.dump(scaler, f) + + label = pd.read_csv(train_label_path, index_col="MessageId") + self.label = label.values + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + ids = self.ids[idx] + tensor = torch.from_numpy(self.data[idx]) + class_id = torch.tensor(self.label[idx][-1]) + return ids, tensor, class_id + + +class CustomDatasetTest(Dataset): + def __init__(self, paths): + [test_data_path, test_label_path, scaler_path] = paths + raw_data = pd.read_csv(test_data_path, index_col="MessageId") + col = raw_data.columns + print(col) + + id_cols = ["Sender", "Receiver", "OrderingAccount", "BeneficiaryAccount"] + ids = raw_data[id_cols] + self.ids = ids.values.tolist() + data = raw_data.drop(columns=id_cols) + self.data = data.values + print(self.data.shape) + + with open(scaler_path, "rb") as f: + scaler = pickle.load(f) + self.data = scaler.transform(self.data) + if test_label_path: + label = pd.read_csv(test_label_path, index_col="MessageId") + self.label = label.values + self.is_submission = False + else: + self.is_submission = True + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + ids = self.ids[idx] + tensor = torch.from_numpy(self.data[idx]) + if self.is_submission: + return ids, tensor + class_id = torch.tensor(self.label[idx][-1]) + return ids, tensor, class_id diff --git a/tools/de-identification/visa-pets-FL/centralized/DataPrepUtils.py b/tools/de-identification/visa-pets-FL/centralized/DataPrepUtils.py new file mode 100644 index 0000000..ddf40b5 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/centralized/DataPrepUtils.py @@ -0,0 +1,387 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +### Libraries for Data Handling + +from pathlib import Path +import numpy as np +import pandas as pd +import copy + + +pd.set_option("display.max_columns", None) + +### Libraries for Algorithms +from sklearn.model_selection import train_test_split, KFold, ShuffleSplit, StratifiedKFold, StratifiedShuffleSplit +from sklearn.model_selection import cross_val_score +from sklearn import metrics +from sklearn.metrics import classification_report, confusion_matrix, accuracy_score +from sklearn.preprocessing import StandardScaler, MinMaxScaler +import sklearn.utils +import pickle +import time + +def prepare_data_train(path, work_dir): + + # Load train/test data from csv + train = pd.read_csv(path) + train.to_csv(work_dir+'train_copy.csv', index=False) + train["Timestamp"] = train["Timestamp"].astype("datetime64[ns]") + + train = train.set_index("UETR") + train = train.sort_values(by=['Timestamp']) + + # Frequency for each sender + + senders = train["Sender"].unique() + + # Frequency for each receiver + receivers = train["Receiver"].unique() + + # If instructed/settled in the same currency + train["same_currency"] = train.apply(lambda row: int(row['InstructedCurrency']==row['SettlementCurrency']), axis=1) + + # Normalize the transaction amount by exchange rate + exchange_rate = {'GBP':1.22,'EUR':1.08, 'USD':1, 'JPY':0.0078, + 'CAD':0.75, 'INR':0.012, 'AUD':0.7, 'NZD':0.64, + 'DKK':0.14,'IDR':0.000066,'MXN':0.054,'ZAR':0.059, + 'KES':0.0081,'ILS':0.29,'THB':0.03,'TND':0.33, + 'TRY':0.053, 'SGD':0.76, 'MAD':0.098, 'AED':0.27, + 'SEK':0.096, 'CHF':1.08, 'SAR':0.27, 'PLN':0.23, + 'BHD':2.65, "CZK":0.045, "NAD":0.059, "NOK":0.10, + "HKD":0.13, "HUF":0.0027, "MYR":0.23, "LKR":0.0027, + "CNY":0.15, "EGP":0.034, "KRW":0.00081, "HRK":0.143122, + "BDT":0.0096, "PHP":0.018, "BOB":0.14, "RON":0.22, + "OMR":2.6, "KWD":3.28, "NPR":0.0076, "FJD":0.46, + "MUR":0.023, "JOD":1.41, "VND":0.000043, "ISK":0.0070, + "BRL":0.2, "TWD":0.033, "QAR":0.27, "XOF":0.0017, + "RSD":0.0092, "COP":0.00021, "BGN":0.55, "RUB":0.015, + "BWP":0.078, "TZS":0.00043, "BAM":0.55 + } + + + train["exchange_rate"] = train["InstructedCurrency"].map(exchange_rate) + train["NormalizedAmount"] = train["InstructedAmount"]*train["exchange_rate"] + train.drop(columns="exchange_rate", inplace=True) + + # Hour + train["hour"] = train["Timestamp"].dt.hour + train["day"] = train["Timestamp"].dt.day + + + join_keys = ['UETR', 'Timestamp'] + + start_time = time.time() + + keys = ["OrderingName", "BeneficiaryName", "InstructedCurrency", "SettlementCurrency"] + fields = ["MessageId", "Timestamp"] + df = train.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_avg_count" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + train["sr_currency_pair_avg_count"] = train["sr_currency_pair_avg_count"]/train["day"] + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + df = train.groupby(keys)[fields].rolling('1H', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_hour_freq" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + fields = ["SettlementAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1 , on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_settlement_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + fields = ["InstructedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_instructed_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["OrderingName", "BeneficiaryName"] + fields = ["MessageId", "Timestamp"] + df = train.groupby(keys)[fields].rolling('7D', closed="left", on="Timestamp").count() + rename_dict = {item:item+"sr_pair_count_last_7d" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["OrderingName"] + fields = ["NormalizedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"sender_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["BeneficiaryName"] + fields = ["NormalizedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"receiver_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + print(train.columns[train.isna().any()].tolist()) + + train.drop(columns=["day", "hour"], inplace=True) + train = train.fillna(0) + # Exclude below categorical columns for training and testing + columns_to_drop = [ + #"UETR", + "TransactionReference", + "OrderingName", + "OrderingStreet", + "OrderingCountryCityZip", + "BeneficiaryName", + "BeneficiaryStreet", + "BeneficiaryCountryCityZip", + "SettlementDate", + "SettlementCurrency", + "InstructedCurrency", + "Timestamp" + ] + + train = train.drop(columns_to_drop, axis=1) + + print("train columns are", train.columns) + train[train.select_dtypes(include=['number']).columns] = train[train.select_dtypes(include=['number']).columns].clip(-1e20, 1e20) + + pd_train = train + pd_train['Label'].to_csv(work_dir+"train_label.csv") + + from sklearn.utils import resample + pd_majority = pd_train[pd_train.Label==0] + pd_minority = pd_train[pd_train.Label==1] + + print(len(pd_majority), len(pd_minority)) + + pd_minority_upsampled = resample(pd_minority, + replace=True, + n_samples=int(len(pd_majority)), + random_state=42) + + print(len(pd_majority), len(pd_minority_upsampled)) + pd_train = pd.concat([pd_majority, pd_minority_upsampled]) + DOWNSAMPLE_FACTOR = 1 + + pd_train_subset = resample(pd_train, + replace=False, + n_samples=int(len(pd_train)/DOWNSAMPLE_FACTOR), + random_state=42) + + pd_train_temp = pd_train_subset + pd_train_temp = pd_train_temp.fillna(0) + columns_to_drop = ["Label"] + pd_train_temp[['MessageId', 'Label']].to_csv(work_dir+'train_label.csv', index=False) + pd_train_temp.drop(columns=columns_to_drop, inplace=True) + pd_train_temp.to_csv(work_dir+'train_data_processor.csv', index=False) + + + +def prepare_data_test(path, work_dir): + + TEST_MODE = False + test = pd.read_csv(path) + actual_test = copy.deepcopy(test) + train = pd.read_csv(work_dir+'train_copy.csv') + test["Timestamp"] = test["Timestamp"].astype("datetime64[ns]") + train["Timestamp"] = train["Timestamp"].astype("datetime64[ns]") + actual_test["Timestamp"] = actual_test["Timestamp"].astype("datetime64[ns]") + if TEST_MODE: + test = pd.concat([train, test.drop(columns=["Label"])]) + else: + test = pd.concat([train, test]) + test = test.set_index('UETR') + test["Timestamp"] = test["Timestamp"].astype("datetime64[ns]") + + test = test.sort_values(by=['Timestamp']) + test["same_currency"] = test.apply(lambda row: int(row['InstructedCurrency']==row['SettlementCurrency']), axis=1) + + exchange_rate = {'GBP':1.22,'EUR':1.08, 'USD':1, 'JPY':0.0078, + 'CAD':0.75, 'INR':0.012, 'AUD':0.7, 'NZD':0.64, + 'DKK':0.14,'IDR':0.000066,'MXN':0.054,'ZAR':0.059, + 'KES':0.0081,'ILS':0.29,'THB':0.03,'TND':0.33, + 'TRY':0.053, 'SGD':0.76, 'MAD':0.098, 'AED':0.27, + 'SEK':0.096, 'CHF':1.08, 'SAR':0.27, 'PLN':0.23, + 'BHD':2.65, "CZK":0.045, "NAD":0.059, "NOK":0.10, + "HKD":0.13, "HUF":0.0027, "MYR":0.23, "LKR":0.0027, + "CNY":0.15, "EGP":0.034, "KRW":0.00081, "HRK":0.143122, + "BDT":0.0096, "PHP":0.018, "BOB":0.14, "RON":0.22, + "OMR":2.6, "KWD":3.28, "NPR":0.0076, "FJD":0.46, + "MUR":0.023, "JOD":1.41, "VND":0.000043, "ISK":0.0070, + "BRL":0.2, "TWD":0.033, "QAR":0.27, "XOF":0.0017, + "RSD":0.0092, "COP":0.00021, "BGN":0.55, "RUB":0.015, + "BWP":0.078, "TZS":0.00043, "BAM":0.55 + } + + test["exchange_rate"] = test["InstructedCurrency"].map(exchange_rate) + test["NormalizedAmount"] = test["InstructedAmount"]*test["exchange_rate"] + test.drop(columns="exchange_rate", inplace=True) + + test["hour"] = test["Timestamp"].dt.hour + test["day"] = test["Timestamp"].dt.day + + + join_keys = ['UETR', 'Timestamp'] + + keys = ["OrderingName", "BeneficiaryName", "InstructedCurrency", "SettlementCurrency"] + fields = ["MessageId", "Timestamp"] + df = test.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_avg_count" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + test["sr_currency_pair_avg_count"] = test["sr_currency_pair_avg_count"]/test["day"] + + + df = test.groupby(keys)[fields].rolling('1H', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_hour_freq" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + fields = ["SettlementAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_settlement_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + fields = ["InstructedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_instructed_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + keys = ["OrderingName", "BeneficiaryName"] + fields = ["MessageId", "Timestamp"] + df = test.groupby(keys)[fields].rolling('7D', closed="left", on="Timestamp").count() + rename_dict = {item:item+"sr_pair_count_last_7d" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + keys = ["OrderingName"] + fields = ["NormalizedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"sender_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + keys = ["BeneficiaryName"] + fields = ["NormalizedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"receiver_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + test.drop(columns=["day", "hour"], inplace=True) + test = test.fillna(0) + + print(test.columns[test.isna().any()].tolist()) + + test = test.reset_index() + test = test.rename(columns={'index':'UETR'}) + test = actual_test[['UETR']].merge(test, left_on=["UETR"], right_on=["UETR"], how="left") + + # Exclude below categorical columns for training and testing + columns_to_drop = [ + "UETR", + "TransactionReference", + "OrderingName", + "OrderingStreet", + "OrderingCountryCityZip", + "BeneficiaryName", + "BeneficiaryStreet", + "BeneficiaryCountryCityZip", + "SettlementDate", + "SettlementCurrency", + "InstructedCurrency", + "Timestamp" + ] + + test = test.drop(columns_to_drop, axis=1) + + print("test columns:", test.columns) + test[test.select_dtypes(include=['number']).columns] = test[test.select_dtypes(include=['number']).columns].clip(-1e20, 1e20) + print(test.columns) + + from sklearn.utils import resample + DOWNSAMPLE_FACTOR = 1 + pd_test = test + pd_test = resample(pd_test, + replace=False, + n_samples=int(len(pd_test)/DOWNSAMPLE_FACTOR) + ) + pd_test = pd_test.fillna(0) + + if TEST_MODE: + pd_test.to_csv(work_dir+'test_data_processor.csv', index=False) + else: + columns_to_drop = ["Label"] + pd_test[['MessageId','Label']].to_csv(work_dir+'test_label.csv', index=False) + pd_test.drop(columns=columns_to_drop, inplace=True) + pd_test.to_csv(work_dir+'test_data_processor.csv', index=False) + + +def prepare_bank_data(bank_path, work_dir): + ### Bank Data + data_bank = pd.read_csv(bank_path) + data_bank.columns + data_bank.head(1) + + BankFlagDict = {} + for i in range(len(data_bank)): + row = data_bank.loc[i] + BankFlagDict[(row["Bank"], row["Account"])] = (row["Flags"]==0) + + import pickle + with open(work_dir+"bank_flag_dict", "wb") as f: + pickle.dump(BankFlagDict, f) + + + +def explore_dataset(datapaths): + [train_path, test_path] = datapaths + train = pd.read_csv(train_path) + test = pd.read_csv(test_path) + print(train.columns, test.columns) + print(train["SettlementDate"].unique(), test["SettlementDate"].unique()) + print(train["InstructedCurrency"].unique(), test["InstructedCurrency"].unique()) + print(train["SettlementCurrency"].unique(), test["SettlementCurrency"].unique()) + lst1 = (train["Sender"]+train["OrderingAccount"]).unique().tolist() + lst2 = (test["Sender"]+test["OrderingAccount"]).unique().tolist() + print(len(lst1), len(lst2)) + lst_diff = list(set(lst2)-set(lst1)) + print(len(lst_diff)) + lst_diff = list(set(lst1)-set(lst2)) + print(len(lst_diff)) diff --git a/tools/de-identification/visa-pets-FL/centralized/NeuralNet.py b/tools/de-identification/visa-pets-FL/centralized/NeuralNet.py new file mode 100644 index 0000000..71033ec --- /dev/null +++ b/tools/de-identification/visa-pets-FL/centralized/NeuralNet.py @@ -0,0 +1,28 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import torch +from torch import nn + +class NeuralNetwork(nn.Module): + def __init__(self, input_dim): + super(NeuralNetwork, self).__init__() + self.flatten = nn.Flatten() + self.linear_relu_stack = nn.Sequential( + nn.Linear(input_dim, 128), + #nn.ReLU(), + #nn.Linear(256, 64), + nn.ReLU(), + nn.Linear(128, 16), + nn.ReLU(), + nn.Linear(16, 2) + ) + + def forward(self, x): + x = self.flatten(x) + logits = self.linear_relu_stack(x) + return logits diff --git a/tools/de-identification/visa-pets-FL/centralized/solution_centralized.py b/tools/de-identification/visa-pets-FL/centralized/solution_centralized.py new file mode 100644 index 0000000..996ca30 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/centralized/solution_centralized.py @@ -0,0 +1,47 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +from pathlib import Path + +import pandas as pd + +import src.DNNCentralizedModule as DNNUtils + +from src.DataPrepUtils import (prepare_data_train, + prepare_data_test, + prepare_bank_data) + +def fit(processor_data_path: Path, bank_data_path: Path, model_dir: Path): + prepare_data = True + processor_data_path, bank_data_path, model_dir = str(processor_data_path), str(bank_data_path), str(model_dir) + if prepare_data: + prepare_data_train(processor_data_path, model_dir) + prepare_bank_data(bank_data_path, model_dir) + n_epochs = 20 + final_model_name = model_dir + "model" + DNNUtils.fit(model_dir, model_dir, model_dir, final_model_name, n_epochs=n_epochs) + + +def predict( + processor_data_path: Path, + bank_data_path: Path, + model_dir: Path, + preds_format_path: Path, + preds_dest_path: Path, +): + prepare_data = True + processor_data_path = str(processor_data_path) + bank_data_path = str(bank_data_path) + model_dir = str(model_dir) + preds_format_path = str(preds_format_path) + preds_dest_path = str(preds_dest_path) + if prepare_data: + prepare_data_test(processor_data_path, model_dir) + prepare_bank_data(bank_data_path, model_dir) + model_name = model_dir + "model" + DNNUtils.predict(model_dir, model_dir, model_dir, model_name, preds_dest_path, preds_format_path) + diff --git a/tools/de-identification/visa-pets-FL/federated/DNN.py b/tools/de-identification/visa-pets-FL/federated/DNN.py new file mode 100644 index 0000000..3ef7e61 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/federated/DNN.py @@ -0,0 +1,126 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import numpy as np +import torch +from torch import nn +import torch.nn.functional as F + +from .NeuralNet import NeuralNetwork +from opacus.grad_sample import GradSampleModule + +def pad_flags(X, flag=0): + # Pad a column of one or zero bank flag. + n_elem = (X.shape)[0] + bankflags = np.ones((n_elem, 1))*flag + bankflags = torch.tensor(bankflags) + return torch.concat([X, bankflags], axis=1) + + +def norm_clipping(X, max_norm=1.0): + # Norm clipping + X_norm = torch.norm(X, dim=1) + X_norm = torch.clamp(X_norm/max_norm, min=1.0) + X_norm = X_norm.unsqueeze(1).repeat(1, (X.shape)[1]) + return X/X_norm + +def to_int(X, factor=1e6, dtype=torch.int32): + return (X*factor).to(dtype) + +def to_float(X, factor=1e6, dtype=torch.float): + return (X.to(dtype))/factor + +def get_aggregated_grads(X): + return torch.sum(X, axis=0) + +def get_noise(shape, device, dtype=torch.int32): + if dtype==torch.int32: + low, high = -2147483648, 2147483648 + return torch.randint(low, high, shape, dtype=dtype, device=device) + +def flatten_grads(model): + grads_flat = [] + param_shapes = [] + for i, p in enumerate(model.parameters()): + param_shapes.append(p.grad.shape) + grads_flat.append(torch.flatten(p.grad_sample, start_dim=1)) + grads_flat = torch.cat(grads_flat, axis=1) + grads_flat = torch.nan_to_num(grads_flat) + return grads_flat, param_shapes + + +def expand_flattened_grads(model, grads_flat, param_shapes): + curr_pos = 0 + for i, p in enumerate(model.parameters()): + param_shape = param_shapes[i] + n_param = np.prod(param_shape) + end_pos = curr_pos+n_param + p.grad = grads_flat[curr_pos:end_pos].reshape(param_shape) + curr_pos = end_pos + +def clear_per_sample_grad(model): + for i, p in enumerate(model.parameters()): + p.grad_sample = None + + +def prepare_serialization(g, device, scale=1e3, dtype=torch.int32): + + # Convert the input to integer value + g = to_int(g, factor=scale, dtype=dtype) + + # Split the gradients with flag 0 and 1 + s = (g.shape)[0]//2 + g0, g1 = g[:s, :], g[s:, :] + + eps = get_noise(g0.shape, device=device) + eps_sum = torch.sum(eps, axis=0, dtype=torch.int32) + g0 = (g0+eps).to(torch.int32) + g1 = (g1+eps).to(torch.int32) + + + return g0, g1, eps + +def prepare_serialization_with_rounding(g, device, dtype=torch.int32): + + # Split the gradients with flag 0 and 1 + s = (g.shape)[0]//2 + g0, g1 = g[:s, :], g[s:, :] + g0 = torch.argmax(g0, dim=1) + g1 = torch.argmax(g1, dim=1) + + eps = get_noise(g0.shape, device=device) + #eps = torch.zeros(g0.shape) + eps_sum = torch.sum(eps, axis=0, dtype=torch.int32) + g0 = (g0+eps).to(torch.int32) + g1 = (g1+eps).to(torch.int32) + + + return g0, g1, eps + +def complete_deserialization(g, eps, + scale=1e3, + dtype_int=torch.int32, + dtype_float=torch.float): + + # Subtract the random int used for OT + g = (g-eps).to(dtype_int) + # Convert back to real value after OT + g = to_float(g, factor=scale, dtype=dtype_float) + return g + +def init_learning(device, + batch_size, + i_epoch, model=None, + INPUT_DIM=9): + if model==None: + model = GradSampleModule(NeuralNetwork(INPUT_DIM).to(device), force_functorch=True) + loss_fn = nn.CrossEntropyLoss() + batch_size = batch_size + lr_init = (5e-2/8192)*batch_size + lr = lr_init/np.sqrt(i_epoch+1) + optimizer = torch.optim.SGD(model.parameters(), lr=lr, weight_decay=1e-5) + return model, optimizer, loss_fn \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/federated/DataObjects.py b/tools/de-identification/visa-pets-FL/federated/DataObjects.py new file mode 100644 index 0000000..845f505 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/federated/DataObjects.py @@ -0,0 +1,82 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import numpy as np +import pandas as pd +pd.set_option("display.max_columns", None) +from sklearn.preprocessing import StandardScaler, MinMaxScaler +import torch +from torch.utils.data import Dataset, DataLoader +import pickle + +class CustomDatasetTrain(Dataset): + def __init__(self, paths): + [train_data_path, train_label_path, scaler_path] = paths + raw_data = pd.read_csv(train_data_path, index_col="MessageId") + col = raw_data.columns + print(col) + + id_cols = ["Sender", "Receiver", "OrderingAccount", "BeneficiaryAccount"] + ids = raw_data[id_cols] + self.ids = ids.values.tolist() + data = raw_data.drop(columns=id_cols) + self.data = data.values + print(self.data.shape) + + scaler = StandardScaler() + scaler.fit(self.data) + self.data = scaler.transform(self.data) + with open(scaler_path, "wb") as f: + pickle.dump(scaler, f) + + label = pd.read_csv(train_label_path, index_col="MessageId") + self.label = label.values + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + ids = self.ids[idx] + tensor = torch.from_numpy(self.data[idx]) + class_id = torch.tensor(self.label[idx][-1]) + return ids, tensor, class_id + + +class CustomDatasetTest(Dataset): + def __init__(self, paths): + [test_data_path, test_label_path, scaler_path] = paths + raw_data = pd.read_csv(test_data_path, index_col="MessageId") + col = raw_data.columns + print(col) + + id_cols = ["Sender", "Receiver", "OrderingAccount", "BeneficiaryAccount"] + ids = raw_data[id_cols] + self.ids = ids.values.tolist() + data = raw_data.drop(columns=id_cols) + self.data = data.values + print(self.data.shape) + + with open(scaler_path, "rb") as f: + scaler = pickle.load(f) + self.data = scaler.transform(self.data) + if test_label_path: + label = pd.read_csv(test_label_path, index_col="MessageId") + self.label = label.values + self.is_submission = False + else: + self.is_submission = True + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + ids = self.ids[idx] + tensor = torch.from_numpy(self.data[idx]) + if self.is_submission: + return ids, tensor + class_id = torch.tensor(self.label[idx][-1]) + return ids, tensor, class_id diff --git a/tools/de-identification/visa-pets-FL/federated/DataPrepUtils.py b/tools/de-identification/visa-pets-FL/federated/DataPrepUtils.py new file mode 100644 index 0000000..b62f4ad --- /dev/null +++ b/tools/de-identification/visa-pets-FL/federated/DataPrepUtils.py @@ -0,0 +1,454 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +### Libraries for Data Handling + +from pathlib import Path +import numpy as np +import pandas as pd +import copy +import torch + + +pd.set_option("display.max_columns", None) + +### Libraries for Algorithms +from sklearn.model_selection import train_test_split, KFold, ShuffleSplit, StratifiedKFold, StratifiedShuffleSplit +from sklearn.model_selection import cross_val_score +from sklearn import metrics +from sklearn.metrics import classification_report, confusion_matrix, accuracy_score +from sklearn.preprocessing import StandardScaler, MinMaxScaler +import sklearn.utils +import pickle +import time + +RESAMPLE_RATIO = 10 + +def prepare_data_train(path, work_dir): + + # Load train/test data from csv + train = pd.read_csv(path) + train.to_csv(Path.joinpath(work_dir,'train_copy.csv'), index=False) + train["Timestamp"] = train["Timestamp"].astype("datetime64[ns]") + + train = train.set_index("MessageId") + train = train.sort_values(by=['Timestamp']) + + train['uniq'] = train['BeneficiaryAccount'] + train['Receiver'] + df4 = train[~train['uniq'].isin(train.loc[train['Label'].eq(0), 'uniq'])] + unknown_accounts = df4[['Receiver','BeneficiaryAccount']].drop_duplicates() + #print(len(df4)) + #unknown_accounts = train[['Receiver','BeneficiaryAccount']].drop_duplicates() + unknown_accounts.to_csv(Path.joinpath(work_dir, "unknown_accounts.csv"), index=False) + #train.drop(['uniq'], axis=1) + + + # Frequency for each sender + + senders = train["Sender"].unique() + + # Frequency for each receiver + receivers = train["Receiver"].unique() + + # If instructed/settled in the same currency + train["same_currency"] = train.apply(lambda row: int(row['InstructedCurrency']==row['SettlementCurrency']), axis=1) + + # Normalize the transaction amount by exchange rate + exchange_rate = {'GBP':1.22,'EUR':1.08, 'USD':1, 'JPY':0.0078, + 'CAD':0.75, 'INR':0.012, 'AUD':0.7, 'NZD':0.64, + 'DKK':0.14,'IDR':0.000066,'MXN':0.054,'ZAR':0.059, + 'KES':0.0081,'ILS':0.29,'THB':0.03,'TND':0.33, + 'TRY':0.053, 'SGD':0.76, 'MAD':0.098, 'AED':0.27, + 'SEK':0.096, 'CHF':1.08, 'SAR':0.27, 'PLN':0.23, + 'BHD':2.65, "CZK":0.045, "NAD":0.059, "NOK":0.10, + "HKD":0.13, "HUF":0.0027, "MYR":0.23, "LKR":0.0027, + "CNY":0.15, "EGP":0.034, "KRW":0.00081, "HRK":0.143122, + "BDT":0.0096, "PHP":0.018, "BOB":0.14, "RON":0.22, + "OMR":2.6, "KWD":3.28, "NPR":0.0076, "FJD":0.46, + "MUR":0.023, "JOD":1.41, "VND":0.000043, "ISK":0.0070, + "BRL":0.2, "TWD":0.033, "QAR":0.27, "XOF":0.0017, + "RSD":0.0092, "COP":0.00021, "BGN":0.55, "RUB":0.015, + "BWP":0.078, "TZS":0.00043, "BAM":0.55 + } + + + train["exchange_rate"] = train["InstructedCurrency"].map(exchange_rate) + train["NormalizedAmount"] = train["InstructedAmount"]*train["exchange_rate"] + train.drop(columns="exchange_rate", inplace=True) + + # Hour + train["hour"] = train["Timestamp"].dt.hour + train["day"] = train["Timestamp"].dt.day + + #train.drop(columns=["day", "hour"], inplace=True) + + + join_keys = ['MessageId', 'Timestamp'] + + start_time = time.time() + + keys = ["Sender", "Receiver", "InstructedCurrency", "SettlementCurrency"] + fields = ["InstructedAmount", "SettlementAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").max() + rename_dict = {item:item+"bank_pair_max" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + df = train.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").min() + rename_dict = {item:item+"bank_pair_min" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + df = train.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").mean() + rename_dict = {item:item+"bank_pair_mean" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + keys = ["OrderingName", "BeneficiaryName", "InstructedCurrency", "SettlementCurrency"] + fields = ["UETR", "Timestamp"] + df = train.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_avg_count" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + train["sr_currency_pair_avg_count"] = train["sr_currency_pair_avg_count"]/train["day"] + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + df = train.groupby(keys)[fields].rolling('1H', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_hour_freq" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + fields = ["SettlementAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1 , on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_settlement_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + fields = ["InstructedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_instructed_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["OrderingName", "BeneficiaryName"] + fields = ["UETR", "Timestamp"] + df = train.groupby(keys)[fields].rolling('7D', closed="left", on="Timestamp").count() + rename_dict = {item:item+"sr_pair_count_last_7d" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["OrderingName"] + fields = ["NormalizedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"sender_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["BeneficiaryName"] + fields = ["NormalizedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"receiver_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + train = train.reset_index() + train = train.rename(columns={'index':'MessageId'}) + + print(train.columns[train.isna().any()].tolist()) + + train.drop(columns=["day", "hour"], inplace=True) + train = train.fillna(0) + # Exclude below categorical columns for training and testing + columns_to_drop = [ + "UETR", + "TransactionReference", + "OrderingName", + "OrderingStreet", + "OrderingCountryCityZip", + "BeneficiaryName", + "BeneficiaryStreet", + "BeneficiaryCountryCityZip", + "SettlementDate", + "SettlementCurrency", + "InstructedCurrency", + "Timestamp", + "uniq" + ] + + train = train.drop(columns_to_drop, axis=1) + + #print("train columns are", train.columns) + train[train.select_dtypes(include=['number']).columns] = train[train.select_dtypes(include=['number']).columns].clip(-1e20, 1e20) + + pd_train = train + pd_train['Label'].to_csv(Path.joinpath(work_dir,"train_label.csv")) + + from sklearn.utils import resample + pd_majority = pd_train[pd_train.Label==0] + pd_minority = pd_train[pd_train.Label==1] + + print(len(pd_majority), len(pd_minority)) + + pd_minority_upsampled = resample(pd_minority, + replace=True, + n_samples=int(len(pd_majority)/RESAMPLE_RATIO), + random_state=42) + + #print(len(pd_majority), len(pd_minority_upsampled)) + pd_train = pd.concat([pd_majority, pd_minority_upsampled]) + DOWNSAMPLE_FACTOR = 1 + + pd_train_subset = resample(pd_train, + replace=False, + n_samples=int(len(pd_train)/DOWNSAMPLE_FACTOR), + random_state=42) + + pd_train_temp = pd_train_subset + pd_train_temp = pd_train_temp.fillna(0) + columns_to_drop = ["Label"] + pd_train_temp[['MessageId', 'Label']].to_csv(Path.joinpath(work_dir,'train_label.csv'), index=False) + pd_train_temp.drop(columns=columns_to_drop, inplace=True) + pd_train_temp.to_csv(Path.joinpath(work_dir,'train_data_processor.csv'), index=False) + + + +def prepare_data_test(path, work_dir): + + TEST_MODE = True + test = pd.read_csv(path) + actual_test = copy.deepcopy(test) + + unknown_accounts = test[['Receiver','BeneficiaryAccount']].drop_duplicates() + unknown_accounts.to_csv(Path.joinpath(work_dir, "unknown_accounts.csv"), index=False) + + + train = pd.read_csv(Path.joinpath(work_dir,'train_copy.csv')) + test["Timestamp"] = test["Timestamp"].astype("datetime64[ns]") + train["Timestamp"] = train["Timestamp"].astype("datetime64[ns]") + actual_test["Timestamp"] = actual_test["Timestamp"].astype("datetime64[ns]") + if TEST_MODE: + test = pd.concat([train.drop(columns=["Label"]), test]) + else: + test = pd.concat([train, test]) + + #print(test.columns) + #print(test.index.is_unique) + #print(test.index) + #print(test.columns.is_unique) + test = test.set_index('MessageId') + print(test.index.is_unique) + test["Timestamp"] = test["Timestamp"].astype("datetime64[ns]") + + test = test.sort_values(by=['Timestamp']) + test["same_currency"] = test.apply(lambda row: int(row['InstructedCurrency']==row['SettlementCurrency']), axis=1) + + exchange_rate = {'GBP':1.22,'EUR':1.08, 'USD':1, 'JPY':0.0078, + 'CAD':0.75, 'INR':0.012, 'AUD':0.7, 'NZD':0.64, + 'DKK':0.14,'IDR':0.000066,'MXN':0.054,'ZAR':0.059, + 'KES':0.0081,'ILS':0.29,'THB':0.03,'TND':0.33, + 'TRY':0.053, 'SGD':0.76, 'MAD':0.098, 'AED':0.27, + 'SEK':0.096, 'CHF':1.08, 'SAR':0.27, 'PLN':0.23, + 'BHD':2.65, "CZK":0.045, "NAD":0.059, "NOK":0.10, + "HKD":0.13, "HUF":0.0027, "MYR":0.23, "LKR":0.0027, + "CNY":0.15, "EGP":0.034, "KRW":0.00081, "HRK":0.143122, + "BDT":0.0096, "PHP":0.018, "BOB":0.14, "RON":0.22, + "OMR":2.6, "KWD":3.28, "NPR":0.0076, "FJD":0.46, + "MUR":0.023, "JOD":1.41, "VND":0.000043, "ISK":0.0070, + "BRL":0.2, "TWD":0.033, "QAR":0.27, "XOF":0.0017, + "RSD":0.0092, "COP":0.00021, "BGN":0.55, "RUB":0.015, + "BWP":0.078, "TZS":0.00043, "BAM":0.55 + } + + test["exchange_rate"] = test["InstructedCurrency"].map(exchange_rate) + test["NormalizedAmount"] = test["InstructedAmount"]*test["exchange_rate"] + test.drop(columns="exchange_rate", inplace=True) + + test["hour"] = test["Timestamp"].dt.hour + test["day"] = test["Timestamp"].dt.day + + + join_keys = ['MessageId', 'Timestamp'] + + keys = ["Sender", "Receiver", "InstructedCurrency", "SettlementCurrency"] + fields = ["InstructedAmount", "SettlementAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").max() + rename_dict = {item:item+"bank_pair_max" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + df = test.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").min() + rename_dict = {item:item+"bank_pair_min" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + df = test.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").mean() + rename_dict = {item:item+"bank_pair_mean" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + keys = ["OrderingName", "BeneficiaryName", "InstructedCurrency", "SettlementCurrency"] + fields = ["UETR", "Timestamp"] + df = test.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_avg_count" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + test["sr_currency_pair_avg_count"] = test["sr_currency_pair_avg_count"]/test["day"] + + + df = test.groupby(keys)[fields].rolling('1H', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_hour_freq" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + fields = ["SettlementAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_settlement_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + fields = ["InstructedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_instructed_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + keys = ["OrderingName", "BeneficiaryName"] + fields = ["UETR", "Timestamp"] + df = test.groupby(keys)[fields].rolling('7D', closed="left", on="Timestamp").count() + rename_dict = {item:item+"sr_pair_count_last_7d" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + keys = ["OrderingName"] + fields = ["NormalizedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"sender_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + keys = ["BeneficiaryName"] + fields = ["NormalizedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"receiver_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + test.drop(columns=["day", "hour"], inplace=True) + test = test.fillna(0) + + #print(test.columns[test.isna().any()].tolist()) + + test = test.reset_index() + test = test.rename(columns={'index':'MessageId'}) + test = actual_test[['MessageId']].merge(test, left_on=["MessageId"], right_on=["MessageId"], how="left") + + # Exclude below categorical columns for training and testing + columns_to_drop = [ + "UETR", + "TransactionReference", + "OrderingName", + "OrderingStreet", + "OrderingCountryCityZip", + "BeneficiaryName", + "BeneficiaryStreet", + "BeneficiaryCountryCityZip", + "SettlementDate", + "SettlementCurrency", + "InstructedCurrency", + "Timestamp" + ] + + test = test.drop(columns_to_drop, axis=1) + + #print("test columns:", test.columns) + test[test.select_dtypes(include=['number']).columns] = test[test.select_dtypes(include=['number']).columns].clip(-1e20, 1e20) + + #print(test.columns) + + from sklearn.utils import resample + DOWNSAMPLE_FACTOR = 1 + pd_test = test + pd_test = resample(pd_test, + replace=False, + n_samples=int(len(pd_test)/DOWNSAMPLE_FACTOR) + ) + pd_test = pd_test.fillna(0) + + if TEST_MODE: + pd_test.to_csv(Path.joinpath(work_dir, 'test_data_processor.csv'), index=False) + else: + columns_to_drop = ["Label"] + pd_test[['MessageId','Label']].to_csv(Path.joinpath(work_dir,'test_label.csv'), index=False) + pd_test.drop(columns=columns_to_drop, inplace=True) + pd_test.to_csv(Path.joinpath(work_dir,'test_data_processor.csv'), index=False) + + +def prepare_bank_data(bank_path, work_dir): + #bank_path, work_dir = str(bank_path), str(work_dir) + ### Bank Data + data_bank = pd.read_csv(bank_path) + + banks = data_bank.Bank.drop_duplicates().to_list() + #print("NUMBER OF BANKS: " + str(len(banks))) + #data_bank.columns + #data_bank.head(1) + + #BankFlagDict = {} + #for i in range(len(data_bank)): + # row = data_bank.loc[i] + # BankFlagDict[(row["Bank"], row["Account"])] = (row["Flags"]==0) + # + bankFlagDict = dict(zip(zip(data_bank.Bank, data_bank.Account), data_bank.Flags.eq(0))) + torch.save(bankFlagDict, Path.joinpath(work_dir, "bank_flag_dict")) + return banks + + +def explore_dataset(datapaths): + [train_path, test_path] = datapaths + train = pd.read_csv(train_path) + test = pd.read_csv(test_path) + print(train.columns, test.columns) + print(train["SettlementDate"].unique(), test["SettlementDate"].unique()) + print(train["InstructedCurrency"].unique(), test["InstructedCurrency"].unique()) + print(train["SettlementCurrency"].unique(), test["SettlementCurrency"].unique()) + lst1 = (train["Sender"]+train["OrderingAccount"]).unique().tolist() + lst2 = (test["Sender"]+test["OrderingAccount"]).unique().tolist() + print(len(lst1), len(lst2)) + lst_diff = list(set(lst2)-set(lst1)) + print(len(lst_diff)) + lst_diff = list(set(lst1)-set(lst2)) + print(len(lst_diff)) diff --git a/tools/de-identification/visa-pets-FL/federated/NeuralNet.py b/tools/de-identification/visa-pets-FL/federated/NeuralNet.py new file mode 100644 index 0000000..71033ec --- /dev/null +++ b/tools/de-identification/visa-pets-FL/federated/NeuralNet.py @@ -0,0 +1,28 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import torch +from torch import nn + +class NeuralNetwork(nn.Module): + def __init__(self, input_dim): + super(NeuralNetwork, self).__init__() + self.flatten = nn.Flatten() + self.linear_relu_stack = nn.Sequential( + nn.Linear(input_dim, 128), + #nn.ReLU(), + #nn.Linear(256, 64), + nn.ReLU(), + nn.Linear(128, 16), + nn.ReLU(), + nn.Linear(16, 2) + ) + + def forward(self, x): + x = self.flatten(x) + logits = self.linear_relu_stack(x) + return logits diff --git a/tools/de-identification/visa-pets-FL/federated/ot.py b/tools/de-identification/visa-pets-FL/federated/ot.py new file mode 100644 index 0000000..fc4e419 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/federated/ot.py @@ -0,0 +1,161 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import ctypes as ct +from pathlib import Path +import sys +import torch + +clib_path = Path(sys.modules[__name__].__file__).parent.joinpath("libwrapper.so") +clib = ct.CDLL(str(clib_path)) + +def senderSetup1(stateDir, bankID): + #return 0, 0 + clib.senderSetup1.argtypes = [ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_int), ct.POINTER(ct.c_longlong)] + #clib.senderSetup1.restype = ct.c_char_p + clib.senderSetup1.restype = ct.POINTER(ct.c_char) + senderSetup1ByteSize = ct.c_int() + senderSetup1State = ct.c_longlong() + + senderSetup1Bytes = clib.senderSetup1(stateDir.encode('utf-8'), bankID.encode('utf-8'), ct.byref(senderSetup1ByteSize), ct.byref(senderSetup1State)) + + # ct.cast(senderSetup1ReturnValue , ct.) + # uchar_p = ct.cast(senderSetup1ReturnValue, ct.POINTER(ct.c_ubyte)) + #print(ct.string_at(senderSetup1ReturnValue, senderSetup1ReturnSize.value) , end='') + # print(bytes(uchar_p[:size]), end='') + + print(senderSetup1ByteSize.value) + p = ct.string_at(senderSetup1Bytes, senderSetup1ByteSize.value) + clib.deleteState(senderSetup1State) + return p, senderSetup1ByteSize.value + +def receiverSetup(stateDir, bankID, p, p_size): + #return 0, 0 + b_stateDir = stateDir.encode('utf-8') + b_bankID = bankID.encode('utf-8') + #### Receiver Setup1 starts here + clib.recverSetup.argtypes = [ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_char), ct.c_int, ct.POINTER(ct.c_int), ct.POINTER(ct.c_longlong)] + clib.recverSetup.restype = ct.POINTER(ct.c_char) + + recverSetupBytesSize = ct.c_int() + recverSetupState = ct.c_longlong() + + recverSetupBytes = clib.recverSetup(b_stateDir, b_bankID, p, p_size, ct.byref(recverSetupBytesSize), ct.byref(recverSetupState) ) + print(recverSetupBytesSize.value) + q = ct.string_at(recverSetupBytes, recverSetupBytesSize.value) + clib.deleteState(recverSetupState) + return q, recverSetupBytesSize.value + +def senderSetup2(stateDir, bankID, q, q_size): + #return 0, 0 + b_stateDir = stateDir.encode('utf-8') + b_bankID = bankID.encode('utf-8') + #### Sender Setup2 starts here + + clib.senderSetup2.argtypes = [ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_char), ct.c_int] + clib.senderSetup2( b_stateDir, b_bankID, q, q_size) + return + +def receiverGenKeys(stateDir, bankID, choices, accountIds, max_length): + #d = dict(zip(accountIds, choices)) + #torch.save(d, stateDir+"ot.pl") + #return 0, 0 + + b_stateDir = stateDir.encode('utf-8') + b_bankID = bankID.encode('utf-8') + b_bankHashTableFilePath = (stateDir + "/" + bankID).encode('utf-8') + #### Receiver passing the choices & generating the keys + clib.recverGenerateKeys.argtypes = [ct.c_char_p, ct.c_char_p,ct.c_char_p, ct.POINTER(ct.c_uint), ct.c_int, ct.POINTER(ct.c_int), ct.POINTER(ct.c_longlong), ct.c_char_p, ct.POINTER(ct.c_int), ct.c_int] + clib.recverGenerateKeys.restype = ct.POINTER(ct.c_char) + + + cChoices = (ct.c_uint * len(choices)) (*choices) + accountIdLengths = [len(accountId) for accountId in accountIds] + assert(max(accountIdLengths) <= max_length) + cAccountIdLengths = (ct.c_int * len(accountIdLengths)) (*accountIdLengths) + b_accountIds = "".join(accountIds).encode('utf-8') + + recverGenKeySize = ct.c_int() + recverGenKeyState = ct.c_longlong() + + cAccountIdLengths = (ct.c_int * len(accountIdLengths)) (*accountIdLengths) + recverGenKeyBytes = clib.recverGenerateKeys(b_stateDir, b_bankID, b_bankHashTableFilePath, cChoices, len(choices), ct.byref(recverGenKeySize), ct.byref(recverGenKeyState), b_accountIds, cAccountIdLengths , max_length) + r = ct.string_at(recverGenKeyBytes, recverGenKeySize.value) + clib.deleteState(recverGenKeyState) + return r, recverGenKeySize.value + +def senderGenKeys(stateDir, bankID, accountIds, r, r_size, max_length): + #return + b_stateDir = stateDir.encode('utf-8') + b_bankID = bankID.encode('utf-8') + b_hashTableFilePath = (stateDir+ "/PROCESSOR" + bankID).encode('utf-8') + + accountIdLengths = [len(accountId) for accountId in accountIds] + cAccountIdLengths = (ct.c_int * len(accountIdLengths)) (*accountIdLengths) + b_accountIds = "".join(accountIds).encode('utf-8') + + clib.senderGenerateKeys.argtypes = [ct.c_char_p, ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_char), ct.c_int, ct.c_int, ct.c_char_p, ct.POINTER(ct.c_int), ct.c_int] + clib.senderGenerateKeys( b_stateDir, b_bankID, b_hashTableFilePath, r, r_size, len(accountIds), b_accountIds, cAccountIdLengths, max_length) + return + +def senderEncrypt(accountIds, g0, g1, stateDir: str, partitionID, gradient_length, max_length): + #return g0, g1 + + b_hashTableFilePath = (stateDir + "/PROCESSOR" + partitionID).encode('utf-8') + assert(g0.shape == g1.shape) + samples, grad_len = g0.shape + assert(grad_len == gradient_length) + assert(samples == len(accountIds)) + + encryptedZeroGradient = torch.zeros( len(accountIds) * (gradient_length+4)) + encryptedOneGradient = torch.zeros( len(accountIds) * (gradient_length+4)) + g0 = torch.flatten(g0) + g1 = torch.flatten(g1) + print(g0.shape) + + g0.to(torch.int32) + g1.to(torch.int32) + encryptedZeroGradient = encryptedZeroGradient.to(torch.int32) + encryptedOneGradient = encryptedOneGradient.to(torch.int32) + + accountIds = [accountId[0] + accountId[1] for accountId in accountIds] + accountIdLengths = [len(accountId) for accountId in accountIds] + b_accountIds = "".join(accountIds).encode('utf-8') + cAccountIdLengths = (ct.c_int * len(accountIdLengths)) (*accountIdLengths) + + + + clib.encrypt.argtypes = [ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_int), ct.c_int, ct.c_int, ct.c_longlong, ct.c_longlong, ct.c_int, ct.c_longlong, ct.c_longlong] + clib.encrypt(b_hashTableFilePath, b_accountIds, cAccountIdLengths, max_length, len(accountIds), g0.data_ptr(), g1.data_ptr(), gradient_length, encryptedZeroGradient.data_ptr(), encryptedOneGradient.data_ptr()) + shape = (len(accountIds), gradient_length+4) + return torch.reshape(encryptedZeroGradient, shape), torch.reshape(encryptedOneGradient, shape) + +def receiverDecrypt(accountIds, enc0, enc1, stateDir: str, partitionID, gradient_length, choices, max_length): + #accountIds = [accountId[0] + accountId[1] for accountId in accountIds] + #d = torch.load(stateDir+"ot.pl") + #unencryptedGradient = [enc0[i] if d[accountIds[i]] == 0 else enc1[i] for i in range(len(accountIds))] + #unencryptedGradient = torch.stack(unencryptedGradient, axis=0) + #return unencryptedGradient + + + b_bankHashTableFilePath = (stateDir + "/" + partitionID).encode('utf-8') + + accountIds = [accountId[0] + accountId[1] for accountId in accountIds] + accountIdLengths = [len(accountId) for accountId in accountIds] + b_accountIds = "".join(accountIds).encode('utf-8') + cAccountIdLengths = (ct.c_int * len(accountIdLengths)) (*accountIdLengths) + cChoices = (ct.c_uint * len(choices)) (*choices) + unencryptedGradient = torch.zeros( len(accountIds) * gradient_length) + unencryptedGradient = unencryptedGradient.to(torch.int32) + enc0 = torch.flatten(enc0) + enc1 = torch.flatten(enc1) + + clib.decrypt.argtypes = [ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_int), ct.c_int, ct.c_int, ct.c_longlong, ct.c_longlong, ct.c_int, ct.c_longlong, ct.POINTER(ct.c_uint)] + clib.decrypt(b_bankHashTableFilePath, b_accountIds, cAccountIdLengths, max_length, len(accountIds), enc0.data_ptr(), enc1.data_ptr(), gradient_length, unencryptedGradient.data_ptr(), cChoices ) + + unencryptedGradient = torch.reshape(unencryptedGradient, (len(accountIds), gradient_length)) + return unencryptedGradient diff --git a/tools/de-identification/visa-pets-FL/federated/solution_federated.py b/tools/de-identification/visa-pets-FL/federated/solution_federated.py new file mode 100644 index 0000000..050f02b --- /dev/null +++ b/tools/de-identification/visa-pets-FL/federated/solution_federated.py @@ -0,0 +1,830 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +from pathlib import Path +from typing import Dict, List, Optional, Tuple + +import flwr as fl +from flwr.common import FitIns, FitRes, Parameters +from flwr.server import ClientManager +from flwr.server.client_proxy import ClientProxy +from loguru import logger +import numpy as np +import pandas as pd +import pickle +import itertools +import traceback +import torch +import os +from torch.utils.data import DataLoader +from hashlib import sha256 +import torch.nn.functional as F + +from .DataPrepUtils import ( + prepare_data_train, + prepare_data_test, + prepare_bank_data +) + +from .DataObjects import ( + CustomDatasetTest, + CustomDatasetTrain +) + +from .DNN import * + +from .ot import ( + senderSetup1, + receiverSetup, + senderSetup2, + receiverGenKeys, + senderGenKeys, + senderEncrypt, + receiverDecrypt +) + +STAGE_UPCYCLE = 1 +STAGE_DOWNCYCLE = 2 +STAGE_SENDING = 3 +STAGE_WAIT_FOR_GRADIENT = 4 +STAGE_FINISHED_SENDING = 5 +STAGE_DONE = 6 + +SAMPLES_PER_ROUND_LIMIT = 2400 +DEFAULT_FLAG = 0 + +TRAIN_ROUNDS = 250 #350 +TEST_ROUNDS = 2 + +NOISE_MEAN = 0 +NOISE_SCALE = .1 +NOISE_MULTIPLIER = 1e3 + +DEVICE = "cpu" +#DEVICE = "cuda" +TRAIN_BATCH_SIZE = 8192*10 + +def empty_parameters() -> Parameters: + """Utility function that generates empty Flower Parameters dataclass instance.""" + return fl.common.ndarrays_to_parameters([]) + + +def df_to_ndarrays( + df: pd.DataFrame, labels: bool = True +) -> List[np.ndarray]: + """Utility function that converts a pandas DataFrame of data to a list of + numpy arrays, which the expected format for communication between a Flower + NumPyClient and a Flower Strategy.""" + cols = [ + "FinalReceiver", + "BeneficiaryAccount", + ] + if labels: + cols.append("Label") + # Need .astype("U") because pandas uses 'object' for string columns by default + # 'object' arrays are not serializable without pickle. + return [ + # Index + df.index.values.astype("U"), + # Transactions: Join keys and label + df[cols].values.astype("U"), + ] + + +def ndarrays_to_df( + index: np.ndarray, transactions: np.ndarray, labels: bool = True +) -> pd.DataFrame: + """Utility function that converts a list of numpy arrays, which the expected format + for communication between a Flower NumPyClient and a Flower Strategy, back to a + pandas DataFrame""" + cols = [ + "FinalReceiver", + "BeneficiaryAccount", + ] + if labels: + cols.append("Label") + return pd.DataFrame( + data=transactions, + index=pd.Series(index, name="MessageId"), + columns=cols, + ) + +def checkTensor(tensor): + return (tensor[0], tensor[1], tensor[-1], tensor[-2]) + +class TrainingClient(fl.client.NumPyClient): + def __init__( + self, cid: str, data_path: Path, client_dir: Path + ): + super().__init__() + self.cid = cid + self.data_path = data_path + self.client_dir = client_dir + self.ot_dir = Path.joinpath(self.client_dir, "ot") + if not os.path.isdir(self.ot_dir): + os.mkdir(self.ot_dir) + + def init_stage(self, n_batches): + stage = STAGE_UPCYCLE + batch_index = 0 + epoch_index = 0 + d = {"stage": stage, "batch": batch_index, "epoch": epoch_index, "n": n_batches} + torch.save(d, Path.joinpath(self.client_dir, "stage.pl")) + + def load_stage(self): + d = torch.load(Path.joinpath(self.client_dir, "stage.pl")) + return (d["stage"], d["batch"], d["epoch"], d["n"]) + + def save_stage(self, stage, batch_index, epoch_index, n_batches): + d = {"stage": stage, "batch": batch_index, "epoch": epoch_index, "n": n_batches} + torch.save(d, Path.joinpath(self.client_dir, "stage.pl")) + + def fit(self, parameters: List[np.ndarray], config: dict): + round = config["round"] + #logger.info("CID: {}, ROUND: {}", self.cid, round) + metrics = {} + try: + if round == 1: + prepare_data_train(self.data_path, self.client_dir) + train_data_path = Path.joinpath(self.client_dir, 'train_data_processor.csv') + train_label_path = Path.joinpath(self.client_dir, 'train_label.csv') + scaler_path = Path.joinpath(self.client_dir, "scaler") + dataset_train = CustomDatasetTrain([train_data_path, train_label_path, scaler_path]) + train_dataloader = DataLoader(dataset_train, batch_size=TRAIN_BATCH_SIZE, shuffle=True) + print("Done! Loading Payment Processo Training") + for batch, (idx, X, y) in enumerate(train_dataloader): + torch.save({"idx":idx, "X":X, "y":y}, Path.joinpath(self.client_dir, str(batch))) + n_batches = len(train_dataloader) + self.init_stage(n_batches) + return [], 1, metrics + elif round == 2: + partitions:Dict = pickle.loads(config["banks"]) + bank_dict = {} + partition_ids = [] + setups = {} + for partition, banks in partitions.items(): + partition_ids.append(partition) + setups[partition] = senderSetup1(str(self.ot_dir), partition) + bank_dict.update(zip(banks, itertools.repeat(partition))) + torch.save(partition_ids, Path.joinpath(self.client_dir, "pids.pl")) + torch.save(bank_dict, Path.joinpath(self.client_dir, "bank_dict.pl")) + unknown_accounts = pd.read_csv(Path.joinpath(self.client_dir, "unknown_accounts.csv")) + maxAccountLen = max((unknown_accounts["Receiver"] + unknown_accounts["BeneficiaryAccount"]).str.len()) + #logger.info("MAX ACCOUNT LEN: {}", maxAccountLen) + torch.save(maxAccountLen, Path.joinpath(self.client_dir, "max.pl")) + unknown_accounts['partition'] = unknown_accounts['Receiver'].map(bank_dict) + #logger.info(unknown_accounts.head()) + accounts = {k: zip(df["Receiver"], df["BeneficiaryAccount"]) for k, df in unknown_accounts.groupby("partition")} + for partition_id in partition_ids: + if(accounts.get(partition_id)): + metrics[partition_id] = pickle.dumps((setups[partition_id][0], setups[partition_id][1], accounts.get(partition_id), maxAccountLen)) + for account in accounts.keys(): + accounts[account] = set(accounts[account]) + torch.save(accounts, Path.joinpath(self.client_dir, "unknown_accounts.pl")) + + return [], 1, metrics + elif round == 3: + return [], 1, {} + elif round == 4: + partition_ids = torch.load(Path.joinpath(self.client_dir, "pids.pl")) + accounts = torch.load(Path.joinpath(self.client_dir, "unknown_accounts.pl")) + maxAccountLen = torch.load(Path.joinpath(self.client_dir, "max.pl")) + + for partition_id in partition_ids: + if(config.get(partition_id)): + q, q_size, r, r_size = pickle.loads(config[partition_id]) + senderSetup2(str(self.ot_dir), partition_id, q, q_size) + acc = sorted(accounts[partition_id]) + acc = [a[0] + a[1] for a in acc] + + senderGenKeys(str(self.ot_dir), partition_id, acc, r, r_size, maxAccountLen) + + stage, batch_index, epoch_index, n_batches = self.load_stage() + self.n_batches = n_batches + if stage == STAGE_WAIT_FOR_GRADIENT: + stage = STAGE_DOWNCYCLE + if stage == STAGE_FINISHED_SENDING: + stage = STAGE_WAIT_FOR_GRADIENT + if stage == STAGE_DOWNCYCLE: + noisy_gradient = pickle.loads(config["noisy_gradient"]) + self.train_down_cycle(noisy_gradient, batch_index, epoch_index) + batch_index += 1 + if batch_index >= n_batches: + batch_index = 0 + epoch_index += 1 + stage = STAGE_UPCYCLE + if stage == STAGE_UPCYCLE: + self.train_up_cycle(batch_index, epoch_index) + stage = STAGE_SENDING + if stage == STAGE_SENDING: + finished, can_send = self.send() + if finished: + stage = STAGE_FINISHED_SENDING + else: + stage = STAGE_SENDING + for p in can_send: + metrics[p] = pickle.dumps(can_send[p]) + metrics["stage"] = stage + self.save_stage(stage, batch_index, epoch_index, n_batches) + return [], 1, metrics + + except Exception: + traceback.print_exc() + + return [], 1, {} + + def send(self): + partition_results: dict = torch.load(Path.joinpath(self.client_dir, "tosend.pl")) + can_send = {} + sending = 0 + finished = True + to_pop = [] + for p in partition_results.keys(): + ids, g0enc, g1enc, grad_len = partition_results[p] + if(len(ids) > SAMPLES_PER_ROUND_LIMIT - sending): + sendingIds = ids[:SAMPLES_PER_ROUND_LIMIT - sending] + sending0 = g0enc[:SAMPLES_PER_ROUND_LIMIT - sending] + sending1 = g1enc[:SAMPLES_PER_ROUND_LIMIT - sending] + ids = ids[SAMPLES_PER_ROUND_LIMIT - sending:] + g0enc = g0enc[SAMPLES_PER_ROUND_LIMIT - sending:] + g1enc = g1enc[SAMPLES_PER_ROUND_LIMIT - sending:] + partition_results[p] = (ids, g0enc, g1enc, grad_len) + can_send[p] = (sendingIds, sending0, sending1, grad_len) + finished = False + break + sending += len(ids) + can_send[p] = (ids, g0enc, g1enc, grad_len) + to_pop.append(p) + for p in to_pop: + partition_results.pop(p) + if not finished: + torch.save(partition_results, Path.joinpath(self.client_dir, "tosend.pl")) + return finished, can_send + + + + def train_up_cycle(self, + i_batch=0, + i_epoch=0, + input_dim=18): + PATH = str(self.client_dir)+'tmp' + #TODO: Load partition_ids and bank_dict + model, optimizer, loss_fn = init_learning(device=DEVICE, batch_size=TRAIN_BATCH_SIZE, i_epoch=i_epoch, INPUT_DIM=input_dim, model=None) + if i_batch != 0 or i_epoch != 0: + checkpoint = torch.load(PATH) + model.load_state_dict(checkpoint["model_state_dict"]) + model = model.to(DEVICE) + model.train() + data = torch.load(Path.joinpath(self.client_dir, str(i_batch))) + idx, X, y = data["idx"], data["X"], data["y"] + batch_size = (X.shape)[0] + X0, X1 = pad_flags(X, 0), pad_flags(X, 1) + X = torch.concat([X0, X1], axis=0) + y = torch.concat([y, y], axis=0) + X, y = X.to(DEVICE), y.to(DEVICE) + pred = model(X.float()) + loss = loss_fn(pred, y) + + optimizer.zero_grad() + loss.backward() + + # Flatten the gradients + grads_flat, param_shapes = flatten_grads(model) + + # Norm clipping + MAX_NORM = 1000 + grads_flat = norm_clipping(grads_flat, max_norm=MAX_NORM) + + # Prepare for serialization + g0, g1, eps = prepare_serialization(grads_flat, device=DEVICE) + eps_sum = torch.sum(eps, axis=0, dtype=torch.int32) + #g0, g1 = g0.to("cpu"), g1.to("cpu") # May need to convert to cpu tensor for serialization + data_ptr0, data_ptr1 = g0.data_ptr, g1.data_ptr + + bid_uid_lst = [item for item in zip(idx[1], idx[3])] + #logger.info("SAMPLES TO ENC: {}", len(bid_uid_lst)) + partition_bid_uids = {} + partition_grads0 = {} + partition_grads1 = {} + partition_results = {} + partition_ids = torch.load(Path.joinpath(self.client_dir, "pids.pl")) + bank_dict = torch.load(Path.joinpath(self.client_dir, "bank_dict.pl")) + maxAccountLen = torch.load(Path.joinpath(self.client_dir, "max.pl")) + + for partition_id in partition_ids: + partition_bid_uids[partition_id] = [] + partition_grads0[partition_id] = [] + partition_grads1[partition_id] = [] + + unknown_accounts = torch.load(Path.joinpath(self.client_dir, "unknown_accounts.pl")) + + skipped = 0 + for i, id in enumerate(bid_uid_lst): + p = bank_dict[id[0]] + if id not in unknown_accounts[p]: + eps_sum = torch.subtract(eps_sum, g0[i]) + skipped += 1 + continue + partition_bid_uids[p].append(id) + partition_grads0[p].append(g0[i]) + partition_grads1[p].append(g1[i]) + grad_len = len(g0[0]) + #logger.info("GRADIENT LENGTH: {}", grad_len) + #logger.info("ABLE TO SKIP {} SAMPLES", skipped) + + for partition_id in partition_ids: + if(len(partition_grads0[partition_id]) == 0): + continue + #logger.info("PARTITION {}: {} SAMPLES", partition_id, len(partition_grads0[partition_id])) + g0 = torch.stack(partition_grads0[partition_id], axis=0) + g1 = torch.stack(partition_grads1[partition_id], axis=0) + ids = partition_bid_uids[partition_id] + #logger.info("SHAPE: {}", g0.shape) + + #logger.info("PARTITION: {}, ID: {}, G0: {}, G1: {}", partition_id, ids[0], g0[0], g1[0]) + g0enc, g1enc = senderEncrypt(ids, g0, g1, str(self.ot_dir), partition_id, grad_len, maxAccountLen) + partition_results[partition_id] = (ids, g0enc, g1enc, grad_len) + + torch.save(partition_results, Path.joinpath(self.client_dir, "tosend.pl")) + torch.save({"model_state_dict": model.state_dict(), + "optimizer_state_dict": optimizer.state_dict(), + "loss": loss, + "grads_flat": grads_flat, + "eps_sum": eps_sum, + "param_shapes": param_shapes + }, PATH) + return + + def train_down_cycle(self, noisy_gradient, i_batch=0, i_epoch=0, + input_dim=18): + + model, optimizer, loss_fn = init_learning(i_epoch=i_epoch, INPUT_DIM=input_dim, device=DEVICE, batch_size=TRAIN_BATCH_SIZE) + PATH = str(self.client_dir)+'tmp' + checkpoint = torch.load(PATH) + model.load_state_dict(checkpoint["model_state_dict"]) + optimizer.load_state_dict(checkpoint["optimizer_state_dict"]) + loss = checkpoint["loss"] + grads_flat = checkpoint["grads_flat"].to(DEVICE) + eps_sum = checkpoint["eps_sum"].to(torch.int32) + param_shapes = checkpoint["param_shapes"] + model = model.to(DEVICE) + model.train() + #end_time_load = time.time() + #print("Save/load for one batch takes {}".format(end_time_load-start_time_save)) + + # Get the gradients from Aggregator + #grads_flat = get_aggregated_grads(g0.to(device)) + #grads_flat = grads_flat.to(device) + + # Deserialize the grads for backprop + grads_flat = complete_deserialization(noisy_gradient, eps_sum) + batch_size = TRAIN_BATCH_SIZE + grads_flat /= batch_size + + # Reshape the flattened per-sample gradient back to gradient matrices. + expand_flattened_grads(model, grads_flat, param_shapes) + + optimizer.step() + clear_per_sample_grad(model) + + loss = loss.item() + logger.info("LOSS AND CURRENT BATCH: {} {}/{}, EPOCH {}", loss, i_batch, self.n_batches, i_epoch) + + torch.save({"model_state_dict":model.state_dict()}, PATH) + + + +class TrainingBankClient(fl.client.NumPyClient): + def __init__( + self, cid: str, data_path: Path, client_dir: Path + ): + super().__init__() + self.cid = cid + self.data_path = data_path + self.client_dir = client_dir + self.ot_dir = Path.joinpath(client_dir, "ot") + if not os.path.isdir(self.ot_dir): + os.mkdir(self.ot_dir) + + def fit( + self, parameters: List[np.ndarray], config: dict + ) -> Tuple[List[np.ndarray], int, dict]: + round = config["round"] + logger.info("CID: {}, ROUND: {}", self.cid, round) + metrics = {} + try: + # Round 1: Send banks in this partition to strategy + if round == 1: + bank_ids = prepare_bank_data(self.data_path, self.client_dir) + #logger.info("{} has {} banks", self.cid, len(bank_ids)) + metrics["banks"] = pickle.dumps(bank_ids) + return [], 1, metrics + elif round == 3: + if config.get("setup"): + p, p_len, unknown_accounts, maxAccountLen = pickle.loads(config["setup"]) + q, q_size = receiverSetup(str(self.ot_dir), self.cid, p, p_len) + bank_dict = torch.load(Path.joinpath(self.client_dir, "bank_flag_dict")) + unknown_accounts = sorted(set(unknown_accounts)) + choices = [bank_dict.get(a, 0) for a in unknown_accounts] + unknown_accounts = [a[0] + a[1] for a in unknown_accounts] + #logger.info("{} ACC HASH: {}", self.cid, sha256(''.join(unknown_accounts).encode("utf-8")).digest()) + r, r_size = receiverGenKeys(str(self.ot_dir), self.cid, choices, unknown_accounts, maxAccountLen) + torch.save(maxAccountLen, Path.joinpath(self.client_dir, "max.pl")) + metrics["qr"] = pickle.dumps((q, q_size, r, r_size)) + return [], 1, metrics + else: + if config.get("grads"): + ids, g0enc, g1enc, grad_len = pickle.loads(config["grads"]) + bank_dict = torch.load(Path.joinpath(self.client_dir, "bank_flag_dict")) + maxAccountLen = torch.load(Path.joinpath(self.client_dir, "max.pl")) + choices = [bank_dict.get((x, y), 0) for (x, y) in ids] + #logger.info(len(choices)) + #logger.info("SHAPE: ", g0enc.shape) + grad = receiverDecrypt(ids, g0enc, g1enc, str(self.ot_dir), self.cid, grad_len, choices, maxAccountLen) + #logger.info("PARTITION: {}, ID: {}, HASH: {}, CHOICE: {}", self.cid, ids[0], grad[0], choices[0]) + grad = torch.sum(grad, dim=0) + metrics["grad"] = pickle.dumps(grad) + return [], 1, metrics + + except Exception: + traceback.print_exc() + return [], 1, {} + +def train_client_factory(cid, data_path: Path, client_dir: Path): + if cid == "swift": + return TrainingClient( + cid, data_path=data_path, client_dir=client_dir + ) + else: + return TrainingBankClient( + cid, data_path=data_path, client_dir=client_dir + ) + + +class TrainStrategy(fl.server.strategy.Strategy): + def __init__(self, server_dir: Path): + self.server_dir = server_dir + self.labels_for_banks = None + self.banks_dict = {} + self.processor_param_dict = {} + self.processor_config_dict = {} + self.bank_config_dict = {} + self.banks = [] + self.current_grad = None + super().__init__() + + def initialize_parameters(self, client_manager: ClientManager) -> Parameters: + """Do nothing. Return empty Flower Parameters dataclass.""" + client_dict: Dict[str, ClientProxy] = client_manager.all() + self.banks = [cid for cid in client_dict.keys() if cid != "swift"] + return empty_parameters() + + def configure_fit( + self, server_round: int, parameters: Parameters, client_manager: ClientManager + ) -> List[Tuple[ClientProxy, FitIns]]: + client_dict: Dict[str, ClientProxy] = client_manager.all() + config_dict = {"round": server_round} + fit_config: List[Tuple[ClientProxy, FitIns]] = [] + processor_config = dict(config_dict) + processor_parameters = empty_parameters() + bank_cids = [cid for cid in client_dict.keys() if cid != "swift"] + bank_parameters = {} + bank_config = {} + for cid in bank_cids: + bank_parameters[cid] = empty_parameters() + bank_config[cid] = dict(config_dict) + if server_round == 1: + pass + elif server_round == 2: + processor_config["banks"] = pickle.dumps(self.bank_config_dict) + elif server_round == 3: + for cid in bank_cids: + bank_parameters[cid] = self.processor_param_dict.get(cid, empty_parameters()) + bank_config[cid].update(self.processor_config_dict.get(cid, {})) + elif server_round == 4: + processor_config.update(self.bank_config_dict) + else: + if self.stage == STAGE_WAIT_FOR_GRADIENT: + noise = np.random.laplace(NOISE_MEAN, NOISE_SCALE, self.current_grad.shape) + self.current_grad = self.current_grad.add(torch.multiply(torch.tensor(noise), NOISE_MULTIPLIER).int()) + processor_config["noisy_gradient"] = pickle.dumps(self.current_grad) + self.current_grad = None + if self.stage == STAGE_SENDING or self.stage == STAGE_FINISHED_SENDING: + for cid in bank_cids: + bank_config[cid].update(self.processor_config_dict.get(cid, {})) + + processor_fit_ins = FitIns(parameters=processor_parameters, config=processor_config) + fit_config += [(client_dict["swift"], processor_fit_ins)] + for cid in bank_cids: + this_bank_fit_ins = FitIns( + parameters=bank_parameters[cid], + config=bank_config[cid], + ) + fit_config.append((client_dict[cid], this_bank_fit_ins)) + return fit_config + + def aggregate_fit( + self, server_round: int, results: List[Tuple[ClientProxy, FitRes]], failures + ) -> Tuple[Optional[Parameters], dict]: + if (n_failures := len(failures)) > 0: + raise Exception(f"Had {n_failures} failures in round {server_round}") + self.processor_config_dict = {} + self.processor_param_dict = {} + self.bank_config_dict = {} + if server_round == 1: + for client, result in results: + result_ndarrays = fl.common.parameters_to_ndarrays(result.parameters) + if client.cid != "swift": + self.bank_config_dict[client.cid] = pickle.loads(result.metrics["banks"]) + elif server_round == 2: + for client, result in results: + result_ndarrays = fl.common.parameters_to_ndarrays(result.parameters) + if client.cid == "swift": + for cid in self.banks: + if result.metrics.get(cid): + self.processor_config_dict[cid] = {} + self.processor_config_dict[cid]["setup"] = result.metrics.get(cid) + elif server_round == 3: + for client, result in results: + result_ndarrays = fl.common.parameters_to_ndarrays(result.parameters) + if client.cid != "swift": + if result.metrics.get("qr"): + self.bank_config_dict[client.cid] = result.metrics["qr"] + else: + for client, result in results: + result_ndarrays = fl.common.parameters_to_ndarrays(result.parameters) + if client.cid == "swift": + self.stage = result.metrics["stage"] + if self.stage == STAGE_SENDING or self.stage == STAGE_FINISHED_SENDING: + for cid in self.banks: + if result.metrics.get(cid): + self.processor_config_dict[cid] = {} + self.processor_config_dict[cid]["grads"] = result.metrics.get(cid) + else: + if result.metrics.get("grad"): + if self.current_grad == None: + self.current_grad = pickle.loads(result.metrics["grad"]) + else: + self.current_grad = torch.add(self.current_grad, pickle.loads(result.metrics["grad"])) + + return None, {} + + def configure_evaluate(self, server_round, parameters, client_manager): + """Not running any federated evaluation.""" + return [] + + def aggregate_evaluate(self, server_round, results, failures): + """Not aggregating any evaluation.""" + return None + + def evaluate(self, server_round, parameters): + """Not running any centralized evaluation.""" + return None + + +def train_strategy_factory(server_dir: Path): + training_strategy = TrainStrategy(server_dir=server_dir) + num_rounds = TRAIN_ROUNDS + return training_strategy, num_rounds + +def test_client_factory( + cid: str, + data_path: Path, + client_dir: Path, + preds_format_path: Path, + preds_dest_path: Path, +): + if cid == "swift": + return TestProcessorClient( + cid, + data_path=data_path, + client_dir=client_dir, + preds_format_path=preds_format_path, + preds_dest_path=preds_dest_path, + ) + else: + #logger.info("Initializing bank client for {}", cid) + return TestBankClient(cid, data_path=data_path, client_dir=client_dir) + + +class TestProcessorClient(fl.client.NumPyClient): + def __init__( + self, + cid: str, + data_path: Path, + client_dir: Path, + preds_format_path: Path, + preds_dest_path: Path, + ): + super().__init__() + self.cid = cid + self.data_path = data_path + self.client_dir = client_dir + self.preds_format_path = preds_format_path + self.preds_dest_path = preds_dest_path + self.ot_dir = Path.joinpath(client_dir, "ot_test") + if not os.path.isdir(self.ot_dir): + os.mkdir(self.ot_dir) + + def init_stage(self): + stage = STAGE_UPCYCLE + d = {"stage": stage} + torch.save(d, Path.joinpath(self.client_dir, "stage.pl")) + + def load_stage(self): + d = torch.load(Path.joinpath(self.client_dir, "stage.pl")) + return d["stage"] + + def save_stage(self, stage): + d = {"stage": stage} + torch.save(d, Path.joinpath(self.client_dir, "stage.pl")) + + def fit( + self, parameters: List[np.ndarray], config: dict + ) -> Tuple[List[np.ndarray], int, dict]: + round = config["round"] + #logger.info("CID: {}, ROUND: {}", self.cid, round) + metrics = {} + try: + if round == 1: + prepare_data_test(self.data_path, self.client_dir) + return [], 1, metrics + elif round == 2: + flags = pickle.loads(config["flags"]) + self.test_up(flags) + return [], 1, metrics + + except Exception: + traceback.print_exc() + + return [], 1, {} + + + def test_up(self, flags): + test_data_path = Path.joinpath(self.client_dir, 'test_data_processor.csv') + scaler_path = Path.joinpath(self.client_dir, "scaler") + dataset_test = CustomDatasetTest([test_data_path, None, scaler_path]) + + model = GradSampleModule(NeuralNetwork(18).to(DEVICE), force_functorch=True) + PATH = str(self.client_dir)+'tmp' + checkpoint = torch.load(PATH) + model.load_state_dict(checkpoint["model_state_dict"]) + model = model.to(DEVICE) + model.eval() + idx, X = dataset_test[:] + #logger.info("SOME IDS: {}", idx[:5]) + with torch.no_grad(): + batch_size = (X.shape)[0] + #X = get_flags(idx, X, bank_flag_dict) + X0, X1 = pad_flags(X, 0), pad_flags(X, 1) + X = torch.concat([X0, X1], axis=0) + X = X.to(DEVICE) + pred = model(X.float()) + + s = (pred.shape)[0]//2 + pred0, pred1 = pred[:s, :], pred[s:, :] + + bid_uid_lst = [(item[1], item[3]) for item in idx] + + missed = 0 + chosen = [] + for i, id in enumerate(bid_uid_lst): + flag = flags.get(id, -1) + if(flag == -1): + missed += 1 + flag = 0 + if flag == 0: + p = pred0[i] + else: + p = pred1[i] + p = F.softmax(p, dim=0) + chosen.append(p) + + probs = torch.stack(chosen, axis=0) + logger.info("PROBS SHAPE: {}", probs.shape) + logger.info("MISSED: {}", missed) + + test_data_path = Path.joinpath(self.client_dir, 'test_data_processor.csv') + raw_data = pd.read_csv(test_data_path, usecols=["MessageId"]) + raw_data["Score"] = probs[:, 1].numpy().tolist() + + res = pd.read_csv(self.preds_format_path) + res = res.merge(raw_data, on="MessageId", how="left") + res = res.rename(columns={"Score_y":"Score"}) + res[["MessageId", "Score"]].to_csv(self.preds_dest_path, index=False) + + return + + + +class TestBankClient(fl.client.NumPyClient): + def __init__(self, cid, data_path: Path, client_dir: Path): + super().__init__() + self.cid = cid + self.data_path = data_path + self.client_dir = client_dir + self.ot_dir = Path.joinpath(client_dir, "ot_test") + if not os.path.isdir(self.ot_dir): + os.mkdir(self.ot_dir) + + def fit( + self, parameters: List[np.ndarray], config: dict + ) -> Tuple[List[np.ndarray], int, dict]: + ## Round 1: Send banks in this partition to strategy + round = config["round"] + #logger.info("CID: {}, ROUND: {}", self.cid, round) + metrics = {} + try: + # Round 1: Send banks in this partition to strategy + if round == 1: + bank_ids = prepare_bank_data(self.data_path, self.client_dir) + flagDict = torch.load(Path.joinpath(self.client_dir, "bank_flag_dict")) + metrics["flags"] = pickle.dumps(flagDict) + return [], 1, metrics + except Exception: + traceback.print_exc() + return [], 1, {} + +def test_strategy_factory(server_dir: Path): + test_strategy = TestStrategy(server_dir=server_dir) + num_rounds = TEST_ROUNDS + return test_strategy, num_rounds + + +class TestStrategy(fl.server.strategy.Strategy): + def __init__(self, server_dir: Path): + self.server_dir = server_dir + self.labels_for_banks = None + self.banks_dict = {} + self.processor_param_dict = {} + self.processor_config_dict = {} + self.bank_config_dict = {} + self.banks = [] + self.indexes = [] + self.preds = [] + self.flags = {} + super().__init__() + + def initialize_parameters(self, client_manager: ClientManager) -> Parameters: + """Do nothing. Return empty Flower Parameters dataclass.""" + client_dict: Dict[str, ClientProxy] = client_manager.all() + self.banks = [cid for cid in client_dict.keys() if cid != "swift"] + return empty_parameters() + + def configure_fit( + self, server_round: int, parameters: Parameters, client_manager: ClientManager + ) -> List[Tuple[ClientProxy, FitIns]]: + client_dict: Dict[str, ClientProxy] = client_manager.all() + config_dict = {"round": server_round} + fit_config: List[Tuple[ClientProxy, FitIns]] = [] + processor_config = dict(config_dict) + processor_parameters = empty_parameters() + bank_cids = [cid for cid in client_dict.keys() if cid != "swift"] + bank_parameters = {} + bank_config = {} + for cid in bank_cids: + bank_parameters[cid] = empty_parameters() + bank_config[cid] = dict(config_dict) + if server_round == 1: + pass + elif server_round == 2: + processor_config["flags"] = pickle.dumps(self.flags) + + processor_fit_ins = FitIns(parameters=processor_parameters, config=processor_config) + fit_config += [(client_dict["swift"], processor_fit_ins)] + for cid in bank_cids: + this_bank_fit_ins = FitIns( + parameters=bank_parameters[cid], + config=bank_config[cid], + ) + fit_config.append((client_dict[cid], this_bank_fit_ins)) + return fit_config + + def aggregate_fit( + self, server_round: int, results: List[Tuple[ClientProxy, FitRes]], failures + ) -> Tuple[Optional[Parameters], dict]: + if (n_failures := len(failures)) > 0: + raise Exception(f"Had {n_failures} failures in round {server_round}") + + if server_round == 1: + for client, result in results: + result_ndarrays = fl.common.parameters_to_ndarrays(result.parameters) + if client.cid != "swift": + self.flags.update(pickle.loads(result.metrics["flags"])) + return None, {} + + def configure_evaluate(self, server_round, parameters, client_manager): + """Not running any federated evaluation.""" + return [] + + def aggregate_evaluate(self, server_round, results, failures): + """Not aggregating any evaluation.""" + return None + + def evaluate(self, server_round, parameters): + """Not running any centralized evaluation.""" + return None \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/figure.png b/tools/de-identification/visa-pets-FL/figure.png new file mode 100644 index 0000000000000000000000000000000000000000..491b0c54389f8c29763f2149b811e8708ddcd4b4 GIT binary patch literal 143190 zcmbUJ1z42b_CAgeT_U9*J%j;Lf^-d{bcu=}rBWi@A&r6}Eg=mG0!j)}BA_VUAR%4S zDM-U_J)Uzu=hgRn{m(gnuL~V#o|$>}-fORQulv3?Pc+ozNr~x*Q79CtqQYfO6p9cV zg~EPCh!4*Uo8=XtP{%Olva%YAva)CmdmB@8OA{1I;YsvO0_|&!)XBP$;h1}A%0wTC zSOtBe$ZyRuew0(den@GAe%#-A1+R~Xx*+WhE3p=4@$ua|R~J2HE??21ArZPh!j9!Q ze6vb;=lh8CQQ}T}oP*i&5Ef?oqC9((+zqS-itkU1{Pt%jpGD54o1p@wQH!Xg+SZd7 zUcUG=Ce7z@^uW!V1l_ggB%a+XJ$Phsb#l$29EHA0p1E#cE=tafkIEkl`s{`x�W+ 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 literal 0 HcmV?d00001 diff --git a/tools/de-identification/visa-pets-FL/ot_library/.gitignore b/tools/de-identification/visa-pets-FL/ot_library/.gitignore new file mode 100644 index 0000000..dfaea16 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/.gitignore @@ -0,0 +1,227 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates +*.vs +out/ + +diskhash/* + +CMakeFiles/* +*/CMakeFiles/* +*cmake_install.cmake + +CMakeCache.txt +*/CMakeCache.txt + +*.a +*.args.json + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*.bin +frontend/My*/ +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +## TODO: Comment the next line if you want to checkin your +## web deploy settings but do note that will include unencrypted +## passwords +#*.pubxml + +# NuGet Packages Directory +packages/* +## TODO: If the tool you use requires repositories.config +## uncomment the next line +#!packages/repositories.config + +# Enable "build/" folder in the NuGet Packages folder since +# NuGet packages use it for MSBuild targets. +# This line needs to be after the ignore of the build folder +# (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml +/WeGarbleTests__ +/thirdparty +kProbe_* + +CodeDB +LinuxFrontEnd/VisualGDBCache +*.opendb +*.pdf +*.db +*.sln + +mpsi.VC* + +/psir_8s.txt +/psis_8s.txt + +testout.txt +online.txt +offline.txt +Makefile + +[path to project]/node_modules/ \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/ot_library/.vscode/launch.json b/tools/de-identification/visa-pets-FL/ot_library/.vscode/launch.json new file mode 100644 index 0000000..7acb2f4 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/out/build/osx/frontend/frontend", + "args": ["-tut"], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/ot_library/.vscode/settings.json b/tools/de-identification/visa-pets-FL/ot_library/.vscode/settings.json new file mode 100644 index 0000000..5d38dc1 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/.vscode/settings.json @@ -0,0 +1,82 @@ +{ + "C_Cpp.default.configurationProvider": "vector-of-bool.cmake-tools", + "files.associations": { + "system_error": "cpp", + "__config": "cpp", + "type_traits": "cpp", + "iosfwd": "cpp", + "__bit_reference": "cpp", + "__bits": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "array": "cpp", + "atomic": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "exception": "cpp", + "coroutine": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ostream": "cpp", + "queue": "cpp", + "random": "cpp", + "ratio": "cpp", + "set": "cpp", + "span": "cpp", + "sstream": "cpp", + "stack": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "thread": "cpp", + "tuple": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "variant": "cpp", + "vector": "cpp", + "algorithm": "cpp" + } +} \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/ot_library/CMakeLists.txt b/tools/de-identification/visa-pets-FL/ot_library/CMakeLists.txt new file mode 100644 index 0000000..211c194 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required (VERSION 3.15) +project(dropOt VERSION 0.5.0) + +include(cmake/preamble.cmake) +include(cmake/buildOptions.cmake) +include(cmake/findDependancies.cmake) + +message(STATUS "Option: CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}\n\tRelease\n\tDebug\n\tRELWITHDEBINFO") +message(STATUS " cryptoTools_INC = ${cryptoTools_INC}") +message(STATUS " cryptoTools_LIB = ${cryptoTools_LIB}") +message(STATUS " ENABLE_BOOST = ${ENABLE_BOOST}\n\n") + +configure_file(drop-ot/config.h.in drop-ot/config.h) + +add_subdirectory(drop-ot) +add_subdirectory(frontend) +add_subdirectory(wrapper) +add_subdirectory(diskhash) + + +include(cmake/install.cmake) \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/ot_library/CMakePresets.json b/tools/de-identification/visa-pets-FL/ot_library/CMakePresets.json new file mode 100644 index 0000000..f20ae86 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/CMakePresets.json @@ -0,0 +1,82 @@ +{ + "version": 2, + "configurePresets": [ + { + "name": "linux", + "displayName": "Linux ", + "description": "Target the Windows Subsystem for Linux (WSL) or a remote Linux system.", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "HYDRA_FETCH_AUTO": true, + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "DROP_OT_FETCH_CRYPTOTOOLS": true, + "DROP_OT_ENABLE_PIC": true, + "DROP_OT_ENABLE_RELIC": true, + "DROP_OT_ENABLE_SODIUM": false + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Linux" ] }, + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" } + } + }, + { + "name": "osx", + "displayName": "macOS", + "description": "Target the Windows Subsystem for Linux (WSL) or a remote Linux system.", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "HYDRA_FETCH_AUTO": true, + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "DROP_OT_FETCH_CRYPTOTOOLS": true, + "DROP_OT_ENABLE_PIC": true + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "macOS" ] }, + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" } + } + }, + { + "name": "x64-Debug", + "displayName": "Windows x64 Debug", + "description": "Target Windows with the Visual Studio development environment.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "HYDRA_ENABLE_BOOST": true, + "HYDRA_FETCH_AUTO": true, + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "DROP_OT_FETCH_CRYPTOTOOLS": true, + "DROP_OT_ENABLE_RELIC": true, + "DROP_OT_ENABLE_SODIUM": false + }, + "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Windows" ] } } + }, + { + "name": "x64-Release", + "displayName": "Windows x64 Release", + "description": "Target Windows with the Visual Studio development environment.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "HYDRA_FETCH_AUTO": true, + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "DROP_OT_FETCH_CRYPTOTOOLS": true + }, + "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Windows" ] } } + } + ] +} \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/ot_library/cmake/Config.cmake.in b/tools/de-identification/visa-pets-FL/ot_library/cmake/Config.cmake.in new file mode 100644 index 0000000..ef259d4 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/cmake/Config.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/dropOtTargets.cmake") + +include("${CMAKE_CURRENT_LIST_DIR}/findDependancies.cmake") + + +get_target_property(dropOt_INCLUDE_DIRS visa::dropOt INTERFACE_INCLUDE_DIRECTORIES) + +get_target_property(dropOt_LIBRARIES visa::dropOt LOCATION) + +message("dropOt_INCLUDE_DIRS=${dropOt_INCLUDE_DIRS}") +message("dropOt_LIBRARIES=${dropOt_LIBRARIES}") diff --git a/tools/de-identification/visa-pets-FL/ot_library/cmake/buildOptions.cmake b/tools/de-identification/visa-pets-FL/ot_library/cmake/buildOptions.cmake new file mode 100644 index 0000000..1e5cd19 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/cmake/buildOptions.cmake @@ -0,0 +1,41 @@ + +macro(EVAL var) + if(${ARGN}) + set(${var} ON) + else() + set(${var} OFF) + endif() +endmacro() + +option(DROP_OT_FETCH_AUTO "automatically download and build dependencies" OFF) +option(DROP_OT_ENABLE_SSE "build with sse" ON) +option(DROP_OT_ENABLE_AVX "build with avx" ON) +option(DROP_OT_ENABLE_PIC "build with PIC" OFF) +option(DROP_OT_ENABLE_ASAN "build with asan" OFF) +option(DROP_OT_ENABLE_RELIC "build with Relic" ON) +option(DROP_OT_ENABLE_SODIUM "build with Sodium" OFF) + +if(NOT DEFINED DROP_OT_STD_VER) + set(DROP_OT_STD_VER 14) +endif() + +#option(DROP_OT_FETCH_CRYPTOTOOLS "download and build CRYPTOTOOLS" OFF)) +EVAL(DROP_OT_FETCH_CRYPTOTOOLS_AUTO + (DEFINED DROP_OT_FETCH_CRYPTOTOOLS AND DROP_OT_FETCH_CRYPTOTOOLS) OR + ((NOT DEFINED DROP_OT_FETCH_CRYPTOTOOLS) AND (DROP_OT_FETCH_AUTO))) + + + +message(STATUS "dropOt options\n=======================================================") + +message(STATUS "Option: DROP_OT_FETCH_AUTO = ${DROP_OT_FETCH_AUTO}") +message(STATUS "Option: DROP_OT_FETCH_CRYPTOTOOLS = ${DROP_OT_FETCH_CRYPTOTOOLS}") +message(STATUS "Option: DROP_OT_ENABLE_SSE = ${DROP_OT_ENABLE_SSE}") +message(STATUS "Option: DROP_OT_ENABLE_AVX = ${DROP_OT_ENABLE_AVX}") +message(STATUS "Option: DROP_OT_ENABLE_PIC = ${DROP_OT_ENABLE_PIC}") +message(STATUS "Option: DROP_OT_ENABLE_ASAN = ${DROP_OT_ENABLE_ASAN}") +message(STATUS "Option: DROP_OT_ENABLE_RELIC = ${DROP_OT_ENABLE_RELIC}") +message(STATUS "Option: DROP_OT_ENABLE_SODIUM = ${DROP_OT_ENABLE_SODIUM}") +message(STATUS "Option: DROP_OT_STD_VER = ${DROP_OT_STD_VER}\n") + + diff --git a/tools/de-identification/visa-pets-FL/ot_library/cmake/findDependancies.cmake b/tools/de-identification/visa-pets-FL/ot_library/cmake/findDependancies.cmake new file mode 100644 index 0000000..9388ad1 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/cmake/findDependancies.cmake @@ -0,0 +1,66 @@ +include(${CMAKE_CURRENT_LIST_DIR}/preamble.cmake) + +message(STATUS "DROP_OT_THIRDPARTY_DIR=${DROP_OT_THIRDPARTY_DIR}") + + +set(PUSHED_CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH}) +set(CMAKE_PREFIX_PATH "${DROP_OT_THIRDPARTY_DIR};${CMAKE_PREFIX_PATH}") + + +####################################### +# CRYPTOTOOLS + +macro(FIND_CRYPTOTOOLS) + set(ARGS ${ARGN}) + set(COMP) + + if(DROP_OT_ENABLE_RELIC) + set(COMP ${COMP} relic) + else() + #set(COMP ${COMP} no_relic) + endif() + if(DROP_OT_ENABLE_SODIUM) + set(COMP ${COMP} sodium) + else() + #set(COMP ${COMP} no_sodium) + endif() + #if(DROP_OT_ENABLE_PIC) + # set(COMP ${COMP} pic) + #else() + # set(COMP ${COMP} no_pic) + #endif() + message(STATUS "COMP=${COMP}") + #explicitly asked to fetch CRYPTOTOOLS + if(FETCH_CRYPTOTOOLS) + list(APPEND ARGS NO_DEFAULT_PATH PATHS ${DROP_OT_THIRDPARTY_DIR}) + endif() + + find_package(cryptoTools ${ARGS} COMPONENTS ${COMP}) + + if(TARGET oc::cryptoTools) + set(CRYPTOTOOLS_FOUND ON) + else() + set(CRYPTOTOOLS_FOUND OFF) + endif() +endmacro() + +if(DROP_OT_FETCH_CRYPTOTOOLS_AUTO) + FIND_CRYPTOTOOLS(QUIET) + include(${CMAKE_CURRENT_LIST_DIR}/../thirdparty/getCryptoTools.cmake) +endif() + +FIND_CRYPTOTOOLS(REQUIRED) + + + +####################################### +# diskhash + +if(NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/../diskhash/Makefile) + + include(${CMAKE_CURRENT_LIST_DIR}/../thirdparty/getDiskHash.cmake) + +endif() + +# resort the previous prefix path +set(CMAKE_PREFIX_PATH ${PUSHED_CMAKE_PREFIX_PATH}) diff --git a/tools/de-identification/visa-pets-FL/ot_library/cmake/install.cmake b/tools/de-identification/visa-pets-FL/ot_library/cmake/install.cmake new file mode 100644 index 0000000..57fec0c --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/cmake/install.cmake @@ -0,0 +1,67 @@ + + + +############################################# +# Install # +############################################# + + +configure_file("${CMAKE_CURRENT_LIST_DIR}/findDependancies.cmake" "findDependancies.cmake" COPYONLY) + +# make cache variables for install destinations +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + + +# generate the config file that is includes the exports +configure_package_config_file( + "${CMAKE_CURRENT_LIST_DIR}/Config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/dropOtConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/dropOt + NO_SET_AND_CHECK_MACRO + NO_CHECK_REQUIRED_COMPONENTS_MACRO +) + +if(NOT DEFINED dropOt_VERSION_MAJOR) + message("\n\n\n\n warning, dropOt_VERSION_MAJOR not defined ${dropOt_VERSION_MAJOR}") +endif() + +set_property(TARGET dropOt PROPERTY VERSION ${dropOt_VERSION}) + +# generate the version file for the config file +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/dropOtConfigVersion.cmake" + VERSION "${dropOt_VERSION_MAJOR}.${dropOt_VERSION_MINOR}.${dropOt_VERSION_PATCH}" + COMPATIBILITY AnyNewerVersion +) + +# install the configuration file +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/dropOtConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/dropOtConfigVersion.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/findDependancies.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/dropOt +) + +# install library +install( + TARGETS dropOt + DESTINATION ${CMAKE_INSTALL_LIBDIR} + EXPORT dropOtTargets) + +# install headers +install( + DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/../dropOt" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/" + FILES_MATCHING PATTERN "*.h") + +# install config +install(EXPORT dropOtTargets + FILE dropOtTargets.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/dropOt + NAMESPACE visa:: +) + export(EXPORT dropOtTargets + FILE "${CMAKE_CURRENT_BINARY_DIR}/dropOtTargets.cmake" + NAMESPACE visa:: +) \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/ot_library/cmake/preamble.cmake b/tools/de-identification/visa-pets-FL/ot_library/cmake/preamble.cmake new file mode 100644 index 0000000..d7a828b --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/cmake/preamble.cmake @@ -0,0 +1,87 @@ + +if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") + + ############################################ + # If top level cmake # + ############################################ + if(MSVC) + else() + set(COMMON_FLAGS "-Wall -Wfatal-errors") + + if(NOT DEFINED NO_ARCH_NATIVE) + set(COMMON_FLAGS "${COMMON_FLAGS} -march=native") + endif() + + SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") + SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO " -O2 -g -ggdb") + SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb") + #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") + + endif() + + + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMMON_FLAGS}") + + + ############################################ + # Build mode checks # + ############################################ + + # Set a default build type for single-configuration + # CMake generators if no build type is set. + if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE Release) + endif() + + if( NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Release" + AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" + AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" ) + + message(WARNING ": Unknown build type - \${CMAKE_BUILD_TYPE}=${CMAKE_BUILD_TYPE}. Please use one of Debug, Release, or RelWithDebInfo. e.g. call\n\tcmake . -DCMAKE_BUILD_TYPE=Release\n" ) + endif() +endif() + +if(MSVC) + set(DROP_OT_CONFIG_NAME "${CMAKE_BUILD_TYPE}") + if("${DROP_OT_CONFIG_NAME}" STREQUAL "RelWithDebInfo" OR "${DROP_OT_CONFIG_NAME}" STREQUAL "") + set(DROP_OT_CONFIG_NAME "Release") + endif() + set(DROP_OT_CONFIG "x64-${DROP_OT_CONFIG_NAME}") +elseif(APPLE) + set(DROP_OT_CONFIG "osx") +else() + set(DROP_OT_CONFIG "linux") +endif() + +if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/install.cmake) + set(DROP_OT_IN_BUILD_TREE ON) +else() + set(DROP_OT_IN_BUILD_TREE OFF) +endif() + +if(DROP_OT_IN_BUILD_TREE) + + # we currenty are in the vole psi source tree, vole-psi/cmake + if(NOT DEFINED DROP_OT_BUILD_DIR) + set(DROP_OT_BUILD_DIR "${CMAKE_CURRENT_LIST_DIR}/../out/build/${DROP_OT_CONFIG}") + get_filename_component(DROP_OT_BUILD_DIR ${DROP_OT_BUILD_DIR} ABSOLUTE) + endif() + + if(NOT (${CMAKE_BINARY_DIR} STREQUAL ${DROP_OT_BUILD_DIR})) + message(WARNING "incorrect build directory. \n\tCMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}\nbut expect\n\tDROP_OT_BUILD_DIR=${DROP_OT_BUILD_DIR}") + endif() + + if(NOT DEFINED DROP_OT_THIRDPARTY_DIR) + set(DROP_OT_THIRDPARTY_DIR "${CMAKE_CURRENT_LIST_DIR}/../out/install/${DROP_OT_CONFIG}") + get_filename_component(DROP_OT_THIRDPARTY_DIR ${DROP_OT_THIRDPARTY_DIR} ABSOLUTE) + endif() +else() + # we currenty are in install tree, /lib/cmake/vole-psi + if(NOT DEFINED DROP_OT_THIRDPARTY_DIR) + set(DROP_OT_THIRDPARTY_DIR "${CMAKE_CURRENT_LIST_DIR}/../../..") + get_filename_component(DROP_OT_THIRDPARTY_DIR ${DROP_OT_THIRDPARTY_DIR} ABSOLUTE) + endif() +endif() + diff --git a/tools/de-identification/visa-pets-FL/ot_library/drop-ot/CMakeLists.txt b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/CMakeLists.txt new file mode 100644 index 0000000..e971d34 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/CMakeLists.txt @@ -0,0 +1,31 @@ + +project(dropOt) + + +############################################# +# Build dropOt # +############################################# + +file(GLOB_RECURSE SRCS *.cpp) + + +add_library(dropOt ${SRCS}) + +target_include_directories(dropOt PUBLIC + $ + $) +target_include_directories(dropOt PUBLIC + $ + $) + + +target_link_libraries(dropOt oc::cryptoTools) + + + +if(MSVC) + target_compile_options(dropOt PUBLIC $<$:/std:c++${DROP_OT_STD_VER}>) + +else() + target_compile_options(dropOt PUBLIC $<$:-std=c++${DROP_OT_STD_VER}> -pthread) +endif() diff --git a/tools/de-identification/visa-pets-FL/ot_library/drop-ot/Defines.h b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/Defines.h new file mode 100644 index 0000000..d3dd15c --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/Defines.h @@ -0,0 +1,116 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#pragma once +#include "drop-ot/config.h" +#include "cryptoTools/Common/Defines.h" +#include "cryptoTools/Common/Version.h" +#include "cryptoTools/Crypto/PRNG.h" +#include "cryptoTools/Network/Channel.h" +#include "cryptoTools/Common/BitVector.h" +#include +#include + + +#if CRYPTO_TOOLS_VERSION < 10502 +static_assert(0, "please update cryptoTools"); +#endif + +#ifdef DROP_OT_ENABLE_RELIC +#include "cryptoTools/Crypto/RCurve.h" + +#ifndef ENABLE_RELIC +static_assert(0, "please enable relic in cryptoTools"); +#endif + +#if !defined(MULTI) || ((MULTI != PTHREAD) && (MULTI != OPENMP)) +static_assert(0,"Relic must be built with -DMULTI=PTHREAD or -DMULTI=OPENMP"); +#endif +#endif +#ifdef DROP_OT_ENABLE_SODIUM +#include "cryptoTools/Crypto/SodiumCurve.h" + +#endif + +namespace dropOt +{ + using u64 = oc::u64; + using i64 = oc::i64; + using u32 = oc::u32; + using i32 = oc::i32; + using u16 = oc::u16; + using i16 = oc::i16; + using u8 = oc::u8; + using i8 = oc::i8; + + template + using span = oc::span; + + using block = oc::block; + inline block toBlock(u8* data) { return oc::toBlock(data); } + inline block toBlock(u64 low_u64) { return oc::toBlock(low_u64); } + inline block toBlock(u64 high_u64, u64 low_u64) { return oc::toBlock(high_u64, low_u64); } + + +#ifdef DROP_OT_ENABLE_RELIC + using Number = oc::REccNumber; + using Point = oc::REccPoint; + using Curve = oc::REllipticCurve; +#else + + using Number = oc::Sodium::Prime25519; + using Point = oc::Sodium::Rist25519; + //using Curve = oc::REllipticCurve; + struct Curve { Curve() {} }; +#endif + +#ifdef ENABLE_BOOST + using Channel = oc::Channel; +#endif + + using BitVector = oc::BitVector; + using PRNG = oc::PRNG; + + std::string hex(span d); + + [[ noreturn ]] inline void panic(const std::string& msg) + { + std::cout << "Panic, " << msg << std::endl; + std::terminate(); + } + + inline u64 roundUpTo(u64 val, u64 step) { return ((val + step - 1) / step) * step; } + + enum class Role { + R0, R1 + }; + + + + enum class Op + { + Default, Add, Mult + }; + + inline void Agg(Point& agg, Point& r, Op op) + { + if (op == Op::Mult) + panic("logic error"); + + agg += r; + } + + inline void Agg(Number& agg, Number& r, Op op) + { + if (op == Op::Add) + agg += r; + else + agg *= r; + } +} + + diff --git a/tools/de-identification/visa-pets-FL/ot_library/drop-ot/IknpOtExt.cpp b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/IknpOtExt.cpp new file mode 100644 index 0000000..211628f --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/IknpOtExt.cpp @@ -0,0 +1,443 @@ +#include "IknpOtExt.h" + +#include +#include + + +#include "cryptoTools/Network/IOService.h" +#include "cryptoTools/Network/Session.h" +#include "cryptoTools/Common/TestCollection.h" +#include "Tools.h" +#include +#include + +#define OTE_RANDOM_ORACLE 1 +#define OTE_DAVIE_MEYER_AES 2 +#define OTE_KOS_HASH OTE_DAVIE_MEYER_AES + +namespace dropOt +{ + + + void IknpOtExtSender::setUniformBaseOts(span baseRecvOts, const BitVector & choices) + { + mPrngIdx = 0; + mBaseChoiceBits = choices; + mGens.setKeys(baseRecvOts); + } + + void IknpOtExtSender::sendRoundOne_r( + span> messages, + PRNG& prng, + span& buffer) + { + + if (hasBaseOts() == false) + panic("IknpOtExtSender has no base OTs"); + + auto numOtExt = u64{}; + auto numBlocks = u64{}; + auto step = u64{}; + auto blkIdx = u64{}; + auto t = oc::AlignedUnVector{ 128 }; + auto u = oc::AlignedUnVector(128 * commStepSize); + auto choiceMask = oc::AlignedArray{}; + auto delta = block{}; + auto recvView = span{}; + auto mIter = span>::iterator{}; + auto uIter = (block*)nullptr; + auto tIter = (block*)nullptr; + auto cIter = (block*)nullptr; + auto uEnd = (block*)nullptr; + + // round up + numOtExt = roundUpTo(messages.size(), 128); + numBlocks = (numOtExt / 128); + //u64 numBlocks = numBlocks * superBlkSize; + + + delta = *(block*)mBaseChoiceBits.data(); + + for (u64 i = 0; i < 128; ++i) + { + if (mBaseChoiceBits[i]) choiceMask[i] = oc::AllOneBlock; + else choiceMask[i] = oc::ZeroBlock; + } + + mIter = messages.begin(); + uEnd = u.data() + u.size(); + uIter = uEnd; + + for (blkIdx = 0; blkIdx < numBlocks; ++blkIdx) + { + tIter = (block*)t.data(); + cIter = choiceMask.data(); + + if (uIter == uEnd) + { + step = std::min(numBlocks - blkIdx, (u64)commStepSize); + step *= 128 * sizeof(block); + recvView = span((u8*)u.data(), step); + uIter = u.data(); + + std::copy(buffer.begin(), buffer.begin() + recvView.size(), recvView.begin()); + buffer = buffer.subspan(recvView.size()); + } + + mGens.ecbEncCounterMode(mPrngIdx, tIter); + ++mPrngIdx; + + // transpose 128 columns at at time. Each column will be 128 * superBlkSize = 1024 bits long. + for (u64 colIdx = 0; colIdx < 128 / 8; ++colIdx) + { + uIter[0] = uIter[0] & cIter[0]; + uIter[1] = uIter[1] & cIter[1]; + uIter[2] = uIter[2] & cIter[2]; + uIter[3] = uIter[3] & cIter[3]; + uIter[4] = uIter[4] & cIter[4]; + uIter[5] = uIter[5] & cIter[5]; + uIter[6] = uIter[6] & cIter[6]; + uIter[7] = uIter[7] & cIter[7]; + + tIter[0] = tIter[0] ^ uIter[0]; + tIter[1] = tIter[1] ^ uIter[1]; + tIter[2] = tIter[2] ^ uIter[2]; + tIter[3] = tIter[3] ^ uIter[3]; + tIter[4] = tIter[4] ^ uIter[4]; + tIter[5] = tIter[5] ^ uIter[5]; + tIter[6] = tIter[6] ^ uIter[6]; + tIter[7] = tIter[7] ^ uIter[7]; + + cIter += 8; + uIter += 8; + tIter += 8; + } + + // transpose our 128 columns of 1024 bits. We will have 1024 rows, + // each 128 bits wide. + transpose128(t.data()); + + + auto mEnd = mIter + std::min(128, messages.end() - mIter); + + tIter = t.data(); + if (mEnd - mIter == 128) + { + for (u64 i = 0; i < 128; i += 8) + { + mIter[i + 0][0] = tIter[i + 0]; + mIter[i + 1][0] = tIter[i + 1]; + mIter[i + 2][0] = tIter[i + 2]; + mIter[i + 3][0] = tIter[i + 3]; + mIter[i + 4][0] = tIter[i + 4]; + mIter[i + 5][0] = tIter[i + 5]; + mIter[i + 6][0] = tIter[i + 6]; + mIter[i + 7][0] = tIter[i + 7]; + mIter[i + 0][1] = tIter[i + 0] ^ delta; + mIter[i + 1][1] = tIter[i + 1] ^ delta; + mIter[i + 2][1] = tIter[i + 2] ^ delta; + mIter[i + 3][1] = tIter[i + 3] ^ delta; + mIter[i + 4][1] = tIter[i + 4] ^ delta; + mIter[i + 5][1] = tIter[i + 5] ^ delta; + mIter[i + 6][1] = tIter[i + 6] ^ delta; + mIter[i + 7][1] = tIter[i + 7] ^ delta; + + } + + mIter += 128; + + } + else + { + while (mIter != mEnd) + { + (*mIter)[0] = *tIter; + (*mIter)[1] = *tIter ^ delta; + + tIter += 1; + mIter += 1; + } + } + } + + + { + +#ifdef IKNP_SHA_HASH + RandomOracle sha; + u8 hashBuff[20]; + u64 doneIdx = 0; + + + u64 bb = (messages.size() + 127) / 128; + for (u64 blockIdx = 0; blockIdx < bb; ++blockIdx) + { + u64 stop = std::min(messages.size(), doneIdx + 128); + + for (u64 i = 0; doneIdx < stop; ++doneIdx, ++i) + { + // hash the message without delta + sha.Reset(); + sha.Update((u8*)&messages[doneIdx][0], sizeof(block)); + sha.Final(hashBuff); + messages[doneIdx][0] = *(block*)hashBuff; + + // hash the message with delta + sha.Reset(); + sha.Update((u8*)&messages[doneIdx][1], sizeof(block)); + sha.Final(hashBuff); + messages[doneIdx][1] = *(block*)hashBuff; + } + } +#else + + oc::mAesFixedKey.hashBlocks((block*)messages.data(), messages.size() * 2, (block*)messages.data()); + } +#endif + } + + IknpOtExtReceiver::IknpOtExtReceiver(span> baseOTs) + { + setUniformBaseOts(baseOTs); + } + + void IknpOtExtReceiver::setUniformBaseOts(span> baseOTs) + { + mPrngIdx = 0; + for (u64 j = 0; j < 2; ++j) + { + block buff[gOtExtBaseOtCount]; + for (u64 i = 0; i < gOtExtBaseOtCount; i++) + buff[i] = baseOTs[i][j]; + + mGens[j].setKeys(buff); + } + + mHasBase = true; + } + + void IknpOtExtReceiver::receiveRoundOne_s( + const BitVector& choices, + span messages, + PRNG& prng, + std::vector& buffer) + { + if (hasBaseOts() == false) + panic("base OTs for receiver not set"); + + if (choices.size() != messages.size()) + throw RTE_LOC; + + auto numOtExt = u64{}; + auto numBlocks = u64{}; + auto blkIdx = u64{}; + auto step = u64{}; + auto choiceBlocks = span{}; + auto t0 = oc::AlignedUnVector{ 128 }; + auto mIter = span::iterator{}; + auto uIter = (block*)nullptr; + auto tIter = (block*)nullptr; + auto cIter = (block*)nullptr; + auto uEnd = (block*)nullptr; + auto uBuff = oc::AlignedUnVector{}; + + // we are going to process OTs in blocks of 128 * superBlkSize messages. + numOtExt = roundUpTo(choices.size(), 128); + numBlocks = (numOtExt / 128); + + choiceBlocks = { choices.blocks(), choices.sizeBlocks() }; + + // the index of the OT that has been completed. + //u64 doneIdx = 0; + + mIter = messages.begin(); + + step = std::min(numBlocks, (u64)commStepSize); + uBuff.resize(step * 128); + + // get an array of blocks that we will fill. + uIter = (block*)uBuff.data(); + uEnd = uIter + uBuff.size(); + + // NOTE: We do not transpose a bit-matrix of size numCol * numCol. + // Instead we break it down into smaller chunks. We do 128 columns + // times 8 * 128 rows at a time, where 8 = superBlkSize. This is done for + // performance reasons. The reason for 8 is that most CPUs have 8 AES vector + // lanes, and so its more efficient to encrypt (aka prng) 8 blocks at a time. + // So that's what we do. + for (blkIdx = 0; blkIdx < numBlocks; ++blkIdx) + { + + // this will store the next 128 rows of the matrix u + + tIter = (block*)t0.data(); + cIter = choiceBlocks.data() + blkIdx; + + mGens[0].ecbEncCounterMode(mPrngIdx, tIter); + mGens[1].ecbEncCounterMode(mPrngIdx, uIter); + ++mPrngIdx; + + for (u64 colIdx = 0; colIdx < 128 / 8; ++colIdx) + { + uIter[0] = uIter[0] ^ cIter[0]; + uIter[1] = uIter[1] ^ cIter[0]; + uIter[2] = uIter[2] ^ cIter[0]; + uIter[3] = uIter[3] ^ cIter[0]; + uIter[4] = uIter[4] ^ cIter[0]; + uIter[5] = uIter[5] ^ cIter[0]; + uIter[6] = uIter[6] ^ cIter[0]; + uIter[7] = uIter[7] ^ cIter[0]; + + uIter[0] = uIter[0] ^ tIter[0]; + uIter[1] = uIter[1] ^ tIter[1]; + uIter[2] = uIter[2] ^ tIter[2]; + uIter[3] = uIter[3] ^ tIter[3]; + uIter[4] = uIter[4] ^ tIter[4]; + uIter[5] = uIter[5] ^ tIter[5]; + uIter[6] = uIter[6] ^ tIter[6]; + uIter[7] = uIter[7] ^ tIter[7]; + + uIter += 8; + tIter += 8; + } + + if (uIter == uEnd) + { + // send over u buffer + auto begin = buffer.size(); + buffer.resize(begin + uBuff.size() * sizeof(block)); + std::copy((u8*)uBuff.data(), (u8*)(uBuff.data() + uBuff.size()), buffer.begin() + begin); + + u64 step = std::min(numBlocks - blkIdx - 1, (u64)commStepSize); + + if (step) + { + uBuff.resize(step * 128); + uIter = (block*)uBuff.data(); + uEnd = uIter + uBuff.size(); + } + } + + // transpose our 128 columns of 1024 bits. We will have 1024 rows, + // each 128 bits wide. + transpose128(t0.data()); + + + auto mEnd = mIter + std::min(128, messages.end() - mIter); + + + tIter = t0.data(); + + memcpy(mIter, tIter, (mEnd - mIter) * sizeof(block)); + mIter = mEnd; + +#ifdef IKNP_DEBUG + ... fix this + u64 doneIdx = mStart - messages.data(); + block* msgIter = messages.data() + doneIdx; + chl.send(msgIter, sizeof(block) * 128 * superBlkSize); + cIter = choiceBlocks.data() + superBlkSize * blkIdx; + chl.send(cIter, sizeof(block) * superBlkSize); +#endif + //doneIdx = stopIdx; + } + + { + +#ifdef IKNP_SHA_HASH + RandomOracle sha; + u8 hashBuff[20]; + u64 doneIdx = (0); + + u64 bb = (messages.size() + 127) / 128; + for (u64 blockIdx = 0; blockIdx < bb; ++blockIdx) + { + u64 stop = std::min(messages.size(), doneIdx + 128); + + for (u64 i = 0; doneIdx < stop; ++doneIdx, ++i) + { + // hash it + sha.Reset(); + sha.Update((u8*)&messages[doneIdx], sizeof(block)); + sha.Final(hashBuff); + messages[doneIdx] = *(block*)hashBuff; + } + } +#else + oc::mAesFixedKey.hashBlocks(messages.data(), messages.size(), messages.data()); +#endif + + } + } + + + + + namespace tests + { + + + void OtExt_Iknp_Buff_test() + { + PRNG prng0(toBlock(4253465, 3434565)); + PRNG prng1(toBlock(233465, 334565)); + + u64 numOTs = 1<<17; + + std::vector recvMsg(numOTs), baseRecv(128); + std::vector> sendMsg(numOTs), baseSend(128); + BitVector choices(numOTs), baseChoice(128); + choices.randomize(prng0); + baseChoice.randomize(prng0); + + for (u64 i = 0; i < 128; ++i) + { + baseSend[i][0] = prng0.get(); + baseSend[i][1] = prng0.get(); + baseRecv[i] = baseSend[i][baseChoice[i]]; + } + + IknpOtExtSender sender; + IknpOtExtReceiver recv; + recv.setUniformBaseOts(baseSend); + sender.setUniformBaseOts(baseRecv, baseChoice); + + recv.mPrngIdx = 42; + sender.mPrngIdx = 42; + + std::stringstream rss, sss; + recv.serialize(rss); + recv = {}; + recv.deserialize(rss); + if (recv.mPrngIdx != 42) + throw RTE_LOC; + + + sender.serialize(rss); + sender = {}; + sender.deserialize(rss); + if (sender.mPrngIdx != 42) + throw RTE_LOC; + + std::vector buffer; + recv.receiveRoundOne_s(choices, recvMsg, prng0, buffer); + + span bSpan = buffer; + sender.sendRoundOne_r(sendMsg, prng1, bSpan); + + for (u64 i = 0; i < choices.size(); ++i) + { + u8 choice = choices[i]; + const block& revcBlock = recvMsg[i]; + const block& senderBlock = sendMsg[i][choice]; + + if (neq(revcBlock, senderBlock)) + throw oc::UnitTestFail(); + + if (eq(revcBlock, sendMsg[i][1 ^ choice])) + throw oc::UnitTestFail(); + } + } + } +} + diff --git a/tools/de-identification/visa-pets-FL/ot_library/drop-ot/IknpOtExt.h b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/IknpOtExt.h new file mode 100644 index 0000000..d9ed0cf --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/IknpOtExt.h @@ -0,0 +1,237 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#pragma once +#include "drop-ot/Defines.h" +#include +#include "cryptoTools/Crypto/RandomOracle.h" + +namespace dropOt { + + + const u64 commStepSize(512); + const u64 gOtExtBaseOtCount(128); + + class IknpOtExtSender + { + public: + + u64 mPrngIdx = 0; + oc::MultiKeyAES mGens; + BitVector mBaseChoiceBits; + + IknpOtExtSender() = default; + IknpOtExtSender(const IknpOtExtSender&) = delete; + IknpOtExtSender(IknpOtExtSender&&) = default; + + IknpOtExtSender( + span baseRecvOts, + const BitVector& choices) + { + setUniformBaseOts(baseRecvOts, choices); + } + + void operator=(IknpOtExtSender&& v) + { + mGens = std::move(v.mGens); + mBaseChoiceBits = std::move(v.mBaseChoiceBits); + } + + // return true if this instance has valid base OTs. + bool hasBaseOts() const + { + return mBaseChoiceBits.size() > 0; + } + + // Returns a independent instance of this extender which can + // be executed concurrently. The base OTs are derived from the + // original base OTs. + //IknpOtExtSender split(); + + // Sets the base OTs which must be peformed before calling split or send. + // See frontend/main.cpp for an example. + void setUniformBaseOts( + span baseRecvOts, + const BitVector& choices); + + + // The first round of the OT-sender protocol. This will receive a + // message and send a message. + // This function can return early with a code::suspend error in which + // case the caller should perform io and call the function again. + // @messages, output: the random messages that will be returned. + // @prng, input: the randomness source. + // @chl, input: the io buffer/socket. + void sendRoundOne_r( + span> messages, + PRNG& prng, + span& ioBuffer); + + static constexpr auto header = "iknp-sender"; + void serialize(std::ostream& out) + { + out.write(header, std::strlen(header)); + bool hasBase = hasBaseOts(); + + out.write((char*)&hasBase, sizeof(hasBase)); + out.write((char*)&mPrngIdx, sizeof(mPrngIdx)); + + if (hasBase) + { + out.write((char*)mBaseChoiceBits.data(), mBaseChoiceBits.sizeBytes()); + + for (u64 i = 0; i < mGens.mAESs.size(); ++i) + { + auto k = mGens.mAESs[i].getKey(); + out.write((char*)&k, sizeof(k)); + } + } + } + + void deserialize(std::istream& in) + { + std::vector buff(std::strlen(header)); + in.read((char*)buff.data(), buff.size()); + + if (std::memcmp(buff.data(), header, buff.size())) + { + std::cout << header << " failed to deserialize. Bad header. " LOCATION << std::endl; + throw RTE_LOC; + } + + bool hasBase; + + in.read((char*)&hasBase, sizeof(hasBase)); + in.read((char*)&mPrngIdx, sizeof(mPrngIdx)); + + if (hasBase) + { + mBaseChoiceBits.resize(gOtExtBaseOtCount); + in.read((char*)mBaseChoiceBits.data(), mBaseChoiceBits.sizeBytes()); + + for (u64 i = 0; i < mGens.mAESs.size(); ++i) + { + block k; + in.read((char*)&k, sizeof(k)); + mGens.mAESs[i].setKey(k); + } + } + } + + }; + + + class IknpOtExtReceiver + { + public: + + + bool mHasBase = false; + oc::AlignedArray, 2> mGens; + u64 mPrngIdx = 0; + + IknpOtExtReceiver() = default; + IknpOtExtReceiver(const IknpOtExtReceiver&) = delete; + IknpOtExtReceiver(IknpOtExtReceiver&&) = default; + IknpOtExtReceiver(span> baseSendOts); + + void operator=(IknpOtExtReceiver&& v) + { + mHasBase = std::move(v.mHasBase); + mGens = std::move(v.mGens); + v.mHasBase = false; + } + + // returns whether the base OTs have been set. They must be set before + // split or receive is called. + bool hasBaseOts() const + { + return mHasBase; + } + + // sets the base OTs. + void setUniformBaseOts(span> baseSendOts); + + // returns an independent instance of this extender which can securely be + // used concurrently to this current one. The base OTs for the new instance + // are derived from the orginial base OTs. + //IknpOtExtReceiver splitBase(); + + // The first round of the OT-receiver protocol. This will send a message. + // This function can return early with a code::suspend error in which + // case the caller should perform io and call the function again. + // @choices, input: the choice bits that the receiver choose. + // @messages, output: the random messages that will be returned. + // @prng, input: the randomness source. + // @chl, input: the io buffer/socket. + void receiveRoundOne_s( + const BitVector& choices, + span messages, + PRNG& prng, + std::vector& ioBuffer); + + + static constexpr auto header = "iknp-recver"; + + void serialize(std::ostream& out) + { + out.write(header, std::strlen(header)); + + out.write((char*)&mHasBase, sizeof(mHasBase)); + out.write((char*)&mPrngIdx, sizeof(mPrngIdx)); + + if (mHasBase) + { + for (u64 i = 0; i < mGens[0].mAESs.size(); ++i) + { + auto k0 = mGens[0].mAESs[i].getKey(); + auto k1 = mGens[1].mAESs[i].getKey(); + + out.write((char*)&k0, sizeof(k0)); + out.write((char*)&k1, sizeof(k1)); + } + } + } + + void deserialize(std::istream& in) + { + std::vector buff(std::strlen(header)); + in.read((char*)buff.data(), buff.size()); + + if (std::memcmp(buff.data(), header, buff.size())) + { + std::cout << header << " failed to deserialize. Bad header. " LOCATION << std::endl; + throw RTE_LOC; + } + + in.read((char*)&mHasBase, sizeof(mHasBase)); + in.read((char*)&mPrngIdx, sizeof(mPrngIdx)); + + if (mHasBase) + { + for (u64 i = 0; i < mGens[0].mAESs.size(); ++i) + { + block k0; + block k1; + in.read((char*)&k0, sizeof(k0)); + in.read((char*)&k1, sizeof(k1)); + mGens[0].mAESs[i].setKey(k0); + mGens[1].mAESs[i].setKey(k1); + } + } + } + + }; + + + namespace tests + { + + void OtExt_Iknp_Buff_test(); + } +} + diff --git a/tools/de-identification/visa-pets-FL/ot_library/drop-ot/MasnyRindal.cpp b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/MasnyRindal.cpp new file mode 100644 index 0000000..069e66d --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/MasnyRindal.cpp @@ -0,0 +1,224 @@ +#include "MasnyRindal.h" + + +#include +#include +#include +#include + +#include "cryptoTools/Network/IOService.h" +#include "cryptoTools/Network/Session.h" +#include "cryptoTools/Common/TestCollection.h" + +#include +#include + +namespace dropOt +{ + + void MasnyRindal::receiveRoundOne( + BitVector choices_, + PRNG& prng, + std::vector& ioBuffer) + { + if (mState != State::RoundOne) + panic("bad state, " LOCATION); + + + mChoices = std::move(choices_); + + auto n = mChoices.size(); + Curve curve; + std::array r; + auto pointSize = Point::size; + + Point hPoint; + std::vector hashBuff(roundUpTo(pointSize, 16)); + mSk = {}; + mSk.reserve(n); + + auto begin = ioBuffer.size(); + ioBuffer.resize(begin + Point::size * 2 * n); + span buff = { ioBuffer.begin() + begin, ioBuffer.end() }; + // loop over the input and generate the private + // keys. + for (u64 i = 0; i < n; ++i) + { + // We process things in batches of max size step. We + // then send those off before the next step is started. + auto& rrNot = r[mChoices[i] ^ 1]; + auto& rr = r[mChoices[i]]; + + rrNot.randomize(prng); + rrNot.toBytes(hashBuff.data()); + hPoint.fromHash(hashBuff.data(), pointSize); + + mSk.emplace_back(prng); + rr = Point::mulGenerator(mSk[i]); + rr -= hPoint; + + r[0].toBytes(buff.subspan(0, Point::size).data()); buff = buff.subspan(Point::size); + r[1].toBytes(buff.subspan(0, Point::size).data()); buff = buff.subspan(Point::size); + } + + // progress to the next round. + mState = State::RoundTwo; + } + + void MasnyRindal::receiveRoundTwo( + span messages, + PRNG& prng, + span& recvBuff) + { + if (mState != State::RoundTwo) + panic("bad state, " LOCATION); + + Curve curve; + Point Mb, k; + auto n = mChoices.size(); + auto pointSize = Point::size; + + if (recvBuff.size() != Point::size) + throw RTE_LOC; + + // Compute the random OT messages. + Mb.fromBytes(recvBuff.data()); + std::vector hashBuff(Point::size); + oc::RandomOracle ro(sizeof(block)); + for (u64 i = 0; i < n; ++i) + { + k = Mb; + k *= mSk[i]; + + k.toBytes(hashBuff.data()); + ro.Reset(); + ro.Update(i * 2 + mChoices[i]); + ro.Update(hashBuff.data(), Point::size); + ro.Final(messages[i]); + } + + resetState(); + } + + void MasnyRindal::sendRoundOne( + u64 n, + PRNG& prng, + std::vector& buff) + { + if (mState != State::RoundOne) + panic("bad state, " LOCATION); + + Curve curve; + auto pointSize = Point::size; + mSk = {}; + mSk.emplace_back(); + mSk[0].randomize(prng); + + Point Mb = Point::mulGenerator(mSk[0]); + + auto begin = buff.size(); + buff.resize(begin + Point::size); + Mb.toBytes(buff.data() + begin); + + mState = State::RoundTwo; + } + + + + void MasnyRindal::sendRoundTwo( + span> messages, + PRNG& prng, + span& buff) + { + if (mState != State::RoundTwo) + panic("bad state, " LOCATION); + + u64 n = static_cast(messages.size()); + Curve curve; + auto pointSize = Point::size; + oc::RandomOracle ro(sizeof(block)); + + std::vector hashBuff(roundUpTo(pointSize, 16)); + Point pHash, r; + + if (buff.size() != n * Point::size * 2) + throw RTE_LOC; + + for (u64 i = 0; i < n; ++i) + { + std::array, 2> buffIters{ + buff.subspan(0,Point::size), + buff.subspan(Point::size,Point::size) + }; + buff = buff.subspan(2 * Point::size); + + for (u64 j = 0; j < 2; ++j) + { + + r.fromBytes(buffIters[j].data()); + pHash.fromHash(buffIters[j ^ 1].data(), int(pointSize)); + r += pHash; + r *= mSk[0]; + + r.toBytes(hashBuff.data()); + ro.Reset(); + ro.Update(i * 2 + j); + ro.Update(hashBuff.data(), Point::size); + ro.Final(messages[i][j]); + } + } + + resetState(); + } + + namespace tests + { + + void Bot_MasnyRindal_Buff_test() + { + + PRNG prng0(oc::ZeroBlock); + PRNG prng1(oc::OneBlock); + + u64 numOTs = 50; + std::vector recvMsg(numOTs); + std::vector> sendMsg(numOTs); + BitVector choices(numOTs); + choices.randomize(prng0); + + MasnyRindal baseOTs0, baseOTs1; + + std::vector buff0, buff1; + + baseOTs0.receiveRoundOne(choices, prng0, buff0); + baseOTs1.sendRoundOne(numOTs, prng1, buff1); + + std::stringstream ss0; + baseOTs0.serialize(ss0); + baseOTs0.resetState(); + baseOTs0.deserialize(ss0); + + //std::stringstream ss1; + //baseOTs1.serialize(ss1); + //baseOTs1.resetState(); + //baseOTs1.deserialize(ss1); + + + span span1 = buff1; + span span0 = buff0; + baseOTs0.receiveRoundTwo(recvMsg, prng0, span1); + baseOTs1.sendRoundTwo(sendMsg, prng1, span0); + + for (u64 i = 0; i < numOTs; ++i) + { + if (neq(recvMsg[i], sendMsg[i][choices[i]])) + { + std::cout << "failed " << i << " exp = m[" << int(choices[i]) << "], act = " << recvMsg[i] << " true = " << sendMsg[i][0] << ", " << sendMsg[i][1] << std::endl; + throw oc::UnitTestFail(); + } + } + } + + } + +} diff --git a/tools/de-identification/visa-pets-FL/ot_library/drop-ot/MasnyRindal.h b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/MasnyRindal.h new file mode 100644 index 0000000..48b1bc7 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/MasnyRindal.h @@ -0,0 +1,183 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#pragma once +#include "Defines.h" + +namespace dropOt +{ + // This is the Base OT protocl of Masny, Rindal 2019. + // See https://eprint.iacr.org/2019/706. + class MasnyRindal + { + public: + + enum State + { + RoundOne, + RoundTwo + }; + + // The secret keys of each party. + std::vector mSk; + + // receiver choice bits. + BitVector mChoices; + + // The current state of the protocol. + State mState = State::RoundOne; + void resetState() { + mState = State::RoundOne; + mSk.resize(0); + mChoices.resize(0); + } + + + + static constexpr auto header = "MasnyRindal"; + + void serialize(std::ostream& out) + { + Curve curve; + + out.write(header, std::strlen(header)); + out.write((char*)&mState, sizeof(mState)); + + u64 size = mSk.size(); + out.write((char*)&size, sizeof(size)); + + if (size) + { + std::vector buff; + size = mSk[0].sizeBytes(); + + out.write((char*)&size, sizeof(size)); + buff.resize(size); + for (auto& sk : mSk) + { + sk.toBytes(buff.data()); + out.write((char*)buff.data(), buff.size()); + } + } + + size = mChoices.size(); + out.write((char*)&size, sizeof(size)); + if (size) + { + out.write((char*)mChoices.data(), mChoices.sizeBytes()); + + } + } + + + void deserialize(std::istream& in) + { + std::vector buff(std::strlen(header)); + in.read((char*)buff.data(), buff.size()); + + if (std::memcmp(buff.data(), header, buff.size())) + { + std::cout << header << " failed to deserialize. Bad header. " LOCATION << std::endl; + throw RTE_LOC; + } + + in.read((char*)&mState, sizeof(mState)); + + if (mState != State::RoundOne && mState != State::RoundTwo) + { + std::cout << header << " failed to deserialize. Bad state. " LOCATION << std::endl; + throw RTE_LOC; + } + + Curve curve; + u64 size; + in.read((char*)&size, sizeof(size)); + mSk.resize(size); + + if (size) + { + u64 size2; + in.read((char*)&size2, sizeof(size2)); + if (size2 != mSk[0].sizeBytes()) + { + std::cout << header << " failed to deserialize. Bad sk key size. " LOCATION << std::endl; + throw RTE_LOC; + } + } + for (auto i = 0; i < size; ++i) + { + buff.resize(mSk[i].sizeBytes()); + in.read((char*)buff.data(), buff.size()); + mSk[i].fromBytes(buff.data()); + } + + + in.read((char*)&size, sizeof(size)); + if (size) + { + mChoices.resize(size); + in.read((char*)mChoices.data(), mChoices.sizeBytes()); + } + } + + + // Perform round 1 of the OT-receiver + // protocol. Will return a dropOt::code + // which encodes {success, error, ...}. + // @choices, input: the choice bits of the messages. + // @prng, input: the source of randomness. + // @chl, input: the location the io should be send/recv. + void receiveRoundOne( + BitVector choices, + PRNG& prng, + std::vector& ioBuffer); + + // Perform round 2 of the OT-receiver + // protocol. Will return a dropOt::code + // which encodes {success, suspend, error, ...}. + // If suspend is returned the caller should + // perform io and call the function again. + // @choices, input: the choice bits of the messages. + // @messages, output: the location that the random messages will be written to. + // @prng, input: the source of randomness. + // @chl, input: the location the io should be send/recv. + void receiveRoundTwo( + span messages, + PRNG& prng, + span& ioBuffer); + + // Perform round 1 of the OT-sender + // protocol. Will return a dropOt::code + // which encodes {success, error, ...}. + // @n, input: the number of OTs to perfom. + // @prng, input: the source of randomness. + // @chl, input: the location the io should be send/recv. + void sendRoundOne( + u64 n, + PRNG& prng, + std::vector& ioBuffer); + + + // Perform round 2 of the OT-sender + // protocol. Will return a dropOt::code + // which encodes {success, suspend, error, ...}. + // If suspend is returned the caller should + // perform io and call the function again. + // @messages, output: the location that the random messages will be written to. + // @prng, input: the source of randomness. + // @chl, input: the location the io should be send/recv. + void sendRoundTwo( + span> messages, + PRNG& prng, + span& ioBuffer); + }; + + namespace tests { + void Bot_MasnyRindal_Buff_test(); + } + +} diff --git a/tools/de-identification/visa-pets-FL/ot_library/drop-ot/Tools.cpp b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/Tools.cpp new file mode 100644 index 0000000..74cafe2 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/Tools.cpp @@ -0,0 +1,942 @@ +#include "Tools.h" +#include +#include +#include + +#ifdef OC_ENABLE_SSE2 +#ifndef _MSC_VER +#include +#endif +#include +#endif +#ifdef OC_ENABLE_AVX2 +#include +#endif + +#include +#include +#include "cryptoTools/Common/Aligned.h" +using std::array; + +namespace osuCrypto { + + + + void eklundh_transpose128(block* inOut) + { + const static u64 TRANSPOSE_MASKS128[7][2] = { + { 0x0000000000000000, 0xFFFFFFFFFFFFFFFF }, + { 0x00000000FFFFFFFF, 0x00000000FFFFFFFF }, + { 0x0000FFFF0000FFFF, 0x0000FFFF0000FFFF }, + { 0x00FF00FF00FF00FF, 0x00FF00FF00FF00FF }, + { 0x0F0F0F0F0F0F0F0F, 0x0F0F0F0F0F0F0F0F }, + { 0x3333333333333333, 0x3333333333333333 }, + { 0x5555555555555555, 0x5555555555555555 } + }; + + u32 width = 64; + u32 logn = 7, nswaps = 1; + +#ifdef TRANSPOSE_DEBUG + stringstream input_ss[128]; + stringstream output_ss[128]; +#endif + + // now transpose output a-place + for (u32 i = 0; i < logn; i++) + { + u64 mask1 = TRANSPOSE_MASKS128[i][1], mask2 = TRANSPOSE_MASKS128[i][0]; + u64 inv_mask1 = ~mask1, inv_mask2 = ~mask2; + + // for width >= 64, shift is undefined so treat as h special case + // (and avoid branching a inner loop) + if (width < 64) + { + for (u32 j = 0; j < nswaps; j++) + { + for (u32 k = 0; k < width; k++) + { + u32 i1 = k + 2 * width*j; + u32 i2 = k + width + 2 * width*j; + + // t1 is lower 64 bits, t2 is upper 64 bits + // (remember we're transposing a little-endian format) + u64& d1 = ((u64*)&inOut[i1])[0]; + u64& d2 = ((u64*)&inOut[i1])[1]; + + u64& dd1 = ((u64*)&inOut[i2])[0]; + u64& dd2 = ((u64*)&inOut[i2])[1]; + + u64 t1 = d1; + u64 t2 = d2; + + u64 tt1 = dd1; + u64 tt2 = dd2; + + // swap operations due to little endian-ness + d1 = (t1 & mask1) ^ ((tt1 & mask1) << width); + + d2 = (t2 & mask2) ^ + ((tt2 & mask2) << width) ^ + ((tt1 & mask1) >> (64 - width)); + + dd1 = (tt1 & inv_mask1) ^ + ((t1 & inv_mask1) >> width) ^ + ((t2 & inv_mask2)) << (64 - width); + + dd2 = (tt2 & inv_mask2) ^ + ((t2 & inv_mask2) >> width); + } + } + } + else + { + for (u32 j = 0; j < nswaps; j++) + { + for (u32 k = 0; k < width; k++) + { + u32 i1 = k + 2 * width*j; + u32 i2 = k + width + 2 * width*j; + + // t1 is lower 64 bits, t2 is upper 64 bits + // (remember we're transposing a little-endian format) + u64& d1 = ((u64*)&inOut[i1])[0]; + u64& d2 = ((u64*)&inOut[i1])[1]; + + u64& dd1 = ((u64*)&inOut[i2])[0]; + u64& dd2 = ((u64*)&inOut[i2])[1]; + + //u64 t1 = d1; + u64 t2 = d2; + + //u64 tt1 = dd1; + //u64 tt2 = dd2; + + d1 &= mask1; + d2 = (t2 & mask2) ^ + ((dd1 & mask1) >> (64 - width)); + + dd1 = (dd1 & inv_mask1) ^ + ((t2 & inv_mask2)) << (64 - width); + + dd2 &= inv_mask2; + } + } + } + nswaps *= 2; + width /= 2; + } +#ifdef TRANSPOSE_DEBUG + for (u32 k = 0; k < 128; k++) + { + for (u32 blkIdx = 0; blkIdx < 128; blkIdx++) + { + output_ss[blkIdx] << inOut[offset + blkIdx].get_bit(k); + } + } + for (u32 k = 0; k < 128; k++) + { + if (output_ss[k].str().compare(input_ss[k].str()) != 0) + { + cerr << "String " << k << " failed. offset = " << offset << endl; + exit(1); + } + } + std::cout << "\ttranspose with offset " << offset << " ok\n"; +#endif + } + + + + void eklundh_transpose128x1024(std::array, 128>& inOut) + { + + + for (u64 i = 0; i < 8; ++i) + { + std::array sub; + for (u64 j = 0; j < 128; ++j) + sub[j] = inOut[j][i]; + + eklundh_transpose128(sub.data()); + + for (u64 j = 0; j < 128; ++j) + inOut[j][i] = sub[j]; + } + + } + + + + // load column w,w+1 (byte index) + // __________________ + // | | + // | | + // | | + // | | + // row 16*h, | #.# | + // ..., | ... | + // row 16*(h+1) | #.# | into u16OutView column wise + // | | + // | | + // ------------------ + // + // note: u16OutView is a 16x16 bit matrix = 16 rows of 2 bytes each. + // u16OutView[0] stores the first column of 16 bytes, + // u16OutView[1] stores the second column of 16 bytes. + void sse_loadSubSquare(block* in, array& out, u64 x, u64 y) + { + static_assert(sizeof(array, 2>) == sizeof(array), ""); + static_assert(sizeof(array, 128>) == sizeof(array), ""); + + array, 2>& outByteView = *(array, 2>*)&out; + array* inByteView = (array*)in; + + for (int l = 0; l < 16; l++) + { + outByteView[0][l] = inByteView[16 * x + l][2 * y]; + outByteView[1][l] = inByteView[16 * x + l][2 * y + 1]; + } + } + + + + // given a 16x16 sub square, place its transpose into u16OutView at + // rows 16*h, ..., 16 *(h+1) a byte columns w, w+1. + void sse_transposeSubSquare(block* out, array& in, u64 x, u64 y) + { + static_assert(sizeof(array, 128>) == sizeof(array), ""); + + array* outU16View = (array*)out; + + + for (int j = 0; j < 8; j++) + { + outU16View[16 * x + 7 - j][y] = in[0].movemask_epi8(); + outU16View[16 * x + 15 - j][y] = in[1].movemask_epi8(); + + in[0] = (in[0] << 1); + in[1] = (in[1] << 1); + } + } + + + void transpose(const MatrixView& in, const MatrixView& out) + { + MatrixView inn((u8*)in.data(), in.bounds()[0], in.stride() * sizeof(block)); + MatrixView outt((u8*)out.data(), out.bounds()[0], out.stride() * sizeof(block)); + + transpose(inn, outt); + } + + void transpose(const MatrixView& in, const MatrixView& out) + { + // the amount of work that we use to vectorize (hard code do not change) + static const u64 chunkSize = 8; + + // the number of input columns + int bitWidth = static_cast(in.bounds()[0]); + + // In the main loop, we tranpose things in subBlocks. This is how many we have. + // a subblock is 16 (bits) columns wide and 64 bits tall + int subBlockWidth = bitWidth / 16; + int subBlockHight = static_cast(out.bounds()[0]) / (8 * chunkSize); + + // since we allows arbitrary sized inputs, we have to deal with the left overs + int leftOverHeight = static_cast(out.bounds()[0]) % (chunkSize * 8); + int leftOverWidth = static_cast(in.bounds()[0]) % 16; + + + // make sure that the output can hold the input. + if (static_cast(out.stride()) < (bitWidth + 7) / 8) + throw std::runtime_error(LOCATION); + + // we can handle the case that the output should be truncated, but + // not the case that the input is too small. (simple call this function + // with a smaller out.bounds()[0], since thats "free" to do.) + if (out.bounds()[0] > in.stride() * 8) + throw std::runtime_error(LOCATION); + + union TempObj + { + //array blks; + block blks[chunkSize]; + //array < array, chunkSize> bytes; + u8 bytes[chunkSize][16]; + }; + + TempObj t; + + + // some useful constants that we will use + auto wStep = 16 * in.stride(); + auto eightOutSize1 = 8 * out.stride(); + auto outStart = out.data() + (7) * out.stride(); + auto step = in.stride(); + auto + step01 = step * 1, + step02 = step * 2, + step03 = step * 3, + step04 = step * 4, + step05 = step * 5, + step06 = step * 6, + step07 = step * 7, + step08 = step * 8, + step09 = step * 9, + step10 = step * 10, + step11 = step * 11, + step12 = step * 12, + step13 = step * 13, + step14 = step * 14, + step15 = step * 15; + + + // this is the main loop that gets the best performance (highly vectorized). + for (int h = 0; h < subBlockHight; ++h) + { + // we are concerned with the output rows a range [16 * h, 16 * h + 15] + + for (int w = 0; w < subBlockWidth; ++w) + { + // we are concerned with the w'th section of 16 bits for the 16 output rows above. + + auto start = in.data() + h * chunkSize + w * wStep; + + auto src00 = start; + auto src01 = start + step01; + auto src02 = start + step02; + auto src03 = start + step03; + auto src04 = start + step04; + auto src05 = start + step05; + auto src06 = start + step06; + auto src07 = start + step07; + auto src08 = start + step08; + auto src09 = start + step09; + auto src10 = start + step10; + auto src11 = start + step11; + auto src12 = start + step12; + auto src13 = start + step13; + auto src14 = start + step14; + auto src15 = start + step15; + + // perform the transpose on the byte level. We will then use + // sse instrucitions to get it on the bit level. t.bytes is the + // same as a but in a 2D byte view. + t.bytes[0][0] = src00[0]; t.bytes[1][0] = src00[1]; t.bytes[2][0] = src00[2]; t.bytes[3][0] = src00[3]; t.bytes[4][0] = src00[4]; t.bytes[5][0] = src00[5]; t.bytes[6][0] = src00[6]; t.bytes[7][0] = src00[7]; + t.bytes[0][1] = src01[0]; t.bytes[1][1] = src01[1]; t.bytes[2][1] = src01[2]; t.bytes[3][1] = src01[3]; t.bytes[4][1] = src01[4]; t.bytes[5][1] = src01[5]; t.bytes[6][1] = src01[6]; t.bytes[7][1] = src01[7]; + t.bytes[0][2] = src02[0]; t.bytes[1][2] = src02[1]; t.bytes[2][2] = src02[2]; t.bytes[3][2] = src02[3]; t.bytes[4][2] = src02[4]; t.bytes[5][2] = src02[5]; t.bytes[6][2] = src02[6]; t.bytes[7][2] = src02[7]; + t.bytes[0][3] = src03[0]; t.bytes[1][3] = src03[1]; t.bytes[2][3] = src03[2]; t.bytes[3][3] = src03[3]; t.bytes[4][3] = src03[4]; t.bytes[5][3] = src03[5]; t.bytes[6][3] = src03[6]; t.bytes[7][3] = src03[7]; + t.bytes[0][4] = src04[0]; t.bytes[1][4] = src04[1]; t.bytes[2][4] = src04[2]; t.bytes[3][4] = src04[3]; t.bytes[4][4] = src04[4]; t.bytes[5][4] = src04[5]; t.bytes[6][4] = src04[6]; t.bytes[7][4] = src04[7]; + t.bytes[0][5] = src05[0]; t.bytes[1][5] = src05[1]; t.bytes[2][5] = src05[2]; t.bytes[3][5] = src05[3]; t.bytes[4][5] = src05[4]; t.bytes[5][5] = src05[5]; t.bytes[6][5] = src05[6]; t.bytes[7][5] = src05[7]; + t.bytes[0][6] = src06[0]; t.bytes[1][6] = src06[1]; t.bytes[2][6] = src06[2]; t.bytes[3][6] = src06[3]; t.bytes[4][6] = src06[4]; t.bytes[5][6] = src06[5]; t.bytes[6][6] = src06[6]; t.bytes[7][6] = src06[7]; + t.bytes[0][7] = src07[0]; t.bytes[1][7] = src07[1]; t.bytes[2][7] = src07[2]; t.bytes[3][7] = src07[3]; t.bytes[4][7] = src07[4]; t.bytes[5][7] = src07[5]; t.bytes[6][7] = src07[6]; t.bytes[7][7] = src07[7]; + t.bytes[0][8] = src08[0]; t.bytes[1][8] = src08[1]; t.bytes[2][8] = src08[2]; t.bytes[3][8] = src08[3]; t.bytes[4][8] = src08[4]; t.bytes[5][8] = src08[5]; t.bytes[6][8] = src08[6]; t.bytes[7][8] = src08[7]; + t.bytes[0][9] = src09[0]; t.bytes[1][9] = src09[1]; t.bytes[2][9] = src09[2]; t.bytes[3][9] = src09[3]; t.bytes[4][9] = src09[4]; t.bytes[5][9] = src09[5]; t.bytes[6][9] = src09[6]; t.bytes[7][9] = src09[7]; + t.bytes[0][10] = src10[0]; t.bytes[1][10] = src10[1]; t.bytes[2][10] = src10[2]; t.bytes[3][10] = src10[3]; t.bytes[4][10] = src10[4]; t.bytes[5][10] = src10[5]; t.bytes[6][10] = src10[6]; t.bytes[7][10] = src10[7]; + t.bytes[0][11] = src11[0]; t.bytes[1][11] = src11[1]; t.bytes[2][11] = src11[2]; t.bytes[3][11] = src11[3]; t.bytes[4][11] = src11[4]; t.bytes[5][11] = src11[5]; t.bytes[6][11] = src11[6]; t.bytes[7][11] = src11[7]; + t.bytes[0][12] = src12[0]; t.bytes[1][12] = src12[1]; t.bytes[2][12] = src12[2]; t.bytes[3][12] = src12[3]; t.bytes[4][12] = src12[4]; t.bytes[5][12] = src12[5]; t.bytes[6][12] = src12[6]; t.bytes[7][12] = src12[7]; + t.bytes[0][13] = src13[0]; t.bytes[1][13] = src13[1]; t.bytes[2][13] = src13[2]; t.bytes[3][13] = src13[3]; t.bytes[4][13] = src13[4]; t.bytes[5][13] = src13[5]; t.bytes[6][13] = src13[6]; t.bytes[7][13] = src13[7]; + t.bytes[0][14] = src14[0]; t.bytes[1][14] = src14[1]; t.bytes[2][14] = src14[2]; t.bytes[3][14] = src14[3]; t.bytes[4][14] = src14[4]; t.bytes[5][14] = src14[5]; t.bytes[6][14] = src14[6]; t.bytes[7][14] = src14[7]; + t.bytes[0][15] = src15[0]; t.bytes[1][15] = src15[1]; t.bytes[2][15] = src15[2]; t.bytes[3][15] = src15[3]; t.bytes[4][15] = src15[4]; t.bytes[5][15] = src15[5]; t.bytes[6][15] = src15[6]; t.bytes[7][15] = src15[7]; + + // get pointers to the output. + auto out0 = outStart + (chunkSize * h + 0) * eightOutSize1 + w * 2; + auto out1 = outStart + (chunkSize * h + 1) * eightOutSize1 + w * 2; + auto out2 = outStart + (chunkSize * h + 2) * eightOutSize1 + w * 2; + auto out3 = outStart + (chunkSize * h + 3) * eightOutSize1 + w * 2; + auto out4 = outStart + (chunkSize * h + 4) * eightOutSize1 + w * 2; + auto out5 = outStart + (chunkSize * h + 5) * eightOutSize1 + w * 2; + auto out6 = outStart + (chunkSize * h + 6) * eightOutSize1 + w * 2; + auto out7 = outStart + (chunkSize * h + 7) * eightOutSize1 + w * 2; + + for (int j = 0; j < 8; j++) + { + // use the special movemask_epi8 to perform the final step of that bit-wise tranpose. + // this instruction takes ever 8'th bit (start at idx 7) and moves them into a single + // 16 bit output. Its like shaving off the top bit of each of the 16 bytes. + *(u16*)out0 = t.blks[0].movemask_epi8(); + *(u16*)out1 = t.blks[1].movemask_epi8(); + *(u16*)out2 = t.blks[2].movemask_epi8(); + *(u16*)out3 = t.blks[3].movemask_epi8(); + *(u16*)out4 = t.blks[4].movemask_epi8(); + *(u16*)out5 = t.blks[5].movemask_epi8(); + *(u16*)out6 = t.blks[6].movemask_epi8(); + *(u16*)out7 = t.blks[7].movemask_epi8(); + + // step each of out 8 pointer over to the next output row. + out0 -= out.stride(); + out1 -= out.stride(); + out2 -= out.stride(); + out3 -= out.stride(); + out4 -= out.stride(); + out5 -= out.stride(); + out6 -= out.stride(); + out7 -= out.stride(); + + // shift the 128 values so that the top bit is now the next one. + t.blks[0] = (t.blks[0] << 1); + t.blks[1] = (t.blks[1] << 1); + t.blks[2] = (t.blks[2] << 1); + t.blks[3] = (t.blks[3] << 1); + t.blks[4] = (t.blks[4] << 1); + t.blks[5] = (t.blks[5] << 1); + t.blks[6] = (t.blks[6] << 1); + t.blks[7] = (t.blks[7] << 1); + } + } + } + + // this is a special case there we dont have chunkSize bytes of input column left. + // because of this, the vectorized code above does not work and we instead so thing + // one byte as a time. + + // hhEnd denotes how many bytes are left [0,8). + auto hhEnd = (leftOverHeight + 7) / 8; + + // the last byte might be only part of a byte, so we also account for this + auto lastSkip = (8 - leftOverHeight % 8) % 8; + + for (int hh = 0; hh < hhEnd; ++hh) + { + // compute those parameters that determine if this is the last byte + // and that its a partial byte meaning that the last so mant output + // rows should not be written to. + auto skip = hh == (hhEnd - 1) ? lastSkip : 0; + auto rem = 8 - skip; + + for (int w = 0; w < subBlockWidth; ++w) + { + + auto start = in.data() + subBlockHight * chunkSize + hh + w * wStep; + + t.bytes[0][0] = *(start); + t.bytes[0][1] = *(start + step01); + t.bytes[0][2] = *(start + step02); + t.bytes[0][3] = *(start + step03); + t.bytes[0][4] = *(start + step04); + t.bytes[0][5] = *(start + step05); + t.bytes[0][6] = *(start + step06); + t.bytes[0][7] = *(start + step07); + t.bytes[0][8] = *(start + step08); + t.bytes[0][9] = *(start + step09); + t.bytes[0][10] = *(start + step10); + t.bytes[0][11] = *(start + step11); + t.bytes[0][12] = *(start + step12); + t.bytes[0][13] = *(start + step13); + t.bytes[0][14] = *(start + step14); + t.bytes[0][15] = *(start + step15); + + + auto out0 = outStart + (chunkSize * subBlockHight + hh) * 8 * out.stride() + w * 2; + + out0 -= out.stride() * skip; + t.blks[0] = (t.blks[0] << int( skip)); + + for (int j = 0; j < rem; j++) + { + *(u16*)out0 = t.blks[0].movemask_epi8(); + + out0 -= out.stride(); + + t.blks[0] = (t.blks[0] << 1); + } + } + } + + // this is a special case where the input column count was not a multiple of 16. + // For this case, we use + if (leftOverWidth) + { + for (int h = 0; h < subBlockHight; ++h) + { + // we are concerned with the output rows a range [16 * h, 16 * h + 15] + + auto start = in.data() + h * chunkSize + subBlockWidth * wStep; + + std::array src { + start, start + step01, start + step02, start + step03, start + step04, start + step05, + start + step06, start + step07, start + step08, start + step09, start + step10, + start + step11, start + step12, start + step13, start + step14, start + step15 + }; + + memset(t.blks, 0,sizeof(t)); + for (int i = 0; i < leftOverWidth; ++i) + { + t.bytes[0][i] = src[i][0]; + t.bytes[1][i] = src[i][1]; + t.bytes[2][i] = src[i][2]; + t.bytes[3][i] = src[i][3]; + t.bytes[4][i] = src[i][4]; + t.bytes[5][i] = src[i][5]; + t.bytes[6][i] = src[i][6]; + t.bytes[7][i] = src[i][7]; + } + + auto out0 = outStart + (chunkSize * h + 0) * eightOutSize1 + subBlockWidth * 2; + auto out1 = outStart + (chunkSize * h + 1) * eightOutSize1 + subBlockWidth * 2; + auto out2 = outStart + (chunkSize * h + 2) * eightOutSize1 + subBlockWidth * 2; + auto out3 = outStart + (chunkSize * h + 3) * eightOutSize1 + subBlockWidth * 2; + auto out4 = outStart + (chunkSize * h + 4) * eightOutSize1 + subBlockWidth * 2; + auto out5 = outStart + (chunkSize * h + 5) * eightOutSize1 + subBlockWidth * 2; + auto out6 = outStart + (chunkSize * h + 6) * eightOutSize1 + subBlockWidth * 2; + auto out7 = outStart + (chunkSize * h + 7) * eightOutSize1 + subBlockWidth * 2; + + if (leftOverWidth <= 8) + { + for (int j = 0; j < 8; j++) + { + *out0 = t.blks[0].movemask_epi8(); + *out1 = t.blks[1].movemask_epi8(); + *out2 = t.blks[2].movemask_epi8(); + *out3 = t.blks[3].movemask_epi8(); + *out4 = t.blks[4].movemask_epi8(); + *out5 = t.blks[5].movemask_epi8(); + *out6 = t.blks[6].movemask_epi8(); + *out7 = t.blks[7].movemask_epi8(); + + out0 -= out.stride(); + out1 -= out.stride(); + out2 -= out.stride(); + out3 -= out.stride(); + out4 -= out.stride(); + out5 -= out.stride(); + out6 -= out.stride(); + out7 -= out.stride(); + + t.blks[0] = (t.blks[0] << 1); + t.blks[1] = (t.blks[1] << 1); + t.blks[2] = (t.blks[2] << 1); + t.blks[3] = (t.blks[3] << 1); + t.blks[4] = (t.blks[4] << 1); + t.blks[5] = (t.blks[5] << 1); + t.blks[6] = (t.blks[6] << 1); + t.blks[7] = (t.blks[7] << 1); + } + } + else + { + for (int j = 0; j < 8; j++) + { + *(u16*)out0 = t.blks[0].movemask_epi8(); + *(u16*)out1 = t.blks[1].movemask_epi8(); + *(u16*)out2 = t.blks[2].movemask_epi8(); + *(u16*)out3 = t.blks[3].movemask_epi8(); + *(u16*)out4 = t.blks[4].movemask_epi8(); + *(u16*)out5 = t.blks[5].movemask_epi8(); + *(u16*)out6 = t.blks[6].movemask_epi8(); + *(u16*)out7 = t.blks[7].movemask_epi8(); + + out0 -= out.stride(); + out1 -= out.stride(); + out2 -= out.stride(); + out3 -= out.stride(); + out4 -= out.stride(); + out5 -= out.stride(); + out6 -= out.stride(); + out7 -= out.stride(); + + t.blks[0] = (t.blks[0] << 1); + t.blks[1] = (t.blks[1] << 1); + t.blks[2] = (t.blks[2] << 1); + t.blks[3] = (t.blks[3] << 1); + t.blks[4] = (t.blks[4] << 1); + t.blks[5] = (t.blks[5] << 1); + t.blks[6] = (t.blks[6] << 1); + t.blks[7] = (t.blks[7] << 1); + } + } + } + + //auto hhEnd = (leftOverHeight + 7) / 8; + //auto lastSkip = (8 - leftOverHeight % 8) % 8; + for (int hh = 0; hh < hhEnd; ++hh) + { + auto skip = hh == (hhEnd - 1) ? lastSkip : 0; + auto rem = 8 - skip; + + // we are concerned with the output rows a range [16 * h, 16 * h + 15] + auto w = subBlockWidth; + + auto start = in.data() + subBlockHight * chunkSize + hh + w * wStep; + + std::array src{ + start, start + step01, start + step02, start + step03, start + step04, start + step05, + start + step06, start + step07, start + step08, start + step09, start + step10, + start + step11, start + step12, start + step13, start + step14, start + step15 + }; + + + t.blks[0] = ZeroBlock; + for (int i = 0; i < leftOverWidth; ++i) + { + t.bytes[0][i] = src[i][0]; + } + + auto out0 = outStart + (chunkSize * subBlockHight + hh) * 8 * out.stride() + w * 2; + + out0 -= out.stride() * skip; + t.blks[0] = (t.blks[0] << int( skip)); + + for (int j = 0; j < rem; j++) + { + if (leftOverWidth > 8) + { + *(u16*)out0 = t.blks[0].movemask_epi8(); + } + else + { + *out0 = t.blks[0].movemask_epi8(); + } + + out0 -= out.stride(); + + t.blks[0] = (t.blks[0] << 1); + } + } + } + } + + + + + void sse_transpose128(block* inOut) + { + array a, b; + + for (int j = 0; j < 8; j++) + { + sse_loadSubSquare(inOut, a, j, j); + sse_transposeSubSquare(inOut, a, j, j); + + for (int k = 0; k < j; k++) + { + sse_loadSubSquare(inOut, a, k, j); + sse_loadSubSquare(inOut, b, j, k); + sse_transposeSubSquare(inOut, a, j, k); + sse_transposeSubSquare(inOut, b, k, j); + } + } + } + + + + + inline void sse_loadSubSquarex(array, 128>& in, array& out, u64 x, u64 y, u64 i) + { + typedef array, 2> OUT_t; + typedef array, 128> IN_t; + + static_assert(sizeof(OUT_t) == sizeof(array), ""); + static_assert(sizeof(IN_t) == sizeof(array, 128>), ""); + + OUT_t& outByteView = *(OUT_t*)&out; + IN_t& inByteView = *(IN_t*)∈ + + auto x16 = (x * 16); + + auto i16y2 = (i * 16) + 2 * y; + auto i16y21 = (i * 16) + 2 * y + 1; + + + outByteView[0][0] = inByteView[x16 + 0][i16y2]; + outByteView[1][0] = inByteView[x16 + 0][i16y21]; + outByteView[0][1] = inByteView[x16 + 1][i16y2]; + outByteView[1][1] = inByteView[x16 + 1][i16y21]; + outByteView[0][2] = inByteView[x16 + 2][i16y2]; + outByteView[1][2] = inByteView[x16 + 2][i16y21]; + outByteView[0][3] = inByteView[x16 + 3][i16y2]; + outByteView[1][3] = inByteView[x16 + 3][i16y21]; + outByteView[0][4] = inByteView[x16 + 4][i16y2]; + outByteView[1][4] = inByteView[x16 + 4][i16y21]; + outByteView[0][5] = inByteView[x16 + 5][i16y2]; + outByteView[1][5] = inByteView[x16 + 5][i16y21]; + outByteView[0][6] = inByteView[x16 + 6][i16y2]; + outByteView[1][6] = inByteView[x16 + 6][i16y21]; + outByteView[0][7] = inByteView[x16 + 7][i16y2]; + outByteView[1][7] = inByteView[x16 + 7][i16y21]; + outByteView[0][8] = inByteView[x16 + 8][i16y2]; + outByteView[1][8] = inByteView[x16 + 8][i16y21]; + outByteView[0][9] = inByteView[x16 + 9][i16y2]; + outByteView[1][9] = inByteView[x16 + 9][i16y21]; + outByteView[0][10] = inByteView[x16 + 10][i16y2]; + outByteView[1][10] = inByteView[x16 + 10][i16y21]; + outByteView[0][11] = inByteView[x16 + 11][i16y2]; + outByteView[1][11] = inByteView[x16 + 11][i16y21]; + outByteView[0][12] = inByteView[x16 + 12][i16y2]; + outByteView[1][12] = inByteView[x16 + 12][i16y21]; + outByteView[0][13] = inByteView[x16 + 13][i16y2]; + outByteView[1][13] = inByteView[x16 + 13][i16y21]; + outByteView[0][14] = inByteView[x16 + 14][i16y2]; + outByteView[1][14] = inByteView[x16 + 14][i16y21]; + outByteView[0][15] = inByteView[x16 + 15][i16y2]; + outByteView[1][15] = inByteView[x16 + 15][i16y21]; + + } + + + + inline void sse_transposeSubSquarex(array, 128>& out, array& in, u64 x, u64 y, u64 i) + { + static_assert(sizeof(array, 128>) == sizeof(array, 128>), ""); + + array, 128>& outU16View = *(array, 128>*)&out; + + auto i8y = i * 8 + y; + auto x16_7 = x * 16 + 7; + auto x16_15 = x * 16 + 15; + + block b0 = (in[0] << 0); + block b1 = (in[0] << 1); + block b2 = (in[0] << 2); + block b3 = (in[0] << 3); + block b4 = (in[0] << 4); + block b5 = (in[0] << 5); + block b6 = (in[0] << 6); + block b7 = (in[0] << 7); + + outU16View[x16_7 - 0][i8y] = b0.movemask_epi8(); + outU16View[x16_7 - 1][i8y] = b1.movemask_epi8(); + outU16View[x16_7 - 2][i8y] = b2.movemask_epi8(); + outU16View[x16_7 - 3][i8y] = b3.movemask_epi8(); + outU16View[x16_7 - 4][i8y] = b4.movemask_epi8(); + outU16View[x16_7 - 5][i8y] = b5.movemask_epi8(); + outU16View[x16_7 - 6][i8y] = b6.movemask_epi8(); + outU16View[x16_7 - 7][i8y] = b7.movemask_epi8(); + + b0 = (in[1] << 0); + b1 = (in[1] << 1); + b2 = (in[1] << 2); + b3 = (in[1] << 3); + b4 = (in[1] << 4); + b5 = (in[1] << 5); + b6 = (in[1] << 6); + b7 = (in[1] << 7); + + outU16View[x16_15 - 0][i8y] = b0.movemask_epi8(); + outU16View[x16_15 - 1][i8y] = b1.movemask_epi8(); + outU16View[x16_15 - 2][i8y] = b2.movemask_epi8(); + outU16View[x16_15 - 3][i8y] = b3.movemask_epi8(); + outU16View[x16_15 - 4][i8y] = b4.movemask_epi8(); + outU16View[x16_15 - 5][i8y] = b5.movemask_epi8(); + outU16View[x16_15 - 6][i8y] = b6.movemask_epi8(); + outU16View[x16_15 - 7][i8y] = b7.movemask_epi8(); + + } + + + // we have long rows of contiguous data data, 128 columns + void sse_transpose128x1024(array, 128>& inOut) + { + array a, b; + + for (int i = 0; i < 8; ++i) + { + for (int j = 0; j < 8; j++) + { + sse_loadSubSquarex(inOut, a, j, j, i); + sse_transposeSubSquarex(inOut, a, j, j, i); + + for (int k = 0; k < j; k++) + { + sse_loadSubSquarex(inOut, a, k, j, i); + sse_loadSubSquarex(inOut, b, j, k, i); + sse_transposeSubSquarex(inOut, a, j, k, i); + sse_transposeSubSquarex(inOut, b, k, j, i); + } + } + + } + + + } + +#ifdef OC_ENABLE_AVX2 + // Templates are used for loop unrolling. + + // Base case for the following function. + template + static OC_FORCEINLINE typename std::enable_if::type + avx_transpose_block_iter1(__m256i* inOut) {} + + // Transpose the order of the 2^blockSizeShift by 2^blockSizeShift blocks (but not within each + // block) within each 2^(blockSizeShift+1) by 2^(blockSizeShift+1) matrix in a nRows by 2^7 + // matrix. Only handles the first two rows out of every 2^blockRowsShift rows in each block, + // starting j * 2^blockRowsShift rows into the block. When blockRowsShift == 1 this does the + // transposes within the 2 by 2 blocks as well. + template + static OC_FORCEINLINE typename std::enable_if< + (j < (1 << blockSizeShift)) && (blockSizeShift > 0) && (blockSizeShift < 6) && + (blockRowsShift >= 1) + >::type avx_transpose_block_iter1(__m256i* inOut) + { + avx_transpose_block_iter1(inOut); + + // Mask consisting of alternating 2^blockSizeShift 0s and 2^blockSizeShift 1s. Least + // significant bit is 0. + u64 mask = ((u64) -1) << 32; + for (int k = 4; k >= (int) blockSizeShift; --k) + mask = mask ^ (mask >> (1 << k)); + + __m256i& x = inOut[j / 2]; + __m256i& y = inOut[j / 2 + (1 << (blockSizeShift - 1))]; + + // Handle the 2x2 blocks as well. Each block is within a single 256-bit vector, so it works + // differently from the other cases. + if (blockSizeShift == 1) + { + // transpose 256 bit blocks so that two can be done in parallel. + __m256i u = _mm256_permute2x128_si256(x, y, 0x20); + __m256i v = _mm256_permute2x128_si256(x, y, 0x31); + + __m256i diff = _mm256_xor_si256(u, _mm256_slli_epi16(v, 1)); + diff = _mm256_and_si256(diff, _mm256_set1_epi16(0xaaaa)); + u = _mm256_xor_si256(u, diff); + v = _mm256_xor_si256(v, _mm256_srli_epi16(diff, 1)); + + // Transpose again to switch back. + x = _mm256_permute2x128_si256(u, v, 0x20); + y = _mm256_permute2x128_si256(u, v, 0x31); + } + + __m256i diff = _mm256_xor_si256(x, _mm256_slli_epi64(y, (u64) 1 << blockSizeShift)); + diff = _mm256_and_si256(diff, _mm256_set1_epi64x(mask)); + x = _mm256_xor_si256(x, diff); + y = _mm256_xor_si256(y, _mm256_srli_epi64(diff, (u64) 1 << blockSizeShift)); + } + + // Special case to use the unpack* instructions. + template + static OC_FORCEINLINE typename std::enable_if< + (j < (1 << blockSizeShift)) && (blockSizeShift == 6) + >::type avx_transpose_block_iter1(__m256i* inOut) + { + avx_transpose_block_iter1(inOut); + + __m256i& x = inOut[j / 2]; + __m256i& y = inOut[j / 2 + (1 << (blockSizeShift - 1))]; + __m256i outX = _mm256_unpacklo_epi64(x, y); + __m256i outY = _mm256_unpackhi_epi64(x, y); + x = outX; + y = outY; + } + + // Base case for the following function. + template + static OC_FORCEINLINE typename std::enable_if::type + avx_transpose_block_iter2(__m256i* inOut) {} + + // Transpose the order of the 2^blockSizeShift by 2^blockSizeShift blocks (but not within each + // block) within each 2^(blockSizeShift+1) by 2^(blockSizeShift+1) matrix in a nRows by 2^7 + // matrix. Only handles the first two rows out of every 2^blockRowsShift rows in each block. + // When blockRowsShift == 1 this does the transposes within the 2 by 2 blocks as well. + template + static OC_FORCEINLINE typename std::enable_if<(nRows > 0)>::type + avx_transpose_block_iter2(__m256i* inOut) + { + constexpr size_t matSize = 1 << (blockSizeShift + 1); + static_assert(nRows % matSize == 0, "Can't transpose a fractional number of matrices"); + + constexpr size_t i = nRows - matSize; + avx_transpose_block_iter2(inOut); + avx_transpose_block_iter1(inOut + i / 2); + } + + // Base case for the following function. + template + static OC_FORCEINLINE typename std::enable_if::type + avx_transpose_block(__m256i* inOut) {} + + // Transpose the order of the 2^blockSizeShift by 2^blockSizeShift blocks (but not within each + // block) within each 2^matSizeShift by 2^matSizeShift matrix in a 2^(matSizeShift + + // matRowsShift) by 2^7 matrix. Only handles the first two rows out of every 2^blockRowsShift + // rows in each block. When blockRowsShift == 1 this does the transposes within the 2 by 2 + // blocks as well. + template + static OC_FORCEINLINE typename std::enable_if<(blockSizeShift < matSizeShift)>::type + avx_transpose_block(__m256i* inOut) + { + avx_transpose_block_iter2< + blockSizeShift, blockRowsShift, (1 << (matRowsShift + matSizeShift))>(inOut); + avx_transpose_block(inOut); + } + + static constexpr size_t avxBlockShift = 4; + static constexpr size_t avxBlockSize = 1 << avxBlockShift; + + // Base case for the following function. + template + static OC_FORCEINLINE typename std::enable_if::type + avx_transpose(__m256i* inOut) + { + for (size_t i = 0; i < 64; i += avxBlockSize) + avx_transpose_block<1, iter, 1, avxBlockShift + 1 - iter>(inOut + i); + } + + // Algorithm roughly from "Extension of Eklundh's matrix transposition algorithm and its + // application in digital image processing". Transpose each block of size 2^iter by 2^iter + // inside a 2^7 by 2^7 matrix. + template + static OC_FORCEINLINE typename std::enable_if<(iter > avxBlockShift + 1)>::type + avx_transpose(__m256i* inOut) + { + assert((u64)inOut % 32 == 0); + avx_transpose(inOut); + + constexpr size_t blockSizeShift = iter - avxBlockShift; + size_t mask = (1 << (iter - 1)) - (1 << (blockSizeShift - 1)); + if (iter == 7) + // Simpler (but equivalent) iteration for when iter == 7, which means that it doesn't + // need to count on both sides of the range of bits specified in mask. + for (size_t i = 0; i < (1 << (blockSizeShift - 1)); ++i) + avx_transpose_block(inOut + i); + else + // Iteration trick adapted from "Hacker's Delight". + for (size_t i = 0; i < 64; i = (i + mask + 1) & ~mask) + avx_transpose_block(inOut + i); + } + + void avx_transpose128(block* inOut) + { + avx_transpose((__m256i*) inOut); + } + + // input is 128 rows off 8 blocks each. + void avx_transpose128x1024(block* inOut) + { + assert((u64)inOut % 32 == 0); + AlignedArray buff; + for (u64 i = 0; i < 8; ++i) + { + + + //AlignedArray sub; + auto sub = &buff[128 * i]; + for (u64 j = 0; j < 128; ++j) + { + sub[j] = inOut[j * 8 + i]; + } + + //for (u64 j = 0; j < 128; ++j) + //{ + // buff[128 * i + j] = inOut[i + j * 8]; + //} + + avx_transpose128(&buff[128 * i]); + } + + for (u64 i = 0; i < 8; ++i) + { + //AlignedArray sub; + auto sub = &buff[128 * i]; + for (u64 j = 0; j < 128; ++j) + { + inOut[j * 8 + i] = sub[j]; + } + } + + } +#endif +} + + + diff --git a/tools/de-identification/visa-pets-FL/ot_library/drop-ot/Tools.h b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/Tools.h new file mode 100644 index 0000000..4e67823 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/Tools.h @@ -0,0 +1,47 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#pragma once +#include +#include +#include +namespace osuCrypto { + + + + + void eklundh_transpose128(block* inOut); + inline void eklundh_transpose128(std::array& inOut) { eklundh_transpose128(inOut.data()); } + +#ifdef OC_ENABLE_AVX2 + void avx_transpose128(block* inOut); +#endif +#ifdef OC_ENABLE_SSE2 + void sse_transpose128(block* inOut); + inline void sse_transpose128(std::array& inOut) { sse_transpose128(inOut.data()); }; +#endif + void transpose(const MatrixView& in, const MatrixView& out); + void transpose(const MatrixView& in, const MatrixView& out); + + + // Input must be given the alignment of an AlignedBlockArray, i.e. 32 bytes with AVX or 16 bytes + // without. + inline void transpose128(block* inOut) + { +#if defined(OC_ENABLE_AVX2) + assert((u64)inOut % 32 == 0); + avx_transpose128(inOut); +#elif defined(OC_ENABLE_SSE2) + assert((u64)inOut % 16 == 0); + sse_transpose128(inOut); +#else + eklundh_transpose128(inOut); +#endif + } + + inline void transpose128(std::array& inOut) { transpose128(inOut.data()); }; +} diff --git a/tools/de-identification/visa-pets-FL/ot_library/drop-ot/UnitTests.cpp b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/UnitTests.cpp new file mode 100644 index 0000000..4eb60d0 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/UnitTests.cpp @@ -0,0 +1,11 @@ +#include "UnitTests.h" +#include "IknpOtExt.h" +#include "MasnyRindal.h" + +namespace dropOt +{ + oc::TestCollection unitTests([](oc::TestCollection& tests) { + tests.add("Bot_MasnyRindal_Buff_test ", tests::Bot_MasnyRindal_Buff_test); + tests.add("OtExt_Iknp_Buff_test ", tests::OtExt_Iknp_Buff_test); + }); +} \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/ot_library/drop-ot/UnitTests.h b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/UnitTests.h new file mode 100644 index 0000000..ba3aa84 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/UnitTests.h @@ -0,0 +1,15 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#pragma once + +#include "cryptoTools/Common/TestCollection.h" + +namespace dropOt +{ + extern oc::TestCollection unitTests; +} \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/ot_library/drop-ot/config.h.in b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/config.h.in new file mode 100644 index 0000000..92b1c8f --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/drop-ot/config.h.in @@ -0,0 +1,18 @@ +#pragma once + +#include "cryptoTools/Common/config.h" + +#ifdef ENABLE_BOOST +#define OC_ENABLE_BOOST +#endif + + +// enable integration with boost for networking. +#cmakedefine ENABLE_BOOST @ENABLE_BOOST@ +#cmakedefine DROP_OT_ENABLE_RELIC @DROP_OT_ENABLE_RELIC@ +#cmakedefine DROP_OT_ENABLE_SODIUM @DROP_OT_ENABLE_SODIUM@ + + +#if defined(ENABLE_BOOST) != defined(OC_ENABLE_BOOST) +static_assert(0, "cryptoTools does not define ENABLE_BOOST the same as hydra"); +#endif \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/ot_library/frontend/CMakeLists.txt b/tools/de-identification/visa-pets-FL/ot_library/frontend/CMakeLists.txt new file mode 100644 index 0000000..a181ba3 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/frontend/CMakeLists.txt @@ -0,0 +1,15 @@ + +project(frontend) + + +############################################# +# Build frontend # +############################################# + +file(GLOB_RECURSE SRC_FRONTEND ${CMAKE_SOURCE_DIR}/frontend/*.cpp) +include_directories(${CMAKE_SOURCE_DIR}/frontend/) + +add_executable(frontend ${SRC_FRONTEND}) + +target_link_libraries(frontend diskhash) +target_link_libraries(frontend dropOt) diff --git a/tools/de-identification/visa-pets-FL/ot_library/frontend/main.cpp b/tools/de-identification/visa-pets-FL/ot_library/frontend/main.cpp new file mode 100644 index 0000000..e9027f7 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/frontend/main.cpp @@ -0,0 +1,243 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#include "drop-ot/UnitTests.h" +#include "cryptoTools/Network/IOService.h" +#include "cryptoTools/Network/Session.h" +#include "cryptoTools/Common/Timer.h" +#include "cryptoTools/Common/CLP.h" +#include "cryptoTools/Common/Log.h" +#include "cryptoTools/Common/Matrix.h" +#include +#include "drop-ot/IknpOtExt.h" +#include "drop-ot/MasnyRindal.h" +#include + +using namespace dropOt; + +// void tutorial(oc::CLP& cmd); +void networked(oc::CLP& cmd); +// void help(oc::CLP& cmd); + + + +struct Ot{ + + + +// The first function to be called. This generates a message to be sent to the +// other party. Intermediate state is written to stateDir directory. +std::vector sender_Setup1(std::string stateDir, std::string bankID) +{ + MasnyRindal mr; + PRNG prng(oc::sysRandomSeed()); + + std::vector ioBuffer; + + // we use random choices here. + oc::BitVector choices(128); + choices.randomize(prng); + + // run the first part of the protocol. + mr.receiveRoundOne(choices, prng, ioBuffer); + + // serialize the state. + std::ofstream out; + out.open(stateDir + "/senderState" + bankID +".bin", std::ios::trunc | std::ios::binary | std::ios::out); + + // char* var = "adsad"; + // out.open(var + "/senderState.bin", std::ios::trunc | std::ios::binary | std::ios::out); + mr.serialize(out); + + + // return the message to be sent. + return ioBuffer; +} + + +// The first function to be called by the sender. It take as input the stateDir +// and the message generated by recver_Setup(...). After this the sender is +// setup and ready to generate OT keys. Intermediate state is written to stateDir directory. +void sender_Setup2( + std::string stateDir, + std::string bankID, + span ioBuffer) +{ + MasnyRindal mr; + PRNG prng(oc::sysRandomSeed()); + + std::ifstream in; + in.open(stateDir + "/senderState" + bankID +".bin", std::ios::binary | std::ios::in); + mr.deserialize(in); + in.close(); + + auto choices = mr.mChoices; + std::remove((stateDir + "/senderState" + bankID +".bin").c_str()); + + std::vector keys(choices.size()); + mr.receiveRoundTwo(keys, prng, ioBuffer); + + IknpOtExtSender sender; + sender.setUniformBaseOts(keys, choices); + std::ofstream out; + out.open(stateDir + "/senderState" + bankID +".bin", std::ios::trunc | std::ios::binary | std::ios::out); + sender.serialize(out); +} + + + +// The setup function for the receiver. It take as input the stateDir +// and the message generated by sender_Setup1(...). After this the receiver is +// setup and ready to generate OT keys. Intermediate state is written to stateDir directory. +// It returns a message to be sent to the sender to complete their setup. +std::vector recver_Setup( + std::string stateDir, + std::string bankID, + span inMessage) +{ + MasnyRindal mr; + PRNG prng(oc::sysRandomSeed()); + + std::vector> keys(128); + + std::vector ioBuffer; + mr.sendRoundOne(128, prng, ioBuffer); + mr.sendRoundTwo(keys, prng, inMessage); + + IknpOtExtReceiver recver; + recver.setUniformBaseOts(keys); + + + std::ofstream out; + out.open(stateDir + "/recverState" + bankID +".bin", std::ios::trunc | std::ios::binary | std::ios::out); + recver.serialize(out); + + return ioBuffer; +} + + +// Perform the main protocol for the OT receiver. This generates useable OT keys. It takes as input stateDir, +// where the receiver state is serialized, and it takes as input choices which are the +// user provided choices values for the OTs. It writes the output OT keys to recverKeys. +// It returns a message to be sent to the sender. +std::vector recver_generateKeys(std::string stateDir, std::string bankID, std::vector& recverKeys, BitVector& choices) +{ + if (recverKeys.size() != choices.size()) + throw RTE_LOC; + + std::ifstream in; + in.open(stateDir + "/recverState" + bankID +".bin", std::ios::binary | std::ios::in); + + IknpOtExtReceiver recver; + recver.deserialize(in); + + + + PRNG prng(oc::sysRandomSeed()); + std::vector ioBuffer; + recver.receiveRoundOne_s(choices, recverKeys, prng, ioBuffer); + + std::ofstream out; + out.open(stateDir + "/recverState" + bankID +".bin", std::ios::trunc | std::ios::binary | std::ios::out); + recver.serialize(out); + + return ioBuffer; +} + + + +// Perform the main protocol for the OT sender. This generates useable OT keys. It takes as input stateDir, +// where the sender state is serialized. It writes the output OT keys to senderKeys. +void sender_generateKeys(std::string stateDir, std::string bankID, spaninMessages, std::vector>& senderKeys) +{ + std::ifstream in; + in.open(stateDir + "/senderState" + bankID +".bin", std::ios::binary | std::ios::in); + + IknpOtExtSender sender; + sender.deserialize(in); + + PRNG prng(oc::sysRandomSeed()); + sender.sendRoundOne_r(senderKeys, prng, inMessages); + + std::ofstream out; + out.open(stateDir + "/senderState" + bankID +".bin", std::ios::trunc | std::ios::binary | std::ios::out); + sender.serialize(out); +} + + + +}; + + +// int main(int argc, char** argv) +// { +// oc::CLP cmd(argc, argv); + +// if (cmd.isSet("u")) +// unitTests.runIf(cmd); +// else if (cmd.isSet("tut")) +// tutorial(cmd); +// else +// help(cmd); + +// return 0; +// } + +// void help(oc::CLP& cmd) +// { +// std::cout << "-u to run unit tests" << std::endl; +// std::cout << "-tut to run tutorial" << std::endl; + +// } + +// // The overall flow is shown here. +// void tutorial(oc::CLP& cmd) +// { +// // Ot ot; + +// // auto stateDir = cmd.getOr("stateDir", "."); +// // auto n = cmd.getOr("n", 100); +// // auto trials = cmd.getOr("t", 2); + + +// // ////////////////////////////// +// // // setup + +// // auto buffer = ot.sender_Setup1(stateDir); + +// // buffer = ot.recver_Setup(stateDir, span(buffer)); + +// // ot.sender_Setup2(stateDir, span(buffer)); + + +// // ////////////////////////////// +// // // main + +// // for (u64 t = 0; t < trials; ++t) +// // { + +// // // these will be input in the real code... +// // oc::BitVector choices(n); +// // for (u64 i = 0; i < choices.size(); ++i) +// // choices[i] = i % 2; + +// // // output keys +// // std::vector recverKeys(n); + +// // buffer = ot.recver_generateKeys(stateDir, recverKeys, choices); + +// // std::vector> senderKeys(n); +// // ot.sender_generateKeys(stateDir, span(buffer), senderKeys); + + +// // std::cout << "generated OT keys" << std::endl; +// // for (u64 i = 0; i < n; ++i) +// // { +// // std::cout << "sender {" << senderKeys[i][0] << ", " << senderKeys[i][1] << "}, recver{" << recverKeys[i] << ", " << choices[i] << "}" << std::endl; +// // } +// // } +// } \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/ot_library/readme.md b/tools/de-identification/visa-pets-FL/ot_library/readme.md new file mode 100644 index 0000000..eb211a2 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/readme.md @@ -0,0 +1,12 @@ + # drop-ot + ===== + +## Building + +For `` in `linux,osx,x64-Release,x64-Debug` + +``` +cmake -S . --preset +cmake --build out/build// +``` +Run the program, `./out/build//frontend/frontend` (or similar), to see the help info. \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/ot_library/wrapper/CMakeLists.txt b/tools/de-identification/visa-pets-FL/ot_library/wrapper/CMakeLists.txt new file mode 100644 index 0000000..39802b1 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/wrapper/CMakeLists.txt @@ -0,0 +1,8 @@ +file(GLOB_RECURSE SRCS *.cpp) +#file(GLOB_RECURSE SRCS *.c) + +include_directories(${CMAKE_SOURCE_DIR}/wrapper/) + +add_library(wrapper SHARED ${SRCS}) + +target_link_libraries(wrapper diskhash dropOt) diff --git a/tools/de-identification/visa-pets-FL/ot_library/wrapper/CWrapper.cpp b/tools/de-identification/visa-pets-FL/ot_library/wrapper/CWrapper.cpp new file mode 100644 index 0000000..2500425 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/ot_library/wrapper/CWrapper.cpp @@ -0,0 +1,385 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#include +#include "frontend/main.cpp" +#include +#include +#include "diskhash/diskhash.hpp" + + +extern "C" +{ + + using HTBlock = std::array; + + + void decrypt(char* hashTableFilePath, char* accountIds, int* accountIdLengths, int maxAccountIdLength, int totalNumberOfAccountIds, int* encryptedZeroGradList_, int* encryptedOneGradList_, int tensorsPerAccount, int* unencryptedGradList_, int *choicesArray) + { + dht::DiskHash ht(hashTableFilePath, maxAccountIdLength+1, dht::DHOpenRW); + + std::vector buffer; + + buffer.resize( ( (tensorsPerAccount * sizeof(oc::block) ) / sizeof(int32_t) )); + + + + auto unencryptedGradList = span(unencryptedGradList_, totalNumberOfAccountIds * tensorsPerAccount); + + auto encryptedZeroGradList = span(encryptedZeroGradList_, totalNumberOfAccountIds * (tensorsPerAccount+4)); + auto encryptedOneGradList = span(encryptedOneGradList_, totalNumberOfAccountIds * (tensorsPerAccount+4)); + + auto unencIter = unencryptedGradList.begin(); + + auto encZeroIter = encryptedZeroGradList.begin(); + auto encOneIter = encryptedOneGradList.begin(); + + std::string subbuff; + for (u64 i = 0; i < totalNumberOfAccountIds; ++i) + { + + // Getting the two keys + subbuff.clear(); + subbuff.append(accountIds, accountIdLengths[i]); + accountIds += accountIdLengths[i]; + + // std::cout << "The subbuff is " << subbuff << std::endl; + + auto ptr = ht.lookup(subbuff.c_str()); + if(ht.lookup(subbuff.c_str()) == nullptr) + { + std::cout << "missing key in hash table: " << subbuff << std::endl; + throw RTE_LOC; + } + + + block tempKey; + memcpy(&tempKey, ptr, sizeof(block)); + + // Decryption Starts Here + oc::AES aes(tempKey); + + block idx; + memcpy(&idx, encZeroIter, sizeof(block) ); + + encZeroIter = encZeroIter + 4; + encOneIter = encOneIter + 4; + + aes.ecbEncCounterMode(idx, buffer.size(), buffer.data()); + + int32_t* xorPointer = ( int32_t *) &buffer[0]; + + for(int j=0; j> ht(hashTableFilePath, maxAccountIdLength+1, dht::DHOpenRW); + + + std::vector bufferZeroGrad, bufferOneGrad; + + bufferZeroGrad.resize( ( (tensorsPerAccount * sizeof(oc::block) ) / sizeof(int32_t) )); + bufferOneGrad.resize( ( (tensorsPerAccount * sizeof(oc::block) ) / sizeof(int32_t) )); + + auto zeroGradList = span(zeroGradList_, totalNumberOfAccountIds * tensorsPerAccount); + auto oneGradList = span(oneGradList_, totalNumberOfAccountIds * tensorsPerAccount); + + auto encryptedZeroGradList = span(encryptedZeroGradList_, totalNumberOfAccountIds * (tensorsPerAccount+4)); + auto encryptedOneGradList = span(encryptedOneGradList_, totalNumberOfAccountIds * (tensorsPerAccount+4)); + + auto zeroIter = zeroGradList.begin(); + auto oneIter = oneGradList.begin(); + + auto encZeroIter = encryptedZeroGradList.begin(); + auto encOneIter = encryptedOneGradList.begin(); + + std::string subbuff; + + + for (u64 i = 0; i < totalNumberOfAccountIds; ++i) + { + + // Getting the two keys + subbuff.clear(); + subbuff.append(accountIds, accountIdLengths[i]); + accountIds += accountIdLengths[i]; + + std::array tempKeys; + + auto ptr = *ht.lookup(subbuff.c_str()); + if(ht.lookup(subbuff.c_str()) == nullptr) + { + std::cout << "missing key in hash table: " << subbuff << std::endl; + throw RTE_LOC; + } + + + tempKeys[0] = ptr[0]; + tempKeys[1] = ptr[1]; + + // Encryption starts here + oc::AES aesZeroGrad(tempKeys[0]); + oc::AES aesOneGrad(tempKeys[1]); + + PRNG prng(oc::sysRandomSeed()); + block idx = prng.get(); + + aesZeroGrad.ecbEncCounterMode(idx, bufferZeroGrad.size(), bufferZeroGrad.data()); + aesOneGrad.ecbEncCounterMode(idx, bufferOneGrad.size(), bufferOneGrad.data()); + + int32_t* xorZeroPointer = ( int32_t *) &bufferZeroGrad[0]; + int32_t* xorOnePointer = ( int32_t *) &bufferOneGrad[0]; + + // Pushing the counter mode index + *encZeroIter++ = idx.get(0); + *encOneIter++ = idx.get(0); + + *encZeroIter++ = idx.get(1); + *encOneIter++ = idx.get(1); + + *encZeroIter++ = idx.get(2); + *encOneIter++ = idx.get(2); + + *encZeroIter++ = idx.get(3); + *encOneIter++ = idx.get(3); + + + + for(int j=0; j " << x << " + " << v << " -> "; + // Encrypting Zero Gradient + *encZeroIter++ = *xorZeroPointer ^ *zeroIter++; + xorZeroPointer++; + + + // Encrypting One Gradient + *encOneIter++ = *xorOnePointer ^ *oneIter++; + xorOnePointer++; + + } + + } + } + + void deleteState( long long state) + { + auto pointer = (std::vector*) state; + delete pointer; + } + + unsigned char* senderSetup1(char* stateDirPointer, char* bankIDPointer, int* returnedPointerSize, long long* state ) + { + Ot ot; + + std::string stateDir(stateDirPointer); + std::string bankID(bankIDPointer); + + std::vector* returnvalue = new std::vector{ ot.sender_Setup1(stateDir,bankID ) } ; + // std::cout << "senderSetup1 called completed" << " BankID " << bankID << "\n"; + + *returnedPointerSize = returnvalue->size(); + + + oc::RandomOracle hash(16); + hash.Update(returnvalue->data(), returnvalue->size()); + oc::block h; + hash.Final(h); + + + // std::cout << "In Sender Setup 1 Byte Hash is " << " " << h << " BankID " << bankID << "\n"; + + *state = (long long)returnvalue; + + return returnvalue->data(); + + } + + + unsigned char* recverSetup(char* stateDirPointer, char* bankIDPointer, char* senderSetup1ReturnValue, int senderSetup1ReturnSize, int* returnedPointerSize, long long* state) + { + Ot ot; + + std::string stateDir(stateDirPointer); + std::string bankID(bankIDPointer); + + span input{(u8*)senderSetup1ReturnValue, size_t(senderSetup1ReturnSize) }; + + oc::RandomOracle hash(16); + hash.Update(senderSetup1ReturnValue, size_t(senderSetup1ReturnSize)); + oc::block h; + hash.Final(h); + + // std::cout << "In Receiver Setup 1 Byte Hash is " << " " << h << " BankID " << bankID << "\n"; + + std::vector* returnvalue = new std::vector{ ot.recver_Setup(stateDir, bankID, input ) } ; + // std::cout << "recverSetup called completed" << " BankID " << bankID << "\n"; + + + *returnedPointerSize = returnvalue->size(); + *state = (long long)returnvalue; + + + oc::RandomOracle hash1(16); + hash1.Update(returnvalue->data(), returnvalue->size()); + oc::block h1; + hash1.Final(h1); + + // std::cout << "In Receiver Setup 1 Byte Hash is " << " " << h1 << " BankID " << bankID << "\n"; + + return returnvalue->data(); + } + + void senderSetup2(char* stateDirPointer, char* bankIDPointer, u8* recverSetupReturnValue, int recverSetupReturnSize) + { + Ot ot; + std::string stateDir(stateDirPointer); + std::string bankID(bankIDPointer); + + // std::cout << "recverSetupReturnSize is " << recverSetupReturnSize << std::endl; + oc::RandomOracle hash(16); + hash.Update(recverSetupReturnValue, size_t(recverSetupReturnSize)); + oc::block h; + hash.Final(h); + + // std::cout << "In Sender Setup2 Byte Hash is " << " " << h << " BankID " << bankID << "\n"; + + span input{recverSetupReturnValue, size_t(recverSetupReturnSize) }; + + ot.sender_Setup2(stateDir, bankID, input ); + // std::cout << "senderSetup2 called completed" << " BankID " << bankID << "\n"; + + } + + + unsigned char* recverGenerateKeys(char* stateDirPointer, char* bankIDPointer, char* hashTableFilePath,int *choicesArray, int choiceslength ,int* returnedPointerSize, long long* state, char* accountIds, int* accountIdLengths, int maxAccountIdLength) + { + + Ot ot; + std::string stateDir(stateDirPointer); + std::string bankID(bankIDPointer); + + // std::cout << "In Receiver Key Gen for BankID = " << bankID << std::endl; + + // output keys + std::vector recverKeys(choiceslength); + + // Construct a zero initialized BitVector of size choiceslength + oc::BitVector choices(choiceslength); + + // std::cout << "Choices length is" << choiceslength << std::endl; + + for (u64 i=0; i < choiceslength; i++) + { + + if(choicesArray[i] == 1) + choices[i] = 1; + // std::cout << choices[i] << " " << std::endl; + } + + // std::cout << "Choices BitVector is initiazed" << std::endl; + + std::vector* returnvalue = new std::vector{ ot.recver_generateKeys(stateDir, bankID, recverKeys, choices) } ; + // std::cout << "Receiver Keys Generator completed" << std::endl; + + + *returnedPointerSize = returnvalue->size(); + *state = (long long)returnvalue; + + + oc::RandomOracle hash1(16); + hash1.Update(returnvalue->data(), returnvalue->size()); + oc::block h1; + hash1.Final(h1); + + dht::DiskHash ht(hashTableFilePath, maxAccountIdLength+1, dht::DHOpenRW); + + std::string subbuff; + // subbuff.reserve(maxAccountIdLength); + for (u64 i = 0; i < choiceslength; ++i) + { + subbuff.clear(); + subbuff.append(accountIds, accountIdLengths[i]); + accountIds += accountIdLengths[i]; + + // MyFile << "recver{" << recverKeys[i] << ", " << choices[i] << "} " << subbuff << std::endl; + + const bool isInserted = ht.insert(subbuff.c_str(), recverKeys[i].get()); + if(isInserted == 0) + { + std::cout << " subbuff is not inserted " << subbuff <data(); + } + + void senderGenerateKeys(char* stateDirPointer, char* bankIDPointer, char* hashTableFilePath, u8* recverGenKeyBytes, int recverGenKeySize, int choiceslength, char* accountIds, int* accountIdLengths, int maxAccountIdLength) + { + + Ot ot; + std::string stateDir(stateDirPointer); + std::string bankID(bankIDPointer); + + // std::cout << "recverGenKeySize is " << recverGenKeySize << " BankID " << bankID << "\n"; + oc::RandomOracle hash(16); + hash.Update(recverGenKeyBytes, size_t(recverGenKeySize)); + oc::block h; + hash.Final(h); + // std::cout << "In Sender Key Gen Byte Hash is " << " " << h << " BankID " << bankID << "\n"; + + + span input{recverGenKeyBytes, size_t(recverGenKeySize) }; + + + std::vector> senderKeys(choiceslength); + dht::DiskHash> ht(hashTableFilePath, maxAccountIdLength+1, dht::DHOpenRW); + + ot.sender_generateKeys(stateDir, bankID, input, senderKeys); + + std::string subbuff; + for (u64 i = 0; i < choiceslength; ++i) + { + subbuff.clear(); + subbuff.append(accountIds, accountIdLengths[i]); + accountIds += accountIdLengths[i]; + + std::array tempBlock { + senderKeys[i][0].get(), + senderKeys[i][1].get() + }; + + ht.insert(subbuff.c_str(),tempBlock); + + } + + } + +} \ No newline at end of file diff --git a/tools/de-identification/visa-pets-FL/solution.md b/tools/de-identification/visa-pets-FL/solution.md new file mode 100644 index 0000000..cfdad51 --- /dev/null +++ b/tools/de-identification/visa-pets-FL/solution.md @@ -0,0 +1,179 @@ +# Problem statement + +The setting considers payment network systems handling transactions. In this setting, there is a number of financial institutions (or Banks) denoted by $B_i$ which route their transactions through a central hub $S$. The transaction messages include the following fields [[1]](https://www.drivendata.org/competitions/98/nist-federated-learning-1/page/524/): + + +1. `MessageId` - Globally unique identifier within this dataset for individual transactions +2. `UETR` - The Unique End-to-end Transaction Reference—a 36-character string enabling traceability of all individual transactions associated with a single end-to-end transaction +3. `TransactionReference` - Unique identifier for an individual transaction +4. `Timestamp` - Time at which the individual transaction was initiated +5. `Sender` - Institution (bank) initiating/sending the individual transaction +6. `Receiver` - Institution (bank) receiving the individual transaction +7. `OrderingAccount` - Account identifier for the originating ordering entity (individual or organization) for end-to-end transaction, +8. `OrderingName` - Name for the originating ordering entity +9. `OrderingStreet` - Street address for the originating ordering entity +10. `OrderingCountryCityZip` - Remaining address details for the originating ordering entity +11. `BeneficiaryAccount` - Account identifier for the final beneficiary entity (individual or organization) for end-to-end transaction +12. `BeneficiaryName` - Name for the final beneficiary entity +13. `BeneficiaryStreet` - Street address for the final beneficiary entity +14. `BeneficiaryCountryCityZip` - Remaining address details for the final beneficiary entity +15. `SettlementDate` - Date the individual transaction was settled +16. `SettlementCurrency` - Currency used for transaction +17. `SettlementAmount` - Value of the transaction net of fees/transfer charges/forex +18. `InstructedCurrency` - Currency of the individual transaction as instructed to be paid by the Sender +19. `InstructedAmount` - Value of the individual transaction as instructed to be paid by the Sender + +As those transaction messages are communicated through $S$ in plaintext, $S$ is able to build a rich dataset that includes all of the above attributes. Naturally, for identifying anomalous transactions, $S$ would be the best choise to deploy machine learning techniques towards anomaly detection. Towards building a classification model, $S$ is provided with a training dataset $T$ which includes a number of transactions with the above fields, plus an additional `Label` Boolean attribute which indicates whether a transaction is anomalous or not. + +Banks participating in the network payment system each has a dataset with the following fields: + +1. `Bank` - Identifier for the bank +2. `Account` - Identifier for the account +3. `Name` - Name of the account +4. `Street` - Street address associated with the account +5. `CountryCityZip` - Remaining address details associated with the account +6. `Flag` - Enumerated data type indicating potential issues or special features that have been associated with an account, with values as follows: + `00` - No flag + `01` - Account closed + `03` - Account recently opened + `04` - Name mismatch + `05` - Account under monitoring + `06` - Account suspended + `07` - Account frozen + `08` - Non-transaction account + `09` - Beneficiary deceased + `10` - Invalid company ID + `11` - Invalid individual ID + +As $S$ already knows all of the information held by Banks except the `Flag` associated with an account, the goal is to augment the machine learning model, taking this attribute into account. However due to privacy regulations and laws (e.g. [GDPR](https://eur-lex.europa.eu/EN/legal-content/summary/general-data-protection-regulation-gdpr.html)), this additional attribute needs to be shared with $S$ in a privacy-preserving way, namely enabling $S$ to enhance its model without directly learning `Flag`. In general, the primary goal is to enable $S$ to decide if a transaction is anomalous or not with high accuracy, while protecting against private data leakage from the respective data holders, while $S$ should be the only entity which learns the model's prediction. + +# Solution overview + +## Key observations + +Based on the synthetic training datasets provided, we observed the following: + +1. For all transactions, the sender account `Flag` in the corresponding bank dataset is always `00`. In other words, a transaction cannot spend funds from an account with an "abnormal" state. Therefore, only the receiving account `Flag` is of interest. +2. If the `Flag` (receiving) account attribute has a value other than `00`, then a transaction sending to this account is always marked as anomalous in the training dataset. The contrapositive is that a transaction marked as non-anomalous, then the receiving account `Flag` is always `00`. + + +## Our approach + +Our tailored solution leverages the above two observations. To incorporate the extra `Flag` information into the model maintained by $S$, we train it with a secret input distinguishing between normal and abnormal accounts. +During training, $S$ selects a batch of transactions and computes updates to the model for both possible inputs for each transaction in the batch. For each transaction, $S$, the receiving bank, and a third party referred to as the *aggregator* perform an instance MPC. $S$ provides the two possible updates for the transaction, and the receiver bank provides a bit indicating whether the receiver account was normal or abnormal. +The MPC ensures that the parties *obliviously* select the correct update, based on the bit provided by the receiver bank. All such selected updates are then aggregated. Finally, the aggregator also adds enough noise to hide contributions of individual transactions to protect against inference attacks by $S$. The MPC ensures that this aggregated and noised update for the entire batch is revealed to $S$ alone. The magnitude of noise used is a hyperparameter that depends on the batch size, the clipping bound, and the desired level of privacy. So, neither the aggregator nor the banks can gain insight into the transaction details. + + +During inference, when $S$ receives a transaction and wants to classify it, $S$ runs that transaction through the model with both inputs to obtain two possible model outputs, or labels. $S$ and the receiving bank perform an instance of secure two-party computation. $S$ provides the two possible labels for the transaction, and the receiver bank provides a bit indicating whether the receiver account was normal or abnormal. The computation ensures that the parties *obliviously* select the correct label and reveal it to $S$ alone. + + +![](figure.png) + + +## Implementing our approach + +In our approach, $S$ trains a model with a secret input that differs for "normal" accounts and for "abnormal" accounts. $S$ initializes this model, but requires aid in training it. + +### Setup \& Key Management + +We assume there is an underlying PKI in place: all parties are aware of the public keys of the other parties and can use those to encrypt messages that can be decrypted only by said parties. $S$ uses the public keys of banks to encrypt account identifiers for them; the banks and $S$ use the public key of $S$ to encrypt other information. + +### Training + +$S$ initializes and manages the main training procedure which operates in batches. For each batch, $S$ randomly samples a batch $K$ of $k$ transactions ($k$ being a hyperparameter). For each transaction $s$, $S$ inputs $s$ through its model $M$ twice: once with the secret input set to normal and once to abnormal. Note that $S$ does not know if the receiving account flag is normal or abnormal, and thus compute two possible updates $u_0(s)$ and $u_1(s)$. + + + +### Inference Time + +During inference time, when $S$ receives a transaction $s$ and wishes to predict the label, $S$ runs it through its model twice, once guessing that the account is normal and once guessing that the account is abnormal. + + +### Algorithms & Protocols Utilized + +In principle, $S$ can use any model structure that can be trained by incremental and aggregatable updates, e.g. stochastic gradient descent. + + +Our approach uses a combination of oblivious transfer and secret sharing for protecting privacy. + + +## Discussion of Privacy + +As privacy in this setting implies protecting against private data leakage from the respective data holders, we discuss each case separately below. + +### Privacy Against Aggregator. + +The aggregator aids $S$ and the banks in training the federated model. In general we assume that either the aggregator or $S$ is honest, and for this section we assume that $S$ is honest. Privacy against the aggregator then reduces to the security of the underlying oblivious transfer protocol and the properties of secret sharing. + + + +To protect privacy, the aggregator samples and adds noise to aggregated updates. We assume that the aggregator will not observe the model or its outputs. + + +### Privacy Against Bank(s). + +During training, a Bank only learns the account queried and the secret share $s_1$, which leaks no information. + +### Privacy Against $S$. + +During training, $S$ receives a model update from the aggregator. Having computed the individual updates (two per transaction), $S$ could potentially attempt to find the update selected for each transaction to infer associated account flag. However, our approach applies noise of sufficient magnitude to the aggregator's update for each batch to prevent $S$ from inferring flags. + +Our goal is to hide flag-information at account level and not merely pertaining to individual transactions from $S$. Hence, we consider the expected number of transactions from the same account in a batch. This aspect is exaggerated by up-scaling abnormal transactions in the dataset. + +During inference, $S$ learns the final classification of a transaction, which does not provide information about the Bank's flag other than the inherent privacy leakage. + +## Empirical Evaluation of Privacy + +We empirically evaluated the privacy-accuracy trade-off against the membership inference attack during training time. + +### Threat Model + +$S$ attempts to learn accounts' bit flags from the Banks. In our learning protocol, $S$ already knows the model architecture, the training parameters and all features of a transaction except the receiver bank account flag. In addition, we also assume $S$ already knows the status flags of a fraction (denoted by $\alpha$) of the bank accounts. With this prior knowledge, $S$ can then build an attack model that predicts the receiving account bit flag based on the features and the fincrime detection model's prediction of a transaction. The attacker's strength is characterized by the fraction $\alpha$ of account flags already known to $S$. We consider both a strong ($\alpha=0.2$) and a weak ($\alpha=0.05$) attacker. + +### Attack Algorithm. + +We empirically evaluate the privacy leakage during training time leveraging aspects of the classic membership inference attack (MIA) framework. +1. (Shadow model generation.) + In MIA, the attacker learns a set of shadow models using its own data to investigate the relation between training data and resulting model. Let $D_{known}$ denote the transactions where $S$ knows the receiver's bank account flag, and let $D_{unknown}$ denote the remaining transactions. $S$ first trains $m=5$ shadow models over different train/test splits of $D_{known}$. The shadow model training is identical to training the target anomaly detector. + +2. (MIA model generation.) During the test time of the shadow model, $S$ collects the model prediction over two copies, one with bank status flag 0 and another with flag 1, for each transaction. This prediction, together with the transaction's feature, becomes training data for the MIA model. The label of the data is 1 if the bank flag is correct and 0 otherwise. $S$ then trains an MIA model over the generated dataset. In our evaluation, the model is a MLP with three hidden layers of 128, 64 and 64 nodes. + +3. (Account flag inference.) For each transaction in $D_{unknown}$, $S$ collects two copies of predictions from the target anomaly detection model --- one with bank flag 0 and another with bank flag 1 --- and concatenates each prediction with the transaction features. Then, $S$ use the MIA model generated in Step 2 to calculate which bank flag is more likely to be true. + + +### Key Baselines + +**(Privacy.)** Since non-anomalous transactions always have a 'normal' receiver account flag 0, we only consider $S$ in the attacker role for deriving the bank flag bit associated with anomalous transactions. Furthermore, we observe that 82\% of accounts associated with an anomalous transaction have account flag 0 in the training dataset. $S$ can already achieve a success rate of 0.82 by guessing 0 all the time. Attack success rates below 0.82, achieved by adding noise, indicate that privacy is preserved. + +**(Accuracy.)** The sample XGBoost solution with AUPRC=0.6 without bank account flags is considered a baseline. An AUPRC lower than that will defeat the purpose of sharing information through federated learning. + +## Evaluation + +The table below shows the training time privacy-accuracy trade-off of our learning protocol. Even in the strong attack scheme, our noise injection mechanism can effectively enhance privacy in federated learning. When adding Gaussian noise ($\sigma=0.2$) and Laplace noise ($\lambda=0.1$), our pipeline can limit the success rate of MIA close to the naive 0.82 baseline (of guessing all 0). Meanwhile, the AUPRC of both methods are higher than a centralized solution *without* bank flags. The results show that a well-designed privacy enhancing federated learning pipeline can help achieve higher utility against financial crimes with little additional privacy leakage. + + +**Table: Privacy-accuracy tradeoff.** A larger AUPRC indicates better anomaly detection model performance. A smaller MIA success rate corresponds to better privacy-protection of bank account flags during training. Gaussian noise with $\sigma=0.2$ and Laplace noise with $\lambda=0.1$ achieve a good trade-off. The baseline for attacker success (always guess 00) given account flag distribution in training set is 0.82. MIA success rate below 0.82 is a good measure of privacy-protection. + +| Attack
Strength | Noise | MIA
Success Rate | AUPRC | +|:------:|:-----:|:-----:|:------:| +| Strong
$$\alpha=0.2$$ | No noise
Gaussian, $\sigma=0.1$
Laplace, $\lambda=0.1$
Gaussian, $\sigma=0.2$
Laplace, $\lambda=1$ | 0.93
0.92
0.86
0.80
0.57 | 0.79
0.72
0.70
0.65
0.13 | +| Weak
$$\alpha=0.05$$ | No noise
Gaussian, $\sigma=0.1$
Laplace, $\lambda=0.1$
Gaussian, $\sigma=0.2$
Laplace, $\lambda=1$ | 0.89
0.89
0.84
0.79
0.56 | 0.79
0.76
0.70
0.65
0.13 | + From 4f8df8e48382bfcb387d03475d0f24e0f194cf7e Mon Sep 17 00:00:00 2001 From: Hafiz Asif Date: Thu, 20 Apr 2023 16:40:52 -0400 Subject: [PATCH 14/58] Create README.md --- tools/de-identification/Scarlet-PETs/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tools/de-identification/Scarlet-PETs/README.md diff --git a/tools/de-identification/Scarlet-PETs/README.md b/tools/de-identification/Scarlet-PETs/README.md new file mode 100644 index 0000000..7ca173d --- /dev/null +++ b/tools/de-identification/Scarlet-PETs/README.md @@ -0,0 +1,17 @@ +# Scarlet-PETs + +**Name of Tool: **Anomaly Detection via Privacy-Enhanced Two-Step Federated Learning + +**Primary Focus Area (select one):** De-identification + +**De-identification Keywords (select any relevant):** Differential Privacy, Anonymization, Information Leakage, Federated Learning, Anomaly Detection, SMC + +**Brief Description: ** We developed a novel privacy-preserving (PP) two-step federated learning approach to identify anomalous financial transactions. In the first step, we performed PP feature mining for account-level banks’ data, followed by their augmentation to the payment network’s data using a PP encoding scheme. In the second step, a classifier is learned by the messaging network from the augmented data. A key benefit of our approach is that the performance in the federated setting is comparable to the performance in the centralized setting, and there is no significant drop in accuracy. Furthermore, our approach is extremely flexible since it allows the messaging network to adapt its model and features to build a better classifier without imposing any additional computational or privacy burden on the banks. + +**Additional Notes:** https://rutgers.box.com/s/q84zjo3edv5d1e1eu67ypihiw8cb2djq + +**GitHub User Serving as POC (or Email Address):** @[h-asif], @[sitaomin1994] + +**Affiliation/Organization(s) Contributing (if relevant):** Rutgers University + +**Tool Link:** https://github.com/idsla/Scarlet-PETs From 36171e92cd4d9a344bace149a882ae26543130c3 Mon Sep 17 00:00:00 2001 From: Hafiz Asif Date: Thu, 20 Apr 2023 16:51:15 -0400 Subject: [PATCH 15/58] Update README.md --- tools/de-identification/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/de-identification/README.md b/tools/de-identification/README.md index 9edb655..ca24828 100644 --- a/tools/de-identification/README.md +++ b/tools/de-identification/README.md @@ -80,3 +80,12 @@ Contributions are listed in alphabetical order. **Keywords:** Differential Privacy, Machine Learning **[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Private-Aggregation-of-Teacher-Ensembles-PATE)** | **[Link to Tool](https://github.com/tensorflow/privacy/tree/master/research)** + +## Scarlet-PETs: Anomaly Detection via Privacy-Enhanced Two-Step Federated Learning + +**Keywords:** Differential Privacy, Anonymization, Information Leakage, Federated Learning, Anomaly Detection, SMC + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Scarlet-PETs)** | **[Link to Tool](https://github.com/idsla/Scarlet-PETs)** + + + From 89ef80098c20ce1be3b3a4bb6722eee87e8e45ce Mon Sep 17 00:00:00 2001 From: LOLIN-G <56833210+LOLIN-G@users.noreply.github.com> Date: Mon, 1 May 2023 15:02:14 -0400 Subject: [PATCH 16/58] Create README.md --- tools/risk-assessment/HyFL-Framework/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 tools/risk-assessment/HyFL-Framework/README.md diff --git a/tools/risk-assessment/HyFL-Framework/README.md b/tools/risk-assessment/HyFL-Framework/README.md new file mode 100644 index 0000000..5fea9db --- /dev/null +++ b/tools/risk-assessment/HyFL-Framework/README.md @@ -0,0 +1 @@ +PETs Prize Challenge open-source release - Illidan Lab From a2a9bbecd3463f1f7558779dcbfcc62bed2d889c Mon Sep 17 00:00:00 2001 From: LOLIN-G <56833210+LOLIN-G@users.noreply.github.com> Date: Mon, 1 May 2023 15:20:39 -0400 Subject: [PATCH 17/58] Update README.md --- tools/risk-assessment/HyFL-Framework/README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tools/risk-assessment/HyFL-Framework/README.md b/tools/risk-assessment/HyFL-Framework/README.md index 5fea9db..65f44a7 100644 --- a/tools/risk-assessment/HyFL-Framework/README.md +++ b/tools/risk-assessment/HyFL-Framework/README.md @@ -1 +1,13 @@ -PETs Prize Challenge open-source release - Illidan Lab +# HyFL Framework for anomaly detection in financial transactions. + +- **Name of Tool:** HyFL framework for financial anomaly detection +- **Primary Focus Area:** Privacy Risk Assessment +- **Privacy Risk Assessment Keywords:** Differential Privacy, Information Leakage, Anomaly Detection, Federated Learning, Encryption +- **Brief Desription:** The repository provides a framework HyFL as a tool to detect anomaly in financial transactions. This framework supporst a hybrid federated learning paradigm to offer secure and privacy-aware learning and inference for financial anomaly detection. +- **GitHub User Serving as POC:** zhan2060@msu.edu +- Affiliation/Organazations Contributing:** + - Illidan Lab, Michigan State University, USA + - DENOS Lab, University of Calgary, Canada + +# For a Linked Tool +**Tool Link:** https://github.com/illidanlab/HyFL From 2ef91c82aee570664f68359f93ce6a87ecdbdda8 Mon Sep 17 00:00:00 2001 From: LOLIN-G <56833210+LOLIN-G@users.noreply.github.com> Date: Mon, 1 May 2023 15:21:07 -0400 Subject: [PATCH 18/58] Update README.md --- tools/risk-assessment/HyFL-Framework/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/risk-assessment/HyFL-Framework/README.md b/tools/risk-assessment/HyFL-Framework/README.md index 65f44a7..9a4ca2d 100644 --- a/tools/risk-assessment/HyFL-Framework/README.md +++ b/tools/risk-assessment/HyFL-Framework/README.md @@ -5,7 +5,7 @@ - **Privacy Risk Assessment Keywords:** Differential Privacy, Information Leakage, Anomaly Detection, Federated Learning, Encryption - **Brief Desription:** The repository provides a framework HyFL as a tool to detect anomaly in financial transactions. This framework supporst a hybrid federated learning paradigm to offer secure and privacy-aware learning and inference for financial anomaly detection. - **GitHub User Serving as POC:** zhan2060@msu.edu -- Affiliation/Organazations Contributing:** +- **Affiliation/Organazations Contributing:** - Illidan Lab, Michigan State University, USA - DENOS Lab, University of Calgary, Canada From ffae9d1ad1e7ea556b1eb8b2ece60e766f761807 Mon Sep 17 00:00:00 2001 From: LOLIN-G <56833210+LOLIN-G@users.noreply.github.com> Date: Mon, 1 May 2023 15:21:31 -0400 Subject: [PATCH 19/58] Update README.md --- tools/risk-assessment/HyFL-Framework/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/risk-assessment/HyFL-Framework/README.md b/tools/risk-assessment/HyFL-Framework/README.md index 9a4ca2d..b76ee42 100644 --- a/tools/risk-assessment/HyFL-Framework/README.md +++ b/tools/risk-assessment/HyFL-Framework/README.md @@ -4,7 +4,7 @@ - **Primary Focus Area:** Privacy Risk Assessment - **Privacy Risk Assessment Keywords:** Differential Privacy, Information Leakage, Anomaly Detection, Federated Learning, Encryption - **Brief Desription:** The repository provides a framework HyFL as a tool to detect anomaly in financial transactions. This framework supporst a hybrid federated learning paradigm to offer secure and privacy-aware learning and inference for financial anomaly detection. -- **GitHub User Serving as POC:** zhan2060@msu.edu +- **GitHub User Serving as POC:** hbzhang879@gmail.com - **Affiliation/Organazations Contributing:** - Illidan Lab, Michigan State University, USA - DENOS Lab, University of Calgary, Canada From 28a56ccf3ab7f31d2b3d58e2dd5080c587167578 Mon Sep 17 00:00:00 2001 From: Ken Liu Date: Mon, 1 May 2023 16:46:44 -0400 Subject: [PATCH 20/58] Add puffle to usnistgov/PrivacyEngCollabSpace --- tools/de-identification/README.md | 15 +++++++++------ tools/de-identification/puffle/README.md | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 tools/de-identification/puffle/README.md diff --git a/tools/de-identification/README.md b/tools/de-identification/README.md index 1392d8f..6399610 100644 --- a/tools/de-identification/README.md +++ b/tools/de-identification/README.md @@ -28,7 +28,7 @@ Contributions are listed in alphabetical order. **[Check out the Algorithms](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Differential-Privacy-Synthetic-Data-Challenge-Algorithms)** -## Differentially Private Stochastic Gradient Descent (DP-SGD) +## Differentially Private Stochastic Gradient Descent (DP-SGD) **Keywords:** Differential Privacy, Machine Learning @@ -75,23 +75,26 @@ Contributions are listed in alphabetical order. **[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/PixelDP)** | **[Link to Tool](https://github.com/columbia/pixeldp)** -## Privacy Protection Application (PPA) +## Privacy Protection Application (PPA) **Keywords:** K-Anonymity, Anonymization, Information Leakage, Algorithmic Fairness, Database Queries, Location Data **[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Privacy-Protection-Application-PPA)** | **[Link to Tool](https://github.com/usdot-its-jpo-data-portal/privacy-protection-application)** -## Private Aggregation of Teacher Ensembles (PATE) +## Private Aggregation of Teacher Ensembles (PATE) **Keywords:** Differential Privacy, Machine Learning **[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Private-Aggregation-of-Teacher-Ensembles-PATE)** | **[Link to Tool](https://github.com/tensorflow/privacy/tree/master/research)** -## Scarlet-PETs: Anomaly Detection via Privacy-Enhanced Two-Step Federated Learning +## puffle -**Keywords:** Differential Privacy, Anonymization, Information Leakage, Federated Learning, Anomaly Detection, SMC +**Keywords:** Differential Privacy, Machine Learning, Federated Learning, Model Personalization -**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Scarlet-PETs)** | **[Link to Tool](https://github.com/idsla/Scarlet-PETs)** +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/puffle)** | **[Link to Tool](https://github.com/kenziyuliu/pets-challenge)** +## Scarlet-PETs: Anomaly Detection via Privacy-Enhanced Two-Step Federated Learning +**Keywords:** Differential Privacy, Anonymization, Information Leakage, Federated Learning, Anomaly Detection, SMC +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Scarlet-PETs)** | **[Link to Tool](https://github.com/idsla/Scarlet-PETs)** diff --git a/tools/de-identification/puffle/README.md b/tools/de-identification/puffle/README.md new file mode 100644 index 0000000..1cb8063 --- /dev/null +++ b/tools/de-identification/puffle/README.md @@ -0,0 +1,15 @@ +# puffle + +**Primary Focus Area (select one):** De-identification + +**De-identification Keywords (select any relevant):** Differential Privacy, Machine Learning, Federated Learning, Model Personalization + +**Brief Description:** +This tool contains the solution of team puffle at the [US/UK PETs Prize Challenge](https://www.drivendata.org/competitions/group/nist-federated-learning/) that won [1st place](https://drivendata.co/blog/federated-learning-pets-prize-winners-phases-2-3) in the Pandemic Forecasting Track. Our solution is a simple, general, and easy-to-use multi-task learning (MTL) framework that balances the interplay between privacy, utility, and data heterogeneity in private cross-silo federated learning. Our framework involves three key components: (1) model personalization for capturing data heterogeneity across data silos, (2) local noisy gradient descent for silo-specific, node-level differential privacy in contact graphs, and (3) model mean-regularization to balance privacy-heterogeneity trade-offs and minimize the loss of accuracy. Combined together, our framework can provide differential privacy with flexible data granularity and improved privacy-utility tradeoffs; has high adaptability to gradient-based learning algorithms; and is simple to implement and tune. Our solution is in part based on our NeurIPS'22 [paper](https://arxiv.org/abs/2206.07902) studying privacy and personalization in cross-silo federated learning. + + +**GitHub User Serving as POC (or Email Address):** @kenziyuliu + +**Affiliation/Organization(s) Contributing (if relevant):** Carnegie Mellon University, School of Computer Science + +**Tool Link:** https://github.com/kenziyuliu/pets-challenge From 7ad237ced963f470c37701000ce3122539e25ed3 Mon Sep 17 00:00:00 2001 From: steveng9 <37061078+steveng9@users.noreply.github.com> Date: Tue, 2 May 2023 14:53:55 -0700 Subject: [PATCH 21/58] Create README.md --- tools/de-identification/PPMLHuskies/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 tools/de-identification/PPMLHuskies/README.md diff --git a/tools/de-identification/PPMLHuskies/README.md b/tools/de-identification/PPMLHuskies/README.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tools/de-identification/PPMLHuskies/README.md @@ -0,0 +1 @@ + From a1c63fd75e5d15972d725d02c84fae9d30c877b6 Mon Sep 17 00:00:00 2001 From: steveng9 <37061078+steveng9@users.noreply.github.com> Date: Tue, 2 May 2023 15:07:31 -0700 Subject: [PATCH 22/58] Update README.md Created README --- tools/de-identification/PPMLHuskies/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tools/de-identification/PPMLHuskies/README.md b/tools/de-identification/PPMLHuskies/README.md index 8b13789..0bee4ad 100644 --- a/tools/de-identification/PPMLHuskies/README.md +++ b/tools/de-identification/PPMLHuskies/README.md @@ -1 +1,19 @@ +# PPMLHuskies +**Primary Focus Area (select one):** De-identification + +**De-identification Keywords (select any relevant):** Differential Privacy, Homomorphic Encryption, Machine Learning, Federated Learning, + +**Brief Description:** +We propose a cross-silo federated architecture in which a payment network system (PNS) denoted by $\mathcal{S}$ has labeled data to train a model $\mathcal{M}$ for detection of anomalous payments. The other entities in the federation are banks $\mathcal{B}_1, \mathcal{B}_2, \ldots, \mathcal{B}_n$ that collaborate with $\mathcal{S}$ to create feature values to improve the utility of $\mathcal{M}$. To jointly extract feature values in a privacy-preserving manner, $\mathcal{S}$ and the banks engage in cryptographic protocols to perform computations over their joint data, without the need for $\mathcal{S}$ and the banks to disclose their data in an unencrypted manner to each other, i.e. our solution provides _input privacy_ through encryption, with mathematically verifiable guarantees. To the best of our knowledge, such joint privacy-preserving feature extraction in a federation with horizontally and vertically partitioned data is novel. + +Furthermore, to prevent the model from memorizing instances from the training data, the model is trained with a machine learning (ML) algorithm that provides Differential Privacy (DP). Our overall solution therefore provides both _input privacy_, as none of the entities in the federation ever sees the data of any of the other entities in an unencrypted manner, and _output privacy_, as the model and any inferences with that model avoid information leakage about the underlying training data under DP guarantees. + +For the privacy-preserving feature extraction we propose a custom protocol based on elliptic curve-based ElGamal and oblivious key-value stores (OKVS). The model is a neural network trained with DP-SGD. We prove that our overall solution is secure in the honest-but-curious setting. Experimental results demonstrate that our solution is efficient and scalable, and that it yields accurate models while preserving input and output privacy. + + +**GitHub User Serving as POC (or Email Address):** golobs@uw.edu + +**Affiliation/Organization(s) Contributing (if relevant):** University of Washington Tacoma, Universidade de Brasilia, TU Delft + +**Tool Link:** https://github.com/steveng9/PETsChallenge From 7481caf6515e541ac335bfaeb27b7d6479733341 Mon Sep 17 00:00:00 2001 From: steveng9 <37061078+steveng9@users.noreply.github.com> Date: Tue, 2 May 2023 15:11:27 -0700 Subject: [PATCH 23/58] Update README.md --- tools/de-identification/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/de-identification/README.md b/tools/de-identification/README.md index 1392d8f..ad22011 100644 --- a/tools/de-identification/README.md +++ b/tools/de-identification/README.md @@ -75,6 +75,12 @@ Contributions are listed in alphabetical order. **[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/PixelDP)** | **[Link to Tool](https://github.com/columbia/pixeldp)** +## PPMLHuskies + +**Keywords:** Differential Privacy, Homomorphic Encryption, Machine Learning, Federated Learning + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/PPMLHuskies)** | **[Link to Tool](https://github.com/steveng9/PETsChallenge)** + ## Privacy Protection Application (PPA) **Keywords:** K-Anonymity, Anonymization, Information Leakage, Algorithmic Fairness, Database Queries, Location Data From 0d7c879d92d1e27ca0f4d4fee0c258c2a8f02d24 Mon Sep 17 00:00:00 2001 From: Suchakra Sharma Date: Sat, 6 May 2023 21:29:03 -0400 Subject: [PATCH 24/58] Add Privado Scan to tools --- tools/risk-assessment/Privado-Scan/README.md | 33 ++++++++++++++++++++ tools/risk-assessment/README.md | 4 +++ 2 files changed, 37 insertions(+) create mode 100644 tools/risk-assessment/Privado-Scan/README.md diff --git a/tools/risk-assessment/Privado-Scan/README.md b/tools/risk-assessment/Privado-Scan/README.md new file mode 100644 index 0000000..73d3b3f --- /dev/null +++ b/tools/risk-assessment/Privado-Scan/README.md @@ -0,0 +1,33 @@ +# Privado Scan + +**Primary Focus Area:** Privacy Risk Assessment + +**Brief Description:** Privado Scan is an open-source privacy scanner that allows an engineer to scan their application +code and discover how data flows in the application. It detects hundreds of personal data elements being processed and +further maps the data flow from the point of collection to "sinks" such as external third parties, databases, logs, and +internal APIs. It allows privacy engineers to concretely verify and assess if a certain data collection policy set on an +application actualy matches the implementation right in the code itself - thus embedding privacy assesments in the +developers' workflow. + +**Tool Link:** https://github.com/Privado-Inc/privado + +**Additional Info:** Here are some resources to learn how Privado Scan works and how to contribute to it: + * Source: [Rules](https://github.com/Privado-Inc/privado), [Engine](https://github.com/Privado-Inc/privado-core) + * Docs: https://docs.privado.ai + * Use Cases: + * Generate and maintain data maps and Record of Processing Activity (RoPA) Reports by scanning code + * Discover and classify personal data elements inside the application's code and verify if they adhere to privacy policies + * Get comprehensive insight on dataflows within an application from interesting sources (such as user input forms) to + interesting sinks (such as logs, external services, third parties, databases etc.) + * Verify and enforce data protection and governance policies right in code + * Assess private data leakage risks by directly verifying it at an engineering level (eg. verify if a developer collected + precise location in a phone app and if it was actualy sent to a remote third party logging service) + * Talks/Videos: *Building an Automated Machine for Discovering Privacy Violations at Scale (Usenix Enigma 2023)* + [[Link]](https://www.usenix.org/conference/enigma2023/presentation/sharma) + +Feedback and suggestions for improvement of Privado Scan are welcome. Please reach out to us on our +[Privado Slack Community](https://join.slack.com/t/privado-community/shared_invite/zt-yk5zcxh3-gj8sS9w6SvL5lNYZLMbIpw) + +**GitHub User Serving as POC (or Email Address):** @tuxology + +**Affiliation/Organization(s) Contributing (if relevant):** Privado Inc. \ No newline at end of file diff --git a/tools/risk-assessment/README.md b/tools/risk-assessment/README.md index 36b1268..3d65839 100644 --- a/tools/risk-assessment/README.md +++ b/tools/risk-assessment/README.md @@ -8,3 +8,7 @@ Contributions are listed in alphabetical order. ## NIST Privacy Risk Assessment Methodology (PRAM) **[Link to tool](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/risk-assessment/NIST-Privacy-Risk-Assessment-Methodology-PRAM)** + +## Privado Scan + +**[Link to tool](https://github.com/Privado-Inc/privado)** From 712798299fd47a92bf005908b125c722ff00be47 Mon Sep 17 00:00:00 2001 From: LOLIN-G <56833210+LOLIN-G@users.noreply.github.com> Date: Mon, 8 May 2023 14:45:27 -0400 Subject: [PATCH 25/58] move README.md to the de-identification tools --- .../HyFL-Framework/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/{risk-assessment => de-identification}/HyFL-Framework/README.md (100%) diff --git a/tools/risk-assessment/HyFL-Framework/README.md b/tools/de-identification/HyFL-Framework/README.md similarity index 100% rename from tools/risk-assessment/HyFL-Framework/README.md rename to tools/de-identification/HyFL-Framework/README.md From e1fa321a29025e84a2705b34b222b72014d29017 Mon Sep 17 00:00:00 2001 From: LOLIN-G <56833210+LOLIN-G@users.noreply.github.com> Date: Mon, 8 May 2023 14:46:42 -0400 Subject: [PATCH 26/58] Update README.md --- tools/de-identification/HyFL-Framework/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/de-identification/HyFL-Framework/README.md b/tools/de-identification/HyFL-Framework/README.md index b76ee42..613bf3a 100644 --- a/tools/de-identification/HyFL-Framework/README.md +++ b/tools/de-identification/HyFL-Framework/README.md @@ -1,7 +1,7 @@ # HyFL Framework for anomaly detection in financial transactions. - **Name of Tool:** HyFL framework for financial anomaly detection -- **Primary Focus Area:** Privacy Risk Assessment +- **Primary Focus Area:** De-identification - **Privacy Risk Assessment Keywords:** Differential Privacy, Information Leakage, Anomaly Detection, Federated Learning, Encryption - **Brief Desription:** The repository provides a framework HyFL as a tool to detect anomaly in financial transactions. This framework supporst a hybrid federated learning paradigm to offer secure and privacy-aware learning and inference for financial anomaly detection. - **GitHub User Serving as POC:** hbzhang879@gmail.com From 10705b0c8a582d6d6cecef4884ba2d7762ea8f18 Mon Sep 17 00:00:00 2001 From: David Darais Date: Sun, 10 Dec 2023 18:13:33 -0700 Subject: [PATCH 27/58] NIST SP 800-226 Supplemental Material --- .../Count.ipynb | 1641 +++++++++++++++++ .../HistogramBias.ipynb | 1018 ++++++++++ .../MachineLearningBias.ipynb | 686 +++++++ .../lattes.csv | 1000 ++++++++++ 4 files changed, 4345 insertions(+) create mode 100644 tools/de-identification/NIST-SP-800-226-SupplementalMaterial/Count.ipynb create mode 100644 tools/de-identification/NIST-SP-800-226-SupplementalMaterial/HistogramBias.ipynb create mode 100644 tools/de-identification/NIST-SP-800-226-SupplementalMaterial/MachineLearningBias.ipynb create mode 100644 tools/de-identification/NIST-SP-800-226-SupplementalMaterial/lattes.csv diff --git a/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/Count.ipynb b/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/Count.ipynb new file mode 100644 index 0000000..01da4ac --- /dev/null +++ b/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/Count.ipynb @@ -0,0 +1,1641 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a4314281", + "metadata": {}, + "source": [ + "***Disclaimer:** This code was written for the sole educational purpose of\n", + "helping practitioners understand differential privacy concepts.*\n", + "\n", + "***This code is not suitable for use in production environments that process\n", + "sensitive data.***\n", + "\n", + "*One security issues with running this code in a production setting is that it\n", + "is susceptible to side-channel attacks related to the use of floating point\n", + "values. Other security issues surely exist, and we make no effort to enumerate\n", + "all of them.*\n", + "\n", + "---------------------------------------------------------------------------------\n", + "\n", + "# Counting Queries\n", + "\n", + "This Python notebook shows how to perform basic counting queries using the\n", + "differential privacy Laplace mechanism. The notebook covers reading in data from\n", + "a file, preprocessing it, running the query, and invoking the mechanism to\n", + "satisfy differential privacy. In addition to implementing the query, we also\n", + "demonstrate how to quantify and visualize query accuracy.\n", + "\n", + "As a running example, let's answer the following question using a simple dataset\n", + "containing transaction data from a coffee shop:\n", + "\n", + "**How many pumpkin spice lattes have been sold?**\n", + "\n", + "To achieve this using differential privacy, we will complete the following\n", + "steps:\n", + "\n", + "1. Read in the transaction data from the coffee shop\n", + "2. Select the `product` column from the data; this column indicates which\n", + " product was sold in the transaction\n", + "3. Filter the results to include just pumpkin spice lattes\n", + "4. Count the number of rows\n", + "5. Add noise with the Laplace mechanism to achieve differential privacy\n", + "\n", + "Before we start writing code, let's import a few third-party Python packages. We\n", + "will use `csv` for importing datasets encoded in CSV format into Python data\n", + "objects, `numpy` for the ability to sample numbers from the Laplace\n", + "distribution, and `random` for sampling subsets of the data chosen at random." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "43365513", + "metadata": {}, + "outputs": [], + "source": [ + "import csv\n", + "import numpy\n", + "import random\n", + "import matplotlib.pyplot as plt\n", + "import ipywidgets" + ] + }, + { + "cell_type": "markdown", + "id": "1687b0d3", + "metadata": {}, + "source": [ + "## Step 1: Reading in the Data\n", + "\n", + "Our first step is to load the data. We read the CSV file into variable\n", + "`input_data` using regular Python functions, and we also define `col_names` to\n", + "contain the column names for the dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "45bc0606", + "metadata": {}, + "outputs": [], + "source": [ + "filename = \"lattes.csv\"\n", + "with open(filename) as input_file:\n", + " input_data = list(csv.reader(input_file))\n", + " \n", + "col_names = [\"id\", \"product\", \"price\"]" + ] + }, + { + "cell_type": "markdown", + "id": "a6e65493", + "metadata": {}, + "source": [ + "Let's print out the dataset and take a look at what is inside." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cb13d0c3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The data has 3 columns::\n", + "['id', 'product', 'price']\n", + "The data has 1000 rows:\n", + "['1', 'Coffee', '$2.04']\n", + "['2', 'Coffee', '$1.55']\n", + "['3', 'Pumpkin Spice Latte', '$5.94']\n", + "['4', 'Pumpkin Spice Latte', '$3.67']\n", + "['5', 'Latte', '$4.15']\n", + "['6', 'Latte', '$3.47']\n", + "['7', 'Latte', '$1.37']\n", + "['8', 'Coffee', '$5.50']\n", + "['9', 'Latte', '$4.54']\n", + "['10', 'Latte', '$1.32']\n", + "['11', 'Pumpkin Spice Latte', '$5.89']\n", + "['12', 'Latte', '$4.42']\n", + "['13', 'Latte', '$1.08']\n", + "['14', 'Latte', '$2.74']\n", + "['15', 'Latte', '$1.53']\n", + "['16', 'Coffee', '$3.07']\n", + "['17', 'Latte', '$2.31']\n", + "['18', 'Pumpkin Spice Latte', '$1.24']\n", + "['19', 'Pumpkin Spice Latte', '$2.33']\n", + "['20', 'Coffee', '$5.24']\n", + "['21', 'Latte', '$3.08']\n", + "['22', 'Latte', '$4.73']\n", + "['23', 'Pumpkin Spice Latte', '$3.53']\n", + "['24', 'Pumpkin Spice Latte', '$4.46']\n", + "['25', 'Coffee', '$4.86']\n", + "['26', 'Coffee', '$1.72']\n", + "['27', 'Latte', '$2.68']\n", + "['28', 'Coffee', '$2.02']\n", + "['29', 'Pumpkin Spice Latte', '$5.85']\n", + "['30', 'Latte', '$3.72']\n", + "['31', 'Coffee', '$4.63']\n", + "['32', 'Latte', '$2.56']\n", + "['33', 'Latte', '$4.57']\n", + "['34', 'Coffee', '$3.56']\n", + "['35', 'Latte', '$1.09']\n", + "['36', 'Coffee', '$3.58']\n", + "['37', 'Coffee', '$3.48']\n", + "['38', 'Pumpkin Spice Latte', '$1.69']\n", + "['39', 'Latte', '$4.56']\n", + "['40', 'Latte', '$1.03']\n", + "['41', 'Pumpkin Spice Latte', '$4.46']\n", + "['42', 'Pumpkin Spice Latte', '$3.58']\n", + "['43', 'Pumpkin Spice Latte', '$5.74']\n", + "['44', 'Coffee', '$5.78']\n", + "['45', 'Pumpkin Spice Latte', '$2.56']\n", + "['46', 'Latte', '$5.39']\n", + "['47', 'Latte', '$1.79']\n", + "['48', 'Pumpkin Spice Latte', '$3.74']\n", + "['49', 'Pumpkin Spice Latte', '$3.15']\n", + "['50', 'Pumpkin Spice Latte', '$5.36']\n", + "['51', 'Latte', '$3.53']\n", + "['52', 'Pumpkin Spice Latte', '$1.76']\n", + "['53', 'Coffee', '$3.49']\n", + "['54', 'Pumpkin Spice Latte', '$4.83']\n", + "['55', 'Coffee', '$5.25']\n", + "['56', 'Pumpkin Spice Latte', '$2.33']\n", + "['57', 'Pumpkin Spice Latte', '$4.77']\n", + "['58', 'Latte', '$2.85']\n", + "['59', 'Coffee', '$4.88']\n", + "['60', 'Latte', '$5.87']\n", + "['61', 'Pumpkin Spice Latte', '$2.84']\n", + "['62', 'Coffee', '$4.30']\n", + "['63', 'Coffee', '$4.97']\n", + "['64', 'Latte', '$4.07']\n", + "['65', 'Pumpkin Spice Latte', '$1.69']\n", + "['66', 'Latte', '$5.16']\n", + "['67', 'Pumpkin Spice Latte', '$4.36']\n", + "['68', 'Latte', '$5.39']\n", + "['69', 'Latte', '$4.34']\n", + "['70', 'Coffee', '$1.76']\n", + "['71', 'Latte', '$2.90']\n", + "['72', 'Latte', '$1.65']\n", + "['73', 'Pumpkin Spice Latte', '$3.51']\n", + "['74', 'Pumpkin Spice Latte', '$2.38']\n", + "['75', 'Coffee', '$2.66']\n", + "['76', 'Pumpkin Spice Latte', '$3.59']\n", + "['77', 'Coffee', '$5.64']\n", + "['78', 'Pumpkin Spice Latte', '$4.99']\n", + "['79', 'Pumpkin Spice Latte', '$3.92']\n", + "['80', 'Pumpkin Spice Latte', '$3.90']\n", + "['81', 'Coffee', '$1.38']\n", + "['82', 'Coffee', '$2.48']\n", + "['83', 'Pumpkin Spice Latte', '$5.93']\n", + "['84', 'Coffee', '$5.95']\n", + "['85', 'Latte', '$1.58']\n", + "['86', 'Latte', '$1.33']\n", + "['87', 'Latte', '$2.63']\n", + "['88', 'Pumpkin Spice Latte', '$5.81']\n", + "['89', 'Coffee', '$5.98']\n", + "['90', 'Coffee', '$1.79']\n", + "['91', 'Coffee', '$3.19']\n", + "['92', 'Latte', '$2.72']\n", + "['93', 'Coffee', '$5.29']\n", + "['94', 'Coffee', '$3.23']\n", + "['95', 'Pumpkin Spice Latte', '$3.63']\n", + "['96', 'Coffee', '$2.86']\n", + "['97', 'Coffee', '$4.14']\n", + "['98', 'Pumpkin Spice Latte', '$2.29']\n", + "['99', 'Pumpkin Spice Latte', '$4.37']\n", + "['100', 'Latte', '$4.64']\n", + "['101', 'Pumpkin Spice Latte', '$3.78']\n", + "['102', 'Pumpkin Spice Latte', '$1.89']\n", + "['103', 'Coffee', '$4.37']\n", + "['104', 'Pumpkin Spice Latte', '$4.80']\n", + "['105', 'Coffee', '$2.00']\n", + "['106', 'Latte', '$3.30']\n", + "['107', 'Coffee', '$5.16']\n", + "['108', 'Pumpkin Spice Latte', '$3.34']\n", + "['109', 'Latte', '$5.07']\n", + "['110', 'Pumpkin Spice Latte', '$5.04']\n", + "['111', 'Coffee', '$1.24']\n", + "['112', 'Coffee', '$5.46']\n", + "['113', 'Coffee', '$1.30']\n", + "['114', 'Coffee', '$3.81']\n", + "['115', 'Coffee', '$5.08']\n", + "['116', 'Pumpkin Spice Latte', '$2.55']\n", + "['117', 'Coffee', '$4.14']\n", + "['118', 'Pumpkin Spice Latte', '$3.85']\n", + "['119', 'Pumpkin Spice Latte', '$5.73']\n", + "['120', 'Coffee', '$4.76']\n", + "['121', 'Pumpkin Spice Latte', '$5.29']\n", + "['122', 'Pumpkin Spice Latte', '$5.55']\n", + "['123', 'Pumpkin Spice Latte', '$3.75']\n", + "['124', 'Coffee', '$4.93']\n", + "['125', 'Latte', '$2.78']\n", + "['126', 'Latte', '$4.39']\n", + "['127', 'Coffee', '$3.83']\n", + "['128', 'Latte', '$1.84']\n", + "['129', 'Coffee', '$5.85']\n", + "['130', 'Coffee', '$2.09']\n", + "['131', 'Coffee', '$4.52']\n", + "['132', 'Pumpkin Spice Latte', '$4.09']\n", + "['133', 'Latte', '$4.85']\n", + "['134', 'Coffee', '$3.49']\n", + "['135', 'Pumpkin Spice Latte', '$4.33']\n", + "['136', 'Latte', '$5.62']\n", + "['137', 'Latte', '$1.07']\n", + "['138', 'Coffee', '$2.30']\n", + "['139', 'Coffee', '$2.76']\n", + "['140', 'Pumpkin Spice Latte', '$5.68']\n", + "['141', 'Latte', '$5.86']\n", + "['142', 'Latte', '$5.72']\n", + "['143', 'Latte', '$4.95']\n", + "['144', 'Pumpkin Spice Latte', '$5.03']\n", + "['145', 'Latte', '$1.33']\n", + "['146', 'Pumpkin Spice Latte', '$5.63']\n", + "['147', 'Pumpkin Spice Latte', '$3.87']\n", + "['148', 'Pumpkin Spice Latte', '$2.13']\n", + "['149', 'Latte', '$2.78']\n", + "['150', 'Pumpkin Spice Latte', '$3.48']\n", + "['151', 'Coffee', '$5.88']\n", + "['152', 'Pumpkin Spice Latte', '$5.10']\n", + "['153', 'Pumpkin Spice Latte', '$4.27']\n", + "['154', 'Coffee', '$5.47']\n", + "['155', 'Pumpkin Spice Latte', '$3.30']\n", + "['156', 'Coffee', '$5.45']\n", + "['157', 'Coffee', '$5.76']\n", + "['158', 'Latte', '$1.56']\n", + "['159', 'Pumpkin Spice Latte', '$5.30']\n", + "['160', 'Coffee', '$4.88']\n", + "['161', 'Latte', '$1.37']\n", + "['162', 'Pumpkin Spice Latte', '$2.78']\n", + "['163', 'Latte', '$1.20']\n", + "['164', 'Coffee', '$5.61']\n", + "['165', 'Coffee', '$4.05']\n", + "['166', 'Latte', '$1.36']\n", + "['167', 'Pumpkin Spice Latte', '$3.32']\n", + "['168', 'Coffee', '$1.26']\n", + "['169', 'Pumpkin Spice Latte', '$1.36']\n", + "['170', 'Coffee', '$3.35']\n", + "['171', 'Pumpkin Spice Latte', '$5.16']\n", + "['172', 'Coffee', '$1.63']\n", + "['173', 'Coffee', '$1.94']\n", + "['174', 'Pumpkin Spice Latte', '$3.15']\n", + "['175', 'Coffee', '$5.77']\n", + "['176', 'Latte', '$4.60']\n", + "['177', 'Latte', '$2.71']\n", + "['178', 'Pumpkin Spice Latte', '$2.85']\n", + "['179', 'Latte', '$5.66']\n", + "['180', 'Latte', '$3.69']\n", + "['181', 'Pumpkin Spice Latte', '$4.19']\n", + "['182', 'Coffee', '$5.43']\n", + "['183', 'Latte', '$2.09']\n", + "['184', 'Coffee', '$1.08']\n", + "['185', 'Coffee', '$5.71']\n", + "['186', 'Pumpkin Spice Latte', '$5.39']\n", + "['187', 'Latte', '$2.83']\n", + "['188', 'Latte', '$2.58']\n", + "['189', 'Latte', '$5.28']\n", + "['190', 'Coffee', '$2.58']\n", + "['191', 'Pumpkin Spice Latte', '$1.17']\n", + "['192', 'Coffee', '$5.19']\n", + "['193', 'Pumpkin Spice Latte', '$1.06']\n", + "['194', 'Latte', '$2.52']\n", + "['195', 'Latte', '$5.05']\n", + "['196', 'Latte', '$4.55']\n", + "['197', 'Coffee', '$5.05']\n", + "['198', 'Coffee', '$4.51']\n", + "['199', 'Pumpkin Spice Latte', '$3.64']\n", + "['200', 'Latte', '$3.75']\n", + "['201', 'Pumpkin Spice Latte', '$5.55']\n", + "['202', 'Pumpkin Spice Latte', '$4.92']\n", + "['203', 'Coffee', '$3.40']\n", + "['204', 'Pumpkin Spice Latte', '$5.16']\n", + "['205', 'Coffee', '$2.53']\n", + "['206', 'Pumpkin Spice Latte', '$3.29']\n", + "['207', 'Coffee', '$1.96']\n", + "['208', 'Coffee', '$1.52']\n", + "['209', 'Latte', '$4.39']\n", + "['210', 'Latte', '$5.83']\n", + "['211', 'Pumpkin Spice Latte', '$1.25']\n", + "['212', 'Coffee', '$5.75']\n", + "['213', 'Coffee', '$4.35']\n", + "['214', 'Coffee', '$3.23']\n", + "['215', 'Pumpkin Spice Latte', '$1.65']\n", + "['216', 'Pumpkin Spice Latte', '$2.31']\n", + "['217', 'Latte', '$4.80']\n", + "['218', 'Coffee', '$3.69']\n", + "['219', 'Coffee', '$2.44']\n", + "['220', 'Latte', '$2.10']\n", + "['221', 'Pumpkin Spice Latte', '$1.32']\n", + "['222', 'Latte', '$1.71']\n", + "['223', 'Coffee', '$4.66']\n", + "['224', 'Latte', '$1.42']\n", + "['225', 'Latte', '$4.34']\n", + "['226', 'Pumpkin Spice Latte', '$3.12']\n", + "['227', 'Latte', '$2.93']\n", + "['228', 'Coffee', '$1.29']\n", + "['229', 'Coffee', '$2.65']\n", + "['230', 'Latte', '$3.99']\n", + "['231', 'Pumpkin Spice Latte', '$2.65']\n", + "['232', 'Pumpkin Spice Latte', '$4.26']\n", + "['233', 'Latte', '$3.35']\n", + "['234', 'Coffee', '$4.09']\n", + "['235', 'Latte', '$4.93']\n", + "['236', 'Latte', '$1.20']\n", + "['237', 'Latte', '$1.22']\n", + "['238', 'Latte', '$3.76']\n", + "['239', 'Pumpkin Spice Latte', '$2.72']\n", + "['240', 'Latte', '$2.52']\n", + "['241', 'Coffee', '$3.02']\n", + "['242', 'Latte', '$5.74']\n", + "['243', 'Pumpkin Spice Latte', '$2.05']\n", + "['244', 'Latte', '$3.83']\n", + "['245', 'Pumpkin Spice Latte', '$2.44']\n", + "['246', 'Latte', '$3.23']\n", + "['247', 'Latte', '$2.79']\n", + "['248', 'Latte', '$3.93']\n", + "['249', 'Latte', '$5.63']\n", + "['250', 'Coffee', '$2.66']\n", + "['251', 'Coffee', '$1.84']\n", + "['252', 'Coffee', '$4.81']\n", + "['253', 'Coffee', '$5.95']\n", + "['254', 'Coffee', '$1.02']\n", + "['255', 'Latte', '$4.11']\n", + "['256', 'Pumpkin Spice Latte', '$2.31']\n", + "['257', 'Latte', '$1.30']\n", + "['258', 'Pumpkin Spice Latte', '$5.76']\n", + "['259', 'Latte', '$3.89']\n", + "['260', 'Pumpkin Spice Latte', '$2.64']\n", + "['261', 'Coffee', '$1.06']\n", + "['262', 'Coffee', '$4.27']\n", + "['263', 'Pumpkin Spice Latte', '$4.94']\n", + "['264', 'Pumpkin Spice Latte', '$2.75']\n", + "['265', 'Latte', '$4.67']\n", + "['266', 'Coffee', '$1.86']\n", + "['267', 'Coffee', '$5.18']\n", + "['268', 'Latte', '$5.91']\n", + "['269', 'Latte', '$2.47']\n", + "['270', 'Latte', '$5.78']\n", + "['271', 'Latte', '$4.18']\n", + "['272', 'Pumpkin Spice Latte', '$1.52']\n", + "['273', 'Pumpkin Spice Latte', '$4.10']\n", + "['274', 'Coffee', '$5.18']\n", + "['275', 'Pumpkin Spice Latte', '$5.70']\n", + "['276', 'Coffee', '$1.88']\n", + "['277', 'Pumpkin Spice Latte', '$3.64']\n", + "['278', 'Pumpkin Spice Latte', '$4.53']\n", + "['279', 'Coffee', '$2.48']\n", + "['280', 'Pumpkin Spice Latte', '$5.13']\n", + "['281', 'Pumpkin Spice Latte', '$5.52']\n", + "['282', 'Pumpkin Spice Latte', '$1.71']\n", + "['283', 'Coffee', '$5.44']\n", + "['284', 'Coffee', '$4.65']\n", + "['285', 'Coffee', '$3.07']\n", + "['286', 'Latte', '$5.83']\n", + "['287', 'Pumpkin Spice Latte', '$2.90']\n", + "['288', 'Latte', '$4.23']\n", + "['289', 'Pumpkin Spice Latte', '$2.62']\n", + "['290', 'Latte', '$5.70']\n", + "['291', 'Coffee', '$2.78']\n", + "['292', 'Latte', '$3.07']\n", + "['293', 'Coffee', '$2.11']\n", + "['294', 'Pumpkin Spice Latte', '$1.30']\n", + "['295', 'Latte', '$2.40']\n", + "['296', 'Latte', '$5.16']\n", + "['297', 'Latte', '$4.14']\n", + "['298', 'Pumpkin Spice Latte', '$3.19']\n", + "['299', 'Pumpkin Spice Latte', '$5.32']\n", + "['300', 'Coffee', '$2.21']\n", + "['301', 'Latte', '$3.29']\n", + "['302', 'Pumpkin Spice Latte', '$5.72']\n", + "['303', 'Pumpkin Spice Latte', '$3.96']\n", + "['304', 'Coffee', '$2.39']\n", + "['305', 'Latte', '$1.45']\n", + "['306', 'Coffee', '$3.64']\n", + "['307', 'Coffee', '$1.72']\n", + "['308', 'Latte', '$3.47']\n", + "['309', 'Pumpkin Spice Latte', '$1.75']\n", + "['310', 'Coffee', '$2.33']\n", + "['311', 'Coffee', '$3.81']\n", + "['312', 'Latte', '$2.72']\n", + "['313', 'Coffee', '$5.94']\n", + "['314', 'Pumpkin Spice Latte', '$2.39']\n", + "['315', 'Latte', '$4.31']\n", + "['316', 'Coffee', '$1.60']\n", + "['317', 'Pumpkin Spice Latte', '$5.14']\n", + "['318', 'Pumpkin Spice Latte', '$1.83']\n", + "['319', 'Latte', '$5.02']\n", + "['320', 'Coffee', '$2.65']\n", + "['321', 'Pumpkin Spice Latte', '$2.15']\n", + "['322', 'Coffee', '$2.23']\n", + "['323', 'Pumpkin Spice Latte', '$2.79']\n", + "['324', 'Coffee', '$5.82']\n", + "['325', 'Latte', '$5.33']\n", + "['326', 'Latte', '$2.24']\n", + "['327', 'Pumpkin Spice Latte', '$1.64']\n", + "['328', 'Latte', '$2.63']\n", + "['329', 'Pumpkin Spice Latte', '$5.00']\n", + "['330', 'Coffee', '$5.31']\n", + "['331', 'Coffee', '$5.76']\n", + "['332', 'Pumpkin Spice Latte', '$4.06']\n", + "['333', 'Pumpkin Spice Latte', '$5.65']\n", + "['334', 'Coffee', '$4.98']\n", + "['335', 'Coffee', '$3.91']\n", + "['336', 'Latte', '$2.28']\n", + "['337', 'Coffee', '$5.05']\n", + "['338', 'Latte', '$2.60']\n", + "['339', 'Coffee', '$5.57']\n", + "['340', 'Pumpkin Spice Latte', '$5.82']\n", + "['341', 'Latte', '$1.29']\n", + "['342', 'Pumpkin Spice Latte', '$5.19']\n", + "['343', 'Latte', '$5.27']\n", + "['344', 'Pumpkin Spice Latte', '$6.00']\n", + "['345', 'Latte', '$1.00']\n", + "['346', 'Latte', '$3.69']\n", + "['347', 'Coffee', '$2.17']\n", + "['348', 'Pumpkin Spice Latte', '$4.89']\n", + "['349', 'Coffee', '$4.52']\n", + "['350', 'Coffee', '$3.58']\n", + "['351', 'Latte', '$4.05']\n", + "['352', 'Pumpkin Spice Latte', '$2.42']\n", + "['353', 'Latte', '$1.41']\n", + "['354', 'Latte', '$3.03']\n", + "['355', 'Coffee', '$4.73']\n", + "['356', 'Coffee', '$2.89']\n", + "['357', 'Pumpkin Spice Latte', '$5.22']\n", + "['358', 'Pumpkin Spice Latte', '$2.51']\n", + "['359', 'Coffee', '$2.56']\n", + "['360', 'Latte', '$1.37']\n", + "['361', 'Pumpkin Spice Latte', '$4.33']\n", + "['362', 'Coffee', '$5.21']\n", + "['363', 'Coffee', '$3.04']\n", + "['364', 'Latte', '$4.66']\n", + "['365', 'Latte', '$4.58']\n", + "['366', 'Coffee', '$2.51']\n", + "['367', 'Coffee', '$1.21']\n", + "['368', 'Coffee', '$4.61']\n", + "['369', 'Latte', '$4.78']\n", + "['370', 'Coffee', '$5.78']\n", + "['371', 'Latte', '$3.14']\n", + "['372', 'Pumpkin Spice Latte', '$4.66']\n", + "['373', 'Coffee', '$1.82']\n", + "['374', 'Pumpkin Spice Latte', '$2.63']\n", + "['375', 'Pumpkin Spice Latte', '$2.39']\n", + "['376', 'Latte', '$1.38']\n", + "['377', 'Latte', '$2.16']\n", + "['378', 'Latte', '$1.26']\n", + "['379', 'Coffee', '$5.59']\n", + "['380', 'Coffee', '$3.03']\n", + "['381', 'Latte', '$4.56']\n", + "['382', 'Latte', '$3.21']\n", + "['383', 'Latte', '$1.89']\n", + "['384', 'Latte', '$5.00']\n", + "['385', 'Pumpkin Spice Latte', '$3.38']\n", + "['386', 'Latte', '$3.32']\n", + "['387', 'Latte', '$2.32']\n", + "['388', 'Latte', '$1.44']\n", + "['389', 'Latte', '$3.25']\n", + "['390', 'Pumpkin Spice Latte', '$3.31']\n", + "['391', 'Coffee', '$4.80']\n", + "['392', 'Latte', '$3.05']\n", + "['393', 'Latte', '$3.73']\n", + "['394', 'Pumpkin Spice Latte', '$4.70']\n", + "['395', 'Pumpkin Spice Latte', '$3.68']\n", + "['396', 'Coffee', '$5.78']\n", + "['397', 'Pumpkin Spice Latte', '$3.50']\n", + "['398', 'Latte', '$5.77']\n", + "['399', 'Coffee', '$5.27']\n", + "['400', 'Pumpkin Spice Latte', '$3.09']\n", + "['401', 'Coffee', '$4.34']\n", + "['402', 'Coffee', '$2.55']\n", + "['403', 'Coffee', '$5.06']\n", + "['404', 'Latte', '$3.84']\n", + "['405', 'Latte', '$3.63']\n", + "['406', 'Coffee', '$2.44']\n", + "['407', 'Coffee', '$3.06']\n", + "['408', 'Pumpkin Spice Latte', '$5.62']\n", + "['409', 'Coffee', '$5.66']\n", + "['410', 'Latte', '$5.43']\n", + "['411', 'Pumpkin Spice Latte', '$4.82']\n", + "['412', 'Coffee', '$2.35']\n", + "['413', 'Pumpkin Spice Latte', '$2.28']\n", + "['414', 'Latte', '$3.92']\n", + "['415', 'Pumpkin Spice Latte', '$3.39']\n", + "['416', 'Coffee', '$4.32']\n", + "['417', 'Coffee', '$5.13']\n", + "['418', 'Coffee', '$4.21']\n", + "['419', 'Coffee', '$3.40']\n", + "['420', 'Latte', '$4.90']\n", + "['421', 'Pumpkin Spice Latte', '$2.70']\n", + "['422', 'Coffee', '$5.48']\n", + "['423', 'Latte', '$1.94']\n", + "['424', 'Latte', '$1.42']\n", + "['425', 'Coffee', '$3.25']\n", + "['426', 'Pumpkin Spice Latte', '$5.42']\n", + "['427', 'Pumpkin Spice Latte', '$5.17']\n", + "['428', 'Latte', '$4.50']\n", + "['429', 'Pumpkin Spice Latte', '$5.49']\n", + "['430', 'Coffee', '$1.88']\n", + "['431', 'Pumpkin Spice Latte', '$1.80']\n", + "['432', 'Pumpkin Spice Latte', '$1.14']\n", + "['433', 'Latte', '$1.44']\n", + "['434', 'Pumpkin Spice Latte', '$2.91']\n", + "['435', 'Latte', '$2.74']\n", + "['436', 'Latte', '$5.83']\n", + "['437', 'Coffee', '$1.04']\n", + "['438', 'Coffee', '$4.56']\n", + "['439', 'Latte', '$5.58']\n", + "['440', 'Pumpkin Spice Latte', '$5.02']\n", + "['441', 'Latte', '$2.49']\n", + "['442', 'Pumpkin Spice Latte', '$1.17']\n", + "['443', 'Pumpkin Spice Latte', '$1.03']\n", + "['444', 'Latte', '$1.28']\n", + "['445', 'Latte', '$4.56']\n", + "['446', 'Pumpkin Spice Latte', '$3.06']\n", + "['447', 'Coffee', '$5.46']\n", + "['448', 'Pumpkin Spice Latte', '$3.50']\n", + "['449', 'Coffee', '$2.54']\n", + "['450', 'Latte', '$4.21']\n", + "['451', 'Pumpkin Spice Latte', '$4.98']\n", + "['452', 'Coffee', '$5.88']\n", + "['453', 'Latte', '$1.58']\n", + "['454', 'Pumpkin Spice Latte', '$4.69']\n", + "['455', 'Pumpkin Spice Latte', '$4.02']\n", + "['456', 'Pumpkin Spice Latte', '$4.29']\n", + "['457', 'Pumpkin Spice Latte', '$4.16']\n", + "['458', 'Pumpkin Spice Latte', '$5.35']\n", + "['459', 'Coffee', '$1.38']\n", + "['460', 'Pumpkin Spice Latte', '$1.43']\n", + "['461', 'Coffee', '$2.17']\n", + "['462', 'Latte', '$5.42']\n", + "['463', 'Pumpkin Spice Latte', '$3.44']\n", + "['464', 'Latte', '$5.62']\n", + "['465', 'Latte', '$5.47']\n", + "['466', 'Latte', '$3.82']\n", + "['467', 'Latte', '$4.39']\n", + "['468', 'Latte', '$3.94']\n", + "['469', 'Pumpkin Spice Latte', '$6.00']\n", + "['470', 'Coffee', '$1.62']\n", + "['471', 'Coffee', '$4.81']\n", + "['472', 'Latte', '$1.50']\n", + "['473', 'Coffee', '$3.40']\n", + "['474', 'Latte', '$2.13']\n", + "['475', 'Coffee', '$1.56']\n", + "['476', 'Pumpkin Spice Latte', '$2.09']\n", + "['477', 'Latte', '$4.67']\n", + "['478', 'Latte', '$2.59']\n", + "['479', 'Latte', '$1.94']\n", + "['480', 'Pumpkin Spice Latte', '$4.28']\n", + "['481', 'Coffee', '$2.72']\n", + "['482', 'Coffee', '$5.16']\n", + "['483', 'Latte', '$4.41']\n", + "['484', 'Pumpkin Spice Latte', '$3.09']\n", + "['485', 'Pumpkin Spice Latte', '$2.61']\n", + "['486', 'Coffee', '$3.69']\n", + "['487', 'Pumpkin Spice Latte', '$3.71']\n", + "['488', 'Coffee', '$1.06']\n", + "['489', 'Pumpkin Spice Latte', '$5.10']\n", + "['490', 'Coffee', '$2.57']\n", + "['491', 'Pumpkin Spice Latte', '$1.43']\n", + "['492', 'Latte', '$2.26']\n", + "['493', 'Coffee', '$3.99']\n", + "['494', 'Pumpkin Spice Latte', '$5.54']\n", + "['495', 'Latte', '$1.49']\n", + "['496', 'Latte', '$1.28']\n", + "['497', 'Coffee', '$4.57']\n", + "['498', 'Coffee', '$2.26']\n", + "['499', 'Coffee', '$5.72']\n", + "['500', 'Coffee', '$5.66']\n", + "['501', 'Coffee', '$5.45']\n", + "['502', 'Pumpkin Spice Latte', '$3.35']\n", + "['503', 'Coffee', '$4.56']\n", + "['504', 'Pumpkin Spice Latte', '$5.14']\n", + "['505', 'Pumpkin Spice Latte', '$1.31']\n", + "['506', 'Latte', '$2.83']\n", + "['507', 'Latte', '$3.13']\n", + "['508', 'Coffee', '$1.78']\n", + "['509', 'Coffee', '$3.72']\n", + "['510', 'Pumpkin Spice Latte', '$4.85']\n", + "['511', 'Pumpkin Spice Latte', '$2.86']\n", + "['512', 'Pumpkin Spice Latte', '$1.48']\n", + "['513', 'Pumpkin Spice Latte', '$1.43']\n", + "['514', 'Pumpkin Spice Latte', '$1.01']\n", + "['515', 'Pumpkin Spice Latte', '$2.09']\n", + "['516', 'Coffee', '$5.02']\n", + "['517', 'Coffee', '$1.97']\n", + "['518', 'Coffee', '$3.90']\n", + "['519', 'Coffee', '$1.40']\n", + "['520', 'Latte', '$2.03']\n", + "['521', 'Latte', '$4.49']\n", + "['522', 'Latte', '$1.42']\n", + "['523', 'Pumpkin Spice Latte', '$4.29']\n", + "['524', 'Pumpkin Spice Latte', '$3.07']\n", + "['525', 'Pumpkin Spice Latte', '$4.22']\n", + "['526', 'Latte', '$4.62']\n", + "['527', 'Coffee', '$4.29']\n", + "['528', 'Pumpkin Spice Latte', '$4.34']\n", + "['529', 'Coffee', '$3.56']\n", + "['530', 'Latte', '$1.98']\n", + "['531', 'Latte', '$4.05']\n", + "['532', 'Latte', '$3.19']\n", + "['533', 'Coffee', '$5.14']\n", + "['534', 'Latte', '$2.70']\n", + "['535', 'Pumpkin Spice Latte', '$2.28']\n", + "['536', 'Coffee', '$5.12']\n", + "['537', 'Latte', '$1.55']\n", + "['538', 'Coffee', '$2.88']\n", + "['539', 'Latte', '$1.62']\n", + "['540', 'Pumpkin Spice Latte', '$1.75']\n", + "['541', 'Coffee', '$4.33']\n", + "['542', 'Latte', '$5.96']\n", + "['543', 'Latte', '$4.02']\n", + "['544', 'Coffee', '$3.51']\n", + "['545', 'Pumpkin Spice Latte', '$3.59']\n", + "['546', 'Pumpkin Spice Latte', '$2.54']\n", + "['547', 'Pumpkin Spice Latte', '$5.63']\n", + "['548', 'Coffee', '$2.32']\n", + "['549', 'Latte', '$4.14']\n", + "['550', 'Pumpkin Spice Latte', '$5.43']\n", + "['551', 'Coffee', '$4.06']\n", + "['552', 'Coffee', '$3.90']\n", + "['553', 'Pumpkin Spice Latte', '$1.16']\n", + "['554', 'Pumpkin Spice Latte', '$3.39']\n", + "['555', 'Pumpkin Spice Latte', '$1.23']\n", + "['556', 'Pumpkin Spice Latte', '$3.66']\n", + "['557', 'Latte', '$5.40']\n", + "['558', 'Coffee', '$1.46']\n", + "['559', 'Coffee', '$2.21']\n", + "['560', 'Latte', '$4.90']\n", + "['561', 'Pumpkin Spice Latte', '$2.87']\n", + "['562', 'Latte', '$5.09']\n", + "['563', 'Latte', '$4.11']\n", + "['564', 'Latte', '$5.14']\n", + "['565', 'Latte', '$2.01']\n", + "['566', 'Latte', '$2.36']\n", + "['567', 'Coffee', '$1.54']\n", + "['568', 'Latte', '$4.77']\n", + "['569', 'Latte', '$5.11']\n", + "['570', 'Coffee', '$1.14']\n", + "['571', 'Pumpkin Spice Latte', '$5.01']\n", + "['572', 'Coffee', '$3.12']\n", + "['573', 'Coffee', '$1.21']\n", + "['574', 'Pumpkin Spice Latte', '$3.68']\n", + "['575', 'Coffee', '$3.05']\n", + "['576', 'Coffee', '$4.23']\n", + "['577', 'Coffee', '$1.62']\n", + "['578', 'Pumpkin Spice Latte', '$1.36']\n", + "['579', 'Latte', '$4.65']\n", + "['580', 'Latte', '$2.22']\n", + "['581', 'Pumpkin Spice Latte', '$4.66']\n", + "['582', 'Latte', '$4.00']\n", + "['583', 'Latte', '$3.13']\n", + "['584', 'Coffee', '$3.90']\n", + "['585', 'Coffee', '$3.91']\n", + "['586', 'Latte', '$3.23']\n", + "['587', 'Coffee', '$3.18']\n", + "['588', 'Coffee', '$2.62']\n", + "['589', 'Latte', '$1.74']\n", + "['590', 'Latte', '$2.31']\n", + "['591', 'Coffee', '$1.97']\n", + "['592', 'Latte', '$5.03']\n", + "['593', 'Coffee', '$2.04']\n", + "['594', 'Pumpkin Spice Latte', '$3.33']\n", + "['595', 'Coffee', '$3.86']\n", + "['596', 'Latte', '$4.74']\n", + "['597', 'Pumpkin Spice Latte', '$1.91']\n", + "['598', 'Coffee', '$4.91']\n", + "['599', 'Pumpkin Spice Latte', '$2.30']\n", + "['600', 'Latte', '$1.49']\n", + "['601', 'Pumpkin Spice Latte', '$3.33']\n", + "['602', 'Pumpkin Spice Latte', '$2.58']\n", + "['603', 'Pumpkin Spice Latte', '$4.39']\n", + "['604', 'Coffee', '$3.93']\n", + "['605', 'Pumpkin Spice Latte', '$4.51']\n", + "['606', 'Pumpkin Spice Latte', '$2.76']\n", + "['607', 'Pumpkin Spice Latte', '$3.03']\n", + "['608', 'Coffee', '$2.32']\n", + "['609', 'Coffee', '$2.98']\n", + "['610', 'Latte', '$5.77']\n", + "['611', 'Latte', '$3.58']\n", + "['612', 'Pumpkin Spice Latte', '$1.95']\n", + "['613', 'Coffee', '$4.86']\n", + "['614', 'Pumpkin Spice Latte', '$5.70']\n", + "['615', 'Coffee', '$5.91']\n", + "['616', 'Coffee', '$4.80']\n", + "['617', 'Pumpkin Spice Latte', '$1.79']\n", + "['618', 'Pumpkin Spice Latte', '$5.16']\n", + "['619', 'Pumpkin Spice Latte', '$2.88']\n", + "['620', 'Latte', '$2.01']\n", + "['621', 'Pumpkin Spice Latte', '$5.44']\n", + "['622', 'Latte', '$5.11']\n", + "['623', 'Pumpkin Spice Latte', '$3.51']\n", + "['624', 'Latte', '$5.50']\n", + "['625', 'Latte', '$1.11']\n", + "['626', 'Latte', '$4.72']\n", + "['627', 'Pumpkin Spice Latte', '$2.92']\n", + "['628', 'Pumpkin Spice Latte', '$5.70']\n", + "['629', 'Latte', '$5.64']\n", + "['630', 'Pumpkin Spice Latte', '$1.21']\n", + "['631', 'Coffee', '$5.62']\n", + "['632', 'Pumpkin Spice Latte', '$1.65']\n", + "['633', 'Coffee', '$4.62']\n", + "['634', 'Coffee', '$5.73']\n", + "['635', 'Coffee', '$3.90']\n", + "['636', 'Pumpkin Spice Latte', '$5.68']\n", + "['637', 'Pumpkin Spice Latte', '$1.30']\n", + "['638', 'Pumpkin Spice Latte', '$5.95']\n", + "['639', 'Latte', '$3.09']\n", + "['640', 'Pumpkin Spice Latte', '$2.75']\n", + "['641', 'Latte', '$3.86']\n", + "['642', 'Pumpkin Spice Latte', '$5.58']\n", + "['643', 'Pumpkin Spice Latte', '$3.34']\n", + "['644', 'Latte', '$1.81']\n", + "['645', 'Latte', '$2.42']\n", + "['646', 'Latte', '$3.63']\n", + "['647', 'Coffee', '$5.50']\n", + "['648', 'Coffee', '$4.55']\n", + "['649', 'Pumpkin Spice Latte', '$5.85']\n", + "['650', 'Coffee', '$2.02']\n", + "['651', 'Pumpkin Spice Latte', '$2.52']\n", + "['652', 'Latte', '$2.58']\n", + "['653', 'Pumpkin Spice Latte', '$3.38']\n", + "['654', 'Latte', '$3.27']\n", + "['655', 'Pumpkin Spice Latte', '$3.00']\n", + "['656', 'Coffee', '$2.67']\n", + "['657', 'Pumpkin Spice Latte', '$3.72']\n", + "['658', 'Pumpkin Spice Latte', '$4.28']\n", + "['659', 'Latte', '$1.41']\n", + "['660', 'Pumpkin Spice Latte', '$5.67']\n", + "['661', 'Coffee', '$1.52']\n", + "['662', 'Latte', '$4.97']\n", + "['663', 'Pumpkin Spice Latte', '$4.01']\n", + "['664', 'Pumpkin Spice Latte', '$5.78']\n", + "['665', 'Pumpkin Spice Latte', '$1.67']\n", + "['666', 'Pumpkin Spice Latte', '$2.23']\n", + "['667', 'Latte', '$1.17']\n", + "['668', 'Coffee', '$5.86']\n", + "['669', 'Pumpkin Spice Latte', '$5.56']\n", + "['670', 'Coffee', '$5.91']\n", + "['671', 'Pumpkin Spice Latte', '$4.33']\n", + "['672', 'Coffee', '$5.67']\n", + "['673', 'Latte', '$1.96']\n", + "['674', 'Pumpkin Spice Latte', '$2.47']\n", + "['675', 'Pumpkin Spice Latte', '$1.62']\n", + "['676', 'Latte', '$3.70']\n", + "['677', 'Coffee', '$3.76']\n", + "['678', 'Coffee', '$4.72']\n", + "['679', 'Coffee', '$4.22']\n", + "['680', 'Coffee', '$5.16']\n", + "['681', 'Coffee', '$1.93']\n", + "['682', 'Coffee', '$5.85']\n", + "['683', 'Latte', '$4.20']\n", + "['684', 'Pumpkin Spice Latte', '$1.78']\n", + "['685', 'Pumpkin Spice Latte', '$5.26']\n", + "['686', 'Pumpkin Spice Latte', '$5.06']\n", + "['687', 'Coffee', '$5.96']\n", + "['688', 'Latte', '$1.10']\n", + "['689', 'Coffee', '$4.50']\n", + "['690', 'Coffee', '$1.60']\n", + "['691', 'Pumpkin Spice Latte', '$3.65']\n", + "['692', 'Coffee', '$5.30']\n", + "['693', 'Pumpkin Spice Latte', '$1.02']\n", + "['694', 'Coffee', '$5.33']\n", + "['695', 'Latte', '$3.53']\n", + "['696', 'Pumpkin Spice Latte', '$2.77']\n", + "['697', 'Pumpkin Spice Latte', '$4.46']\n", + "['698', 'Pumpkin Spice Latte', '$5.68']\n", + "['699', 'Coffee', '$2.92']\n", + "['700', 'Coffee', '$4.91']\n", + "['701', 'Pumpkin Spice Latte', '$5.68']\n", + "['702', 'Latte', '$3.11']\n", + "['703', 'Pumpkin Spice Latte', '$3.21']\n", + "['704', 'Pumpkin Spice Latte', '$5.71']\n", + "['705', 'Coffee', '$4.93']\n", + "['706', 'Latte', '$5.63']\n", + "['707', 'Coffee', '$5.40']\n", + "['708', 'Coffee', '$3.76']\n", + "['709', 'Latte', '$4.65']\n", + "['710', 'Coffee', '$1.73']\n", + "['711', 'Coffee', '$3.54']\n", + "['712', 'Coffee', '$1.79']\n", + "['713', 'Latte', '$1.17']\n", + "['714', 'Coffee', '$5.43']\n", + "['715', 'Latte', '$2.39']\n", + "['716', 'Pumpkin Spice Latte', '$5.79']\n", + "['717', 'Coffee', '$3.82']\n", + "['718', 'Latte', '$4.22']\n", + "['719', 'Latte', '$5.18']\n", + "['720', 'Pumpkin Spice Latte', '$4.63']\n", + "['721', 'Coffee', '$4.80']\n", + "['722', 'Latte', '$2.96']\n", + "['723', 'Coffee', '$3.67']\n", + "['724', 'Coffee', '$5.17']\n", + "['725', 'Pumpkin Spice Latte', '$4.42']\n", + "['726', 'Coffee', '$4.67']\n", + "['727', 'Pumpkin Spice Latte', '$2.90']\n", + "['728', 'Pumpkin Spice Latte', '$4.20']\n", + "['729', 'Latte', '$4.77']\n", + "['730', 'Pumpkin Spice Latte', '$3.72']\n", + "['731', 'Coffee', '$1.90']\n", + "['732', 'Pumpkin Spice Latte', '$3.21']\n", + "['733', 'Latte', '$1.36']\n", + "['734', 'Latte', '$4.05']\n", + "['735', 'Coffee', '$1.85']\n", + "['736', 'Pumpkin Spice Latte', '$2.48']\n", + "['737', 'Latte', '$5.45']\n", + "['738', 'Coffee', '$3.31']\n", + "['739', 'Pumpkin Spice Latte', '$3.70']\n", + "['740', 'Latte', '$4.12']\n", + "['741', 'Pumpkin Spice Latte', '$4.96']\n", + "['742', 'Coffee', '$4.22']\n", + "['743', 'Pumpkin Spice Latte', '$4.77']\n", + "['744', 'Pumpkin Spice Latte', '$3.77']\n", + "['745', 'Pumpkin Spice Latte', '$4.82']\n", + "['746', 'Coffee', '$5.38']\n", + "['747', 'Latte', '$3.65']\n", + "['748', 'Latte', '$1.40']\n", + "['749', 'Coffee', '$3.41']\n", + "['750', 'Pumpkin Spice Latte', '$1.28']\n", + "['751', 'Coffee', '$1.66']\n", + "['752', 'Pumpkin Spice Latte', '$4.33']\n", + "['753', 'Latte', '$3.85']\n", + "['754', 'Pumpkin Spice Latte', '$1.73']\n", + "['755', 'Coffee', '$5.96']\n", + "['756', 'Latte', '$4.94']\n", + "['757', 'Coffee', '$2.42']\n", + "['758', 'Latte', '$2.35']\n", + "['759', 'Pumpkin Spice Latte', '$4.07']\n", + "['760', 'Pumpkin Spice Latte', '$1.51']\n", + "['761', 'Pumpkin Spice Latte', '$1.34']\n", + "['762', 'Latte', '$3.76']\n", + "['763', 'Latte', '$5.13']\n", + "['764', 'Coffee', '$3.68']\n", + "['765', 'Pumpkin Spice Latte', '$5.32']\n", + "['766', 'Coffee', '$2.29']\n", + "['767', 'Pumpkin Spice Latte', '$3.60']\n", + "['768', 'Coffee', '$1.89']\n", + "['769', 'Coffee', '$3.86']\n", + "['770', 'Latte', '$1.57']\n", + "['771', 'Coffee', '$4.43']\n", + "['772', 'Coffee', '$5.41']\n", + "['773', 'Coffee', '$4.47']\n", + "['774', 'Latte', '$2.85']\n", + "['775', 'Latte', '$3.32']\n", + "['776', 'Latte', '$5.55']\n", + "['777', 'Coffee', '$5.84']\n", + "['778', 'Latte', '$4.90']\n", + "['779', 'Pumpkin Spice Latte', '$4.58']\n", + "['780', 'Pumpkin Spice Latte', '$3.12']\n", + "['781', 'Coffee', '$2.99']\n", + "['782', 'Coffee', '$1.65']\n", + "['783', 'Latte', '$1.93']\n", + "['784', 'Coffee', '$2.51']\n", + "['785', 'Coffee', '$1.45']\n", + "['786', 'Pumpkin Spice Latte', '$5.38']\n", + "['787', 'Coffee', '$1.19']\n", + "['788', 'Coffee', '$3.50']\n", + "['789', 'Coffee', '$2.80']\n", + "['790', 'Latte', '$1.64']\n", + "['791', 'Pumpkin Spice Latte', '$5.04']\n", + "['792', 'Latte', '$4.92']\n", + "['793', 'Coffee', '$1.60']\n", + "['794', 'Pumpkin Spice Latte', '$1.32']\n", + "['795', 'Pumpkin Spice Latte', '$5.37']\n", + "['796', 'Pumpkin Spice Latte', '$5.19']\n", + "['797', 'Coffee', '$5.81']\n", + "['798', 'Latte', '$4.43']\n", + "['799', 'Coffee', '$2.02']\n", + "['800', 'Pumpkin Spice Latte', '$2.21']\n", + "['801', 'Latte', '$2.41']\n", + "['802', 'Pumpkin Spice Latte', '$1.86']\n", + "['803', 'Latte', '$4.43']\n", + "['804', 'Latte', '$1.32']\n", + "['805', 'Pumpkin Spice Latte', '$2.37']\n", + "['806', 'Latte', '$1.49']\n", + "['807', 'Pumpkin Spice Latte', '$5.46']\n", + "['808', 'Latte', '$1.92']\n", + "['809', 'Latte', '$4.34']\n", + "['810', 'Pumpkin Spice Latte', '$3.52']\n", + "['811', 'Pumpkin Spice Latte', '$1.70']\n", + "['812', 'Coffee', '$4.70']\n", + "['813', 'Coffee', '$2.36']\n", + "['814', 'Latte', '$5.90']\n", + "['815', 'Latte', '$1.42']\n", + "['816', 'Coffee', '$5.25']\n", + "['817', 'Coffee', '$4.38']\n", + "['818', 'Coffee', '$1.49']\n", + "['819', 'Pumpkin Spice Latte', '$4.64']\n", + "['820', 'Pumpkin Spice Latte', '$3.93']\n", + "['821', 'Pumpkin Spice Latte', '$1.65']\n", + "['822', 'Latte', '$4.28']\n", + "['823', 'Latte', '$1.82']\n", + "['824', 'Latte', '$3.90']\n", + "['825', 'Latte', '$2.47']\n", + "['826', 'Latte', '$3.75']\n", + "['827', 'Coffee', '$4.10']\n", + "['828', 'Pumpkin Spice Latte', '$1.87']\n", + "['829', 'Pumpkin Spice Latte', '$4.45']\n", + "['830', 'Pumpkin Spice Latte', '$1.35']\n", + "['831', 'Coffee', '$4.82']\n", + "['832', 'Latte', '$3.16']\n", + "['833', 'Coffee', '$2.10']\n", + "['834', 'Coffee', '$5.57']\n", + "['835', 'Latte', '$3.57']\n", + "['836', 'Coffee', '$3.67']\n", + "['837', 'Pumpkin Spice Latte', '$1.76']\n", + "['838', 'Latte', '$3.44']\n", + "['839', 'Pumpkin Spice Latte', '$3.61']\n", + "['840', 'Pumpkin Spice Latte', '$3.24']\n", + "['841', 'Pumpkin Spice Latte', '$5.45']\n", + "['842', 'Pumpkin Spice Latte', '$4.28']\n", + "['843', 'Latte', '$2.54']\n", + "['844', 'Coffee', '$2.82']\n", + "['845', 'Latte', '$2.63']\n", + "['846', 'Latte', '$5.78']\n", + "['847', 'Pumpkin Spice Latte', '$1.24']\n", + "['848', 'Pumpkin Spice Latte', '$1.04']\n", + "['849', 'Pumpkin Spice Latte', '$3.11']\n", + "['850', 'Pumpkin Spice Latte', '$5.16']\n", + "['851', 'Latte', '$5.92']\n", + "['852', 'Latte', '$4.38']\n", + "['853', 'Latte', '$2.41']\n", + "['854', 'Pumpkin Spice Latte', '$2.00']\n", + "['855', 'Latte', '$2.58']\n", + "['856', 'Latte', '$3.49']\n", + "['857', 'Pumpkin Spice Latte', '$1.65']\n", + "['858', 'Latte', '$4.71']\n", + "['859', 'Coffee', '$4.37']\n", + "['860', 'Latte', '$1.23']\n", + "['861', 'Latte', '$5.43']\n", + "['862', 'Latte', '$3.58']\n", + "['863', 'Latte', '$5.99']\n", + "['864', 'Latte', '$1.79']\n", + "['865', 'Coffee', '$1.88']\n", + "['866', 'Pumpkin Spice Latte', '$1.22']\n", + "['867', 'Pumpkin Spice Latte', '$4.98']\n", + "['868', 'Pumpkin Spice Latte', '$2.95']\n", + "['869', 'Latte', '$5.41']\n", + "['870', 'Coffee', '$2.14']\n", + "['871', 'Latte', '$5.76']\n", + "['872', 'Coffee', '$3.74']\n", + "['873', 'Pumpkin Spice Latte', '$3.41']\n", + "['874', 'Latte', '$1.47']\n", + "['875', 'Pumpkin Spice Latte', '$3.53']\n", + "['876', 'Coffee', '$4.36']\n", + "['877', 'Latte', '$5.18']\n", + "['878', 'Pumpkin Spice Latte', '$4.63']\n", + "['879', 'Coffee', '$5.83']\n", + "['880', 'Pumpkin Spice Latte', '$4.59']\n", + "['881', 'Latte', '$4.90']\n", + "['882', 'Coffee', '$2.43']\n", + "['883', 'Coffee', '$4.46']\n", + "['884', 'Latte', '$2.50']\n", + "['885', 'Coffee', '$3.21']\n", + "['886', 'Coffee', '$1.97']\n", + "['887', 'Pumpkin Spice Latte', '$1.01']\n", + "['888', 'Latte', '$5.90']\n", + "['889', 'Coffee', '$1.57']\n", + "['890', 'Pumpkin Spice Latte', '$2.77']\n", + "['891', 'Coffee', '$1.07']\n", + "['892', 'Coffee', '$2.23']\n", + "['893', 'Latte', '$3.85']\n", + "['894', 'Latte', '$2.98']\n", + "['895', 'Pumpkin Spice Latte', '$4.22']\n", + "['896', 'Pumpkin Spice Latte', '$5.14']\n", + "['897', 'Coffee', '$4.90']\n", + "['898', 'Coffee', '$3.43']\n", + "['899', 'Coffee', '$5.06']\n", + "['900', 'Coffee', '$4.12']\n", + "['901', 'Latte', '$3.94']\n", + "['902', 'Coffee', '$4.78']\n", + "['903', 'Pumpkin Spice Latte', '$3.80']\n", + "['904', 'Latte', '$5.65']\n", + "['905', 'Coffee', '$4.68']\n", + "['906', 'Latte', '$1.03']\n", + "['907', 'Pumpkin Spice Latte', '$2.74']\n", + "['908', 'Coffee', '$2.55']\n", + "['909', 'Latte', '$2.45']\n", + "['910', 'Pumpkin Spice Latte', '$1.23']\n", + "['911', 'Coffee', '$3.92']\n", + "['912', 'Latte', '$2.92']\n", + "['913', 'Coffee', '$2.98']\n", + "['914', 'Latte', '$4.38']\n", + "['915', 'Pumpkin Spice Latte', '$3.42']\n", + "['916', 'Latte', '$5.78']\n", + "['917', 'Coffee', '$1.96']\n", + "['918', 'Pumpkin Spice Latte', '$2.37']\n", + "['919', 'Coffee', '$4.89']\n", + "['920', 'Coffee', '$4.19']\n", + "['921', 'Coffee', '$4.40']\n", + "['922', 'Latte', '$1.60']\n", + "['923', 'Pumpkin Spice Latte', '$5.64']\n", + "['924', 'Latte', '$1.60']\n", + "['925', 'Latte', '$4.55']\n", + "['926', 'Coffee', '$2.28']\n", + "['927', 'Coffee', '$2.54']\n", + "['928', 'Pumpkin Spice Latte', '$4.38']\n", + "['929', 'Pumpkin Spice Latte', '$2.28']\n", + "['930', 'Latte', '$5.39']\n", + "['931', 'Latte', '$2.70']\n", + "['932', 'Latte', '$1.92']\n", + "['933', 'Pumpkin Spice Latte', '$2.73']\n", + "['934', 'Pumpkin Spice Latte', '$5.61']\n", + "['935', 'Latte', '$4.76']\n", + "['936', 'Coffee', '$4.03']\n", + "['937', 'Coffee', '$5.05']\n", + "['938', 'Coffee', '$1.71']\n", + "['939', 'Coffee', '$1.59']\n", + "['940', 'Coffee', '$5.24']\n", + "['941', 'Latte', '$4.63']\n", + "['942', 'Coffee', '$1.13']\n", + "['943', 'Latte', '$2.78']\n", + "['944', 'Latte', '$3.98']\n", + "['945', 'Pumpkin Spice Latte', '$2.12']\n", + "['946', 'Coffee', '$3.45']\n", + "['947', 'Coffee', '$2.73']\n", + "['948', 'Pumpkin Spice Latte', '$2.34']\n", + "['949', 'Coffee', '$2.51']\n", + "['950', 'Pumpkin Spice Latte', '$5.57']\n", + "['951', 'Pumpkin Spice Latte', '$3.94']\n", + "['952', 'Coffee', '$4.41']\n", + "['953', 'Latte', '$2.11']\n", + "['954', 'Coffee', '$3.63']\n", + "['955', 'Latte', '$1.91']\n", + "['956', 'Latte', '$5.74']\n", + "['957', 'Pumpkin Spice Latte', '$1.51']\n", + "['958', 'Coffee', '$3.37']\n", + "['959', 'Coffee', '$3.13']\n", + "['960', 'Coffee', '$5.67']\n", + "['961', 'Latte', '$4.75']\n", + "['962', 'Coffee', '$3.94']\n", + "['963', 'Coffee', '$1.44']\n", + "['964', 'Pumpkin Spice Latte', '$2.19']\n", + "['965', 'Latte', '$4.43']\n", + "['966', 'Latte', '$3.31']\n", + "['967', 'Latte', '$2.36']\n", + "['968', 'Latte', '$3.55']\n", + "['969', 'Pumpkin Spice Latte', '$2.87']\n", + "['970', 'Coffee', '$4.21']\n", + "['971', 'Latte', '$3.07']\n", + "['972', 'Pumpkin Spice Latte', '$4.90']\n", + "['973', 'Coffee', '$1.91']\n", + "['974', 'Latte', '$5.29']\n", + "['975', 'Pumpkin Spice Latte', '$3.77']\n", + "['976', 'Pumpkin Spice Latte', '$1.15']\n", + "['977', 'Latte', '$5.56']\n", + "['978', 'Latte', '$4.45']\n", + "['979', 'Pumpkin Spice Latte', '$4.67']\n", + "['980', 'Latte', '$4.15']\n", + "['981', 'Coffee', '$5.24']\n", + "['982', 'Coffee', '$4.64']\n", + "['983', 'Coffee', '$5.16']\n", + "['984', 'Coffee', '$4.43']\n", + "['985', 'Coffee', '$4.90']\n", + "['986', 'Pumpkin Spice Latte', '$4.66']\n", + "['987', 'Latte', '$4.77']\n", + "['988', 'Latte', '$4.06']\n", + "['989', 'Pumpkin Spice Latte', '$2.07']\n", + "['990', 'Pumpkin Spice Latte', '$1.27']\n", + "['991', 'Coffee', '$3.43']\n", + "['992', 'Pumpkin Spice Latte', '$5.74']\n", + "['993', 'Pumpkin Spice Latte', '$4.13']\n", + "['994', 'Coffee', '$3.62']\n", + "['995', 'Latte', '$4.05']\n", + "['996', 'Latte', '$2.92']\n", + "['997', 'Coffee', '$4.61']\n", + "['998', 'Pumpkin Spice Latte', '$4.28']\n", + "['999', 'Latte', '$2.24']\n", + "['1000', 'Latte', '$4.84']\n" + ] + } + ], + "source": [ + "print(f\"The data has {len(col_names)} columns::\")\n", + "print(col_names)\n", + "print(f\"The data has {len(input_data)} rows:\")\n", + "for row in input_data:\n", + " print(row)" + ] + }, + { + "cell_type": "markdown", + "id": "0da357e6", + "metadata": {}, + "source": [ + "## Step 2: Selecting the Product Column\n", + "\n", + "Our second step is to select the `product` column of the data. We do this using\n", + "standard python functions and a list comprehension expression, and call this\n", + "operation `query1`. After defining the function we print out the first five\n", + "results." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ea921091", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The product type for transactions 1–5:\n", + "Coffee\n", + "Coffee\n", + "Pumpkin Spice Latte\n", + "Pumpkin Spice Latte\n", + "Latte\n" + ] + } + ], + "source": [ + "def query1(data):\n", + " return [ row[col_names.index(\"product\")] \n", + " for row\n", + " in data \n", + " ]\n", + "\n", + "print(\"The product type for transactions 1–5:\")\n", + "for x in query1(input_data)[:5]:\n", + " print(x)" + ] + }, + { + "cell_type": "markdown", + "id": "8692d934", + "metadata": {}, + "source": [ + "## Step 3: Filter and Count\n", + "\n", + "Now we're ready to count the number of pumpkin spice lattes. The results tell us\n", + "(without differential privacy) how many pumpkin spice lattes were sold. To\n", + "create this query, we reuse `query1` from before that selects the `\"product\"`\n", + "column, and count up the elements that are pumpkin spice lattes. We call this\n", + "operation `query2`, and after defining the function we print out the results." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ffbb0ebc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The number of 'Pumpkin Spice Latte' product types:\n", + "331\n" + ] + } + ], + "source": [ + "def query2(data): \n", + " return sum([ 1 if product == \"Pumpkin Spice Latte\" else 0 \n", + " for product \n", + " in query1(data) ])\n", + " \n", + "print(\"The number of 'Pumpkin Spice Latte' product types:\")\n", + "print(query2(input_data))" + ] + }, + { + "cell_type": "markdown", + "id": "99e53a11", + "metadata": {}, + "source": [ + "## Step 4: Laplace Mechanism\n", + "\n", + "The last step is to apply the Laplace mechanism in order to achieve differential\n", + "privacy. To apply the Laplace mechanism we sample noise from the Laplace\n", + "distribution and add it to the query count (the\n", + "output of `query2`).\n", + "\n", + "In order to achieve the correct quantity of privacy we need to use the correct\n", + "scale parameter when sampling from the Laplace distribution. This correct scale\n", + "parameter depends on the *sensitivity* of the query whose result we are adding\n", + "noise to (`query2` in this case), as well as the desired *privacy budget*\n", + "$\\epsilon$ we would like to obtain. The formula for the Laplace scale parameter\n", + "is $sensitivity / \\epsilon$. In our case the query has $sensitivity = 1$, and we\n", + "have decided to pick $\\epsilon = 0.1$ for the privacy budget, so we use a scale\n", + "of $1/0.1 = 10$ for the noise parameter.\n", + "\n", + "We call this operation `query3`, and after defining the function we print out\n", + "the results. Note that the result is based on sampling random noise, so the\n", + "output will be different every time the function is called." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5a24c5b7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(Running multiple times will give different results each time.)\n", + "A differentially private count of Pumpkin Spice Lattes in the dataset:\n", + "323.57855412344014\n" + ] + } + ], + "source": [ + "sensitivity = 1\n", + "epsilon = 0.1\n", + "noise_scale = sensitivity / epsilon\n", + "\n", + "def query3(data):\n", + " return query2(data) + numpy.random.laplace(0, noise_scale)\n", + "\n", + "print(\"(Running multiple times will give different results each time.)\")\n", + "print(\"A differentially private count of Pumpkin Spice Lattes in the dataset:\")\n", + "print(query3(input_data))" + ] + }, + { + "cell_type": "markdown", + "id": "87058ea1", + "metadata": {}, + "source": [ + "## Accuracy\n", + "\n", + "Let's try to get a sense of how *accurate* the differentially private results\n", + "are. We expect that when called multiple times, the average of all results\n", + "(i.e., the mean) will be equal to the true count. And we are interested on how\n", + "spread out the results are in general (i.e., the variance). Let's test this\n", + "empirically by performing one-thousand trials and computing the mean and\n", + "variance." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "3db99caf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(Running multiple times will give different results.)\n", + "Empirical mean and variance for 1000 samples:\n", + "mean: 330.4299119957137\n", + " var: 179.0723055181324\n" + ] + } + ], + "source": [ + "trials = [ query3(input_data) \n", + " for _ \n", + " in range(0, 1000) \n", + " ]\n", + "\n", + "print(\"(Running multiple times will give different results.)\")\n", + "print(\"Empirical mean and variance for 1000 samples:\")\n", + "print(f\"mean: {numpy.mean(trials)}\")\n", + "print(f\" var: {numpy.var(trials)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "67dcf7f0", + "metadata": {}, + "source": [ + "In fact, because we can characterize the exact distribution of values the\n", + "results are drawn from—the Laplace distribution—we can also just calculate the\n", + "mean and variance without doing any trials. The formula for variance is\n", + "$variance = 2 * scale^2$. We call these the *analytic* mean and variance because\n", + "they are calculated directly, as opposed to the *empirical* mean and variance,\n", + "which are estimated based on random trials. Notice that the analytic mean and\n", + "variance are very likely to be slightly different than the empirical ones. As we\n", + "perform more and more random trials to compute the empirical mean, we expect\n", + "them to come closer and closer together." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9b506d50", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Analytic mean and variance:\n", + "mean: 331\n", + "var: 200.0\n" + ] + } + ], + "source": [ + "analytic_mean = query2(input_data)\n", + "analytic_variance = 2 * noise_scale ** 2\n", + "\n", + "print(\"Analytic mean and variance:\")\n", + "print(f\"mean: {analytic_mean}\")\n", + "print(f\"var: {analytic_variance}\")" + ] + }, + { + "cell_type": "markdown", + "id": "881607e9", + "metadata": {}, + "source": [ + "Now we know the variance of the result is 200. However, interpreting this number\n", + "(the distribution variance) isn't very intuitive. A common method for making\n", + "distribution variance more interpretable is to convert the quantity to a *95%\n", + "confidence interval* (95% CI) represented as a numeric range (lower bound and\n", + "upper bound). 95% of the time, samples from the distribution will fall within\n", + "the 95% CI range, and the 95% CI for a dataset with $N$ elements is computed\n", + "using the formula $CI = 1.96 * \\frac{\\sqrt{variance}}{\\sqrt{N}}$.\n", + "\n", + "We would like to visualize the 95% CI range for a number of different choices\n", + "for $\\epsilon$. To do this we will create a line and error bar plot with choices\n", + "for $\\epsilon$ on the $x$ axis, the mean of `query3` (for that choice of\n", + "$\\epsilon$) on the $y$ axis, and the 95% CI as error bars. The mean of `query3`\n", + "is independent from the choice of $\\epsilon$, so the line will be a straight\n", + "horizontal line, but the 95% CI (calculated from the variance) will change for\n", + "different choices of $\\epsilon$." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "7607857b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(330.0, 332.0)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "def analytic_variance_from_epsilon(epsilon):\n", + " sensitivity = 1\n", + " noise_scale = sensitivity / epsilon\n", + " return 2 * noise_scale ** 2\n", + "\n", + "def ci_from_variance(variance, samples):\n", + " return 1.96 * numpy.sqrt(variance) / numpy.sqrt(samples)\n", + "\n", + "# plot 200 epsilon values from 0.1 to 2.0 on x-axis\n", + "epsilons = numpy.linspace(0.1, 2.0, 200)\n", + "\n", + "# for each epsilon (x-axis), plot the analytic mean and associated 95% CI\n", + "plt.errorbar(epsilons,\n", + " [analytic_mean for epsilon in epsilons],\n", + " [ci_from_variance(analytic_variance_from_epsilon(epsilon), \n", + " len(input_data)) \\\n", + " for epsilon in epsilons])\n", + "plt.grid()\n", + "plt.xlabel(\"Privacy parameter ε\")\n", + "plt.ylabel(\"Mean with 95% CI\")\n", + "plt.ylim((330,332))" + ] + }, + { + "cell_type": "markdown", + "id": "3ceefc4b", + "metadata": {}, + "source": [ + "Another nice way to interpret the variance of a distribution is to just\n", + "visualize the distribution itself in a histogram plot. Let's create a histogram\n", + "of `query3` output for 20 different choices of $\\epsilon$. This will create an\n", + "interactive histogram plot where changing the slider will recreate the histogram\n", + "for each value of $\\epsilon$." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8d905991", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f5db54aa9fb54dd4bd8125b0b13eb2b5", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=9, description='i', max=19), Output()), _dom_classes=('widget-interact',…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def many_query3_from_epsilon(epsilon):\n", + " sensitivity = 1\n", + " noise_scale = sensitivity / epsilon\n", + " return numpy.random.laplace(0, noise_scale, size=1000) + query2(input_data)\n", + "\n", + "# run query 3 for epsilon values between 0.01 and 2 \n", + "epsilons = numpy.linspace(0.01, 2, 20)\n", + "query_results = [many_query3_from_epsilon(epsilon) for epsilon in epsilons]\n", + "\n", + "def plot_hist(i):\n", + " plt.hist(query_results[i], 100, range=(analytic_mean-50, analytic_mean+50))\n", + " plt.title(f\"ε = {epsilons[i]:.2f}\")\n", + " plt.xlabel(\"count of pumpkin spice lattes\")\n", + " plt.ylabel(\"probability density\")\n", + " plt.ylim((0, 400))\n", + "\n", + "# construct an interactive widget showing the histogram for each epsilon\n", + "ipywidgets.interact(plot_hist, i=(0, len(epsilons)-1));" + ] + }, + { + "cell_type": "markdown", + "id": "5c020e97", + "metadata": {}, + "source": [ + "Let's explore one final way to interpret the distribution variance as it relates\n", + "to choices of epsilon. There is another setting where distribution variance\n", + "appears, and that is from dataset *subsampling*. The idea is to compare the\n", + "variance of the result when computed on the entire dataset to the variance of\n", + "the result when computed on a random subsample of the dataset. For this\n", + "experiment we will run `query2`, the raw count of pumpkin spice lattes, and\n", + "measure the variance of the result based on the size of subsample." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9993d4b5", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "def subsample_variance(data, ratio):\n", + " # convert the ratio into a subsample size\n", + " size = round(len(data)*ratio)\n", + " trials = [ # run query2 on a subsample of the dataset\n", + " query2(random.sample(data, size))\n", + " # rescale the result by the size of the subsample\n", + " * len(data) / size\n", + " for _ \n", + " # do this 1000 times\n", + " in range(0, 1000) \n", + " ]\n", + " # compute the variance of all subsample trials\n", + " return numpy.var(trials)\n", + "\n", + "subsample_sizes = numpy.linspace(0.01, 0.99, 30)\n", + "plt.errorbar(subsample_sizes,\n", + " [analytic_mean for _ in subsample_sizes],\n", + " [ci_from_variance(subsample_variance(input_data, size), len(input_data) * size) \\\n", + " for size in subsample_sizes])\n", + "plt.grid()\n", + "plt.xlabel(\"Subsample size (fraction of data)\")\n", + "plt.ylabel(\"Mean with 95% C.I\")\n", + "plt.ylim((330,332));" + ] + }, + { + "cell_type": "markdown", + "id": "39789ead", + "metadata": {}, + "source": [ + "This plot shows that running `query2` (the raw query) on a subsample introduces\n", + "noise into the result, and we can visualize this variance as a 95% CI. In a\n", + "previous plot we visualized in a similar way running `query3` (the\n", + "differentially private query) which also introduces noise into the result. Our\n", + "final question is then: given that subsampling and differential privacy both\n", + "introduce noise into the result, which choices of subsample size (for `query2`)\n", + "are equivalent to which choices of $epsilon$ (for `query3`)? Let's make a plot\n", + "that relates these two values, and for different sizes of the original dataset.\n", + "\n", + "(Creating the next plot takes more than a few seconds to generate.)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "dbcbd318", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "def epsilon_from_variance(variance):\n", + " sensitivity = 1\n", + " return sensitivity / numpy.sqrt(variance / 2)\n", + "\n", + "input_data_large = sum([input_data for _ in range(0, 5)], [])\n", + "\n", + "# Create a plot with:\n", + "# - 10 points between 0.01 and 0.10\n", + "# - 10 points between 0.10 and 0.9\n", + "# - 10 points between 0.9 and 0.99\n", + "subsample_sizes = numpy.concatenate([numpy.linspace(0.01, 0.10, 10),\n", + " numpy.linspace(0.10, 0.9, 10),\n", + " numpy.linspace(0.9, 0.99, 10)])\n", + "\n", + "def graph_size(size):\n", + " ys = [epsilon_from_variance(subsample_variance(input_data_large[0:(size-1)], x)) \\\n", + " for x in subsample_sizes]\n", + " plt.plot(subsample_sizes, ys, label=f\"Data size={size}\")\n", + "\n", + "graph_size(100)\n", + "graph_size(1000)\n", + "graph_size(5000)\n", + "\n", + "plt.title(\"subsample size vs epsilon with equal variance\")\n", + "plt.xlabel(\"Subsample size (fraction of data)\")\n", + "plt.ylabel(\"Privacy parameter ε\")\n", + "plt.ylim((0, 2))\n", + "plt.grid()\n", + "plt.legend();" + ] + }, + { + "cell_type": "markdown", + "id": "c0246fd2", + "metadata": {}, + "source": [ + "As you can see, for the small (size = 100) dataset in the experiment, an\n", + "$\\epsilon$ value of 1 introduces the same amount of noise as taking a subsample\n", + "of 90% of the dataset. For the large (size = 5000) dataset in the experiment, an\n", + "$\\epsilon$ value of 1 introduces the same amount of noise as dropping a\n", + "negligibly small number of rows (just 1 or 2) from the dataset.\n", + "\n", + "Note that this experiment is likely *highly* dependent on the specific query\n", + "*and* the dataset in question, which in this case is just a simple counting\n", + "query performed over naively generated synthetic data. For this reason, these\n", + "results should *not* be interpreted as relating distribution variance due to\n", + "$\\epsilon$ and subsample size choice in general." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/HistogramBias.ipynb b/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/HistogramBias.ipynb new file mode 100644 index 0000000..bcd1639 --- /dev/null +++ b/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/HistogramBias.ipynb @@ -0,0 +1,1018 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b7967df1", + "metadata": {}, + "source": [ + "# Histograms, Differential Privacy and Bias\n", + "\n", + "This Python notebook shows how differential privacy can magnify bias in simple\n", + "data queries like histograms. The notebook covers reading in data from a file,\n", + "preparing the data, computing a histogram, and observing issues with bias.\n", + "\n", + "As a running example, let's generate a simple histogram of subpopulations in the\n", + "Diverse Communities Data Excerpts dataset curated by NIST and drawn from the US\n", + "Census Bureau's American Communities Survey (ACS).\n", + "\n", + "## Step 1: Loading the Data\n", + "\n", + "Before we start writing code, let's import a few third-party Pyhon packages. We\n", + "will use `pandas` for creating and manipulation data frames, `numpy` for basic\n", + "operations over vectorized datatypes, and `matplotlib` for visualizing results." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f42a514d", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "id": "59df6096", + "metadata": {}, + "source": [ + "Our first step is to load the data. To do this we read the CSV file into a\n", + "dataframe object using `pandas`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "73b5e96b", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv('https://media.githubusercontent.com/media/usnistgov/SDNist/main/nist%20diverse%20communities%20data%20excerpts/massachusetts/ma2019.csv')" + ] + }, + { + "cell_type": "markdown", + "id": "12c8a842", + "metadata": {}, + "source": [ + "## Step 2: Preparing the Data\n", + "\n", + "Let's display the data frame to get a sense for what the data looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7c5a4fff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PUMAAGEPSEXMSPHISPRAC1PNOCNPFHOUSING_TYPEOWN_RENT...PINCPPINCP_DECILEPOVPIPDVETDREMDPHYDEYEDEARPWGTPWGTP
025-00503181601NN30...5000.01NN2222720
125-00703212601NN30...0.00NN222260
225-00503222606NN30...18000.03NN2222800
325-01300581601NN20...0.00NN1222570
425-00703182601NN30...3300.01NN2222240
..................................................................
762925-0130081N012311...NN501N22224541
763025-00703141N011311...NN501N2222115114
763125-0050331N062411...NN347NNN226975
763225-0050312N062411...NN347NNN226475
763325-0280001N011311...NN365NNN22107145
\n", + "

7634 rows × 24 columns

\n", + "
" + ], + "text/plain": [ + " PUMA AGEP SEX MSP HISP RAC1P NOC NPF HOUSING_TYPE OWN_RENT \\\n", + "0 25-00503 18 1 6 0 1 N N 3 0 \n", + "1 25-00703 21 2 6 0 1 N N 3 0 \n", + "2 25-00503 22 2 6 0 6 N N 3 0 \n", + "3 25-01300 58 1 6 0 1 N N 2 0 \n", + "4 25-00703 18 2 6 0 1 N N 3 0 \n", + "... ... ... ... .. ... ... .. .. ... ... \n", + "7629 25-01300 8 1 N 0 1 2 3 1 1 \n", + "7630 25-00703 14 1 N 0 1 1 3 1 1 \n", + "7631 25-00503 3 1 N 0 6 2 4 1 1 \n", + "7632 25-00503 1 2 N 0 6 2 4 1 1 \n", + "7633 25-02800 0 1 N 0 1 1 3 1 1 \n", + "\n", + " ... PINCP PINCP_DECILE POVPIP DVET DREM DPHY DEYE DEAR PWGTP WGTP \n", + "0 ... 5000.0 1 N N 2 2 2 2 72 0 \n", + "1 ... 0.0 0 N N 2 2 2 2 6 0 \n", + "2 ... 18000.0 3 N N 2 2 2 2 80 0 \n", + "3 ... 0.0 0 N N 1 2 2 2 57 0 \n", + "4 ... 3300.0 1 N N 2 2 2 2 24 0 \n", + "... ... ... ... ... ... ... ... ... ... ... ... \n", + "7629 ... N N 501 N 2 2 2 2 45 41 \n", + "7630 ... N N 501 N 2 2 2 2 115 114 \n", + "7631 ... N N 347 N N N 2 2 69 75 \n", + "7632 ... N N 347 N N N 2 2 64 75 \n", + "7633 ... N N 365 N N N 2 2 107 145 \n", + "\n", + "[7634 rows x 24 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(df)" + ] + }, + { + "cell_type": "markdown", + "id": "e2b2974b", + "metadata": {}, + "source": [ + "As you can see, the data uses numeric codes for various attributes. We'd like to\n", + "create histogram plot of race category subpopulations, encoded as the `RAC1P`\n", + "column in the data. To make the results easier interpret, lets first convert\n", + "those numeric codes to textual descriptions." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cf6713ae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PUMAAGEPSEXMSPHISPRAC1PNOCNPFHOUSING_TYPEOWN_RENT...PINCPPINCP_DECILEPOVPIPDVETDREMDPHYDEYEDEARPWGTPWGTP
025-0050318160WhiteNN30...5000.01NN2222720
125-0070321260WhiteNN30...0.00NN222260
225-0050322260AsianNN30...18000.03NN2222800
325-0130058160WhiteNN20...0.00NN1222570
425-0070318260WhiteNN30...3300.01NN2222240
..................................................................
762925-0130081N0White2311...NN501N22224541
763025-00703141N0White1311...NN501N2222115114
763125-0050331N0Asian2411...NN347NNN226975
763225-0050312N0Asian2411...NN347NNN226475
763325-0280001N0White1311...NN365NNN22107145
\n", + "

7634 rows × 24 columns

\n", + "
" + ], + "text/plain": [ + " PUMA AGEP SEX MSP HISP RAC1P NOC NPF HOUSING_TYPE OWN_RENT \\\n", + "0 25-00503 18 1 6 0 White N N 3 0 \n", + "1 25-00703 21 2 6 0 White N N 3 0 \n", + "2 25-00503 22 2 6 0 Asian N N 3 0 \n", + "3 25-01300 58 1 6 0 White N N 2 0 \n", + "4 25-00703 18 2 6 0 White N N 3 0 \n", + "... ... ... ... .. ... ... .. .. ... ... \n", + "7629 25-01300 8 1 N 0 White 2 3 1 1 \n", + "7630 25-00703 14 1 N 0 White 1 3 1 1 \n", + "7631 25-00503 3 1 N 0 Asian 2 4 1 1 \n", + "7632 25-00503 1 2 N 0 Asian 2 4 1 1 \n", + "7633 25-02800 0 1 N 0 White 1 3 1 1 \n", + "\n", + " ... PINCP PINCP_DECILE POVPIP DVET DREM DPHY DEYE DEAR PWGTP WGTP \n", + "0 ... 5000.0 1 N N 2 2 2 2 72 0 \n", + "1 ... 0.0 0 N N 2 2 2 2 6 0 \n", + "2 ... 18000.0 3 N N 2 2 2 2 80 0 \n", + "3 ... 0.0 0 N N 1 2 2 2 57 0 \n", + "4 ... 3300.0 1 N N 2 2 2 2 24 0 \n", + "... ... ... ... ... ... ... ... ... ... ... ... \n", + "7629 ... N N 501 N 2 2 2 2 45 41 \n", + "7630 ... N N 501 N 2 2 2 2 115 114 \n", + "7631 ... N N 347 N N N 2 2 69 75 \n", + "7632 ... N N 347 N N N 2 2 64 75 \n", + "7633 ... N N 365 N N N 2 2 107 145 \n", + "\n", + "[7634 rows x 24 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "dict_race = {\n", + " 1: \"White\",\n", + " 2: \"Black or African American\",\n", + " 3: \"American Indian\",\n", + " 4: \"Alaska Native\",\n", + " 5: \"American Indian and Alaska Native tribes specified; or American Indian or Alaska Native, not specified and no other races\",\n", + " 6: \"Asian\",\n", + " 7: \"Native Hawaiian and Other Pacific Islander alone\",\n", + " 8: \"Some Other Race\",\n", + " 9: \"Two or More Races\"\n", + " }\n", + "df = df.replace({'RAC1P': dict_race})\n", + "display(df)" + ] + }, + { + "cell_type": "markdown", + "id": "d9125bd2", + "metadata": {}, + "source": [ + "As you can see, the values in the `RAC1P` column of the data have been replaced\n", + "with textual descriptions.\n", + "\n", + "## Step 3: Creating the Histogram\n", + "\n", + "Next, let's focus our attention on a smaller region of the dataset. The `PUMA`\n", + "column in the data is an identifier for the geographical area where each person\n", + "is located, so let's focus on just that geographical area, group the\n", + "results by the race attribute, and then count the total number of people in each\n", + "group." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "96ba8978", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "RAC1P\n", + "American Indian 1\n", + "Some Other Race 12\n", + "Black or African American 32\n", + "Two or More Races 52\n", + "Asian 261\n", + "White 1150\n", + "dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "results = df[df['PUMA'] == '25-00503'].groupby('RAC1P').size().sort_values()\n", + "display(results)" + ] + }, + { + "cell_type": "markdown", + "id": "bf988f8c", + "metadata": {}, + "source": [ + "## Step 4: Observing Issues with Bias\n", + "\n", + "To make these query results differentially private we would add noise to each\n", + "subpopulation count with scale inversely proportional to the desired epsilon. To\n", + "characterize the effect this noise will have on the raw counts, let's compute\n", + "the 95% Confidence Interval (CI) for the result. The formula for this is\n", + "$-\\ln(0.05)/\\epsilon$. Let's calculate the 95% CI for $\\epsilon=1$." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "fb968390", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.995732273553991\n" + ] + } + ], + "source": [ + "print(-np.log(0.05)/1)" + ] + }, + { + "cell_type": "markdown", + "id": "9e049ee5", + "metadata": {}, + "source": [ + "Let's plot a histogram with error bars to visualize the distribution generated\n", + "by the differentially private result. In this plot, the histogram bar represents\n", + "the distribution's mean, and the error bars represent the distribution's 95% CI.\n", + "\n", + "Note that this plot will be in *log scale*. This means instead of the axis\n", + "stepping between a linear sequence of values like 10, 20, 30, 40, etc., it will\n", + "instead step between exponentially increasing values like 1, 10, 100, 1000, etc.\n", + "In particular, notice that the error bars all represent the same range of\n", + "values, but the visual presentation of the error bar will look longer for low\n", + "y-axis values and shorter for high y-axis values.\n", + "\n", + "Here is the plot for $\\epsilon = 1$." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7861057b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "epsilon = 1\n", + "err_95 = -np.log(0.05)/epsilon\n", + "\n", + "plt.figure(figsize=(5,6))\n", + "results.plot(\n", + " kind='bar', \n", + " yerr=err_95, \n", + " error_kw=dict(ecolor='red', lw=5, capsize=10, capthick=3))\n", + "plt.xticks(rotation=70)\n", + "plt.yscale('log')\n", + "plt.xlabel('')\n", + "plt.ylabel('Count (log scale)')\n", + "plt.ylim((.5, 1300))\n", + "pass" + ] + }, + { + "cell_type": "markdown", + "id": "38bd4f67", + "metadata": {}, + "source": [ + "As you can see, for minority populations like `American Indian`, the scale of\n", + "the noise (visualized by the 95% CI error bar) could result in a significant\n", + "over-counting or under-counting of that subpopulation with respect to the raw\n", + "counts. By contrast, for majority subpopulations like `White`, the scale of the\n", + "noise is negligible with respect to the raw count.\n", + "\n", + "Let's generate a new plot for $\\epsilon = 10$." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "8def6a41", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "epsilon = 10\n", + "err_95 = -np.log(0.05)/epsilon\n", + "\n", + "plt.figure(figsize=(5,6))\n", + "results.plot(\n", + " kind='bar', \n", + " yerr=err_95, \n", + " error_kw=dict(ecolor='red', lw=5, capsize=10, capthick=3))\n", + "plt.xticks(rotation=70)\n", + "plt.yscale('log')\n", + "plt.xlabel('')\n", + "plt.ylabel('Count (log scale)')\n", + "plt.ylim((.5, 1300))\n", + "pass" + ] + }, + { + "cell_type": "markdown", + "id": "bccada5b", + "metadata": {}, + "source": [ + "As you can see, for higher epsilon values like $\\epsilon = 10$ the magnitude of\n", + "the bias effect is reduced, but still present." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/MachineLearningBias.ipynb b/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/MachineLearningBias.ipynb new file mode 100644 index 0000000..fa26d15 --- /dev/null +++ b/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/MachineLearningBias.ipynb @@ -0,0 +1,686 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1be92077", + "metadata": {}, + "source": [ + "# Machine Learning: Differential Privacy Magnifies Bias\n", + "\n", + "This Python notebook shows how differential privacy can magnify bias\n", + "in machine learning tasks like regression. The notebook covers reading\n", + "in data from a file, preparing the data for training, training the\n", + "classifier using IBM's Diffprivlib library, and observing issues with\n", + "bias.\n", + "\n", + "As a running example, let's train a classifier for the Diverse Communities Data\n", + "Excerpts dataset curated by NIST and drawn from the US Census Bureau's American\n", + "Communities Survey (ACS).\n", + "\n", + "## Step 1: Loading the Data\n", + "\n", + "Before we start writing code, let's import a few third-party Python packages. We\n", + "will use `pandas` for creating and manipulating data frames, `numpy` for basic\n", + "operations over vectorized datatypes, `diffprivlib` for training a classifier\n", + "with differential privacy, and `matplotlib` for visualizing results." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "93c5dd1d", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import diffprivlib.models as dp\n", + "import warnings\n", + "\n", + "warnings.filterwarnings('ignore')" + ] + }, + { + "cell_type": "markdown", + "id": "c8699fcd", + "metadata": {}, + "source": [ + "Our first step is to load the data. To do this we read the CSV file into a\n", + "dataframe object using `pandas`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4ebeaf72", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv('https://media.githubusercontent.com/media/usnistgov/SDNist/main/nist%20diverse%20communities%20data%20excerpts/massachusetts/ma2019.csv')" + ] + }, + { + "cell_type": "markdown", + "id": "3781c29e", + "metadata": {}, + "source": [ + "Let's display the data frame to get a sense for what the data looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f9b987ad", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PUMAAGEPSEXMSPHISPRAC1PNOCNPFHOUSING_TYPEOWN_RENT...PINCPPINCP_DECILEPOVPIPDVETDREMDPHYDEYEDEARPWGTPWGTP
025-00503181601NN30...5000.01NN2222720
125-00703212601NN30...0.00NN222260
225-00503222606NN30...18000.03NN2222800
325-01300581601NN20...0.00NN1222570
425-00703182601NN30...3300.01NN2222240
..................................................................
762925-0130081N012311...NN501N22224541
763025-00703141N011311...NN501N2222115114
763125-0050331N062411...NN347NNN226975
763225-0050312N062411...NN347NNN226475
763325-0280001N011311...NN365NNN22107145
\n", + "

7634 rows × 24 columns

\n", + "
" + ], + "text/plain": [ + " PUMA AGEP SEX MSP HISP RAC1P NOC NPF HOUSING_TYPE OWN_RENT \\\n", + "0 25-00503 18 1 6 0 1 N N 3 0 \n", + "1 25-00703 21 2 6 0 1 N N 3 0 \n", + "2 25-00503 22 2 6 0 6 N N 3 0 \n", + "3 25-01300 58 1 6 0 1 N N 2 0 \n", + "4 25-00703 18 2 6 0 1 N N 3 0 \n", + "... ... ... ... .. ... ... .. .. ... ... \n", + "7629 25-01300 8 1 N 0 1 2 3 1 1 \n", + "7630 25-00703 14 1 N 0 1 1 3 1 1 \n", + "7631 25-00503 3 1 N 0 6 2 4 1 1 \n", + "7632 25-00503 1 2 N 0 6 2 4 1 1 \n", + "7633 25-02800 0 1 N 0 1 1 3 1 1 \n", + "\n", + " ... PINCP PINCP_DECILE POVPIP DVET DREM DPHY DEYE DEAR PWGTP WGTP \n", + "0 ... 5000.0 1 N N 2 2 2 2 72 0 \n", + "1 ... 0.0 0 N N 2 2 2 2 6 0 \n", + "2 ... 18000.0 3 N N 2 2 2 2 80 0 \n", + "3 ... 0.0 0 N N 1 2 2 2 57 0 \n", + "4 ... 3300.0 1 N N 2 2 2 2 24 0 \n", + "... ... ... ... ... ... ... ... ... ... ... ... \n", + "7629 ... N N 501 N 2 2 2 2 45 41 \n", + "7630 ... N N 501 N 2 2 2 2 115 114 \n", + "7631 ... N N 347 N N N 2 2 69 75 \n", + "7632 ... N N 347 N N N 2 2 64 75 \n", + "7633 ... N N 365 N N N 2 2 107 145 \n", + "\n", + "[7634 rows x 24 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(df)" + ] + }, + { + "cell_type": "markdown", + "id": "5ae704ab", + "metadata": {}, + "source": [ + "## Step 2: Preprocess the Data for Training\n", + "\n", + "To train a classifier, we need to separate the dataset into *features* and a\n", + "*label*. A well trained model will be able to predict the *label* for an entry\n", + "in the dataset based on the *features*. We call the feature dataset `X` and the\n", + "label dataset `y` following standard convention.\n", + "\n", + "We have decided to use `'AGEP'` (age), `'SEX'` (sex), `'RAC1P'` (race) and\n", + "`'HOUSING_TYPE'` (single housing unit or group quarters such as dorms, barracks,\n", + "or nursing homes) as our features, and `'OWN_RENT'` (own or rent housing) as the\n", + "label for the model to predict." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "20257449", + "metadata": {}, + "outputs": [], + "source": [ + "X_train = df[['AGEP', 'SEX', 'RAC1P', 'HOUSING_TYPE']].to_numpy()\n", + "y_train = df['OWN_RENT'].to_numpy()" + ] + }, + { + "cell_type": "markdown", + "id": "636d9240", + "metadata": {}, + "source": [ + "To observe bias, we are interested in comparing the accuracy of the\n", + "trained classifier for minority groups to its accuracy for the\n", + "majority group. For this purpose, we will consider the White\n", + "race—encoded as 1—as a majority group, and all other races—Black or\n", + "African American, American Indian, Alaska Native, Asian, Native\n", + "Hawaiian and Other Pacific Islander, some other race, and two or more\n", + "major race groups, encoded as 2–9 respectively—as minority groups." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a6a3d4b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "There are 6658 records in the majority race category.\n", + "There are 976 records in the minority race categories.\n" + ] + } + ], + "source": [ + "majority_races = df['RAC1P'] == 1\n", + "minority_races = df['RAC1P'].isin([2,3,4,5,6,7,8,9])\n", + "print(f'There are {majority_races.sum()} records in the majority race category.')\n", + "print(f'There are {minority_races.sum()} records in the minority race categories.')" + ] + }, + { + "cell_type": "markdown", + "id": "1d73e8f6", + "metadata": {}, + "source": [ + "## Step 3: Train a classifier\n", + "\n", + "To train a classifier we use the Gaussian Naive Bayes (GaussianNB) with\n", + "differential privacy implementation from IBM's `diffprivlib` library. We pick\n", + "the privacy parameter $\\epsilon = 1$, fit the model, and then print out the\n", + "mean accuracy of the model." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f5a9dd80", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.7991878438564317\n" + ] + } + ], + "source": [ + "dp_clf = dp.GaussianNB(epsilon=1.0)\n", + "dp_clf.fit(X_train, y_train)\n", + "print(dp_clf.score(X_train, y_train))" + ] + }, + { + "cell_type": "markdown", + "id": "716f240c", + "metadata": {}, + "source": [ + "## Step 4: Observe Accuracy for Minority Majority Groups\n", + "\n", + "Our primary interest is to compare the accuracy of the model when predicting\n", + "labels for majority groups vs minority groups." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "36d9a88e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The observed accuracy for records with minority races is 0.820967257434665.\n", + "The observed accuracy for records with minority races is 0.6506147540983607.\n" + ] + } + ], + "source": [ + "majority_accuracy = dp_clf.score(X_train[majority_races], y_train[majority_races])\n", + "minority_accuracy = dp_clf.score(X_train[minority_races], y_train[minority_races])\n", + "print(f'The observed accuracy for records with minority races is {majority_accuracy}.')\n", + "print(f'The observed accuracy for records with minority races is {minority_accuracy}.')" + ] + }, + { + "cell_type": "markdown", + "id": "ea737831", + "metadata": {}, + "source": [ + "As you can see, the accuracy is lower for minority groups than for the\n", + "majority group. This effect is due to the fact that the dataset\n", + "contains less data for the minority groups than it does for the\n", + "majority group, and it can be seen even when training without\n", + "differential privacy. However, differential privacy *magnifies* the\n", + "bias we see in the trained classifier, because the noise affects\n", + "smaller groups more than it does larger ones.\n", + "\n", + "## Step 5: Observe Accuracy Difference when Varying Epsilon\n", + "\n", + "To see the impact of differential privacy on the classifier's bias,\n", + "we'll vary the value of $\\epsilon$. We plot the accuracy for majority\n", + "groups vs minority groups on the y-axis, with the $\\epsilon$ used to\n", + "train the model on the x-axis.\n", + "\n", + "(Creating the raw data for the plot takes more than a few seconds to generate.)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e13ceacf", + "metadata": {}, + "outputs": [], + "source": [ + "def experiment(epsilon):\n", + " brs = []\n", + " srs = []\n", + " for _ in range(100):\n", + " dp_clf = dp.GaussianNB(epsilon=epsilon)\n", + " dp_clf.fit(X_train, y_train)\n", + " brs.append(dp_clf.score(X_train[majority_races], y_train[majority_races]))\n", + " srs.append(dp_clf.score(X_train[minority_races], y_train[minority_races]))\n", + " return np.mean(brs), np.std(brs), np.mean(srs), np.std(srs)\n", + "\n", + "xs = np.linspace(0.01, 0.2, 30)\n", + "results = [experiment(epsilon) for epsilon in xs]" + ] + }, + { + "cell_type": "markdown", + "id": "2614e34e", + "metadata": {}, + "source": [ + "Let's plot the results." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9008c84e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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gwYNAIiPj6d379507NiRqVOnsnHjRgC+//57HnjgAQACAgKoWbMm3333HatWraJbt27ExMTw3XffsWvXLlq0aMGuXbsYO3YsX3/9daGF5jzhSXfO70TkZmCOKaywz9kSgURjzK/O49nAeGPModwVROQ9YEER9qmUKksenJl7U35lmc8VFBR05r4nacrTssyhoaH079+fefPmMXPmTFatWpXner1792bBggX89ttvXHLJJdx6663ExMQwcuRIPvvsM6Kjo5k8efKZSV/yYoxhxIgRPPfcc+c9t27dOhYuXMjbb7/NzJkzS1yWwpM2/tHALCBDRE6KyCkRKXRKHmPMQWCviLRxFvUDNomI+4XhG4H4ogatlPIP+ZVl9kTv3r2ZMWMGLpeLpKQkfvrpJ7p3717gNnmVZR41ahTjxo2jW7duREZGFrh98+bNGT9+PP/+978BW3K5YcOGZGVlnSkRDdCvXz/eeustAFwuF8nJyfTr14/Zs2dz+PBhwE4duXv3bo4cOUJOTg4333wzzzzzDKtXry7ye3GuQhO/MaaGMaaKMaaaMSbceezpd42x2HIP67FNO/8HvCAiG5xlfYE/FTd4pVTlll9ZZk/ceOONdOrUiejoaK644gpeeOGFM00w+enUqRMBAQFER0efuYjatWtXwsPDParHDzBmzBh++uknEhIS+Ne//sXFF19Mr169aNv29ynLX331VX744Qc6duxI165d2bRpE+3ateOZZ55hwIABdOrUif79+3PgwAH27dtHnz59iImJYdiwYXl+IygqT8oyX5bXcmPMTyV+dQ9pWWalyk5FLsvsDfv376dPnz5s2bKFKlU8aSTxjdIuy/yY2/1goDu2h84VJQlSKaXKuw8//JAnn3ySl19+uVwn/aLypEjbde6PReQC4BVvBaSUUuXF8OHDGT688hUjLs4hLBHQ74FKKVVBFXrGLyKvAbkXAqpgL9KW/LKyUqrcMsacmbtWlX9F62nvWRu/+1XVbGC6MaZ8F6JQShVbcHAwR48epXbt2pr8KwBjDEePHiU4ONjjbTxJ/LOBdGOMC0BEAkQk1BiTWsw4lVLlWJMmTUhMTCQpKcnXoSgPBQcH06RJE4/X92jkLnAlkDvzVgjwDdCzyNEppcq9wMBAj8oZqIrLk4u7we7TLTr3Q70XklJKKW/yJPGniEiX3Aci0hVI815I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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "maj_avgs = [x[0]*100 for x in results]\n", + "min_avgs = [x[2]*100 for x in results]\n", + "maj_stds = [x[1] for x in results]\n", + "min_stds = [x[3] for x in results]\n", + "plt.plot(xs, maj_avgs, label='Majority Race')\n", + "plt.plot(xs, min_avgs, label='Minority Races')\n", + "\n", + "plt.legend()\n", + "plt.xlabel('Privacy Parameter (ε)')\n", + "plt.ylabel('Accuracy (%)')\n", + "pass" + ] + }, + { + "cell_type": "markdown", + "id": "195807b0", + "metadata": {}, + "source": [ + "As you can see, the disparity between accuracy for the minority and\n", + "majority groups gets worse as $\\epsilon$ gets smaller—because noise\n", + "has more impact on smaller groups, and more noise makes this disparity\n", + "larger. Both results are noisy, due to the noise required by differential\n", + "privacy.\n", + "\n", + "This general effect—where the accuracy for minority groups is lower than for\n", + "majority groups—is actually *always* present, even if we didn't train the model\n", + "with differential privacy. However, what this plot shows is that using\n", + "differential privacy *amplifies* the disadvantage for minority groups. As the\n", + "$\\epsilon$ is increased, the graph shows accuracy improving on average for\n", + "majority groups, but very little improvement on average for minority groups." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/lattes.csv b/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/lattes.csv new file mode 100644 index 0000000..00fd2ca --- /dev/null +++ b/tools/de-identification/NIST-SP-800-226-SupplementalMaterial/lattes.csv @@ -0,0 +1,1000 @@ +1,Coffee,$2.04 +2,Coffee,$1.55 +3,Pumpkin Spice Latte,$5.94 +4,Pumpkin Spice Latte,$3.67 +5,Latte,$4.15 +6,Latte,$3.47 +7,Latte,$1.37 +8,Coffee,$5.50 +9,Latte,$4.54 +10,Latte,$1.32 +11,Pumpkin Spice Latte,$5.89 +12,Latte,$4.42 +13,Latte,$1.08 +14,Latte,$2.74 +15,Latte,$1.53 +16,Coffee,$3.07 +17,Latte,$2.31 +18,Pumpkin Spice Latte,$1.24 +19,Pumpkin Spice Latte,$2.33 +20,Coffee,$5.24 +21,Latte,$3.08 +22,Latte,$4.73 +23,Pumpkin Spice Latte,$3.53 +24,Pumpkin Spice Latte,$4.46 +25,Coffee,$4.86 +26,Coffee,$1.72 +27,Latte,$2.68 +28,Coffee,$2.02 +29,Pumpkin Spice Latte,$5.85 +30,Latte,$3.72 +31,Coffee,$4.63 +32,Latte,$2.56 +33,Latte,$4.57 +34,Coffee,$3.56 +35,Latte,$1.09 +36,Coffee,$3.58 +37,Coffee,$3.48 +38,Pumpkin Spice Latte,$1.69 +39,Latte,$4.56 +40,Latte,$1.03 +41,Pumpkin Spice Latte,$4.46 +42,Pumpkin Spice Latte,$3.58 +43,Pumpkin Spice Latte,$5.74 +44,Coffee,$5.78 +45,Pumpkin Spice Latte,$2.56 +46,Latte,$5.39 +47,Latte,$1.79 +48,Pumpkin Spice Latte,$3.74 +49,Pumpkin Spice Latte,$3.15 +50,Pumpkin Spice Latte,$5.36 +51,Latte,$3.53 +52,Pumpkin Spice Latte,$1.76 +53,Coffee,$3.49 +54,Pumpkin Spice Latte,$4.83 +55,Coffee,$5.25 +56,Pumpkin Spice Latte,$2.33 +57,Pumpkin Spice Latte,$4.77 +58,Latte,$2.85 +59,Coffee,$4.88 +60,Latte,$5.87 +61,Pumpkin Spice Latte,$2.84 +62,Coffee,$4.30 +63,Coffee,$4.97 +64,Latte,$4.07 +65,Pumpkin Spice Latte,$1.69 +66,Latte,$5.16 +67,Pumpkin Spice Latte,$4.36 +68,Latte,$5.39 +69,Latte,$4.34 +70,Coffee,$1.76 +71,Latte,$2.90 +72,Latte,$1.65 +73,Pumpkin Spice Latte,$3.51 +74,Pumpkin Spice Latte,$2.38 +75,Coffee,$2.66 +76,Pumpkin Spice Latte,$3.59 +77,Coffee,$5.64 +78,Pumpkin Spice Latte,$4.99 +79,Pumpkin Spice Latte,$3.92 +80,Pumpkin Spice Latte,$3.90 +81,Coffee,$1.38 +82,Coffee,$2.48 +83,Pumpkin Spice Latte,$5.93 +84,Coffee,$5.95 +85,Latte,$1.58 +86,Latte,$1.33 +87,Latte,$2.63 +88,Pumpkin Spice Latte,$5.81 +89,Coffee,$5.98 +90,Coffee,$1.79 +91,Coffee,$3.19 +92,Latte,$2.72 +93,Coffee,$5.29 +94,Coffee,$3.23 +95,Pumpkin Spice Latte,$3.63 +96,Coffee,$2.86 +97,Coffee,$4.14 +98,Pumpkin Spice Latte,$2.29 +99,Pumpkin Spice Latte,$4.37 +100,Latte,$4.64 +101,Pumpkin Spice Latte,$3.78 +102,Pumpkin Spice Latte,$1.89 +103,Coffee,$4.37 +104,Pumpkin Spice Latte,$4.80 +105,Coffee,$2.00 +106,Latte,$3.30 +107,Coffee,$5.16 +108,Pumpkin Spice Latte,$3.34 +109,Latte,$5.07 +110,Pumpkin Spice Latte,$5.04 +111,Coffee,$1.24 +112,Coffee,$5.46 +113,Coffee,$1.30 +114,Coffee,$3.81 +115,Coffee,$5.08 +116,Pumpkin Spice Latte,$2.55 +117,Coffee,$4.14 +118,Pumpkin Spice Latte,$3.85 +119,Pumpkin Spice Latte,$5.73 +120,Coffee,$4.76 +121,Pumpkin Spice Latte,$5.29 +122,Pumpkin Spice Latte,$5.55 +123,Pumpkin Spice Latte,$3.75 +124,Coffee,$4.93 +125,Latte,$2.78 +126,Latte,$4.39 +127,Coffee,$3.83 +128,Latte,$1.84 +129,Coffee,$5.85 +130,Coffee,$2.09 +131,Coffee,$4.52 +132,Pumpkin Spice Latte,$4.09 +133,Latte,$4.85 +134,Coffee,$3.49 +135,Pumpkin Spice Latte,$4.33 +136,Latte,$5.62 +137,Latte,$1.07 +138,Coffee,$2.30 +139,Coffee,$2.76 +140,Pumpkin Spice Latte,$5.68 +141,Latte,$5.86 +142,Latte,$5.72 +143,Latte,$4.95 +144,Pumpkin Spice Latte,$5.03 +145,Latte,$1.33 +146,Pumpkin Spice Latte,$5.63 +147,Pumpkin Spice Latte,$3.87 +148,Pumpkin Spice Latte,$2.13 +149,Latte,$2.78 +150,Pumpkin Spice Latte,$3.48 +151,Coffee,$5.88 +152,Pumpkin Spice Latte,$5.10 +153,Pumpkin Spice Latte,$4.27 +154,Coffee,$5.47 +155,Pumpkin Spice Latte,$3.30 +156,Coffee,$5.45 +157,Coffee,$5.76 +158,Latte,$1.56 +159,Pumpkin Spice Latte,$5.30 +160,Coffee,$4.88 +161,Latte,$1.37 +162,Pumpkin Spice Latte,$2.78 +163,Latte,$1.20 +164,Coffee,$5.61 +165,Coffee,$4.05 +166,Latte,$1.36 +167,Pumpkin Spice Latte,$3.32 +168,Coffee,$1.26 +169,Pumpkin Spice Latte,$1.36 +170,Coffee,$3.35 +171,Pumpkin Spice Latte,$5.16 +172,Coffee,$1.63 +173,Coffee,$1.94 +174,Pumpkin Spice Latte,$3.15 +175,Coffee,$5.77 +176,Latte,$4.60 +177,Latte,$2.71 +178,Pumpkin Spice Latte,$2.85 +179,Latte,$5.66 +180,Latte,$3.69 +181,Pumpkin Spice Latte,$4.19 +182,Coffee,$5.43 +183,Latte,$2.09 +184,Coffee,$1.08 +185,Coffee,$5.71 +186,Pumpkin Spice Latte,$5.39 +187,Latte,$2.83 +188,Latte,$2.58 +189,Latte,$5.28 +190,Coffee,$2.58 +191,Pumpkin Spice Latte,$1.17 +192,Coffee,$5.19 +193,Pumpkin Spice Latte,$1.06 +194,Latte,$2.52 +195,Latte,$5.05 +196,Latte,$4.55 +197,Coffee,$5.05 +198,Coffee,$4.51 +199,Pumpkin Spice Latte,$3.64 +200,Latte,$3.75 +201,Pumpkin Spice Latte,$5.55 +202,Pumpkin Spice Latte,$4.92 +203,Coffee,$3.40 +204,Pumpkin Spice Latte,$5.16 +205,Coffee,$2.53 +206,Pumpkin Spice Latte,$3.29 +207,Coffee,$1.96 +208,Coffee,$1.52 +209,Latte,$4.39 +210,Latte,$5.83 +211,Pumpkin Spice Latte,$1.25 +212,Coffee,$5.75 +213,Coffee,$4.35 +214,Coffee,$3.23 +215,Pumpkin Spice Latte,$1.65 +216,Pumpkin Spice Latte,$2.31 +217,Latte,$4.80 +218,Coffee,$3.69 +219,Coffee,$2.44 +220,Latte,$2.10 +221,Pumpkin Spice Latte,$1.32 +222,Latte,$1.71 +223,Coffee,$4.66 +224,Latte,$1.42 +225,Latte,$4.34 +226,Pumpkin Spice Latte,$3.12 +227,Latte,$2.93 +228,Coffee,$1.29 +229,Coffee,$2.65 +230,Latte,$3.99 +231,Pumpkin Spice Latte,$2.65 +232,Pumpkin Spice Latte,$4.26 +233,Latte,$3.35 +234,Coffee,$4.09 +235,Latte,$4.93 +236,Latte,$1.20 +237,Latte,$1.22 +238,Latte,$3.76 +239,Pumpkin Spice Latte,$2.72 +240,Latte,$2.52 +241,Coffee,$3.02 +242,Latte,$5.74 +243,Pumpkin Spice Latte,$2.05 +244,Latte,$3.83 +245,Pumpkin Spice Latte,$2.44 +246,Latte,$3.23 +247,Latte,$2.79 +248,Latte,$3.93 +249,Latte,$5.63 +250,Coffee,$2.66 +251,Coffee,$1.84 +252,Coffee,$4.81 +253,Coffee,$5.95 +254,Coffee,$1.02 +255,Latte,$4.11 +256,Pumpkin Spice Latte,$2.31 +257,Latte,$1.30 +258,Pumpkin Spice Latte,$5.76 +259,Latte,$3.89 +260,Pumpkin Spice Latte,$2.64 +261,Coffee,$1.06 +262,Coffee,$4.27 +263,Pumpkin Spice Latte,$4.94 +264,Pumpkin Spice Latte,$2.75 +265,Latte,$4.67 +266,Coffee,$1.86 +267,Coffee,$5.18 +268,Latte,$5.91 +269,Latte,$2.47 +270,Latte,$5.78 +271,Latte,$4.18 +272,Pumpkin Spice Latte,$1.52 +273,Pumpkin Spice Latte,$4.10 +274,Coffee,$5.18 +275,Pumpkin Spice Latte,$5.70 +276,Coffee,$1.88 +277,Pumpkin Spice Latte,$3.64 +278,Pumpkin Spice Latte,$4.53 +279,Coffee,$2.48 +280,Pumpkin Spice Latte,$5.13 +281,Pumpkin Spice Latte,$5.52 +282,Pumpkin Spice Latte,$1.71 +283,Coffee,$5.44 +284,Coffee,$4.65 +285,Coffee,$3.07 +286,Latte,$5.83 +287,Pumpkin Spice Latte,$2.90 +288,Latte,$4.23 +289,Pumpkin Spice Latte,$2.62 +290,Latte,$5.70 +291,Coffee,$2.78 +292,Latte,$3.07 +293,Coffee,$2.11 +294,Pumpkin Spice Latte,$1.30 +295,Latte,$2.40 +296,Latte,$5.16 +297,Latte,$4.14 +298,Pumpkin Spice Latte,$3.19 +299,Pumpkin Spice Latte,$5.32 +300,Coffee,$2.21 +301,Latte,$3.29 +302,Pumpkin Spice Latte,$5.72 +303,Pumpkin Spice Latte,$3.96 +304,Coffee,$2.39 +305,Latte,$1.45 +306,Coffee,$3.64 +307,Coffee,$1.72 +308,Latte,$3.47 +309,Pumpkin Spice Latte,$1.75 +310,Coffee,$2.33 +311,Coffee,$3.81 +312,Latte,$2.72 +313,Coffee,$5.94 +314,Pumpkin Spice Latte,$2.39 +315,Latte,$4.31 +316,Coffee,$1.60 +317,Pumpkin Spice Latte,$5.14 +318,Pumpkin Spice Latte,$1.83 +319,Latte,$5.02 +320,Coffee,$2.65 +321,Pumpkin Spice Latte,$2.15 +322,Coffee,$2.23 +323,Pumpkin Spice Latte,$2.79 +324,Coffee,$5.82 +325,Latte,$5.33 +326,Latte,$2.24 +327,Pumpkin Spice Latte,$1.64 +328,Latte,$2.63 +329,Pumpkin Spice Latte,$5.00 +330,Coffee,$5.31 +331,Coffee,$5.76 +332,Pumpkin Spice Latte,$4.06 +333,Pumpkin Spice Latte,$5.65 +334,Coffee,$4.98 +335,Coffee,$3.91 +336,Latte,$2.28 +337,Coffee,$5.05 +338,Latte,$2.60 +339,Coffee,$5.57 +340,Pumpkin Spice Latte,$5.82 +341,Latte,$1.29 +342,Pumpkin Spice Latte,$5.19 +343,Latte,$5.27 +344,Pumpkin Spice Latte,$6.00 +345,Latte,$1.00 +346,Latte,$3.69 +347,Coffee,$2.17 +348,Pumpkin Spice Latte,$4.89 +349,Coffee,$4.52 +350,Coffee,$3.58 +351,Latte,$4.05 +352,Pumpkin Spice Latte,$2.42 +353,Latte,$1.41 +354,Latte,$3.03 +355,Coffee,$4.73 +356,Coffee,$2.89 +357,Pumpkin Spice Latte,$5.22 +358,Pumpkin Spice Latte,$2.51 +359,Coffee,$2.56 +360,Latte,$1.37 +361,Pumpkin Spice Latte,$4.33 +362,Coffee,$5.21 +363,Coffee,$3.04 +364,Latte,$4.66 +365,Latte,$4.58 +366,Coffee,$2.51 +367,Coffee,$1.21 +368,Coffee,$4.61 +369,Latte,$4.78 +370,Coffee,$5.78 +371,Latte,$3.14 +372,Pumpkin Spice Latte,$4.66 +373,Coffee,$1.82 +374,Pumpkin Spice Latte,$2.63 +375,Pumpkin Spice Latte,$2.39 +376,Latte,$1.38 +377,Latte,$2.16 +378,Latte,$1.26 +379,Coffee,$5.59 +380,Coffee,$3.03 +381,Latte,$4.56 +382,Latte,$3.21 +383,Latte,$1.89 +384,Latte,$5.00 +385,Pumpkin Spice Latte,$3.38 +386,Latte,$3.32 +387,Latte,$2.32 +388,Latte,$1.44 +389,Latte,$3.25 +390,Pumpkin Spice Latte,$3.31 +391,Coffee,$4.80 +392,Latte,$3.05 +393,Latte,$3.73 +394,Pumpkin Spice Latte,$4.70 +395,Pumpkin Spice Latte,$3.68 +396,Coffee,$5.78 +397,Pumpkin Spice Latte,$3.50 +398,Latte,$5.77 +399,Coffee,$5.27 +400,Pumpkin Spice Latte,$3.09 +401,Coffee,$4.34 +402,Coffee,$2.55 +403,Coffee,$5.06 +404,Latte,$3.84 +405,Latte,$3.63 +406,Coffee,$2.44 +407,Coffee,$3.06 +408,Pumpkin Spice Latte,$5.62 +409,Coffee,$5.66 +410,Latte,$5.43 +411,Pumpkin Spice Latte,$4.82 +412,Coffee,$2.35 +413,Pumpkin Spice Latte,$2.28 +414,Latte,$3.92 +415,Pumpkin Spice Latte,$3.39 +416,Coffee,$4.32 +417,Coffee,$5.13 +418,Coffee,$4.21 +419,Coffee,$3.40 +420,Latte,$4.90 +421,Pumpkin Spice Latte,$2.70 +422,Coffee,$5.48 +423,Latte,$1.94 +424,Latte,$1.42 +425,Coffee,$3.25 +426,Pumpkin Spice Latte,$5.42 +427,Pumpkin Spice Latte,$5.17 +428,Latte,$4.50 +429,Pumpkin Spice Latte,$5.49 +430,Coffee,$1.88 +431,Pumpkin Spice Latte,$1.80 +432,Pumpkin Spice Latte,$1.14 +433,Latte,$1.44 +434,Pumpkin Spice Latte,$2.91 +435,Latte,$2.74 +436,Latte,$5.83 +437,Coffee,$1.04 +438,Coffee,$4.56 +439,Latte,$5.58 +440,Pumpkin Spice Latte,$5.02 +441,Latte,$2.49 +442,Pumpkin Spice Latte,$1.17 +443,Pumpkin Spice Latte,$1.03 +444,Latte,$1.28 +445,Latte,$4.56 +446,Pumpkin Spice Latte,$3.06 +447,Coffee,$5.46 +448,Pumpkin Spice Latte,$3.50 +449,Coffee,$2.54 +450,Latte,$4.21 +451,Pumpkin Spice Latte,$4.98 +452,Coffee,$5.88 +453,Latte,$1.58 +454,Pumpkin Spice Latte,$4.69 +455,Pumpkin Spice Latte,$4.02 +456,Pumpkin Spice Latte,$4.29 +457,Pumpkin Spice Latte,$4.16 +458,Pumpkin Spice Latte,$5.35 +459,Coffee,$1.38 +460,Pumpkin Spice Latte,$1.43 +461,Coffee,$2.17 +462,Latte,$5.42 +463,Pumpkin Spice Latte,$3.44 +464,Latte,$5.62 +465,Latte,$5.47 +466,Latte,$3.82 +467,Latte,$4.39 +468,Latte,$3.94 +469,Pumpkin Spice Latte,$6.00 +470,Coffee,$1.62 +471,Coffee,$4.81 +472,Latte,$1.50 +473,Coffee,$3.40 +474,Latte,$2.13 +475,Coffee,$1.56 +476,Pumpkin Spice Latte,$2.09 +477,Latte,$4.67 +478,Latte,$2.59 +479,Latte,$1.94 +480,Pumpkin Spice Latte,$4.28 +481,Coffee,$2.72 +482,Coffee,$5.16 +483,Latte,$4.41 +484,Pumpkin Spice Latte,$3.09 +485,Pumpkin Spice Latte,$2.61 +486,Coffee,$3.69 +487,Pumpkin Spice Latte,$3.71 +488,Coffee,$1.06 +489,Pumpkin Spice Latte,$5.10 +490,Coffee,$2.57 +491,Pumpkin Spice Latte,$1.43 +492,Latte,$2.26 +493,Coffee,$3.99 +494,Pumpkin Spice Latte,$5.54 +495,Latte,$1.49 +496,Latte,$1.28 +497,Coffee,$4.57 +498,Coffee,$2.26 +499,Coffee,$5.72 +500,Coffee,$5.66 +501,Coffee,$5.45 +502,Pumpkin Spice Latte,$3.35 +503,Coffee,$4.56 +504,Pumpkin Spice Latte,$5.14 +505,Pumpkin Spice Latte,$1.31 +506,Latte,$2.83 +507,Latte,$3.13 +508,Coffee,$1.78 +509,Coffee,$3.72 +510,Pumpkin Spice Latte,$4.85 +511,Pumpkin Spice Latte,$2.86 +512,Pumpkin Spice Latte,$1.48 +513,Pumpkin Spice Latte,$1.43 +514,Pumpkin Spice Latte,$1.01 +515,Pumpkin Spice Latte,$2.09 +516,Coffee,$5.02 +517,Coffee,$1.97 +518,Coffee,$3.90 +519,Coffee,$1.40 +520,Latte,$2.03 +521,Latte,$4.49 +522,Latte,$1.42 +523,Pumpkin Spice Latte,$4.29 +524,Pumpkin Spice Latte,$3.07 +525,Pumpkin Spice Latte,$4.22 +526,Latte,$4.62 +527,Coffee,$4.29 +528,Pumpkin Spice Latte,$4.34 +529,Coffee,$3.56 +530,Latte,$1.98 +531,Latte,$4.05 +532,Latte,$3.19 +533,Coffee,$5.14 +534,Latte,$2.70 +535,Pumpkin Spice Latte,$2.28 +536,Coffee,$5.12 +537,Latte,$1.55 +538,Coffee,$2.88 +539,Latte,$1.62 +540,Pumpkin Spice Latte,$1.75 +541,Coffee,$4.33 +542,Latte,$5.96 +543,Latte,$4.02 +544,Coffee,$3.51 +545,Pumpkin Spice Latte,$3.59 +546,Pumpkin Spice Latte,$2.54 +547,Pumpkin Spice Latte,$5.63 +548,Coffee,$2.32 +549,Latte,$4.14 +550,Pumpkin Spice Latte,$5.43 +551,Coffee,$4.06 +552,Coffee,$3.90 +553,Pumpkin Spice Latte,$1.16 +554,Pumpkin Spice Latte,$3.39 +555,Pumpkin Spice Latte,$1.23 +556,Pumpkin Spice Latte,$3.66 +557,Latte,$5.40 +558,Coffee,$1.46 +559,Coffee,$2.21 +560,Latte,$4.90 +561,Pumpkin Spice Latte,$2.87 +562,Latte,$5.09 +563,Latte,$4.11 +564,Latte,$5.14 +565,Latte,$2.01 +566,Latte,$2.36 +567,Coffee,$1.54 +568,Latte,$4.77 +569,Latte,$5.11 +570,Coffee,$1.14 +571,Pumpkin Spice Latte,$5.01 +572,Coffee,$3.12 +573,Coffee,$1.21 +574,Pumpkin Spice Latte,$3.68 +575,Coffee,$3.05 +576,Coffee,$4.23 +577,Coffee,$1.62 +578,Pumpkin Spice Latte,$1.36 +579,Latte,$4.65 +580,Latte,$2.22 +581,Pumpkin Spice Latte,$4.66 +582,Latte,$4.00 +583,Latte,$3.13 +584,Coffee,$3.90 +585,Coffee,$3.91 +586,Latte,$3.23 +587,Coffee,$3.18 +588,Coffee,$2.62 +589,Latte,$1.74 +590,Latte,$2.31 +591,Coffee,$1.97 +592,Latte,$5.03 +593,Coffee,$2.04 +594,Pumpkin Spice Latte,$3.33 +595,Coffee,$3.86 +596,Latte,$4.74 +597,Pumpkin Spice Latte,$1.91 +598,Coffee,$4.91 +599,Pumpkin Spice Latte,$2.30 +600,Latte,$1.49 +601,Pumpkin Spice Latte,$3.33 +602,Pumpkin Spice Latte,$2.58 +603,Pumpkin Spice Latte,$4.39 +604,Coffee,$3.93 +605,Pumpkin Spice Latte,$4.51 +606,Pumpkin Spice Latte,$2.76 +607,Pumpkin Spice Latte,$3.03 +608,Coffee,$2.32 +609,Coffee,$2.98 +610,Latte,$5.77 +611,Latte,$3.58 +612,Pumpkin Spice Latte,$1.95 +613,Coffee,$4.86 +614,Pumpkin Spice Latte,$5.70 +615,Coffee,$5.91 +616,Coffee,$4.80 +617,Pumpkin Spice Latte,$1.79 +618,Pumpkin Spice Latte,$5.16 +619,Pumpkin Spice Latte,$2.88 +620,Latte,$2.01 +621,Pumpkin Spice Latte,$5.44 +622,Latte,$5.11 +623,Pumpkin Spice Latte,$3.51 +624,Latte,$5.50 +625,Latte,$1.11 +626,Latte,$4.72 +627,Pumpkin Spice Latte,$2.92 +628,Pumpkin Spice Latte,$5.70 +629,Latte,$5.64 +630,Pumpkin Spice Latte,$1.21 +631,Coffee,$5.62 +632,Pumpkin Spice Latte,$1.65 +633,Coffee,$4.62 +634,Coffee,$5.73 +635,Coffee,$3.90 +636,Pumpkin Spice Latte,$5.68 +637,Pumpkin Spice Latte,$1.30 +638,Pumpkin Spice Latte,$5.95 +639,Latte,$3.09 +640,Pumpkin Spice Latte,$2.75 +641,Latte,$3.86 +642,Pumpkin Spice Latte,$5.58 +643,Pumpkin Spice Latte,$3.34 +644,Latte,$1.81 +645,Latte,$2.42 +646,Latte,$3.63 +647,Coffee,$5.50 +648,Coffee,$4.55 +649,Pumpkin Spice Latte,$5.85 +650,Coffee,$2.02 +651,Pumpkin Spice Latte,$2.52 +652,Latte,$2.58 +653,Pumpkin Spice Latte,$3.38 +654,Latte,$3.27 +655,Pumpkin Spice Latte,$3.00 +656,Coffee,$2.67 +657,Pumpkin Spice Latte,$3.72 +658,Pumpkin Spice Latte,$4.28 +659,Latte,$1.41 +660,Pumpkin Spice Latte,$5.67 +661,Coffee,$1.52 +662,Latte,$4.97 +663,Pumpkin Spice Latte,$4.01 +664,Pumpkin Spice Latte,$5.78 +665,Pumpkin Spice Latte,$1.67 +666,Pumpkin Spice Latte,$2.23 +667,Latte,$1.17 +668,Coffee,$5.86 +669,Pumpkin Spice Latte,$5.56 +670,Coffee,$5.91 +671,Pumpkin Spice Latte,$4.33 +672,Coffee,$5.67 +673,Latte,$1.96 +674,Pumpkin Spice Latte,$2.47 +675,Pumpkin Spice Latte,$1.62 +676,Latte,$3.70 +677,Coffee,$3.76 +678,Coffee,$4.72 +679,Coffee,$4.22 +680,Coffee,$5.16 +681,Coffee,$1.93 +682,Coffee,$5.85 +683,Latte,$4.20 +684,Pumpkin Spice Latte,$1.78 +685,Pumpkin Spice Latte,$5.26 +686,Pumpkin Spice Latte,$5.06 +687,Coffee,$5.96 +688,Latte,$1.10 +689,Coffee,$4.50 +690,Coffee,$1.60 +691,Pumpkin Spice Latte,$3.65 +692,Coffee,$5.30 +693,Pumpkin Spice Latte,$1.02 +694,Coffee,$5.33 +695,Latte,$3.53 +696,Pumpkin Spice Latte,$2.77 +697,Pumpkin Spice Latte,$4.46 +698,Pumpkin Spice Latte,$5.68 +699,Coffee,$2.92 +700,Coffee,$4.91 +701,Pumpkin Spice Latte,$5.68 +702,Latte,$3.11 +703,Pumpkin Spice Latte,$3.21 +704,Pumpkin Spice Latte,$5.71 +705,Coffee,$4.93 +706,Latte,$5.63 +707,Coffee,$5.40 +708,Coffee,$3.76 +709,Latte,$4.65 +710,Coffee,$1.73 +711,Coffee,$3.54 +712,Coffee,$1.79 +713,Latte,$1.17 +714,Coffee,$5.43 +715,Latte,$2.39 +716,Pumpkin Spice Latte,$5.79 +717,Coffee,$3.82 +718,Latte,$4.22 +719,Latte,$5.18 +720,Pumpkin Spice Latte,$4.63 +721,Coffee,$4.80 +722,Latte,$2.96 +723,Coffee,$3.67 +724,Coffee,$5.17 +725,Pumpkin Spice Latte,$4.42 +726,Coffee,$4.67 +727,Pumpkin Spice Latte,$2.90 +728,Pumpkin Spice Latte,$4.20 +729,Latte,$4.77 +730,Pumpkin Spice Latte,$3.72 +731,Coffee,$1.90 +732,Pumpkin Spice Latte,$3.21 +733,Latte,$1.36 +734,Latte,$4.05 +735,Coffee,$1.85 +736,Pumpkin Spice Latte,$2.48 +737,Latte,$5.45 +738,Coffee,$3.31 +739,Pumpkin Spice Latte,$3.70 +740,Latte,$4.12 +741,Pumpkin Spice Latte,$4.96 +742,Coffee,$4.22 +743,Pumpkin Spice Latte,$4.77 +744,Pumpkin Spice Latte,$3.77 +745,Pumpkin Spice Latte,$4.82 +746,Coffee,$5.38 +747,Latte,$3.65 +748,Latte,$1.40 +749,Coffee,$3.41 +750,Pumpkin Spice Latte,$1.28 +751,Coffee,$1.66 +752,Pumpkin Spice Latte,$4.33 +753,Latte,$3.85 +754,Pumpkin Spice Latte,$1.73 +755,Coffee,$5.96 +756,Latte,$4.94 +757,Coffee,$2.42 +758,Latte,$2.35 +759,Pumpkin Spice Latte,$4.07 +760,Pumpkin Spice Latte,$1.51 +761,Pumpkin Spice Latte,$1.34 +762,Latte,$3.76 +763,Latte,$5.13 +764,Coffee,$3.68 +765,Pumpkin Spice Latte,$5.32 +766,Coffee,$2.29 +767,Pumpkin Spice Latte,$3.60 +768,Coffee,$1.89 +769,Coffee,$3.86 +770,Latte,$1.57 +771,Coffee,$4.43 +772,Coffee,$5.41 +773,Coffee,$4.47 +774,Latte,$2.85 +775,Latte,$3.32 +776,Latte,$5.55 +777,Coffee,$5.84 +778,Latte,$4.90 +779,Pumpkin Spice Latte,$4.58 +780,Pumpkin Spice Latte,$3.12 +781,Coffee,$2.99 +782,Coffee,$1.65 +783,Latte,$1.93 +784,Coffee,$2.51 +785,Coffee,$1.45 +786,Pumpkin Spice Latte,$5.38 +787,Coffee,$1.19 +788,Coffee,$3.50 +789,Coffee,$2.80 +790,Latte,$1.64 +791,Pumpkin Spice Latte,$5.04 +792,Latte,$4.92 +793,Coffee,$1.60 +794,Pumpkin Spice Latte,$1.32 +795,Pumpkin Spice Latte,$5.37 +796,Pumpkin Spice Latte,$5.19 +797,Coffee,$5.81 +798,Latte,$4.43 +799,Coffee,$2.02 +800,Pumpkin Spice Latte,$2.21 +801,Latte,$2.41 +802,Pumpkin Spice Latte,$1.86 +803,Latte,$4.43 +804,Latte,$1.32 +805,Pumpkin Spice Latte,$2.37 +806,Latte,$1.49 +807,Pumpkin Spice Latte,$5.46 +808,Latte,$1.92 +809,Latte,$4.34 +810,Pumpkin Spice Latte,$3.52 +811,Pumpkin Spice Latte,$1.70 +812,Coffee,$4.70 +813,Coffee,$2.36 +814,Latte,$5.90 +815,Latte,$1.42 +816,Coffee,$5.25 +817,Coffee,$4.38 +818,Coffee,$1.49 +819,Pumpkin Spice Latte,$4.64 +820,Pumpkin Spice Latte,$3.93 +821,Pumpkin Spice Latte,$1.65 +822,Latte,$4.28 +823,Latte,$1.82 +824,Latte,$3.90 +825,Latte,$2.47 +826,Latte,$3.75 +827,Coffee,$4.10 +828,Pumpkin Spice Latte,$1.87 +829,Pumpkin Spice Latte,$4.45 +830,Pumpkin Spice Latte,$1.35 +831,Coffee,$4.82 +832,Latte,$3.16 +833,Coffee,$2.10 +834,Coffee,$5.57 +835,Latte,$3.57 +836,Coffee,$3.67 +837,Pumpkin Spice Latte,$1.76 +838,Latte,$3.44 +839,Pumpkin Spice Latte,$3.61 +840,Pumpkin Spice Latte,$3.24 +841,Pumpkin Spice Latte,$5.45 +842,Pumpkin Spice Latte,$4.28 +843,Latte,$2.54 +844,Coffee,$2.82 +845,Latte,$2.63 +846,Latte,$5.78 +847,Pumpkin Spice Latte,$1.24 +848,Pumpkin Spice Latte,$1.04 +849,Pumpkin Spice Latte,$3.11 +850,Pumpkin Spice Latte,$5.16 +851,Latte,$5.92 +852,Latte,$4.38 +853,Latte,$2.41 +854,Pumpkin Spice Latte,$2.00 +855,Latte,$2.58 +856,Latte,$3.49 +857,Pumpkin Spice Latte,$1.65 +858,Latte,$4.71 +859,Coffee,$4.37 +860,Latte,$1.23 +861,Latte,$5.43 +862,Latte,$3.58 +863,Latte,$5.99 +864,Latte,$1.79 +865,Coffee,$1.88 +866,Pumpkin Spice Latte,$1.22 +867,Pumpkin Spice Latte,$4.98 +868,Pumpkin Spice Latte,$2.95 +869,Latte,$5.41 +870,Coffee,$2.14 +871,Latte,$5.76 +872,Coffee,$3.74 +873,Pumpkin Spice Latte,$3.41 +874,Latte,$1.47 +875,Pumpkin Spice Latte,$3.53 +876,Coffee,$4.36 +877,Latte,$5.18 +878,Pumpkin Spice Latte,$4.63 +879,Coffee,$5.83 +880,Pumpkin Spice Latte,$4.59 +881,Latte,$4.90 +882,Coffee,$2.43 +883,Coffee,$4.46 +884,Latte,$2.50 +885,Coffee,$3.21 +886,Coffee,$1.97 +887,Pumpkin Spice Latte,$1.01 +888,Latte,$5.90 +889,Coffee,$1.57 +890,Pumpkin Spice Latte,$2.77 +891,Coffee,$1.07 +892,Coffee,$2.23 +893,Latte,$3.85 +894,Latte,$2.98 +895,Pumpkin Spice Latte,$4.22 +896,Pumpkin Spice Latte,$5.14 +897,Coffee,$4.90 +898,Coffee,$3.43 +899,Coffee,$5.06 +900,Coffee,$4.12 +901,Latte,$3.94 +902,Coffee,$4.78 +903,Pumpkin Spice Latte,$3.80 +904,Latte,$5.65 +905,Coffee,$4.68 +906,Latte,$1.03 +907,Pumpkin Spice Latte,$2.74 +908,Coffee,$2.55 +909,Latte,$2.45 +910,Pumpkin Spice Latte,$1.23 +911,Coffee,$3.92 +912,Latte,$2.92 +913,Coffee,$2.98 +914,Latte,$4.38 +915,Pumpkin Spice Latte,$3.42 +916,Latte,$5.78 +917,Coffee,$1.96 +918,Pumpkin Spice Latte,$2.37 +919,Coffee,$4.89 +920,Coffee,$4.19 +921,Coffee,$4.40 +922,Latte,$1.60 +923,Pumpkin Spice Latte,$5.64 +924,Latte,$1.60 +925,Latte,$4.55 +926,Coffee,$2.28 +927,Coffee,$2.54 +928,Pumpkin Spice Latte,$4.38 +929,Pumpkin Spice Latte,$2.28 +930,Latte,$5.39 +931,Latte,$2.70 +932,Latte,$1.92 +933,Pumpkin Spice Latte,$2.73 +934,Pumpkin Spice Latte,$5.61 +935,Latte,$4.76 +936,Coffee,$4.03 +937,Coffee,$5.05 +938,Coffee,$1.71 +939,Coffee,$1.59 +940,Coffee,$5.24 +941,Latte,$4.63 +942,Coffee,$1.13 +943,Latte,$2.78 +944,Latte,$3.98 +945,Pumpkin Spice Latte,$2.12 +946,Coffee,$3.45 +947,Coffee,$2.73 +948,Pumpkin Spice Latte,$2.34 +949,Coffee,$2.51 +950,Pumpkin Spice Latte,$5.57 +951,Pumpkin Spice Latte,$3.94 +952,Coffee,$4.41 +953,Latte,$2.11 +954,Coffee,$3.63 +955,Latte,$1.91 +956,Latte,$5.74 +957,Pumpkin Spice Latte,$1.51 +958,Coffee,$3.37 +959,Coffee,$3.13 +960,Coffee,$5.67 +961,Latte,$4.75 +962,Coffee,$3.94 +963,Coffee,$1.44 +964,Pumpkin Spice Latte,$2.19 +965,Latte,$4.43 +966,Latte,$3.31 +967,Latte,$2.36 +968,Latte,$3.55 +969,Pumpkin Spice Latte,$2.87 +970,Coffee,$4.21 +971,Latte,$3.07 +972,Pumpkin Spice Latte,$4.90 +973,Coffee,$1.91 +974,Latte,$5.29 +975,Pumpkin Spice Latte,$3.77 +976,Pumpkin Spice Latte,$1.15 +977,Latte,$5.56 +978,Latte,$4.45 +979,Pumpkin Spice Latte,$4.67 +980,Latte,$4.15 +981,Coffee,$5.24 +982,Coffee,$4.64 +983,Coffee,$5.16 +984,Coffee,$4.43 +985,Coffee,$4.90 +986,Pumpkin Spice Latte,$4.66 +987,Latte,$4.77 +988,Latte,$4.06 +989,Pumpkin Spice Latte,$2.07 +990,Pumpkin Spice Latte,$1.27 +991,Coffee,$3.43 +992,Pumpkin Spice Latte,$5.74 +993,Pumpkin Spice Latte,$4.13 +994,Coffee,$3.62 +995,Latte,$4.05 +996,Latte,$2.92 +997,Coffee,$4.61 +998,Pumpkin Spice Latte,$4.28 +999,Latte,$2.24 +1000,Latte,$4.84 From 2c922b197a2e1970d434cd5c10b38eff571a0f69 Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:15:18 -0500 Subject: [PATCH 28/58] Add files via upload --- assets/MAanderson_Headshot.jpeg | Bin 0 -> 24904 bytes assets/NakiaGrayson.png | Bin 0 -> 190346 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/MAanderson_Headshot.jpeg create mode 100644 assets/NakiaGrayson.png diff --git a/assets/MAanderson_Headshot.jpeg b/assets/MAanderson_Headshot.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d82f04e32dd3a8443b7ec448dd41db7bde3e4df5 GIT binary patch literal 24904 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 !F(QaqatSJ*rqVZ13|D=;` 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 literal 0 HcmV?d00001 From 34cf970a604dc7feed84d9a8ca88412afdacf17b Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:19:56 -0500 Subject: [PATCH 29/58] Update README.md --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fe78089..bcba3a9 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ The NIST Privacy Engineering Collaboration Space is an online venue open to the public where practitioners can discover, share, discuss, and improve upon open source tools, solutions, and processes that support privacy engineering and risk management. ## Focus Areas -We have launched this space with an initial focus on de-identification and privacy risk assessment tools and use cases, and welcome [feedback](mailto:collabspace@nist.gov) on topics of interest from the community. +We have launched this space with an initial focus on disassociability and privacy risk assessment tools and use cases, and welcome [feedback](mailto:collabspace@nist.gov) on topics of interest from the community. -* **De-identification:** a technique or process applied to a dataset with the goal of preventing or limiting certain types of privacy risks to individuals, protected groups, and establishments, while still allowing for the production of aggregate statistics. This focus area includes a broad scope of de-identification to allow for noise-introducing techniques such as differential privacy, data masking, and the creation of synthetic datasets that are based on privacy-preserving models. +* **Disassociability:** a technique or process applied to a dataset with the goal of preventing or limiting certain types of privacy risks to individuals, protected groups, and establishments, while still allowing for the production of aggregate statistics. This focus area includes a broad scope of disassociability to allow for noise-introducing techniques such as differential privacy, data masking, and the creation of synthetic datasets that are based on privacy-preserving models. * **Privacy Risk Assessment:** a process that helps organizations to analyze and assess privacy risks for individuals arising from the processing of their data. This focus area includes, but is not limited to, risk models, risk assessment methodologies, and approaches to determining privacy risk factors. @@ -26,7 +26,7 @@ Tools and use cases are contributed via pull requests, while feedback is contrib 3. In your branch: - A. Create a new directory within the relevant tool or use case directory: tools/de-identification, tools/risk-assessment, use-cases/de-identification, or use-cases/risk-assessment. Example: *tools/de-identification/[your-contribution-name]* + A. Create a new directory within the relevant tool or use case directory: tools/disassociability, tools/risk-assessment, use-cases/disassociability, or use-cases/risk-assessment. Example: *tools/disassociability/[your-contribution-name]* B. Name the directory to describe your contribution. @@ -52,7 +52,7 @@ Submit an [issue](https://github.com/usnistgov/PrivacyEngCollabSpace/issues/new) ## Browse Tools and Use Cases -Interested in tools or use cases for de-identification and privacy risk assessment? **Browse the contributions [here](https://www.nist.gov/itl/applied-cybersecurity/privacy-engineering/collaboration-space/browse).** +Interested in tools or use cases for disassociability and privacy risk assessment? **Browse the contributions [here](https://www.nist.gov/itl/applied-cybersecurity/privacy-engineering/collaboration-space/browse).** ## Operating Rules @@ -78,7 +78,7 @@ This platform is provided as a public service. Information, data, and software p ## Moderators -### De-Identification Moderators +### Disassociability Moderators ![Joseph Near](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/joseph-near.jpg) @@ -90,9 +90,13 @@ This platform is provided as a public service. Information, data, and software p ### Privacy Risk Management Moderator -![Katie Boeckl](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/katie-boeckl.jpg) +![Nakia Grayson](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/NakiaGrayson.png) -**Katie Boeckl [@kboeckl]:** Katie Boeckl is a privacy risk strategist at NIST. As part of the Privacy Engineering Program, Katie develops privacy risk management guidance, collaborates on the development of international privacy standards, and works to advance tools for privacy engineering and risk management. Katie has a B.A. in English from the University of Maryland, College Park, where she specialized in technology through a digital cultures honors program. +**Nakia Grayson [@ngrayson1]:** Nakia Grayson is an IT Security Specialist with the Privacy Engineering Program at the National Institute of Standards and Technology (NIST). She supports the Privacy Engineering Program with development of privacy risk management best practices, guidance and communications efforts. She also leads Supply Chain Assurance project efforts at the National Cybersecurity Center of Excellence (NCCoE). Nakia serves as the Contracting Officer Representative for NIST cybersecurity contracts. She holds a Bachelor’s in Criminal Justice from University of Maryland-Eastern Shore and a Master’s in Information Technology, Information Assurance and Business Administration from the University of Maryland University College. + +![Meghan Anderson](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/MAanderson_Headshot.jpeg) + +**Meghan Anderson [@manderson11]:** Meghan Anderson is a Privacy Risk Strategist with the Privacy Engineering Program at the National Institute of Standards and Technology, U.S. Department of Commerce. She supports the development of privacy engineering, international privacy standards, and privacy risk management guidance. Meghan has a Bachelor’s in Emergency Preparedness, Homeland Security, and Cybersecurity with a concentration in Cybersecurity and a minor in Economics from the University of Albany, SUNY and a Master’s in Cybersecurity from the Georgia Institute of Technology (Georgia Tech). ## NIST Privacy Engineering Program Learn about NIST's Privacy Engineering Program by visiting our [website](https://www.nist.gov/itl/applied-cybersecurity/privacy-engineering). From 840a20c5305952d132467a86def9eb7dd22624ae Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:23:30 -0500 Subject: [PATCH 30/58] Add files via upload --- assets/meghan-anderson.jpeg | Bin 0 -> 21725 bytes assets/nakia-grayson.jpeg | Bin 0 -> 15913 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/meghan-anderson.jpeg create mode 100644 assets/nakia-grayson.jpeg diff --git a/assets/meghan-anderson.jpeg b/assets/meghan-anderson.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..e8ad21f5c59cbe17058fa75a901e51b79df68e86 GIT binary patch literal 21725 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 XE>3e$`JUpb&o4NrJkN7d*=PURhOyx7 literal 0 HcmV?d00001 diff --git a/assets/nakia-grayson.jpeg b/assets/nakia-grayson.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5c3dfb2133409ec61944b39a6f1a1fbd11215538 GIT binary patch literal 15913 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 literal 0 HcmV?d00001 From ac66501795b5f1ed1484a8eca949b0afd0057df5 Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:24:42 -0500 Subject: [PATCH 31/58] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bcba3a9..624d9cf 100644 --- a/README.md +++ b/README.md @@ -90,11 +90,11 @@ This platform is provided as a public service. Information, data, and software p ### Privacy Risk Management Moderator -![Nakia Grayson](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/NakiaGrayson.png) +![Nakia Grayson](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/nakia-grayson.jpeg) **Nakia Grayson [@ngrayson1]:** Nakia Grayson is an IT Security Specialist with the Privacy Engineering Program at the National Institute of Standards and Technology (NIST). She supports the Privacy Engineering Program with development of privacy risk management best practices, guidance and communications efforts. She also leads Supply Chain Assurance project efforts at the National Cybersecurity Center of Excellence (NCCoE). Nakia serves as the Contracting Officer Representative for NIST cybersecurity contracts. She holds a Bachelor’s in Criminal Justice from University of Maryland-Eastern Shore and a Master’s in Information Technology, Information Assurance and Business Administration from the University of Maryland University College. -![Meghan Anderson](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/MAanderson_Headshot.jpeg) +![Meghan Anderson](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/meghan-anderson.jpeg) **Meghan Anderson [@manderson11]:** Meghan Anderson is a Privacy Risk Strategist with the Privacy Engineering Program at the National Institute of Standards and Technology, U.S. Department of Commerce. She supports the development of privacy engineering, international privacy standards, and privacy risk management guidance. Meghan has a Bachelor’s in Emergency Preparedness, Homeland Security, and Cybersecurity with a concentration in Cybersecurity and a minor in Economics from the University of Albany, SUNY and a Master’s in Cybersecurity from the Georgia Institute of Technology (Georgia Tech). From da0e7fca2b9ee5cd634b030a284bc48c02e6b817 Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:25:17 -0500 Subject: [PATCH 32/58] Delete assets/MAanderson_Headshot.jpeg --- assets/MAanderson_Headshot.jpeg | Bin 24904 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/MAanderson_Headshot.jpeg diff --git a/assets/MAanderson_Headshot.jpeg b/assets/MAanderson_Headshot.jpeg deleted file mode 100644 index d82f04e32dd3a8443b7ec448dd41db7bde3e4df5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24904 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 Date: Mon, 5 Feb 2024 10:31:38 -0500 Subject: [PATCH 33/58] Add files via upload --- assets/meghan-anderson.jpg | Bin 0 -> 6833 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/meghan-anderson.jpg diff --git a/assets/meghan-anderson.jpg b/assets/meghan-anderson.jpg new file mode 100644 index 0000000000000000000000000000000000000000..24af657820f6fab5af56aa49ccfd3901603f3bf3 GIT binary patch literal 6833 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 Date: Mon, 5 Feb 2024 10:32:06 -0500 Subject: [PATCH 34/58] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 624d9cf..806cff7 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ This platform is provided as a public service. Information, data, and software p **Nakia Grayson [@ngrayson1]:** Nakia Grayson is an IT Security Specialist with the Privacy Engineering Program at the National Institute of Standards and Technology (NIST). She supports the Privacy Engineering Program with development of privacy risk management best practices, guidance and communications efforts. She also leads Supply Chain Assurance project efforts at the National Cybersecurity Center of Excellence (NCCoE). Nakia serves as the Contracting Officer Representative for NIST cybersecurity contracts. She holds a Bachelor’s in Criminal Justice from University of Maryland-Eastern Shore and a Master’s in Information Technology, Information Assurance and Business Administration from the University of Maryland University College. -![Meghan Anderson](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/meghan-anderson.jpeg) +![Meghan Anderson](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/meghan-anderson.jpg) **Meghan Anderson [@manderson11]:** Meghan Anderson is a Privacy Risk Strategist with the Privacy Engineering Program at the National Institute of Standards and Technology, U.S. Department of Commerce. She supports the development of privacy engineering, international privacy standards, and privacy risk management guidance. Meghan has a Bachelor’s in Emergency Preparedness, Homeland Security, and Cybersecurity with a concentration in Cybersecurity and a minor in Economics from the University of Albany, SUNY and a Master’s in Cybersecurity from the Georgia Institute of Technology (Georgia Tech). From 90ed39013f04f9b2d83ad4dedd9406fd044b3216 Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:32:27 -0500 Subject: [PATCH 35/58] Delete assets/meghan-anderson.jpeg --- assets/meghan-anderson.jpeg | Bin 21725 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/meghan-anderson.jpeg diff --git a/assets/meghan-anderson.jpeg b/assets/meghan-anderson.jpeg deleted file mode 100644 index e8ad21f5c59cbe17058fa75a901e51b79df68e86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21725 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 XE>3e$`JUpb&o4NrJkN7d*=PURhOyx7 From 849141e1ee32fdb1f426e8afcefcf8f6a9196902 Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:32:42 -0500 Subject: [PATCH 36/58] Delete assets/NakiaGrayson.png --- assets/NakiaGrayson.png | Bin 190346 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/NakiaGrayson.png diff --git a/assets/NakiaGrayson.png b/assets/NakiaGrayson.png deleted file mode 100644 index a56cb8e044f20cd27520865e0bbb08e16fddc2cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 190346 zcmV)pK%2jbP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 !F(QaqatSJ*rqVZ13|D=;` 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 From 1e15d94fdb68128b289f3ef008981d9febccff08 Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:37:24 -0500 Subject: [PATCH 37/58] Update README.md --- use-cases/de-identification/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/use-cases/de-identification/README.md b/use-cases/de-identification/README.md index 9ee87a1..7c4c78a 100644 --- a/use-cases/de-identification/README.md +++ b/use-cases/de-identification/README.md @@ -1,3 +1,3 @@ -# De-identification Use Cases +# Disassociability Use Cases -Contributions are listed in alphabetical order. \ No newline at end of file +Contributions are listed in alphabetical order. From fa33403960aab11c36a1718ceebeff27f7b8d5f5 Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:39:42 -0500 Subject: [PATCH 38/58] Update CONTRIBUTING.md --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3605950..c8e835f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,11 +10,11 @@ NIST is accepting the following contributions. **Feedback:** Help the community. Provide feedback on tools and use cases. ## Contribution Focus Areas -Please limit your contributions to the topics of de-identification or privacy risk assessment. We welcome [feedback](mailto:collabspace@nist.gov) on future topics of interest. +Please limit your contributions to the topics of disassociability or privacy risk assessment. We welcome [feedback](mailto:collabspace@nist.gov) on future topics of interest. -**De-identification:** a technique or process applied to a dataset with the goal of preventing or limiting certain types of privacy risks to individuals, protected groups, and establishments, while still allowing for the production of aggregate statistics. This focus area includes a broad scope of de-identification to allow for noise-introducing techniques such as differential privacy, data masking, and the creation of synthetic datasets that are based on privacy-preserving models. +**Disassociability:** This focus area can help system designers and engineers consider how to enable the processing of personal information or events without association to individuals or devices beyond the operational requirements of the system. These tools and use cases also support the achievement of the Dissociated Processing Subcategory (CT.DP.P) of the NIST Privacy Framework. -**Privacy Risk Assessment:** a process that helps organizations to analyze and assess privacy risks for individuals arising from the processing of their data. This focus area includes, but is not limited to, risk models, risk assessment methodologies, and approaches to determining privacy risk factors. +**Privacy Risk Assessment:** A process that helps organizations to analyze and assess privacy risks for individuals arising from the processing of their data. This focus area includes, but is not limited to, risk models, risk assessment methodologies, and approaches to determining privacy risk factors. # How to Contribute @@ -28,7 +28,7 @@ Tools and use cases are contributed via pull requests, while feedback is contrib 3. In your branch: - A. Create a new directory within the relevant tool or use case directory: tools/de-identification, tools/risk-assessment, use-cases/de-identification, or use-cases/risk-assessment. Example: *tools/de-identification/[your-contribution-name]* + A. Create a new directory within the relevant tool or use case directory: tools/disassociability, tools/risk-assessment, use-cases/disassociability, or use-cases/risk-assessment. Example: *tools/disassociability/[your-contribution-name]* B. Name the directory to describe your contribution. From b927a783489438d563b0898ec89ccf5017eda286 Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:40:51 -0500 Subject: [PATCH 39/58] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 806cff7..7387b27 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ This platform is provided as a public service. Information, data, and software p **David Darais [@davdar]:** David Darais is a Principal Scientist at Galois, Inc. and supports NIST as a moderator for the Privacy Engineering Collaboration Space. David's research focuses on tools for achieving reliable software in critical, security-sensitive, and privacy-sensitive systems. David received his B.S. from the University of Utah, M.S. from Harvard University and Ph.D. from the University of Maryland. -### Privacy Risk Management Moderator +### Privacy Risk Management Moderators ![Nakia Grayson](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/nakia-grayson.jpeg) From 63789969c10bbfc8c6258191c7dc1450b8362540 Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:42:26 -0500 Subject: [PATCH 40/58] Update tool-template.md --- templates/tool-template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/tool-template.md b/templates/tool-template.md index 14e0d03..aed8c9d 100644 --- a/templates/tool-template.md +++ b/templates/tool-template.md @@ -2,9 +2,9 @@ **Name of Tool:** -**Primary Focus Area (select one):** De-identification or Privacy Risk Assessment +**Primary Focus Area (select one):** Disassociability or Privacy Risk Assessment -**De-identification Keywords (select any relevant):** Differential Privacy, K-Anonymity, Anonymization, Access Control, Information Leakage, Algorithmic Fairness, Verification of Algorithms, Machine Learning, Database Queries, Synthetic Data Generation, Location Data, Adversarial Examples, Other/Propose New Keyword +**Disassociability Keywords (select any relevant):** Differential Privacy, K-Anonymity, Anonymization, Access Control, Information Leakage, Algorithmic Fairness, Verification of Algorithms, Machine Learning, Database Queries, Synthetic Data Generation, Location Data, Adversarial Examples, Other/Propose New Keyword **Brief Description:** From 5646f5d6d37ac08d71a8bbf4406ef560aabdd389 Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:43:16 -0500 Subject: [PATCH 41/58] Update use-case-template.md --- templates/use-case-template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/use-case-template.md b/templates/use-case-template.md index 6abb11b..188cb65 100644 --- a/templates/use-case-template.md +++ b/templates/use-case-template.md @@ -2,9 +2,9 @@ **Name of Use Case:** -**Primary Focus Area (select one):** De-identification or Privacy Risk Assessment +**Primary Focus Area (select one):** Disassociability or Privacy Risk Assessment -**De-identification Keywords (select any relevant):** Differential Privacy, K-Anonymity, Anonymization, Access Control, Information Leakage, Algorithmic Fairness, Verification of Algorithms, Machine Learning, Database Queries, Synthetic Data Generation, Location Data, Other/Propose New Keyword +**Disassociability Keywords (select any relevant):** Differential Privacy, K-Anonymity, Anonymization, Access Control, Information Leakage, Algorithmic Fairness, Verification of Algorithms, Machine Learning, Database Queries, Synthetic Data Generation, Location Data, Other/Propose New Keyword **Brief Description:** From a86d726dfff156e5225fca4af0b1fddddd17e7fd Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:43:47 -0500 Subject: [PATCH 42/58] Add files via upload --- assets/nakia-grayson.jpg | Bin 0 -> 6698 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/nakia-grayson.jpg diff --git a/assets/nakia-grayson.jpg b/assets/nakia-grayson.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b08e6e8fe02fc3d099dd14d2b193fa2f75ac4ee2 GIT binary patch literal 6698 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 Date: Mon, 5 Feb 2024 10:44:12 -0500 Subject: [PATCH 43/58] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7387b27..9aefd8b 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ This platform is provided as a public service. Information, data, and software p ### Privacy Risk Management Moderators -![Nakia Grayson](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/nakia-grayson.jpeg) +![Nakia Grayson](https://github.com/usnistgov/PrivacyEngCollabSpace/blob/master/assets/nakia-grayson.jpg) **Nakia Grayson [@ngrayson1]:** Nakia Grayson is an IT Security Specialist with the Privacy Engineering Program at the National Institute of Standards and Technology (NIST). She supports the Privacy Engineering Program with development of privacy risk management best practices, guidance and communications efforts. She also leads Supply Chain Assurance project efforts at the National Cybersecurity Center of Excellence (NCCoE). Nakia serves as the Contracting Officer Representative for NIST cybersecurity contracts. She holds a Bachelor’s in Criminal Justice from University of Maryland-Eastern Shore and a Master’s in Information Technology, Information Assurance and Business Administration from the University of Maryland University College. From cf9be4c157adc43054336dd45cf0ce17f13fd68e Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:44:35 -0500 Subject: [PATCH 44/58] Delete assets/nakia-grayson.jpeg --- assets/nakia-grayson.jpeg | Bin 15913 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/nakia-grayson.jpeg diff --git a/assets/nakia-grayson.jpeg b/assets/nakia-grayson.jpeg deleted file mode 100644 index 5c3dfb2133409ec61944b39a6f1a1fbd11215538..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15913 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 From 653158bde17e3d4958da26f3fa9ddd14ca5958ef Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:46:35 -0500 Subject: [PATCH 45/58] Update README.md --- tools/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/README.md b/tools/README.md index 9527c1d..cc0cca4 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,9 +1,9 @@ # Focus Areas -Tools and use cases are currently focused on de-identification and privacy risk assessment. We welcome [feedback](mailto:collabspace@nist.gov) on future topics of interest. +Tools and use cases are currently focused on disassociability and privacy risk assessment. We welcome [feedback](mailto:collabspace@nist.gov) on future topics of interest. -**De-identification:** a technique or process applied to a dataset with the goal of preventing or limiting certain types of privacy risks to individuals, protected groups, and establishments, while still allowing for the production of aggregate statistics. This focus area includes a broad scope of de-identification to allow for noise-introducing techniques such as differential privacy, data masking, and the creation of synthetic datasets that are based on privacy-preserving models. +**Disassociability:** This focus area can help system designers and engineers consider how to enable the processing of personal information or events without association to individuals or devices beyond the operational requirements of the system. These tools and use cases also support the achievement of the Dissociated Processing Subcategory (CT.DP.P) of the NIST Privacy Framework. -**Privacy Risk Assessment:** a process that helps organizations to analyze and assess privacy risks for individuals arising from the processing of their data. This focus area includes, but is not limited to, risk models, risk assessment methodologies, and approaches to determining privacy risk factors. +**Privacy Risk Assessment:** A process that helps organizations to analyze and assess privacy risks for individuals arising from the processing of their data. This focus area includes, but is not limited to, risk models, risk assessment methodologies, and approaches to determining privacy risk factors. # Contribute Feedback From 23b383ec3d1b48e58d2dc6588d562bb8c86509a3 Mon Sep 17 00:00:00 2001 From: manderson11 <131808161+manderson11@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:49:54 -0500 Subject: [PATCH 46/58] Update README.md --- tools/de-identification/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/de-identification/README.md b/tools/de-identification/README.md index 531d348..d2e0c34 100644 --- a/tools/de-identification/README.md +++ b/tools/de-identification/README.md @@ -1,4 +1,4 @@ -# De-identification Tools +# Disassociability Tools Contributions are listed in alphabetical order. @@ -109,4 +109,4 @@ Contributions are listed in alphabetical order. **Keywords:** Differential Privacy, Machine Learning, Privacy Preservering Computation, MPC -**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/visa-pets-FL)** | **[Link to Tool](https://github.com/Visa-Research/visa-pets-FL)** \ No newline at end of file +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/visa-pets-FL)** | **[Link to Tool](https://github.com/Visa-Research/visa-pets-FL)** From ac103ab06de5480c290fc6c7aceca7f70c88289b Mon Sep 17 00:00:00 2001 From: ngrayson1 Date: Wed, 14 Feb 2024 11:35:59 -0500 Subject: [PATCH 47/58] new folder for disassociability --- tools/disassociability/AMP/README.md | 15 + tools/disassociability/ARX/README.md | 13 + tools/disassociability/Chorus/README.md | 16 + .../DPFieldGroups/README.md | 68 + .../DPFieldGroups/deployment_guide.txt | 45 + .../gardn999_NistDp3_code_guide.odt | Bin 0 -> 20848 bytes .../gardn999_NistDp3_code_guide.pdf | Bin 0 -> 63982 bytes ...n999_NistDp3_writeup_and_privacy_proof.odt | Bin 0 -> 25079 bytes ...n999_NistDp3_writeup_and_privacy_proof.pdf | Bin 0 -> 63251 bytes .../DPFieldGroups/run_docker.sh | 23 + .../DPFieldGroups/solution/Dockerfile | 25 + .../DPFieldGroups/solution/codebook.cbk | 8629 +++++++++++++++++ .../DPFieldGroups/solution/compile.sh | 8 + .../DPFieldGroups/solution/run.sh | 3 + .../DPFieldGroups/solution/src/Field.java | 45 + .../DPFieldGroups/solution/src/Main.java | 126 + .../DPFieldGroups/solution/src/Solution.java | 323 + .../DPSyn/LICENSE | 21 + .../DPSyn/README.md | 19 + .../DPSyn/config.py | 3 + .../DPSyn/document/README.md | 52 + .../DPSyn/document/algorithm description.pdf | Bin 0 -> 234972 bytes .../DPSyn/experiment/experiment.py | 58 + .../DPSyn/experiment/experiment_C5.py | 423 + .../DPSyn/experiment/experiment_C5_1.py | 184 + .../DPSyn/experiment/experiment_C5_2.py | 195 + .../DPSyn/experiment/experiment_dpsyn.py | 118 + .../DPSyn/experiment/experiment_dpsyn_mcf.py | 208 + .../DPSyn/generate_submission.py | 50 + .../DPSyn/lib_attributes/attributes_group.py | 89 + .../lib_attributes/attributes_postprocess.py | 421 + .../lib_attributes/attributes_preprocess.py | 151 + .../DPSyn/lib_attributes/attributes_recode.py | 151 + .../lib_composition/advanced_composition.py | 31 + .../DPSyn/lib_dpsyn/records_update.py | 287 + .../DPSyn/lib_view/consistent.py | 151 + .../DPSyn/lib_view/non_negativity.py | 39 + .../DPSyn/lib_view/view.py | 194 + .../DPSyn/load_csv.py | 84 + .../DPSyn/main.py | 75 + .../DPSyn/temp_data/mapping/code_mapping | Bin 0 -> 38321 bytes .../mapping/general_detail_mapping.json | 3074 ++++++ .../DPSyn/temp_data/mapping/pair_mapping.json | 2190 +++++ .../DPSyn/utility.py | 7 + .../DP_WGAN-UCLANESL/README.md | 42 + .../README.md | 44 + .../rmckenna/LICENSE | 339 + .../rmckenna/README.md | 28 + .../rmckenna/counties.txt | 3260 +++++++ .../rmckenna/domain.ipynb | 101 + .../rmckenna/domain.json | 1 + .../rmckenna/match3.py | 262 + .../rmckenna/matrix.py | 17 + .../rmckenna/mechanism.py | 194 + .../rmckenna/select-queries.ipynb | 191 + .../rmckenna/writeup.pdf | Bin 0 -> 98945 bytes .../README.md | 15 + tools/disassociability/Diffprivlib/README.md | 15 + tools/disassociability/Duet/README.md | 16 + tools/disassociability/Ektelo/README.md | 13 + tools/disassociability/GUPT/README.md | 17 + .../disassociability/Google-DP-Lib/README.md | 13 + .../disassociability/HyFL-Framework/README.md | 13 + tools/disassociability/MusCAT/README.md | 18 + .../Count.ipynb | 1641 ++++ .../HistogramBias.ipynb | 1018 ++ .../MachineLearningBias.ipynb | 686 ++ .../lattes.csv | 1000 ++ tools/disassociability/PPMLHuskies/README.md | 19 + tools/disassociability/PixelDP/README.md | 22 + .../README.md | 15 + .../README.md | 15 + tools/disassociability/README.md | 112 + tools/disassociability/Scarlet-PETs/README.md | 17 + tools/disassociability/puffle/README.md | 15 + .../disassociability/visa-pets-FL/LICENSE.txt | 408 + tools/disassociability/visa-pets-FL/README.md | 24 + .../centralized/DNNCentralizedModule.py | 204 + .../visa-pets-FL/centralized/DataObjects.py | 82 + .../visa-pets-FL/centralized/DataPrepUtils.py | 387 + .../visa-pets-FL/centralized/NeuralNet.py | 28 + .../centralized/solution_centralized.py | 47 + .../visa-pets-FL/federated/DNN.py | 126 + .../visa-pets-FL/federated/DataObjects.py | 82 + .../visa-pets-FL/federated/DataPrepUtils.py | 454 + .../visa-pets-FL/federated/NeuralNet.py | 28 + .../visa-pets-FL/federated/ot.py | 161 + .../federated/solution_federated.py | 830 ++ .../disassociability/visa-pets-FL/figure.png | Bin 0 -> 143190 bytes .../visa-pets-FL/ot_library/.gitignore | 227 + .../ot_library/.vscode/launch.json | 16 + .../ot_library/.vscode/settings.json | 82 + .../visa-pets-FL/ot_library/CMakeLists.txt | 21 + .../visa-pets-FL/ot_library/CMakePresets.json | 82 + .../ot_library/cmake/Config.cmake.in | 13 + .../ot_library/cmake/buildOptions.cmake | 41 + .../ot_library/cmake/findDependancies.cmake | 66 + .../ot_library/cmake/install.cmake | 67 + .../ot_library/cmake/preamble.cmake | 87 + .../ot_library/drop-ot/CMakeLists.txt | 31 + .../visa-pets-FL/ot_library/drop-ot/Defines.h | 116 + .../ot_library/drop-ot/IknpOtExt.cpp | 443 + .../ot_library/drop-ot/IknpOtExt.h | 237 + .../ot_library/drop-ot/MasnyRindal.cpp | 224 + .../ot_library/drop-ot/MasnyRindal.h | 183 + .../visa-pets-FL/ot_library/drop-ot/Tools.cpp | 942 ++ .../visa-pets-FL/ot_library/drop-ot/Tools.h | 47 + .../ot_library/drop-ot/UnitTests.cpp | 11 + .../ot_library/drop-ot/UnitTests.h | 15 + .../ot_library/drop-ot/config.h.in | 18 + .../ot_library/frontend/CMakeLists.txt | 15 + .../visa-pets-FL/ot_library/frontend/main.cpp | 243 + .../visa-pets-FL/ot_library/readme.md | 12 + .../ot_library/wrapper/CMakeLists.txt | 8 + .../ot_library/wrapper/CWrapper.cpp | 385 + .../disassociability/visa-pets-FL/solution.md | 179 + 116 files changed, 33445 insertions(+) create mode 100644 tools/disassociability/AMP/README.md create mode 100644 tools/disassociability/ARX/README.md create mode 100644 tools/disassociability/Chorus/README.md create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/README.md create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/deployment_guide.txt create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_code_guide.odt create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_code_guide.pdf create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_writeup_and_privacy_proof.odt create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_writeup_and_privacy_proof.pdf create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/run_docker.sh create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/Dockerfile create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/codebook.cbk create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/compile.sh create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/run.sh create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Field.java create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Main.java create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Solution.java create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/LICENSE create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/README.md create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/config.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/document/README.md create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/document/algorithm description.pdf create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5_1.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5_2.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_dpsyn.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_dpsyn_mcf.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/generate_submission.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_group.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_postprocess.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_preprocess.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_recode.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_composition/advanced_composition.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_dpsyn/records_update.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/consistent.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/non_negativity.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/view.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/load_csv.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/main.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/temp_data/mapping/code_mapping create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/temp_data/mapping/general_detail_mapping.json create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/temp_data/mapping/pair_mapping.json create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/utility.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DP_WGAN-UCLANESL/README.md create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/README.md create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/LICENSE create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/README.md create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/counties.txt create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/domain.ipynb create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/domain.json create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/match3.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/matrix.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/mechanism.py create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/select-queries.ipynb create mode 100644 tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/writeup.pdf create mode 100644 tools/disassociability/Differentially-Private-Stochastic-Gradient-Descent-DP-SGD/README.md create mode 100644 tools/disassociability/Diffprivlib/README.md create mode 100644 tools/disassociability/Duet/README.md create mode 100644 tools/disassociability/Ektelo/README.md create mode 100644 tools/disassociability/GUPT/README.md create mode 100644 tools/disassociability/Google-DP-Lib/README.md create mode 100644 tools/disassociability/HyFL-Framework/README.md create mode 100644 tools/disassociability/MusCAT/README.md create mode 100644 tools/disassociability/NIST-SP-800-226-SupplementalMaterial/Count.ipynb create mode 100644 tools/disassociability/NIST-SP-800-226-SupplementalMaterial/HistogramBias.ipynb create mode 100644 tools/disassociability/NIST-SP-800-226-SupplementalMaterial/MachineLearningBias.ipynb create mode 100644 tools/disassociability/NIST-SP-800-226-SupplementalMaterial/lattes.csv create mode 100644 tools/disassociability/PPMLHuskies/README.md create mode 100644 tools/disassociability/PixelDP/README.md create mode 100644 tools/disassociability/Privacy-Protection-Application-PPA/README.md create mode 100644 tools/disassociability/Private-Aggregation-of-Teacher-Ensembles-PATE/README.md create mode 100644 tools/disassociability/README.md create mode 100644 tools/disassociability/Scarlet-PETs/README.md create mode 100644 tools/disassociability/puffle/README.md create mode 100644 tools/disassociability/visa-pets-FL/LICENSE.txt create mode 100644 tools/disassociability/visa-pets-FL/README.md create mode 100644 tools/disassociability/visa-pets-FL/centralized/DNNCentralizedModule.py create mode 100644 tools/disassociability/visa-pets-FL/centralized/DataObjects.py create mode 100644 tools/disassociability/visa-pets-FL/centralized/DataPrepUtils.py create mode 100644 tools/disassociability/visa-pets-FL/centralized/NeuralNet.py create mode 100644 tools/disassociability/visa-pets-FL/centralized/solution_centralized.py create mode 100644 tools/disassociability/visa-pets-FL/federated/DNN.py create mode 100644 tools/disassociability/visa-pets-FL/federated/DataObjects.py create mode 100644 tools/disassociability/visa-pets-FL/federated/DataPrepUtils.py create mode 100644 tools/disassociability/visa-pets-FL/federated/NeuralNet.py create mode 100644 tools/disassociability/visa-pets-FL/federated/ot.py create mode 100644 tools/disassociability/visa-pets-FL/federated/solution_federated.py create mode 100644 tools/disassociability/visa-pets-FL/figure.png create mode 100644 tools/disassociability/visa-pets-FL/ot_library/.gitignore create mode 100644 tools/disassociability/visa-pets-FL/ot_library/.vscode/launch.json create mode 100644 tools/disassociability/visa-pets-FL/ot_library/.vscode/settings.json create mode 100644 tools/disassociability/visa-pets-FL/ot_library/CMakeLists.txt create mode 100644 tools/disassociability/visa-pets-FL/ot_library/CMakePresets.json create mode 100644 tools/disassociability/visa-pets-FL/ot_library/cmake/Config.cmake.in create mode 100644 tools/disassociability/visa-pets-FL/ot_library/cmake/buildOptions.cmake create mode 100644 tools/disassociability/visa-pets-FL/ot_library/cmake/findDependancies.cmake create mode 100644 tools/disassociability/visa-pets-FL/ot_library/cmake/install.cmake create mode 100644 tools/disassociability/visa-pets-FL/ot_library/cmake/preamble.cmake create mode 100644 tools/disassociability/visa-pets-FL/ot_library/drop-ot/CMakeLists.txt create mode 100644 tools/disassociability/visa-pets-FL/ot_library/drop-ot/Defines.h create mode 100644 tools/disassociability/visa-pets-FL/ot_library/drop-ot/IknpOtExt.cpp create mode 100644 tools/disassociability/visa-pets-FL/ot_library/drop-ot/IknpOtExt.h create mode 100644 tools/disassociability/visa-pets-FL/ot_library/drop-ot/MasnyRindal.cpp create mode 100644 tools/disassociability/visa-pets-FL/ot_library/drop-ot/MasnyRindal.h create mode 100644 tools/disassociability/visa-pets-FL/ot_library/drop-ot/Tools.cpp create mode 100644 tools/disassociability/visa-pets-FL/ot_library/drop-ot/Tools.h create mode 100644 tools/disassociability/visa-pets-FL/ot_library/drop-ot/UnitTests.cpp create mode 100644 tools/disassociability/visa-pets-FL/ot_library/drop-ot/UnitTests.h create mode 100644 tools/disassociability/visa-pets-FL/ot_library/drop-ot/config.h.in create mode 100644 tools/disassociability/visa-pets-FL/ot_library/frontend/CMakeLists.txt create mode 100644 tools/disassociability/visa-pets-FL/ot_library/frontend/main.cpp create mode 100644 tools/disassociability/visa-pets-FL/ot_library/readme.md create mode 100644 tools/disassociability/visa-pets-FL/ot_library/wrapper/CMakeLists.txt create mode 100644 tools/disassociability/visa-pets-FL/ot_library/wrapper/CWrapper.cpp create mode 100644 tools/disassociability/visa-pets-FL/solution.md diff --git a/tools/disassociability/AMP/README.md b/tools/disassociability/AMP/README.md new file mode 100644 index 0000000..11f5993 --- /dev/null +++ b/tools/disassociability/AMP/README.md @@ -0,0 +1,15 @@ +# Approximate Minima Perturbation (AMP) + +**Primary Focus Area:** De-identification + +**De-identification Keywords:** Differential Privacy, Machine Learning + +**Brief Description:** This work presents a novel algorithm called Approximate Minima Perturbation (AMP) for differentially private convex optimization, and an extensive empirical evaluation on real datasets of both AMP and a number of previous approaches for solving this problem. The Github repository contains Python implementations of AMP, noisy stochastic gradient descent, noisy Frank-Wolfe, objective perturbation, and two variants of output perturbation, as well as a number of benchmarks for generating experimental results. + +**Additional Notes:** The AMP algorithm and associated experimental results are described in an IEEE Symposium on Security and Privacy 2019 paper available [here](http://www.uvm.edu/~jnear/papers/TPDPCO.pdf). + +**GitHub POC:** @jnear + +**Affiliation/Organization(s) Contributing:** Carnegie Mellon University, Boston University, University of California, Berkeley, University of California, Santa Cruz, Peking University + +**Tool Link:** [https://github.com/sunblaze-ucb/dpml-benchmark](https://github.com/sunblaze-ucb/dpml-benchmark) diff --git a/tools/disassociability/ARX/README.md b/tools/disassociability/ARX/README.md new file mode 100644 index 0000000..1715ead --- /dev/null +++ b/tools/disassociability/ARX/README.md @@ -0,0 +1,13 @@ +# ARX Data Anonymization Tool + +**Primary Focus Area:** De-identification + +**De-identification Keywords:** Differential Privacy, K-Anonymity, Anonymization, Machine Learning + +**Brief Description:** ARX is a comprehensive open source software for anonymizing sensitive personal data. It supports a wide variety of (1) privacy and risk models, (2) methods for transforming data and (3) methods for analyzing the usefulness of output data. + +**GitHub User Serving as POC:** @prasser + +**Affiliation/Organization(s) Contributing:** TUM - Technical University of Munich + +**Tool Link:** [https://arx.deidentifier.org](https://arx.deidentifier.org) diff --git a/tools/disassociability/Chorus/README.md b/tools/disassociability/Chorus/README.md new file mode 100644 index 0000000..8abe284 --- /dev/null +++ b/tools/disassociability/Chorus/README.md @@ -0,0 +1,16 @@ +# Chorus + +**Primary Focus Area:** De-identification + +**De-identification Keywords:** Differential Privacy + +**Brief Description:** Chorus is a tool for answering SQL queries with differential privacy. Chorus works with a standard SQL database, and scales to large datasets by offloading the heavy lifting of query answering to the database. To implement differential privacy mechanisms, Chorus uses a combination of query rewriting and post-processing. + +**Additional Notes:** +- Chorus is described in a EuroS&P paper available [here](https://ieeexplore.ieee.org/document/9230409). + +**GitHub POC:** @jnear + +**Affiliation/Organization(s) Contributing:** University of Vermont, University of California Berkeley + +**Tool Link:** https://github.com/uvm-plaid/chorus diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/README.md b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/README.md new file mode 100644 index 0000000..7bea244 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/README.md @@ -0,0 +1,68 @@ +DPFieldGroups + +Team Members & Affiliation: John Gardner (no affiliation) + +GitHub username: gardn999 + +How to Cite: + +- general information + + Author: John Gardner + + Date: 2019 + + Title: gardn999 - Differential Privacy Synthetic Data Challenge Algorithm + + Version: 1.0 + + Type: Source code + + URL: https://github.com/gardn999/PrivacyEngCollabSpace/tree/master/tools/de-identification/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups + +- example for citing in a report or paper: + +Gardner,J. (2019) gardn999 - Differential Privacy Synthetic Data Challenge Algorithm (Version 1.0) [Source code]. https://github.com/gardn999/PrivacyEngCollabSpace/tree/master/tools/de-identification/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups + +- example for citing in source code: + +``` +/*************************************************************************************** +* Title: gardn999 - Differential Privacy Synthetic Data Challenge Algorithm +* Author: John Gardner +* Date: 2019 +* Code version: 1.0 +* Availability: https://github.com/gardn999/PrivacyEngCollabSpace/tree/master/tools/de-identification/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups +* +***************************************************************************************/ +``` + +Brief Description: + + This is the fourth place entry in the third round of the NIST Differential Privacy Synthetic Data Challenge. The goal of this challenge is to produce differentially private synthetic data while retaining as much useful information as possible about the original data set. Colorado census data from 1940 with 98 field columns were provided for algorithm development with census data from other states used for testing. + + This solution groups together fields which have been found to be highly correlated. For each of these groups, a histogram is created for the purpose of counting the number of occurrences of every possible combination of values of all fields in the group. For privatization, Laplacian noise is added to every bin with scale proportional to the number of groups / total epsilon. Synthetic data is generated by selecting a random bin for each group with probability weighted by these noisy bin counts. The field values corresponding to each group's selected bin are written out as a single row of synthetic data. + +Deployment: + + Look at deployment_guide.txt for docker or command-line running instructions. + +License: + +3-Clause BSD License: +https://opensource.org/licenses/BSD-3-Clause + +Copyright 2019 John Gardner + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/deployment_guide.txt b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/deployment_guide.txt new file mode 100644 index 0000000..19634be --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/deployment_guide.txt @@ -0,0 +1,45 @@ + +NistDp3 – Differential Privacy 3 +DPFieldGroups +Deployment Guide + + +// Docker instructions ///////////////////////////////////////////////////////// + +to create synthetic data with epsilon = 8.0, 1.0 and 0.3: + + sh run_docker.sh + +Data is saved to 8_0.csv, 1_0.csv and 0_3.csv in the output folder. +Paths are case sensitive. + +to create synthetic data with a specific epsilon and optional delta limit: + (delta limit is not used, but included for compatibility purposes) + + sh run_docker.sh + [delta limit] + + +// Alternative command-line instructions /////////////////////////////////////// + +requirements: + Java version 1.8 or later is required + +The following scripts are in the solution folder. + +to compile: + sh compile.sh + +to create synthetic data with epsilon = 8.0, 1.0 and 0.3: + sh run.sh + + (this is a shortcut for running the java .jar file) + java -Xmx1G -jar NistDp3.jar + +to create synthetic data with a specific epsilon: + sh run.sh [delta limit] + + (this is a shortcut for running the java .jar file) + java -Xmx1G -jar NistDp3.jar + +//////////////////////////////////////////////////////////////////////////////// diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_code_guide.odt b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_code_guide.odt new file mode 100644 index 0000000000000000000000000000000000000000..7490b4bda78a2d49a0e491793deff6b6110859c3 GIT binary patch literal 20848 zcmbTd1#leAvL-6D*kUG&nVFfTku+jvjhLA%X0Vu<(XyDCnJs2!W?9ewd3!hFo;bU) zyB$&86*HM%W>r^bWMyS4$w5M4fPuk+fyvjsh-oe%3AEE!3db-%#+x*{4b9OOu zas7YL@*imqMs{X4|EA^9j~jO?+P`o2Z~JF9RV`d?jqQvqZJe20{;SLAUIbjSMO z3I)<9+yF1bzemnOtEZtaHd#?;i}U%QA(5EYU@_^2fSauM>`48 zTzXJi+^N(qc3M*B*XHKQ4U^)~!0&_B2zoGPa3vva^lfXB8)`1S7h$JuD;$gkDRGT@ zhLe3@stfo{l-{^H!@})(TNZJ)YrkE4^^)D>KlTbq>7t$nKU#=qMd)Mxg^c0f)*ckL z_IOznns2F%q^)KcT}MfQ#QFWRVG*5OPcK#MJF_Xg4>8_W_`>0H=}C_DI&h6nz(v8j zyV0z zfl?<@4|_Ls@?&_CFCL%_qVr^4h;`hyp2*+Y&LvN40VjbeHk$Zu67U9 z>gG&b^e#n~hU(cu3VgkSe3wgiJ*#TEVF0JhBQE(8{jW&xrP%O&fq5wbtH3!pSUYU^j}Qi*u#C<6$~^E4Vqs)qblEZ za`YAhDSh)Bue=m>EY$QGCxe#ayyk8z;uXdU%C_@yRYMFAkS=D<@R=d2`;a}y>8#!& zhbGT;N_fWTidlj}=dXe^!fb%yfm`SYe15(~*n;m^cV3h)5|dY63k7~f9P=p}|5~n& zzLc0H%ID3SXR5sojD8ng&{k?`t}uh9no?d zNb9WZw|gC3gqo=$@%j>~*>FH=v5u~n)#dLS0(hZrS#Kst>!h{nL);@8I;K|iCmzI8 zFFR$25XwPrlIWLbmJJ_E zdn&Uv7v4^|*QRp6%~o7v@~H^3$aFXwCU>6Q>pPn>HSFMDyd0`2p)C|Jzj;z3{8}%w zukPGtwT5J0LQbt(wFxh-*NmBcUhRt&d+bR}}f2EXcjC^?D+09Un!R+kZ+9H43&{|ts zIEMB2`t52jimmO5`>h4ZQU4<8)=nl%_Cpu614?`xC&=&*O?OqKY!`Z=1*rXi^n70dgBjAOLvd?fhTQTW4Pg>MiJAKvM-99+MxtJ79ljsuK? zqU!Sc%&|M{tyC&R;3)wrz@%0wm7}as#Qu$!z^I;t5G!?=c&Nr+j~Oe(hz9$@uI;$t z{ONmcPA-ia=8!Al*SE<>IAr3rl#l*yq9#T69T#Qeii#!>KS|_62+fcbS=Mxp4&!8%&PUO z*)zU9buan^j21-CWpG9W{rVQ2;w9Po8;^2*eP#xsi&x6se_4X1fr)Fa{n%v|p!Me< zTvVc0$9Pn7X*Yc~6Wx)%;&WfxKM+Xp3_)j<)d>n4bY^Y9TV%8*u02TX1KW9(0_`~IV_8PlZPA5 zy}7t{Ow167>u2-mI=vd7Or(-$e$QyQ!F8_kxW?Q&azs`59ixGnAA?d7cg4Q9Mkbg2 z9~}$2l9)Y9jaZM9j9AtHhUt;6C7GeCp1)syrj>Yv{?+@I~8wS@M!O>7s6D zBwGc3xQz1C2AM09;-^m}-8cX4+nA^YBl2AU_GLBpP<#u%e8tHFhB*q4dn76gj~M6V znf;zYpQX2tSKJfMR=FYBmoBRVyy+LU$i*MZ1TZSPj#6B-)Z}+E`fep-+|O`omJhNs z99O9OjvI`L_DLgF7sE$JQ=3E1uyvC`yS7(jG!VQQ{eH8++Jo;)3*k;l2(&_^GtO&; zNA%2<6i`Y+x`|#_OfBkNbg8~}x!)`y{*0r{uy{Y$UCoSP0$&G~sa7&xB#6l~m z6F-GrsF+=)k+2UA_b>1p2j$ahXL<}|=&8_!hzKx-iaYwp-OIr1beW*4%Vd*84*vbx zFl%azPK-@FTu(@ut%xIcG+LE#ud)pi@x8AiS!MeRzP?376%PNRsQ)`24Iw%FEiuAo zL~wrPB-rY{Ifw`PEkZ1^+PVH-&0YXrV%t#iV|3Hu0%JeDxM_`i>W?Ct(g}+imO_o z)kjD~^9O*vZAx0?Swjl6c%X}jOsPY4WN~-r9{#+{|L$i)Ol0HEk(ceAJ4cI}V(mMZ23mbzv(Og*M`KHU8<*6L1lIA`&l|a}ngJ@w_)M z2stjl5CE1cik~=<#%r#E&M_8g5-g+Rdrx3p1D?lysiMSYsJFO2nl#0W(9a_;j(B83 zj7%o6uMu+x;_U+QuqWa*1v9(i1qCWA;j*~!e}2pg3QCsp5ZR7MVjwxwlZ~t0!MMiL zj5Q*6B}PXEoapul-bt$T7D?>Mv;KLI?V~r2u{(b$T$+bEKh-qw}gQ3oiPyRyI9wxMB z6Ll1!x5GvQAxUPdhbN&q5r=~u_j2$DR?(eTwkiz`PF@ygadMK<%bcyd*IOvT1!iJQ z{aaMu8+IFgQmoSo$VcejtA0o3;KuZVl*&JqEL9MVsA=PdpM4&hK!Z2|VQguh#wc%0 zI>(eAm44TXC0-k6BD|+}NW118cM^z6n{F-%40r3zLj1xu-sBOwJeZF0*|@WwtsFYy z={LaAt(v^qn;%op;fu~VcpWX9b7NAlefcr?yrTE~^YOd|YeIxT20Tq+sV@zE;R)rg z_6IY|5B?Lg@HDIed3tT`NpxJz1bGIj^jPzt2;8_KWfpH8X*JEHIJ?uf@HX1R6|8BL zcJ9PoKp1=EEGp(+KVttLV*E~(p5d2=sN$*m_v_@*RW7tx#m9uVV9(3Hlw9<^&PHBS z@&wSOxB|mcTj5`wqu_jFSw>-bwI1+>vkn9d7&Q8zT%zT|2d!wk4QoxxL=GHK zOB+wBStsb(^KIEWnAYtNdxz;~2a^{@`exdkc4}yS>R2-yU;q;CtkV+2sE=rLPT-^ogV>(88XYV1b*_ai<%G zz3Q!qt@Z_{6kS$bJOp zPO{UeKY8|yqdgw2Q{Vu^_73u*w+NZ_D&$NDk!y|DA$7gXZU_X8>bxYWbMWgAKu{cO zz5>ft{4=ig60&!w`YxzPEfdap?jS6DBl#~@iX2M> zHT<*zL2-d%cmnu~i|F)RikyQtIJ(T3^F2BJSzcW|#vFvs7En*{ihwra{A%te|H+<2J{NFbmahVIProwqy^M`2QrEof$;jvm@WCewH z?=lmvXn(nV*Zk0n|lL{>leYvmqBr8u@#-jylmowIl7Gl#5pkcD{3;>ZHK& zx&9-lCXIVGG$-Xezo#a^9tYptS0nM4BQ-K7ADGR|4YJDueoLb&>YGKT1@?Y@Gg<4_ z(ARYR?E{F7`WO3a(%MEg6AOm1x(DtQNrM3{E4WJeQ}JNLZ16Vn;>_rSePI{T=FfnS z?T#={wUgW;ve_hnF_a^4nGs@#oaFovnr;pPT?F^p&J-MI zL~IeoFZ@31MF5GBYbQr&VHo-r!ExhU70 zC@c!2ZUDK_EmtpG*^!jC!^B2n2g18U|CFEEl2=OtxneLsHpr($N@FS|`ly?@6INGh z_l!H`@XkTY>yJv(L{xmW^#rMlry^87y^)>YZiEs>2#T9DcFmtq=IdXjCw;ZmEVW0a z?9O4l+Q&UMt3L~fY8D80>T+Pk7q5o&vmvSO&MoG0_)#4)+tgF&9mCPkBkyS$ zbkD?!l4d;)@7dKiVqvaglSwUA_Js(b{-{`c=&eL2hEAGb6A#fIgR9mPOG@#q{P97Q zNR%hU)aDhM19K74glvunZAZow6S!qE9WwdxUH_}VOq4is{FC|n54-}S!uY`&sAUrj zM#-xbE~y{xV2`am=(}HEULA~n0-D{<1C?GaoNmOowS1&JqsmkPBy#_r)i0L#1Uvdks4H|IjAprw%6(s zxU*6`L4x8HQ}Jgk0QL#2wk)$IaETg?eVpCB(jHxYVHMxiutY1yJmnbjOzI`-?#ES{ zo8fb0lracs_VqiLyJ8j8D{Nw85$%{wY@9|T$`-_-QU5PK!Kz}>QozwyJZC25$yvCD z!el-du|{kfK_6_(Ae*xxyP}eGFVh~!20ocW?T3=$vJ9Co{`K)Z^viv>{wMcspvReM zx2M!Ux4QK6w7RR6^-1SPWU>_Cn{(s?qhKm|!=SAtx-e^*Me_Pq9irjpvQy>IlW+~< z_Mq4nY+`VlEEk_k9%byCWuGD>jqtQ+hCh=M?e**x9(uoVV<(7bL=9plXf$i}h_3Go z6x`jBk><4P+VW49o(ugD6oY>P61wQ6OBRK;mewrDHxDBdQBe~guLvEM`2p^(UhejN z&CKP|TfzS=nmYvlb4uDaN}~HNHpJTL%=8_$szr{caWcy9+t;3|T3mp#V0lycuYGEt z=)Q>${Lp!R6#}gxY@>~!>2`#IggsKtZoJBlbT3z7y0Wam|4@ zyyA^+5hrG@VpEmQ8eLGiWAKZxX9C<4LNv4C&|Cudz}y%m21rPui+5Eqiq^iG4I0t< zd*(NO>?F;}q&1aH!*AO-7Q~wz(Y;y?Q5h%8FnpiYNixUTT&{z@h&{jf-2hGW&+A;xQ7?%@Q*ke z;?!XJ;o05EvaZ(WTHtTBl}5Mxm8-ZX#W=x(neNZ7`{`-u=Y%s&vOj-!LijP3Htj=4 zcl%ZOkJe)C{igZnjC>5Oe@uSp{~d(I*lnI9NR(dKUOWa$qjy-}fj*N)t(<+hV)%ub z=f*sVhwc80@>ltq8R~-(?->fJqm$o)B{FsAAeBRcb(2nd7IYW#(uT$a_RqD=#laQi5Z%ZtKT1E!Z z_eSazvT1<2@UYpy9#XHw@K_%;ObL{oR5~+EhioU1Lqke?j0NL?KbAVKU{Gu~b@n_~ zNBiyes!KD$I}$U0->aeA8Y?H@CUL5L94i7cM+h#L$<>dtPW~18KP*IPCfA`eOhW~v z1T}6E(1>f-@##rYPq~Gs{)e{$f?kU7v`und0xA^(MQ`L5;tKc!WIK2F=o8Ibj{0fG zlO-(-?Mh!Dm4kelz&Td&peT5|m$8?;cVwH+0$8(q*ZoV?Enp|;pfO@WW@>1~wa9_! zPv`fS?l;0{pyOtR!2NVwQ|8cHpp(qEcsiDDGhGt_Sz|_;yWU!Ln!YkQEIgXrJKF2K z>W63kbYZ75+rY&&JzqBi*Aa^v)XTS zE?awEE<5X{BM;J*C?~5&^;gPq;NppmmJVCy#vzVOIKpOT7mu~S@{k<6n)a_3s+hiD z)hx)gi_Z(hOD}Ea@TU+t=>nH_9150ZNE1}FhFM)Bh@nx@4a)gQ2~8J5a%B*cQyI6& zd?+9Zn&~L^)p2syG-r8j5(ydXu5r%j7PdGLNF0hDY%ogm+A7W93aEE^9acj<;6<*< zf%PL2*R1Fu+h8>7z=)~G$XAitc`X8#*Pw~6L*|ZoeJ@;x#IFvj`d1nCJp&jGxO5qMlt0bS>I1zF;RX0MAgT8 ziDCNpV=e3-yfoLb6bXtNM7m|Vd9liMPLfGV!$PbCX;7>YvVQ4@4%?Nq? zJH>x?bU{|l_cLbDr+OR>^@BI9Me^epbAB0hrDuq&(@?0)QXJ^`ocxZCk}b=X{9W`PPlIObA*)lmuNIzm5cB4!~J z9lhr3J0y~ZHJ1)iKrCl>96>J6obT0CzOo6)r_~57DpGUa z@AOY|hiqLerr-Rhb={JtK?RpX4UjPFS@DfLm42%@Q=JXZ3O;SS*XyKHKJrP1N(-xy zIm#AIeJ2?%UQ31P8GfEUemhVjU+UcWynA1L36Q#w0{xjF>A=RmJtg3sBbSheXZSV1 zgQX91K#PJYvGSx>oo~sjN+T0om0T8|AKS`-x+H3wDMX@8utCpqyw=bhCSYBC&Q|{I zI6bXXg&kNysgQZEXEK3$PuQ*%DBjhMkyrHlZ@>;?Q!oW6u`n2Tf&4~x-h_|S>iZi1 z>~)Eg66ZR1#lvLT5GlY{Zu&m)Iqxr2sn=f0dgfzQ@TcF820+Y-ul%_xP2lEN@5538 z>Qca<`A+h4N|c|>SEZu!YZ~H?c!2%I2SKqv_xF4+wi1zux_e#_215;ZTRSo&EPUy2 zHe354+e&HxkscW4`K0;LfTFxK%A(@W8IX%%m<;x&;2nqBW$9*5yss=-vtma`#8uhP zqs_T(U&AhggTmj~A417@$;m3v$7ZLoNqowk_}>*HTo$S4a@X=csl4Z67dGO(vcOVr zPZ1fTZ-Y!$eJLJWAe6Hz=CM(ptcv1Lj)b4LBTotkjR>?ahcK<;gQ7Cr|D>P8XbJJ< zV_YTAi^1lk?~!SvxL#mQ_m(cS``#|i<(#$t-jN3F!GT0{5h-JB8A!8{>=i>(T@8n!)(y7@S>O)XPq!HzI) zc){{&+!wux$A>Ynmga!^tr?^v2C7HC?LA5fY@ikUSQcZQ0Q@`7&pxR?M##$V@~T8m zyc=|*j?YcD(Z$efe9^8i^)TbP^dhW(a7Sh4LXz(H$WJH)LS|OF$%fb>mHVcLeBc=%j~EN4v5 z@NKoBb&HUtRw9jZa5UIPBws9*d;<+hZ&odPuvq#W9wbq!LA1>L5jL6DXd~*Ey9tw< z#<|=80)Q;wKeN!pi+9Yv+qkP0`!2`*sGi9@gWNLPx2GcTi!*s*5mK&u{y7v0W57}~ zJJQWq4Zq??Fi&ePGu?leqXns#I#ZIeF7o(?be1WHh(|u52%9myi0B4wv(QBPIU2ax z83HP%s-oC`ihs^pz2Y(l?13~+`dk611AX&M?}Y`m#FN=^-gm*tUx#DDQ=fm0b-cNI z!v0|{zvCEtm^v5wnVjWwv?&BjGP<>$o(x4NPm6ZoU!FYB`2_^fWb7jvYm~}%-B0Fv zfFQJg=A^w)9kZ7+{LZC546lqNj4!@9J&i#4&N1NxKtJ%)28WGq_pSEFe=!iJ!lb zd1ccg*j1iv4U%L>4&i{)_nfl%qd?I4W<(#Er^6gVa?#ktV1yvDb@5b$y(viqgOpvO z9IMFhR3hF|Q|x4BU<|D>$p<&=i`wAWR__KO;^SwLSZ}g zn%K89U|XhlbWWdHs(52GsfMv)Pw4JXP0RF8cwY+l!{=B7vI~GnX~Ic#oesb77}@8t zAAS2Diwv~GiY zuKgkn(N<=G(z7Vf;cTscxCjzKIK}=eXhLSX^T+~EQ}?LMz7&9I*sP|lyQupMZ_pO- z+GWWlRLwkWOntXIz{vwa5X`;w;=~ec5$LnafMX`PzhdFYoHc>cd>EcmtHX+yXf;*Mmqap?D;!_*!Q>Ez#Q~^&KV8qx>59{pNVZ@ z{tM7Fygz{xf$8~A^I1oG^rAJrZyk(!2{(!DBQ5;u>&4+p?d&5#?sYC~!*h89y zPF^-1xuh|ym-QmB^o>QTeuCg>Juk>u5(>V<%?oF8@M+<61l|L-`xLI5<^S z|N7?mHvp^PK>NtEO;z5JbXgQPF0vmh-BZzgy^~7_kbnjFIyAoGlX6BViV+UMxIMrp zlj4|gW84rGtvF;=UT@8-M&`Zq3XO)dTdA}RB0`8zy$Om?))nM)krI#8FDo2l13(lD zS|W?aNyFoL@FY^L$}FU5m@z(_Xx?qJ*_P5me%a%n5UV7%4iUvvruL#$+Xm9oew|3> zkBTO#i5HD;$9s-Ap`HFlAu4C1bAM>h;}?3Y+|l6T*E3v{A0lpUjH^4%M9aJm8b-lSrEt~~WgLc}0RoVqwbU953JxxBPv8PB zn~V8w>GSjZyf$VD?$}TFEpR$wpNg8dFX$P~y|@*ibPE0Fton;q zIssjaswZYt-TsP*&2Oa8L2$xFg?7Gj)|4E^HKOhIKY+K zH=9bou)0owVi?bB>aJ;fU}Ae;1_HFBql{4MoMWh5y7`HzSlJ<2#Gma&b`vSchPHQC zG!<7Kf3-ntonsK&2&AedBcRR6K~01S49}HslYx3*Evc;a5WPMRUg_4lm~hXu58-g9 z8T(o8oeMS+UaOUm3hsB}(K`D@`#SbZilR~8Vu3x?;W9rX^F5mtsAPEy!CF2tC?QW{ ze@LcPop^>=BtBI{vEfTP3YBzq5;tBFnvH8Ra6P@y0< z+Z)|;*FhQ_t+lpz$k5DCVCDHs4p;XcKHH4>qGl9~C~V^8b{8I{%US4bY!^Bo)5B+&WSaOlujFV)VlfF5XKgYa&m~ zSOSsth}jc`_RGCwhUYS9n^Zr$`oXSJQ1Qtzt=4RZHZ^zE=7{t|sS~L;UT$zUMLj zYVsyLU1OuI|C=A2moT1z0&p(!8Gw* zsP;>uu|9KG8)2O%pS-s_fO`St+rFkJipCH+g1e#~uAAps4UY%=CKSqY%+J-g zC5HFSZI9kmB8km{-PT9;A~=L!aNcqc6@2Vj|JkM-T~J(|wfjXx^K7x)IP@}-kMS%; zZzL~n<#t_w5({$$$%w}x5~9NCOZiott9yCR421+J9{{}K4iXGTsa6J-#iH#`LzMam zFX{$N+`q3G9Ry9YxzJo{ihc(+XPM39Mno1D_@{*&EU#x2Wbk0@doMKiTm1wK&!lJO zy0dSO8?>=M$Q2Yg6QnV^gR3FHn+`pR8hiP*A_A{2`1dNWq5sCRy7B+}dBcB;x&1$9 zPzs3x%`h5;R~HBr@bcB&&|@FGPdk8 z4vK`jDj@+r*D1ka!Zmm2TZ&1STX0GeI8cHixKiD1`PB^9d7=w87|mFdOGo|OglmS3 zX_O|t+xPajeh3$R-?tx>roBbGqQ zGHI=nPH9s-KeV*$@?V4kc9^XA2o)`OAa*mWs={9*TIy4Hzl(>>8Q#QC)aE?eP-~`_ z&PBMgqwDaUPxS7RV;p?l=uhfSOp~CM&gM%CPWEAvJygKUN_iIF<1vxprZ3}koU88~ zo7;66`Ns97MzpwaVkGt0v~da4oIFX;$uOn#QkZq+U4sBK3}z}NmMU*bO?E)_B6oSt z&`_&a@(L>!>nzl$6)cMap5z$kepvg^W^68Gd#0Lch4LKxh|yYaq@csSSF3CUR@*kL zF*{*QnH(;+dD-7b2qd&Bj)4qHCU(bfVR~w&Y`0@6^bYwK)l=1sj@_bh056CK~HsMF@pL{G$aJQ1c+y0^CbH(r=e@$z}!F0)YX zi6~~0>Wiu>WtYKwh@2>SUIFOxgiLB+Y(C!ll3FuQ z6+9LubL9enzSdYd5&9e91-D(cECAh~psYAa+Oy6HlA(?TTdD(hyvIq}wQ*mzv90}K zD=;o{y_Uk0378>JKSON6xR6Ucx)R2hnhMy`COzVXVANJ(CWL%~o0moIUbstTB=lKX zr7PBYO&I*?NgjfxqTpAs#n#0^9=DGdynoQ7;K56iGoE0zU0mtjtQO%7KtPHp_T0x6 zP5Uuh2OehKp`1sz$JN5PqA0e)1c^1 z8j(o`0wwoPPj5gyrmrOXr*9Fw{Q1PX#dR5v<6N2U@mo7TJmy21yb*d8`qNGEr&;mQ z^SW<*dI-Q{r~~Tjl><(Qb0%&RYO6dNH5J^KNW^T9 z_`0>abl-F=`G{0np895AO2#$i@rxv0A(1B^r~YKYaqs=D&v$H|y?GijtRP4fPpX$d zNxC^_*}Dxd{iw9~sKDW>0Dgk(PufG=RxqdFM93HO(C%o-Lbv)+;`tQ=!9WOmb4(Z3 z68wpCH6iPjJca-Xok`i702yLbf6?8IRHX;Do#)F2B!2r2Ji>fdl(co?JBsY-KHk6#F%FnN zZx|un<3^E3q^E_8xuJj@h0?HLVoW~>>gNQXi=^U-Vg$E;E zX1PGrFO9&hO}{n1drfhE4dUd9F?#V@N^Y_NQ^%nq-A?Btx#XMO_=vK_c^(QC=|+O{ zLPl04(b9rX)6h!7VA)3*Ok1K~H~5M~!e2L{AH@#sa5I}$BtIh7Y%to(ai`fSZfa-VZ{z=k=fVu2-g9pLG+PD6V~%*81m!i zA=vJD)70hGOAAv`)aM`uITho~?YYpe=`(}cP`wFk4^P*iRBCIL$-B*IZ{xZMNUu*D ztjE=TKF%PHZ)I9)zYe^-bxN_DM$LvCQ^WqmNr&f2NBG7_nqzr`5uiZ-fm+|IV*msA zYa3#?Ay3YKU!?Yag%(CykBq}^5kWqWiYHvJPpUoFK zV9h+^W_7>6y?%Vu7XKMRfcNyJOxp4riTqrTeaYeLt+A6I{62nON*6H^7}M)B>h^G6 zddhg_Z=oFhtmX*&#%D>$cQzZ&&#?JQ0TK2ZxI zT^~k{x&_&mWriKw)$fJUrG9gHn1lSXx&5V?0*uq0zrNUnd&Y?ogE@#OMe9*(+%73o zpiI@Qadswvcg^a9M!1ifm8wIE7WS*HZfvNnlX(N*9teoh>S{R!x-&JCVG_}&^UN=6 zJA9xlti>F#?!bUkTcmdQE*aK|;#FIV%$ZBJj>>tmyfttGoe|TmDkbzbKyuntWXX|t zKGlG$ezZwB-d?^Wg?NkAvxyX+j|LEO#kRF)b?2l;BnZ8!?X>UasKw%3WnF0iXv*9 zqf|e+4EqsJZjAN3Wy7lZ<~ZFwPywwVQNUn8?yy>8-K+zBNpNjI+E6iqk}_8OLdIt@ zH^zaBHdO#Y3JH@#&TvMw1(y`uAyg$397Yp2o8Fc2;Ct;q08BRlZLO^L|x z9go(2c8x-wA51ndrKF~F-2~S#ABAbsT>f&NWP4TzlCCSJ3J_|Q+>1O(CYO8}9_Jpr z{Jt91laWpd<;G@$zzDzCAc?1aLTsnMmcf;C5StDgPo^{_-SO=XxmFD(kgN4>B>h%> zM$^tfQ^d8M56Q(U?rAyJz-PU_@7p@if=wE}`A;GG>k=h-e@^}CA2fH-Cj8`9s!;Gj zKWGifpt`L#kEPS>jWMDN*w-&};QDGpAz*X)ZI1!$9vRTy@>3|!cT(?##(NAkhZRj7 zk^3@*?UDK)$QuqGACp~37j`dO1b{z~{}j*tVmd`7`0F!MvHVx@+<&UcL;UMh+ZcJ; zySgx#7@1g@nV1@yQg53W8p3Xy8XC?*vi_?OPt$q%674VLKka|*p#OvAZ06!(X$Nxt zr--jnTgQ2o7sXGgn{@99N_r$l<`|5I0SONAhdE#?xYLlMZi#FZ{LhEh%8$fA88}Hr zd3sBY)$ykb+13}Ph8IujIr!cc{5x|M8RRCw41h15bm~&-HE7KHoCvV=7c> z=c-9vub{rw-%otrQwHHam1zMUKrU40JXdin7!-kQ=2cRIfN|z#K7^5 z&Z{LSNJ~%|V>E;+_0^~BWHW(osp}yTqv(2!l#l5!loN^GUVg5A+VYR?+w;us^QFnR zgE@~lq1^bw!pd;fKSpVeIiO9%!R5VN3(=MprKC79r`Y)o!Pbft(`NXhgkm*)Cd|N` zVL5G+i49NiWGNe$CwH?AZ7Wx)BlUmUnY8*-HACw+I;pQ1oTiAE>nBxeyvZ*5&~I;h zCdj(R|1RI|UBbwJr)xT*#0yf+IlsFbA3yc0`%WTBme4?CO;TZOxL}4LFOMLBHu{hx zm~>CMJKpQ$HP2{!h_CwM{WIxl-B$`(#NYm#1>I}y@RGA&*Kmq$zg){$uJA$&U%ELl z+90Cx9Z0h=nOdT~!z45&{mUEQfcOjkY$}PKB}@c02eNQXS%Zf<{pH^jS9juOC+UE2 z3n-CX8cyS+$o{k!w=ft`g&^TNVo!=dZR}?lw>agNv1%?){wfuc04hJ_EY1Q97Ohus zmnfkM7Luf%1}#vEuXaWot${f*4bh3Y1$J^{hb)6Qg43t&7-(6X+X_dVCZA>jqqBcWo=xB<8vbX#f71~->fY0X`*~i`Dea` zVEdfNupc5UpfxGYD-n*Q9pRgT-ZMw!KMY>ql6TI`s!7x-POd25!lFndH9IuRl#r9c3(i%_t<`O(dO>;1Qy7nd zu>}m$v=>a%A4pFSPF$z)OzAK&uyG4NUl7N}YAqc>c@f$qww4p3OBOpAD2re=jsbJf zmZc*7a*crDm#vcbtA`|vBf?OhzOUta;1(^O7g8(+{AHJjQ}iqmbCyV0x= z@&uP$OOikY1A*$gVZVw(FNT@oCl>6EKe0>`5m2K(t7Gq8Xp{Jq537#YE7URyKV3$}s&TxVnBBy(nTdx9wZ`L{xpfFKM84r3bI~$} zuv#_(da;>R)F7p@A%5QGbMvJPKI$dw3`CUgTZ4p{*LcSPF6I9GwPt{1{POkITN`~l zdtM?cdex-@fs;e#B!ba|PQl*=ck=w``M8Y?D^8~xgEGQ=lbT@qdF*RQX#*57j0(NK zJS`-rcOm&3r2~!_T*gGbHc1pGi(;=^Bb?^0==dl$L7j>T=oTR(7st4N%r$++mwm$g zW658;^*_X+!NAN&{&&{*pTb&a7f&0rf7#v*ZMnELPL!@E4IKDYe1`GOunTi8Y6uLt znIhpt0xx)rh^A%f0%711Z+Dj%Fv%kGmiBCts3Qz|!1Fs>f2l#Y=Scl*ii3!jBewXS z3^S?31e&(ZWgt`Tr}*9WU5vZl#{BfBc|N(&QumaW zI-V3uMZn<>%(v6;#D7k$0*=INX*LDQi~-}lMtF7_IV?+U6;Sd)R_>*VqiPmigqN@6 z-jn6z>qc>kb;I0IIs;mlkVwx3DBLJ36|9Zxy3iYyFTDFjrn*hBRbh%s{!-_!;2)uU)5!4uljlp9=33YFd%1ZRw9UjX36>p6`k<p7(sC1AcE0F{o)x?602S%9YZd>v!Vyo5*z9->hNl^A9l^>V^@V1v?5@kq7)kV zaau!$Diog3&U_2eL>Tvc9(W3J<-@GHHnbf9B*5c(8cQY$z+knQii)m`T=DP4~ zrmy2Mi3IZy<%QwZttY4|S`qjD2hm;3?Q)>qv2Y!h!9fFjBXJH(uj(0J`Zj265m}>kX^Pfz3R6n@-Ilr++(fd8+GP zEF+^@>_QAlC`~7l?gNO-3#V1iIPjDZMz~y&cYrAR8h+%*s6zhbq>mM{Cfx+nkt~IZfwMYNqS`)EP}$zowxt8%BS>$lCNgw)QDfyUjWKp?l_*o&erp_j`5Is`vl2O82XN zpyR;P(EwKN68GtYMyDQwXV0S}w1NDMJuXZ3?YbmATUPj4uS<7$uz!oUuH`FD=*)^FlwZ|D8K2N+mrH3)7x@% zd0nsEN7x6Z&bZn5x4g1;Znz(-;QYhs!UDJI4>dy-OodS~C)8P7X!pW64@ z<=2ten2e;2-i?nN19t9FooT&$wxnYAfyC1bdNvrL=){(yHgvG%^6)Q)e}Cur_x7yU zrkt*^8TkM4v}a%akmuy!punxmnHFVgr135JEh;^0=AHpTrmVHYA8>Tk zcr{HCmJPO#Cvh`TKV^n zmO(Yl3*UA^w>zqqc^1rjwpst}@uk%|)lz=U;Y^0FtMbOV60dCqLjrLvz3~=r%Uaha zWA59|^K(`yhI`WI@>}mY$FC1ZdyDc?M`*(RcOXMist0;)7`o={TubB^e^OT{YYBM%`4AL z3lGH}W%r98*Q`t(d-$4pW9*;zP8+wbR{FPolkg(%p>fbNa?NQxeZ~=ao8uG*2mM<_ z`Uo`3f>2efJh5$gP(=l}%rdpE#fP6k5Ft^gjT}hD%x#Sd$(kvxK01S_V6M3`%$OHjG z7LV|+28GK~LsF~N6OQmcSh{F-$7Zp(faRX>l%SDHl@s!m4q&nzAOVoVD9qTZkm(i@ z8aO)WjXprmgpcveQ-+957ZKxu2QCtr!&j&jr~+0p5RD8+1&M;xausb2tdXi@8UZk= zh(*z?fQ>!_i&G|Ivp_zB1v1#61mr+m5aM!~>=ok%tVIP<1fuA0861{`&4xf81oD|2 zPyi6^xZvbrQ5r14$fQ^=R0#~}I5kNWCh=X%WrcEhACAZHCkbmsBo)E-_aYA4Mtr>9 zBAg(@l48!17#m%&d2Rz-Cdl+zv|gd&uxVs9KIkzRxM;HsK>;r4lLRhWm7-8e8GK(p zYupEE&SHMzDZxQP3N5NYXy*K*aGX-cK-4KJR7l4jG|Qmi946aJbsX-`wltN|iVRr1fl^^9FYjEm2BL)6ABHPs*Ik)iOjwhy}^;Mk}K2aqV3&KEV zr6jA{?eOxngyR#^J zyFezC+?o1gc>2Om(m9=Cd>wxo5#SDD>v%D?T89NsnL69SKCui-Jf#r)GFvT*h#5ge z-iKnBwrnsGRA%u_Jr*TsV>qgeEPCwH)`}f)*_%ScL1kpQja}Lr{S&s#Iy|4P>W@HJS#ELc6%)TWYY(|!>$t+PYXZG#j2r;t!Fo_)!duHE?bu`11 zSfOIj{;C}YHa%>!OvR#E+pz_sk-d^(cI;u&hzUf@O6oB_{tFngTwHGTdl*|q>X|)$ zGEWwFp7oi(ttR!n$yyV^G^r>2wwlx(E`ASbjntby0x?RXb8Ztg_PYep=odH wM|%8UAf|~e_xMJT&?1sbU4rn%oh*W2J4=07u;X-G%nkdT#8$2|A^6?@0h*B{cK`qY literal 0 HcmV?d00001 diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_code_guide.pdf b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_code_guide.pdf new file mode 100644 index 0000000000000000000000000000000000000000..68feef492fc76972ecc068b86df7aea954e8cd60 GIT binary patch literal 63982 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

ta59*>I8C0b~fBDi*1H}euUL@L| z0R3#-~!pRt9xG9DzTB673YMd-<2nE6avt!(BmAyq75IY)4E?Q?5em9s4#kk z1)|53L-LVJ+Yeq}VLVdy9XPl0LOM3k4jiuWWswBwC7~lo_g)^K{2!Ze^6&14;KHfC4uPTC5=-}UUN{*@$< zc`XWbNj|ym;iACl)~3qJ+{a2pC_b)=%%m^cis)-_)Dq7zqTOnEw5L3No@XE98fMbP+@i@13<0v=+Byg)EWBt?xRsx1bPD{hF`b3}YE}q5uqIkn*w~3e1jr}WZJPMgOxM;iMZEo@rf%~~&go|`cx3#dZVLZU{`t76ySV6ew zX$!5e8iPGF`%z$RIh&8>`5aA9^REx_Q8#mbwY;2cHJ|Nue`B5j7AhouYu|vm1f-x3 zrZ!!#t5%JsVg!* zCRTClSV`=pt%Xs=s_1d>U4d*9~HQ}8I$dyj(*p-r^0GZP2G)~>#DiNs*) z6`#bz+2cZYEab<;DnnOTO)Zfg; zug8e!Jp89USKyXXM^t-au34sr`Sf)vc^j)h$eW6DV93Szr<=EanD^Vv$2d@X2WW=Y z)J^{iYGW|yV)pn}gg{ZZspEIob`5k>!djwLUy!W)4@<>A@Y&d14b?MNm9305a?j!* z(+-e6u2RCDu+x=+kj?7C?_Dprkok=?d1i96yq~p7VNXa=Y@hW9FCkQ?PvSi9$d8Oe z1`-Uu-oLL;m%IO#w>rj^nc2cGQ~*K(w0>XLgO>I8yM2%gI8%gG1^jg7D@Hnm5sd_J z{lcP4JiQk%rocD-Z`LEf>$92vzEUMf^>A@mUGVUI9yvBU+j(!3u9m-Jw4PG@_f=6u zCa_OPD^E-uC%NBc7I#Kat&?U7-LEU<^B$r7WF{7(Ub2lf*I(fg8aw+P=**O3NS zqojPKf(OQUMI|1YXO0XKL4&f_s{A2all|CBBR0W)N3ehBfSQ4w;Le55`3H#|?JIuMb5 zHry9j5cGFg;vpL|Hu8ZQeXTEi_<VKEv-fJtZH3JUX_m-D592Y8V56DM8_ln zD!(s@*qcbGu`qD5(ET({Ay#~*&a5skm+EbvzxYx?5;gpFz%y(t6l z$X7(eoAu&sWB{5lg1|T`siPm+5kes8x4hs}0R4d}C~?%>U2(I3&o}uQ|vj zB#IV<8}iasK*d0Cog7TD+Y34?1IoYzf`%`Sb(*yL8ZE%+61la zwTD!Pmj;YohO{eJ%Zvsd;~??*y`QP~CU<)3P?Fo~roUMb6Vct2oF^=}PU;ZNLN2|<}g!wm<1Et9l&7bN>mE?eTHD<@l+kQ?fo@D+U8{wT@7e|bwosK??I zK^I5+&?8?ftyY$lblU?V#pus#bdk{!oLV}m^4zFQigDr~B8h_F>Dyw=@9)XG z_icPL`4s91(IQYl+kjo0qFk%fX!{wjLjS z6eF2H4){%N1Z+?ASR9H>uNC6YAPDMDrcd)QaJsB6^*m#wG|@!IR{ihcqWv)4*@k}E zyeht}SNJxOWT&?AQ^fNuh}jv?te49)33#gBcatFr3a3YBdau`S408tvCRTg&VI*Mm z9i5>~0zjZ~1HtzgH+|Gsbr9?gvrg|QwIASeWCCq9qfT2<_{-SQqG4J_Do-(^nn6Tf z?Zo^r0VR5=N6Qrk^vifnTf{6KaCzV2k0rU6wWlzJoe_~1qcJu7yN8@Fw{-AB-Pi@4va~7P9qcqOZCYtO z*`5Kb&}~i?y0m-cSIgJ{$}nB%XOCI&@1iE1K#!~W%kQDybcr@NSfkI>5rYWXGGTCs z%4mJ|DSBJ~?HWP9LwLQxRmM!qf*xBRegEgHPiF&@k6NkYJ6RO34a-SS_~>w)=k=#O z6k9`g-FtkPeiD_6TWlL1xmZ-IK-k46qFqR|m<7y!B>I>ZTGrm^58^pzNzNJsrLLQx zH9wNQ3C#&333_>mxr<+{5w!3#MA9x8=&y+BI4lxTq#&aj#9epUAph|5EmA8yR@wCH zJ~}MKRu&G3Lo{BTSVZ&k-w;*vw;(*NJPW*+bm#{Iw{AeGQxM(H;8 zak~Rs@pJ=sU#DvFHxXXK8oixh2KXO}k-cTMA54Nw@WETj%a-QwndkhsJiVs-)(X}g>A3+%yj@y6y1^Hu5U13PJR4L2L8 zGF=oz$`YPf!*vaPGQ{~YzTNJe##~%uJc^OUDfDXDDYESN$9>r8>TreORtY7$KLFZk zWDI5N&Zx%8r5ehiK6v-*W@CXnvPA#fJVik8N&fBeqY9zEH8~=3?(It7C#CfrJIqw& zryJxal2_laRRCM@f~apDYbRgjg%sggZbcYky3T$!Jd?Y6<~f=wQ+48c^XH6{xVE%3 z`j}+sM>0%rB;!ZrS9KlFHdl-dkhWvk<93uhFv3!X;1Ycq^^MY@2qFGUbY1zD9ld8o zRPL%)q3}<0HGbsOj)?LbN#FA%A_K*E@gAhZ@j|8T?jr|&1U*{(df#T+X~soYp?R(Jq^HP~T)3a{2B*G8Y!G7y_^G_m?b)?rf)Fl9 zeIS339qP3GUWa|WM0{sN&%0tX^sb0L9oEb-)>@pY&I!2_n^Zt66}8u9;7b87cuJ~7 zs50a4`I;6l+e~3kWs}5E+nJ z-rzxbY_C1l_+!e14Rk{wut~4ZlnIl!jfDQs^+N9;U9FRybGB2TSHBUG$@P1ytTS-3 z^D5hivOcOul@1R-olXy)GT@Q(Hj|1CTy;%fO22_4oOp3#OL54DQ=dLA9CDe#`K0!Z z*XZBfo{t{15d>}75>@ne`fxhZ^U#?QA6Q|Rx|}=zIpGMk`0Fg}S=V0$&BkSG6570Z z$oVy^X)bUOLc^@J@bg;7-1713uMWv*ajkj&5Mr39l^G76J2YBiogT~u7cNAiNxQ^4 zW5l%iV_NPrHS8pkH6D$AqH&Hmfe0f(Z6J(ucpq*ulrw)h+tFKX=?-4$FeAGHs1Rk) z;NC-%w6DHR`1sy#Om}1GeAvH-Zsbu(Y;3%kmw@>a@y2sXrh0flgP&}UIfq;2pHsJ^ znykPDD`|V2AvmpT26*S@N%;w5aXaW z(@^u=Vx6y}%yE)uo5+ALl4T=i=z`?GIDYp?~y!a+K9|og$7nI$#50}3p)|pk zAxW2iX*V%ifi}_YwTUmC6t<)jNfYt>xK*w{X`#J0a?3E9@?5(Qus9Rn03JqQ$i}NDBc^xc+g zV=bGaxqjbsK-BwI!Z9MY+d9&Rj+H>~SlRbtB9r{`=|4b8=c2st7iz*%6T4$?G{dLf z-A;S7^a^4uL7?owkg~8N-t#{6W>t%lGDcTdc>2wfXu&?t;)9j!!EwcG>7r*)7C~Q4 zOv;qbP{#)VeFFoC0}b)1`peZ4i8r_R0EkP6DiheCGk0h*G@yj2UIqAogf869qi_{OZjuo5CT8tn?N+Rb9ZIrRz*EYlO;rlpS78@DNtHh zzS`Ewxq5OoC=SAo?7VyEq!k~^u1~p`aAKH8op+_+oRRCbXjGo*2b+9x2)6wnzV@Txdq|r)NcI*wb8d;0aI~C zEqw0FDAV26G=u~=;#=`pUiIht-NJj(4fAx>mtd^;!Mm64unuR2RO*HR-AR<{5^a+J z;casijG%8riPercc<^uL=!iJ?M?{E|U{S1=Z((LnksMm9v@XJc8KLm29K_xqf%ZUS z8hg@@4AvkABLx(nvZ8_b!+1$NIYg9GMM7-Az96&0#^7MW7ZJgPZv*_4h3ZODR?MTSp4&)gNJ$qNPUIYeAlpr}isx?T z40l7&_m`BEU%nsr^P#jUD35{Tw^!{Y50XR??D;moHN8eC9fN343zG_#{lDg&O*P+U zT)g$Bw#75wvc3(7KJRB?Oz7^pm9}%#TcP>Z$g1Qhi$K#`qmVXYlt<2H#f;3WaPiwi z^jL);X0VLbWTI4|E$`wgUAPG@c6cYQaX>aE6qH~at{5PdUngIM`%LJKY1e5@qhJs| z9zusI=&JzEcWzpYANdbOA5pG_4YU)@s6kkgc?EN)^W_53^qe+Q%@iKj^jUWRItSd> zCBG;Pvb-t0zHplQaz!5}?_rWkYKFZ>wwVtnV*6V$e2UNpXg^oq>{L%i7 zRs29Ek(V>tTYTXxGmzFM9$HuztoArJ-aOgzmUv*qZa21r>93qXT(<*;kj6#>q)p4Dtto7H}_mHe`_9>4|MAgj($8r9Sr$}cz?whPbP<9mha;2L2zVYjZR9 zT!HmGC0?pYULa1WOIFwgOaB*~;q@(xu0*aNd8QzVNr^e=5jO-Hr&G{ylji5i7y>KB zQ^7C;*34DCt9hroIgxZx-IX}6(?v)oarED+#1MB5*viqCx;x3#-~zfz$#?c zuwOPhTFXxL00H9b58C$xE6rq_h$bdo!N!NBJ67_tB@2O=yi`v%mJrBkI6S4#!}I;L z*&YKqRp?C z!u<#BO*G9vmVVHN-NWBCsG|Z+N!({F`1+bMOROdMYr+~z|*}=mn54? zIzs3SS;Kvz$y#6IIpcC_9+Vg9C3_loQz&CvN&_CMjRew@BS;nv5}m4h86KpSuo+d= z8uV+azS4)dFREjF(aZ&74;--OiRGv_p~DVXBF6V*x~k(XFlihelqrZ(RGP^Q-555a z{lz6!8j$Yz7g8{T&Yc8=$;eZlFXC1PbR)??fz*$}5kf{bGX-=fi;PqptL zuB9gWyYS7I47+N})l7plrrGD4<1D7(@n28sJf*c7JNLT%u2>L$ctn8CZ#>ecCj4W@ z`RPl061Y$6+A$auAwo`TW3z4|1Tyy zUOP-6aVbO+!4V}phZGFZ)AUv0p2U6@)*PJx*?FWZ(F3VU4=n*57>`OtJsFQyQG+&# zGq|*+S9$kau|xMPJf;qM!cu06<62$(_;R97hcEvdvw=?>-^!HpDrkgu&2;W{1H^On zvBA&JAApVMm+t>fwsHRdlWlDOTgChz*~ZMw!t(zl+gMoH{|~b56;vU6YY`0U2rIA) z)^=cLXNNTa5G?I~4m*VHT_ooJ(QR(hfnTOmoL+f%e^s|N6`3A+tuM}Xx4vPKGNL(( z%gehMB&K#})8muFgWw42D#@m%fc5qD%nkMRgo=t58eCiczr^B&i$I)Qnwwh>KT|^6 zh#=0sNs<6LdoZXsHvo=wYyfJm0MzLC)a>}s(Ec$YA#ePlud&_$koi?5lmZEm1pn;7 z9fb;!+Q7Xyv@*3h`f{G~!~tp`as5-$(!PfBZUGdvzzNP3vBI9ufd!_0Cz#yQppIo%h=J;xs~II_ivp2JF9^NFku?MrZf)d zgW&!9qJRxdjehLf`+EK$Of25YnCFLwchUYQa^B1gp02s+4=||&B{O?Fiw_{cy~Ga_ zz$W)<_bZ^DUjjS-KJy>jCg7i3P6hCuda(DwnZcnIn3I#9O=I&rp70UJU}u`z%#7OB z)&jc0$xZk(AV0W>X!5A$27lT|zqUEMJ2QL5KR&%UyL=tTBBR?so#~niM@OI;(Py(O zBJnpqEjSAxhlXeCiYEs!4hZ1L-eB;11ON-qa4t`Jg@4$F9u>si)fw=^{YU$u4O)Qgj*o*q~=Eh zkjBsZ_v`tgW=Q7d#+v7s@$V@zqr{nIc%%g5SMH-vijmRF&Hq0EH@1I5OcH4S@?oJcs>7nFX|1AT+j-rFDoBP_=lZXHO*Qkq4UWjo^qy8`LP}6X$Ps-Pe{ zUF(N*?Z;UEhQ?-q%5@ISp9Z7de`&5i@i#V8vQ;~);fosfKPAANU9s7Zxu|d8pxw|* zKM#q`jPCMz_(eZNcxZpk6Z~N)1GOLU2Eg={U%_htvlqX4ZIm28 z1bTnXx4!T<+R|PAaO{4VZ+au2C<_?JU-ABr)6Z5L>p#;$Km#_=jqj<@7aR9qfc?j{=eftHHrHzf{m=-qrXwWZ+av< zdMdLpzk#;rKf~$Q(fNtRZ~d1ePvZ*{VCTm7v?=Tt|H+1 z4gOh3BcqFtwe|iu<#oSY``zc+K_HCdngljC8`urvTdyE&yQq^(osYJ&69RZ8_?8Wg zawvX74tOXyj@nZz@{)UL{x&Ty0#q%NSLu;5hIMVfj7Zn*h%J)A$6|{qRR}vN9leLF zVUKr^)M*nvf-bX#1Kp@FBK0zj2|}PAQZ)047%D#rx=^XuWrqIZOWH1LoG5KO66#9v z`na5IKyiq4Yn5$T&fbg?V|it(yR&#~wK@oLFMuIhNM#&90`a~{MA){NXASL%t1W(8NIzt zh%;>-^VvcdnKYTND$jWtIp0&IYIGX=F8CH-NZDdgI$zZ*EY>8c`GuBDuf1H_7+H1H zBY4D}bo^e-kfVRFX#PwT7;M5vMg-EUc9rqXsO&J-niVMEvba>7cO}AbY&ehcnF)pn zC$n)@u{RSsZCZcwT%hS<36)fJ58(cConK#5)TUmrX5M9F!G|75JKJ^haOy$PdwC9* zr{P)_T^lhJ^0H&A_b^96lDO@LJlk8LUKt(N8KXrbX(3v6=v0tXLEb4 zsnTKi_YZ43#QQjo*vd)RXVAXqs`QP;Kd-#6>>_-XVI-(Mj1DR6Q+Q9=**GpTdzbH| zW>?o`_3w$ldMe3HLOKsjjHsZOTK8@36sB-h{WA@&CH1cLBdR_ofw?U0zT>j0{MWKr z1<6G32fsFDxxJK2`-XXLvuup0Y<4aFHX_S@J}8oxIYRaEF9;5#Z7hcq$*dE!*V9UZ zkhMAw9_&5D;dI^l02$!D-5{Jr!hGnU!o}Icyv(&@L)sbx7^%XhIPCFRPF7-})x`1y zwHEze=C@(syY4-1)a+8DcsqD<08wkOJC3r{y#`X*Y8O`{`!Td1bCD70l z(+3UdMWw4F;D&phPy3LuiwUT6O!vHNqfq83i0?uib|Q;jEyI#Hp?4Z4ZoI3DW6Xcr zxtSF^S64n~MZ|tcRZlGzCS&!+(%%AFGm5{DQrs{>4Du3*d@n2qT%86A`Gezg1=JU8 zPSV;v=UfX$9>og4w&C(RbSocW-f%qKDvnj`wZKORFNRA5gN4;EivYv-zkm|a{gTu# zrEBV9=gg^D&gA`M>Lalh*d5%FfWj|cEvtgza-6{npRTb|$chyeKoYTXJUN|Cd*szHHy*&@!+~)2V$XOWY9sLywm`l9V*Pp9B+%AjP{l(CF zo?K}a^htoCde(~ML;ncExT~d6la%7`atrEmfY{0#og8x+vB{FOIbA4cUuCP%_NuRO zoM~X?WEntrpq1yoX}}O6N(3V0--K-8a~Xuh%jEM9z%%3gqKez+QmKSp69dqD4ksih&Hr zB+LvXkwJxDkRMzbRTdsMzMdTQLAZbIb2IwYPE|xIjV~?>dQCwQ(T8rZQ+P$6-~8sk zk|c%JII`6K8tm99^#SNZhp}im^|%zRDEVi7ljF3@9@tvnbFp?qHe`xilm@?iJG^k` zlV`(Eok8tL?KJUO&}&N}Klnx7$>)f$(@KM<1b8D(_T|c4PYYW|tc1>=85r!;qSjK| zC^MZ~xU7%ia6AcETfUb7CZ_3_XBBwrQOZ@x7WvK>yw_W0S~b=)i(66H%bkmPFuWOO z*Y-i&4y~{NN7{FiH+z_3;^5W(cyAw^xfRByXHUQAW>IF%PgWmhZ$S!C7@Sv4uF6_{ zR916IxI8PZWD7$tSd5qP!Usu5`Qy(7lk;#KRlCEi2vttI$ZwYWXjJd?5HgM$M!5%D zP}Lo%6#aGwj=Vuihz}(+eiTcrLyB?;au#^MF;%3x4R!VIjJED{aZNNSk2pRS5A5DI z+dFjbpF-3SSHIX{#yLZjlRM43y$+Kar3CD~F`_L!h}cxr#CO=Aco|jY6B9~pE~PIQ zadW@@gY`;I9HU*diHjrB&rI5U!6#pdnq$lDQC1Tlip&=mU;?*X6@&^ue^j#=WH=## zULAxS?Ctyd0@!Uuf9pKw+iCKU-udV;4t`#d>!G@4)Qo~LkLb1j!x;Sp>oBSXHMV~` za=PzGFIJ)tGQQo5oiL$OaqT^Hc2f3x3p!TU(z3?zUqbpKY(vI1C(}wQnG^h z3oUE5Zp44_Wr5gO?x*Ye4m}FJMxIzspG{munv*C0mei)_-++^ixdd~WCrV5HK#84K zo~26r$cSJUax z;O9gG#Us@zbu)7(nqN6^JhDxi$N5IG@-P=_yScQU!IfyGS?lt;P9`m{i`7I_W1~ea zUemu`xJ+;;Z!%?@@;d}OhdwzJvK5l{w4yVR`hTKh)x)$z zC_OqACd)+Ag6x3ZdNOibr#yvj=9;F$l0v6+v2HZEgXQtxEd(5Z7?;Jh)D1R|4`{Ba zCo5VE1jwhL@yA|NTQzOcB6>vsL%tQs<~!K!knUq54JOLZ-KX3OHMdLwss@V8sC#(b*L6rL`kRd?(A4w%C=>#=M98i+iO&y zi~S0Q!{ufHtB%3KNR%lbDc1F@=z#8rh>MB{du7J@t1R&H-0djUGqZV|97eF9`av!) zH*pT&EkBvoub9;2lB#I+B4Mx7b}mm5#UxIC9Di$1tkLmMjDA&K`^sn`8O3=iYAR(kKlDEZl8<#uQyhjLE46KeoI4jLC+64inHW9q z8+S{D0VWxz3!`w!$*n}l8(0w)xs-2>OcQ6ERvSY+8&haEVB$T>zgDHi+CRLH;)z(z ztN0BTH=hP4N;qk~kIPN$6<3>=KsZF=?A*6{E-ERWte+;x57U z(zc2L5NP8!P@$6J*GQOPg=WX;^J%QFpbUQxjsXn<7E+6sjn4#+IV<2>bFK|ODn6J{VK2c*KsGG&-K-*7VcL4i3~owYxPn{ ztY8tb9}GdFM#8?Pmu)U%wOK}RMpaEs=xUXZA5pO1uCA6QFdzwY#S@;*OocK#vlo@- zs1+piFi(&`Tmjj6;!q$$mSW$79()kwLTaSlz`9q@*ZN)GbjX(5 zk3M?&^c2yZaeGX~V{?ItP>G5@jG60R0JFOQj*Zn7nsP^oK%3Gqrb#ytmF-9(b#jYG z7+Cntv8AA#UBsOtiPY_NjWo=t^$I4P*oP`ok*yOlv3d`g(A7f_`j+>2XS_-LpZK*! zh{Ez?!u7~mSwQK>mv*1IiJZ{{mH`2|$*a0c^yD7CNQxwEw$Be*%`mx{A0yre3Ovt# z+ytjgi;co;paNkbus;sF2vd;B;*B?H?pU3PuAKSSD1RhAaE$B%L}>VrmQr*|U%;KU z?uvNwJ>R>%lp5@Cr}K1mtm)XExsu`*^g`g#G2J0i*|;t?RTZ{eb(#zT{jwkuNK^+> zIFF#zmc6gRM3lbeYcpHr@woq*#p+`%lIZdie)mcIgcK3|AUW|s~$JWq8y$c2QXwGffQQwb7Wo~IXqTdCviGg za{$xer>C2a33x#V3olgpIsPvzDtgqR7x4HOWi>{F2bGpujpw^Enuc7Us!ruYy88V|P%n zac#Uj1%8N5Y%#xz<=~s>t1XIsB{7I5B<dx5J5HXk4q?FYx=n>l7a!zQ_M_reRV6k0dBua797VqH{L8F}T@tR8 zvG$3tXkknDni~+d5i>|PnZnh)k7V3p#W|JqM4Bp-Yx~(=A~C+LbWKc{MY>F@Ssetk zNf9!;jBWK$0X>d73MO#kwWMdQN)C-X9@E#o2E{hLR1sN@w83K(?K~Wr2p@gpwkctR z2`{u=e-xEy+W{!N47MI7lC0G=!nPS>0OW{G;~u#S1;J+>WcIdL)U}Gh4^icfuVxc2 zb&P6D{OKhq^I~4jpvWHp(Py>j&p*rY`i#vl5;pBaqT)dacAQI5k!C)Bi8+lBU9%H; z#e;4ooL7zmHMduPeE$p?Q7)ddhI`p8_C6sTu_gR(q-om}?`2K8TWFc?LiW+>pPb7g2gQHxhGp#WCw0WznODa9 zZ(d@;(zM6|l_j%SfAPre(zm)F-(J{s#LXA#MrM#k)++jmd+Ji`@Qwhe16KemV}!#L zXq0r^px#ON>Qg4CDYUgwkz!$59*)e^P3leL4ft8V!Hvu2{X@0rl;?3#BlL_W#sm=h zRXw+4Q;>g5jP@h+pt%dfcc^%R-Svf!*1W$RSA~N!*h1w+1H_S&4*$QQV&Uo1Y7EZ# zqy)fqx*wgb8Ut!%EJRRZ*qjiehe;kghr0UV5hcZ70b>{^)sDbCieA}Pjxuv;rms$G zob8J|KT71W&;};PYl*bDikwJgHO8vL5zap{{GG?vS&A~y17)V8nkm&+3F0;zvIH1? zzX?(!#qQR!dH{JV97Wjg-5?W{sWMcmf;@MDY)XDy!y}lsbFS|5`&83I=MSUeN+stm z1c`soRYbC?F6}4buaLi~#1Y8!B@fD)^PctusfS`UJ{*idWh^}j_wAX9}upP+?dPT{C4&6VxE2Qt3|7Vv{2`m&7Y`0s;g z8g%P?j$KQ+uJ-;3Rx0kD=Mm`!I-C(sIbt!OwZ_=|ROO+KI`exi<9Q`COwExH0qGcN zH~e#>xV_;=Yx_lm#b_`eTi)Vb7@2JMBkIQerOr2P@+lM(8f@@80`3nG?fkR+AV#(B zTPLIokPW41%Lytd1K7J>+U;VJ_H6OSRUFFm-*7uu7I6lF$#ls);tFY5%XqSwUo!c@ zL{LR-yf+w*eLcdjGr!faXU*w6^b%*9mZf4%`HB?3#qQxX=k%zgsY@n2uRO}a-Yu`Hp!RhrABrpG3(%ZbdDFeb zG?$xu0I8S+Bff)Ag?n=v&+E-FB-NI0t}Hq6umfcWnto?kQh*~z&py_AAyz(S(Od8a z7sHUvNT^Xabj=p)e(c_CE3nS`DyTulTM)Oga|i}Qi~y!-hqjqd%i3LVDF7axR_Gfx zPZPa~y8~G{D~z_~8qRhMM-UfmGEHISYNekIc94SZ;k`=L0dRLXxN@h6rIq9n>qX;U zW8RQ5BIwWVXe;n(--=5GML;lAj^S`z#mlGwc^%%;dlTnhbFpn1wLVB*>Tv|iYD+aS z9eC@-RgWDjFj@VT_IV zWkp!`Sw2o0Ql_p2ap!dp`y_p?wbFZy`9R^@Wh|tK4N*W`wu`0_%!JR~4IP&g9UqfQ zFKxamY-BD{bQ<=yem%Lxq4iM8dR5P3y9{CJj4u&7Nf42HW+FSU+_L)HpSq~E=o1l{ zRz_S3)LuQdk{`yz!~W%a0H#F>{8cphfLKcfLCxjY$k!}-x`F{PNPu*_s<(678REKF zDf`N#9VOhdDtSo+oY%-@MWmmA{5a`NPOuneU3L8Em4Y0R@fZNfxrDZGUE-2MbK5~p z1;m-_N;Z&*4+OXHr*}RXe!j5k2DMs|JOxHZ@@(`d=ucg%V8~Rj+GkHQYHu9$gPh!! z9o+^QeLvXV^?qZ4x(rGof^%qaqb&m#Gt&F=a%1F6b2nxAQ@m%#pS%Hh-uovDYU%32 zNO(eX1RGZ1$sEp*b9*9(Z$zuLRS27g0z+}n6p~oW^wFny$*m{v(q_?PHHYz z(G?!ToBxe{uBxiuqVbzqZ_@dK_k8)#eF6wRfX-^}+AQ*?6m+(5f}uNhyv_)%aCiGN z{{90wDMbhW%?4Lh7)I=MBd^NIi;H`_(KVcqn~}2ym%L+WZb!FG9|P$$X{^z=`*Kvc z77LQ6kOv zizAH-i&mku*~s(-N%!CUNWoOG%$K$sQ@3c6^?VeddePVM5asVm9lH)h9Y>geUX&7u z(J~MLTt}aA+vYbF;~>c$uF3imZP!-v6}mkHx}Vx)&6Rbxdyi8!A~KFwYo5oV7{{j%1oq!Hu5#@l z|*EF^z}y5N97n?YGlMUzuk z#*xfqlD2K#=5KCJXDA#-(=n~AxyKhAqlb>fgvwWO-}$Z(>mF2p|0TQ9&;oY#ue?*`4J{# z`h*KZdGl}UU|IfG?Egr>QF9o}A7=Vh%D~)7T&Efj89+*l) z(#8r%Rv|T#NRse7VHmLBLz-}EXy#Ex3ck%ELi3I;-wD4D%zL}#Vq^Hg@%*Nd3F{5> zk1$xZNVtPRTJ#-jb6fZFLIDE?SBT((8!Yb~x#p@7r3GUZ3K!#biyCdsDpFXl{xjBC zN-7Hpx;!Z+(f0qIo0`hYqCcC3?9o|z8>3cP+~50`OJ0-8Fp=&^QKk(VkBn4GbZUc5 zuQ#@^nFZTU)9luoGpW2V80?@~yX5Vk#w1Fl78xcPx@VEzf}TakiYbt#(3XRJzwCSI zrP1|?2`}~50)%$Noy+ij=oO==1EO0jtP&-we7u%2e;4U&2e+g}R7o@Pu8Pt8fYM*2yU4 zk++qPs)HbOgD_8IT2doP4OV9~*}wTOb|}k_)?LEaE{1PFleO)dFTD61dJ^zO`GR|^ zk?cF^%olwrYVM*#IN1Ni*f|7=q6BTWZQHipecQHe+qP}rwr$(CZQC~f`(k1i6Eln1 zRz}pas-iN_ceuDNC~f}&hC;W9<>2&_&v=6*$+@cIP*rKm>5Ui{eoB3Q&?pM=lVE%@ zkAx~FzSpsvuKNm`b4-GfA;K{bae-lcgDvNhR0Z|+d2wF(C>2)we>ox>4^-qZDZC8= zXgxRd-=aOKuUV%N4qYM|pUgQltl@GHDxq=GW$Pd!$O=XZ`{v~A^W7vIbGHB z4rF4L-E(-bX!59Wfra9tpZyD;i@Y8(N3{1Z8?@j)Jl5*kJcZnkr#}D?)At%_4nKF- z&D=~5K^w}QPb7!=0Ob`KpMdcMZ*&y1u)6@67MGgbHlf2|NKvaPp~61y(7#E5Ig`aw z?7k~MJ?5K|k@ZzkMrG?L;>juy$6_Uy+lgc=J4&tpnM{gQQu8e6oiDz#`3g?f<8QhJ zoNe%w$N9jkZmvU9HoL?UwG{NkV zrw|njAPHVqV!5ILy`}{7MO>fht1ovT%lAbYm2vmazhpAF-s`H2tVKXr_MrN&8!92GwpuH4geune^R^D(I-b4Q3C-AS7~kDrx}8Py1KYhb<&T zP4mZ9S*w5i|K5Hw%%O{O%p!* zWGxf3lEdK>f1D>bX?#nNReZI06VR=eTR5zz;wf)iyp-K?At9(TO9=2O$pSyD!XFsD z)PinvTdgB+V{71d-`@LwJj07>-NGB2(6*wXAB9mskPHRbB5Y?0g5NiD)D~c40=_i~ zv98r2r0?0(vrvp8&r7~&gI~PkM@Ng!HYL@oDMt&1IGJy6hpq8jRIS8-oRPXu^%F~t z9BB6~BlNF8e=U2Tbz)UEs0!1wSa)aA1!z8ARovGWWk(~W!W-!)84GiazcZT!lX5bN zP)P^Kq9kL-{=iU4_w4WIxQ&orL6!731DQN#TmuFWNzezd(u(>)I;N@{q(du3VJY-XzE!t6;%tH@)GLKJ!?ir!8(k=w8^ z<%2H4H+tiRw}5+sf_rHj&D{{``iI;HaGFLZbW!6H1gBX>op>ac6 z@$Klw7SB5yrh=fW1WFnEWU^KVY0V?%-Y$qOSkqlHRMy^=I{m$IM@6BN6aV7jYi*!- zw5l{)mvDj_l~k1AZ_u^o>o3WqANfh_%@cHtrWW56>|JgS?(5yMUBgsWBB2(LChFQb zE6`BpSb4p#eB3y<>Y({mNWixwoBxM}V}NLUQ@)tWlvkKN02r^7vve|UI(Juh&F z!%0;gds%73Gnd3p&HI5X<$R80?=4-D@`r16PCHeZ6Q*mjdbrJ~&>+I7xTE<^HCaZD z^W(_BOA)xi2pF_5d4&q$IS$&ddJ_!_C&|#(Css38A#^@iivV>PP?EwO)GeO2vc%2Y_UcBX6naMakALt)m|J)(w99eJDue=9yYNwPmvP3*N9%pg#? z_x^yHKA7g~Hv@;k7HckoOW;%G8@hK}z{tYN1<)EERMdj^Fexc{4kCv)fJ2ZM`ugXL zf1DeV)7;05AHr02zIF!K=`Z!P2YT_?kUlxY^Gf)zvZm>&VAvU1YsFZj>&)Z75kGe? zP~_KUD-$W(EQ$JEvcM^kB!U%Pz~At8FGUqNe(ZiZTEq62T%s{@TgoVTorf4s;5XQT zD?1>a3O?>{8>o%o@G?vmMprk7EEJuxnHQ6Bs$a|UtaG;4`2NkXVM7X8A1Q7!fT{!m z%k-m-zkB26U#1P`&Rj<#6ITdSrK{AV97B)V`_SqwMlXAI{de_Cz=mVyzycQ$~U|#4fRqc#tChD*| z_B!WgxKLAaD!yN8RpByY4Ia&u(HILv?-N*&yF9iic6gIXVt#3^tXzmer}Euk|GcSi zzqOi)fz%%xsiI63eUIll#a*U-z|+Zua^@6{&9^fI=9Yb|E-F!7~`8iMCwt-kbaRMrk5}j((@EeW7QR}SbF~W;P+8QhWMac z+geiXB>lYb5TQ6^;dN=fV@9f%Y_FG}PoKGuMbdBqMWdxfd?8#O7w{e*Uj{PeyHYp0 zH^7yvri9|PZTyN^T_*pvb#Rpqn`iio;!DEhE&kQl!nSxy}iQLEIw#T3==*7n1QM$5~tJ*}DxhbShQ8ZP>Wm z9_~0*2aiA}Z4O*<8f&hRH_C9LAjG@qVIVNTh+~kS2>F{g(5Tjs&UOpUbmmQHq~4w@ zX46zled7E};!~*A)**s@O(v5h{6Yf7&{b>FDdkxdoQ%K^WKR^IQ%3Qf_0Joo*y<@K zhR5h90Xevo@Ts$8{p#{+^f(5I{j$mFg+bd8V>=DO{@5`gvP>>{iSVRzdihD~;#NTG z`A__`X3IHF>62h6DaJP3h8}akTJlrXr}CbJIL-aVI1&uJ&k7_#qtcWoAznNxU>d=C zddWIjM%`LXz1QHO!(+S7ZvpEBPBj@t_yp#IrXP$GgB0~*G*^_{nK_wO|Ufr+({zNbN6DNpxSw3C+nUl)@|%bxw`fJy3}ytv*|FT_ zVvm^F3sVgCkzXrCOLYp__}pGT(}#$Bwr+IE^|?JiS9vF0F%O2GL*k*d7K{E2!($4e zf>VFfTV_d3h@PrdsKD|eX}OR|s4+^?hAeBiki76i)H3B2!d1$1tsqu^>ViEr-XgEh zPSYDL^>acs^vvheX>o(ea4I!3ql7t0w7dlYydVVK3_E!eD=Li@wapeinl z>I=s_%aJ4}Y)yN)kjOHT)>lg@FEY;~bI2pr_@E&qvueSVDY|H$RFm!bwJ1b#$`y4& zY*y{Tm;-LZ-P?qc7iL-ZGKQ(t&Y}8Sg+G)a<2@;E%jR(oZpe+lW*<(e*2`d zjXuJqU8zYDI`R$$v6HAxn!Wa?tXK4$9K9VMEkTwCD8QzX>A)M26x4$E|sUht)FK*TB(*M&p` zdliD_V=sYIdQL1bG7LL|Ml{(mED|nO*;#2DU|DW2=~3I#S*TXG6Fru??08ph_Vpl) z1s}^SL&1HgPnyAbTwLYbP9EC;vNQ%hmuf1(WIFM|qyw&4x->?aj=Ce&R+xix97F9W zb1X4V8F5s5n9zu!QvM`6&7acwFX z%;hIgc@hwzlIKSFbT?DbPC|D9;MDZQl5)4P%F+I3$)GpF^B}9*>J~w@P|-qDRI1v8 zAPn`m*Yh?qidgJByi&JU;7_~7cg5%*;gCYdc)*uJ>l|nGAdJf!C~)DF9LeHsxlY(# z@1a<)BAW&rOfGaSe(n4{*_{^q{5;|omW7@gKMc%WH%C;;hBT1nr4eD)BL>J?Ino?2 zxRj20-~wI>`R?R4afAmG7J&*&Qez9L56BRtL^0j0I90*>RO<_BVgr)V=OVFJE|_l1 z^Aichbt9Z)xv3+OO)ep{_7BdlhK7v*CKz$gtM-Z(q|V8nsp7Fl{Fe>*W{6s}&O}(> zHKV1Mj_s5uW9sz@vjD<@K2#(r{C0bTr+?@Qb~|1Z)1-FMfx&}ok!xJg21y}Ju6%*K zs>%JCL-IhP4tP=(rM7bCu|}g6hM{4{Oo$2-XuI-CB9Ov@tY!ahO1N zuef)*Lta(oAAknwqUP_ORYd9@z;KS+(ILO?mkmm^U_Y9(z)SQnLbz<$B}Q9UjR;Q% z#((d(gY?4h_6-&w12!irRHNyno*L`ygg)x}^XJU zk%18ey{~Mk?i}&h{G5>^0g>HN+{SAIo4Xapz1tS88YLL``Jjs;&CMoGhVw}+Xob;7 z57*1(_3MAjMMvsOnuyn(I+)O(g5WxN42y?Ju!kBE8GH-SRZ{AMsXMy0o^X0`@tix$ zgOX3SCq6!4bq1+&=_?XC*gy~AYJ;9r52urjgftz%b{$#4D^Yc>O@hsiCJT86fIx<- zpH1~P-$#R2Hqve>1yqLci7wsq>W4w$qZ_xzlA3l)Rr$ydr8eg)LKkNV=SW*Mxs)02 zh6qQFsXU|y6Vz)mnqE&=vA1g*!9}%hc-9h#?~xQfy{(x=_bL5#5KF*;LRwu;NmtNv z#3=%cInY$NlDOMiPv(a-PT>7WJJT-R;c_>uP!LryR)jF2jzS>*EE8(P=^UT{HW=xs|nlI8Z#zaKXvKi*vqX@HPiF^JwzOPqD)dsAx-a-+LkSA)B^18_~ck(i@ zop`@U-)cJTB>M{sJjFk{!JyEQ*N7ulp0X>n-D_>KM)1bGSy&-wMv(;+$SOfKoE}(Owd_^$dct50E46F# zHZH9g{EbJqZ3A3$$fpA~%SZK{v;sv#P{0^c%TlTpONs6*2vMj{4ybiNyVW3GXx3CD z041bPKYI*gu1&sWG_=Rctb%Lf%tyI)L94NN0(&{ON31A*2sCnW0F74zGSO)6aqxaL zUlOYuJ-DOjzmIoF^bz|w+tplLm?wqjOS>}b3g-_kv5s-F;canw$!BT~XQ%78iOCI2 z1l(-e`8N9tN7E)Q8XyQLhJaT$n8ePSK(s1IM+xbLDS&ox+rkTfOYTA>EncNc_SB6g zMkysH)6md?a!P1_ew0PO=v>*A`t!xJqJwaiB-lpRs!ptxvI(u(u)7y+bF&bz{0nS! zt9$u>F_tV0|G$hS6B`@T|As1=2$-0e{@;ux6AL>lXn;%v$zqIAN35x*%>qg$QKnxo}~fwM+Xxb z36+0)VBy@Z9cyfAeR>QL0sz{YM#kS&Rv-Q!UztY?kcE>+Mhq|C0KV~UL-X6(kKM=D z4Oqui|AT#lf2$WjbN%DU#>5D?zJa~&p}vt0JVj&U4?rs6L1yN5$__xxwWt>=$|g%t z*PBq8P#lxMxVA@sA_^dZi0Y5n*5^0j#N^P_&joM>0ML=C&hQIWdt(6qB{lvb1yj>EI*Dft&j5@L_*BmV9QaG%@dvXV z2yiBrjvv345A|0>ys8R7NebD_pK)$$0q)$*xemkpg&hojcYXmkZxU#SM-^bQHrJ1@ z8@x7^a%^Vgu=QQ<(L@z7K>&!wP%;6Jz$9QuF3l^<5)ZTY)TevP25=YANSKaBuQzc}Eyd%g820(;e1 zt^2(NU%M$^e#nW=_4V&kGM}kGzlCfK|ACdG09{RVGkd`D00FaoH-4FxfWN$&pn2uR zQP;mK6CBt9@B%|KTPM40nw%1v9l$bwX|rW$es)rNkFk1drbZUv%3vHDzMj(a?^oJnt3_*7bDCfRw zNx>MH*8{Tp=>Y6KcmbH>i5F@k936ntM0_KDM704-=Xd{Mku)#Z-JlFnzoOg#rV)OK z?Eiqu2X;eH2B~jRYXH;p-EiDKnn&z_#>sEdy{m^_M2^k@JCK>0w|`62-%&e(jnu#Y z3RM3UF8(XLU^@V$E&CBOpvUY&Oo=?Q13Arq#Pn%5{kN3;kJwC`?A(R8GJB#o1l7Vy z_fNfaL+^))&hpC)0>=Kgm0&b(jC57N9(vL`zUtNQ#4m4+AL`y4*)Nf-Jp*z)tb6^B z*g*fil%drpIPu*GW9Cn&wSG@Y3cpw$^4tB_CWQ16yV+d^Bk*rh>R;F?h^}-|S+rj2 z&*Q^Apq|rRgT6wBPuPAiQ$PM#y}5_DH2p7Z$A;g)8o#=?V(@m3A4EKB!yj-E@GqG# zU~X+_crWO@ZG1HSb$m#TJ{OH|!2asGAK<{wcQe{O)y*FT=(d<%Vy8fz>%P^`Fg$IV zGj_~6<}*G^d&ZaEk z*EjTrphd1W|GwF_U)lRrh@UfZ>NcKUBm2PYEdQ32-))K+JvRPTzK#h+1uEZ(>|)zL@G8X+MlM_EK5f{C-0KVJ`2$fu)BZ;2=pZ zpTu};o8RydGiUeUAnd!3?0WVja(Rcl>m(_j$J;28w8 z^#0Osfz=m*n|4;h;oBImWFV&HFHMSP7~LtjT-rDDdwlaMW+1j4x>4R(=fmoo33=T} zx`bX2oVMj*@K!SN7G8J0Ea6JoJ61xP83Wi^G#qH}-28!WkqSEQyPxCzTFiY|9iRVTw#+(nG%*E5Lu_w^Qm zM>2ju@A(&{29~b9^$tDJq_9Uq*JwzeQ(vmKMLDL)>Y~yGSL?=jd-@#&qJ$7{Xh)`u z)@FdwzDRzc+Lk$d$hF3#6)X|QIU}N`RnO`rO@p*ob3gt43n)!!xt`<2?&jXJ%uO91 z6W6#4QpUC$*8*$%{C=Ja4GY23@kX+z zje?*_Kb2t$qIg;+_3S`k8^tXws~6iAMQ6+GKjIV2>BlIEU{X0s{>e%EGLZjc1*YjxJqAoEOAGS7(_s zG&=5kTk#BT)@Q!bGGFJW1f_~32~x$GsaX&F{M&PRM;UdyjS@^pN=M)KjJw->;btZi zrUO;RfJq2oyU-8A?eVMBL5NQ<>(78Yeoe;L`UPjQtp%Yx6T=FhqRCaQU@zMlhQ&gc zHyIMCs#5Q?*L6GOd@BcYkI;{aXV}7IiDGS9VJ~LiDCS=%8)Pw=p0>l>7b78mHmZYl znu7Pr&`j}v@zEPk?E-rv<98U*XI5`;u4p*@5y%$&0+cc%YVU$zS*anxG?g~9()(Tw zX{G^GjZaJE{zKeem5u|^9f?BOqXtL)z-$($8`?+sy3DMEqHfNh-xe^l-iWf2Q9|OCt=3ctH&aKJ! zE<* z1;maIC2cHT9Fm<3c#QqT7ZHN|o23UUQ(bGSGIO5WUkC_v++SD;ix)QSIbK(-Y_?D3 z59~=-W!T#iET${}{Az!PbG6waNendzDE{aOyvP!2g%umpj4Vf41raz1+_u029H(6~ zB#QRSQrH7$r?9%+gc}N#7uC{w{0+AiMmEJJjXzf zspSV&=k`5TN_kt#uHm5jXkz;yUhvafzCS_BtqPBa%KPe2aK=w&&M03a1jYKe zX|SdjhKhg&TlwweCN>Veo*e~p-%`lkme_`Vm48romWbJJxAfT7cZ2_ZwgISs$q6>5 z>B;NFIf~rKkr>#2WUDJ6q#;cwN`~{Y-v)85^#+X>FGvAF9@})N@Np}oGvUsuE}uz! z=%U^KLxg})60uv1>t&hh(Inqht9Q4dG60lUTqii$nK89_FrdkX;-UnvM3IHF{sPTz zF&(CggrdArgQ-+cQ`)z%i(P^{_?aQ3Pv64v8)ofB=~Qg!r{0ZTfm!nj<>tJ-0B@#+dHRPE!nFUGcA(65+ zb|dh?=`sqUBVgblX}Pz(*g@B1Gb~kkYMfkzk3*Cqv##?;q_#>Q6~RF+GN zOow_JYxBN})Bt>N7#vWA)wfvh*m#cLMX?lM)5W~S^NIZXxLaLs8YBSPe`c2%(k&0LaeGI0SSNP*cO?qr0m@;L9 zQLeeABdrM=eE3`uq2UTeL&b@WChx_c7n^q=i#Z8B^Kg$A|D3)OhLEF^?yRF~&zgH7 zN$pb8J~3=f?8l0nDb&OCi8Ggnlc0w8qIY^vEjc&Qf72artVTWljyX2q(*q|5^@99e zNj=XNIZ5ZE&Ri_BZFnGW6JK_qUC^~hjogsCenVXpPQtMiQ`FFoDk!(n%JBy$`N{us zJWFH|G?>VD8XET>{9%$rq7#%Y-)88;+T5zRb@QGSgoE{$SXugSFQaAfo?An;vb%>p zGmar4s{fXE6I|SIrvUuvVO|Z5_;tRzeY$T?;+zr>TgDf=y;^(X1-e%1$TE zw~Zm|nnr2s7vZSW{qXdUBA-kMh-Kx@m_rzzse}X6kTwR{(1PJ#=UnbgF7X|JG``Id z5%xAKPi@vJeOWWbF2Cz@sd#Pc+mCK=)+7=Zx@S*|yx?(#i^+73#$sTAPvO6MN_rU@S7&pp^1vwlw=G*sFWN0}sT+_O+q-+V0gXy68 z#%D?9)nW3D4s8a1H{6MCm1gQKvW-^TtHWS&DDE+Ok8TwxP)&^DVy&L%r3}0E%;=b& zD54b0MPTqHmXzL?q-Z4cT1?Ah-7tOfY9ZR#SjW^DV5lHe3Kf5=)~H5tm16qxGCJ%F zF4fb1II5gj{e3D90iG;sbv#29F+3WJkW0LOku3y~6orUxk4BxAhO4yYU%5M+QDk(2 z2#&Z=_aU9rk7dj%zP3|~qHVX4IB$F}W=3(MAUma#H3x)nGul-OcSN6iq_AwndfQ`^ zO}T(O<>ne5+TF(>nj{9qU3MLNq>Bkv*eDX+$0gXTt8~w_O0oT}o4Fo_xmc5HXZ+&o zES2*Tpx2r9o-tyNJACMR>sLB*M-%RnSVI=zH|JH4j;GF%WpDT5Ze4HL^3IhuK_u-& zvuo)1&pAd0bRF=Z#~_Xj4UH3Y-^d`OHGOZf*CM>oVqT>J+-PycaKb@wS*_p2%65(qv#fJd@kN5^OM+B0N=NLQTScjFBC) zt;GC;I6vlnn=w{OVJ(v7ep2jv`p_sXSfl&4Y|?S?Aoo+excjoXebx|W*y(;Q3^6KBAECb{!}R&4vMpL&t0jmXAkOel7O#%Lg-%B=h&%4 zIs<|WfoBHlJdvM1#EpJ$(pvCDt~=_qX{{7REGZ$&4*&Bjc?q!DuCvXT;={^L&6y#_ zoHX%CZ9Ay!2suO zpH&+iYBX}N{1=fkXwnHywLzO~zkofwJ2#qT0!6in_#Jwhp5mgb14X~SpTb3Efbqp$ zuLXT3!hfyox%Kai2{uU}^jJ3(ZSsSxa}czn{CvTg~NY7U!Q6Yyi_!zZ<8&jPS` zq?hQ-n~fu|l`tzXK`?cG^>@k?c|rj<4MkF!JvW}W2s)fDZoG^9MJ>pv8?ft#aWm2) zfUTqwiONo)WUvkWtn%dT%3ugmUVbHkDm(Xp_&6ZK_ZhFm8A}fwh@MF}ZIRE{$0#G^ zAJsrCi}0tBzRW^cdxMGw-e?qZ6_I_;hZfQzw_MHM#4^8w(`|vX-Ez1jH;%LXgY~!d zZ&Su?pE_sbxsJrF^I%CnS$-ETCNhKptHlM$5Bl268#6{;CGL>cmA~|OpPDCEMzhHk zFkdv4$QTdzhMPOR8K!-M^w{Gzy?zo<3^{5JImJQy?UYZS5DK< zVG?v=dR&AMKCoF_82$U=$LIT$*TLouGD{5m-}vmAKhB>iT?@NToPPyB)GR7t{3QXy zND?!$XW^9W-2+kSz%#$26}$GeQonNqGziZZZlB5wS!8umI|#)UO1a&hAlZ7uMZM(T zsVnT**&Cn{lr8Y>pqe)i#zuAz7-t&5cjMJ>Xe6O)hr!Zhz<4y+E17Uh^?2GB-r(1n z4CbRNub(1YV`w+sxf2oX$@h-)&$Ma7eQI#(c&z8_s!|;HS>h$bdTrEAX+8PfOk*>I ze0QSpHhcItj^5-aH7(F>?+ED5jNnJ>>{Lx>j07Yc^_T$Ob>Fo2XY3{XN<<`YGU6I> zYWi%>+F(u9M&|dmi-NbMN$iEd7Z-{Ldfj|lZ@osJ-p655Vu?oxBi@!-?WCU~DJi=w z37x6^rw7X+7*dyCUn>z z6M{vH3ASx0Ud0W=^7FjmF)%lZQeCNGWZw^%(ruH#@a>=%Y$rJ6?rp4N#&^_P*|N<; zV@o65x{yzIKgglMnAAIa*w@}@fjc9@2w-(=L-NDB45(zs?9o>S;4c=zp4HmIXVV(F zOYmW0=p5@KLluafE6Ir%V9Yp2Ng5u!w!=O3<)XzS6dZY8X?rKJK@W}I)nit?u@Qa8 z;?^E9@0`ST`Bp81L*%v_p`AX`1`NgGPC`^|h@Wbf9>tDI6{^JhPwedE#;-7sOUuI^ z@$+VnVL$;xpZ|p?hDvytUFs0ang~|c=xu;KKjZPujULi*mq*+FrNGJzVW?_x^M#!O zQXmkeN4Fwq@sj;z;A6sP-}uJ3RHRR5&Ysu}tM<9A-_cfO1a*;oC~)`~L_aQJ;Ps8vx(l|g+Y_s3(y>NUwvs4@U?0v; zbuXjhW6pd`8t9vwmH# z@(V7HOO*57Pov=dBuj>4lyLHKTy#4nkFcwtB<;Nm-N(*BwhdWw>M!ASaw-?{ z!JisqEfQG*@EqRW=30Q7)xTFXGeCL4CQ#M%d6_ zvwu`p(ste9YHwD--Ztw{pJFt`@RRpZH+}y?%rW9D8>G62Qfz?GB@r~9Fmu_Jq{YTWb+ug?ys!%L;()qOYHcQ!HOX#wrv`;$j^ko-GKXzxi z!E;H|{DtalgO;fYZ4ZcxV@jKNAcMwODJ_+|oPYMTtY3;LA#}CnowGv_)~jTl(wB`C z^rhhwR276%4Fzp|ca`1Qqi>fxFJ!6h$m`>~vVOo4-JklzD(-e00T;M3_7Gb%A8UsJ z1)YK?8-TAMxXI8G1%W%<+nNizOat#i5e>tFoebv{07yKTx=0D!iC zTVivN5RSpr9K86$uo3^(A^+o3#BbEsOAj^1^e`l2Zqug74XKDlt;cZi_JW4YLH&;& zmOc$1aeb~RiG*T*l9l1t`d#DwJ}ynA4gJ|%NN!?s zgZE`UFQ2Jd`Db7_f=CG#+&cJdGMIg> zPo2%Jj{DE)T!5*5uJ++XCNtQA76Hg`rfd&cMvwALLhdd}zJ2jY?5inPEY*V2r-*p( z&;+w5dAMPw>P_oWpO{D)rt0?$=Vo!Djwt>iBA!ZHE=k;pqB-1WW2~?mWVEt{!n5ef zf|Ourm1g%Mj~rLXpfHwki=_t4bl$r8M%BH&1^!kh#6wMWYU>1)_=!0e5t%0diDC$M^5LZ=yt#DVCN(?9~%Gmri4XCS`fs<6* zNHCQv%HuB4s9r9hr^l@A66&uye4$J*T#8##cWUS~Q&j+LoZ__Z!n^)C5uL}?0z<+i zq_;+PLK!-$+`LP{;*)TvNLbVD8kjw;6qCMxOoWR)qC6~{Y{=a(WvD4~16wsdb<%bb zN&XOI5U#M^-KQea@NiL_%5zA|{*Lp;Qiz#fW3y0L3c_CNH09DQ zW$hp@WKhbtGedo`SLN))#ozjV1q}mU0lfM!sLkT6n;ha+N2L_UIs#X=sJd@BOZY#y zgtn^jF6B_}ZbkPGJgc|DTm?sC34tOZo*LX|#Nz36d))-NX7eehsW8xgI>yZuAaFe^ zo)C}cO?c`yRU&9%d(KhmaVpkILJigR z1m*PJtz?g8S!9;(c}>cLi)-j6q9^Wj!%W=WJz(D13&$vhWN=3-fDU1I-yABN;-F$n z>Hj>xp^<&cWl0wpgrxEAius;48JKtT2jPAHOt}6P=_Auc@-%^cDXpvr-WYmQqVzfatA@I=T`Ccm-t`QHd)q#Nb0; zLCUlkn-XcVIg9o8^tujNzB#DwZq;ZT1~i*R7Jk#Ve*@BZ1w$3B7Y0Pih4Yx&4Bi|& zCb^w|GcV40K+LzIA{)hbfnoC<2Tn*a5mYw9*r8+fd<<(A(Max(CTwthCq0bN@b0;ic24yQR*!%Bl z=MDl(&k#mBq|Eh;sj^jS@R1cYnU2N}^={QYZ=LU94@r>jqet}B$d#Ov{Ab$s}mWAffr~O&j|s zQixodjqH+b^EF~}Q5G36J=K#*60?8MIRC{$_g_>b8+t?pg^QJR!1NX>8hRfR`OFAsYL5bfE?*W4)sysD; zvFFDfzKU`77))k_b?H_vyb}!t%etOv>x$=T>!PU`D*y*Lr6^9_L#wbtNym=wl9A?k zqIE_?u|%hI3wyg@GQT_@KN<^ zsmks21Wu|eB3=ch&5|GGd1YnOs*eET8zOcZdqh+&#J*AG=|O7=FcJ~bC79%>s*SzF z@yr(V*b(b=O$pCuZ`tp;hOax1??LzzRdq$RCZP0(GeJO*9DMdaKf8sJPEZ>WGtgj+ zs$6-xWImNb8!(shv$mKh2uEn&wLAyi2HB1AJ0>O0Li@gv!6m%cl%B}k40_iK7&@l zYGCaRXVZyQOB)f3X45*tL%vnzD^c5OuWptZsFqL|OQP3V%0s1I*d@xVF??SNRA5l5 zFjR-9AT!uG9vG25+YFPRNz zqf%>Gz-hJ6je$|w52-?LCf)kboYI)LshWoSQD^Z^&>i&QsO?g6&9urbf%ZYZC4b}| zrZf>Cf_)W;c!v=<=@By%&Uk)PPRH!p_z_DpnwyWrcjexEo8ge_cLl^zC(Pz3X3sGFp`LdSlYR?Fu?3hl@sMUVLHAh<61#7ms!ty=qg^}dP!%>Rb zfq3FG4lC|I(d+sFmJoe%yxA0bHfUDnp}BW=ANva!fHxi5)!1Nzcec!#`@o8TNIU4dlBRv92<>&m0B5Ii^ zi`vLN8@Gk3oZrP7Lrdvwl&~dn@d9)&XAwL-AOzY5n6@Ad?#MUn)zq_3jna9;bpu6Q2b zK(um1s_L)r!JDa)Epl1EWbDShF1Qpn1$f@FFWjgwYDlA8i?njp{vIa2Ue?%E=O#~9 zJD4!t^!yrXRMnF^3(XE)Pq!6PnCYR;bSI6Rc+?4WKY9KbY2(Sq71UHLn;Kqq0%r?n!?vN01=%62?x9I+y)N{GPz z79*by1?Y;?$v2rARHC|oKDq9kr;_=c&>_;@aTw5|n`UgQs7Ccb(i95zcm*3Ou$h3f zEEPeh7Nh!8-45rm?-mbErThc^;Uqo$B;Zt^XHzh@&htavR6H@FKv%|nX@?tJVyyTF zjH&RcZ~9AUhG`Gm*zM?!y!32g+9{=*9NX_;tPkLTBt6|-<@cdO?FA{F{ckt>Tf@(U zNtmJClEKl}B-AZvezNe6&(D(w%7)5<5oN*n zJlhN$+@31J48bxw4Bi0ZcbM!w`~$hxL>BQC&~1e|U#8vTt>5x@`e%Y{>lN_eGR&ZG z;6(`8c_gGv@>@8`fipp#mZA+1k`WQh6;?NQgZx@J_aEAy{k6;QuT;$)`^&Lf6Z%En z_ztV6cB(dKypY5?TFLxH^z+t;=Uja=ECI+{%Q-T1YVK(tqPxS(D`@Go3BIccGuhS; zoD}@)irY9sW7Jmbdm5IsY#ztDKNMwg2v)EyM@XgHtfJK}^7HPD5e3qSm90Ic5#`yr zK|S0U+G41gi$_@Pn=D#l`B%}xB`H*1x=BPj@v#(8=}4%TiNW$3^BRn*{;#aQjX#<( z6(hQO*g>`yF~F$Uy+D{ z@Yieb@0sq~E8r8Y4PUPr!_;b&H(4$3k?UynJ6CxxM) zs)-=xIas1qQQO*nS{qviPz!JOHL(WJ3g)~ngUMf0{j5i)sQ&h{w`WQb2vk^=(4o1c zN^=YeTTjv89%W{6c);Cj%a0Lw77t<&?PGGeN7mF}jlp*Ra+LH6d`x(deDSkB4cgDu z>lyp4$mJ*3Qem3&62Od?v%-~*G`LG-uuw%pQ7wa7m-*O<82LxGNQ|Fl@~Wl&eMgB* z!H^I($L&_^WPs`Z3&pMngl@T_%YD5+6oldrEHZQjYqF;6{yOO zD0D}3pR;LNZ~L`tnm0azR2sESh6bK;#4B{~o)c%`N^^$#ai`x=FV)Z(`z5Y2m2%E? z$jDDyHfZLj$|l5dgtub4w4e1QgOY>Ijh73Se6&yF-Uhu?1e|pG_Avj z>Q>k5N&1K-M}{?Slwz~-)LlU9V+AjS;D;rPtFT{9w#&%0fGptzPlp67WC+oo!pFZ* zex$RpuVg~m5C3cld`-d6LoJg)%_|(fN^F&oR1%e+GiZ!J1LG&(?eU#=uo;xcj3d?% zIoWs*oJ5`3|AcgK%3)Gyic*S3bAe&``tF6+Th~Y(kc618A<{ePgkxVrq_2SD7Ujq= zP<2C7s~{EoE;9@=FqfOp-P--jyfHvnWkeG3H5u{`p&1=4)-e2XWIs?1O>0EfWN($y<+<9kw|)Y zz=6l;)Wx+su*IBLiGG#r@TD>@-@qyngNlU4C}m z8I)_P`qb2u>6KPxeMDFUt2=Ga`>#Y~IbWzIEBA;1@!v)?q4~l7N~UDjE&{P2C4Va8 zYodbM%FY|je3}_gHMZ4ru9>KtMEgEPsML*dPyxsO;E z_HnBnRcJnSzZdY)*()KU6su;(ZK71lkY0_QLrbvMz`o-TIDE4k?_E>n`+cqh`N@v- zbHU)onLs>f=ycY(B-6lE7seVv%2w(~umah^q3H1UzPkFFtkm&;#k`QX7h!-5h)B1% zf`?Z}`B1EU%&+q?i{`+s(lso8RLi5Uf81eavR4t(KOA$oM{=Q|Q03x2^q2 z3`|}a#Zkct=C_*i`>7Mr1?d&(X#jN;(@gp0SN!GM&8l>gS|-hxR434ht?xx&(=b^( z@rr3cGaJft1Ce#|&a=ji-vpW{*rjqh`f321H-8SWg$h&(5oulH(n2$iFS? z@Q)2lO-*(x$ziv6=hHB*7|w8MR7&^;0LX~3Lu;QT&>Esahn~*>^fbCd!r9ZPCY>KR z0{kyW)D)J-uWW}7ne+Y3K*R*2%hkleQ^nlh=sxC0Xvf05HdtJQV!_4N)=C;R!5hS*%U9`Y%u~BV39CrTRb2ja_0Mz>wCoLKu zmx|WkaqlM$T(PNU8$ClcJ;OJwK76gu)(%5uEZLgb#8O4}lfb%EuiZ~ra;S!cNL3Ga zsUNG&RJaqREB@Py*NuB?9DNN3p*Nhl!ThDrDntykX6a}7Z))P8diPEs)d7@0A~7M_ zUK#J?T;%7fY)W|g%X^+zUmzh%3Hn)t+Z;l5CB6|j%~6X;B(oMX?E+Ij)Rl#Q0<3zj zN`^eKai@UbbPkVM#rg_a5^Gpd-aVqdZQ#FX8)L4~q0Pqyn^}Lt0Q*r*rrqA(vE7QD z>rl2Nccmy5-r%LC`;k;h*&osIkzC6^c=~HS4H19C`xYVs30dl|(fh9!Y$cn_2!`J* zgOpQP^L=8GvKF!^jR3V20)9Vr6eXR=G^`EK^iUb9+;+RF6mAf|a#>k-h?SLVigd*T z=iCyl;v~HzDYsrMeNNs7@GWijF44;Lo6Mo2sE?8xQ&<}t!Ok^I(^bh%H%UGzmF9_O z3BX=i%QB39(VkwnX|Sdc50OwMA0y(29qbcT7u#pXEct<%v4iT8eByq z%iEm6##{o-Q@YGNkr3`8Lb4NOs}t9(6rZ7jCLC=j*$qD>+ES3V#I{ZRf7IqE$iCSPaGaqP`v7N z8VlSN{k0j-zYXU{%e#SjZHg%omF4+!nDz_PkBvYgp8wwf)E^)s{^O#TWQ)HBf| zH7`xAjrhYdS{TY->&QpE)0*DU-KsfX1j6YCE6`<^uJ0cPp&dY~A$Gn3k~<#v6)Gj6 z3pDU#qOztSLMJR+fUZYqYV(h4#ho_O)$z9D@sGvsBuwb7fLa*XMJb*?%Z~C97?{94 zdoFCS3K#!zpZ=#-Z|hxUC6lGEWzuh1=Cs(ZiQzEEg(=QHAGZy|@?n?O2L!loyg1Ks zcp_1w55^Ba139lE!1?Ekov+$I+8n!~1`P()@IwPfJ|#W6cZ;YHHm2F6B94BeSrac3 z>Wx7E^zmck|Fxe@KV^UhLPMp=RTq39n)QXK?t>Owfq+Cfaln9VzE(5it=zZ7e zX&;k`Cc9ce8=2pb#$8Fu84VJvcD**s)rphWkL$6}te-0LPJj`$%UVMQ?8wsTU>Pz_KoE(Fnv-maL@mMp-~|%soUHRC33g>b`!UrxCy{u- z5|<;Fyw|KC{>6)17*{Z(QVbPI`W%VhnY+n!k}9 zMY?y@)?!diV4}cJ?IUA@${zOm(9QOa)?w*8gu%5eAGV1V`lC1teQs)*?6tZO$n_E) ze7FZbzIW#O$DH?Hac5TkuE6kIB>z)p7h3J$hNKg;dF=4E8~^i#jF^>x1~EyN1I_(* z%LyC0XUPV15%l{IFcl2lAV>xkOw_7qw}u<(WVdM;eqOK43X8|NJatYNmdQYAoBRH?WOhJ!YhcJR|ijAIJ0e#i#_ zvUvK^ocJ0kcxy>qa0g)#~Sft0+8Pc zdwB1w1K+Yt;m75}Yg*d!;P4Hlq-B+Lq@I~RaLnYt#i3W<&&a%mfeH#enWUC>E_fK9 zNq83hp#Z3q=YfbMey%=pzEr)|ZL{!e1Vz`dMC6k(iV>_~i{$>Br&Xg8N1S%N+1Gwz z)UP;wU-nxz_QkJ&(oW#wr(W(A%J@*-2pNFteWE1(ixP*HoOh+BEmYGt$3}E$iJIpv zLT*!?)8qRt^{8?lE%4<8`J{f{R(o{|@H{~WcM@Em(MPW=gFpjDN@T{^x}Zg0QGbSc zQ5mkk!0-nB2ByHpQR*yo;^wnWVfvP)Z z-Q{Vj(68@@W0RyG=UYgbsF4fhp67jWmB3NQ!ogn-3bTI!Mwg5=R4y-wEM@yhXd{*W zhfh<*HP;eBD1~36DxsM;0#>EPim1h#D3;_rA%;vnCSdh!K7(~0WCBwK^XLE=G%itKil=w zEzXCyShUeEB~sS;b7W^fQs`1jn>qrA;T-s4HZxE6u3x)e;|*XVm!Hn~L(x-+>4dV^ zD-FD3Dq}X4Ep@esBRz)d5YxDD%YBw?$KgD04sO4->OkNK2sx6OYI4tB0W~7b>Ewy6 zs!`gykjT04GNkSv{*6qL8{XfO&--ING~j_BZCrRlBNbHEULIPIJXBbJOia$xY;8by zhFo!#kY3YPg~z_9?b1h|YjBD|4K|r2M&4XL${puawE2isRD4lQI_Sh0#i?`_08Iw= z7P0mYF@7PLWcLWz`e0UkwzOH)N2d@4ec_63muLlPt$#X9{bGZAEo;8(ug89d7O*e7 zq!xAeC&U4y93+r6xW316=^4DClOJ+gM-fkA-a^a4t^oz*vJ|hF&9g?v+7e_YdCPHf zl7}|Sgk)C=0oiOJJg5=mO!Y+?X%%xcbAE*!p?f9DR8$5 zh0w#?i<#D%EI$6`2-1c+qKXPe|E8tSaT}VUCX`iSroVyK}O2NeBlMGqw2DGNtW zBvYSP{ew$>V!RhJRE)IH^i=pS#}7$^gymTjPQUfP5MKS#gOM-@O}J)?e5y(B9r%Be zQEO+}jko)RlmqXN%+RDpUO;1HK1(P$G)Isl=M!bqw?#ehs;8CJs;*5cMVNZrCzMB! zO^~|h6s61PbjC49h7Q{Jt`-{n>C@OYG|a&mh1|k!`Z{$dtpd~4=4-&-j%eOeiC?CHZ)3{7XL(eG9`zA*VRo;t7M@c$bJ?|6xFi+uTFG}H=B`g!Rika#S(M&a0ejY~9Fo@R;ks{9cft@y|4|3WlSca*^qZ4~XMbcQ@|H1vdE=#)rV z#AG7j>q9hnXc;dk+|~7_vJbyiu>XBid#kW~?=Qu>+-Sh1Szd%>Ure z#*I@!np^h{w68TRaW?v(&*zifLcAJsT+Iq-fp2-{t!ucA^6na5VaCvzo2a%v8&BgV z8(p8nog}I>o?0@WvMEf5GdLcLlE{_XQgw=%42$=>TI?;k9mXY%`=#K|+r-0va1q^` z*qD1*$(!#tai0F9+!s_oVw9mnmOb|aqgnrZyc_?xu6QAgYNw>?-Wz&-r3xP0G;m^w zDnG%QFa`BrEK4)?LoV(Ujl~asS(KsmdG#FypTl*_d98}16N9*2ZrX=`HNZfSAyl^d z?YsY?#SEPoln+1xjup7V3as?XZx&87%{XY^F#UKVl7Y-K#C$JA##Zhh7(M1pfw914 z1aL$W=SJOrxN#csU9r(birUMDEX_G2tENb)&kbLuo(=}PdnokJS=ZwuBU(d1u{ka~ zX&7$ka1QcW>`t1_g{d$%8`=(|GI9`i6amxkX#Cg&BN9&yfJ>EQwzqDRIqOPQ-fU6K z^GewD4AYmYejg*aL=RrO!NWjH&Lx<&%qcXjl)N86Ayq{!mJ&g9` z3XSYv? zcnjEXc52MEU4I^zzXPn-p<>{)W;f2fotsT~Ho^qjE!CJsfh!N%SKgB#;v=qZ z+Lw0SMtvYJKtv#w?bZ&yUzv^v|BV!3t3m&^Bq=S&qXFz&Uu`v4W>F~~2(={R?5JUks^|f?oi}0#;5G|; zZBSyn5Hr0}O~Z@A>>6)UF|CCWZ6- z)wLct)j5;Ud5pcnfvWJwo_CaGH&c$thpELc*Lzsp?tVn8sd|yKe+;7qMj* z{_niqkHvl)A;GEN zL(bqCq$~|@T)xP@>J}i+p<7}rFdNo2-bJ=Ke!8s{aP6sLDDuQpy&)S^wf6OXI4cXx z{{Vr^!f2lIyKJ8>RA-}8PQJIRjv)p^uB}R57jrqi=*bmxkIC@RW04!DUOd^2H?NOjC*QVt8a7kG zKpcGeSf{wMUe+ZqX{(Iy$eXl>%Z=2GAUML~GsTbP5NP2@?b|WR11dWY%IhWK zp%3FZs7ON-d@0or&&_?4_Kl$o6k(iJ0q5I?0PM>G8dG?MmJ#`=jejv*p|I&Hl%?SW zg)?(5w=bL~C(1Mp6SghkA5_r`E+37^B8js?_;c^7hOMKi z4%c?xr;RzMQ=u4;W9nQWM$t{#?Kl4ShN!63sVVoD&m%y}&{+EleFj=165{4E9%hB1 zTX;rq#kHFh?YG*Bkhvsdy_(YzgGN5kVFxw7q=l>nJ3^`#+FNXFq_**`bFbc-TrSMF zY1fm!Wy~BdzErI4wMBRS?@nk(n%QVNs26W{#CH$*gOW^w_u|v*_c}gpv%40srpX%P zk1*;SWDN9~$``DCV7VQ`MTh%y8&a#s)rjjP=w$ZukR}2U?<|xn*00fmSk;{D4x&t2B)tdolWt#QsbBFGoEbNUTpvn!_H;85fh7|iCo*jm#=$h7f(gKvh8Dvj)Y3wvOugEr(+b+ zRzX%%4Wvv}e4IJ{q`I*%7Tr_#U$VdS&dD3m)C{&V@Bx*1m=-#i7MSleuM>b_Ocidi z^g_sIZeqs-gq7qxvJ*ggWi_PL8AHSEU;Zae;uN0bZlH{HQg|;HAGV;H*1aKLJ6|TazxC*2$OWJ_JC# zkTl4)j$)tud~!sDvk?w1pQXy>0tQ<>=F}w;;e8cmyELxj0G$Q%7FEjF@%BviLF(#IXH$b`F5a6@2Oq z^38uZHpEhVXf76zZsmHT!hj4FMC9#2g2gLru%<^xLNyDv_*5q%Zr!)XL-#hVjeGp2 zFhc?e4|=#m=JH;m3u}HTN)wT4{NOYH4=8(J)C&YzrsdRGiks|gSj81& z)Ttt8iiUAv+=+zL_Yb|124zO0|9+Lg$#{}z!FTV{fct}AY@$LEJpgO#0B!#QZX!71 zmk&mfU}+~3fZtW=jr?;fj@hxcUcJ`zb!)RYHYbWaOEdYI zY%;0gooR6L`crH1@b1yaLyiue>Ol_&X@W+6Yin<`4_vNWdW3Y3(fn>UlO@%A&b!e# zLKkLZCGN)l-|^ai-?z2|Nn%?Ak9VQUn0`S99Ym(GX$sKO+-9w6+VBbTHR?%{xs{?5U*W())I421aNc&{o*834x z%o>8H@xc?zZ$KSDEVJsTT5d`nq~1AtuGK5C-*PuDjnd4ethqYW`5e@kwa}q@oqFod zAU3I>5FR&TAAYA)ANq8nud!C~@n3$@wD`nP#~`p6ezi7E>xpe}cqV+qAnoSv@YwV1g z@CyEPHi{{A?dYFN8#--f_>T$Z`zj9L@z*e)qH5dMT2)*;Qne_iUSd10)x&ovCZ^G)gV z%iTlOY=q3-^(hHmcj`P;dXH&Dyg;GwAUl8?5YK2}?8tpHot?cld=^rFQW@;^!z@K| zWcHc(pm$+C)m-_~j2Yb+?4f^`7r~R0b+`j%IM7prZZkhy@D%WL{xu3H`qYx__5IR# zD&ERp%QF;^VF9G6la!H*oTM6tfWKKUL8qkTUALe8ffPfmeY4@+8aUw-+Ds7>+y5bT z?|0CraGv#$a@I^&96y&p^F)RKs#k1qTbm*R5(w9^ronl`(+VhqrEIx#dagVcod`ZD z(Vbe_vicI`o1AvP6YD>!llHmDp2=*MkOSzf{+z-LlcGzAK^f;SbrbXWZnHF|`apvA z!Cl>QbCYtNcV*m)wwM)eku3r#0{~6P(I*^lf>Gu2oa~d`AEO@$yA=t((WpG)5Qy3t zk<+t&ikjc%FrLCxW~E8sN%}l)GFfNNMdfr_{%v_HEL#QRcq9y=e4<`5Ml`G>AxRg< zafS#xYdegBT&=-*5UQC-k~yXD3zx>YyjIhi(a9bDZGZNU(z<24?e{a~z=NOlhMUpK zK)()^SgVsd9JTnOZLcpqp->UX0^BV~F)ax6PboV+4rxLEQZYUy*mz48oJE}^1#9MH zoBG1_20kos502pYYiRzh!j~J7a09)-Ijz^AUi~wE9{Bx|vto?8m@+t)xDJ*yXAFzN zpo4!N9M*iM^HbTYL^xri$>~t*;61o_Maj0K+=K*?nwH`jzZ`Kyabssq`za~ArQz1x zqzC1Gb=pGD)I(t0eVsEFcBz|q&!0rgD@4Qf0$^B6T!Ke}fL%pMrbnA0!40xQ!f%cU zj`{dpVL_wfp-4?ehu3&K9>8lw$P)oVzTb_+`&ZPq_{&WjYCv3ianY+s8dB<);(k<8 z2a~P&-x)N&Bn}0iOIjO5Hi+=uHjoGCGgJtyq?8%#3iP#5n|jV-s#L*m zMJ{PAw{Vj?9$E*Fp%1;^kq4=8S3TMWPHff!zm1N@A!uc%DWlQ0O9S<=o_qg50-@#4 zyyuA!L3{?tufSpU6-N&Ap)Z8v^Um!Su>0{r_9rP8kU7KVWvq%|RRyBDPwYZDXPrwR zcJ926j6n$J$z95l(c&mmeq$~`bptc z_57&`=0MVB6SRgCaJ!OtbZqAA!NbRiWpTbRFhkML7AD5Kz8yr)2N*zSj;Pp%;08Dh z?(-z-RZ!TRZuk)Rgbm@zalm5*vauVk16PRh(-ZcExEWUz=l7CBfm>*nW`Fr-cSU)3 zjc@n(K7u{Dy`T{GE~`c<@Lzt<);>(Eu)useIrLreUVVLTVawTeIE@gE>0gNl<9q@m7R%ewNf;$VO3&7Dw@MRwP8-0{n|Ys|7RP|FfT6=V>PZ{K6huo|rH z1^zq{j}{M6h%YqpIlUefkX8gg z6Z{9?@W^U(+|yH+tZn0PFnSf0`kjv+kFcXTcR!3NxCeFeV0&ioJ7tN8G+;JnKOmT8 zy^jN^KCxWEa`c`ZRx-v$xzKw4m+xT~Y%p!O?_A~#s2ITo*$CY|ach1o#i>EZSrm0& zI|i%0$%9|d_-~wklZ-r(6Uy>bq;?*QMUB748=}}qwVAg@U1f;!y88rm9dV?Ngsq5! z|IJj@9E@p05*R!8PRNuz%b@2Eh&@=!;ESfI`SJ;oErbqT7zPlIQuqFCm0m%=X&FidS&kJVL7sZ z#r%9=7^Ah|{Mx}V*#jst%oM}87kiO81V9$0mlwsmC+2S)Sn7A@D30%6c^{fi4uKTa`rvcm+A(Xt$j+*tOnFGcL5USOZYgkX`B7JNPitvLV z4TX+uYG3=lOFGHKfnsWG=$}n3(fafH?o9L>w#T`@1bN7yF^)<`PoR^+Z^I|^woHWg9z^`g`l7yx zkC+ZFnBeCWE8+eD+LA7?JXXofEg&O0qOD}np#|>S4gX*VwkOBaZ{x6AyV*_578ZK- zGbD@uM*Hm*SFLHpL~z40`97{voM)f=&!grRfjEOO6`alT+Z19-Bhf~mD8qRNJ)hUG zol{Vi%=6qQ;g!^P*tG+kMv|vNRxkZ)9g`S=;37GG5JoeomYw!dfPNwGB0V zeCU6kCX$-Zo}lZy%HtL(JX-2jv`tC^80#h=$_BF0UXO*)$fyP6CE_yITBNL;_BaWE zB+|Ne=0y*f9)QB;Nik-iUN)|OdomR57He3skz)9C`gFJALV(eV(B*H)Mt0DI=WH2f_eJj_( zLy-QT*7vkuWWUaSmj=ZDwrjs-b%y(UW(k;mr?i4|Dg$KgLBspRiFZEYK^4JRr_)h@ z8zuL3fjKeGL#70LU=0HP>MV6mzu^#ZN#Ogl5MUtnQ}T<|LI)v2xbIz@9=-b)5G4Yx zd}L6wxf>8YywG1_l9-pyoAl@kBI8K%m$%v#uLwKy;e~;x?MiLbmunC4j=?e_B>T04 z0|u3s^ct2??MHEe!)NhhZGZARB?UNb6=bj$?Ucpj1d~sW)1jIuT{12WZ0pep(o95h zLbjsxO5qb7UrB7E*zQjJ&t@Lsl-NkrFEviOv$Q+it7~(HHYyMkC=Pq2DS#`j4+VWv z2k)?dIzPTKzfi@&PIEtOq(`AtQ+P0;2>V%q!}-W9kVxGp=4VItNHnpR^%SG9)R zy}{8v9tMr%EQS4f<h z?~17%PY*z8B(~Wi*f~zt`)$dl#$YQl_a=`8Aaqe9X=`2bqd4#oR#q(fsMosaw1@zAf|nTt|GdmH03a zb=$}hGBK?jwUvT_)kMf5@!6&!)GC5;ve;uXZU9!4sMF2F8$p1C&i5)HgDXqGKAjT$ z!y8TiV%$Gqznk>0ILieJ#pcnZZr{W^A59nAJ&Y9r;7_NS)vz5=aJ&)XwI-6U< zML3JNIkJCOcZDote$?1`>m!mmTx>L+bz>}gH@duKwOu8jSfO0J=&GHN&{SwzKNX&9 zoOF%lzTPwFQZN4c7+Ewd-X@z1`#Pm?ZX%CC#58tueAxY_ZkQz@P`Y30OwV?YRgo@O zvY{yIo*z-qd<0S1OXTy;==}~(3IjyswWV~dAO)VI84>P`5&i!DO!6c|q%tCXt>=im zlTtD2|2iQiUG2f4Da)zxuTvM>Yo6zf@Uw;QIrT8U>=Bp?rimu?q?wp+vZsP3*S}xf zx#qiaE0u53ht#^u2m=NuZT4-5NsSddU8oZ5cw*(~i- z^%d||k-QUM*pwFojGQ!)Q7KGn@bUGdG%R)zDGf)$Lw>PkLLs}pvhqULxvN;WfC%f_ zn+RhufVxt^-lQ7Uco!!7Vt%E!C2$x;fpW^fCbFMKP?Yu?{^a5l zWkhUtl_qd;ZWf_)o1jD#9}28P)Yn_l^>rQN;mJ?2C%M&sc$H3HBb$q~A=+^%`mI>r zIQ#X`j*iniJRNQOg=R5!Yz<&Pv-$V!oZ z*mS}&UAN{W1}m)K06jb3Greb$H7gY7=+V99XXuMZ^dX_!L;J!-)oAvKdj^o~x%u^@ ziO7s2dZcxke)i-+kn8TJ?)_`6T=D82Z{xDW%u-jXn>tj|+68lI_gOk;l(nP*#D^MD zkj4aqYfn3?lx4JULRI=`(IU9G#VR8wm&(S&Ob`DV+i_E`SUg6NT?FH)LOf8Fp;S!# zN6Fr;N?jgWFNAZ7NIBZdNNMIj4b0q-y{j24fL_BL*9uh+6T~MvDd>| z+=nc!kVhXFjfA&(if2$B@ptx%%3ox~85g1J=}Bd~NZt==Oe}w%EF2x4Y!px2YYIhA z;5;k@nsXf-*q(P!?I&)5=XAO&?>5kE08PPqg}A#gw$CbE$g*hdeLT8ROs4m1 zaPkthh-_2J+4i_+*C*Wo+fG1wQFn}dgad7B8sglE1VrP;ALaUT+u zg(d3_0Dz$9S8=!EcD|42t5l+EiC%)h7vp~>Xo4o$3wtC8Sm7U2cuZG}fy_@W`vm1q zBXl%8-nJ@?ly@C2NhN^CElC6(lJ2N=I%8CqKXoM2vWx$ck+i@iqmjxRMShjfpY$jl z9;pkam_I&E>LJ8Jgsk`pF(80jvvjgn%*q=oA8XOdcaL|z^~o{~jblV7Qu z2)gP3;3^h{rNI#gaofirR3cby;=mWiv~*gm1JuHQo&N+bDltVV&$Td}btX1$b3~H z(J#S9s@+VFY=~yj5|Jbr>LUw+A*L{ccj~Cbreh4J5Htv%aw*0aue=nJY5fdWqj|VF zvCH$Y5sLat20I?2Z#B=3vHf`Dbd%Y8vn%!)66@ay(?*?h) zaDUyIR||C8=_`e=;XnMzs^6S*a=P>Wkj%aRA4p*Q-vSAYEG*3b7f4`Z`Ja6Rb{0mC z|A$A2Z3Ab?u|{W$A&?4+p!0Y}GJVD>6uawz2P>L35VK$CLMVJgLn7@=I1f1wvQR8~ zqtoJ>nDCtQ-u=_R`dV#T<(b?4+Wgx6>aDxdv&OJoY2hHL*+)a*Ak$6kg~H~>L8V1NJ$SH!wKz5r@z5Z$ePdL!>Qnt*cn z&j4urlLIHgC8Sf3W`I!uB(^1tYopc*!UdphCq)T*{#J)XW7H;DlnjZU+S$oPzK+-r z=~|CyY6QMjD5m`fDJY<`O`z6amk6j@PkcOQk;4%a&`OK)pe_Jy7wZDvB@mz;4!n#6 zB)nbPK3Gja*MD;vkQT)B?-V5P2UP7Bx*zU)11BIm`HTPL*W`~1B=koPOj~OZH@BZb zo?;rTK8#BMfR)o!t{6NC7htH`4{!hu;@y^S9TvtFKzkd=uMH0Dn1mJJ{wBo#R9K@^ zAU6R*4rKXYEk>tra@($nW?BmC_8cOR5Rs$rGdZwRfX4o9C;4jbU~_2ymf^0C)|z5^ z@^cMCtyWHli=n)UT14;^{&Y3;DSi$t2;^OmAORr)J%9rk0bW|WRv+5Jn@i9Syu-i4 z-u(W>Er=U%war+-r(iW<3q6h<=o$<#fvy%o|Lza&du#CE6s-DRnnXX45pd|ixAM;_ zY{TzVFRwp(Ik*8xX9+$#z~{^N+0=2Gp_)TR@EQO8p7?Z09a&LL)!+Nv_#dQ{?8XAA2K+%5H8Oj zfnDTPR5xlM)VCT`{a@&%;IC3G)G*ZL<$IqZD9O7TOcDcj_@5043L*GQfJTLIVp^;J z!wG%gEPV;5fFXh_2hiu09Uw=$;J?4foyn=i-`8J(?W9L{2&bw%e|k#jrV#C)Buw}| z001{fK<~o1Xy&`ahbTo$cF`&+OrL*?(KVzT^S~ zcnH!t6_D*E7WKNS_u)_ONh!BD)W8`}(KCpSV7dq25~X#VyRDP%OL?(e(wW++i7%v8 z@@XM)GVcnFH+k@07;UB?)1(vT#EK?eX(g|mPTu}FEgJ&mHTx)R!a);=Q}C~ctmXGJ zvGcJR$j@Z8LLgh7Ym@5Gwb_W)VT0NM#0oA_0@)&wF^SXv>7>z2|4cPR{MZ=CB1l~r zr5sn(<}o|PqJu{BBz+^u{th~9P_jQiw%YPyZfi|;hj>cjpaL7`!;eMuqM9{*>G>0p z$IK~Ki4`)6-~XFOLh^VU|&XO zUC=-gB68qVdm~57VSoSEO7?tsTQRy`Sq%PD=!2WQJUycYI)WC$m$+bTzQf~;;S^@^ z5khda(ekd~(u(TW>BN&H?Q=(;(caT|_5j`CJi!TLSS3dRyx>BSN$f*MkJy2Ul=G_TKuGMQ9yKmRkAQJ?z#1) z2#{ef^JPbTr>sD<8;DXxweFUeDAluhm#Dc*GwF(u=afF%JEr@nC-msjc}+4Ts>^Y< z@phX!=`Vz6faOT2_sx?$<)=K4*HM!}rl zB5iK51P~g30ROvdmM-QTudTV!4yCWkh({MBV=y!e@6#ZB=Dvav{_L&3Q?C$kZU@7S+I2KpVbGKU@JD>C*?Dna_0Y<+} zcS1x>>}d>;7eUd^-N&~&j5VD0YB*jtt!c8kXK<3+%Hteb&@5#{BaJ)3jC||Zm&(}k zM4s2pN|~QqQytuf_pTF!Z}xjX=PUN8hX%#|C3pR6^0+q^vWu_-VIpOWKj+hxeB-rp zYntCGG~#Pq!wx&a#;#)rUm#lN)MNX)mxZtrwJ;kXxLCMw`CTA(^ zjwUc6RhP=Q9ld*G@_(Ijg1sKMzYSwC63a!JPB$Tkepuryi$>)5u;M(W`lkH~0L56{ zR@RT^5SdZkR(Bvh`b|RDu{g`FfkTZ#$ddv}>J-pPHz@5gvQ^U?_|IwC=wuNVy?Dh^ zrW=F^{3kT`KdZ$EE3?}q$w%k9?{Mv#YhEwN9~1D zg%)BZAfmmDuyT8LDzlvp@^^1Npn^{)+~gm#*eKB7z+PJ-?2-^eux1eqXK!_5Xntwo znmM`kmZ&34>pPZ~U5GcFz98(RHecn%N_mQya#yNOal~O)Dw5*TS1FOg@-*8i4RRcM z)iyqYs43d&Ib@VFHtVIG42$#-;xq%*XC;rGEm7gt3=orgm;Be3_vf(U>eH32yLXKI z9K6u!o?P(;_3NEx5>kwpE$f_h6!T3Xcd2kZ5=u-+# zW%6Pi=&kGDWBM_q^z-i|6pjd%7S0Nk;|B@X4*CX|Bg7-~iamy@F*e6DmFg)@F#~em zlCWl`zl@S{>~JlA83bnJ98g?Ip(O{)@m3lyNb^c%CFm=IV_`Dpn!&?MLK8|&HCoi7 zV|(l3Pn4Ub-bQ`&cgzL)Ldi#@T>5@Dx`)&+ZBO9ohl7QZ~(D2IjMF}T4t zHS&;*_+h@=oYEddrx$?MesassD~Dqu zqO-KPi+SZ3aI8U=zNdPcE4r2N@nv#v-!gIj@8NJbiJx33G?rt^ExkoGs_V>gsk}Q* zqnaOh&lhwjFcxWQQK+)( zQ=aSkIS40hk$Zk<)REX?ICJLQwD;|<&p&GeX6&X?2rrHk2Qv#BI&t?DF}=-+kFs=F zp$8EF)^saFa)*qW6b($X7x-5pwdz?Qf|cpU@du&mgjN#|@G892eE5$g$rqTA&Ebmt z-O#kcd%Jr+fgWzJ_vS|9Q}04X>AM<~JAe|!MIF%|2ayORwQ7G)#Dw+(;`ZRacjtD_ z&u0eNdAVJn$DJ^WBerKu5F@uqn4Y5##Ldh3mv*jSXOtM6jh44`)!-$w+L+2kbT`e? z=VJ!fH6tNogcPu*FuU7zA4DH^Mezkym(ByXCdYN+qP}nwr$&1zkensq9=OO%Z#{rbCa9A-^p{%G*>{Fm!D;Mp)ICd z8tiFT-YPIN)5=9g+$AAG(7w*yP2s!e3av@5&hCA6uY5_;1d_*0Aw zG0FA}wC4tcozG*{q^KhCtIgb8kiOis^b5==G`HsIS&BAc*w-M7&%%!X_BYus(mxJv zx9g?U$WS12ygZcgLGlqfJ>bNDnm7}u;UXC>T5g^O53+n3E5Bds`!Ia>2TsZ;vAF<} z2GEgba)i@Yd=u5D2R(CwD8zBL({o7WK$g~g*ndf1q)ZJNVF$m}h4r_S-7+bL8fAL$ zIJvXqbc7&ygIgvfLyc1|2&(&>y~y8!y^l_mSmQMEV2phJd_S#STvLs-B2ysfChGuJ z(1&+{QKH%?#TTSzN1vNMSr^e8JoBBchTu+srx;Ot>Ca{(n69^Q91c>;vaVO?T_9ihT`P`|Wm4>knz%&a+Cd zrHFACNlhY%kA?Sw>kWRWhFJum3&In=8#hgQr5gI~=^476Pzpr?fqU?}z7DXN%N#Yy zKpWF?M)Maac-T{{;4(tAf%SBh#V`M<)Q?tmhgwWO!E|vhA_2WvU)F(-k@)KE7ACf! zh|}Ts^=a;@gv;hHPS;PHMqBjAdctiuhpAXb^DpvyyE&<}W9+47{h4R;+Wjao-a&o$ z3hBO)z{*Nhzs7gk$cVf-;y$&K z+C3^9S~uMSi)Ee$u}^%(m2b^91w5x9>)=l z>>PX8K9E;KtPlYP>KQlxigCp2&V}4xOPCSH9}p%_ObL+3T5A#!Kjh zFk;qRY$IGoiNjWO627#y25N(SnH>208WT+H#hz}naTK0y>OK)~_u=Fbj22+CBln+YkRtL`tYwV{9_BUZk0#iM}BiO zjJ@JC9nEA5mvteyW$U1cNjB4hKSGfQ>gI_zp&jqtyv!9?{0kSAdLWjCikPhj;BK;F z-^s^SLTT{TE@GI{+Zha((P%|yka(@)_gLT`fUu27RN?uN+aOe=6rXql?xhIaOWHq* zXT}t(Y`znE=iIKBC$EHSnn`Hroy`u zs>uj604Mb$>JzngnDf%l@##OS4+lMYi%$plD-3IPb1)Cf6cR-mJ)x1i~Rm! zachJ|MnAduURV#ISp$6-_71F|^NhuOB$e#c7L08w|zL5#?m&Vweo|N9=OcFllP4%uPNp#y^_WZo)x#XD8@8ooy@_+Rz zRo6CL4+odAa*NTP_rc3*fai9Ykx~MqXWnKdS?@8V)VK;_-7%KNkyvO$=SYfYbj{f! z;h~C;la1NEq9qKQ921T65p2~`e-rS0e10{RwiR)?Mv~V{WXDG;(li`TCjS;r&Zk^v zmB%5kJ!KNOwPJx_^eM-~=B-<37Ci864vet6I_aCOIjm`?=is4%h`->!3OE0K3!*R4VNeIz3kO;xss{gxZ4pzDEAi z$u-+4ay97$FV5H{u8NiE+CU;sZ_3LY^N zLdAjP%$l=6_}Vr=sYO03)U?tbNMl8#aH38ci(OV;$u+xM1X0tsSBh%uY@Yjxd&6^x z%{34do&D=nOX_fTm*72O7Srh6CGJJt&N4`*=>M(Uq{WM#X#+5;ItCkEmQ6VE^%n8u z`WL=l5j0+$s1=(gxoBv^EU$U8=gPeEr2W}ZtfZh)qjCA$I&h{M_^DV}^HVrfL7k<= z;bkKCee`-??TjFr;);C0XMOsZJPW*?eRczB_3-{f<{e|cjB?M)du&13)uOTFufsFT z;BszT4%fWGS&I*;Yl*a(*}z`7ni8ju!&!It$X^!CwLk>96%H3Rd#*yhXl-RDk{Ui? zgh-d-Lg)i}|spB#w=*3!R4&L7+c3`Txg(PCX6_`4Yl_-(YS16%DGccyp)<0gQ?l&A zcVKZL<37}fhX3qRA6)D*FQOq3V|PB9-uS?qBzBx#}L7r1H&8 zWZysTJZ_CKGcP<@JHh6x!HB91C|zWp8uq5ZTH-j$)Xl%7${J42h&txFezl^v26Lp; zamKZ3y2G0KrlGQ`9lwAEG!AdllJ`P5?24jhE`XoD4^OY~$6{*dG%j_2i{cNwLkTWN zQ$@HXZ{6%YLT8%+nTr18F`#RH(OycZr+)V}-XB$dFwh5Ljx=L$`X5<+&hKPGQ6;G@ z)Ect5bcVrw?ED>RaN?;zuf^+t17lcW#?@1QIj7bhk&)}H;lUWaz@b6v@WuGOnf5u_ zoO4rN%(*2{|NF2+x-Q7OlSoyjRxZ}9!6??OqJOpXRg9ROlvEh7W;eNYgB{uuhhKJ686A9L6c8=Rofk^^2-}FY+aN()fPwBs z)!qZ_@sJ}BT-eUeboAWC&}+GL8PokyIimh+&@+P!ioUM*5=?f;_U8vj&* zRvBWjMIDrnU0ap{J!x;dc7$AF0v5950$T}lIvEgU;=vP9F`l)3dpF`bd|w3_A%&F~ zWwc>}=mp%pl;QLMT?Fa% z%zaN7+-);d;aaR?c@jfRO7Zax^p?Ne;GT*#vLQ73dGh1%Fe1Fp2<^S1=o%?_`H^FT zC_gb}ttSeu#2mu#yP(6?tumiK+c1S+;PL}vwtaoI9P!VB z_vulaL$6s68d>vt&C#6*v6CjG;PTlF+Y3OZM!vhfqgvyjBOmbv;&xqI37B;Ib_`2V zT-qYE?h)1_Lom05d=XPvJtXq7$|T{_WmsC~w*TY>g;Xm{b)ZBL3_fgB)3jcaD&{PU zTzMfww37VjweB6J0$i2Md}UlxQ9RRwuG0`++0zcNlB>5W67{;z7*D+I5i8Wnd)tvi z4-MUDvTQ&ppQ<2%?~4?z(Th#bpL0hr4z^tN^=3uSd-;+o%;**1p=Xu19~Fb6jiv|! z@4hXpYKzo&s>=1XiI1)^B~l(V)*B64dQrMvp`AMxIdTtyDLhm2vF+X_S-&{+WRWb# zvgv8Zcgy>1eZG`b*DALz)9GC#tWit%l{@WWtudW3CDf&p5;ThT;erL%9w}b$fV@?$ zvMO66_}M)~WRyI0>@6>KxoiXUg}u)?OMPf`Ix4}Jlj&po{b92ryTyQD6HRjtXbP>&_Zh+3Dc|Av4$k3lk} zSDJpIEmt2H4qiHPPE&``Z3R*CS5a*AW z^2pHJ>wxUD49y)n@D0uzdPz#}XZ)#d^nf|zLX$bNRd&8 zu=2GZp>)mMrlL7a!nS6-9Uh&%Nj-DgA+7ActXu*XGq55MCRoNlI0@J>0yRp3_tv3B z6+J?PKpMrV6rsR}GUsnw8!cz4v|5gfiSkMZ@i&fo3)3Ri6p5mE(F-}z8U@qhexbqT zGCEfrz60^4#u0%Zp#lCpzq?4Lx`vI7Day2*AfJVR6$UB>cZ#&@c>jKM-HG+?J>qJs zIz>a3>aH+1URZX=UeN zW&3~JmHA%_dMRTYQztV54o+5<|DUwBfhsNDVzb3GiIqMHV40hr-^53V*#LtX3bSDq zbt+0A6bUPIAjZ)N$_aH8A2d&eLJomKiu4u#49hwAs};_rHLyZS zFA4SmX4EkF#X$&&NlA!lNys4(&`?mkqa*GUfy%YG1O@=-~^E9sS0s;b_iTw z$(!-}@&dI9Yyl98i{F3tE(1czAfQu>VZqJS|6Lc(n!^)ZVelga0twFJ`85o<%;$wV zrXeNRySbUMy$(7OZCe|6*adGBmVW^NhCn(v00RW}DTbMAb_w`O$KnU%?i<64_!8C& zs}pJ0>=Xbn0{{aR+!JexpvHl*2h8gRhGA_2^q4_-!#BF-hj<0_MFS@oqJL^_=Wq7K z3>fgG)(jMYAUCyaBS*0TuL~X^0N_=&_DKT2;OoPJ`w?jhAw)Fuwq*YYM>oKv-*7uv z1(XHx^`d9)X20SH(94*i#$$s9e2yvZCSf|JsO;*H2F4+QLgUs2u~ z+hgp4xVJuQ6KPT4TE3IP)|S=#FyOpTpyuU%5(7gZe)_KnDE!Luiqg``5C9%v0PX?S zp}!pTj?Q7;X}@3#9seQLzZ}3;^XC5$YYV~?xIg>C1}u<3dz+9~_b=_+ZMc9K1VBiD z8~}D0E{e=utTPKt{r6$iO)vBkupU_$9zUVqUhV3x?8zAlf=!>V^*Pa8A~c}gH}jb+s0gUw8GHWksr_&D!!P@BZ`GGB__r^yf^%@c z&t&@#!I$sRii}*^X)esuQAfd?W?rDcEzp;5c`Si%RSg^njI;BHZaLTlRyQn^9rx3p zM@Zg1bgw&bIFSk=UIIM=6wroWLyMojXy0pW7^qMHM6t74Y_MuliZ_0yrv!hT`Bn%d z-i%%mgeSP~)mkW=Ab?*DHhpCL=J_;+w2Q$jzLpXMH4!vP@xz)sy%@}c8iLWsg~2B(`}k@-Ail$(gJ z&=4>!eTp*X4kF)9FbIyqLP5Xz)kXTwJ>NA0vv_DAT9J+of?w2(IE0P3NY=MkiXT{s zJjO6un1g%z7vBqb2WNZ1&$n)hvy(69AMZ!Oe{dI}e+veP1ouh~dCNn-Sm1W1l`o*P zX$ue0b_1I(;^6IVVp9j3UfB8}y#TfC;AReb&r1AkC<>l;_Gh?kbg`z&Z&>LW2PH(} zw&H+L_onYPk&YVz8AH&No$QCmAo+?wFgJc^0%kLc`FwuLILxhe>?AY#tJ3A@1bJNB z4>|8HDv2~154A9EyC)LUcw(R>3X8>C0!#A7AmzlONT0_g7!x#_SJf)I7%5O#{z;6d8`zkrx*pfdmB$%DEpE~i_*-`3UcxrWgx@w>(9k=N?LZT(P) z7Vp0m<9=_W)|j2QE=X7`e`fjO((lT`@++G8h`4Cl4;v0kzF-_0>iuc2Ef!>tNKHr8 z52gNyIsE&ZfMsrLP;|b-mJB6-mrF!iVz))F@AX)&W%ZUUrB^5w5!Ac)IC~h{n)KW| z60cIbGk^ONV~A*b=s-abWt*S8_%pOqA(pC${7itS(TnZdUTnsNX-C1=m{#a!ePt>x z|Mg`aV?8yyW}8`R3-5qOy%fUlFGAuf^--2ITJIMQ5gw7`b{zWu(W>8&ykppbRc8pjvW8vXD~8Db(Whyt5VBfTV0>iepeevtj}Ux-G*-z)w{|$mpYUYrfp#c z7cuvhG`6|Rw!2T>)gmj(k@{+s*W^feYsXJ+?}=r9o<*UCw^6AVnrU*S&_-*+lQO_5 z1Emm}>G?WYO?{=4E^DMsQek7 zdK!2EY44M|j=!&bF7jy-*hC#JFt9b(N&-ee=YSfU*tWgt0t*nT81rt9$3VHzAs^l_ zXHhs2?w0N5Fv{dDFRNKKm;#ER3AgYWtN(c+A-`R$pt75!Y(6=rl*?yo)2sThYx7dD zgn#W5#WAZqKF^u*w+5YmdRsYHy^M%(Ig~bcI!ubs@G$ou*bL#D8}R`L$MsZNkcdCL zcZ3(9g%VLB{=IBQYMIUl;FA3wsLLbcz1ia`a1gvNj}~`1h}-P92zuv7g6+~#%*tA` z@eGaDjHBF~&E?aYe3X&Zm6yWYNWcG{UuRE^Mrk=#;=0$;DsQ;*Wq$s^p+_r_)}N`s=~JFb8IO$2t8gijkn+4bR0FFib97qiEf3=qqkUm?q{IX7UOd`eyYJ zAGWYjQ&AR?)*lUj9gCx2RW@dNoJ5b(-V%0dK@vUv=~gxU*WSQChCexb!ah&=z-#;PF7h!jw9c)Xe zCFvn7AeveqFPiu1xIy`D1-S>8B3TN? zB~NxFAZ&IQuCUyUeu}7xMJ2kI*-8dKZ~5$r%xB0eP3G&NXpxW)Pc zf5DT6(|G+>;(PhX#kBf|9>r5VTwYw|p^dDB^BG`uYM+<&ch+i=%WR@vZVAM(rj$4Q zn^+G$bj20ts^|hQ|i@ZKukW7Q9w_J{)#y&4J)MZ&aiOi@uMo_Fnj;M#T!J*HNS|2;{k)w3-hL1ujjr(wkt4eZRGWDn*{3gJ*<*JSxb8sZUPF zr+rny+#m>fa&5pFMzI^nTh0DbPC6hA3bqy z<`+rmFgtez!U!g{!L| z%(oNVSF%rSwlPX)2lf_2NA(X&$7Y^ToYaRIU&-!ufs7l*Rc&=2nPJ?yx3O$w{vLE? zn+Mp`Mtqr=)+DiG{?Xdk{bhJt%VY7j%?v47tu0H)=_d?s+*}UXN3Q6&9IZS0E#;rK~PO8HMBCX+wR(yRo~=Xw>iE=q$jOayQrm zXT_*a=N$|hE;)Cq*T!Qt z#I{aGC>~Ufp~A#97v-P;84r_KS3Km=mG{WlxSERA?)MJy%r+j+5=LGx321HZ|1U=PLF&s3Ad>_w55!zbJ+voV7x+Yp&GsT4@hhh_47 zvf0zL%(8qEGQiQ(p|qW;I$3fN#U>` zY9&N|>J@d$d0!BY)HYT_wTQH~y<3VA;>qTkQdz9t1%7UKf=Y-vBdc0<652|o2dvdz9O$0!otO57Tyq}PrD?j6 zPq57*BC_j=c)Oh2MRVtz<9YGpYm8gc)I$ExjiHuQ_)Pt~Yq@CCLXwfq)IDL5IY+E& zk$nJaIW@c?Sq(oA>8;-m8DV2Ov4fDe8 zur4_1C8Z+fL?f17VbJo=RzMl-vSjU^LN@eaRqq+CR69@m^V5?To2j25H-IBM1$x>L zVT@3`UQT<3ySU{#@0$}7;vmWP-o$i=HubmR4g+cPX0`R%-9zTWqeeU2bRL@jA7vFr z!yhWab!n|B#T|Fr?{Ymsjg|iL2FUbnKjNc*!NPIiLW^GShLn?apT%iAK)tp~E&LX) zW3owC46{bYWko5iSyg%YT?LN`@;nVI7?^4ICM89)j*i}U+DJL_GEE6*#Tk*?5sWw7 z3iJ9cZFT7LIDE?Xw%kPbxurB^$u-jwM!jo<6V*e@_L{l}4tav8pw)DMVKE~Qd!nA{ zCxs{7A8CWJHz8-!WOKpXh>xPZG1VMh)_N;4%sC-*-e-2Uz`q&Hc8~uef`R;I znaB0V6mfe~^Dybk)y1@$x75YXsD=}tz@;?yUG&#TyEWgpNi#`M9_H2m9*_H(%Um+6 zyztTDQ+w0y(!1ZP#%d=EHzp8;dCPKq)$WLkJ?KuOG+S-VwNnPTeJgRf9YcWsfga@4 zODK)a^lLb@>s;K#s>oDu*e*L-cheF!?3S0quMezCNL24nW#YA82N)IQ$ANy3*kB07 zQ*{7azG6mGFpZaU=4YX+crW`@7s3W~d5iiIqY=U)lT7o8&AFj)1{!|n0F@#guO!m( zF6#`5%!P|dxw~QIk8!c^phWoVS$Z4B(s^u1qYRif6U{b(!i=)Y@_d!Cj=L{q-;q6& zQ1bxyrl6$^XYYEI4IKB!;kYe;wQ-V8obkc<$?O)#j{ikTV_t|~rj|tf$P+yF05^Vv zK-CQzO>;2eFaG_EbWAZG-Os6Ba4FrEy8;1Dx;V{{EguxkIH52w^1zhR!R5#sx;;iYa z_#A}R7MT!gPqy8d>!V24RYZ0ITm(P98 zj}i!~imuCI)}g}N!~~VRM#Sn4w;V8 zo6ypnHB)n^{RO}-*U>4R?f6mXr@N9J!dkD$o2P~gcvff=;T+h?|8g~ zvBV=-%UzS}a?X4did}5CHJv{VD_AO2sQoVa0<@%)SOunMJ8{*W{m|kEIgCjl6R(HSdB1Aj}EM*TIb0!cz*EHg%>)gcH+- z8WX5hxdPW8LyHLwBM?&=N*@~`u8orPOI~Oy=lB1DufE-suk53d%tIT{67le?Rnx4= zQnR}{+pY7(Kjd21V|E|6%?OuoH>J=>ponaaTR&Bwn6}BNF)xB`+Yyo6zSY<*w7wWg zI9dhF9dBtKQ`AdRPt^`su-X2UeKj67ok7?652#Yt>>#+`x?$qXiZ?*0Bq=8I}~;2&jsyv-@j|Lp=98Q4c$^$H`_MS9sM0L_KZiu`V$cD6pH z{QC-V0%n=9%UOE)lLwC)d1V@(8{o{y!dqQSTe3^Cdz>S4`e-v3X<4z5`A#ThA-AP1 zdT{S!iJr@;)rNwauzHznCo&LutQ_)$I~npKmZ4PlyU zAc{Kx=C1W_10i}!9iPOaHme6Rap!f2%Wc?)_nP9@DEK>kpk+#VslDG?17c1U_h%k@ z$JJ4Jtblwp%jqp?Sz2Uwp~q??=yG)~8&3suV9XK6V;11P$w|k}rU|1c@`rkM{hhL@ zE9XMtLowNT)M*eomN9tH0Dq6H z0qOai@6s2Q*zLvUp8;Sz-rC&hue=dta5;x`%F*M6ud6Ezk*!tSbZz?wqs7j1XH)cB zc-I^1umNz^6eVIskcK0=P~yeZ@TuzfFXs-RRf$E~v>}R&cj{*ZR(U~qU5LVHk{@Vc zjhI{YYe>3kYKSNq~11SKDCveq`1P| z?cF&(Q{d$6S&xEL-JvHc4kpMg7?tcB0Dp)zwaJB>HynjEX7h{AeQJjVUhi;aZlQ5L znw4%9;s)jN@#s+1n!R$?uxDs}wRMsQ)AS;uquoQt?}G;= z1t}e)Wq(6k&`G;4HCu;xNw|u5Ah@Cmw(1SVYt;!IX?nX0>V1QLo+mIKCefM`XF+T? zcz2pYPlw`2s{1`pLWLsNo&`EdK_nIQ-idB&Gbp#~{p*DmBJ?5<)kbf6^zgxSixkYM zp0<`TOX?H42v=!QtnwgmACbhfpzE%43wVOjxP=H?W=(PUM8mWdY?wM;Z`pICItZMJ zdF$}R2U@HAfgmo3r|#G%aTUlMZ3_RmUaSlbwjajie4}((Q>hAKs`@HT9tUaFm*PfI zPLI9D%?1xXL9r=>&*kkKi6ve|hp}4!WBaMOX^KqY1BK{3prgR1x^p1NHL^Y&a=Y6j+&Ir0PPEH?^i&CeO* zdr(N`QYq)m%Pn%$sm*rLT1Go~6~%nTn`QrzR1u#;RKo3%Zv{7>s&MG{o6()~%I z<$cE8u^hGDtv5Eja$71g zb>8o`U{rDX*=1t%ygSiDjrv7NnLG9#zS&0;w*hcd=6nN)0?LhL6<_Ebuvx+|fnj;Oy1lzbP zUaX$O&{x}I8gSIP6dr!imU1e_zRyyfd`XgbGhQgpjdNE}2P)P%!`cZ!sX$!Yhc~OV zD@7O`E$T?gi381{p%sW6g3VWux7jE1;D)#UD?%a8SOdZXUaFKFRMV4D^;nIU&rIO> zR+mo}y|JbnO-ZjV@s$3dqE#j~id3*@l`FiO1}b52Y)V|0pKcXaU{o+0vGx~|NwPi^ zw4{VM0*mnrbDw;WmlKRPTeIe}*cQKwcRwZLKT1%xb zb6-hZC*RJGcpfnD%tjBrWbn+%GEJW~rgQKc;e2=GuwAP5jUZvPYakP{-_@y8X2x@G z_P4_C(+a5<2V^|@9JVW{hzW8@!mXK6Q^Ou6^~>FZ$?p5#5(3HHCyjkkXw~ao;Q#W- zRYh3^aA*CSLlZybPnp|uxH14vwi%i&J9#MC;rd{i#NXAc#sV&9s~#tvGLNjDD9#S! z)_>|mi^at|dTijfshf%Mz)W=$M5sS@%)8S3-TRwV@40(VYfG2qX-_p-vP$+2+dNku zZ-|umdV|^%Bv6KS6)pk+5#C^a$A5D1%o8o>LurlRfOz~Xh|v?Z)8x4lPJbcspRkO0)ytgjZ3Ap z3OiHWN%^Rgfy(OI=!@MhsBOs2MBdF9D+2hShcK?j#Ci+Kf3fNnW0m)704Uo3@E(RMOEzkSzXS+~T~2P=-%l{tUF_x$?~0eqEe|TnKez{-dWQB6T>5O?rPkhv>V^ z=Fxp6Jm%xq>E9a&;D)LPoR+jbSL7*s*9V-PE-AJCfx&m}gvO<{t_Ys-89N z$lp2CqO+ufN{HTm3Mtl~TC#|_%Qe@=ir|-$0BRl`BHhH`F2=d!7_zfnyg`R;lX?(w z!oNvXOUC$WT=K5kHGK$M!3DV84_#cfGb>wB;R5Z@(B?jEwkdB62B3p(c+Ab1FF6r^ zTW?xqzYH{Ht#w}ZXFaru4-c6>SJv{V#uF|(c3Q{emp}1z)@5TmGc=HOOv?tyq27FU zCax$UH{Ay<0a=JW4m{E9A}>C~N_@Xo^rJ->j_tMy$FXZHlr!6e7OcCjVAw{ZS5x>e z^*sWF2+4-KRB@dcPJ}!#!~OLVD&SLtkuhmmkH+Ai&}x0pOkKPzVkzfJN0`vfz{ej^ z45q`QQi$2qJq12v+#``w-bxf2pthr=#EZgIe_VB5G7cXVHu<9mCYayJ5VD*d`)Qd7 zH?{PkW(e(Z6Qt6sLAz^ny%`FLv@lu$E~-bwU6kxSO zyw;|T%HzTcYl+2mCWF_~a?4!XxU4!9D~RKjVOk6wVk!`FreudcZp!Wg1fVq5S6ac& zwXw~8KZ8oKk4ksAe-b6{;@jwc?Ms)iOOP6a^|roZm$K8bhBH|>daiwWS0Xz%D@vGd z?p}DiHZxmznr#rHXth*t0HfO+r(fcv zinDVolaw5Z*wjKb29H!xA2C95B$e)M-(cyE_-=)s0o{8NoXx{5uI>tvE=z?FWCRk}R2(AUSk>?CnpviLlysN1Z0U7U0nfLe@0 z-4Ld)?i&60)9IzL9OG5=A398c=7L?XYYnwx<2NhnRXUe~2g8#*E*-V6yzfM*0t~nVt2{8vsM zP~I7UtuM&EE2!N&<_!0&zF5je^1F1p5nb4;>#XT=_++;2tcrxo3^=54Ml z2q~^owV2`>q6eGYgqdpu1W7HIqd1gD`yv-(@Ee71#V0H73fS6=$z?4~_WG7<{^exS z7Bor-VwKM_ew#}fKkMFX-neDD!|t<|o2E9vBo$$y(J~V{fIaQu`9|q^Kf8c`tw-8j z7-<&f&`pLl{s;D0!@PTff5-)i;=t{1HACm3xIY7?^XEHfCsanH>DPz%>foANEbQf( z9t}bzZM(FSh8&z+;GR|U84OEShhgI>QEqNd6KaHu@=_y-^tNyO z@WG~$y$D(pwW(=gb*Kt&KD=j+1vTzm21iR$%$yBd*c6}rwiiqdlk<7We$=v)&)_i$ z@%f#7$RJ@>Gz1#4YkA@l4QwHSsR?=qlN}=*(=iAgvug%!aJ~NGqP)PdFMZ$?Cfx8?K0q|Q0pJ|j zef&H(ir(p;MSv^3N`Dw7c^Np$CS9<8*w*pHh4g?S0&?}qgs!pEkCeaN0`p4ydddhq z$mg4pnVaap9{vd_HYgA_Ieoas03stPncwfjyT9o+aC2ZP;09F&kAHY)v6JI}N-`Hu zz9V%xzam|aO%>?sHPAqOskG%m>&{ecr3|D`hIu7(@iOeNWwHp_IfYZ>FEvWH_94I1 zP^E*OrR7)zF^tg>6#vHL%TqHHAz24j9J_a1_|G>>gIK}h6ihJipF}c+6eL&`oPz&^rCMi_k$f!|q2K<@G zCnFJ|v>pMcW4$KUhGT0iWwndu5V8>NcZ>ar-IP#;38L(hDMWxdOBGD1YJ(HLHf55N zBcy0UXe@~AW~qaR6w9H;H8A1d)gT$6&h1Ma;H<-Gmr%3r*0fmk%m0jM2}S8W6;TyP zJFKsbJ(4oy7h)TA$zHdn0)gr`cwZsHB0~%Yk@rkKR^;-6A zrF+?(w5nWTK#odTR2*#%V|@u|V3~d1;1N+MJHW1cZAV3YV-v3lYF-AwTH6ruMi`U_ zv7&pmvAzltmd>(6GNgv{Zzd!RJ08Pqb#WM}hMj3cEgjr2Pp6PKt=h+K?dQiPXUX4e z8G&X}2PsWp$rX*Cm}IL4F#se7;sRAv$N(~KMIDE0=P4PjXHvo+!ws}iyoo#m+y^NY zR}Hf~<6f$&;>AQ+!W(#bnOg;~HA)0=<^J1B;Tmp8!pAA0N5s`fAdc3+?G5E24N(^W z(>SI(>46T!QoM%lbO0^n{Q#Kna5iH{oRy%&H33<+2XIqmpD3shu$Wt$B%src?_MVx z|8^&-9|K8T3kR+T12*T8(u4b&MCtJW5XQuE3Ko@7M^v6;HW9v_oT#@t7-og(^oq}T zZI7^;Db43Ge^44a41-B*KYw|YrsWvBHG78ZjnKiO&zsqc+o!@pprP_n3QQ{t?hACx z72FC~54QUuhCm0b|s%B|aI1mR9#K50c4 zYktPKn4?B4_4A9Un@gs)b$cwxdlfGAvBE{lCk^EFDv;L3bF{Tpku+!nFrnw8gWoP` zk0Or=%PWBU<5=U>{&)sDv5Yc%w!Lgh1~EPM`^<;mfw*+E>quz--ocfKrGMnj9U1b6 z{mJKbGBkI`F~0c%?&}iE+b>Bf<(Qq;&gI`#Ns;26kiwM=yz+LL(P?BzoV?|*@qiQ| zia=X1fY_UPB05fy1rYN z+qP}nwr$(C_Ogw=Y}?k^PbF_EspOnGl^-zg{y6&@-J=K4q&>Z3biJLPvv`;7+f3_$ zn@jv@Af-~a;zw@vT$8G$0C{BeP%+G-rO;U%HB9w(d-~eN5AoZe#pk@L(D$QKMfo?V)7B_2xg(G@ZV3PKX?v^ z$5nUnVj5Wq;MU+VzMT?#!oJ-NxOh~~GUH{08o79-F6R=(4RgxELR_+^Kxv4n3)IF> zgMT4<)9OW#PUO>c0pPz(8VM};(a9EOeS8UiT|U$Ecl|m%Y1{oCKZdGT54OJVrs7v& zOgz!X>I|?KDM8iaKabpffPL^vmN|@Tdyq2g$FzRmA+|Kl()5*Sn!QAgY}pD~HkTJK zDNtsg-v|<>KpNt`BmP=dCjAWFdVjT*BuB|9P}XGO#V2;bV-Ia;IpK{T-8D$2&deM+ zdfgS|X!ZHLl=X~3I6HyO&Fp+e@l2u{)*j>ob0nXk`|Mk#&}#8NS!ce5XyzXRuijP1c*zoxzUz6E(xG`M&@`kTO_!6)-skU$GPp2qWe^>GL!-Fj zDNZ05jd0^>c&y}h^>MlA2#<)tl2(&DvoKZrn|46xR^F~M%eCmjnMG4vtBe*UqT9#? zPC9aUh^G6ch$wM-R&?@5Y-uOmzD=bgY#zZJd=or2N(Km}*az^L2l&P+dT+v*^V3NG z*)XSSDTq62hK>~9XZP_`_UH)(>Yrc3{k-s*A(~q!zY+8$jH~AJ5^sgrgMV80=4yE> zY1X3()ARpGw5`#Td!o}5s}!zcd+>g&mzFp#S(2vT2-oor2u;H1++IKV4Fzq=nm*dGP2>09?{siTjFF9 z^Lzr0Ss9EDlTBnHl(ZpCA=0)up29eT7>qUdTy0@RHmk%VnbzUMn#gx@gpKFqIpYHU z%q^C1D9(9g0FeHmX(NFE$gRRdGpHN`ai(wZTaSMPPiG+d_yEF{VY$vRu+q8(_$0^4 z7e3x^%9NOj!5b4#!zzZUX>1EepzR~V- zC!CJ^Pw{%@SiVnMCj@pH*Iyx;(YWP0kiYtqhq!D`EpVZQT+)zx827}?OTf^w!f%14 z4fJ}HA1J9jh$La~bdsAiZzFHtBUeqThRHd6#FWW{{>mlhzIOlMn7{fYQ2T$=3I5}z z`47YM@DR}d51oMJzrp_hg-*csA3@=t#PEOA3I6l!f7KGRv~e+YBA^$uF?2B%F*UX~ z`KK%VcUKQH#Ed2!uzPwQj6Bu%L?`#JyKU?AE+Ex7 z$f2nsK_!1(b^jxPgp{kpyqbAJ2!;l2(5Z3t<5sw5;);}`(+hEQTm2rLp1v-5a~dfp zcLGl?W$IT}oXi5@i&~ZZ{%};hS|@8vwj@ESx6tP5`Nv*1_3Rd( zl+~2*itwM+J8q<(I4kS27L!1$cc#oR#T#CfW zvtNnuoWL~cG*`mu*U#h078A`%cKviFNEGcBGg6rmyLQTT6Xrz(L|kRL-aqmwN;~g! z?#=k}nU7{*e_eUSwRv!pecXIsUk+xjan@XsywiL7H#gaTvmnoh0HqTVGm*Eq%st25(!$Gf)^cV>#dNEH@})@=G@n&y*J^UhE_@i5WZz&6YV zkxs+P*lb$nbl|bJ?4!C$K<^}i$Gt18Y(jS4ucEL`^zPK{>J0n#7l!tfCBuMfhh`$P zQQ`6Dy2Ouxcwh7Wl4_oA)HSV-Q<F0cZ}!h55OEdEI&UL% zXSZj5&PMIq;0eJt<_;USHIpdxFNjFPP=NDGgV>oLvCwWDb06kwE%QUZg zS`khxsw^xl$}rRj`7V(;U_i7{7&24>4yytjj>{al9}&hI+fdv)jCCSCA>YF@a~PO$ zNZ~V6NloFf-si*#$o}e*rI81NPv`**(<8Y3w(j&O+2Amm+pTENn3;cEY1P5bVQ~BT z>e~Arx#DZp{$ww)ZcJoK*}|6*v-buyU)n+DOsIZ0|X>{fI4N z`r0Q+v>z~y`Olf{X?~if-TUS`j^?br*(thle(C;OlfZXAMrxcB|H{W_>_t%xVUmr* zl+ONi#30h1hJc{VRRYM;EnoqiXNU~Agy|;3K4JkxF;#)qqt)NzxNuUZgDh?bV`B-9`T7k{$TXqCK@gT|0P=F^52?m*@;lYyrKuN&8g{aB~R(7v5P+akEiL*lV zns*+RfjjF$C^=&soRR}G!(DKOA*<%fg!4#1R@l|i7~h1mhW7zZAr4m8GoBP{Y;yc^ zM+A>_7#akk1<0c02B;tDA&^n_!4okt5Gf~s`J?2}(Zv?g&ca~#asr^pJ*iq#dL;aR z8;o}f>hW{*fjhv((14^U0H{SR`C&IBW?S{(LE;wzcM!a}EH{+uj9ZmA+7s~(N#n?x zhd_%!?59s=mL&g9efkg8OFQ-VIjn`C1#bJ$;kje~3wxkIB0Z#Xb8boKvdo;U+#x7N zx*0+lH?n(j=uZ^+F5DNZ6cuEcR|<-(z3CFdHg}rRnXS=95M43Ep&De4GdCm z8GJ(E_7P0*7EvKr7j$Pksb{#lHyriDyBFcLYHJUdl1h2K_?}yXxN3*TK|wHl3ex^- zfE*1R7^Vkkk5ljymy12?*nI1jLq&q`ms0ju_y-BZYj}v7KZYh63Wy;^Lwh&48|Ded zd{sj{-=Is2p#&Uq(Kum96Y>p5nStkYCrH+50yU6960?60!e@=L{pc_`5E4i~{rdqU zP%Y1G0A>nmHY0Gv0r6oT)=$IzPv8bheADk9bTS<{F5rBOFhSD;B*y?+Xo7|hC&mph zyApk$t@1v8QzeB0%ERs-?>Sr~5bVEoL^+@iNTPyWv;d%!AI^6KBSofiT#`kwrw&h; zG8od52{&)_B>jp#tKN*{By0WV~gy@86;0j6xaf_6lo|NP{DzXhV}+b zYo#p!KU_)(rPTnT2LOo>aXZTfN=(^C5NrhS`kGk{ZeGD`-exwMrx1ncCT^^Tw*&o( zZ6!Y*?jC?37$(Z6_?V=|qKMQ6!dUL%2t=k*1m z0p_nqrs2!lh^avj^Ft}u*Qa|tY>B6hMO|_zw%mt^@|x%^eOHl^1?*PiRUhfIoHU|t zAYbT%f=A%X!uW)T`0zN>4Lk12>$@N$pT&g+6xQYzt@fX7TN`S$^`&n+o8cJc6>lTT zM}%)1%ro!F#0r=;PxwytPI-gTi`Js!u=cR+>K4b z890NTAQM2+X3rT?Mj1F`0|agS>}Tw^7amFO_W*QY-QxX0*Wj+X`sF=WkIR6kkSpKG zw1<;YoPQ9-l;qQ}l`<=2b%e&Wz@8M_KR(b|0<6E&js7&RNv9tO{9+>J6b$3wU-SW8 ze7LYh5Y>fUY}~R>vp3mq@Xmar`jQ&Yyg%!#a6-W$ak?=VWIl^n%lVdtImK=MBq^btJ6x)&fUGZU@YD4TSmkaV&WA2KH8x#Rye(cfInEuwkS z+v)cmxVvYQid;FwBp8EArX4-WR4Houf(LBMf;!;@erNtQ9d-51jiHzyyP|o*V{MiR z0GTs_e6cI0FJ1`{D0D{Sq=$t?Xlq)dyZVxbu{v~MVPDAAOaOMSvn#6AK7W0M&zIaK z&b$S_xEk3sh1+{c==g;S3x*Yj`WD6Z5czJa8YO!hdG9`_*Q-^HZLp!y&esn8Me7m zwFnn>I6sOvSD0%H{)r*}E~&WRCdSp|A=0ZgwXvyy21E88wHl&NWMC(Pgxt@-#qjf_ z#?+6*U0j9@F_Cz2D`n!-a_|f;O%>x}fVSN59QzX4S?+4~J7wa;MJ5YzWc9VKl($jDiF1S23jEx!VU z?sS@w&wyc7T|=afWK3MR2#_QRs^C~T$+O#iEGz{j+Ov=~D?=I^V{J~S46mro>(S`D zZ%6~FR}IHPYH~<-71ZEVzJT(5FRh!D%MjTIUT;T7(mEtzS#vxeyt`XAub=5)&c4SoJI z-{+kFH|X=9k?a2(`uykF|El%>i9YPi?EfqJVCgzQ&c(Noh@>=;T>U&T7hQC_dEvF2p39+2=GfBFMSa=4WLlFf zs+>P53VBVV<$`~?v_|vM-BkV4-^ilB#qyb^rQ1opQFMCmx`3{`?!HWY!OH4qU6rL3 zU^+FWdt)$8GpYJ4O_HqIxbDNlIuS$(ARxw+F!7ysuBDD&#hOToez~My{{!}^Cq?@@AE`Vs({$mYepIsxtF*D{qR~E;qo`iEm>eU%uuhD*@H` z1F15Xp-;O$q5rKxJm+4>bLs^8ZQkKtbo#0J^6$;(RJ3w2+T3ne;;R3jk5kZDZ9kLB zITzy)rp!Z$^?D$k=+beOD+52FBT_@UF1tUpk$FF}lCq~}ymE%WbplfSIinlC`s`2L zR$5cWQw$<*`hZ+h5gKAdNoJFgU?p*+);dQqM+kf&sxP>*@YY$NQbt8hJ&QUE6}@34P<|H|66k9oxDf4eFhG$9phw?u;6I+Hm@j<2@58j~ zZIlZK*YY>l-HM{v+t$%JT-%QGs=E-)_W|}quQAbV)C9cM7 zpbd<)18p>_j*TL}EOMQ2`r^#%f@ILtlDh?kOu_}EgxxZp5h7HCvn*Jha91%>Qx3QT z1cKX?Me0HiIuqWwbt}P>A-uQI|_=oz)GqrU3AS2AGM03oTQ_(90k42+I zi;f^Ky9|n{+qz{tw^Uru_x`R_on@zWjd+l)_4+*C97E>`!flM2IR~J`V;^D7ZsqYD zv?~d;=T+q9RM5*wVtsnCf+m~TQn%96>-YJFJM;H7TA>E`cYNGmU;1mX19a2(eZGvj z+Y3jr%pwsky+x3z5=|_1&Wx32WAR9Jhm`LYoL;hnbQc;Pfnz!WPx37=r;ncYFfkKD z`(5&S%&`pVj14?|=Up+w1Se^gd3L>>V-$$ACYZY@ad^61`>KcC;9gZ2ZTHaGM}3!- z&xS9tfL?j4EepneeU-U;ZG!=9xMx@K1LZwpK(2w>k_622zH>*pV8|Eq@=mF z+xJFmJki9kvjoZAFQbVs+iJjnIY9JRb*bSS0~OI3>cEoqru5p&S+JG7VB(2h`L?kA|+X307ZD;kNb%wH(J3 zvoV0tqB{v zpRJn6;P1z%dxXZyl!337eyk;PjPvD%LHuKAt-ZWr_h-QM zBZ(Ks^y!WB=DRrfc-u#oF!eAH999b)#z`Dk=^LyWaF|d|8}qj#;;Nt&-`T~twP+ft z`~YMmL0wIJX}ZpqtP*U(sj>)1Ai$qxDd`t5_zm!4Om*EAWLI!}zEF(D6JR#j$$d?b zHg?x_Rt3l;>^c=RJoAT93F;>&BB(H+0n}qp1Rjgqn}J@e-*n60J)SlSIBGI)Z;O3!VNU>qat#?zpgTT425{82@KF zMeJStUda=%eIYXQpd_rjfLT_^9zd-M(ccYW>hx3ex1sw0dDomM6Nv1S7Z)VX0IDTm z*ttm7YsxUP=dN`lEbft+v*$cQj_jcGCDpH#4OGIq~PE2S861YM1&fssV2wJgu zaE*$Pg$eTphLn^CMY>#>gV`xB6#SHlfefwadgi+UZ7d3_ymd`AH69r@BpR0#h}y&T zTAJ$3ZtD>HwQ7kO4Kj``FFHeJ;KEZwGjTJCtAbtM2+%Kz)Y$|H*Q7`tYGI(Hp!`x1 zvUL`b!Z&4$2%2=fWy}Tn**;qzn3}v2qRBAcuV})gNFTCw0uV<7O9zG`!}wbXC;O>@ z^uo}|MPbJ*35Gmhu4H}YLb9m?`NaI@0OgixK^R7E2s<@8O+(!lK*v;~nXsvM=d(QM z+;jPY*FtKR^Z-gY6afFZ0?V0$>;wi&LS77>5lM?wi4vz8 zdj^rC2^%7&`L)P|0B2YH9LMq{h|fkT3IwC&i|CfhO0D! zmGX~Q*{cAtu?>KH2#{qlip*jvNRHTI`w5QCq@~iDfWRi<+?r-MM&g+8P*`iEP4lJO z?u8n7oB)2a)`%f-GE~8COYmaFkhK^`s5q}R=Z(tc8;P2i$8L-Rdqs$j@{F3^K^F-w z@w7NUugxxSC;AD%v48l<45t#Vq(q9~lTP`dX^vXa<>{xp3Tr-7IAVSbZ(Cm|>}fc0 z{e~rBB*xa&2D82=B^ef0*C-W^c3m=dorQr(a1G~}?)~d4vh*N^nIM8}5JPs5D{y%F zE0G}$jr8km06F%zaM`{fCgIjp`fBYQ0n$m5iWIV?dbjVw`%$yehmS6?NCm~^UTdIa z%Cswz@Bo&Vt1;SZ4-Ji#y-+_BCPU{{hl^t&8!+nD&q~&$3jahK=1*IbuuR#;A&TGL zRu!HBZL1E50zhOAc&l6#pb2Cm8);2)GGjPU)}0dusYb69FD9M@2X{H0KN{Ya;h%mn2y?f@}{c?DoB# zWKQ`AsfP}$Zd)m{=sc?GnS7yeSsih0t@J7iY66o?dK-8RM&vzJ^0$^iqSP+I$z=dP z(|Ojqc^J_ zC}(Pa3e0ctV33#_aEy8+7d`h_03UkiB??=C`kIIrJKmc$F=isbNTFW)HegiytL<&`c=JcZ$RJn`{9rbgs6h&g*t{Mg; z1%dvE3?K0bICn>0jDPLd2iJMtm_U{9XpN+UgR}_l0i}nLlL5Ybt?rHj<-7x@l|nD^ z47||DhL8`VRSyNm;IvHZC}B(Hgq9*>n~k6H6o>JgghDgVGf84) zLHnR1?{Q2Wu93k=^hs8N;h^r2zzFhiBF}Lh3>!=oHS@a~hL0dt^aMFrkxvm5Ngfku zPJtjR;A1$w{4d75hNB}`Cuv71fqzZSXTxTj0t9o5;z~hsoG;G~W5^;Cvutl{O`9rw zC_>le&&Y2~Z7F1n1w=F|%ay(K1JOj<{iaP7`IyW8#-hJx?uHbGCyfe+%Ut1i*FFd( zZ*FPO-;@t#zd#muw=#pe3LptP>Qj4dg0MYwPaZZ`huGe$FSU9a#+`(P16)yno_`M3 zoCwSdeVrU_8!|p7ky-d3eAQ>N82^E>Bq17ZUZCz4i^fls{ob>Dyo)u{&}{ zHsn1B=`6>DV>ajX?U-cUHAB<(RxEv$fuwedtwt0)3Wla}STE;)=@o0_kbSlUPxV4O z45*tl2&{9MuWqjy-XY+UEl=AV%VA=mK5T!aaNIy zLqrJG4VszhvPqE>($)39uw^E6bN@G(X8)f6_kX}N$A2T@{~MV8?<2_n;~eon&;I{` zX;x--#{UJTk9D-|kJ!@uZuJ$%`%)O&20uv!`CZ7C251_B^JIWM&pvyHvidq_w;%@8J zvT9lOL62RY{hB`-PC&W43sNSY_1)cH&Czq}+-0}_cGKoqcly;x`#&6FB46f19-1d* zySDNMr^&bedHwEg*5CPkUA3Q?L{no71x{Q6Wd&{b!|>R6D-Sh$WBD2`&s@H|{rB1N z%8tJ6<-=cn|Mq(Pxe}f9bormbIgup%KmOq07xX>|f8LTWt+cPFHa~u=M)lWZo;+_q zRD|=7-45k1$awi!R#t>Ps$f>7{5v+Usr}n%H^Y5b-8*UR^RYbeEcW$w6}SKVuJrX( z^X==ned;Gpx_qCP31KO>?Wiz3U%Phbw6p(Qg+CNpUN~L)+q%0dqne`!0gq65a5vVn z{k+FB^y#U$cQbSNaE(PfZSSc32o_}6w{<_D|0`Hc=g7x?+-7&<1MTD9v(jRE$z6WQ z6|<}M*t52IycC`xzmQh=Yg-S1oN9uat52ywp~ z{uW0Axw0QFQ!X+{2{gV-9OsV=10FjF27O+PG4-l36D%=Qk<9AxK#b{D3qp`9?JhAV zNTCEAs+j@FG=MmI2mxi)cV@|WPr3)pwPWVp&+PV=05&_NRB038q5a>A#I9EV6G5%lTzr)*4wl3 zU_VA^gOSWy##8|}nVKZCB-5c&Ml(iYOjJc6oy(l&A2Tk5P*xWa+GHMkm+9+3e@!B! zP^^rHuLgKAC|MMirF~N!^7?-%YAg2j4mM3i)K(qHBBCmSk$Sp2y&rebnznmn6@Us4 zX3rC>z1oamyu`*kbwAG+`8z~88oJ$DTcG` z;q_Dog(1j*MAt-z6NrbIkbT@??*4NV0Zj)9&t;z;x%>JahQ#_lWD7P2tO4n0Xj;fd z@ELX_TOB{goB$T@HxyD?)tvOE)oT7vRAaEs&p0uhCtXebnCjq1YcwdCP1Pwgl^qr{Ic?0Qt zbd=60eJDJaYkSYbjRVk$#>|02l}Dk+2ND>Q8u3^={SZ?1xM?6VZQa25=Q2#-8G9eO z!t&_@kmE7Zg7{9%PicZSH30s^LN;}WWi{IfR@*mUsh8nQ(?!o1PYFGvC}EY#9T=Rq z+71SVzx7N?giyvug*K%BMzfbiL)rZvI-ot61jX(p0H$2%w84J`V5Qw6Cd8vOE@15B z&141Os}dDr)HI^{1bvUqT|wc1a|-Z3j_PfyKR!nHf3|l%(;Xa@+f0>PH52Aay|t4B zj_!?+jnM`9(u?JgdI6TedjS{{z$TNXC2Ho;L|Cj`^Dv>+@Op57D{==!&EkPxoglX! zRTpcFheVi=+B}o@kA*1Rps4-xFNK?6KFMHGpYE;Aiq1qD=ouEKlZswvl1W*^sj*lu zP&-pX!($3z62RrvrF5y>N&0$z$ot)M#tI7tiGpvS})2KWi3i%)WeI!1nQ5NK%HhhvR6K|u_zQ5zEr0%p z?0yndA;4i}PU0vcr9Z8{kFx}+UqB6_p>9?qT+pA38AmujXP%Yl&Ufhp|Mjy9a(rFi zq6(qTl>>|JD=P8$V&b=hihgr6zm470qoik~fP;YTx0xz?{$r7pn*nE+oLlG&H6HJL z0Mh91;#^^s&h4~0%ol?>orU!3bKm7kE9{cZhaVsiw+1BD4pgETV|-(WK4=8=lOpE8 zP+MeG?|3PHK;z$6hQs0ay)9Gt066$f+2C_fLZ36NZ2g;;28dU>-iA#(p0HuN7jFOS zs(bWF--vJfs%ogb^*4REkD+Tm*1MA(<=&uKzx>!A;xS8uX%cV%B)a57cVSC1ZO_{k?f~f@6Pdh z-ZgFt_f3zmVh_qdru~n+o$`iGK~h}ipk>$p!a}L8;2=d4XBC!LY>uxFD!(a*Yk>ox zCk_^V_V3eRuZwLvBer_wLNAHz+p{6!n}?%FrV=D(PGqci_5_rB-1OF7ek_>2?=3?E zzL^)SD4g^BwY^*Nu>vclZoUc%K18AqoScT)aTlCROg`yrDS=tR-qT6B+|Glu>qf&=g2l}QU4wah=or2 zdSNIzNfOI`z(Z@8l&PE8$18$jw0MNsc^M~ zBfBJxCtoy+IPcss5(aXHN+|Q}=sDNe#@usMIJk>tIV&JkQ!u{^r>bZr#!dLpHkx?|JW!4NNav4F~zD*$eZNmC73mQV7Ls5oV0 z*mE!R7 zo@gm}DrF)AeKKH!*UUJ;gEod7V(pP5Vhv{Lp%a0#RYMpM)fQ&CU*)~mU}BAOLBl;& zf}&&Ng_N8XWObjZam5*U+vq?9SQe5zGXDU&tDQ%btACthPvo_CmoB`})UbvEGUUKE zyc-}s!Ii#Mv^VvrH`H^ZQ^(RzL=(2B4pgm*b`Wo&|3>kH5lgf+F!(T8waES5iuY`05&NAzc~(jVa7{U zlPFBNh-4+uHg*3mwLJmmo$Zpn!H>u#h(nTGl#EOW^r`q55N~We5J+=N{bm{UsgTI_ zD^~h+WH8cwS2$ZbCc;3YsK(co0P>xkqiAUdoV;ttw?L#Q{~(A``?vY&mDnHCj}$cc zv`~xj!uGY1_R@CMIRmlEUg7lg`}o?0f&^;Pz_%1D)F*?eB8UErD@*(^B@MtP$dCPpi$GW2;%AWXZA$8Of7F$?r&$}F`N=asByewbEPTdw zSOR!ViTaTZ zi^3+&h9y-))*uiMz#VX68pBed_s6w7hDCLGWz(rHSMw7{j7t~4w6%GmiWvtzsgW`u zgd${&Op$&hI`gk&&zd@$hJNq9#*}!ijhJVf?y@)X2AEq;77m7u(F5Z!j&`7-K^Q$G z=*F~SQl8$|8nu1Soc1zkyZn#1s+a1OKWFT|zB7~&4B#_cJ0GG|Yz&FZ`Q(%E3R61( zZ>vnJmvggs2kgBgdVa6hgWv-4X4cSlI*rJs-6J@ z`#=B0$X7O+i)ZKVn^^WmBc1RlsGU~}IQasQV0A>9!`uRk`r1rGf;@0xPqZMl-zVGzHp zU^UYo`H1;8Y=W)MqRAG?R(ZZ9z;s1ZrlC!Grs#z+$TYjx)m?%M%~>5U z%&JX#n{3^=gq}Fa@Gt@mhWL2^qd5cwZ74HsGPF6v@L;?)hp0~NC?Yhut6)u*W=9=$ z@JJt(Zbz`wZ%b2ZA`L+^IBCN+RXhzxG_^E&4ZFr>a!tEt!_Ig?7=?%aWzC9n4SO@+ z9%xkKUXtH=50{pGXcWhem{4=65W^EFWr}VZ>@^a@PS6oFA3!!h@Ml&Qrj;eMk@RWR zBq|z4BqW&rU2!~Qog9i<wu8XSGR)SUhn+3GT%3)VV&5_>ePo* z&pdJB0bvCJox~0h3pTv0S?bqNmBjiV@lX5Me_@ zKs?bA!I?EDcDq_p!cbUd{)d3ARUyrrjthu>IrVTc5-2I06bcHOyGu9n{n+=n?REPW zU}Y$&^=t9)GX4(yGmOKFhFfD=XA>A%{|bvwUVi}yt2(e>H$qm;?S<`VNB@+u3!na} zwD>t&pd{)_VB{{7quxw_A?Y*{o{oS|0Ru~JqmU4B*U>yWb;w?Gzo3Q7PXT2E|J}=0 zgMpVUmsQY$khl&PIgO&RC2RQ#CTkfF+!3MW@VwSDW30{!8yxX`_gO%IG13JoT&EE_ zTd$D-JX|}WP7JGzNM{v3B8*E`xFg~Ix)<6O55cje*Ncg~Cu8hlIR_&g`^Zz$h~%lL z3oY3KE0|M~$j;@+U=L=BxgO3j?T7_N1NmFw84TrM9{59lQE+b=p0DpY%_Kr$A~JVe zk{2Y{2cEGa`d%ZJ^e2&4jgaGS-KZ5mAaRSq5Re;aG7ZXs=mI8!GkRr7yN1gIT> zS7LqIGMhORGz11AD5{N`wR6}2dV;uhlqW)8L2Gy%28Z`Sbj9^Tn3|PQt*>@!Hrd_8 zEb~~3P^%-`7qf9ZUr9)@C?VR9?_Tn8#~!bvw2n5+|&^bm;ky#0}k7tQD7{_PJ$4J>iOH z_d7L1;VNhtYFr%_(=HWl^-}out+r7@*oc2yp|h)no@+ZEH-BV6kY7|U*PeIS0rjV} zXZGVB(HygFTst@Ix0<2qx~>F${>%wp+!S+v>YV(2&x~@T9~gwr_gsrc=UZuS?~_d@ z>VuQz_rkuq8R$6mo2~WU4f4aU3DrEJFq)AWB+C`J(Xc_ zWbGhKnAFRLd9NN+nc!?7;j*j|B8(%07!nrp9mVux1A>P4-Y6;+@470XTp%GF8OYd3 zOz`rYps=pI9sNfSZP8R)1RW;v9#>4HxC2EaZO0;Vo2Y^7h5w0LB*lA_&#!Xc?foa@ zJs(ingUy`LJQ=A+#`?^z5JfsP5Y||BRJld0rxVq0uEq8D=d_L)t`C|1sj-DHDBRQy zFUwwDAWGx=9*XF+{D26;xe-b}{kpFF-@%7-{{ESP4hxdiauMmcsF`W|RgLM!;I_S( z;n6&|>{AN;-@Uta;qb>UdEkd3`u9l6Zed+C#{tVGM^|BqsntRi+G$IzUqx&AYSlL+ zmn+ny?5h>b=Qa4*a$y{)psAJwONP*6$q>hG>uS4HA4W6^QWu5_NyTXD$*Dbn<(~)9 z#jBVljFwI*ygRq;Rc_aG)5@v%_S8QI{0AIlKxs0$2*-j##9`q-J36g(1n{FVifO_g zT|N_9pWGXsJJg6oRI3AND$u?oG8c#&)H0Ho33-_kJeQZe<&ZB zMwBTB@v$8;=J>mOm)?l;x}+;QlTDhPFX_1ohmcRfoz&$oK(-aj)OfO=&vFM03X z$Co^J6%DztbFBr+9*$&zyxjC*CP*Jr_8ZIIrxX?M%t&(5LA+_nG!@{B>|IE_do?g?WRgCSsR$_j-lqHWF8|#0StS_d%O*y4 zIDuvK6L=B0vGL1np*xxo*lq#)#cCg-nQe)2sS};};4-ZPQ%${d%DfurrcPDbC;FK4 zOlTk~sDR`^W+=U$^JSWJ1VsJ9H#1vP{BLqC=l?$mm63pvnT_$kj2TP>Yz$2Q#~cjJ8ha{E#j07OK(4MWM9;<7pqD>cLb!!Qg3Bb?5%D3Kllq@+YdN*cr> zA?aQbvPgP|f0*<5o9n&WvOv^SLe<@H#S;g-A`>F-4Gar;DAyOkf0A#P*G|? z1GRUG4)pK{ebCs5IjAr@|Hq7{Os%1SLjjGo_z`MGhJeyuzCU7$gJ$ zhzJR&2DHFPms^1st;3U7&F4>k_s9pZ63Z zB0Bov3l4m|TOg4E0t0Rys2Hb!?kozSfw><5Fi=pD?^j}g@HjxIQ|j*C)6)}hfbK>B zWKl0H8GH{^m=oZs@J6A8d>QS|+8}^!4fMmDi2x6~e;)AUXUs03LD(}eQ2}6M4<=Y( z|KAc zz`;8JcN!CF9?%jdX%#qN0r&@Z4mT*I zvuG>uhT&A7_~BbBxX;wIqn2oJ1QbfV^YHsZ9x^OMXnyr(=+(Sg&LISS>-Kg44y?WP zlQpC^oH`#7=i~%XRrv#2F#GPuxH-HCVDRhftB7b2paL7<0@iWwGj(Tv4E1A^@@^_7 zkDz}Jbswrh7#Jk6#_WPmoJo~H~%i%ZkZI=%eG=#lB zUR?vDpPFJC9svOb89V|C8UW}gQw$yEa}(^>-)Lpe4I>`sXmVP7b)Pdo`VDM zce*rIpcxHn?+t!p<~{VC@GQLlmwm-Q{A(BEhkEKa`sjBXVSH%)y*>M|{rA@)Sl3{d z&mYiY(W_t{XkMtm6yVqX%=ip{fr=o<_II;945-+Y0KVP*=aqDDOOV07qLvN@)bVX7 zmEWycAM0OJQrHFIzE&R(6`&rF|NF3uIoZCDiM;j3EFXLcCy}5a7uH5P%y5>2{TG0p~RN*L3hxTq^<` zDe~{iZELah&*hi)utEhpN)S79Xn;f*=ijhi?&`Gi=YwsGG(7W7kDP&tOZH%!=MI8l z$>W+yNKK)<@$#2m@;rUz6OOcO)1HS-JFj5m^_-ejr}N(>D#_m^G3cEq3emaoDckoh zyN_;{o-n8EA9cH*_@LZ|EQXDh^@nYqVF*`f$+;fB3XfIS^ch$&8QU{kwMdJtPffN_ zY;zQAJRPP1HG^bs$&r}YgVM2MqW67S7Dpc@xg>U{p^CiWSo8*Da+M?VE*cDfzVmJc zLyEHT%|tzsJ<_wr>(iVw>?Jy1R<`LAtx zWXl&5T9S1q*++&*l3zcsH0SW5f;{euFYk4xeV7{1{DPm0p`Fobb1gKB`0dw_{D*Zv zJ#vc6wKhX|0<=>@7!xmE1OA_}Z63I_E)S`t%HPnag6+uJ242$}I5$Pj3riSapDAih z&7_-VGDU_}#F#qH4YZ<}+gR4k_Kgc}h;~i8b0aH-=DXD82vK@v!?=`Kt*Pxd?Q~`? zhMrmUMuQc#b@?@i2{gz^s!h4YZZGQ)=vx$B$#hna53$F>6qj!0uCh1Hk>dN+}`4E{EMJ7_TV?lCyz1o#HtMrLY12M4X6oRI#!21O8 zKBv=Cldio5Wc)tqK+ER^dw`t4_|$b~Jj(HI#cu>$LUH`K=ORs+UNMsTzl*y~_4l6*f`1c2=BG zOFGaJaz-UR;8t1Ft?m275KaZWsy%xVx=2Nof}w`TPG8e2Z$4-+BvSv5+)}Arw)IMX zg$-!mS`m?4%|drK<_Qkfgss{|P^%%FJuz20xyZ&vX+qMxonLG^UmuA$ z8ZRN@JrN7Ki^T_Wg@2hzcujJJ#6f=zx8*v89qxiX3Uh(E3vToUwjjxj`o?j#W`HoO z&wmx(>AT1smK@NLk}t3L<7=9mrK>2L71U~2WIZbMM*Lm2?^(E3TxU1y6Ja0FaUwH; zp5#&|ipgh{Tl2ocD7D}A6JF9zJJw=m-sz*SkFbY39g}wD{r*f0@ggj};>-Opdppwz zn?qerx?m*d@O8B-y@L6L#NLsj4mw^y7E0Ddi{7y55=T9gZYyKMrc}y%B!IHqml-R| z-LUG9-`}*N6PA8rwuo^TjJioDst{e5T;pMxx!MBSY`AQ&)@BNvzj4qwZpOV^Bb<2& z6oDMAewRTl%sqbv;qI=SK!GgAts^(#ztLU6)9|qOundzdYFm2-YMncvnIskr+ZnFh zy<*lt8cdAtNM+8UhAmaHgdzE>%VfBa@m8{!V>ug^ByophoQu9bLGpgbmhaFJ|EJy& z2JHyjEfX(oHyNR()3h;S>R+Hp+2T;7WlgEm%MWS$9Fo0`y=(*tTe8Dn+NCQ+CrKI$ zc~`2-CEER6wgL1XPhR>~WF9gv&CeyG-g@4Y7M;vI&tgH+UH#)2zLq_re@>}sdfb*P zkFMf4@fpRg4?Pq0Mf{^Oo7*q9kt;i@J8xf~$L8dZNy%MqEp)6AeOXg8U|LYwbYXp& zcAm<9^D9;a+ikj090;rPv!W$r)g60Je(%`rj?|3OzE9bnbjgQ7ks-;kb}IL%{pBmw zAko4FRTBkv2872TO3JGQ`>1Z4Lvz6u zR9%hJNM^39R9;)x#6^qYRcn%vl^B=w5tUZc)7?>FS-Qef0+>+YT9u?39o@2ga#a-+ z$vsur%Fr58u#Bqb#nJL58qV<3i&B4wHk-ox3PXmw9;9TMzhCQ0WcgaV5vm>0o}h9E z#h$1n$KQBoM{@^;p3ec_!OT}z-_=tKSmHJau!C@Wa96Lv*sf1!1`6-#of)*W^G=>% znAJ^SB#|Y0NI%V(oq$W?QifIZE#)N6A{sVHc9Ut-IA}gqPn|5O4VA7-WdAf-wlO4Q z+m)?lKk$+C&umO&Z2-w>h&DJOr&6x&R?F3F(`C#w{!}Ga1$!`sMpH{lwQX5-mnfx;m4Zo6 zWLK5!?#1Tx3z+9iN+q{&V!-1_jczenH?21tI>)rhvdR6k#$PUuq63`!C$IuICCW=X zHo*lbegKAJxPrk_`o>2r>e%^WnepU6{0kAo2;N(UC#R#{I9S<~s%gXTYp8D_CPd2e z*}Dtzyou!@PAY37pAGNTd3p^=Z9tp%jCwq>^m~4TX!1i57WSbK0h28PIxqC9eCa`(JpQGRY3kBlYN-Q3wFcS(nq16lnmI5Py_1_LRSV`EOX~ykQJ>&B@t1RU zT;jz~?3Fskb;b>Ns=9BSP|2Xih}rC0`GpMf=4uSdSjgWKoT#|)>!X*14hvZ=y&Gp% zS7nebrOJbCB`Ou>8eH)~39_!(830r!_5F5BU&na?@8ih9qTh6wt!vS`Pe{C7Y=7}t zP&=cDTDy{6wg>65rDa=>{~l6}%c+;D6jm7XEcHd-onY*{7R)_dV$+qZvgeB9md1Pj zLL?AVl@Hl8QM`Md4nmNe^(&>uHlcG+&z8jDj;G)&fl(f95v;b)MsK@QV_TMWD{U;% zxZq}iK*?ttj3c93)C<4yg}}_g62DYauzxG0Sbv(L{T52y-!_-0;CFANG7cM&-rMZDsnIle~qWh9c!)TAq?Cx2`I zr<6jN6uCsSwzND>#x9B+Aw8ZICJ-b)&W)RgdeJ(x(_l zI^4MJ%=Q&0DVXw*E9QxnvW98OV0xKksrI~0j=1;*uL4UsvR;l0=-;TR0K%-g*LZu` z`o^hhx8?z2*=zaj1wr=5e{R&hz1=!I=^Fa%)*Q!I&!e67+@}WVnz&;uf9JQjY$Ghu z77X)LCw(4UAP$1k_$>647cMQ;klEdmb~m$=3nJNT_C3-}ph;_S72!42c zFwVOh+I{SF%R{-fn5fn6FnGafZ7*xz^zW(B#7O;H3zv?%$Gn&QN16{K!Grd zi3MuPU+BoP2lIkrZKfNnqiN%U0Pz@HU85^`G3IxZnLE#EMt1lpl_1PdX_Mgc-2*+P za@(k`zD3FmgtZm!WTDnZ*%IHW3NY${myQtEdvne%D^k(BtM(yV2h3lvUFSNQPxc z*KD$ygK}Dz_V2ZzrRG(9ivVPP&O83CFeE+LiwN-;?~HCJw|{nJ*chp=C^MWms9QB{ zSkw<-r4o+b4l^zFr<5q`9*pytNq*it=)yt}8n z(0LBLVJ7~-NQtH!KQhPRx-#F$opqe^hS5Lsupgapb69xhtobDBS&7IZf0!ZlGg7Tb zI7#}}`pJ12pSx=(n3{Q_qZh}d>!Cinw-q&}?2#-`&qBg@0Pu|ceg71F4*No^B z^3Q^oGxlOv=IpRtHqOyXHl6~tO9@G|1vf=FaqKTcmxQjZLZKvUb^m1k>8N7(gTxfy zConoJJD{c*KvpOkYe}bFpBoyc>N&Eit$HCM%S?A4Pu&i2mu1a1v#9tMM;%HX-uV?{ zTlRE04+-l&jvB(}D=y?yu~T&X(RpJ{Ucu13rgH=dHeYu=y;2Ex19u+I-`#M%x=E3V zkx<`KaUrK=+xz^@uvpA|ej0xCeVsWpmnSqw)L|#G!o`=rO z34Qv7y_Th)c}MiD&EQ_>dNlGxU|mFcGse2SpQq_;SGEs8pTrbcZ(dzShFIbAO0L-^ zbx)$o-I}?Nvb=m1=u(%(DcaxlabF$lm}_RT9tEeTM8$ELGF95eSFhmu=UKu%$E#@I zaI`EDXO+zWyNRSl!3?(*(k!bga8Y(7`Ck=oIZtz^-Fg_7Le`G+8CH!Fr-OEV(Y7&R z{iORQ9n*yArrhJ~xipcSL2ai-Yj7pm3-sf7q{`FTXS0#y=UktdXI2agBx^qpUROZs z>skpK9Q>*{5ey_^9`x3k=S;m>iWEwrI9763VrJ4?~-+~Ags0t(+Eq&GODV`&j3wS296sIxx0VIts1Ab%x}BV z|1t#sl*qOliz)lZuq!z*%ikl#-7sVoiz1}$yWW;L)|N@iIzb4clz68;ANbf|mCk~V zZ1+IS|2dnQ!=g^6LJ(XG-X?Oy8sVIygH$2D?mnt&2etI9kvvs6rt0&5Z=UWier0-S!u)=24h#>ShKiGuSN1sQ)UljW$k*xB zVd#7H#%Ly0SQb!U5m4;A>?T*Uv{~}p%CDT0o3~(V#yLBJsFGLH#pKfO{fzNRgD;d6 zE5>cjiW2lAqoP`2odGI&&(p5Mrz>&Byp|?5K=~Oc=3xq0vjsK2PmGXoAb!3z@L=BH z{vgKOQLb!i!=DY%Friu~ffT4{0o80Nx?c)uHLsr$Kvqd8=1|JTIQU1ae)WJk_F#ZM zMs3|iJJ$*;8E@IM9`ARY$bXTT*G}3JY!O#+H=Sh^|MT=OuOzUX`7eC%pe(N1Cb}+7 z5@Sm@ELDCY4f_nYe2{xB`{cmTh{@iC@FJbX``X}KvRn?UbblZ7uOwfu_n4*KU}C)w zJO?fJD7t=u9r#~`LY&=vf%>Wo%H((Z2_Cq?m0MKbJ;uu-wh)my1CN`rX-R_sEy9nE z$62he*jLu=3_nJ`fbZ+T95R852|}nvQ0`lfrbAFn9$;|WAAM?*jaCCg@DtGXCmkeo z2km|!PdFxTAr()oc6ha;85SfIodpCtQ+lvM5Pv)}s9f}xpHghHjn>vQM^YN^AaC#{L%o~HTUxWprXw5&$C zI`<^Bs8b`6`n~($_tX!6ht-V&*{CtgL3bwpnypgc#X@ zq`6?k(eZIqiNaTSd#PdPs26ygtP74D8TqFvZxw0;fuU&=!>8KL8m~knhQnkwk?=en z)o$hKKUc*8@%YiliL6EHrGGb2$iJ7z>Uobc8BkVS01tQNBlQd=<_kmSUgcrH5Jf=} zt5m2+O_06FqBLq}Li#QyOqTj>*1;os|1E#l6Ep$+%XPcqGWF?Vxe01D1#*X4rfj9* zj?NV;(RfbFpTL$==8t$}dNZ+4p(~CTde)Sb&>#03TV0qqi@_zJ&d>_i7Lr<^<5$Wv z!UQE60J8C1U-wabA5QFtQ0plIhq6bbp0ef=Z=U9E%LwM{bA!^2CMDtx50bMdt1%jC zfbmCAje||#qLk`L+%0+eLL+lE;07Bu9V)ECdMBWPFE@QLzy=_r5P?aH)M#UjimDy~&#A6RTws z#$^8z*dWW@ zp88WkNcKpqKCc$Kj%Mi+ufvtNNO412{{HH%h|9^Mn82{6)WBhO3y8n>f{u@z^k&*s?B&tO#fhi-M zp{lx5irTx1Cu%E1nTULE#2O?$Nm<3^A%#^V2V$aRqf?huHNuqxV1YLxb>2GUPD; zO!bAc?u1K%I8iR$LBS_-`47oM69Rl2?FQ!81{m$dNGv%JO9pZI*NEv4wSY2OGj4g{AXN~!^n1Fitg4#YrE9dJmG4{aC(R|pwUAPiEA4PO(;Z_8IJ z3|aU`zX#Bd2HgraFWy{l0H*%QAHFdc`tB`F7&6}iI2uS$7&?%|ijNUDasvn)<no*3{{4hP6;?rk-GgY(jNk9p z9v(uBfc#9$sUI2&?0WF?NSqq{AL4x>VEOmU6S6G1v|qmA6Pw#l7=`vqoG~u%1E|#Z zxs&67q5p8q7_iH;lVAz7C#W4U?0UXEJ3M3Bh^w}(a+v?ypEE@7_XLN@JkUx|v_VYh zXcRw$@nHWYw){|qbYjx%{6r1T6~Gmyaq?fSYbinqCoXrV#a)&hn7nkyk>D`HesJNPU*%s7c29MVO;qznIh27e!! zQ)w`Ov7@*QVUk9EGSK3Dgb!lBzaPfn}j675{*I~?xL3ioGlVmedGE;Hw+&!S5><~ku0xgOHd=DHb& z)99p+uIJ%H1!6xy-qgk&V#<(M4-XJRajd zpy5`8v7_p>BM_V5YBt;KyPU#Pb>$aIN6wC;gK8+X)5(#g7ua=Ju?&~=re;hD?~Zyc z9hdG>2l}VzR!|#F?yCE=`9y={MQ2WqIZ8zZtk&6wrpng+A&%_(=aS6udb0TT&20*a zkM=U3=H zm26eaHp`iOo`TM*q04TC=w=?YV~lIU5+)dqU?bLv!sVpBLapp&r>f&XyZ7d+7~7V5 zJ?3pSaJ*;I3`}->OU18kdaQd@9VUDg5zjNkm@+nH=M+3MPIm>@ZqkL$*#sR=5;lyM z@2j^2_8L#P9hzAB-nz16y#D*=2;1PlD)9p0-!VEL*yrmGMyY1@98ca7mlT_opTo4J zb_d~7#XUEi+tZgEi;5=df-z9(bJ_2eo>#e@6W0(+(QUR<>2i`=%w`v4$w+Oc!4I-F zc3Z0rJP<27C87Es6C*PQ+t?eQ)k5FW*WhYGTT<;vL)Q21Qr9>$PmuPr8Yrt{EamI@{hsJKQk|89$vj z1Hkx4IV{zZZIkWs_ve#|g%eTNwv2g)#qP3ei&GZhHeX}naeGZ&4ZC*_=43N=fZnh* zy$%zDZ_|g;frqJ^kso@@I0^D%hipsbi!FG1ROnh9o-d#^dsl4Qr?^C>T_>2a-f@n_ z-k4_vx98}xCg6N514*_@+};CcE=zWyy8$ZL9jzUfdS%vI)2yPVyfjcaeOsoTQ@riG z5##NTu_+EK!cBWL&YIy}s_#}#$X?6G;j`S-Zs*fpqOS#(O$qff)^gdA$Qi9C5*JkC z?@qR^Sc#s^)9c>;YFILJ$2;nuW#$QTH#@G^ASw%~c#lTN#@Kw!b8M%^8f=Z|jz={-CSae^CPj@F zI;?aVZGXjFrFE!YFy880)jajZ#;r7-@j}sx=9L06$gD0ZpJ zn8(Djp)bb8x*pq6gTud@0$OG|2c9?*N8NQU^!FsK^>(zF6mB@TTT~#~4H2$-Yxi~f0f^i%s_1s#~mXJnCGSo-h*#ccKRj(yz{ zubS@Lf4=04LH_rh`_&KO$H9&qWsY%{wFmyb-^8F-|QN3A_ zae!TFR*ctvum(7-z zhU11P;;+j_{|KV8iPRBK_K4svzMg8@gJ#J_1(~mxm^fcL9ujuYlS# zaUCptH_vo-VPs{*&&h?Oupt+B?)B|ptv zV2Tm?5Gu<~1(klzXoxlHTNEX`hs*Qn3ToJc+JQ981oQP}^Gh`S4c2WIl+HPAb_X%e z@h_*tkZev@-xPe2waDES6pcrNY^%{ABomjxuf~M^ThlrN0$!RcH_AMge9(@tP<`}|D+8)rE=V8vjnivo>)o+@W$Mv8qox#d**hiXykSGg)#!vy@(RhOv3_*U zmH%pbQV?cJTnA1>HpK?KH>Fy|>+FlOE-Rd<)?-RH#2>uzCkHIIHEm(3=?baGm@Bi2 z${9p)a4Wz@Zg><6bMA^7#ZHT*Zvv~n$zGo5S~ooe6Bg><`Eq}93;SCuA@)_f*i*J~ zmHAQS5?IJ_`u2^@zsj(q`{kvOoyPSZNa7KvZF?I9{2coz`+zVSv!^YB0{=07v)|(51@b@KrKgu5MT}lgA_}r zE!~js>$LYO@wa$oeHxy)eTUsq%m(dER1WfcyC3Vd1WsPw-VffmH2KR4x zH}*ZO9kiK8W^x3Z@WUFjVVGQKSm-fL4#VsW;~?!jT?E{Go{t|yjpUqvnW~}V`D^?o z!0i9pf1Q5fWYg>&S74CzudR{EdGP#=uoFIBx_q4L5d_EkjRekR4Jhs|X~IxT&hUq& zK-fO|!QiVxDzf~E#H<9h?*&qH5uPF)`3w!EG&P{)8s#HXZQlc(^z#)fny*d5R~{Ft zAlrW5|`=qDBU_K?cq9mS!}^< z|7nD6R{0xF;dveC^3n*1YTmCBOC*Wp09-j}2A;L{p$@gt0tCo!5yd+UN$oa6K!rMw z$kFHzn+m2CM0L);pm=)6Dr1q_tndISAIh-=Ivp>ab>){QQ6##tq+hoF%ea~Dc=G?k zbSq#uz-jR#1Z6d9U01X4ZOL=puAfmvE#5QW$&UwU24eKhT{iGo$@{%0MCVa%rt#Sk zIi-70LD#aX77E(2l7j2>L4H`I)d&F$F((BjsNjAjo5TnzZMT*Q;!l)x4*{4ilMLx? z1p|lMh}`f_V+H6{F4#8@T)_WM>H)ZwvID`68+nb)c}9c6)Mqkoof*a3r&UFOOs}2K z03UkB4uNroYYk{uOLPM>8?{b_zm}M=b)v2e;snr2o*C9`Z+^c&cVlRYlU3W3TBpay z-ZV!QGZIxYL5RNRAr)xpgCK6&HAoW)&~s#~{wf+ro=zY=W>7SFo0=-uKAijG*PjZm zD2ve{IMA9}^^k#S`PW&8l3)f zf+3i`cUu4_610>uf!_$u z>PxUzi*LsKAwR{?;RMD-N`l(pH;*F*d~63?=@^S%`$kx@&e67q-_Y#8&Yz2)uezmv zl0J_i!@jB-IG#i8?ChIp0Vxo>WFSbOZ!(<2kZT(wA1-a*3mX~&6`&*^2xC8fZeUJ`? zMfI9onKd}A8grwKS$}8hn+E^k@zToLQ zo`t!^0ny$}i+r@SQSrRZ0_vPw)tbAAm?erVxbZjPB6scZ1=B0N`3E}kCj!+(RIJOb z@wf2t2(Xn6fNAhs7e{T2{2XSQK%XrdDrVUNto^fs5`{MIL#nI}&{<#C7gqM5MAOvl zi!3u=Xx14DIyyPZC6uyC?T3*lIE7F=LnP};ibN@sBv87A%eYdyXe*{Tg~f9frDg-! z3N{cS%$hGX^1|~mNBV37?r1vC4-T0>*&!e_Juz>Q13s%o_n=G~lybH3J*Zt&Gzy)W zz}&kS)=XM-n`;3BOIqS^cGr5Fg?HOw3(2!eO6W3x5K<1)B;P&ha`}95JN$+bt3>rF z$k9iA;6nxH&*Kq+%7N!B@4(n7Jy>}Q9>|cU%+Qy5iwW33u>0faoc|2;LaD8t!P3{a zg1$kfEV-jZ9k?O>1bV~Tw>qAOC0B(%PC)-uh961`I#78+IW85O zYWL-C*F@nH%h!r<11k?~@7D4rV?ws#F1?fyqD88En4y@yeK&_jqn0C1t_F6#4&Gaj zQsaT4Xr~a6>9U;)zf5&a<+luBK3v2aPn8n6uK>te@kd82+vjmw###oI+M5~ipxNtQZ?Cw=*6iw8C_4KTjdf;@+{ z-?Ef0gH8IjP7uYcZEck-Y2$6@ks@|JO0lk5Ik*_s(oJxSD0=I1Ia{H#T7G&$VSbO`RI#Rp z>&6mCa8bLdJnrZrWt+&s89vRhj)3-WdS{ifQ0Kaflc8Hk-6ifYPDQi$Kd9o)(9;BC@@Psab!kt z@z5cT@wPYvarrT3INrS#pV$Vv63$W}xiDK8V4S`CzdGK7i}-gw>)2y^O!cDNM0Ad_ z_CNu{3GamR4}5DV^36!BJ@`F+KyArRXwM-_nJFK`9)Ym^3mr%`?%Y|;05Cl)qtWLj z)1$Co#O;f16|0{!AApX{0o3}ot(+QZHKy!>y;{ki{D?GE0Ct0V0^`qg+>bm!OJZ~7 zU%k7F1d>|*EW49}-bVxK`Fnk<$^l|_^jTW9jUtxuFOL!}&X9*W27u7)kHMIj;7cC+ zJoVDaC9b`Z(KGB2$S5uB37{b*TrC5fJlF%k=mGEupoKJ4&I!_Kc#}Qe+%>F5u9B&l zTR?O*yLV7=_;7o-*drWyA^XfLGOpt+OQJ6jK3*b42>^y7`^YNK=B0em+;z?@Zyl4% z;56dVDY$hXFluGZr=dS>CVE8 zUdUvN_Xm1US?AG#x#}*`{=DT1-}f0~(kx_TTZdzhP#Dqxr;aaiXpCvd!u}*YyTQ+3EAxTrwK;@3c0Q2RKAbZ#req!33r$&LZiYi&}+P#M3*aw%Uu-CYRk35U<(CZkt_Ev{(p}UP+~ee_rwSqkJszoLE%xNtncQ_Xe7)p7BDva<=%x z=eO%Uxp`p1@NM5v|B~y?|L5}je<_e*_&+tsu>I%t-(vj#G0n35?+h~kW#9jk@|>B0 z;eX^PUukPOZVaRRmgl>#|7I73UwkBnvn5DnGG%eCMwqM*Uj0+>!y!b*iH$Y9&-89M zI`I8KV8P`JtX}T*BY{&qIZ|)50a@G9YR7VOYnOLy_4wWfUPR@*uA}J_S5Aps``CT-o`doe5-suLvM5rLK98`uv!s(p0BpXl+oj-Qcc-PHr_1W zpu4p)r&{jbkB`xYr_AfZ^;)PtseX0i=fbU*f45GJ`Zw`Iv$jbaXXnU@uo#|SJ7|4J+uqJaA6T4`@huQHV)-fiX=Nm6`b)a;*GWFVP2 z1U=nCfABQa_*7;HR+sdj=94Z4D(HjWKGsZ8G~BPvwW%)5s$wloK^=9%;C+Q2by4sk zHXex7w&ab`vxv0<|3?0@{&pjEYp4;KmY3LXrD>1yyh!ZrebSP zxbu~7yx(3%3aVN-M6^>>lh~R89<9?>K0fejiZP}7w{C0-;C}JefyKO%$xGdoF*qN1 zR5g%{dl=tVq43;s%}u)^xYAq1VGMG+D~(%3*oeOt&!yTVEFC;4SxekZr_Z~#{-bPx zB~!D=SP#bc*`6U*8>2_mBI|~VflBwx0o#A1>+YZwGzyX8d7P0Mu*NZwqolO4d4<3+ zE3b<{MCJo0IyRp2ZMUW`ql}-9rMu5a#0s4Pb6C$1HqV?mq51hEKpk9ud|jtJL@!9@ zd~GrC#?)3Ca`S-}oXwO=jj1hi_-f+AqBpthr+uqpWD4@SWTBDN&%`Ge1pak*k!juN zj`(bnEw$v{O`t$)y4jRO&mp`hxS5M9#L<-)TBb

5ZZD9`4!(63GVg@3vMKUCFC6 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 SqSQ1l;B`4%s;aL3Zd?Eql*Ehx literal 0 HcmV?d00001 diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment.py new file mode 100644 index 0000000..eae3f5d --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment.py @@ -0,0 +1,58 @@ +import logging + +import numpy as np + +from load_csv import LoadCSV +from generate_submission import GenerateSubmission + + +class Experiment: + def __init__(self, path, anonymization): + self.logger = logging.getLogger("experiment") + + self.input_path = path["input_path"] + self.output_path = path["output_path"] + self.specs_path = path["specs_path"] + + self.epsilon = anonymization["epsilon"] + self.delta = anonymization["delta"] + self.sensitivity = anonymization["sensitivity"] + + self.load_dataset() + + self.num_records = self.records.shape[0] + self.num_attributes = self.num_categories.size + self.header_mapping = self.generate_header_mapping() + + def load_dataset(self): + self.logger.info("loading original dataset") + + csv = LoadCSV() + csv.load_original_records(self.input_path) + csv.load_dataset_parameters(self.input_path, self.specs_path) + csv.calculate_num_categories() + csv.transform_records_to_num() + + self.records = csv.records + self.num_categories = csv.num_categories + self.dataset_header = csv.dataset_header + self.attri_name_index_mapping = csv.attri_name_index_mapping + self.code_mapping = csv.code_mapping + + self.original_num_categories = np.copy(self.num_categories) + self.original_records = np.copy(self.records) + + self.logger.info("loaded original dataset") + + def generate_submission_dataset(self, records): + submission = GenerateSubmission(self.attri_name_index_mapping) + submission.load_records(records) + submission.generate_csv(self.dataset_header, self.code_mapping, self.output_path) + + def generate_header_mapping(self): + header_mapping = {} + + for index, name in enumerate(self.dataset_header): + header_mapping[name] = index + + return header_mapping diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5.py new file mode 100644 index 0000000..23d571d --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5.py @@ -0,0 +1,423 @@ +import numpy as np + +from experiment.experiment_dpsyn_mcf import ExperimentDPSynMCF +from lib_view.view import View + + +class Experiment_C5(ExperimentDPSynMCF): + def __init__(self, path, anonymization, synthesizer): + super(Experiment_C5, self).__init__(path, anonymization, synthesizer) + + self.age_singleton = None + self.geo_attributes_mapping = None + + self.epsilon_enumdist_singleton = 0.0 + self.epsilon_incwage_singleton = 0.0 + self.epsilon_minc_marginal = 0.0 + + def synthesize_records(self): + self.logger.info("synthesizing records") + + ##################### iteratively synthesize data #################### + records = np.zeros([self.num_synthesize_records, self.num_attributes], dtype=np.uint32) + + # iteratively update marginals in group + attribute_index_dict = {} + error_tracker = np.zeros([1, self.update_iterations * 2]) + + for views_group_name in ["A", "AGE1", "AGE2"]: + attri_index = self.key_attri_index(self.views_iterate_key[views_group_name]) + attribute_index_dict[views_group_name] = attri_index + synthesizer = self.update_records(views_group_name, 0.0) + + # save age records, or it will be rewrite by AGE2 + if views_group_name == "AGE1": + # self.update_records_fully(synthesizer, ("BPL", "AGE"), "AGE1") + self.age_records = np.copy(synthesizer.records[:, self.attri_name_index_mapping["AGE"]]) + + elif views_group_name == "AGE2": + self.update_records_fully(synthesizer, ("INCWAGE", "SEX", "CITY"), "AGE2") + # self.update_records_fully(synthesizer, ("CITYPOP", "CITY"), "AGE2") + + records[:, attri_index] = synthesizer.records[:, attri_index] + error_tracker = np.concatenate((error_tracker, synthesizer.error_tracker)) + + attri_index = self.key_attri_index(self.views_iterate_key["SL"]) + attribute_index_dict["SL"] = attri_index + num_records = (self.views_group["SL"][("SSENROLL",)].sum / self.views_group["AGE1"][("CITIZEN",)].sum) * self.num_synthesize_records + synthesizer = self.update_records("SL", 0.0, num_synthesize_records=int(num_records)) + self.postprocess.decode_anchor_attributes(synthesizer.records, ["SL"]) + + sl_records = synthesizer.records[:, attri_index] + + self.join_records(records, sl_records, attribute_index_dict) + + def join_records(self, records, sl_records, attribute_index_dict): + self.logger.info("joining records") + + self.synthesized_records = np.zeros([self.num_synthesize_records, self.num_attributes], dtype=np.uint32) + + ####################################### join group A ################################### + attri_index = attribute_index_dict["A"] + self.synthesized_records[:, attri_index] = records[:, attri_index] + + np.random.shuffle(self.synthesized_records) + + ####################################### join group AGE1 ################################### + attri_index = attribute_index_dict["AGE1"] + age_index = self.attri_name_index_mapping["AGE"] + + self.synthesized_records[:, attri_index] = records[:, attri_index] + self.synthesized_records[:, age_index] = self.age_records + + ####################################### join group AGE2 ################################### + attri_index = attribute_index_dict["AGE2"] + self.postprocess.decode_ordinal_attributes(records, self.age_singleton.normalize_count, self.age_bin_2, age_index) + + record = np.copy(self.synthesized_records[:, age_index]) + age_bin_1_remain_indices = np.zeros(0, dtype=np.uint32) + age_bin_2_remain_indices = np.zeros(0, dtype=np.uint32) + + for value_1 in range(1, self.age_bin_1.size + 1): + age_bin_1_indices = np.where(record == value_1)[0] + age_bin_2_indices = np.zeros(0, dtype=np.uint32) + + if value_1 < self.age_bin_1.size: + value_2_list = np.arange(self.age_bin_1[value_1 - 1], self.age_bin_1[value_1]) + else: + value_2_list = np.arange(self.age_bin_1[value_1 - 1], self.age_singleton.num_key) + + for value_2 in value_2_list: + indices = np.where(records[:, age_index] == value_2)[0] + age_bin_2_indices = np.union1d(age_bin_2_indices, indices) + + # join procedure + if age_bin_1_indices.size >= age_bin_2_indices.size: + self.synthesized_records[np.ix_(age_bin_1_indices[:age_bin_2_indices.size], attri_index)] = records[np.ix_(age_bin_2_indices, attri_index)] + age_bin_1_remain_indices = np.union1d(age_bin_1_remain_indices, age_bin_1_indices[age_bin_2_indices.size:]) + else: + self.synthesized_records[np.ix_(age_bin_1_indices, attri_index)] = records[np.ix_(age_bin_2_indices[:age_bin_1_indices.size], attri_index)] + age_bin_2_remain_indices = np.union1d(age_bin_2_remain_indices, age_bin_2_indices[age_bin_1_indices.size:]) + + self.synthesized_records[np.ix_(age_bin_1_remain_indices, attri_index)] = records[np.ix_(age_bin_2_remain_indices[: age_bin_1_remain_indices.size], attri_index)] + self.logger.warning("age indices inconsist number is %s" % (age_bin_2_remain_indices.size - age_bin_1_remain_indices.size,)) + + age_bin_1_indices_0 = np.where(record == 0)[0] + self.synthesized_records[np.ix_(age_bin_1_indices_0, attri_index)] = records[np.ix_(age_bin_2_remain_indices[age_bin_1_remain_indices.size:], attri_index)] + + np.random.shuffle(self.synthesized_records) + + ####################################### join group SL ################################### + attri_index = attribute_index_dict["SL"] + default_value, default_attri_index = self.calculate_sample_line_default() + slrec_index = self.attri_name_index_mapping["SLREC"] + + fbpl_indices = np.where(self.synthesized_records[:, self.attri_name_index_mapping["FBPL"]] == 0)[0] + mbpl_indices = np.where(self.synthesized_records[:, self.attri_name_index_mapping["MBPL"]] == 0)[0] + sample_line_indices_candidate = np.setdiff1d(np.arange(self.num_synthesize_records), np.union1d(fbpl_indices, mbpl_indices)) + + if sample_line_indices_candidate.size != 0: + sample_line_indices = np.random.choice(sample_line_indices_candidate, sl_records.shape[0], replace=False) + non_sample_line_indices = np.setdiff1d(np.arange(self.num_synthesize_records), sample_line_indices) + + self.synthesized_records[np.ix_(sample_line_indices, attri_index)] = sl_records + self.synthesized_records[sample_line_indices, slrec_index] = 1 + + self.synthesized_records[np.ix_(non_sample_line_indices, default_attri_index)] = np.tile(default_value, (non_sample_line_indices.size, 1)) + self.synthesized_records[non_sample_line_indices, slrec_index] = 0 + else: + self.synthesized_records[:, default_attri_index] = np.tile(default_value, (self.num_synthesize_records, 1)) + self.synthesized_records[:, slrec_index] = 0 + + np.random.shuffle(self.synthesized_records) + + def calculate_sample_line_default(self): + self.sample_line_default = { + "SSENROLL": 0, + "UOCC": 999, + "UOCC95": 999, + "UIND": 999, + "UCLASSWK": 0, + "MTONGUED": 0, + "VET1940": 0, + "VETSTATD": 0, + "VETPER": 0, + "VETCHILD": 0, + "VETWWI": 0, + "CHBORN": 0, + "AGEMARR": 0, + "NATIVITY": 0, + "MARRNO": 0 + } + default_value = np.zeros(len(self.sample_line_default), dtype=np.uint32) + default_attri_index = np.zeros(len(self.sample_line_default), dtype=np.uint32) + i = 0 + + for attri_name in self.sample_line_default: + if attri_name in self.code_mapping: + default_value[i] = np.where(self.code_mapping[attri_name] == self.sample_line_default[attri_name])[0] + default_attri_index[i] = self.attri_name_index_mapping[attri_name] + else: + default_value[i] = self.sample_line_default[attri_name] + default_attri_index[i] = self.attri_name_index_mapping[attri_name] + + i += 1 + + return default_value, default_attri_index + + def post_processing(self): + # decode incwage and fill sea, supdist + self.postprocess.decode_incwage_C5(self.synthesized_records, self.incwage_singleton, self.incwage_bin) + self.postprocess.fill_onebyone_mapping_attributes(self.synthesized_records) + self.postprocess.fill_supdist_attribute(self.synthesized_records, self.gauss_sigma) + + # decode attributes + self.postprocess.decode_anchor_attributes(self.synthesized_records, ["A", "AGE1", "AGE2"]) + self.postprocess.decode_ordinal_attributes(self.synthesized_records) + self.postprocess.decode_geo_attributes(self.synthesized_records, self.geo_attributes_mapping) + + # fill attributes from mapping + self.postprocess.load_mapping_data() + # pair mapping and general detail mapping cannot change order (EDUCD -> HIGRADED) + self.postprocess.fill_pair_mapping_attributes(self.synthesized_records) + self.postprocess.fill_general_from_detail(self.synthesized_records) + + # fill attributes + self.postprocess.fill_bpl_attributes(self.synthesized_records) + self.postprocess.fill_geo_attributes(self.synthesized_records) + self.postprocess.fill_citizen_attribute(self.synthesized_records) + + self.logger.info("synthesized records") + + def recode_sample_line_attributes(self): + slrec_index = self.attri_name_index_mapping["SLREC"] + indices_sample = np.where(self.records[:, slrec_index] == 1)[0] + + # only remain the records for slrec=1, after this step, self.records should not be used + self.records = self.records[indices_sample] + self.recode.records = self.records + threshold = max(3.0 * self.gauss_sigma, 800) + + for attributes_group_key in self.attributes_group.recode_group_key["SL"]: + self.recode.attributes_group_anonymize(attributes_group_key, self.sensitivity, self.gauss_sigma) + + for attributes_group_key in self.attributes_group.recode_group_key["SL"]: + self.recode.attributes_recode(attributes_group_key, threshold) + + for attribute in self.attributes_group.recode_single_key["SL"]: + self.recode.attributes_group_anonymize(attribute, self.sensitivity, self.gauss_sigma) + + for attribute in self.attributes_group.recode_single_key["SL"]: + self.recode.attributes_recode(attribute, threshold) + + def generate_work_time_marginal(self): + for attribute_name in ["WKSWORK1", "HRSWORK1"]: + attribute_index = self.attri_name_index_mapping[attribute_name] + view = self.anonymize_views(self.construct_views([attribute_index])) + + view.non_negativity() + view.calculate_normalize_count() + + self.ordinal_views_dict[attribute_name] = view + + if attribute_name == "WKSWORK1": + self.ordinal_bin_dict[attribute_name] = np.array([0, 1, 14, 27, 40, 48, 50]) + elif attribute_name == "HRSWORK1": + self.ordinal_bin_dict[attribute_name] = np.array([0, 1, 15, 30, 35, 40, 41, 49, 60]) + + def generate_enumdist_marginal(self): + attribute_index = self.attri_name_index_mapping["ENUMDIST"] + view = self.anonymize_views(self.construct_views([attribute_index]), epsilon=self.epsilon_enumdist_singleton) + + if self.epsilon <= 0.2: + valid_value_index = np.arange(10, view.num_key, 10) + invalid_value_index = np.setdiff1d(np.arange(view.num_key), valid_value_index) + view.count[invalid_value_index] = 0.0 + + view.non_negativity() + view.calculate_normalize_count() + + self.ordinal_views_dict["ENUMDIST"] = view + self.ordinal_bin_dict["ENUMDIST"] = self.preprocess.bucketize_enumdist() + + def generate_rent_marginal(self): + attribute_index = self.attri_name_index_mapping["RENT"] + view = self.anonymize_views(self.construct_views([attribute_index])) + + if self.epsilon <= 0.2: + view.count[1001: 9998] = 0 + + view.non_negativity() + view.calculate_normalize_count() + + self.ordinal_views_dict["RENT"] = view + self.ordinal_bin_dict["RENT"] = self.preprocess.bucketize_rent() + + def generate_valueh_marginal(self): + attribute_index = self.attri_name_index_mapping["VALUEH"] + view = self.anonymize_views(self.construct_views([attribute_index])) + + if self.epsilon <= 0.2: + valid_value_index = np.arange(0, 50001, 50) + invalid_value_index = np.setdiff1d(np.arange(9999998), valid_value_index) + view.count[invalid_value_index] = 0.0 + else: + view.count[50001: 9999998] = 0.0 + + view.non_negativity() + view.calculate_normalize_count() + + self.ordinal_views_dict["VALUEH"] = view + self.ordinal_bin_dict["VALUEH"] = self.preprocess.bucketize_valueh() + + def generate_famsize_marginal(self): + attribute_index = self.attri_name_index_mapping["FAMSIZE"] + view = self.anonymize_views(self.construct_views([attribute_index])) + + view.count[31:] = 0.0 + + view.non_negativity() + view.calculate_normalize_count() + + self.ordinal_views_dict["FAMSIZE"] = view + self.ordinal_bin_dict["FAMSIZE"] = self.preprocess.bucketize_famsize() + + def generate_incwage_marginal(self): + self.logger.info("generating incwage marginal") + + ########################### calculate singleton ########################## + attribute_index = self.attri_name_index_mapping["INCWAGE"] + view = self.anonymize_views(self.construct_views([attribute_index]), epsilon=self.epsilon_incwage_singleton) + + view.count[5001: 999998] = 0.0 + + self.incwage_singleton = view + + ########################### buketize INCWAGE #################################### + if self.epsilon >= 1.0: + self.incwage_bin = self.preprocess.bucketize_incwage_4() + elif self.epsilon > 0.2 and self.epsilon < 1.0: + self.incwage_bin = self.preprocess.bucketize_incwage_5() + elif self.epsilon <= 0.2: + self.incwage_bin, self.original_incwage_record = self.preprocess.bucketize_incwage_6() + + ################ calculate 3-way marginal for INCWAGE, SEX, CITY ############### + basis = [] + + for attribute_name in ["INCWAGE", "SEX", "CITY"]: + basis.append(self.attri_name_index_mapping[attribute_name]) + + self.minc_marginal = self.anonymize_views(self.construct_views(basis), epsilon=self.epsilon_minc_marginal) + self.minc_marginal.calculate_tuple_key() + + # after bucketizing, value 0 do not exist since it corresponds to negative value + cell_indices = np.where(self.minc_marginal.tuple_key[:, 2] == 0)[0] + self.minc_marginal.count[cell_indices] = 0.0 + + self.minc_marginal.non_negativity() + self.minc_marginal.calculate_normalize_count() + + self.views_group["AGE2"][("INCWAGE", "SEX", "CITY")] = self.minc_marginal + self.views_iterate_key["AGE2"].append(("INCWAGE", "SEX", "CITY")) + + ########################## consist between marginals ########################### + self.consist_incwage() + + ########################### get finer-grained INCWAGE ############################## + if self.epsilon <= 0.2: + self.finer_minc_marginal() + + def consist_incwage(self): + self.logger.info("consisting incwage related marginals") + + def consist(): + minc_count = np.zeros(self.incwage_bin.size + 1) + + for index, value in enumerate(range(minc_count.size)): + cell_indices = np.where(self.minc_marginal.tuple_key[:, 2] == value)[0] + minc_count[index] = np.sum(self.minc_marginal.count[cell_indices]) + + for index, value in enumerate(range(1, minc_count.size)): + if value < minc_count.size - 2: + indices = np.arange(self.incwage_bin[value - 1], self.incwage_bin[value]) + elif value == minc_count.size - 2: + indices = np.arange(self.incwage_bin[value - 1], 5001) + else: + indices = np.array([999998]) + + singleton_count = np.sum(self.incwage_singleton.count[indices]) + self.incwage_singleton.count[indices] += (minc_count[index + 1] - singleton_count) / indices.size + + for iteration in range(10): + self.logger.info("iteration %s" % (iteration,)) + + self.incwage_singleton.non_negativity() + + consist() + + if not (self.incwage_singleton.count < 0.0).any(): + break + + self.incwage_singleton.calculate_normalize_count() + + def finer_minc_marginal(self): + ########################## get finer bucketized INCWAGE ########################### + incwage_index = self.attri_name_index_mapping["INCWAGE"] + self.records[:, incwage_index] = self.original_incwage_record + incwage_finer_bin = self.preprocess.bucketize_incwage_7() + + finer_dist = [0.0] + + for index in range(incwage_finer_bin.size): + if index < incwage_finer_bin.size - 1: + finer_dist.append(np.sum(self.incwage_singleton.count[incwage_finer_bin[index]: incwage_finer_bin[index + 1]])) + else: + finer_dist.append(np.sum(self.incwage_singleton.count[incwage_finer_bin[index: ]])) + + finer_dist = np.array(finer_dist) + + ########################## update minc_marginal ########################### + basis = [] + + for attribute_name in ["INCWAGE", "SEX", "CITY"]: + basis.append(self.attri_name_index_mapping[attribute_name]) + + indicator = np.zeros(len(self.num_categories), dtype=np.uint8) + indicator[basis] = 1 + view = View(indicator, self.num_categories) + view.calculate_tuple_key() + + for incwage_value in range(1, 7): + minc_cell_indices = np.where(self.minc_marginal.tuple_key[:, -1] == incwage_value)[0] + + if incwage_value in [1]: + view_cell_indices = np.where(view.tuple_key[:, -1] == 1)[0] + view.count[view_cell_indices] = self.minc_marginal.count[minc_cell_indices] + elif incwage_value in [2, 3, 4, 5]: + finer_dist_index = [2 * (incwage_value - 1), 2 * (incwage_value - 1) + 1] + view_dist = [] + + if np.sum(finer_dist[finer_dist_index]) != 0: + ratio = finer_dist[finer_dist_index] / np.sum(finer_dist[finer_dist_index]) + else: + ratio = np.array([0.5, 0.5]) + + for index in minc_cell_indices: + view_dist.append(self.minc_marginal.count[index] * ratio[0]) + view_dist.append(self.minc_marginal.count[index] * ratio[1]) + + view_cell_indices = np.where(view.tuple_key[:, -1] == finer_dist_index[0])[0] + view_cell_indices = np.union1d(view_cell_indices, np.where(view.tuple_key[:, -1] == finer_dist_index[1])[0]) + view.count[view_cell_indices] = view_dist + + elif incwage_value in [6]: + view_cell_indices = np.where(view.tuple_key[:, -1] == 10)[0] + view.count[view_cell_indices] = self.minc_marginal.count[minc_cell_indices] + + self.minc_marginal = view + self.minc_marginal.calculate_normalize_count() + + self.views_group["AGE2"][("INCWAGE", "SEX", "CITY")] = self.minc_marginal + self.views_iterate_key["AGE2"].append(("INCWAGE", "SEX", "CITY")) + self.incwage_bin = incwage_finer_bin + diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5_1.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5_1.py new file mode 100644 index 0000000..7c9e70a --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5_1.py @@ -0,0 +1,184 @@ +from itertools import combinations + +import numpy as np + +from experiment.experiment_C5 import Experiment_C5 + + +class Experiment_C5_1(Experiment_C5): + def __init__(self, path, anonymization, synthesizer): + super(Experiment_C5_1, self).__init__(path, anonymization, synthesizer) + + # this function specify the attributes to be compressed, do not consume privacy budget + # the consumed privacy budget for recoded attributes are calculated in function configuration_C5_1() + self.attributes_group.use_recode_scheme_3() + + # construct noisy views based on configuration C5_1 + # this function is the only part that touches dataset and consumes privacy budget + self.configuration_C5_1() + + # consist noisy views + # post-processing code + for view_group_name in ["A", "AGE1", "AGE2", "SL"]: + self.logger.info("consist for group %s" % (view_group_name,)) + + self.update_num_categories(view_group_name) + self.consist_views(self.views_group[view_group_name], self.views_consist_key[view_group_name]) + + # generate synthetic dataset + # post-processing code + self.synthesize_records() + + # fill in some attributes based on mapping and decode attributes + # post-precessing code + self.post_processing() + + self.generate_submission_dataset(self.synthesized_records) + + def configuration_C5_1(self): + self.epsilon_incwage_singleton = self.epsilon / 24.0 + self.epsilon_enumdist_singleton = self.epsilon / 24.0 + self.epsilon_minc_marginal = self.epsilon / 4.0 + + self.reserve_epsilon = self.epsilon_incwage_singleton + self.epsilon_enumdist_singleton + self.epsilon_minc_marginal + self.remain_epsilon = self.epsilon - self.reserve_epsilon + + ####### calculate algorithm parameters and preprocess data ######## + # calculate the noise variance for each marginal based on Renyi-DP + self.calculate_noise_parameter(187) + + # preprocess attributes + self.geo_attributes_mapping = self.preprocess.refine_geo_attributes() + + ##################################### recode attributes groups ########################### + # Totally 26 marginals + + threshold = max(4.5 * self.gauss_sigma, 800) + # city_threshold = 240 / self.epsilon + self.recode_attributes(threshold) + + ###################################### generate marginals ################################# + # generate income related marginals + # we have reserved privacy budget for INCWAGE related marginals, + # thus do not count as the number of marginals to calculate sigma in function calculate_noise_parameter() + self.generate_incwage_marginal() + + ########################################## Group A ############################################# + # Totally 4 marginals + + ##### generate other misc information 1 related marginals (1) ##### + self.generate_marginal(["SPL_GQ_GQF", "GQTYPED"]) + + ##### generate race related marginals (3) ##### + attributes_list = ["RACED", "SPANN", "HISPAND"] + for attributes_group_key in combinations(attributes_list, 2): + self.generate_marginal(attributes_group_key) + + ########################################## Group AGE1 ############################################# + # Totally 6 marginal + + # handle AGE attributes (1) + age_index = self.attri_name_index_mapping["AGE"] + + self.age_singleton = self.anonymize_views(self.construct_views([age_index])) + self.age_singleton.non_negativity() + self.age_singleton.calculate_normalize_count() + + age_record = np.copy(self.records[:, age_index]) + self.age_bin_1 = self.preprocess.bucketize_age_1() + + # generate individual information 1 related marginals (2) + self.generate_marginal(["BPL", "AGE"], views_group_name="AGE1") + self.generate_marginal(["CITIZEN"], views_group_name="AGE1", is_iterate=False) + + # generate individual information 2 related marginals (3) + attributes_list = ["FBPL", "MBPL", "AGE"] + + for attributes_group_key in combinations(attributes_list, 2): + self.generate_marginal(attributes_group_key, views_group_name="AGE1") + + ########################################## Group AGE2 ############################################# + # Totally 139 marginals + + ###### handle AGE attributes ##### + self.records[:, age_index] = age_record + self.age_bin_2 = self.preprocess.bucketize_age_2() + + ##### generate individual information 3 related marginals (6) ##### + self.generate_marginal(["AGE", "RESPONDT"], views_group_name="AGE2") + self.generate_marginal(["AGE", "SCHOOL"], views_group_name="AGE2") + self.generate_marginal(["AGE", "MARST"], views_group_name="AGE2") + self.generate_marginal(["AGE", "NCHLT5"], views_group_name="AGE2") + self.generate_marginal(["SCHOOL", "MARST", "NCHLT5"], views_group_name="AGE2") + self.generate_marginal(["SCHOOL", "MARST", "RESPONDT"], views_group_name="AGE2") + + ##### generate work related marginals (55 + 2 = 57) ##### + attributes_list = ["INCWAGE", "LF_INCNON", "EMPSTATD", "CLASSWKRD", "OCC1950", + "IND1950", "WKSWORK2", "HRSWORK2", "AGE", "EDUCD"] + + for attributes_group_key in combinations(attributes_list, 2): + self.generate_marginal(attributes_group_key, views_group_name="AGE2") + + self.generate_work_time_marginal() + + ##### generate geo-spatial related marginals (20) ##### + # we have reserved privacy budget for ENUMDIST related marginals, + # thus do not count as the number of marginals to calculate sigma in function calculate_noise_parameter() + self.generate_enumdist_marginal() + + attributes_list = ["METAREAD", "UR_FA_MT", "CITY", "COUNTY", "ENUMDIST"] + for attributes_group_key in combinations(attributes_list, 2): + self.generate_marginal(attributes_group_key, views_group_name="AGE2") + + self.generate_marginal(["CITYPOP", "CITY"], views_group_name="AGE2") + self.generate_marginal(["CITYPOP", "COUNTY"], views_group_name="AGE2") + + self.onebyone_mapping_from_marginal("COUNTY", "SEA") + self.generate_marginal(["COUNTY", "SUPDIST"], views_group_name="AGE2", is_iterate=False, is_consist=False) + self.generate_marginal(["CITYPOP", "URBPOP"], views_group_name="AGE2", is_iterate=False, is_consist=False) + + ##### generate migration related marginals (28) ##### + attributes_list = ["MIGRATE", "MIGPLAC5", "MIGSEA5", "MIGMET5", "MIGCITY5", "MIGCOUNTY", + "SAME_MIGT"] + for attributes_group_key in combinations(attributes_list, 2): + self.generate_marginal(attributes_group_key, views_group_name="AGE2") + + ##### inter-group marginals for geo-spatial and migration attributes (15) ##### + attributes_list = ["MIGPLAC5", "MIGSEA5", "MIGMET5", "MIGCITY5", "MIGCOUNTY"] + for attri_group_1 in attributes_list: + for attri_group_2 in ["CITY", "COUNTY", "AGE"]: + self.generate_marginal([attri_group_1, attri_group_2], views_group_name="AGE2") + + ##### generate other misc information 2 related marginals (7) ##### + self.generate_rent_marginal() + self.generate_valueh_marginal() + self.generate_famsize_marginal() + + self.generate_marginal(["OWNERSHPD", "RENT"], views_group_name="AGE2") + self.generate_marginal(["OWNERSHPD", "VALUEH"], views_group_name="AGE2") + self.generate_marginal(["OWNERSHPD", "FAMSIZE"], views_group_name="AGE2") + + #### generate inter-group marginals (6) ##### + self.generate_marginal(["FAMSIZE", "AGE"], views_group_name="AGE2") + self.generate_marginal(["OWNERSHPD", "MIGRATE"], views_group_name="AGE2") + + self.generate_marginal(["SEX", "LF_INCNON"], views_group_name="AGE2") + self.generate_marginal(["SEX", "EMPSTATD"], views_group_name="AGE2") + self.generate_marginal(["SEX", "OCC1950"], views_group_name="AGE2") + self.generate_marginal(["SEX", "IND1950"], views_group_name="AGE2") + + ########################################## Group SL ############################################# + # Totally marginals 12 marginals + + ##### recode (4) ###### + self.recode_sample_line_attributes() + + ##### generate sample line attributes ##### + # 1-way marginals (6 + 2) + attributes_list = ["SSENROLL", "MTONGUED", "CHBORN", "AGEMARR", "NATIVITY", "MARRNO"] + for attributes_group_key in attributes_list: + self.generate_marginal([attributes_group_key], views_group_name="SL") + + self.generate_marginal(["VET1940", "VETSTATD", "VETPER"], views_group_name="SL") + self.generate_marginal(["VETCHILD", "VETWWI"], views_group_name="SL") + diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5_2.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5_2.py new file mode 100644 index 0000000..a86eb7b --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_C5_2.py @@ -0,0 +1,195 @@ +from itertools import combinations + +import numpy as np + +from experiment.experiment_C5 import Experiment_C5 + + +class Experiment_C5_2(Experiment_C5): + def __init__(self, path, anonymization, synthesizer): + super(Experiment_C5_2, self).__init__(path, anonymization, synthesizer) + + # this function specify the attributes to be compressed, do not consume privacy budget + # the consumed privacy budget for recoded attributes are calculated in function configuration_C5_2() + self.attributes_group.use_recode_scheme_3() + + # construct noisy views based on configuration A1 + # this function is the only part that touches dataset and consumes privacy budget + self.configuration_C5_2() + + # consist noisy views + # post-processing code + for view_group_name in ["A", "AGE1", "AGE2", "SL"]: + self.logger.info("consist for group %s" % (view_group_name,)) + + self.update_num_categories(view_group_name) + self.consist_views(self.views_group[view_group_name], self.views_consist_key[view_group_name]) + + # generate synthetic dataset + # post-processing code + self.synthesize_records() + + # fill in some attributes based on mapping and decode attributes + # post-precessing code + self.post_processing() + + self.generate_submission_dataset(self.synthesized_records) + + def configuration_C5_2(self): + self.epsilon_incwage_singleton = self.epsilon / 24.0 + self.epsilon_enumdist_singleton = self.epsilon / 24.0 + self.epsilon_minc_marginal = self.epsilon / 4.0 + + self.reserve_epsilon = self.epsilon_incwage_singleton + self.epsilon_enumdist_singleton + self.epsilon_minc_marginal + self.remain_epsilon = self.epsilon - self.reserve_epsilon + + ####### calculate algorithm parameters and preprocess data ######## + # calculate the noise variance for each marginal based on Renyi-DP + if self.epsilon >= 1.0: + self.calculate_noise_parameter(203) + else: + self.calculate_noise_parameter(193) + + # preprocess attributes + self.geo_attributes_mapping = self.preprocess.refine_geo_attributes() + + ################## recode attributes groups ######################## + # Totally 26 marginals + + threshold = max(4.5 * self.gauss_sigma, 800) + self.recode_attributes(threshold) + + ###################################### generate marginals ################################# + # generate income related marginals + # we have reserved privacy budget for INCWAGE related marginals, + # thus do not count as the number of marginals to calculate sigma in function calculate_noise_parameter() + self.generate_incwage_marginal() + + ########################################## Group A ############################################# + # Totally 4 marginals + + ##### generate other misc information 1 related marginals (1) ##### + self.generate_marginal(["SPL_GQ_GQF", "GQTYPED"]) + + ##### generate race related marginals (3) ##### + attributes_list = ["RACED", "SPANN", "HISPAND"] + for attributes_group_key in combinations(attributes_list, 2): + self.generate_marginal(attributes_group_key) + + ########################################## Group AGE1 ############################################# + # Totally 6 marginal + + # handle AGE attributes (1) + age_index = self.attri_name_index_mapping["AGE"] + + self.age_singleton = self.anonymize_views(self.construct_views([age_index])) + self.age_singleton.non_negativity() + self.age_singleton.calculate_normalize_count() + + age_record = np.copy(self.records[:, age_index]) + self.age_bin_1 = self.preprocess.bucketize_age_1() + + # generate individual information 1 related marginals (2) + self.generate_marginal(["BPL", "AGE"], views_group_name="AGE1") + self.generate_marginal(["CITIZEN"], views_group_name="AGE1", is_iterate=False) + + # generate individual information 2 related marginals (3) + attributes_list = ["FBPL", "MBPL", "AGE"] + + for attributes_group_key in combinations(attributes_list, 2): + self.generate_marginal(attributes_group_key, views_group_name="AGE1") + + ########################################## Group AGE2 ############################################# + # Totally 145 marginals + + ###### handle AGE attributes ##### + self.records[:, age_index] = age_record + self.age_bin_2 = self.preprocess.bucketize_age_2() + + ##### generate individual information 3 related marginals (6) ##### + self.generate_marginal(["AGE", "RESPONDT"], views_group_name="AGE2") + self.generate_marginal(["AGE", "SCHOOL"], views_group_name="AGE2") + self.generate_marginal(["AGE", "MARST"], views_group_name="AGE2") + self.generate_marginal(["AGE", "NCHLT5"], views_group_name="AGE2") + self.generate_marginal(["SCHOOL", "MARST", "NCHLT5"], views_group_name="AGE2") + self.generate_marginal(["SCHOOL", "MARST", "RESPONDT"], views_group_name="AGE2") + + ##### generate work related marginals (55 + 2 = 57) ##### + attributes_list = ["EDUCD", "INCWAGE", "LF_INCNON", "EMPSTATD", "CLASSWKRD", "OCC1950", + "IND1950", "WKSWORK2", "HRSWORK2", "AGE"] + + for attributes_group_key in combinations(attributes_list, 2): + self.generate_marginal(attributes_group_key, views_group_name="AGE2") + + self.generate_work_time_marginal() + + ##### generate geo-spatial related marginals (26) ##### + # we have reserved privacy budget for ENUMDIST related marginals, + # thus do not count as the number of marginals to calculate sigma in function calculate_noise_parameter() + self.generate_enumdist_marginal() + + attributes_list = ["METAREAD", "UR_FA_MT", "CITY", "WARD", "COUNTY", "ENUMDIST"] + for attributes_group_key in combinations(attributes_list, 2): + self.generate_marginal(attributes_group_key, views_group_name="AGE2") + + self.generate_marginal(["CITYPOP", "CITY"], views_group_name="AGE2") + self.generate_marginal(["CITYPOP", "COUNTY"], views_group_name="AGE2") + + self.onebyone_mapping_from_marginal("COUNTY", "SEA") + self.generate_marginal(["COUNTY", "SUPDIST"], views_group_name="AGE2", is_iterate=False, is_consist=False) + self.generate_marginal(["CITYPOP", "URBPOP"], views_group_name="AGE2", is_iterate=False, is_consist=True) + + ##### generate migration related marginals (28) ##### + attributes_list = ["MIGRATE", "MIGPLAC5", "MIGSEA5", "MIGMET5", "MIGCITY5", "MIGCOUNTY", + "SAME_MIGT"] + + for attributes_group_key in combinations(attributes_list, 2): + self.generate_marginal(attributes_group_key, views_group_name="AGE2") + + ##### inter-group marginals for geo-spatial and migration attributes (15) ##### + attributes_list = ["MIGPLAC5", "MIGSEA5", "MIGMET5", "MIGCITY5", "MIGCOUNTY"] + for attri_group_1 in attributes_list: + for attri_group_2 in ["CITY", "COUNTY", "AGE"]: + self.generate_marginal([attri_group_1, attri_group_2], views_group_name="AGE2") + + ##### generate other misc information 2 related marginals (7) ##### + self.generate_rent_marginal() + self.generate_valueh_marginal() + self.generate_famsize_marginal() + + self.generate_marginal(["OWNERSHPD", "RENT"], views_group_name="AGE2") + self.generate_marginal(["OWNERSHPD", "VALUEH"], views_group_name="AGE2") + self.generate_marginal(["OWNERSHPD", "FAMSIZE"], views_group_name="AGE2") + self.generate_marginal(["OWNERSHPD", "FAMSIZE"], views_group_name="AGE2") + + #### generate inter-group marginals (6) ##### + self.generate_marginal(["FAMSIZE", "AGE"], views_group_name="AGE2") + self.generate_marginal(["OWNERSHPD", "MIGRATE"], views_group_name="AGE2") + + self.generate_marginal(["SEX", "LF_INCNON"], views_group_name="AGE2") + self.generate_marginal(["SEX", "EMPSTATD"], views_group_name="AGE2") + self.generate_marginal(["SEX", "OCC1950"], views_group_name="AGE2") + self.generate_marginal(["SEX", "IND1950"], views_group_name="AGE2") + + ########################################## Group SL ############################################# + # Totally 12 / 22 marginals + + ##### recode (4) ###### + self.recode_sample_line_attributes() + + ##### generate sample line attributes ##### + # 1-way marginals (8) + attributes_list = ["SSENROLL", "MTONGUED", "CHBORN", "AGEMARR", "NATIVITY", "MARRNO"] + for attributes_group_key in attributes_list: + self.generate_marginal([attributes_group_key], views_group_name="SL") + + self.generate_marginal(["VET1940", "VETSTATD", "VETPER"], views_group_name="SL") + self.generate_marginal(["VETCHILD", "VETWWI"], views_group_name="SL") + + # 2-way marginals (10) + if self.epsilon >= 1.0: + attributes_list = ["SSENROLL", "UOCC", "UOCC95", "UIND", "UCLASSWK"] + for attributes_group_key in combinations(attributes_list, 2): + self.generate_marginal(attributes_group_key, views_group_name="SL") + + diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_dpsyn.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_dpsyn.py new file mode 100644 index 0000000..9d40dd6 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_dpsyn.py @@ -0,0 +1,118 @@ +import logging +from collections import defaultdict + +import numpy as np + +from experiment.experiment import Experiment +from lib_view.view import View +from lib_view.consistent import Consistenter +from lib_composition.advanced_composition import AdvancedComposition +from lib_attributes.attributes_recode import RecodeAttribute +from lib_attributes.attributes_group import AttributeGroup +from lib_attributes.attributes_preprocess import PreprocessAttribute +from lib_attributes.attributes_postprocess import PostprocessAttribute + + +class ExperimentDPSyn(Experiment): + def __init__(self, path, anonymization, synthesizer): + super(ExperimentDPSyn, self).__init__(path, anonymization) + + self.logger = logging.getLogger("dpsyn") + + self.num_synthesize_records = synthesizer["num_records"] + self.consist_iterations = synthesizer["consist_iterations"] + self.update_iterations = synthesizer["update_iterations"] + + self.reserve_epsilon = 0.0 + self.remain_epsilon = self.epsilon + + self.attributes_group = AttributeGroup(self.attri_name_index_mapping) + + self.views_group = defaultdict(dict) + self.views_iterate_key = defaultdict(list) + self.views_consist_key = defaultdict(list) + self.num_records_estimation = [0.0] + + self.ordinal_views_dict = {} + self.ordinal_bin_dict = {} + + self.recode_significant_indices_dict = {} + self.recode_group_indices_dict = {} + self.recode_views_dict = {} + + self.onebyone_mapping = {} + + self.common_parameters = { + "recode_significant_indices_dict": self.recode_significant_indices_dict, + "recode_group_indices_dict": self.recode_group_indices_dict, + "recode_views_dict": self.recode_views_dict, + "views_group": self.views_group, + "views_iterate_key": self.views_iterate_key, + "views_consist_key": self.views_consist_key, + "ordinal_views_dict": self.ordinal_views_dict, + "ordinal_bin_dict": self.ordinal_bin_dict, + "attributes_group": self.attributes_group, + "num_records_estimation": self.num_records_estimation, + "epsilon": self.epsilon, + "code_mapping": self.code_mapping, + "attri_name_index_mapping": self.attri_name_index_mapping, + "specs_path": self.specs_path, + "incwage_bin_value": [], + "onebyone_mapping": self.onebyone_mapping + } + + self.preprocess = PreprocessAttribute(self.records, self.num_categories, self.common_parameters) + self.recode = RecodeAttribute(self.records, self.num_categories, self.common_parameters) + self.postprocess = PostprocessAttribute(self.num_categories, self.common_parameters) + + def calculate_noise_parameter(self, num_views): + composition = AdvancedComposition() + + self.gauss_sigma = composition.gauss_renyi(self.remain_epsilon, self.delta, self.sensitivity, num_views) + self.laplace_epsilon = self.epsilon / num_views + + self.logger.info("sigma = %s" % (self.gauss_sigma,)) + + def construct_views(self, basis): + view_attri = [] + + for bas in basis: + view_attri.append(self.dataset_header[bas]) + + self.logger.info("constructing %s" % (tuple(view_attri),)) + + view_indicator = np.zeros(len(self.num_categories), dtype=np.uint8) + view_indicator[basis] = 1 + + view = View(view_indicator, self.num_categories) + view.count_records(self.records) + + return view + + def anonymize_views(self, view, epsilon=0.0): + self.logger.info("anonymizing views") + + if epsilon != 0.0: + view.count += np.random.laplace(scale=self.sensitivity / epsilon, size=view.num_key) + else: + view.count += np.random.normal(scale=self.gauss_sigma, size=view.num_key) + + return view + + def consist_views(self, views, consist_key): + self.logger.info("consisting views") + + consist_views = {} + + for key in consist_key: + consist_views[key] = views[key] + + consist_parameters = { + "consist_iterations": self.consist_iterations + } + + consistenter = Consistenter(consist_views, self.num_categories, consist_parameters) + consistenter.consist_views() + + self.logger.info("consisted views") + diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_dpsyn_mcf.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_dpsyn_mcf.py new file mode 100644 index 0000000..f930c05 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/experiment/experiment_dpsyn_mcf.py @@ -0,0 +1,208 @@ +import logging + +import numpy as np + +from experiment.experiment_dpsyn import ExperimentDPSyn +from lib_dpsyn.records_update import RecordUpdate +from lib_view.view import View + + +class ExperimentDPSynMCF(ExperimentDPSyn): + def __init__(self, path, anonymization, synthesizer): + super(ExperimentDPSynMCF, self).__init__(path, anonymization, synthesizer) + + self.logger = logging.getLogger("dpsyn_mcf") + + self.age_bin_1 = None + self.age_bin_2 = None + + self.incwage_fill_value = None + + def update_records(self, views_group_name, threshold, num_synthesize_records=None): + self.logger.info("-" * 20 + "updating group %s" + "-" * 20, views_group_name) + + views_iterate_key = self.views_iterate_key[views_group_name] + self.update_num_categories(views_group_name) + synthesizer = RecordUpdate(self.num_categories, self.original_num_categories) + + if num_synthesize_records == None: + num_synthesize_records = self.num_synthesize_records + + synthesizer.initialize_records(num_synthesize_records, self.num_attributes, len(views_iterate_key), self.update_iterations) + + alpha = 0.2 + + count = 1 + update_round = 0 + + while count != 0 and update_round < self.update_iterations: + self.logger.info("Update round: %d" % (update_round,)) + + count = 0 + + for index, key in enumerate(views_iterate_key): + self.logger.info("updating %s view: %s, num_key: %s" % (index, key, self.views_group[views_group_name][key].num_key)) + + count += synthesizer.update_records_S5(self.views_group[views_group_name][key], alpha, threshold, index, update_round) + + update_round += 1 + + return synthesizer + + def update_num_categories(self, view_group_name): + if view_group_name == "AGE1": + self.num_categories[self.attri_name_index_mapping["AGE"]] = self.age_bin_1.size + 1 + elif view_group_name == "AGE2": + self.num_categories[self.attri_name_index_mapping["AGE"]] = self.age_bin_2.size + 1 + + def update_records_fully(self, synthesizer, view_key, group_name): + self.logger.info("update records to fully match %s marginal" % (view_key,)) + + if view_key in self.views_group[group_name]: + view = self.views_group[group_name][view_key] + view_index = self.views_iterate_key[group_name].index(view_key) + + synthesizer.update_records_fully(view, 0.0, view_index, self.update_iterations - 1) + else: + self.logger.warning("view %s do not exist in the specific group" % (view_key,)) + + def recode_attributes(self, threshold, city_epsilon=0, city_threshold=0): + self.logger.info("recoding attributes") + num_recode_group = 0 + + for group_name in ["A", "AGE1", "AGE2"]: + group = self.attributes_group.recode_group_key[group_name] + + for attributes_group_key in group: + self.recode.attributes_group_anonymize(attributes_group_key, self.sensitivity, self.gauss_sigma) + num_recode_group += 1 + + for group_name in ["A", "AGE1", "AGE2"]: + group = self.attributes_group.recode_single_key[group_name] + + for attribute in group: + if attribute == "CITY": + if city_epsilon == 0: + self.recode.attributes_group_anonymize(attribute, self.sensitivity, self.gauss_sigma) + else: + self.recode.attributes_group_anonymize(attribute, self.sensitivity, self.gauss_sigma, epsilon=city_epsilon) + else: + self.recode.attributes_group_anonymize(attribute, self.sensitivity, self.gauss_sigma) + + num_recode_group += 1 + + self.num_records_estimation[0] /= num_recode_group + + for group_name in ["A", "AGE1", "AGE2"]: + group = self.attributes_group.recode_group_key[group_name] + + for attributes_group_key in group: + self.recode.attributes_recode(attributes_group_key, threshold) + + for group_name in ["A", "AGE1", "AGE2"]: + group = self.attributes_group.recode_single_key[group_name] + + for attribute in group: + if attribute == "CITY": + if city_threshold == 0: + self.recode.attributes_recode(attribute, threshold) + else: + self.recode.attributes_recode(attribute, city_threshold) + else: + self.recode.attributes_recode(attribute, threshold) + + self.logger.info("recoded attributes") + + def generate_marginal(self, attributes_list, views_group_name="A", is_iterate=True, is_consist=True): + basis = [] + + for attribute_name in attributes_list: + if attribute_name in self.attributes_group.recode_group_key_summary: + attri_name = self.attributes_group.recode_group_key[views_group_name][attribute_name][0] + basis.append(self.attri_name_index_mapping[attri_name]) + else: + basis.append(self.attri_name_index_mapping[attribute_name]) + + if all(self.num_categories[basis] != 0): + view = self.anonymize_views(self.construct_views(basis)) + + self.views_group[views_group_name][tuple(attributes_list)] = view + + if is_iterate: + self.views_iterate_key[views_group_name].append(tuple(attributes_list)) + + if is_consist: + self.views_consist_key[views_group_name].append(tuple(attributes_list)) + + def generate_marginal_general(self, attributes_list, records, num_categories): + basis = [] + + for attribute_name in attributes_list: + basis.append(self.attri_name_index_mapping[attribute_name]) + + view_indicator = np.zeros(len(num_categories), dtype=np.uint8) + view_indicator[basis] = 1 + + view = View(view_indicator, num_categories) + view.count_records(records) + view.calculate_count_matrix() + + row_nonzero_indices = [] + column_nonzero_indices = [] + + for i in range(view.count_matrix.shape[0]): + if np.sum(view.count_matrix[i, :]) != 0: + row_nonzero_indices.append(i) + + for j in range(view.count_matrix.shape[1]): + if np.sum(view.count_matrix[:, j]) != 0: + column_nonzero_indices.append(j) + + valid_count_matrix = view.count_matrix[np.ix_(row_nonzero_indices, column_nonzero_indices)] + + return view, valid_count_matrix + + def onebyone_mapping_from_marginal(self, anchor_attribute, fill_attribute): + mapping = {} + basis = [] + basis.append(self.attri_name_index_mapping[fill_attribute]) + basis.append(self.attri_name_index_mapping[anchor_attribute]) + + view = self.anonymize_views(self.construct_views(basis)) + view.calculate_count_matrix() + + anchor_index = np.where(view.attributes_index == self.attri_name_index_mapping[anchor_attribute])[0] + + if anchor_index == 0: + for anchor in range(view.count_matrix.shape[0]): + fill = np.argmax(view.count_matrix[anchor, :]) + mapping[anchor] = fill + elif anchor_index == 1: + for anchor in range(view.count_matrix.shape[1]): + fill = np.argmax(view.count_matrix[:, anchor]) + mapping[anchor] = fill + + self.onebyone_mapping[(anchor_attribute, fill_attribute)] = mapping + + def key_attri_index(self, iterate_keys): + ret_set = set() + + for keys in iterate_keys: + if type(keys) is str: + # if len(keys) == 1: + if keys in self.attributes_group.recode_group_key_summary: + ret_set.update({self.attributes_group.recode_group_index[keys][0]}) + else: + ret_set.update({self.attri_name_index_mapping[keys]}) + elif type(keys) is tuple: + for key in keys: + if key in self.attributes_group.recode_group_key_summary: + ret_set.update({self.attributes_group.recode_group_index[key][0]}) + else: + ret_set.update({self.attri_name_index_mapping[key]}) + + return list(ret_set) + + + + \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/generate_submission.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/generate_submission.py new file mode 100644 index 0000000..cb2f0af --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/generate_submission.py @@ -0,0 +1,50 @@ +import csv +import logging + +import numpy as np + + +class GenerateSubmission: + def __init__(self, attri_name_index_mapping): + self.logger = logging.getLogger("generate_submission") + + self.attri_name_index_mapping = attri_name_index_mapping + + def load_records(self, records): + self.records = records + + def generate_csv(self, dataset_header, code_mapping, output_path): + save_file = open(output_path, 'w', newline='') + + save_csv = csv.writer(save_file, dialect='excel') + + save_csv.writerow(dataset_header) + + self.logger.info("mapping attributes") + + for attribute_name in code_mapping: + if attribute_name in self.attri_name_index_mapping: + self.logger.info("mapping %s" % (attribute_name,)) + + attribute_index = self.attri_name_index_mapping[attribute_name] + unique_value = np.unique(self.records[:, attribute_index]) + attri_records = np.zeros(self.records.shape[0]) + else: + continue + + for value in unique_value: + indices = np.where(self.records[:, attribute_index] == value)[0] + attri_records[indices] = code_mapping[attribute_name][value] + + self.records[:, attribute_index] = attri_records + + self.logger.info("writing records") + + for i in range(self.records.shape[0]): + if i % 10000 == 0: + self.logger.info("processing %s records" % (i,)) + + record = np.ndarray.tolist(self.records[i]) + + save_csv.writerow(record) + \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_group.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_group.py new file mode 100644 index 0000000..888ae84 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_group.py @@ -0,0 +1,89 @@ + + +class AttributeGroup: + def __init__(self, attri_name_index_mapping): + self.attri_name_index_mapping = attri_name_index_mapping + + def use_recode_scheme_3(self): + self.recode_group_key = { + "A": { + "SPL_GQ_GQF": ["SPLIT", "GQ", "GQFUNDS"], + "SPANN": ["SPANNAME", "HISPRULE"], + }, + "AGE1": { + }, + "AGE2": { + "UR_FA_MT": ["URBAN", "FARM", "METRO"], + "MIGRATE": ["MIGRATE5", "MIGRATE5D"], + "SAME_MIGT": ["SAMESEA5", "SAMEPLAC", "MIGTYPE5"], + # "LF_SEX_INCNON": ["LABFORCE", "SEX", "INCNONWG"], + "LF_INCNON": ["LABFORCE", "INCNONWG"], + }, + "SL": { + # "VETERAN": ["VETPER", "VET1940", "VETSTATD"], + # "VETC_VETW": ["VETWWI", "VETCHILD"], + } + } + self.recode_single_key = { + "A": { + "AGEMONTH", + "DURUNEMP", + "GQTYPED", + + "RACED", + "HISPAND", + }, + "AGE1": { + "BPL", + "FBPL", + "MBPL" + }, + "AGE2": { + # "METAREAD", + # "CITY", + "CITYPOP", + "WARD", + # "ENUMDIST", + "MIGPLAC5", + "MIGSEA5", + "MIGMET5", + "MIGCITY5", + "MIGCOUNTY", + + "EDUCD", + "EMPSTATD", + "CLASSWKRD", + "OCC1950", + "IND1950", + }, + "SL": { + "UOCC", + "UOCC95", + "UIND", + "UCLASSWK" + } + } + + self.recode_group_index = {} + + self.translate_recode_group() + self.translate_recode_single() + + def translate_recode_group(self): + self.recode_group_key_summary = [] + + for _, group in self.recode_group_key.items(): + for key, value in group.items(): + index_list = [] + + for attribute_name in value: + index_list.append(self.attri_name_index_mapping[attribute_name]) + + self.recode_group_index[key] = index_list + self.recode_group_key_summary.append(key) + + def translate_recode_single(self): + for _, group in self.recode_single_key.items(): + for attribute_name in group: + self.recode_group_index[attribute_name] = [self.attri_name_index_mapping[attribute_name]] + \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_postprocess.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_postprocess.py new file mode 100644 index 0000000..a1c5542 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_postprocess.py @@ -0,0 +1,421 @@ +import logging +import json +from itertools import chain + +import numpy as np + +import config +from lib_view.non_negativity import NonNegativity + + +class PostprocessAttribute: + def __init__(self, num_categories, common_parameters): + self.logger = logging.getLogger("postprocess_attribute") + + self.num_categories = num_categories + self.specs_path = common_parameters["specs_path"] + + self.attributes_group = common_parameters["attributes_group"] + self.views_group = common_parameters["views_group"] + + self.ordinal_views_dict = common_parameters["ordinal_views_dict"] + self.ordinal_bin_dict = common_parameters["ordinal_bin_dict"] + + self.recode_significant_indices_dict = common_parameters["recode_significant_indices_dict"] + self.recode_group_indices_dict = common_parameters["recode_group_indices_dict"] + self.recode_views_dict = common_parameters["recode_views_dict"] + + self.attri_name_index_mapping = common_parameters["attri_name_index_mapping"] + self.code_mapping = common_parameters["code_mapping"] + self.onebyone_mapping = common_parameters["onebyone_mapping"] + + #################################### general functions ################################### + def decode_anchor_attributes(self, records, group_name_list): + self.logger.info("decoding anchor attributes") + + for group_name in group_name_list: + for attributes_group_key in chain(self.attributes_group.recode_group_key[group_name].keys(), + self.attributes_group.recode_single_key[group_name]): + + if attributes_group_key not in self.recode_views_dict: + self.logger.warning("attributes group %s has been removed" % (attributes_group_key,)) + else: + recode_view = self.recode_views_dict[attributes_group_key] + significant_cell_indices = self.recode_significant_indices_dict[attributes_group_key] + group_cell_indices = self.recode_group_indices_dict[attributes_group_key] + + attributes_group_index = self.attributes_group.recode_group_index[attributes_group_key] + num_value = self.num_categories[attributes_group_index[0]] + record = np.zeros([records.shape[0], len(attributes_group_index)], dtype=np.uint32) + + if group_cell_indices.size == 0: + # decode the significant value + for anchor_value in range(num_value): + anchor_value_indices = np.where(records[:, attributes_group_index[0]] == anchor_value)[0] + + for attri_index, attri in enumerate(attributes_group_index): + record[anchor_value_indices, attri_index] = \ + recode_view.tuple_key[significant_cell_indices[anchor_value]][attri_index] + else: + # decode the significant value + for anchor_value in range(num_value - 1): + anchor_value_indices = np.where(records[:, attributes_group_index[0]] == anchor_value)[0] + + for attri_index, attri in enumerate(attributes_group_index): + record[anchor_value_indices, attri_index] = \ + recode_view.tuple_key[significant_cell_indices[anchor_value]][attri_index] + + # decode the grouped value + anchor_value_indices = np.where(records[:, attributes_group_index[0]] == num_value - 1)[0] + + if anchor_value_indices.size != 0: + group_value_dist = recode_view.count[group_cell_indices] / np.sum(recode_view.count[group_cell_indices]) + group_value_cumsum = np.cumsum(group_value_dist) + start = 0 + + for index, value in enumerate(group_value_cumsum): + end = int(round(value * anchor_value_indices.size)) + + for attri_index, attri in enumerate(attributes_group_index): + record[anchor_value_indices[start: end], attri_index] = \ + recode_view.tuple_key[group_cell_indices[index]][attri_index] + + start = end + + records[:, attributes_group_index] = record + + def decode_ordinal_attributes(self, records, attribute_dist=None, attribute_bin=None, attribute_index=None): + def decode(attribute_dist, attribute_bin, attribute_index): + record = np.copy(records[:, attribute_index]) + + for value in range(1, attribute_bin.size + 1): + indices = np.where(record == value)[0] + + if value < attribute_bin.size: + attri_dist = attribute_dist[attribute_bin[value - 1]: attribute_bin[value]] + attri_value = np.arange(attribute_bin[value - 1], attribute_bin[value]) + else: + attri_dist = attribute_dist[attribute_bin[value - 1]:] + attri_value = np.arange(attribute_bin[value - 1], attribute_dist.size) + + if np.sum(attri_dist) != 0: + attri_dist /= np.sum(attri_dist) + attri_dist_cumsum = np.cumsum(attri_dist) + start = 0 + + for index, dist in enumerate(attri_dist_cumsum): + end = int(round(dist * indices.size)) + + records[indices[start: end], attribute_index] = attri_value[index] + + start = end + + if attribute_dist is not None: + self.logger.info("decoding ordinal attribute %s" % (attribute_index,)) + + decode(attribute_dist, attribute_bin, attribute_index) + + np.random.shuffle(records) + + else: + self.logger.info("decoding ordinal attributes") + + records[:, self.attri_name_index_mapping["WKSWORK1"]] = records[:, self.attri_name_index_mapping["WKSWORK2"]] + 1 + records[:, self.attri_name_index_mapping["HRSWORK1"]] = records[:, self.attri_name_index_mapping["HRSWORK2"]] + 1 + + for attribute_name in self.ordinal_views_dict: + attribute_dist = self.ordinal_views_dict[attribute_name].normalize_count + attribute_bin = self.ordinal_bin_dict[attribute_name] + attribute_index = self.attri_name_index_mapping[attribute_name] + + decode(attribute_dist, attribute_bin, attribute_index) + + np.random.shuffle(records) + + def decode_geo_attributes(self, records, geo_attributes_mapping): + self.logger.info("decoding geo attributes") + + for attribute_name, mapping in geo_attributes_mapping.items(): + attribute_index = self.attri_name_index_mapping[attribute_name] + record = np.copy(records[:, attribute_index]) + unique_value = np.unique(record) + + for value in unique_value: + indices = np.where(record == value) + records[indices, attribute_index] = mapping[value] + + def load_mapping_data(self): + self.general_detail_mapping = json.load(open(config.MAPPING_PATH + "general_detail_mapping.json")) + self.pair_mapping = json.load(open(config.MAPPING_PATH + "pair_mapping.json")) + + def fill_general_from_detail(self, records): + self.logger.info("filling general from detail") + + for general_name in self.general_detail_mapping: + if general_name in ["BPL", "FBPL", "MBPL"]: + continue + + detail_name = general_name + "D" + detail_index = self.attri_name_index_mapping[detail_name] + general_index = self.attri_name_index_mapping[general_name] + + record = np.copy(records[:, detail_index]) + unique_value = np.unique(record) + + for value in unique_value: + indices = np.where(record == value)[0] + detail_code = str(self.code_mapping[detail_name][value]) + general_code = self.general_detail_mapping[general_name][detail_code] + + if general_code in self.code_mapping[general_name]: + general_value = np.where(self.code_mapping[general_name] == general_code)[0][0] + records[indices, general_index] = general_value + else: + self.logger.info("general code do not exist for detail code %s of %s attribute" % (detail_code, detail_name)) + # todo: determine how to fill value in this situation + records[indices, general_index] = 0 + + np.random.shuffle(records) + + def fill_pair_mapping_attributes(self, records): + self.logger.info("filling pairwise mapping") + + for anchor_attribute, fill_attributes in self.pair_mapping.items(): + for fill_attribute, mapping in fill_attributes.items(): + anchor_index = self.attri_name_index_mapping[anchor_attribute] + fill_index = self.attri_name_index_mapping[fill_attribute] + + record = np.copy(records[:, anchor_index]) + unique_value = np.unique(record) + + for value in unique_value: + indices = np.where(record == value)[0] + anchor_code = self.code_mapping[anchor_attribute][value] + + if str(anchor_code) in mapping: + fill_code = mapping[str(anchor_code)] + + if fill_attribute in self.code_mapping: + fill_value = np.where(self.code_mapping[fill_attribute] == fill_code)[0][0] + else: + fill_value = fill_code + + records[indices, fill_index] = fill_value + else: + self.logger.info("mapping do not exist for %s for %s and %s" % (anchor_code, anchor_attribute, fill_attribute)) + # todo: determine how to fill value in this situation + records[indices, fill_index] = 0 + + np.random.shuffle(records) + + def fill_onebyone_mapping_attributes(self, records): + self.logger.info("filling one by one mapping") + + for attributes in self.onebyone_mapping: + anchor_attribute = attributes[0] + fill_attribute = attributes[1] + anchor_index = self.attri_name_index_mapping[anchor_attribute] + fill_index = self.attri_name_index_mapping[fill_attribute] + + record = np.copy(records[:, anchor_index]) + unique_value = np.unique(record) + + for value in unique_value: + indices = np.where(record == value)[0] + fill_value = self.onebyone_mapping[attributes][value] + + records[indices, fill_index] = fill_value + + np.random.shuffle(records) + + ####################################### handling incwage ###################################### + def decode_incwage_C5(self, records, incwage_singleton, incwage_bin): + self.logger.info("decoding incawge") + + incwage_dist = incwage_singleton.normalize_count + incwage_index = self.attri_name_index_mapping["INCWAGE"] + record = np.copy(records[:, incwage_index]) + + for value in range(1, self.num_categories[incwage_index]): + indices = np.where(record == value)[0] + + if value < incwage_bin.size: + attri_dist = incwage_dist[incwage_bin[value - 1]: incwage_bin[value]] + attri_value = np.arange(incwage_bin[value - 1], incwage_bin[value]) + else: + attri_dist = incwage_dist[incwage_bin[value - 1]:] + attri_value = np.arange(incwage_bin[value - 1], incwage_dist.size) + + if np.sum(attri_dist) != 0: + attri_dist /= np.sum(attri_dist) + attri_dist_cumsum = np.cumsum(attri_dist) + start = 0 + + for index, dist in enumerate(attri_dist_cumsum): + end = int(round(dist * indices.size)) + + records[indices[start: end], incwage_index] = attri_value[index] + + start = end + + np.random.shuffle(records) + + ############################################### dealing with special attributes ############################## + def fill_bpl_attributes(self, records): + self.logger.info("filling bpl attributes") + + for attribute_name in ["BPL", "FBPL", "MBPL"]: + general_name = attribute_name + detail_name = attribute_name + "D" + + record = records[:, self.attri_name_index_mapping[general_name]] + unique_value = np.unique(record) + + for value in unique_value: + indices = np.where(record == value)[0] + detail_code = self.code_mapping[general_name][value] * 100 + + records[indices, self.attri_name_index_mapping[detail_name]] = \ + np.where(self.code_mapping[detail_name] == detail_code)[0][0] + + np.random.shuffle(records) + + def fill_geo_attributes(self, records): + self.logger.info("filling geo attributes") + + # fill URBPOP and SIZEPL from CITYPOP + record = np.copy(records[:, self.attri_name_index_mapping["CITYPOP"]]) + unique_value = np.unique(record) + + urbpop_index = self.attri_name_index_mapping["URBPOP"] + sizepl_index = self.attri_name_index_mapping["SIZEPL"] + + for value in unique_value: + indices = np.where(record == value)[0] + + # fill URBPOP + if ("CITYPOP", "URBPOP") in self.views_group["AGE2"]: + if value == 0: + matrix_count = self.views_group["AGE2"][("CITYPOP", "URBPOP")].calculate_count_matrix() + citypop_zero_count = matrix_count[0] + non_negativity = NonNegativity(citypop_zero_count) + citypop_zero_count = non_negativity.norm_cut() + + if np.sum(citypop_zero_count) == 0: + records[indices, urbpop_index] = 0 + else: + zero_count_cumsum = np.cumsum(citypop_zero_count) / np.sum(citypop_zero_count) + start = 0 + + for index, dist in enumerate(zero_count_cumsum): + end = int(round(dist * indices.size)) + records[indices[start: end], urbpop_index] = index + start = end + + elif value > 0 and value < 25: + records[indices, urbpop_index] = 0 + else: + records[indices, urbpop_index] = value + + # fill SIZEPL, RHS is the encoded value, not actual value for SIZEPL + if value < 10: + records[indices, sizepl_index] = 1 + elif value >= 10 and value < 25: + records[indices, sizepl_index] = 2 + elif value >= 25 and value < 40: + records[indices, sizepl_index] = 3 + elif value >= 40 and value < 50: + records[indices, sizepl_index] = 4 + elif value >= 50 and value < 100: + records[indices, sizepl_index] = 5 + elif value >= 100 and value < 250: + records[indices, sizepl_index] = 6 + elif value >= 250 and value < 500: + records[indices, sizepl_index] = 7 + elif value >= 500 and value < 750: + records[indices, sizepl_index] = 8 + elif value >= 750 and value < 1000: + records[indices, sizepl_index] = 9 + elif value >= 1000 and value < 2000: + records[indices, sizepl_index] = 10 + elif value >= 2000 and value < 3000: + records[indices, sizepl_index] = 11 + elif value >= 3000 and value < 4000: + records[indices, sizepl_index] = 12 + elif value >= 4000 and value < 5000: + records[indices, sizepl_index] = 13 + elif value >= 5000 and value < 6000: + records[indices, sizepl_index] = 14 + elif value >= 6000 and value < 7500: + records[indices, sizepl_index] = 15 + elif value >= 7500 and value < 10000: + records[indices, sizepl_index] = 16 + elif value >= 10000 and value < 20000: + records[indices, sizepl_index] = 17 + else: + records[indices, sizepl_index] = 18 + + np.random.shuffle(records) + + def fill_supdist_attribute(self, records, sigma): + self.logger.info("filling supdist attribute") + + county_supdist_matrix_count = self.views_group["AGE2"][("COUNTY", "SUPDIST")].calculate_count_matrix() + county_supdist_matrix_count = np.transpose(county_supdist_matrix_count) + + county_index = self.attri_name_index_mapping["COUNTY"] + supdist_index = self.attri_name_index_mapping["SUPDIST"] + record = np.copy(records[:, county_index]) + unique_value = np.unique(record) + + for value in unique_value: + indices = np.where(record == value)[0] + valid_supdist_index = np.where(county_supdist_matrix_count[value] >= 4.5 * sigma)[0] + + if valid_supdist_index.size == 0: + valid_supdist_index = np.argmax(county_supdist_matrix_count[value]) + records[indices, supdist_index] = valid_supdist_index + elif valid_supdist_index.size == 1: + records[indices, supdist_index] = valid_supdist_index + else: + valid_count = county_supdist_matrix_count[value, valid_supdist_index] + valid_count_cumsum = np.cumsum(valid_count) / np.sum(valid_count) + start = 0 + + for index, dist in enumerate(valid_count_cumsum): + end = int(round(dist * indices.size)) + records[indices[start: end], supdist_index] = valid_supdist_index[index] + start = end + + np.random.shuffle(records) + + def fill_citizen_attribute(self, records): + self.logger.info("filling citizen attribute") + + bpl_index = self.attri_name_index_mapping["BPL"] + citizen_index = self.attri_name_index_mapping["CITIZEN"] + threshold_index = np.where(self.code_mapping["BPL"] == 99)[0] + + # when BPL <= 99, set CITIZEN = 0 + under_indices = np.where(records[:, bpl_index] <= threshold_index)[0] + records[under_indices, citizen_index] = 0 + + # when BPL > 99, fill CITIZEN based on its distribution + over_indices = np.where(records[:, bpl_index] > threshold_index)[0] + citizen_dist = self.views_group["AGE1"][("CITIZEN",)].normalize_count[1:] + + if np.sum(citizen_dist) != 0: + citizen_cumsum = np.cumsum(citizen_dist) / np.sum(citizen_dist) + start = 0 + + for index, dist in enumerate(citizen_cumsum): + end = int(round(dist * over_indices.size)) + + records[over_indices[start: end], citizen_index] = index + 1 + + start = end + + np.random.shuffle(records) + + + diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_preprocess.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_preprocess.py new file mode 100644 index 0000000..e725d89 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_preprocess.py @@ -0,0 +1,151 @@ +import logging +import json + +import numpy as np + + +class PreprocessAttribute: + def __init__(self, records, num_categories, common_parameters): + self.logger = logging.getLogger("preprocess_attribute") + + self.records = records + self.num_categories = num_categories + + self.code_mapping = common_parameters["code_mapping"] + self.specs_path = common_parameters["specs_path"] + self.attri_name_index_mapping = common_parameters["attri_name_index_mapping"] + self.incwage_bin_value = common_parameters["incwage_bin_value"] + + def refine_geo_attributes(self): + self.logger.info("refining geo attribute") + geo_attributes_mapping = {} + + for attribute_name in ["COUNTY", "METAREAD", "CITY"]: + attribute_index = self.attri_name_index_mapping[attribute_name] + record = np.copy(self.records[:, attribute_index]) + unique_value = np.unique(record) + + for index, value in enumerate(unique_value): + indices = np.where(record == value)[0] + self.records[indices, attribute_index] = index + + self.num_categories[attribute_index] = unique_value.size + geo_attributes_mapping[attribute_name] = unique_value + + return geo_attributes_mapping + + def bucketize_attribute(self, attribute_name, attribute_bin): + attribute_index = self.attri_name_index_mapping[attribute_name] + bucketized_value = np.digitize(self.records[:, attribute_index], attribute_bin) + self.records[:, attribute_index] = bucketized_value + self.num_categories[attribute_index] = attribute_bin.size + 1 + + def bucketize_enumdist(self): + enumdist_bin = [100 * x for x in range(31)] + + enumdist_bin = np.array(enumdist_bin) + + self.bucketize_attribute("ENUMDIST", enumdist_bin) + + return enumdist_bin + + def bucketize_rent(self): + rent_bin = [0] + rent_bin += [1 + 5 * x for x in range(6)] + rent_bin += [31 + 10 * x for x in range(2)] + rent_bin += [51, 101, 9998] + + rent_bin = np.array(rent_bin) + + self.bucketize_attribute("RENT", rent_bin) + + return rent_bin + + def bucketize_valueh(self): + valueh_bin = [0] + valueh_bin += [500 + 500 * x for x in range(8)] + valueh_bin += [4999, 6999, 9999, 10000, 9999998, 9999999] + + valueh_bin = np.array(valueh_bin) + + self.bucketize_attribute("VALUEH", valueh_bin) + + return valueh_bin + + def bucketize_famsize(self): + famsize_bin = [1 + x for x in range(10)] + + famsize_bin = np.array(famsize_bin) + + self.bucketize_attribute("FAMSIZE", famsize_bin) + + return famsize_bin + + def bucketize_age_1(self): + age_bin = [10 * x for x in range(7)] + + age_bin = np.array(age_bin) + + self.bucketize_attribute("AGE", age_bin) + + return age_bin + + def bucketize_age_2(self): + age_bin = [x for x in range(31)] + age_bin += [31 + 2 * x for x in range(4)] + age_bin += [39 + 3 * x for x in range(7)] + age_bin += [60 + 5 * x for x in range(3)] + age_bin += [75, 101] + + age_bin = np.array(age_bin) + + self.bucketize_attribute("AGE", age_bin) + + return age_bin + + def bucketize_incwage_4(self): + incwage_bin = [0, 1] + incwage_bin += [100 + 100 * x for x in range(10)] + incwage_bin += [1200, 1500, 1800, 2200, 3000, 999998] + + incwage_bin = np.array(incwage_bin) + + self.bucketize_attribute("INCWAGE", incwage_bin) + + return incwage_bin + + def bucketize_incwage_5(self): + incwage_bin = [0, 1, 200, 500, 800, 1200, 1800, 999998] + + incwage_bin = np.array(incwage_bin) + + self.bucketize_attribute("INCWAGE", incwage_bin) + + return incwage_bin + + def bucketize_incwage_6(self): + incwage_bin = [0, 1, 400, 900, 1500, 999998] + + incwage_bin = np.array(incwage_bin) + + attribute_index = self.attri_name_index_mapping["INCWAGE"] + original_incwage_record = np.copy(self.records[:, attribute_index]) + + bucketized_value = np.digitize(self.records[:, attribute_index], incwage_bin) + self.records[:, attribute_index] = bucketized_value + self.num_categories[attribute_index] = incwage_bin.size + 1 + + return incwage_bin, original_incwage_record + + def bucketize_incwage_7(self): + incwage_bin = [0, 1, 200, 400, 700, 900, 1200, 1500, 2000, 999998] + + incwage_bin = np.array(incwage_bin) + + self.bucketize_attribute("INCWAGE", incwage_bin) + + return incwage_bin + + + + \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_recode.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_recode.py new file mode 100644 index 0000000..1efd8bf --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_attributes/attributes_recode.py @@ -0,0 +1,151 @@ +import logging +import math + +import numpy as np + +from lib_view.view import View + + +class RecodeAttribute: + def __init__(self, records, num_categories, common_parameters): + self.logger = logging.getLogger("recode_attribute") + + self.records = records + self.num_categories = num_categories + + self.attributes_group_views_dict = {} + + self.epsilon = common_parameters["epsilon"] + self.num_records_estimation = common_parameters["num_records_estimation"] + self.attributes_group = common_parameters["attributes_group"] + + self.recode_significant_indices_dict = common_parameters["recode_significant_indices_dict"] + self.recode_group_indices_dict = common_parameters["recode_group_indices_dict"] + self.recode_views_dict = common_parameters["recode_views_dict"] + + self.views_group = common_parameters["views_group"] + self.views_iterate_key = common_parameters["views_iterate_key"] + self.views_consist_key = common_parameters["views_consist_key"] + # self.views_iterate_key_B = common_parameters["views_iterate_key_B"] + + def attributes_group_anonymize(self, attributes_group_key, sensitivity, gauss_sigma, recode_times=0, epsilon=0.0, is_anonymize=True): + attributes_group = self.attributes_group.recode_group_index[attributes_group_key] + indicator = np.zeros(len(self.num_categories), dtype=np.uint8) + indicator[attributes_group] = 1 + + # choose the cells with values above threshold + view = View(indicator, self.num_categories) + view.calculate_tuple_key() + view.count_records(self.records) + + if is_anonymize or self.epsilon == -1.0: + if epsilon != 0.0: + view.count += np.random.laplace(scale=sensitivity / epsilon, size=view.num_key) + else: + view.count += np.random.normal(scale=gauss_sigma, size=view.num_key) + + self.attributes_group_views_dict[attributes_group_key] = view + + if recode_times == 0: + self.num_records_estimation[0] += np.sum(view.count) + + def attributes_recode(self, attributes_group_key, threshold, recode_times=0): + self.logger.info("recoding group %s" % (attributes_group_key,)) + + special_attribute = ["CITY", "METAREAD"] + + # load view generate from attributes_group_anonymize function + attributes_group_index = self.attributes_group.recode_group_index[attributes_group_key] + view = self.attributes_group_views_dict[attributes_group_key] + + if attributes_group_key not in special_attribute: + view.non_negativity() + + # find the significant value + significant_cell_indices = np.where(view.count >= threshold)[0] + group_cell_indices = np.where(np.logical_and(view.count < threshold, view.count > 0))[0] + + # keep the top-k values + num_keep_value = 14 + int(math.floor(self.epsilon * 5)) + + if significant_cell_indices.size > num_keep_value: + # if attributes_group_key not in ["CITYPOP"]: + sort_index = np.argsort(view.count) + keep_indices = sort_index[-num_keep_value:] + group_cell_indices = np.union1d(group_cell_indices, np.setdiff1d(significant_cell_indices, keep_indices)) + significant_cell_indices = keep_indices + + # encode the cells with values above threshold + encode_records = np.matmul(self.records[:, attributes_group_index], view.encode_num) + new_record = np.zeros(self.records.shape[0], dtype=np.uint32) + significant_records_indices = np.zeros(0) + + for index, value in enumerate(significant_cell_indices): + indices = np.where(encode_records == value)[0] + new_record[indices] = index + significant_records_indices = np.union1d(significant_records_indices, indices) + + # encode the cells with values below threshold + remain_indices = np.setdiff1d(np.arange(self.records.shape[0]), significant_records_indices) + num_group = np.sum(view.count[group_cell_indices]) + significant_value = view.count[significant_cell_indices] + significant_ratio = significant_value / np.sum(significant_value) + significant_ratio_cumsum = np.cumsum(significant_ratio) + group_value_threshold = 0 + + # remain the grouped value if their number exceed some threshold + if num_group > group_value_threshold and attributes_group_key not in special_attribute: + new_record[remain_indices] = significant_cell_indices.size + else: + group_cell_indices = np.zeros(0) + significant_value += significant_ratio * remain_indices.size + + start = 0 + + for index, ratio in enumerate(significant_ratio_cumsum): + end = int(round(len(remain_indices) * ratio)) + new_record[remain_indices[start: end]] = index + start = end + + ################################# update global parameters ##################################### + if num_group > group_value_threshold and attributes_group_key not in special_attribute: + num_categories = significant_cell_indices.size + 1 + else: + num_categories = significant_cell_indices.size + + if num_categories == 0: + self.num_categories[attributes_group_index[0]] = num_categories + + self.logger.info("maximum remain: %s, actual remain: %s" % (num_keep_value, num_categories)) + else: + # update records and num_categories + self.num_categories[attributes_group_index[0]] = num_categories + self.records[:, attributes_group_index[0]] = new_record + + # update decode related parameters + if recode_times == 0: + self.recode_significant_indices_dict[attributes_group_key] = significant_cell_indices + self.recode_group_indices_dict[attributes_group_key] = group_cell_indices + self.recode_views_dict[attributes_group_key] = view + + # construct view to synthesize dataset + new_indicator = np.zeros(len(self.num_categories), dtype=np.uint8) + new_indicator[attributes_group_index[0]] = 1 + new_view = View(new_indicator, self.num_categories) + + if num_group > group_value_threshold and attributes_group_key not in special_attribute: + new_view.count = np.concatenate((significant_value, np.array([np.sum(view.count[group_cell_indices])]))) + else: + new_view.count = significant_value + + # update synthesizing related parameters + for view_group_name in ["A", "AGE1", "AGE2", "SL"]: + if attributes_group_key in self.attributes_group.recode_group_key[view_group_name] \ + or attributes_group_key in self.attributes_group.recode_single_key[view_group_name]: + self.views_group[view_group_name][attributes_group_key] = new_view + self.views_iterate_key[view_group_name].append(attributes_group_key) + self.views_consist_key[view_group_name].append(attributes_group_key) + + break + + self.logger.info("maximum remain: %s, actual remain: %s" % (num_keep_value, num_categories)) \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_composition/advanced_composition.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_composition/advanced_composition.py new file mode 100644 index 0000000..0751cf8 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_composition/advanced_composition.py @@ -0,0 +1,31 @@ +import numpy as np + + +class AdvancedComposition: + def __init__(self): + pass + + def my_minimize(self, func, l, h): + vfunc = np.vectorize(func) + cur_l, cur_h = l, h + n = 20000 + for i in range(10): + xs = np.linspace(cur_l, cur_h, n) + vs = vfunc(xs) + vs_index = np.argsort(vs) + cur_l_index, cur_h_index = vs_index[0], vs_index[1] + cur_l, cur_h = xs[cur_l_index], xs[cur_h_index] + + return (cur_l + cur_h) / 2 + + def gauss_renyi(self, epsilon, delta, sensitivity, k): + def renyi(low): + epsilon0 = max(1e-20, epsilon - np.log(1.0 / delta) * 1.0 / (low - 1)) + sigma = np.sqrt(k * low * sensitivity * 1.0 / 2 / epsilon0) + return sigma + + l, h = 1.00001, 100000 + min_low = self.my_minimize(renyi, l, h) + min_sigma = renyi(min_low) + + return min_sigma diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_dpsyn/records_update.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_dpsyn/records_update.py new file mode 100644 index 0000000..34a9b0f --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_dpsyn/records_update.py @@ -0,0 +1,287 @@ +import logging +import copy + +import numpy as np + +import utility + + +class RecordUpdate: + def __init__(self, num_categories, original_num_categories): + self.logger = logging.getLogger("record_update") + + self.num_categories = num_categories + self.original_num_categories = original_num_categories + + def initialize_records(self, num_records, num_attributes, num_views, max_iteration): + self.records = np.empty([num_records, num_attributes], dtype=np.uint32) + + for j in range(num_attributes): + if self.num_categories[j] != 0: + self.records[:, j] = np.random.randint(0, self.num_categories[j], size=(num_records)) + else: + self.records[:, j] = np.random.randint(0, self.original_num_categories[j], size=(num_records)) + + self.error_tracker = np.zeros([num_views, max_iteration * 2]) + + def update_records_S5(self, original_view, alpha, threshold, view_index, iteration): + view = copy.deepcopy(original_view) + l1_error = self.update_records_before(view, view_index, iteration) + + if l1_error > threshold: + self.update_records_main(view, alpha) + self.determine_throw_indices() + self.handle_zero_cells(view) + + if iteration % 2 == 0: + self.complete_partial_ratio(view, 0.5) + else: + self.complete_partial_ratio(view, 1.0) + + self.update_records_after(view, view_index, iteration) + + return 1 + else: + return 0 + + def update_records_main(self, view, alpha): + # deal with the cell that synthesize_marginal != 0 and synthesize_marginal < actual_marginal + self.cell_under_indices = np.where((self.synthesize_marginal < self.actual_marginal) & (self.synthesize_marginal != 0))[0] + + ratio_add_full = (self.actual_marginal[self.cell_under_indices] - self.synthesize_marginal[self.cell_under_indices]) \ + / self.synthesize_marginal[self.cell_under_indices] + self.ratio_add = np.minimum(ratio_add_full, np.full(self.cell_under_indices.shape[0], alpha)) + + num_add = np.sum(self.ratio_add * (self.synthesize_marginal[self.cell_under_indices] * self.records.shape[0])) + + # deal with the case synthesize_marginal == 0 and actual_marginal != 0 + self.cell_zero_indices = np.where((self.synthesize_marginal == 0) & (self.actual_marginal != 0))[0] + self.num_add_zero = self.actual_marginal[self.cell_zero_indices] * alpha * self.records.shape[0] + num_add += np.sum(self.num_add_zero) + + # determine the number of records to be removed + self.cell_over_indices = np.where(self.synthesize_marginal > self.actual_marginal)[0] + beta = self.find_optimal_beta(num_add, self.cell_over_indices) + ratio_reduce = np.minimum( + (self.synthesize_marginal[self.cell_over_indices] - self.actual_marginal[self.cell_over_indices]) + / self.synthesize_marginal[self.cell_over_indices], np.full(self.cell_over_indices.shape[0], beta)) + self.cell_num_reduce = np.rint(ratio_reduce * (self.synthesize_marginal[self.cell_over_indices] * self.records.shape[0])).astype(int) + + self.encode_records = np.matmul(self.records[:, view.attributes_index], view.encode_num) + self.encode_records_sort_index = np.argsort(self.encode_records) + self.encode_records = self.encode_records[self.encode_records_sort_index] + + valid_indices = np.nonzero(self.cell_num_reduce)[0] + self.valid_cell_over_indices = self.cell_over_indices[valid_indices] + self.valid_cell_num_reduce = self.cell_num_reduce[valid_indices] + self.valid_data_over_index_left = np.searchsorted(self.encode_records, self.valid_cell_over_indices, side="left") + self.valid_data_over_index_right = np.searchsorted(self.encode_records, self.valid_cell_over_indices, side="right") + + def determine_throw_indices(self): + num_reduce = np.sum(self.valid_cell_num_reduce) + self.records_throw_indices = np.zeros(num_reduce, dtype=np.uint32) + throw_pointer = 0 + + for i, cell_index in enumerate(self.valid_cell_over_indices): + match_records_indices = self.encode_records_sort_index[self.valid_data_over_index_left[i]: self.valid_data_over_index_right[i]] + throw_indices = np.random.choice(match_records_indices, self.valid_cell_num_reduce[i], replace=False) + + self.records_throw_indices[throw_pointer: throw_pointer + throw_indices.size] = throw_indices + throw_pointer += throw_indices.size + + np.random.shuffle(self.records_throw_indices) + + def complete_partial_ratio(self, view, complete_ratio): + num_complete = np.rint((self.ratio_add * complete_ratio) * self.synthesize_marginal[self.cell_under_indices] * self.records.shape[0]).astype(int) + num_partial = np.rint((self.ratio_add * (1 - complete_ratio)) * self.synthesize_marginal[self.cell_under_indices] * self.records.shape[0]).astype(int) + + valid_indices = np.nonzero(num_complete + num_partial) + num_complete = num_complete[valid_indices] + num_partial = num_partial[valid_indices] + valid_cell_under_indices = self.cell_under_indices[valid_indices] + valid_data_under_index_left = np.searchsorted(self.encode_records, valid_cell_under_indices, side="left") + valid_data_under_index_right = np.searchsorted(self.encode_records, valid_cell_under_indices, side="right") + + for index, cell_index in enumerate(valid_cell_under_indices): + match_records_indices = self.encode_records_sort_index[valid_data_under_index_left[index]: valid_data_under_index_right[index]] + + if self.records_throw_indices.shape[0] >= (num_complete[index] + num_partial[index]): + # complete code + if num_complete[index] != 0: + self.records[self.records_throw_indices[: num_complete[index]]] = self.records[match_records_indices[: num_complete[index]]] + + # partial code + if num_partial[index] != 0: + for i in range(view.ways): + # self.records[self.records_throw_indices[num_complete[index]: (num_complete[index] + num_partial[index])], + # view.attributes_index[i]] = view.tuple_key[cell_index, i] + self.records[np.ix_(self.records_throw_indices[num_complete[index]: (num_complete[index] + num_partial[index])], + view.attributes_index)] = view.tuple_key[cell_index] + + self.records_throw_indices = self.records_throw_indices[num_complete[index] + num_partial[index]:] + + else: + self.records[self.records_throw_indices] = self.records[match_records_indices[: self.records_throw_indices.size]] + + def handle_zero_cells(self, view): + # overwrite / partial when synthesize_marginal == 0 + if self.cell_zero_indices.size != 0: + for index, cell_index in enumerate(self.cell_zero_indices): + num_partial = int(round(self.num_add_zero[index])) + + if num_partial != 0: + for i in range(view.ways): + self.records[self.records_throw_indices[: num_partial], view.attributes_index[i]] = \ + view.tuple_key[cell_index, i] + + self.records_throw_indices = self.records_throw_indices[num_partial:] + + def find_optimal_beta(self, num_add, cell_over_indices): + actual_marginal_under = self.actual_marginal[cell_over_indices] + synthesize_marginal_under = self.synthesize_marginal[cell_over_indices] + + lower_bound = 0.0 + upper_bound = 1.0 + beta = 0.0 + current_num = 0.0 + + while abs(num_add - current_num) > 10.0: + beta = (upper_bound + lower_bound) / 2.0 + current_num = np.sum( + np.minimum((synthesize_marginal_under - actual_marginal_under) / synthesize_marginal_under, + np.full(cell_over_indices.shape[0], beta)) * synthesize_marginal_under * self.records.shape[0]) + + if current_num < num_add: + lower_bound = beta + elif current_num > num_add: + upper_bound = beta + else: + return beta + + return beta + + def update_records_before(self, view, view_index, iteration): + self.actual_marginal = view.normalize_count + self.synthesize_marginal = self.synthesize_marginal_distribution(view) + + l1_error = utility.l1_distance(self.actual_marginal, self.synthesize_marginal) + self.error_tracker[view_index, 2 * iteration] = l1_error + + self.logger.info("the l1 error before updating is %s" % (l1_error,)) + + return l1_error + + def update_records_after(self, view, view_index, iteration): + self.synthesize_marginal = self.synthesize_marginal_distribution(view) + + l1_error = utility.l1_distance(self.actual_marginal, self.synthesize_marginal) + self.error_tracker[view_index, 2 * iteration + 1] = l1_error + + self.logger.info("the l1 error after updating is %s" % (l1_error,)) + + if iteration == 1: + np.random.shuffle(self.records) + + def synthesize_marginal_distribution(self, view): + count = view.count_records_general(self.records) + # count_matrix = view.calculate_count_matrix_general(count) + + return view.calculate_normalize_count_general(count) + + # based on minimum cost flow, all overwrite + def update_records_fully(self, original_view, threshold, view_index, iteration): + view = copy.deepcopy(original_view) + l1_error = self.update_records_before(view, view_index, iteration) + + if l1_error > threshold: + self.marginal_reallocation = self.determine_marginal_reallocation() + + # encode the synthesized records + encode_records = np.matmul(self.records[:, view.attributes_index], view.encode_num) + + for key_index in range(view.num_key): + # get the indices corresponding to each key + key_index_list = np.where(encode_records == key_index)[0] + nonzero_index = np.nonzero(self.marginal_reallocation[key_index])[0] + cum_sum = np.cumsum(self.marginal_reallocation[key_index, nonzero_index]) + start = 0 + + for index, value in enumerate(cum_sum): + end = int(round(key_index_list.shape[0] * value)) + + indices = key_index_list[start: end] + start = end + + for i in range(view.ways): + self.records[indices, view.attributes_index[i]] = view.tuple_key[nonzero_index[index], i] + + self.update_records_after(view, view_index, iteration) + + return 1 + else: + return 0 + + def determine_marginal_reallocation(self): + marginal_reallocation = self.minimum_cost_flow_problem_intuitive() + + summation = np.sum(marginal_reallocation, axis=1) + + nonzero_index = np.nonzero(summation)[0] + + marginal_reallocation[nonzero_index] = (marginal_reallocation[nonzero_index].T / summation[nonzero_index]).T + + return marginal_reallocation + + def minimum_cost_flow_problem_intuitive(self): + actual_distribution = copy.deepcopy(self.actual_marginal) + synthesize_distribution = copy.deepcopy(self.synthesize_marginal) + + num_cells = len(actual_distribution) + marginal_reallocation = np.zeros([num_cells, num_cells]) + + minimum_value = np.minimum(actual_distribution, synthesize_distribution) + + actual_distribution -= minimum_value + synthesize_distribution -= minimum_value + + self.actual_cumulative(marginal_reallocation, actual_distribution, synthesize_distribution) + + marginal_reallocation[[i for i in range(num_cells)], [j for j in range(num_cells)]] = minimum_value + + return marginal_reallocation + + def actual_cumulative(self, marginal_reallocation, actual_distribution, synthesize_distribution): + while (not np.sum(synthesize_distribution) == 0) and (not np.sum(actual_distribution) == 0): + nonzero_index = np.nonzero(synthesize_distribution)[0][0] + nonzero_value = synthesize_distribution[nonzero_index] + + actual_cum_sum = np.cumsum(actual_distribution) + result_index = np.nonzero((actual_cum_sum <= nonzero_value) & (actual_cum_sum > 0.0))[0] + try: + next_index = np.nonzero(actual_cum_sum > nonzero_value)[0][0] + except: + next_index = np.nonzero((actual_cum_sum <= nonzero_value) & (actual_cum_sum > 0.0))[0][0] + + if result_index.size == 0: + marginal_reallocation[nonzero_index, next_index] = nonzero_value + synthesize_distribution[nonzero_index] = 0.0 + actual_distribution[next_index] -= nonzero_value + else: + marginal_reallocation[nonzero_index, result_index] = actual_distribution[result_index] + synthesize_distribution[nonzero_index] -= np.sum(actual_distribution[result_index]) + actual_distribution[result_index] = 0.0 + + if result_index[-1] > next_index: + synthesize_distribution[nonzero_index] = 0.0 + elif actual_cum_sum[result_index[-1]] < nonzero_value: + marginal_reallocation[nonzero_index, next_index] = nonzero_value - actual_cum_sum[ + result_index[-1]] + synthesize_distribution[nonzero_index] = 0.0 + actual_distribution[next_index] = actual_cum_sum[next_index] - nonzero_value + + +if __name__ == "__main__": + update = RecordUpdate(None) + + update.synthesize_marginal = np.array([0.4, 0.6]) + update.actual_marginal = np.array([0.6, 0.4]) \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/consistent.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/consistent.py new file mode 100644 index 0000000..e2aae04 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/consistent.py @@ -0,0 +1,151 @@ +import logging +import copy + +import numpy as np + +from lib_view.view import View + + +class Consistenter: + class SubsetWithDependency: + def __init__(self, attributes_set): + # a list of categories + self.attributes_set = attributes_set + # a set of tuples this object depends on + self.dependency = set() + + def __init__(self, views, num_categories, consist_parameters): + self.logger = logging.getLogger("Consistenter") + + self.views = views + self.num_categories = num_categories + self.iterations = consist_parameters["consist_iterations"] + + def compute_dependency(self): + subsets_with_dependency = {} + ret_subsets = {} + + for key, view in self.views.items(): + new_subset = self.SubsetWithDependency(view.attributes_set) + subsets_temp = copy.deepcopy(subsets_with_dependency) + + for subset_key, subset_value in subsets_temp.items(): + attributes_intersection = subset_value.attributes_set & view.attributes_set + + if attributes_intersection: + if tuple(attributes_intersection) not in subsets_with_dependency: + intersection_subset = self.SubsetWithDependency(attributes_intersection) + subsets_with_dependency[tuple(attributes_intersection)] = intersection_subset + + if not tuple(attributes_intersection) == subset_key: + subsets_with_dependency[subset_key].dependency.add(tuple(attributes_intersection)) + new_subset.dependency.add(tuple(attributes_intersection)) + + subsets_with_dependency[tuple(view.attributes_set)] = new_subset + + for subset_key, subset_value in subsets_with_dependency.items(): + if len(subset_key) == 1: + subset_value.dependency = set() + + ret_subsets[subset_key] = subset_value + + return subsets_with_dependency + + def consist_views(self): + def find_subset_without_dependency(): + for key, subset in subsets_with_dependency_temp.items(): + if not subset.dependency: + return key, subset + + return None, None + + def find_views_containing_target(target): + result = [] + + for key, view in self.views.items(): + if target <= view.attributes_set: + result.append(view) + + return result + + # currentStrategy: if two views do not agree on the levels: v1: 4*2, v2: 2*4, then consist on 2*2 + def consist_on_subset(target): + target_views = find_views_containing_target(target) + + common_view_indicator = np.zeros(self.num_categories.shape[0]) + for index in target: + common_view_indicator[index] = 1 + + common_view = View(common_view_indicator, self.num_categories) + common_view.initialize_consist_parameters(len(target_views)) + + for index, view in enumerate(target_views): + common_view.project_from_bigger_view(view, index) + + common_view.calculate_delta() + + for index, view in enumerate(target_views): + view.update_view(common_view, index) + + def remove_subset_from_dependency(target): + for _, subset in subsets_with_dependency_temp.items(): + if tuple(target.attributes_set) in subset.dependency: + subset.dependency.remove(tuple(target.attributes_set)) + + # calculate necessary variables + for key, view in self.views.items(): + view.calculate_tuple_key() + view.generate_attributes_index_set() + view.get_sum() + + # calculate the dependency relationship + subsets_with_dependency = self.compute_dependency() + self.logger.debug("dependency computed") + + # ripple steps needs several iterations + # for i in range(self.iterations): + non_negativity = True + iterations = 0 + + while non_negativity and iterations < self.iterations: + # first make sure summation are the same + consist_on_subset(set()) + + for key, view in self.views.items(): + view.get_sum() + + subsets_with_dependency_temp = copy.deepcopy(subsets_with_dependency) + + while len(subsets_with_dependency_temp) > 0: + key, subset = find_subset_without_dependency() + + if not subset: + break + + consist_on_subset(subset.attributes_set) + remove_subset_from_dependency(subset) + subsets_with_dependency_temp.pop(key, None) + + self.logger.debug("consist finish") + + views_count = 0 + + for key, view in self.views.items(): + if (view.count < 0.0).any(): + view.non_negativity() + view.get_sum() + else: + views_count += 1 + + if views_count == len(self.views): + self.logger.info("finish in %s round" % (iterations,)) + non_negativity = False + + iterations += 1 + + self.logger.debug("non-negativity finish") + + # calculate normalized count + for key, view in self.views.items(): + view.calculate_normalize_count() + view.get_sum() diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/non_negativity.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/non_negativity.py new file mode 100644 index 0000000..3f405d3 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/non_negativity.py @@ -0,0 +1,39 @@ +import numpy as np + + +class NonNegativity: + def __init__(self, count): + self.count = np.copy(count) + + def norm_cut(self): + # set all negative value to 0.0 + negative_indices = np.where(self.count < 0.0)[0] + negative_total = abs(np.sum(self.count[negative_indices])) + self.count[negative_indices] = 0.0 + + # find all positive value and sort them in ascending order + positive_indices = np.where(self.count > 0.0)[0] + + if positive_indices.size != 0: + positive_sort_indices = np.argsort(self.count[positive_indices]) + sort_cumsum = np.cumsum(self.count[positive_indices[positive_sort_indices]]) + + # set the smallest positive value to 0.0 to preserve the total density + threshold_indices = np.where(sort_cumsum <= negative_total)[0] + + if threshold_indices.size == 0: + self.count[positive_indices[positive_sort_indices[0]]] = sort_cumsum[0] - negative_total + else: + self.count[positive_indices[positive_sort_indices[threshold_indices]]] = 0.0 + next_index = threshold_indices[-1] + 1 + + # self.count[positive_indices[positive_sort_indices[next_index]]] = sort_cumsum[next_index] - negative_total + + if next_index < positive_sort_indices.size: + self.count[positive_indices[positive_sort_indices[next_index]]] = sort_cumsum[next_index] - negative_total + # else: + # self.count[positive_indices[positive_sort_indices[-1]]] = sort_cumsum[-1] - negative_total + else: + self.count[:] = 0.0 + + return self.count diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/view.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/view.py new file mode 100644 index 0000000..ad7080d --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/lib_view/view.py @@ -0,0 +1,194 @@ +import numpy as np + +from lib_view.non_negativity import NonNegativity + + +class View: + def __init__(self, indicator, num_categories): + self.indicator = indicator + self.num_categories = num_categories + + self.num_key = np.product(self.num_categories[np.nonzero(self.indicator)[0]]) + self.num_attributes = self.indicator.shape[0] + self.ways = np.count_nonzero(self.indicator) + + self.encode_num = np.zeros(self.ways, dtype=np.uint32) + self.cum_mul = np.zeros(self.ways, dtype=np.uint32) + self.attributes_index = np.nonzero(self.indicator)[0] + + self.count = np.zeros(self.num_key) + + self.calculate_encode_num(self.num_categories) + + ########################################### general functions #################################### + def calculate_encode_num(self, num_categories): + if self.ways != 0: + categories_index = self.attributes_index + + categories_num = num_categories[categories_index] + categories_num = np.roll(categories_num, 1) + categories_num[0] = 1 + self.cum_mul = np.cumprod(categories_num) + + categories_num = num_categories[categories_index] + categories_num = np.roll(categories_num, self.ways - 1) + categories_num[-1] = 1 + categories_num = np.flip(categories_num) + self.encode_num = np.flip(np.cumprod(categories_num)) + + def calculate_tuple_key(self): + self.tuple_key = np.zeros([self.num_key, self.ways], dtype=np.uint32) + + if self.ways != 0: + for i in range(self.attributes_index.shape[0]): + index = self.attributes_index[i] + categories = np.arange(self.num_categories[index]) + column_key = np.tile(np.repeat(categories, self.encode_num[i]), self.cum_mul[i]) + + self.tuple_key[:, i] = column_key + else: + self.tuple_key = np.array([0], dtype=np.uint32) + self.num_key = 1 + + def count_records(self, records): + encode_records = np.matmul(records[:, self.attributes_index], self.encode_num) + encode_key, count = np.unique(encode_records, return_counts=True) + + indices = np.where(np.isin(np.arange(self.num_key), encode_key))[0] + self.count[indices] = count + + def calculate_normalize_count(self): + if np.sum(self.count) == 0: + self.normalize_count = self.count + else: + self.normalize_count = self.count / np.sum(self.count) + + return self.normalize_count + + def calculate_count_matrix(self): + shape = [] + + for attri in self.attributes_index: + shape.append(self.num_categories[attri]) + + self.count_matrix = np.copy(self.count).reshape(tuple(shape)) + + return self.count_matrix + + def reserve_original_count(self): + self.original_count = self.count + + def get_sum(self): + self.sum = np.sum(self.count) + + def generate_attributes_index_set(self): + self.attributes_set = set(self.attributes_index) + + ################################### functions for outside invoke ######################### + def calculate_encode_num_general(self, attributes_index): + categories_index = attributes_index + + categories_num = self.num_categories[categories_index] + categories_num = np.roll(categories_num, attributes_index.size - 1) + categories_num[-1] = 1 + categories_num = np.flip(categories_num) + encode_num = np.flip(np.cumprod(categories_num)) + + return encode_num + + def count_records_general(self, records): + count = np.zeros(self.num_key) + + encode_records = np.matmul(records[:, self.attributes_index], self.encode_num) + encode_key, value_count = np.unique(encode_records, return_counts=True) + + indices = np.where(np.isin(np.arange(self.num_key), encode_key))[0] + count[indices] = value_count + + return count + + def calculate_normalize_count_general(self, count): + return count / np.sum(count) + + def calculate_count_matrix_general(self, count): + shape = [] + + for attri in self.attributes_index: + shape.append(self.num_categories[attri]) + + return np.copy(count).reshape(tuple(shape)) + + def calculate_tuple_key_general(self, unique_value_list): + self.tuple_key = np.zeros([self.num_key, self.ways], dtype=np.uint32) + + if self.ways != 0: + for i in range(self.attributes_index.shape[0]): + categories = unique_value_list[i] + column_key = np.tile(np.repeat(categories, self.encode_num[i]), self.cum_mul[i]) + + self.tuple_key[:, i] = column_key + else: + self.tuple_key = np.array([0], dtype=np.uint32) + self.num_key = 1 + + def project_from_bigger_view_general(self, bigger_view): + encode_num = np.zeros(self.num_attributes, dtype=np.uint32) + encode_num[self.attributes_index] = self.encode_num + encode_num = encode_num[bigger_view.attributes_index] + + encode_records = np.matmul(bigger_view.tuple_key, encode_num) + + for i in range(self.num_key): + key_index = np.where(encode_records == i)[0] + self.count[i] = np.sum(bigger_view.count[key_index]) + + ######################################## functions for consistency ####################### + ############ used in commom view ############# + def initialize_consist_parameters(self, num_target_views): + self.summations = np.zeros([self.num_key, num_target_views]) + self.weights = np.zeros(num_target_views) + + def calculate_delta(self): + target = np.matmul(self.summations, self.weights) / np.sum(self.weights) + self.delta = - (self.summations.T - target).T * self.weights + + def project_from_bigger_view(self, bigger_view, index): + encode_num = np.zeros(self.num_attributes, dtype=np.uint32) + encode_num[self.attributes_index] = self.encode_num + encode_num = encode_num[bigger_view.attributes_index] + + encode_records = np.matmul(bigger_view.tuple_key, encode_num) + + self.weights[index] = 1.0 / np.product(self.num_categories[np.setdiff1d(bigger_view.attributes_index, self.attributes_index)]) + + for i in range(self.num_key): + key_index = np.where(encode_records == i)[0] + self.summations[i, index] = np.sum(bigger_view.count[key_index]) + + ############### used in views to be consisted ############### + def update_view(self, common_view, index): + encode_num = np.zeros(self.num_attributes, dtype=np.uint32) + encode_num[common_view.attributes_index] = common_view.encode_num + encode_num = encode_num[self.attributes_index] + + encode_records = np.matmul(self.tuple_key, encode_num) + + for i in range(common_view.num_key): + key_index = np.where(encode_records == i)[0] + self.count[key_index] += common_view.delta[i, index] + + ######################################### non-negative functions #################################### + def non_negativity(self): + non_negativity = NonNegativity(self.count) + + self.count = non_negativity.norm_cut() + + +if __name__ == "__main__": + view = View([1, 1, 0, 0], [3, 3, 0, 0]) + + + + + + diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/load_csv.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/load_csv.py new file mode 100644 index 0000000..747a87c --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/load_csv.py @@ -0,0 +1,84 @@ +import logging +import pickle +import csv +import json + +import numpy as np + +import config + + +class LoadCSV: + def __init__(self): + self.logger = logging.getLogger("load_csv") + self.attri_name_index_mapping = {} + + def load_original_records(self, dataset_path): + self.logger.info("loading raw data") + + self.encoded_records = np.genfromtxt(dataset_path, delimiter=",", skip_header=1, filling_values=0.0) + + if len(self.encoded_records.shape) == 1: + self.encoded_records = self.encoded_records.reshape((1, self.encoded_records.size)) + + self.logger.info("loaded %s records" % (self.encoded_records.shape[0])) + + def load_dataset_parameters(self, dataset_path, json_path): + self.logger.info("loading data parameters") + + # read dataset header and spec file + dataset = csv.reader(open(dataset_path, newline=''), dialect='excel') + self.dataset_header = next(dataset) + self.dataset_spec = json.load(open(json_path)) + + for index, attribute_name in enumerate(self.dataset_header): + self.attri_name_index_mapping[attribute_name] = index + + # read code mapping + self.code_mapping = pickle.load(open(config.MAPPING_PATH + "code_mapping", "rb")) + + self.logger.info("loaded data parameters") + + def calculate_num_categories(self): + self.logger.info("calculating num_categories") + + self.num_categories = np.zeros(self.encoded_records.shape[1], dtype=np.int64) + + for index, attribute_name in enumerate(self.dataset_header): + maxval = self.dataset_spec[attribute_name]["maxval"] + + if attribute_name in self.code_mapping: + maxval_index = np.where(self.code_mapping[attribute_name] == maxval)[0] + + if maxval_index.size == 0: + self.num_categories[index] = maxval + 1 + self.code_mapping[attribute_name] = np.arange(maxval + 1, dtype=np.uint32) + else: + self.num_categories[index] = maxval_index[0] + 1 + else: + self.num_categories[index] = maxval + 1 + + self.logger.info("calculated num_categories") + + def transform_records_to_num(self): + self.logger.info("transforming the records") + + self.records = np.zeros([self.encoded_records.shape[0], self.encoded_records.shape[1]], dtype=np.uint32) + + for index, attribute_name in enumerate(self.dataset_header): + self.logger.info("transforming attribute %s: %s" % (attribute_name, index)) + + # if attribute_name in self.code_mapping and attribute_name not in self.inconsistent_attributes: + if attribute_name in self.code_mapping: + records = self.encoded_records[:, index] + unique_value, count = np.unique(records, return_counts=True) + + for value in unique_value: + code_mapping_index = np.where(self.code_mapping[attribute_name] == value)[0] + self.records[records == value, index] = code_mapping_index + else: + self.records[:, index] = self.encoded_records[:, index] + + self.logger.info("transformed the records") + + \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/main.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/main.py new file mode 100644 index 0000000..0e4949d --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/main.py @@ -0,0 +1,75 @@ +import logging +import argparse + +from experiment.experiment_C5_1 import Experiment_C5_1 +from experiment.experiment_C5_2 import Experiment_C5_2 + + +def config_logger(): + # create logger + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + + # create console handler + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + + # create formatter and add it to the handlers + formatter = logging.Formatter('%(levelname)s:%(asctime)s: - %(name)s - : %(message)s') + ch.setFormatter(formatter) + + # add the handlers to the logger + logger.addHandler(ch) + + +def main(path, anonymization, synthesizer): + if anonymization["epsilon"] <= 0.2: + Experiment_C5_1(path, anonymization, synthesizer) + elif anonymization["epsilon"] > 0.2: + Experiment_C5_2(path, anonymization, synthesizer) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + ################################### interface to final scoring ################################## + parser.add_argument("input_path", type=str, help="specify the path of data file in csv formate") + parser.add_argument("output_path", type=str, help="specify the output path of the synthetic data") + parser.add_argument("specs_path", type=str, help="specify the path of specs file in json formate") + parser.add_argument("epsilon", type=float, help="specify the total privacy budget") + + ####################################### general parameters ################################### + # specify privacy parameters + parser.add_argument('--delta', type=float, default=1.0 / (662000 ** 2)) + parser.add_argument('--sensitivity', type=float, default=1.0) + + # parameters for views generation + parser.add_argument('--consist_iterations', type=int, default=100, + help="specify the number of iterations for consist procedure") + + # parameters for dpsyn_mcf method + parser.add_argument('--synthesizer_num_records', type=int, default=662000, + help="specify the number records to be generated") + parser.add_argument('--update_iterations', type=int, default=100, + help="specify the number of iterations for synthesizing procedure") + + args = parser.parse_args() + config_logger() + + path = { + "input_path": args.input_path, + "output_path": args.output_path, + "specs_path": args.specs_path, + } + anonymization = { + "epsilon": args.epsilon, + "delta": args.delta, + "sensitivity": args.sensitivity + } + synthesizer = { + "num_records": args.synthesizer_num_records, + "update_iterations": args.update_iterations, + "consist_iterations": args.consist_iterations, + } + + main(path, anonymization, synthesizer) \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/temp_data/mapping/code_mapping b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/temp_data/mapping/code_mapping new file mode 100644 index 0000000000000000000000000000000000000000..15861fd957c046f1c0645e3d25817b58e7b0cc92 GIT binary patch literal 38321 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 zCSWRNVG&kg@Differential Privacy Synthetic Data Challenge Algorithms + +De-identification Keywords: Differential Privacy, Synthetic Data Generation + +Brief Description: Participants in Match #3 of NIST's 2018 Public Safety Communications Research [Differential Privacy Synthetic Data Challenge](https://www.nist.gov/ctl/pscr/funding-opportunities/prizes-challenges/2018-differential-privacy-synthetic-data-challenge) developed these open source algorithms as part of an effort to advance differential privacy. Participants were challenged to create new methods, or improve existing methods of data de-identification, while preserving the dataset’s utility for analysis. All solutions were required to satisfy the differential privacy guarantee, a provable guarantee of individual privacy protection. Participants used a data set of emergency response events occurring in San Francisco and a sub-sample of the IPUMS USA data for the 1940 U.S. Census. + +Contributions are listed in alphabetical order. + +

DP_WGAN-UCLANESL

+Team Members: Prof. Mani Srivastava (@msrivastava) - Team Captain (Match 1 and Match 3), Moustafa Alzantot (@malzantot) - (Match 1 and Match 3), Nat Snyder (@natsnyder1) - Match 1, Supriyo Charkaborty (@supriyogit) - Match 1 + +Brief Description: This repo contains an implementation for the award-winning solution to the 2018 Differential Privacy Synthetic Data Challenge by team UCLANESL. Our solution has been awarded the 5th place in Match #3 of the challenge and an earlier version has also won the 4th place in Match #1. The solution trains a wasserstein generative adversarial network (w-GAN) that is trained on the real private dataset. Differentially private training is applied by sanitizing (norm clipping and adding Gaussian noise) the gradients of the discriminator. Once the model is trained, it can be used to generate synthetic dataset by feeding random noise into the generator. + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DP_WGAN-UCLANESL) | [Link to Tool](https://github.com/nesl/nist_differential_privacy_synthetic_data_challenge)** + +

DPFieldGroups

+Team Members & Affiliation: John Gardner (no affiliation)
+Brief Description: This is the fourth place entry in the third round of the NIST Differential Privacy Synthetic Data Challenge. The goal of this challenge is to produce differentially private synthetic data while retaining as much useful information as possible about the original data set. Colorado census data from 1940 with 98 field columns were provided for algorithm development with census data from other states used for testing. This solution groups together fields which have been found to be highly correlated. For each of these groups, a histogram is created for the purpose of counting the number of occurrences of every possible combination of values of all fields in the group. For privatization, Laplacian noise is added to every bin with scale proportional to the number of groups / total epsilon. Synthetic data is generated by selecting a random bin for each group with probability weighted by these noisy bin counts. The field values corresponding to each group's selected bin are written out as a single row of synthetic data. + + +
**[Link to Tool and More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups)** + +

DPSyn

+Team Members & Affiliations: Ninghui Li (Purdue University), Zhikun Zhang (Zhejiang University), Tianhao Wang (Purdue University) + +Brief Description: We present DPSyn, an algorithm for synthesizing microdata while satisfying differential privacy, and its instantiation to the dataset used in the competition, namely Public Use Microdata Sample (PUMS) of the 1940 USA Census Data. + +**[Link to Tool and More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn)** + +

rmckenna

+Team Member & Affiliation: Ryan McKenna (UMass Amherst) + +Brief Description: The first place entry in the third round of the NIST Differential Privacy Synthetic Data Challenge. +The high-level idea is to (1) use the Gaussian mechanism to obtain noisy answers to a carefully selected set of counting queries (1, 2, and 3 way marginals) and (2) find a synthetic data set that approximates the true data with respect to those queries. The latter step is accomplished with [3], and the previous step uses ideas inspired by [1] and [2]. More specifically, this is done by calculating the mutual information (on the public dataset) for each pair of attributes and selecting the marginal queries that have high mutual information. + +[1] Zhang, Jun, et al. "Privbayes: Private data release via bayesian networks." ACM Transactions on Database Systems (TODS) 42.4 (2017): 25. + + +[2] Chen, Rui, et al. "Differentially private high-dimensional data publication via sampling-based inference." Proceedings of the 21th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining. ACM, 2015. + + +[3] McKenna, Ryan, Daniel Sheldon, and Gerome Miklau. "Graphical-model based estimation and inference for differential privacy." Proceddings of the 36th International Conference on Machine Learning. 2019. + +**[Link to Tool and More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna)** diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/LICENSE b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/README.md b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/README.md new file mode 100644 index 0000000..2d1efb3 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/README.md @@ -0,0 +1,28 @@ +# rmckenna - Differential Privacy Synthetic Data Challenge Algorithm + +## Team Member and Affiliation: +Ryan McKenna (ryan112358; UMass Amherst) + +## Brief Description +The high-level idea is to (1) use the Gaussian mechanism to obtain noisy answers to a carefully selected set of counting queries (1, 2, and 3 way marginals) and (2) find a synthetic data set that approximates the true data with respect to those queries. The latter step is accomplished with [3], and the previous step uses ideas inspired by [1] and [2]. More specifically, this is done by calculating the mutual information (on the public dataset) for each pair of attributes and selecting the marginal queries that have high mutual information. + +[1] Zhang, Jun, et al. "Privbayes: Private data release via bayesian networks." ACM Transactions on Database Systems (TODS) 42.4 (2017): 25. + +[2] Chen, Rui, et al. "Differentially private high-dimensional data publication via sampling-based inference." Proceedings of the 21th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining. ACM, 2015. + +[3] McKenna, Ryan, Daniel Sheldon, and Gerome Miklau. "Graphical-model based estimation and inference for differential privacy." Proceddings of the 36th International Conference on Machine Learning. 2019. + +## Setup: +First make sure all the necessary python dependencies are installed. These are numpy, scipy, pandas, and networkx. Then clone these repositories and follow the necessary setup instructions: + +[1] https://github.com/ryan112358/private-pgm + +[2] https://github.com/tensorflow/privacy + + +## Running the code: +To generate synthetic data, run the following code: + +$ python match3.py --dataset colorado.csv --specs colorado-specs.json --epsilon 1.0 --delta 2.2820544e-12 --save synthetic-1.0.csv + +The file assumes the data has the same columns as the provisional dataset (colorado.csv), but the possible values for each column may be different. As per the match 3 rules, the set of unique values for geographic attributes is directly calculated on the input dataset, although in reality it should be provided externally as public information. The mechanism may be time consuming, requiring a few hours to complete, depending on the machine it is being run on. diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/counties.txt b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/counties.txt new file mode 100644 index 0000000..7ad13aa --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/counties.txt @@ -0,0 +1,3260 @@ +State STATEICP STATEFIPS County cod County + +Alabama 41 1 10 Autauga +Alabama 41 1 30 Baldwin +Alabama 41 1 50 Barbour +Alabama 41 1 70 Bibb +Alabama 41 1 90 Blount +Alabama 41 1 110 Bullock +Alabama 41 1 130 Butler +Alabama 41 1 150 Calhoun/Benton +Alabama 41 1 170 Chambers +Alabama 41 1 190 Cherokee +Alabama 41 1 210 Chilton/Baker +Alabama 41 1 230 Choctaw +Alabama 41 1 250 Clarke +Alabama 41 1 270 Clay +Alabama 41 1 290 Cleburne +Alabama 41 1 310 Coffee +Alabama 41 1 330 Colbert +Alabama 41 1 350 Conecuh +Alabama 41 1 370 Coosa +Alabama 41 1 390 Covington +Alabama 41 1 410 Crenshaw +Alabama 41 1 430 Cullman +Alabama 41 1 450 Dale +Alabama 41 1 470 Dallas +Alabama 41 1 490 DeKalb +Alabama 41 1 510 Elmore +Alabama 41 1 530 Escambia +Alabama 41 1 550 Etowah +Alabama 41 1 570 Fayette +Alabama 41 1 590 Franklin +Alabama 41 1 610 Geneva +Alabama 41 1 630 Greene +Alabama 41 1 650 Hale +Alabama 41 1 670 Henry +Alabama 41 1 690 Houston +Alabama 41 1 710 Jackson +Alabama 41 1 730 Jefferson +Alabama 41 1 750 Lamar/Sanford +Alabama 41 1 770 Lauderdale +Alabama 41 1 790 Lawrence +Alabama 41 1 810 Lee +Alabama 41 1 830 Limestone +Alabama 41 1 850 Lowndes +Alabama 41 1 870 Macon +Alabama 41 1 890 Madison +Alabama 41 1 910 Marengo +Alabama 41 1 930 Marion +Alabama 41 1 950 Marshall +Alabama 41 1 970 Mobile +Alabama 41 1 990 Monroe +Alabama 41 1 1010 Montgomery +Alabama 41 1 1030 Morgan/Cotaco +Alabama 41 1 1050 Perry +Alabama 41 1 1070 Pickens +Alabama 41 1 1090 Pike +Alabama 41 1 1110 Randolph +Alabama 41 1 1130 Russell +Alabama 41 1 1150 St Clair +Alabama 41 1 1170 Shelby +Alabama 41 1 1190 Sumter +Alabama 41 1 1210 Talladega +Alabama 41 1 1230 Tallapoosa +Alabama 41 1 1250 Tuscaloosa +Alabama 41 1 1270 Walker +Alabama 41 1 1290 Washington +Alabama 41 1 1310 Wilcox +Alabama 41 1 1330 Winston/Hancock +Alaska 81 2 2900 Northern District +Alaska 81 2 2950 Southern District +Alaska 81 2 3000 First Judicial District +Alaska 81 2 3100 Second Judicial District +Alaska 81 2 3200 Third Judicial District +Alaska 81 2 3300 Fourth Judicial District +Arizona 61 4 10 Apache +Arizona 61 4 30 Cochise +Arizona 61 4 50 Coconino +Arizona 61 4 70 Gila +Arizona 61 4 90 Graham +Arizona 61 4 110 Greenlee +Arizona 61 4 130 Maricopa +Arizona 61 4 150 Mohave +Arizona 61 4 170 Navajo +Arizona 61 4 190 Pima +Arizona 61 4 210 Pinal +Arizona 61 4 230 Santa Cruz +Arizona 61 4 250 Yavapai +Arizona 61 4 270 Yuma +Arizona 61 4 9010 San Carlos Indian Reservation +Arizona 61 4 9999 Unorganized Territory, Arizona +Arkansas 42 5 10 Arkansas +Arkansas 42 5 30 Ashley +Arkansas 42 5 50 Baxter +Arkansas 42 5 70 Benton +Arkansas 42 5 90 Boone +Arkansas 42 5 110 Bradley +Arkansas 42 5 130 Calhoun +Arkansas 42 5 150 Carroll +Arkansas 42 5 170 Chicot +Arkansas 42 5 190 Clark +Arkansas 42 5 210 Clay +Arkansas 42 5 230 Cleburne +Arkansas 42 5 250 Cleveland/Dorsey +Arkansas 42 5 270 Columbia +Arkansas 42 5 290 Conway +Arkansas 42 5 310 Craighead +Arkansas 42 5 330 Crawford +Arkansas 42 5 350 Crittenden +Arkansas 42 5 370 Cross +Arkansas 42 5 390 Dallas +Arkansas 42 5 410 Desha +Arkansas 42 5 430 Drew +Arkansas 42 5 450 Faulkner +Arkansas 42 5 470 Franklin +Arkansas 42 5 490 Fulton +Arkansas 42 5 510 Garland +Arkansas 42 5 530 Grant +Arkansas 42 5 550 Greene +Arkansas 42 5 570 Hempstead +Arkansas 42 5 590 Hot Spring +Arkansas 42 5 610 Howard +Arkansas 42 5 630 Independence +Arkansas 42 5 650 Izard +Arkansas 42 5 670 Jackson +Arkansas 42 5 690 Jefferson +Arkansas 42 5 710 Johnson +Arkansas 42 5 730 Lafayette +Arkansas 42 5 750 Lawrence +Arkansas 42 5 770 Lee +Arkansas 42 5 790 Lincoln +Arkansas 42 5 810 Little River +Arkansas 42 5 830 Logan +Arkansas 42 5 850 Lonoke +Arkansas 42 5 870 Madison +Arkansas 42 5 890 Marion +Arkansas 42 5 910 Miller +Arkansas 42 5 930 Mississippi +Arkansas 42 5 950 Monroe +Arkansas 42 5 970 Montgomery +Arkansas 42 5 990 Nevada +Arkansas 42 5 1010 Newton +Arkansas 42 5 1030 Ouachita +Arkansas 42 5 1050 Perry +Arkansas 42 5 1070 Phillips +Arkansas 42 5 1090 Pike +Arkansas 42 5 1110 Poinsett +Arkansas 42 5 1130 Polk +Arkansas 42 5 1150 Pope +Arkansas 42 5 1170 Prairie +Arkansas 42 5 1190 Pulaski +Arkansas 42 5 1210 Randolph +Arkansas 42 5 1230 St Francis +Arkansas 42 5 1250 Saline +Arkansas 42 5 1270 Scott +Arkansas 42 5 1290 Searcy +Arkansas 42 5 1310 Sebastian +Arkansas 42 5 1330 Sevier +Arkansas 42 5 1350 Sharp +Arkansas 42 5 1370 Stone +Arkansas 42 5 1390 Union +Arkansas 42 5 1410 Van Buren +Arkansas 42 5 1430 Washington +Arkansas 42 5 1450 White +Arkansas 42 5 1470 Woodruff +Arkansas 42 5 1490 Yell +California 71 6 10 Alameda +California 71 6 30 Alpine +California 71 6 50 Amador +California 71 6 70 Butte +California 71 6 90 Calaveras +California 71 6 110 Colusa +California 71 6 130 Contra Costa +California 71 6 150 Del Norte +California 71 6 170 El Dorado +California 71 6 190 Fresno +California 71 6 210 Glenn +California 71 6 230 Humboldt +California 71 6 250 Imperial +California 71 6 270 Inyo +California 71 6 290 Kern +California 71 6 310 Kings +California 71 6 315 Klamath +California 71 6 330 Lake +California 71 6 350 Lassen +California 71 6 370 Los Angeles +California 71 6 390 Madera +California 71 6 410 Marin +California 71 6 430 Mariposa +California 71 6 435 Mariposa-Mono +California 71 6 450 Mendocino +California 71 6 470 Merced +California 71 6 490 Modoc +California 71 6 510 Mono +California 71 6 530 Monterey +California 71 6 550 Napa +California 71 6 570 Nevada +California 71 6 590 Orange +California 71 6 610 Placer +California 71 6 630 Plumas +California 71 6 650 Riverside +California 71 6 670 Sacramento +California 71 6 690 San Benito +California 71 6 710 San Bernardino +California 71 6 730 San Diego +California 71 6 750 San Francisco +California 71 6 770 San Joaquin +California 71 6 790 San Luis Obispo +California 71 6 810 San Mateo +California 71 6 830 Santa Barbara +California 71 6 850 Santa Clara +California 71 6 870 Santa Cruz +California 71 6 890 Shasta +California 71 6 910 Sierra +California 71 6 930 Siskiyou +California 71 6 950 Solano +California 71 6 970 Sonoma +California 71 6 990 Stanislaus +California 71 6 1010 Sutter +California 71 6 1030 Tehama +California 71 6 1050 Trinity +California 71 6 1070 Tulare +California 71 6 1090 Tuolumne +California 71 6 1110 Ventura +California 71 6 1130 Yolo +California 71 6 1150 Yuba +Colorado 62 8 10 Adams +Colorado 62 8 30 Alamosa +Colorado 62 8 50 Arapahoe +Colorado 62 8 70 Archuleta +Colorado 62 8 90 Baca +Colorado 62 8 110 Bent +Colorado 62 8 130 Boulder +Colorado 62 8 150 Chaffee +Colorado 62 8 170 Cheyenne +Colorado 62 8 190 Clear Creek +Colorado 62 8 210 Conejos +Colorado 62 8 230 Costilla +Colorado 62 8 250 Crowley +Colorado 62 8 270 Custer +Colorado 62 8 290 Delta +Colorado 62 8 310 Denver +Colorado 62 8 330 Dolores +Colorado 62 8 350 Douglas +Colorado 62 8 370 Eagle +Colorado 62 8 390 Elbert +Colorado 62 8 410 El Paso +Colorado 62 8 430 Fremont +Colorado 62 8 450 Garfield +Colorado 62 8 470 Gilpin +Colorado 62 8 490 Grand +Colorado 62 8 495 Greenwood +Colorado 62 8 510 Gunnison +Colorado 62 8 530 Hinsdale +Colorado 62 8 550 Huerfano +Colorado 62 8 570 Jackson +Colorado 62 8 590 Jefferson +Colorado 62 8 610 Kiowa +Colorado 62 8 630 Kit Carson +Colorado 62 8 650 Lake +Colorado 62 8 670 La Plata +Colorado 62 8 690 Larimer +Colorado 62 8 710 Las Animas +Colorado 62 8 730 Lincoln +Colorado 62 8 750 Logan +Colorado 62 8 770 Mesa +Colorado 62 8 790 Mineral +Colorado 62 8 810 Moffat +Colorado 62 8 830 Montezuma +Colorado 62 8 850 Montrose +Colorado 62 8 870 Morgan +Colorado 62 8 890 Otero +Colorado 62 8 910 Ouray +Colorado 62 8 930 Park +Colorado 62 8 950 Phillips +Colorado 62 8 970 Pitkin +Colorado 62 8 990 Prowers +Colorado 62 8 1010 Pueblo +Colorado 62 8 1030 Rio Blanco +Colorado 62 8 1050 Rio Grande +Colorado 62 8 1070 Routt +Colorado 62 8 1090 Saguache +Colorado 62 8 1110 San Juan +Colorado 62 8 1130 San Miguel +Colorado 62 8 1150 Sedgwick +Colorado 62 8 1170 Summit +Colorado 62 8 1190 Teller +Colorado 62 8 1210 Washington +Colorado 62 8 1230 Weld +Colorado 62 8 1250 Yuma +Connecticut 1 9 10 Fairfield +Connecticut 1 9 30 Hartford +Connecticut 1 9 50 Litchfield +Connecticut 1 9 70 Middlesex +Connecticut 1 9 90 New Haven +Connecticut 1 9 110 New London +Connecticut 1 9 130 Tolland +Connecticut 1 9 150 Windham +Delaware 11 10 10 Kent +Delaware 11 10 30 New Castle +Delaware 11 10 50 Sussex +District of Columbia 98 11 10 District of Columbia +Florida 43 12 10 Alachua +Florida 43 12 30 Baker +Florida 43 12 50 Bay +Florida 43 12 70 Bradford/New River +Florida 43 12 90 Brevard/St Lucie +Florida 43 12 110 Broward +Florida 43 12 130 Calhoun +Florida 43 12 150 Charlotte +Florida 43 12 170 Citrus +Florida 43 12 190 Clay +Florida 43 12 210 Collier +Florida 43 12 230 Columbia +Florida 43 12 250 Dade +Florida 43 12 270 De Soto +Florida 43 12 290 Dixie +Florida 43 12 310 Duval +Florida 43 12 330 Escambia +Florida 43 12 350 Flagler +Florida 43 12 370 Franklin +Florida 43 12 390 Gadsden +Florida 43 12 410 Gilchrist +Florida 43 12 430 Glades +Florida 43 12 450 Gulf +Florida 43 12 470 Hamilton +Florida 43 12 490 Hardee +Florida 43 12 510 Hendry +Florida 43 12 530 Hernando/ Benton +Florida 43 12 550 Highlands +Florida 43 12 570 Hillsborough +Florida 43 12 590 Holmes +Florida 43 12 610 Indian River +Florida 43 12 630 Jackson +Florida 43 12 650 Jefferson +Florida 43 12 670 Lafayette +Florida 43 12 690 Lake +Florida 43 12 710 Lee +Florida 43 12 730 Leon +Florida 43 12 750 Levy +Florida 43 12 770 Liberty +Florida 43 12 790 Madison +Florida 43 12 810 Manatee +Florida 43 12 830 Marion +Florida 43 12 850 Martin +Florida 43 12 870 Monroe +Florida 43 12 890 Nassau +Florida 43 12 910 Okaloosa +Florida 43 12 930 Okeechobee +Florida 43 12 950 Orange/Mesquito +Florida 43 12 970 Osceola +Florida 43 12 990 Palm Beach +Florida 43 12 1010 Pasco +Florida 43 12 1030 Pinellas +Florida 43 12 1050 Polk +Florida 43 12 1070 Putnam +Florida 43 12 1090 St Johns +Florida 43 12 1110 St Lucie +Florida 43 12 1130 Santa Rosa +Florida 43 12 1150 Sarasota +Florida 43 12 1170 Seminole +Florida 43 12 1190 Sumter +Florida 43 12 1210 Suwannee +Florida 43 12 1230 Taylor +Florida 43 12 1250 Union +Florida 43 12 1270 Volusia +Florida 43 12 1290 Wakulla +Florida 43 12 1310 Walton +Florida 43 12 1330 Washington +Georgia 44 13 10 Appling +Georgia 44 13 30 Atkinson +Georgia 44 13 50 Bacon +Georgia 44 13 70 Baker +Georgia 44 13 90 Baldwin +Georgia 44 13 110 Banks +Georgia 44 13 130 Barrow +Georgia 44 13 150 Bartow/Cass +Georgia 44 13 170 Ben Hill +Georgia 44 13 190 Berrien +Georgia 44 13 210 Bibb +Georgia 44 13 230 Bleckley +Georgia 44 13 250 Brantley +Georgia 44 13 270 Brooks +Georgia 44 13 290 Bryan +Georgia 44 13 310 Bulloch +Georgia 44 13 330 Burke +Georgia 44 13 350 Butts +Georgia 44 13 370 Calhoun +Georgia 44 13 390 Camden +Georgia 44 13 410 Campbell +Georgia 44 13 430 Candler +Georgia 44 13 450 Carroll +Georgia 44 13 470 Catoosa +Georgia 44 13 490 Charlton +Georgia 44 13 510 Chatham +Georgia 44 13 530 Chattahoochee +Georgia 44 13 550 Chattooga +Georgia 44 13 570 Cherokee +Georgia 44 13 590 Clarke +Georgia 44 13 610 Clay +Georgia 44 13 630 Clayton +Georgia 44 13 650 Clinch +Georgia 44 13 670 Cobb +Georgia 44 13 690 Coffee +Georgia 44 13 710 Colquitt +Georgia 44 13 730 Columbia +Georgia 44 13 750 Cook +Georgia 44 13 770 Coweta +Georgia 44 13 790 Crawford +Georgia 44 13 810 Crisp +Georgia 44 13 830 Dade +Georgia 44 13 850 Dawson +Georgia 44 13 870 Decatur +Georgia 44 13 890 De Kalb +Georgia 44 13 910 Dodge +Georgia 44 13 930 Dooly +Georgia 44 13 950 Dougherty +Georgia 44 13 970 Douglas +Georgia 44 13 990 Early +Georgia 44 13 1010 Echols +Georgia 44 13 1030 Effingham +Georgia 44 13 1050 Elbert +Georgia 44 13 1070 Emanuel +Georgia 44 13 1090 Evans +Georgia 44 13 1110 Fannin +Georgia 44 13 1130 Fayette +Georgia 44 13 1150 Floyd +Georgia 44 13 1170 Forsyth +Georgia 44 13 1190 Franklin +Georgia 44 13 1210 Fulton +Georgia 44 13 1230 Gilmer +Georgia 44 13 1250 Glascock +Georgia 44 13 1270 Glynn +Georgia 44 13 1290 Gordon +Georgia 44 13 1310 Grady +Georgia 44 13 1330 Greene +Georgia 44 13 1350 Gwinnett +Georgia 44 13 1370 Habersham +Georgia 44 13 1390 Hall +Georgia 44 13 1410 Hancock +Georgia 44 13 1430 Haralson +Georgia 44 13 1450 Harris +Georgia 44 13 1470 Hart +Georgia 44 13 1490 Heard +Georgia 44 13 1510 Henry +Georgia 44 13 1530 Houston +Georgia 44 13 1550 Irwin +Georgia 44 13 1570 Jackson +Georgia 44 13 1590 Jasper +Georgia 44 13 1610 Jeff Davis +Georgia 44 13 1630 Jefferson +Georgia 44 13 1650 Jenkins +Georgia 44 13 1670 Johnson +Georgia 44 13 1690 Jones +Georgia 44 13 1730 Lanier +Georgia 44 13 1750 Laurens +Georgia 44 13 1770 Lee +Georgia 44 13 1790 Liberty +Georgia 44 13 1810 Lincoln +Georgia 44 13 1830 Long +Georgia 44 13 1850 Lowndes +Georgia 44 13 1870 Lumpkin +Georgia 44 13 1890 Mcduffie +Georgia 44 13 1910 Mcintosh +Georgia 44 13 1930 Macon +Georgia 44 13 1950 Madison +Georgia 44 13 1970 Marion +Georgia 44 13 1990 Meriwether +Georgia 44 13 2010 Miller +Georgia 44 13 2030 Milton +Georgia 44 13 2050 Mitchell +Georgia 44 13 2070 Monroe +Georgia 44 13 2090 Montgomery +Georgia 44 13 2110 Morgan +Georgia 44 13 2130 Murray +Georgia 44 13 2150 Muscogee +Georgia 44 13 2170 Newton +Georgia 44 13 2190 Oconee +Georgia 44 13 2210 Oglethorpe +Georgia 44 13 2230 Paulding +Georgia 44 13 2250 Peach +Georgia 44 13 2270 Pickens +Georgia 44 13 2290 Pierce +Georgia 44 13 2310 Pike +Georgia 44 13 2330 Polk +Georgia 44 13 2350 Pulaski +Georgia 44 13 2370 Putnam +Georgia 44 13 2390 Quitman +Georgia 44 13 2410 Rabun +Georgia 44 13 2430 Randolph +Georgia 44 13 2450 Richmond +Georgia 44 13 2470 Rockdale +Georgia 44 13 2490 Schley +Georgia 44 13 2510 Screven +Georgia 44 13 2530 Seminole +Georgia 44 13 2550 Spalding +Georgia 44 13 2570 Stephens +Georgia 44 13 2590 Stewart +Georgia 44 13 2610 Sumter +Georgia 44 13 2630 Talbot +Georgia 44 13 2650 Taliaferro +Georgia 44 13 2670 Tattnall +Georgia 44 13 2690 Taylor +Georgia 44 13 2710 Telfair +Georgia 44 13 2730 Terrell +Georgia 44 13 2750 Thomas +Georgia 44 13 2770 Tift +Georgia 44 13 2790 Toombs +Georgia 44 13 2810 Towns +Georgia 44 13 2830 Treutlen +Georgia 44 13 2850 Troup +Georgia 44 13 2870 Turner +Georgia 44 13 2890 Twiggs +Georgia 44 13 2910 Union +Georgia 44 13 2930 Upson +Georgia 44 13 2950 Walker +Georgia 44 13 2970 Walton +Georgia 44 13 2990 Ware +Georgia 44 13 3010 Warren +Georgia 44 13 3030 Washington +Georgia 44 13 3050 Wayne +Georgia 44 13 3070 Webster +Georgia 44 13 3090 Wheeler +Georgia 44 13 3110 White +Georgia 44 13 3130 Whitfield +Georgia 44 13 3150 Wilcox +Georgia 44 13 3170 Wilkes +Georgia 44 13 3190 Wilkinson +Georgia 44 13 3210 Worth +Hawaii 82 15 10 Hawaii +Hawaii 82 15 30 Honolulu +Hawaii 82 15 70 Kauai +Hawaii 82 15 90 Maui +Hawaii 82 15 110 Unknown +Hawaii 82 15 9997 Lanai +Hawaii 82 15 9997 Molokai +Hawaii 82 15 9997 Niihau +Hawaii 82 15 9997 Oahu +Idaho 63 16 10 Ada +Idaho 63 16 30 Adams +Idaho 63 16 35 Alturas +Idaho 63 16 50 Bannock +Idaho 63 16 70 Bear Lake +Idaho 63 16 90 Benewah +Idaho 63 16 110 Bingham +Idaho 63 16 130 Blaine +Idaho 63 16 150 Boise +Idaho 63 16 170 Bonner +Idaho 63 16 190 Bonneville +Idaho 63 16 210 Boundary +Idaho 63 16 230 Butte +Idaho 63 16 250 Camas +Idaho 63 16 270 Canyon +Idaho 63 16 290 Caribou +Idaho 63 16 310 Cassia +Idaho 63 16 330 Clark +Idaho 63 16 335 Clark-Fremont +Idaho 63 16 350 Clearwater +Idaho 63 16 370 Custer +Idaho 63 16 390 Elmore +Idaho 63 16 410 Franklin +Idaho 63 16 430 Fremont +Idaho 63 16 450 Gem +Idaho 63 16 470 Gooding +Idaho 63 16 490 Idaho +Idaho 63 16 510 Jefferson +Idaho 63 16 530 Jerome +Idaho 63 16 550 Kootenai +Idaho 63 16 570 Latah +Idaho 63 16 590 Lemhi +Idaho 63 16 610 Lewis +Idaho 63 16 630 Lincoln +Idaho 63 16 635 Logan +Idaho 63 16 650 Madison +Idaho 63 16 670 Minidoka +Idaho 63 16 690 Nez Perce +Idaho 63 16 710 Oneida +Idaho 63 16 730 Owyhee +Idaho 63 16 750 Payette +Idaho 63 16 770 Power +Idaho 63 16 790 Shoshone +Idaho 63 16 810 Teton +Idaho 63 16 830 Twin Falls +Idaho 63 16 850 Valley +Idaho 63 16 870 Washington +Illinois 21 17 10 Adams +Illinois 21 17 30 Alexander +Illinois 21 17 50 Bond +Illinois 21 17 70 Boone +Illinois 21 17 90 Brown +Illinois 21 17 110 Bureau +Illinois 21 17 130 Calhoun +Illinois 21 17 150 Carroll +Illinois 21 17 170 Cass +Illinois 21 17 190 Champaign +Illinois 21 17 210 Christian +Illinois 21 17 230 Clark +Illinois 21 17 250 Clay +Illinois 21 17 270 Clinton +Illinois 21 17 290 Coles +Illinois 21 17 310 Cook +Illinois 21 17 330 Crawford +Illinois 21 17 350 Cumberland +Illinois 21 17 370 DeKalb +Illinois 21 17 390 De Witt +Illinois 21 17 410 Douglas +Illinois 21 17 430 Du Page +Illinois 21 17 450 Edgar +Illinois 21 17 470 Edwards +Illinois 21 17 490 Effingham +Illinois 21 17 510 Fayette +Illinois 21 17 530 Ford +Illinois 21 17 550 Franklin +Illinois 21 17 570 Fulton +Illinois 21 17 590 Gallatin +Illinois 21 17 610 Greene +Illinois 21 17 630 Grundy +Illinois 21 17 650 Hamilton +Illinois 21 17 670 Hancock +Illinois 21 17 690 Hardin +Illinois 21 17 710 Henderson +Illinois 21 17 730 Henry +Illinois 21 17 750 Iroquois +Illinois 21 17 770 Jackson +Illinois 21 17 790 Jasper +Illinois 21 17 810 Jefferson +Illinois 21 17 830 Jersey +Illinois 21 17 850 Jo Daviess +Illinois 21 17 870 Johnson +Illinois 21 17 890 Kane +Illinois 21 17 910 Kankakee +Illinois 21 17 930 Kendall +Illinois 21 17 950 Knox +Illinois 21 17 970 Lake +Illinois 21 17 990 La Salle +Illinois 21 17 1010 Lawrence +Illinois 21 17 1030 Lee +Illinois 21 17 1050 Livingston +Illinois 21 17 1070 Logan +Illinois 21 17 1090 McDonough +Illinois 21 17 1110 McHenry +Illinois 21 17 1130 McLean +Illinois 21 17 1150 Macon +Illinois 21 17 1170 Macoupin +Illinois 21 17 1190 Madison +Illinois 21 17 1210 Marion +Illinois 21 17 1230 Marshall +Illinois 21 17 1250 Mason +Illinois 21 17 1270 Massac +Illinois 21 17 1290 Menard +Illinois 21 17 1310 Mercer +Illinois 21 17 1330 Monroe +Illinois 21 17 1350 Montgomery +Illinois 21 17 1370 Morgan +Illinois 21 17 1390 Moultrie +Illinois 21 17 1410 Ogle +Illinois 21 17 1430 Peoria +Illinois 21 17 1450 Perry +Illinois 21 17 1470 Piatt +Illinois 21 17 1490 Pike +Illinois 21 17 1510 Pope +Illinois 21 17 1530 Pulaski +Illinois 21 17 1550 Putnam +Illinois 21 17 1570 Randolph +Illinois 21 17 1590 Richland +Illinois 21 17 1610 Rock Island +Illinois 21 17 1630 St Clair +Illinois 21 17 1650 Saline +Illinois 21 17 1670 Sangamon +Illinois 21 17 1690 Schuyler +Illinois 21 17 1710 Scott +Illinois 21 17 1730 Shelby +Illinois 21 17 1750 Stark +Illinois 21 17 1770 Stephenson +Illinois 21 17 1790 Tazewell +Illinois 21 17 1810 Union +Illinois 21 17 1830 Vermilion +Illinois 21 17 1850 Wabash +Illinois 21 17 1870 Warren +Illinois 21 17 1890 Washington +Illinois 21 17 1910 Wayne +Illinois 21 17 1930 White +Illinois 21 17 1950 Whiteside +Illinois 21 17 1970 Will +Illinois 21 17 1990 Williamson +Illinois 21 17 2010 Winnebago +Illinois 21 17 2030 Woodford +Indiana 22 18 10 Adams +Indiana 22 18 30 Allen +Indiana 22 18 50 Bartholomew +Indiana 22 18 70 Benton +Indiana 22 18 90 Blackford +Indiana 22 18 110 Boone +Indiana 22 18 130 Brown +Indiana 22 18 150 Carroll +Indiana 22 18 170 Cass +Indiana 22 18 190 Clark +Indiana 22 18 210 Clay +Indiana 22 18 230 Clinton +Indiana 22 18 250 Crawford +Indiana 22 18 270 Daviess +Indiana 22 18 290 Dearborn +Indiana 22 18 310 Decatur +Indiana 22 18 330 De Kalb +Indiana 22 18 350 Delaware +Indiana 22 18 370 Dubois +Indiana 22 18 390 Elkhart +Indiana 22 18 410 Fayette +Indiana 22 18 430 Floyd +Indiana 22 18 450 Fountain +Indiana 22 18 470 Franklin +Indiana 22 18 490 Fulton +Indiana 22 18 510 Gibson +Indiana 22 18 530 Grant +Indiana 22 18 550 Greene +Indiana 22 18 570 Hamilton +Indiana 22 18 590 Hancock +Indiana 22 18 610 Harrison +Indiana 22 18 630 Hendricks +Indiana 22 18 650 Henry +Indiana 22 18 670 Howard +Indiana 22 18 690 Huntington +Indiana 22 18 710 Jackson +Indiana 22 18 730 Jasper +Indiana 22 18 750 Jay +Indiana 22 18 770 Jefferson +Indiana 22 18 790 Jennings +Indiana 22 18 810 Johnson +Indiana 22 18 830 Knox +Indiana 22 18 850 Kosciusko +Indiana 22 18 870 Lagrange +Indiana 22 18 890 Lake +Indiana 22 18 910 La Porte +Indiana 22 18 930 Lawrence +Indiana 22 18 950 Madison +Indiana 22 18 970 Marion +Indiana 22 18 990 Marshall +Indiana 22 18 1010 Martin +Indiana 22 18 1030 Miami +Indiana 22 18 1050 Monroe +Indiana 22 18 1070 Montgomery +Indiana 22 18 1090 Morgan +Indiana 22 18 1110 Newton +Indiana 22 18 1130 Noble +Indiana 22 18 1150 Ohio +Indiana 22 18 1170 Orange +Indiana 22 18 1190 Owen +Indiana 22 18 1210 Parke +Indiana 22 18 1230 Perry +Indiana 22 18 1250 Pike +Indiana 22 18 1270 Porter +Indiana 22 18 1290 Posey +Indiana 22 18 1310 Pulaski +Indiana 22 18 1330 Putnam +Indiana 22 18 1350 Randolph +Indiana 22 18 1370 Ripley +Indiana 22 18 1390 Rush +Indiana 22 18 1410 St Joseph +Indiana 22 18 1430 Scott +Indiana 22 18 1450 Shelby +Indiana 22 18 1470 Spencer +Indiana 22 18 1490 Starke +Indiana 22 18 1510 Steuben +Indiana 22 18 1530 Sullivan +Indiana 22 18 1550 Switzerland +Indiana 22 18 1570 Tippecanoe +Indiana 22 18 1590 Tipton +Indiana 22 18 1610 Union +Indiana 22 18 1630 Vanderburgh +Indiana 22 18 1650 Vermillion +Indiana 22 18 1670 Vigo +Indiana 22 18 1690 Wabash +Indiana 22 18 1710 Warren +Indiana 22 18 1730 Warrick +Indiana 22 18 1750 Washington +Indiana 22 18 1770 Wayne +Indiana 22 18 1790 Wells +Indiana 22 18 1810 White +Indiana 22 18 1830 Whitley +Iowa 31 19 10 Adair +Iowa 31 19 30 Adams +Iowa 31 19 50 Allamakee +Iowa 31 19 70 Appanoose +Iowa 31 19 90 Audubon +Iowa 31 19 110 Benton +Iowa 31 19 130 Black Hawk +Iowa 31 19 150 Boone +Iowa 31 19 170 Bremer +Iowa 31 19 190 Buchanan +Iowa 31 19 210 Buena Vista +Iowa 31 19 230 Butler +Iowa 31 19 250 Calhoun +Iowa 31 19 270 Carroll +Iowa 31 19 290 Cass +Iowa 31 19 310 Cedar +Iowa 31 19 330 Cerro Gordo +Iowa 31 19 350 Cherokee +Iowa 31 19 370 Chickasaw +Iowa 31 19 390 Clarke +Iowa 31 19 410 Clay +Iowa 31 19 430 Clayton +Iowa 31 19 450 Clinton +Iowa 31 19 470 Crawford +Iowa 31 19 490 Dallas +Iowa 31 19 510 Davis +Iowa 31 19 530 Decatur +Iowa 31 19 550 Delaware +Iowa 31 19 570 Des Moines +Iowa 31 19 590 Dickinson +Iowa 31 19 610 Dubuque +Iowa 31 19 630 Emmet +Iowa 31 19 650 Fayette +Iowa 31 19 670 Floyd +Iowa 31 19 690 Franklin +Iowa 31 19 710 Fremont +Iowa 31 19 730 Greene +Iowa 31 19 750 Grundy +Iowa 31 19 770 Guthrie +Iowa 31 19 790 Hamilton +Iowa 31 19 810 Hancock +Iowa 31 19 830 Hardin +Iowa 31 19 850 Harrison +Iowa 31 19 870 Henry +Iowa 31 19 890 Howard +Iowa 31 19 910 Humboldt +Iowa 31 19 930 Ida +Iowa 31 19 950 Iowa +Iowa 31 19 970 Jackson +Iowa 31 19 990 Jasper +Iowa 31 19 1010 Jefferson +Iowa 31 19 1030 Johnson +Iowa 31 19 1050 Jones +Iowa 31 19 1070 Keokuk +Iowa 31 19 1090 Kossuth +Iowa 31 19 1110 Lee +Iowa 31 19 1130 Linn +Iowa 31 19 1150 Louisa +Iowa 31 19 1170 Lucas +Iowa 31 19 1190 Lyon/Buncombe +Iowa 31 19 1210 Madison +Iowa 31 19 1230 Mahaska +Iowa 31 19 1250 Marion +Iowa 31 19 1270 Marshall +Iowa 31 19 1290 Mills +Iowa 31 19 1310 Mitchell +Iowa 31 19 1330 Monona +Iowa 31 19 1350 Monroe +Iowa 31 19 1370 Montgomery +Iowa 31 19 1390 Muscatine +Iowa 31 19 1410 O Brien +Iowa 31 19 1430 Osceola +Iowa 31 19 1450 Page +Iowa 31 19 1470 Palo Alto +Iowa 31 19 1490 Plymouth +Iowa 31 19 1510 Pocahontas +Iowa 31 19 1530 Polk +Iowa 31 19 1550 Pottawattamie +Iowa 31 19 1570 Poweshiek +Iowa 31 19 1590 Ringgold +Iowa 31 19 1610 Sac +Iowa 31 19 1630 Scott +Iowa 31 19 1650 Shelby +Iowa 31 19 1670 Sioux +Iowa 31 19 1690 Story +Iowa 31 19 1710 Tama +Iowa 31 19 1730 Taylor +Iowa 31 19 1750 Union +Iowa 31 19 1770 Van Buren +Iowa 31 19 1790 Wapello +Iowa 31 19 1810 Warren +Iowa 31 19 1830 Washington +Iowa 31 19 1850 Wayne +Iowa 31 19 1870 Webster +Iowa 31 19 1890 Winnebago +Iowa 31 19 1910 Winneshiek +Iowa 31 19 1930 Woodbury +Iowa 31 19 1950 Worth +Iowa 31 19 1970 Wright +Kansas 32 20 10 Allen +Kansas 32 20 30 Anderson +Kansas 32 20 50 Atchison +Kansas 32 20 55 Arapahoe +Kansas 32 20 70 Barber/Barbour +Kansas 32 20 90 Barton +Kansas 32 20 110 Bourbon +Kansas 32 20 115 Breckenridge +Kansas 32 20 130 Brown +Kansas 32 20 135 Buffalo +Kansas 32 20 150 Butler +Kansas 32 20 170 Chase +Kansas 32 20 190 Chautauqua +Kansas 32 20 210 Cherokee/Mcghee +Kansas 32 20 230 Cheyenne +Kansas 32 20 250 Clark +Kansas 32 20 270 Clay +Kansas 32 20 290 Cloud +Kansas 32 20 310 Coffey +Kansas 32 20 330 Comanche +Kansas 32 20 350 Cowley +Kansas 32 20 370 Crawford +Kansas 32 20 390 Decatur +Kansas 32 20 410 Dickinson +Kansas 32 20 430 Doniphan +Kansas 32 20 450 Douglas +Kansas 32 20 470 Edwards +Kansas 32 20 490 Elk +Kansas 32 20 510 Ellis +Kansas 32 20 530 Ellsworth +Kansas 32 20 550 Finney/Sequoyah +Kansas 32 20 555 Foote +Kansas 32 20 570 Ford +Kansas 32 20 590 Franklin +Kansas 32 20 595 Garfield +Kansas 32 20 610 Geary/Davis +Kansas 32 20 630 Gove +Kansas 32 20 650 Graham +Kansas 32 20 670 Grant +Kansas 32 20 690 Gray +Kansas 32 20 710 Greeley +Kansas 32 20 730 Greenwood +Kansas 32 20 750 Hamilton +Kansas 32 20 770 Harper +Kansas 32 20 790 Harvey +Kansas 32 20 810 Haskell +Kansas 32 20 830 Hodgeman +Kansas 32 20 835 Howard +Kansas 32 20 850 Jackson +Kansas 32 20 870 Jefferson +Kansas 32 20 890 Jewell +Kansas 32 20 910 Johnson +Kansas 32 20 915 Kansas +Kansas 32 20 930 Kearny +Kansas 32 20 950 Kingman +Kansas 32 20 970 Kiowa +Kansas 32 20 990 Labette +Kansas 32 20 1010 Lane +Kansas 32 20 1030 Leavenworth +Kansas 32 20 1050 Lincoln +Kansas 32 20 1070 Linn +Kansas 32 20 1090 Logan +Kansas 32 20 1110 Lyon +Kansas 32 20 1115 Madison +Kansas 32 20 1130 McPherson +Kansas 32 20 1150 Marion +Kansas 32 20 1170 Marshall +Kansas 32 20 1190 Meade +Kansas 32 20 1210 Miami/Lykins +Kansas 32 20 1230 Mitchell +Kansas 32 20 1250 Montgomery +Kansas 32 20 1270 Morris +Kansas 32 20 1290 Morton +Kansas 32 20 1310 Nemaha +Kansas 32 20 1330 Neosho/Dorn +Kansas 32 20 1350 Ness +Kansas 32 20 1370 Norton +Kansas 32 20 1390 Osage +Kansas 32 20 1410 Osborne +Kansas 32 20 1415 Otoc +Kansas 32 20 1430 Ottawa +Kansas 32 20 1450 Pawnee +Kansas 32 20 1470 Phillips +Kansas 32 20 1490 Pottawatomie +Kansas 32 20 1510 Pratt +Kansas 32 20 1530 Rawlins +Kansas 32 20 1550 Reno +Kansas 32 20 1570 Republic +Kansas 32 20 1590 Rice +Kansas 32 20 1610 Riley +Kansas 32 20 1630 Rooks +Kansas 32 20 1650 Rush +Kansas 32 20 1670 Russell +Kansas 32 20 1690 Saline +Kansas 32 20 1710 Scott +Kansas 32 20 1730 Sedgwick +Kansas 32 20 1750 Seward +Kansas 32 20 1770 Shawnee +Kansas 32 20 1790 Sheridan +Kansas 32 20 1810 Sherman +Kansas 32 20 1830 Smith +Kansas 32 20 1850 Stafford +Kansas 32 20 1870 Stanton +Kansas 32 20 1890 Stevens +Kansas 32 20 1910 Sumner +Kansas 32 20 1930 Thomas +Kansas 32 20 1950 Trego +Kansas 32 20 1970 Wabaunsee +Kansas 32 20 1990 Wallace +Kansas 32 20 2010 Washington +Kansas 32 20 2030 Wichita +Kansas 32 20 2050 Wilson +Kansas 32 20 2070 Woodson +Kansas 32 20 2090 Wyandotte +Kentucky 51 21 10 Adair +Kentucky 51 21 30 Allen +Kentucky 51 21 50 Anderson +Kentucky 51 21 70 Ballard +Kentucky 51 21 90 Barren +Kentucky 51 21 110 Bath +Kentucky 51 21 130 Bell +Kentucky 51 21 150 Boone +Kentucky 51 21 170 Bourbon +Kentucky 51 21 190 Boyd +Kentucky 51 21 210 Boyle +Kentucky 51 21 230 Bracken +Kentucky 51 21 250 Breathitt +Kentucky 51 21 270 Breckinridge +Kentucky 51 21 290 Bullitt +Kentucky 51 21 310 Butler +Kentucky 51 21 330 Caldwell +Kentucky 51 21 350 Calloway +Kentucky 51 21 370 Campbell +Kentucky 51 21 390 Carlisle +Kentucky 51 21 410 Carroll +Kentucky 51 21 430 Carter +Kentucky 51 21 450 Casey +Kentucky 51 21 470 Christian +Kentucky 51 21 490 Clark +Kentucky 51 21 510 Clay +Kentucky 51 21 530 Clinton +Kentucky 51 21 550 Crittenden +Kentucky 51 21 570 Cumberland +Kentucky 51 21 590 Daviess +Kentucky 51 21 610 Edmonson +Kentucky 51 21 630 Elliott +Kentucky 51 21 650 Estill +Kentucky 51 21 670 Fayette +Kentucky 51 21 690 Fleming +Kentucky 51 21 710 Floyd +Kentucky 51 21 730 Franklin +Kentucky 51 21 750 Fulton +Kentucky 51 21 770 Gallatin +Kentucky 51 21 790 Garrard +Kentucky 51 21 810 Grant +Kentucky 51 21 830 Graves +Kentucky 51 21 850 Grayson +Kentucky 51 21 870 Green +Kentucky 51 21 890 Greenup +Kentucky 51 21 910 Hancock +Kentucky 51 21 930 Hardin +Kentucky 51 21 950 Harlan +Kentucky 51 21 970 Harrison +Kentucky 51 21 990 Hart +Kentucky 51 21 1010 Henderson +Kentucky 51 21 1030 Henry +Kentucky 51 21 1050 Hickman +Kentucky 51 21 1070 Hopkins +Kentucky 51 21 1090 Jackson +Kentucky 51 21 1110 Jefferson +Kentucky 51 21 1130 Jessamine +Kentucky 51 21 1150 Johnson +Kentucky 51 21 1155 Josh Bell +Kentucky 51 21 1170 Kenton +Kentucky 51 21 1190 Knott +Kentucky 51 21 1210 Knox +Kentucky 51 21 1230 Larue +Kentucky 51 21 1250 Laurel +Kentucky 51 21 1270 Lawrence +Kentucky 51 21 1290 Lee +Kentucky 51 21 1310 Leslie +Kentucky 51 21 1330 Letcher +Kentucky 51 21 1350 Lewis +Kentucky 51 21 1370 Lincoln +Kentucky 51 21 1390 Livingston +Kentucky 51 21 1410 Logan +Kentucky 51 21 1430 Lyon +Kentucky 51 21 1450 McCracken +Kentucky 51 21 1470 McCreary +Kentucky 51 21 1490 McLean +Kentucky 51 21 1510 Madison +Kentucky 51 21 1530 Magoffin +Kentucky 51 21 1550 Marion +Kentucky 51 21 1570 Marshall +Kentucky 51 21 1590 Martin +Kentucky 51 21 1610 Mason +Kentucky 51 21 1630 Meade +Kentucky 51 21 1650 Menifee +Kentucky 51 21 1670 Mercer +Kentucky 51 21 1690 Metcalfe +Kentucky 51 21 1710 Monroe +Kentucky 51 21 1730 Montgomery +Kentucky 51 21 1750 Morgan +Kentucky 51 21 1770 Muhlenberg +Kentucky 51 21 1790 Nelson +Kentucky 51 21 1810 Nicholas +Kentucky 51 21 1830 Ohio +Kentucky 51 21 1850 Oldham +Kentucky 51 21 1870 Owen +Kentucky 51 21 1890 Owsley +Kentucky 51 21 1910 Pendleton +Kentucky 51 21 1930 Perry +Kentucky 51 21 1950 Pike +Kentucky 51 21 1970 Powell +Kentucky 51 21 1990 Pulaski +Kentucky 51 21 2010 Robertson +Kentucky 51 21 2030 Rockcastle +Kentucky 51 21 2050 Rowan +Kentucky 51 21 2070 Russell +Kentucky 51 21 2090 Scott +Kentucky 51 21 2110 Shelby +Kentucky 51 21 2130 Simpson +Kentucky 51 21 2150 Spencer +Kentucky 51 21 2170 Taylor +Kentucky 51 21 2190 Todd +Kentucky 51 21 2210 Trigg +Kentucky 51 21 2230 Trimble +Kentucky 51 21 2250 Union +Kentucky 51 21 2270 Warren +Kentucky 51 21 2290 Washington +Kentucky 51 21 2310 Wayne +Kentucky 51 21 2330 Webster +Kentucky 51 21 2350 Whitley +Kentucky 51 21 2370 Wolfe +Kentucky 51 21 2390 Woodford +Louisiana 45 22 10 Acadia +Louisiana 45 22 30 Allen +Louisiana 45 22 50 Ascension +Louisiana 45 22 70 Assumption +Louisiana 45 22 90 Avoyelles +Louisiana 45 22 110 Beauregard +Louisiana 45 22 130 Bienville +Louisiana 45 22 150 Bossier +Louisiana 45 22 170 Caddo +Louisiana 45 22 190 Calcasieu +Louisiana 45 22 210 Caldwell +Louisiana 45 22 230 Cameron +Louisiana 45 22 250 Catahoula +Louisiana 45 22 270 Claiborne +Louisiana 45 22 290 Concordia +Louisiana 45 22 310 De Soto +Louisiana 45 22 330 East Baton Rouge +Louisiana 45 22 350 East Carroll/Carroll +Louisiana 45 22 370 East Feliciana +Louisiana 45 22 390 Evangeline +Louisiana 45 22 410 Franklin +Louisiana 45 22 430 Grant +Louisiana 45 22 450 Iberia +Louisiana 45 22 470 Iberville +Louisiana 45 22 490 Jackson +Louisiana 45 22 510 Jefferson +Louisiana 45 22 530 Jefferson Davis +Louisiana 45 22 550 Lafayette +Louisiana 45 22 570 Lafourche +Louisiana 45 22 590 La Salle +Louisiana 45 22 610 Lincoln +Louisiana 45 22 630 Livingston +Louisiana 45 22 650 Madison +Louisiana 45 22 670 Morehouse +Louisiana 45 22 690 Natchitoches +Louisiana 45 22 710 Orleans +Louisiana 45 22 730 Ouachita +Louisiana 45 22 750 Plaquemines +Louisiana 45 22 770 Pointe Coupee +Louisiana 45 22 790 Rapides +Louisiana 45 22 810 Red River +Louisiana 45 22 830 Richland +Louisiana 45 22 850 Sabine +Louisiana 45 22 870 St Bernard +Louisiana 45 22 890 St Charles +Louisiana 45 22 910 St Helena +Louisiana 45 22 930 St James +Louisiana 45 22 950 St John The Baptist +Louisiana 45 22 970 St Landry +Louisiana 45 22 990 St Martin +Louisiana 45 22 1010 St Mary +Louisiana 45 22 1030 St Tammany +Louisiana 45 22 1050 Tangipahoa +Louisiana 45 22 1070 Tensas +Louisiana 45 22 1090 Terrebonne +Louisiana 45 22 1110 Union +Louisiana 45 22 1130 Vermilion +Louisiana 45 22 1150 Vernon +Louisiana 45 22 1170 Washington +Louisiana 45 22 1190 Webster +Louisiana 45 22 1210 West Baton Rouge +Louisiana 45 22 1230 West Carroll +Louisiana 45 22 1250 West Feliciana +Louisiana 45 22 1270 Winn +Maine 2 23 10 Androscoggin +Maine 2 23 30 Aroostook +Maine 2 23 50 Cumberland +Maine 2 23 70 Franklin +Maine 2 23 90 Hancock +Maine 2 23 110 Kennebec +Maine 2 23 130 Knox +Maine 2 23 150 Lincoln +Maine 2 23 170 Oxford +Maine 2 23 190 Penobscot +Maine 2 23 210 Piscataquis +Maine 2 23 230 Sagadahoc +Maine 2 23 250 Somerset +Maine 2 23 270 Waldo +Maine 2 23 290 Washington +Maine 2 23 310 York +Maryland 52 24 10 Allegany +Maryland 52 24 30 Anne Arundel +Maryland 52 24 50 Baltimore +Maryland 52 24 70 Calvert +Maryland 52 24 90 Caroline +Maryland 52 24 110 Carroll +Maryland 52 24 130 Cecil +Maryland 52 24 150 Charles +Maryland 52 24 170 Dorchester +Maryland 52 24 190 Frederick +Maryland 52 24 210 Garrett +Maryland 52 24 230 Harford +Maryland 52 24 250 Howard +Maryland 52 24 270 Kent +Maryland 52 24 290 Montgomery +Maryland 52 24 310 Prince Georges +Maryland 52 24 330 Queen Annes +Maryland 52 24 350 Somerset +Maryland 52 24 370 St Marys +Maryland 52 24 390 Talbot +Maryland 52 24 410 Washington +Maryland 52 24 430 Wicomico +Maryland 52 24 450 Worcester +Maryland 52 24 5100 Baltimore City +Massachusetts 3 25 10 Barnstable +Massachusetts 3 25 30 Berkshire +Massachusetts 3 25 50 Bristol +Massachusetts 3 25 70 Dukes +Massachusetts 3 25 90 Essex +Massachusetts 3 25 110 Franklin +Massachusetts 3 25 130 Hampden +Massachusetts 3 25 150 Hampshire +Massachusetts 3 25 170 Middlesex +Massachusetts 3 25 190 Nantucket +Massachusetts 3 25 210 Norfolk +Massachusetts 3 25 230 Plymouth +Massachusetts 3 25 250 Suffolk +Massachusetts 3 25 270 Worcester +Michigan 23 26 10 Alcona +Michigan 23 26 30 Alger +Michigan 23 26 50 Allegan +Michigan 23 26 70 Alpena +Michigan 23 26 90 Antrim +Michigan 23 26 110 Arenac +Michigan 23 26 130 Baraga +Michigan 23 26 150 Barry +Michigan 23 26 170 Bay +Michigan 23 26 190 Benzie +Michigan 23 26 210 Berrien +Michigan 23 26 230 Branch +Michigan 23 26 250 Calhoun +Michigan 23 26 270 Cass +Michigan 23 26 290 Charlevoix +Michigan 23 26 310 Cheboygan +Michigan 23 26 330 Chippewa +Michigan 23 26 350 Clare +Michigan 23 26 370 Clinton +Michigan 23 26 390 Crawford +Michigan 23 26 410 Delta +Michigan 23 26 430 Dickinson +Michigan 23 26 450 Eaton +Michigan 23 26 470 Emmet +Michigan 23 26 490 Genesee +Michigan 23 26 510 Gladwin +Michigan 23 26 530 Gogebic +Michigan 23 26 550 Grand Traverse +Michigan 23 26 570 Gratiot +Michigan 23 26 590 Hillsdale +Michigan 23 26 610 Houghton +Michigan 23 26 630 Huron +Michigan 23 26 650 Ingham +Michigan 23 26 670 Ionia +Michigan 23 26 690 Iosco +Michigan 23 26 710 Iron +Michigan 23 26 730 Isabella +Michigan 23 26 735 Isle Royale +Michigan 23 26 750 Jackson +Michigan 23 26 770 Kalamazoo +Michigan 23 26 790 Kalkaska +Michigan 23 26 810 Kent +Michigan 23 26 830 Keweenaw +Michigan 23 26 850 Lake +Michigan 23 26 870 Lapeer +Michigan 23 26 890 Leelanau +Michigan 23 26 910 Lenawee +Michigan 23 26 930 Livingston +Michigan 23 26 950 Luce +Michigan 23 26 970 Mackinac/Michilim +Michigan 23 26 990 Macomb +Michigan 23 26 1010 Manistee +Michigan 23 26 1015 Manitou +Michigan 23 26 1030 Marquette +Michigan 23 26 1050 Mason +Michigan 23 26 1070 Mecosta +Michigan 23 26 1090 Menominee +Michigan 23 26 1110 Midland +Michigan 23 26 1130 Missaukee +Michigan 23 26 1150 Monroe +Michigan 23 26 1170 Montcalm +Michigan 23 26 1190 Montmorency +Michigan 23 26 1210 Muskegon +Michigan 23 26 1230 Newaygo +Michigan 23 26 1250 Oakland +Michigan 23 26 1270 Oceana +Michigan 23 26 1290 Ogemaw +Michigan 23 26 1310 Ontonagon +Michigan 23 26 1330 Osceola +Michigan 23 26 1350 Oscoda +Michigan 23 26 1370 Otsego +Michigan 23 26 1390 Ottawa +Michigan 23 26 1410 Presque Isle +Michigan 23 26 1430 Roscommon +Michigan 23 26 1450 Saginaw +Michigan 23 26 1470 St Clair +Michigan 23 26 1490 St Joseph +Michigan 23 26 1510 Sanilac +Michigan 23 26 1530 Schoolcraft +Michigan 23 26 1550 Shiawassee +Michigan 23 26 1570 Tuscola +Michigan 23 26 1590 Van Buren +Michigan 23 26 1610 Washtenaw +Michigan 23 26 1630 Wayne +Michigan 23 26 1650 Wexford +Minnesota 33 27 10 Aitkin +Minnesota 33 27 30 Anoka +Minnesota 33 27 50 Becker +Minnesota 33 27 70 Beltrami +Minnesota 33 27 90 Benton +Minnesota 33 27 110 Big Stone +Minnesota 33 27 130 Blue Earth +Minnesota 33 27 150 Brown +Minnesota 33 27 155 Buchanan +Minnesota 33 27 170 Carlton +Minnesota 33 27 190 Carver +Minnesota 33 27 210 Cass +Minnesota 33 27 230 Chippewa +Minnesota 33 27 250 Chisago +Minnesota 33 27 270 Clay +Minnesota 33 27 290 Clearwater +Minnesota 33 27 310 Cook +Minnesota 33 27 330 Cottonwood +Minnesota 33 27 350 Crow Wing +Minnesota 33 27 370 Dakota +Minnesota 33 27 390 Dodge +Minnesota 33 27 410 Douglas +Minnesota 33 27 430 Faribault +Minnesota 33 27 450 Fillmore +Minnesota 33 27 470 Freeborn +Minnesota 33 27 490 Goodhue +Minnesota 33 27 510 Grant +Minnesota 33 27 530 Hennepin +Minnesota 33 27 550 Houston +Minnesota 33 27 570 Hubbard +Minnesota 33 27 590 Isanti +Minnesota 33 27 610 Itasca +Minnesota 33 27 630 Jackson +Minnesota 33 27 650 Kanabec +Minnesota 33 27 670 Kandiyohi +Minnesota 33 27 690 Kittson/Pembina +Minnesota 33 27 710 Koochiching +Minnesota 33 27 730 Lac Qui Parle +Minnesota 33 27 750 Lake +Minnesota 33 27 770 Lake of the Woods +Minnesota 33 27 790 Le Sueur +Minnesota 33 27 810 Lincoln +Minnesota 33 27 830 Lyon +Minnesota 33 27 850 McLeod +Minnesota 33 27 870 Mahnomen +Minnesota 33 27 875 Mahkahta +Minnesota 33 27 890 Marshall +Minnesota 33 27 910 Martin +Minnesota 33 27 930 Meeker +Minnesota 33 27 950 Mille Lacs +Minnesota 33 27 955 Monongalia +Minnesota 33 27 970 Morrison +Minnesota 33 27 990 Mower +Minnesota 33 27 1010 Murray +Minnesota 33 27 1030 Nicollet +Minnesota 33 27 1050 Nobles +Minnesota 33 27 1070 Norman +Minnesota 33 27 1090 Olmsted +Minnesota 33 27 1110 Otter Tail +Minnesota 33 27 1130 Pennington +Minnesota 33 27 1135 Pierce +Minnesota 33 27 1150 Pine +Minnesota 33 27 1170 Pipestone +Minnesota 33 27 1190 Polk +Minnesota 33 27 1210 Pope +Minnesota 33 27 1230 Ramsey +Minnesota 33 27 1250 Red Lake +Minnesota 33 27 1270 Redwood +Minnesota 33 27 1290 Renville +Minnesota 33 27 1310 Rice +Minnesota 33 27 1330 Rock +Minnesota 33 27 1350 Roseau +Minnesota 33 27 1370 St Louis +Minnesota 33 27 1390 Scott +Minnesota 33 27 1410 Sherburne +Minnesota 33 27 1430 Sibley +Minnesota 33 27 1450 Stearns +Minnesota 33 27 1470 Steele +Minnesota 33 27 1490 Stevens +Minnesota 33 27 1510 Swift +Minnesota 33 27 1530 Todd +Minnesota 33 27 1550 Traverse +Minnesota 33 27 1570 Wabasha +Minnesota 33 27 1590 Wadena +Minnesota 33 27 1595 Wahnata +Minnesota 33 27 1610 Waseca +Minnesota 33 27 1630 Washington +Minnesota 33 27 1650 Watonwan +Minnesota 33 27 1655 White Earth +Minnesota 33 27 1670 Wilkin/Toombs +Minnesota 33 27 1690 Winona +Minnesota 33 27 1710 Wright +Minnesota 33 27 1730 Yellow Medicine +Minnesota 33 27 9010 White Earth Reservation +Mississippi 46 28 10 Adams +Mississippi 46 28 30 Alcorn +Mississippi 46 28 50 Amite +Mississippi 46 28 70 Attala +Mississippi 46 28 90 Benton +Mississippi 46 28 110 Bolivar +Mississippi 46 28 130 Calhoun +Mississippi 46 28 150 Carroll +Mississippi 46 28 170 Chickasaw +Mississippi 46 28 190 Choctaw +Mississippi 46 28 210 Claiborne +Mississippi 46 28 230 Clarke +Mississippi 46 28 250 Clay +Mississippi 46 28 270 Coahoma +Mississippi 46 28 290 Copiah +Mississippi 46 28 310 Covington +Mississippi 46 28 330 De Soto +Mississippi 46 28 350 Forrest +Mississippi 46 28 370 Franklin +Mississippi 46 28 390 George +Mississippi 46 28 410 Greene +Mississippi 46 28 430 Grenada +Mississippi 46 28 450 Hancock +Mississippi 46 28 470 Harrison +Mississippi 46 28 490 Hinds +Mississippi 46 28 510 Holmes +Mississippi 46 28 530 Humphreys +Mississippi 46 28 550 Issaquena +Mississippi 46 28 570 Itawamba +Mississippi 46 28 590 Jackson +Mississippi 46 28 610 Jasper +Mississippi 46 28 630 Jefferson +Mississippi 46 28 650 Jefferson Davis +Mississippi 46 28 670 Jones +Mississippi 46 28 690 Kemper +Mississippi 46 28 710 Lafayette +Mississippi 46 28 730 Lamar +Mississippi 46 28 750 Lauderdale +Mississippi 46 28 770 Lawrence +Mississippi 46 28 790 Leake +Mississippi 46 28 810 Lee +Mississippi 46 28 830 Leflore +Mississippi 46 28 850 Lincoln +Mississippi 46 28 870 Lowndes +Mississippi 46 28 890 Madison +Mississippi 46 28 910 Marion +Mississippi 46 28 930 Marshall +Mississippi 46 28 950 Monroe +Mississippi 46 28 970 Montgomery +Mississippi 46 28 990 Neshoba +Mississippi 46 28 1010 Newton +Mississippi 46 28 1030 Noxubee +Mississippi 46 28 1050 Oktibbeha +Mississippi 46 28 1070 Panola +Mississippi 46 28 1090 Pearl River +Mississippi 46 28 1110 Perry +Mississippi 46 28 1130 Pike +Mississippi 46 28 1150 Pontotoc +Mississippi 46 28 1170 Prentiss +Mississippi 46 28 1190 Quitman +Mississippi 46 28 1210 Rankin +Mississippi 46 28 1230 Scott +Mississippi 46 28 1250 Sharkey +Mississippi 46 28 1270 Simpson +Mississippi 46 28 1290 Smith +Mississippi 46 28 1310 Stone +Mississippi 46 28 1330 Sunflower +Mississippi 46 28 1350 Tallahatchie +Mississippi 46 28 1370 Tate +Mississippi 46 28 1390 Tippah +Mississippi 46 28 1410 Tishomingo +Mississippi 46 28 1430 Tunica +Mississippi 46 28 1450 Union +Mississippi 46 28 1470 Walthall +Mississippi 46 28 1490 Warren +Mississippi 46 28 1510 Washington +Mississippi 46 28 1530 Wayne +Mississippi 46 28 1550 Webster +Mississippi 46 28 1570 Wilkinson +Mississippi 46 28 1590 Winston +Mississippi 46 28 1610 Yalobusha +Mississippi 46 28 1630 Yazoo +Missouri 34 29 10 Adair +Missouri 34 29 30 Andrew +Missouri 34 29 50 Atchison +Missouri 34 29 70 Audrain +Missouri 34 29 90 Barry +Missouri 34 29 110 Barton +Missouri 34 29 130 Bates +Missouri 34 29 150 Benton +Missouri 34 29 170 Bollinger +Missouri 34 29 190 Boone +Missouri 34 29 210 Buchanan +Missouri 34 29 230 Butler +Missouri 34 29 250 Caldwell +Missouri 34 29 270 Callaway +Missouri 34 29 290 Camden +Missouri 34 29 310 Cape Girardeau +Missouri 34 29 330 Carroll +Missouri 34 29 350 Carter +Missouri 34 29 370 Cass/Van Buren +Missouri 34 29 390 Cedar +Missouri 34 29 410 Chariton +Missouri 34 29 430 Christian +Missouri 34 29 450 Clark +Missouri 34 29 470 Clay +Missouri 34 29 490 Clinton +Missouri 34 29 510 Cole +Missouri 34 29 530 Cooper +Missouri 34 29 550 Crawford +Missouri 34 29 570 Dade +Missouri 34 29 590 Dallas +Missouri 34 29 610 Daviess +Missouri 34 29 630 De Kalb +Missouri 34 29 650 Dent +Missouri 34 29 655 Dodge +Missouri 34 29 670 Douglas +Missouri 34 29 690 Dunklin +Missouri 34 29 710 Franklin +Missouri 34 29 730 Gasconade +Missouri 34 29 750 Gentry +Missouri 34 29 770 Greene +Missouri 34 29 790 Grundy +Missouri 34 29 810 Harrison +Missouri 34 29 830 Henry/Rives +Missouri 34 29 850 Hickory +Missouri 34 29 870 Holt +Missouri 34 29 890 Howard +Missouri 34 29 910 Howell +Missouri 34 29 930 Iron +Missouri 34 29 950 Jackson +Missouri 34 29 970 Jasper +Missouri 34 29 990 Jefferson +Missouri 34 29 1010 Johnson +Missouri 34 29 1030 Knox +Missouri 34 29 1050 Laclede +Missouri 34 29 1070 Lafayette +Missouri 34 29 1090 Lawrence +Missouri 34 29 1110 Lewis +Missouri 34 29 1130 Lincoln +Missouri 34 29 1150 Linn +Missouri 34 29 1170 Livingston +Missouri 34 29 1190 McDonald +Missouri 34 29 1210 Macon +Missouri 34 29 1230 Madison +Missouri 34 29 1250 Maries +Missouri 34 29 1270 Marion +Missouri 34 29 1290 Mercer +Missouri 34 29 1310 Miller +Missouri 34 29 1330 Mississippi +Missouri 34 29 1350 Moniteau +Missouri 34 29 1370 Monroe +Missouri 34 29 1390 Montgomery +Missouri 34 29 1410 Morgan +Missouri 34 29 1430 New Madrid +Missouri 34 29 1450 Newton +Missouri 34 29 1470 Nodaway +Missouri 34 29 1490 Oregon +Missouri 34 29 1510 Osage +Missouri 34 29 1530 Ozark +Missouri 34 29 1550 Pemiscot +Missouri 34 29 1570 Perry +Missouri 34 29 1590 Pettis +Missouri 34 29 1610 Phelps +Missouri 34 29 1630 Pike +Missouri 34 29 1650 Platte +Missouri 34 29 1670 Polk +Missouri 34 29 1690 Pulaski +Missouri 34 29 1710 Putnam +Missouri 34 29 1730 Ralls +Missouri 34 29 1750 Randolph +Missouri 34 29 1770 Ray +Missouri 34 29 1790 Reynolds +Missouri 34 29 1810 Ripley +Missouri 34 29 1830 St Charles +Missouri 34 29 1850 St Clair +Missouri 34 29 1870 St Francois +Missouri 34 29 1890 St Louis +Missouri 34 29 1930 Ste Genevieve +Missouri 34 29 1950 Saline +Missouri 34 29 1970 Schuyler +Missouri 34 29 1990 Scotland +Missouri 34 29 2010 Scott +Missouri 34 29 2030 Shannon +Missouri 34 29 2050 Shelby +Missouri 34 29 2070 Stoddard +Missouri 34 29 2090 Stone +Missouri 34 29 2110 Sullivan +Missouri 34 29 2130 Taney +Missouri 34 29 2150 Texas +Missouri 34 29 2170 Vernon +Missouri 34 29 2190 Warren +Missouri 34 29 2210 Washington +Missouri 34 29 2230 Wayne +Missouri 34 29 2250 Webster +Missouri 34 29 2270 Worth +Missouri 34 29 2290 Wright +Missouri 34 29 5100 St Louis City +Montana 64 30 10 Beaverhead +Montana 64 30 30 Big Horn +Montana 64 30 50 Blaine +Montana 64 30 70 Broadwater +Montana 64 30 90 Carbon +Montana 64 30 110 Carter +Montana 64 30 130 Cascade +Montana 64 30 150 Chouteau +Montana 64 30 155 Crow Reservation +Montana 64 30 170 Custer +Montana 64 30 190 Daniels +Montana 64 30 210 Dawson +Montana 64 30 230 Deer Lodge +Montana 64 30 250 Fallon +Montana 64 30 270 Fergus +Montana 64 30 290 Flathead +Montana 64 30 310 Gallatin +Montana 64 30 330 Garfield +Montana 64 30 350 Glacier +Montana 64 30 370 Golden Valley +Montana 64 30 390 Granite +Montana 64 30 410 Hill +Montana 64 30 430 Jefferson +Montana 64 30 450 Judith Basin +Montana 64 30 470 Lake +Montana 64 30 490 Lewis And Clark +Montana 64 30 510 Liberty +Montana 64 30 530 Lincoln +Montana 64 30 550 Mccone +Montana 64 30 570 Madison +Montana 64 30 590 Meagher +Montana 64 30 610 Mineral +Montana 64 30 630 Missoula +Montana 64 30 650 Musselshell +Montana 64 30 670 Park +Montana 64 30 690 Petroleum +Montana 64 30 710 Phillips +Montana 64 30 730 Pondera +Montana 64 30 750 Powder River +Montana 64 30 770 Powell +Montana 64 30 790 Prairie +Montana 64 30 810 Ravalli +Montana 64 30 830 Richland +Montana 64 30 850 Roosevelt +Montana 64 30 870 Rosebud +Montana 64 30 890 Sanders +Montana 64 30 910 Sheridan +Montana 64 30 930 Silver Bow +Montana 64 30 950 Stillwater +Montana 64 30 970 Sweet Grass +Montana 64 30 990 Teton +Montana 64 30 1010 Toole +Montana 64 30 1030 Treasure +Montana 64 30 1050 Valley +Montana 64 30 1070 Wheatland +Montana 64 30 1090 Wibaux +Montana 64 30 1110 Yellowstone +Nebraska 35 31 10 Adams +Nebraska 35 31 30 Antelope +Nebraska 35 31 50 Arthur +Nebraska 35 31 70 Banner +Nebraska 35 31 75 Blackbird +Nebraska 35 31 90 Blaine +Nebraska 35 31 110 Boone +Nebraska 35 31 130 Box Butte +Nebraska 35 31 150 Boyd +Nebraska 35 31 170 Brown +Nebraska 35 31 190 Buffalo +Nebraska 35 31 210 Burt +Nebraska 35 31 230 Butler +Nebraska 35 31 250 Cass +Nebraska 35 31 270 Cedar +Nebraska 35 31 290 Chase +Nebraska 35 31 310 Cherry +Nebraska 35 31 330 Cheyenne +Nebraska 35 31 350 Clay +Nebraska 35 31 370 Colfax +Nebraska 35 31 390 Cuming +Nebraska 35 31 410 Custer +Nebraska 35 31 430 Dakota +Nebraska 35 31 450 Dawes +Nebraska 35 31 470 Dawson +Nebraska 35 31 490 Deuel +Nebraska 35 31 510 Dixon +Nebraska 35 31 530 Dodge +Nebraska 35 31 550 Douglas +Nebraska 35 31 570 Dundy +Nebraska 35 31 590 Fillmore +Nebraska 35 31 610 Franklin +Nebraska 35 31 630 Frontier +Nebraska 35 31 650 Furnas +Nebraska 35 31 670 Gage +Nebraska 35 31 690 Garden +Nebraska 35 31 710 Garfield +Nebraska 35 31 730 Gosper +Nebraska 35 31 750 Grant +Nebraska 35 31 770 Greeley +Nebraska 35 31 790 Hall +Nebraska 35 31 810 Hamilton +Nebraska 35 31 830 Harlan +Nebraska 35 31 835 Harrison +Nebraska 35 31 850 Hayes +Nebraska 35 31 870 Hitchcock +Nebraska 35 31 890 Holt +Nebraska 35 31 910 Hooker +Nebraska 35 31 930 Howard +Nebraska 35 31 935 Jackson +Nebraska 35 31 950 Jefferson +Nebraska 35 31 970 Johnson/Johnston +Nebraska 35 31 975 Jones +Nebraska 35 31 990 Kearney +Nebraska 35 31 1010 Keith +Nebraska 35 31 1030 Keya Paha +Nebraska 35 31 1050 Kimball +Nebraska 35 31 1070 Knox/L'eau Qui Co +Nebraska 35 31 1090 Lancaster +Nebraska 35 31 1110 Lincoln +Nebraska 35 31 1130 Logan +Nebraska 35 31 1150 Loup +Nebraska 35 31 1155 Lyon +Nebraska 35 31 1170 McPherson +Nebraska 35 31 1190 Madison +Nebraska 35 31 1210 Merrick +Nebraska 35 31 1215 Monroe +Nebraska 35 31 1230 Morrill +Nebraska 35 31 1250 Nance +Nebraska 35 31 1270 Nemaha +Nebraska 35 31 1290 Nuckolls +Nebraska 35 31 1310 Otoe +Nebraska 35 31 1330 Pawnee +Nebraska 35 31 1350 Perkins +Nebraska 35 31 1370 Phelps +Nebraska 35 31 1390 Pierce +Nebraska 35 31 1410 Platte +Nebraska 35 31 1430 Polk +Nebraska 35 31 1450 Red Willow +Nebraska 35 31 1470 Richardson +Nebraska 35 31 1490 Rock +Nebraska 35 31 1510 Saline +Nebraska 35 31 1530 Sarpy +Nebraska 35 31 1550 Saunders +Nebraska 35 31 1570 Scotts Bluff +Nebraska 35 31 1590 Seward +Nebraska 35 31 1610 Sheridan +Nebraska 35 31 1630 Sherman +Nebraska 35 31 1635 Shorter +Nebraska 35 31 1650 Sioux +Nebraska 35 31 1670 Stanton +Nebraska 35 31 1675 Taylor +Nebraska 35 31 1690 Thayer +Nebraska 35 31 1710 Thomas +Nebraska 35 31 1730 Thurston +Nebraska 35 31 1750 Valley +Nebraska 35 31 1770 Washington +Nebraska 35 31 1790 Wayne +Nebraska 35 31 1810 Webster +Nebraska 35 31 1830 Wheeler +Nebraska 35 31 1850 York +Nebraska 35 31 9995 Unorganized Territory +Nevada 65 32 5 Carson +Nevada 65 32 10 Churchill +Nevada 65 32 30 Clark +Nevada 65 32 50 Douglas +Nevada 65 32 70 Elko +Nevada 65 32 90 Esmeralda +Nevada 65 32 110 Eureka +Nevada 65 32 130 Humboldt +Nevada 65 32 150 Lander +Nevada 65 32 170 Lincoln +Nevada 65 32 190 Lyon +Nevada 65 32 210 Mineral +Nevada 65 32 230 Nye +Nevada 65 32 250 Pershing +Nevada 65 32 270 Riovirgin +Nevada 65 32 279 Roop +Nevada 65 32 285 St. Marys +Nevada 65 32 290 Storey +Nevada 65 32 310 Washoe +Nevada 65 32 330 White Pine +Nevada 65 32 510 Carson City +New Hampshire 4 33 10 Belknap +New Hampshire 4 33 30 Carroll +New Hampshire 4 33 50 Cheshire +New Hampshire 4 33 70 Coos +New Hampshire 4 33 90 Grafton +New Hampshire 4 33 110 Hillsborough +New Hampshire 4 33 130 Merrimack +New Hampshire 4 33 150 Rockingham +New Hampshire 4 33 170 Strafford +New Hampshire 4 33 190 Sullivan +New Jersey 12 34 10 Atlantic +New Jersey 12 34 30 Bergen +New Jersey 12 34 50 Burlington +New Jersey 12 34 70 Camden +New Jersey 12 34 90 Cape May +New Jersey 12 34 110 Cumberland +New Jersey 12 34 130 Essex +New Jersey 12 34 150 Gloucester +New Jersey 12 34 170 Hudson +New Jersey 12 34 190 Hunterdon +New Jersey 12 34 210 Mercer +New Jersey 12 34 230 Middlesex +New Jersey 12 34 250 Monmouth +New Jersey 12 34 270 Morris +New Jersey 12 34 290 Ocean +New Jersey 12 34 310 Passaic +New Jersey 12 34 330 Salem +New Jersey 12 34 350 Somerset +New Jersey 12 34 370 Sussex +New Jersey 12 34 390 Union +New Jersey 12 34 410 Warren +New Mexico 66 35 10 Bernalillo +New Mexico 66 35 30 Catron +New Mexico 66 35 50 Chaves +New Mexico 66 35 70 Colfax +New Mexico 66 35 90 Curry +New Mexico 66 35 110 DeBaca +New Mexico 66 35 130 Dona Ana +New Mexico 66 35 150 Eddy +New Mexico 66 35 170 Grant +New Mexico 66 35 190 Guadalupe +New Mexico 66 35 210 Harding +New Mexico 66 35 230 Hidalgo +New Mexico 66 35 250 Lea +New Mexico 66 35 255 Lea-Roosevelt +New Mexico 66 35 270 Lincoln +New Mexico 66 35 290 Luna +New Mexico 66 35 310 Mckinley +New Mexico 66 35 330 Mora +New Mexico 66 35 350 Otero +New Mexico 66 35 370 Quay +New Mexico 66 35 390 Rio Arriba +New Mexico 66 35 410 Roosevelt +New Mexico 66 35 430 Sandoval +New Mexico 66 35 450 San Juan +New Mexico 66 35 470 San Miguel +New Mexico 66 35 475 Santa Ana +New Mexico 66 35 490 Santa Fe +New Mexico 66 35 510 Sierra +New Mexico 66 35 530 Socorro +New Mexico 66 35 550 Taos +New Mexico 66 35 570 Torrance +New Mexico 66 35 590 Union +New Mexico 66 35 610 Valencia +New York 13 36 10 Albany +New York 13 36 30 Allegany +New York 13 36 50 Bronx +New York 13 36 70 Broome +New York 13 36 90 Cattaraugus +New York 13 36 110 Cayuga +New York 13 36 130 Chautauqua +New York 13 36 150 Chemung +New York 13 36 170 Chenango +New York 13 36 190 Clinton +New York 13 36 210 Columbia +New York 13 36 230 Cortland +New York 13 36 250 Delaware +New York 13 36 270 Dutchess +New York 13 36 290 Erie +New York 13 36 310 Essex +New York 13 36 330 Franklin +New York 13 36 350 Fulton +New York 13 36 370 Genesee +New York 13 36 390 Greene +New York 13 36 410 Hamilton +New York 13 36 430 Herkimer +New York 13 36 450 Jefferson +New York 13 36 470 Kings +New York 13 36 490 Lewis +New York 13 36 510 Livingston +New York 13 36 530 Madison +New York 13 36 550 Monroe +New York 13 36 570 Montgomery +New York 13 36 590 Nassau +New York 13 36 610 New York +New York 13 36 630 Niagara +New York 13 36 650 Oneida +New York 13 36 670 Onondaga +New York 13 36 690 Ontario +New York 13 36 710 Orange +New York 13 36 730 Orleans +New York 13 36 750 Oswego +New York 13 36 770 Otsego +New York 13 36 790 Putnam +New York 13 36 810 Queens +New York 13 36 830 Rensselaer +New York 13 36 850 Richmond +New York 13 36 870 Rockland +New York 13 36 890 St Lawrence +New York 13 36 910 Saratoga +New York 13 36 930 Schenectady +New York 13 36 950 Schoharie +New York 13 36 970 Schuyler +New York 13 36 990 Seneca +New York 13 36 1010 Steuben +New York 13 36 1030 Suffolk +New York 13 36 1050 Sullivan +New York 13 36 1070 Tioga +New York 13 36 1090 Tompkins +New York 13 36 1110 Ulster +New York 13 36 1130 Warren +New York 13 36 1150 Washington +New York 13 36 1170 Wayne +New York 13 36 1190 Westchester +New York 13 36 1210 Wyoming +New York 13 36 1230 Yates +North Carolina 47 37 10 Alamance +North Carolina 47 37 30 Alexander +North Carolina 47 37 50 Alleghany +North Carolina 47 37 70 Anson +North Carolina 47 37 90 Ashe +North Carolina 47 37 110 Avery +North Carolina 47 37 130 Beaufort +North Carolina 47 37 150 Bertie +North Carolina 47 37 170 Bladen +North Carolina 47 37 190 Brunswick +North Carolina 47 37 210 Buncombe +North Carolina 47 37 230 Burke +North Carolina 47 37 250 Cabarrus +North Carolina 47 37 270 Caldwell +North Carolina 47 37 290 Camden +North Carolina 47 37 310 Carteret +North Carolina 47 37 330 Caswell +North Carolina 47 37 350 Catawba +North Carolina 47 37 370 Chatham +North Carolina 47 37 390 Cherokee +North Carolina 47 37 410 Chowan +North Carolina 47 37 430 Clay +North Carolina 47 37 450 Cleveland +North Carolina 47 37 470 Columbus +North Carolina 47 37 490 Craven +North Carolina 47 37 510 Cumberland +North Carolina 47 37 530 Currituck +North Carolina 47 37 550 Dare +North Carolina 47 37 570 Davidson +North Carolina 47 37 590 Davie +North Carolina 47 37 610 Duplin +North Carolina 47 37 630 Durham +North Carolina 47 37 650 Edgecombe +North Carolina 47 37 670 Forsyth +North Carolina 47 37 690 Franklin +North Carolina 47 37 710 Gaston +North Carolina 47 37 730 Gates +North Carolina 47 37 750 Graham +North Carolina 47 37 770 Granville +North Carolina 47 37 790 Greene +North Carolina 47 37 810 Guilford +North Carolina 47 37 830 Halifax +North Carolina 47 37 850 Harnett +North Carolina 47 37 870 Haywood +North Carolina 47 37 890 Henderson +North Carolina 47 37 910 Hertford +North Carolina 47 37 930 Hoke +North Carolina 47 37 950 Hyde +North Carolina 47 37 970 Iredell +North Carolina 47 37 990 Jackson +North Carolina 47 37 1010 Johnston +North Carolina 47 37 1030 Jones +North Carolina 47 37 1050 Lee +North Carolina 47 37 1070 Lenoir +North Carolina 47 37 1090 Lincoln +North Carolina 47 37 1110 McDowell +North Carolina 47 37 1130 Macon +North Carolina 47 37 1150 Madison +North Carolina 47 37 1170 Martin +North Carolina 47 37 1190 Mecklenburg +North Carolina 47 37 1210 Mitchell +North Carolina 47 37 1230 Montgomery +North Carolina 47 37 1250 Moore +North Carolina 47 37 1270 Nash +North Carolina 47 37 1290 New Hanover +North Carolina 47 37 1310 Northampton +North Carolina 47 37 1330 Onslow +North Carolina 47 37 1350 Orange +North Carolina 47 37 1370 Pamlico +North Carolina 47 37 1390 Pasquotank +North Carolina 47 37 1410 Pender +North Carolina 47 37 1430 Perquimans +North Carolina 47 37 1450 Person +North Carolina 47 37 1470 Pitt +North Carolina 47 37 1490 Polk +North Carolina 47 37 1510 Randolph +North Carolina 47 37 1530 Richmond +North Carolina 47 37 1550 Robeson +North Carolina 47 37 1570 Rockingham +North Carolina 47 37 1590 Rowan +North Carolina 47 37 1610 Rutherford +North Carolina 47 37 1630 Sampson +North Carolina 47 37 1650 Scotland +North Carolina 47 37 1670 Stanly +North Carolina 47 37 1690 Stokes +North Carolina 47 37 1710 Surry +North Carolina 47 37 1730 Swain +North Carolina 47 37 1750 Transylvania +North Carolina 47 37 1770 Tyrrell +North Carolina 47 37 1790 Union +North Carolina 47 37 1810 Vance +North Carolina 47 37 1830 Wake +North Carolina 47 37 1835 Walton +North Carolina 47 37 1850 Warren +North Carolina 47 37 1870 Washington +North Carolina 47 37 1890 Watauga +North Carolina 47 37 1910 Wayne +North Carolina 47 37 1930 Wilkes +North Carolina 47 37 1950 Wilson +North Carolina 47 37 1970 Yadkin +North Carolina 47 37 1990 Yancey +North Dakota 36 38 10 Adams +North Dakota 36 38 30 Barnes +North Dakota 36 38 50 Benson +North Dakota 36 38 70 Billings +North Dakota 36 38 75 Billings-Sioux +North Dakota 36 38 90 Bottineau +North Dakota 36 38 110 Bowman +North Dakota 36 38 115 Buford +North Dakota 36 38 130 Burke +North Dakota 36 38 150 Burleigh +North Dakota 36 38 170 Cass +North Dakota 36 38 190 Cavalier +North Dakota 36 38 195 Church +North Dakota 36 38 210 Dickey +North Dakota 36 38 230 Divide +North Dakota 36 38 250 Dunn +North Dakota 36 38 270 Eddy +North Dakota 36 38 290 Emmons +North Dakota 36 38 295 Flannery +North Dakota 36 38 310 Foster +North Dakota 36 38 315 Garfield +North Dakota 36 38 330 Golden Valley +North Dakota 36 38 350 Grand Forks +North Dakota 36 38 370 Grant +North Dakota 36 38 390 Griggs +North Dakota 36 38 410 Hettinger +North Dakota 36 38 415 Howard +North Dakota 36 38 430 Kidder +North Dakota 36 38 450 La Moure +North Dakota 36 38 470 Logan +North Dakota 36 38 490 McHenry +North Dakota 36 38 510 McIntosh +North Dakota 36 38 530 McKenzie +North Dakota 36 38 550 McLean +North Dakota 36 38 570 Mercer +North Dakota 36 38 590 Morton +North Dakota 36 38 610 Mountrail +North Dakota 36 38 630 Nelson +North Dakota 36 38 650 Oliver +North Dakota 36 38 670 Pembina +North Dakota 36 38 690 Pierce +North Dakota 36 38 710 Ramsey +North Dakota 36 38 730 Ransom +North Dakota 36 38 750 Renville +North Dakota 36 38 770 Richland +North Dakota 36 38 790 Rolette +North Dakota 36 38 810 Sargent +North Dakota 36 38 830 Sheridan +North Dakota 36 38 850 Sioux +North Dakota 36 38 870 Slope +North Dakota 36 38 875 Standing Rock +North Dakota 36 38 890 Stark +North Dakota 36 38 910 Steele +North Dakota 36 38 915 Stevens +North Dakota 36 38 930 Stutsman +North Dakota 36 38 950 Towner +North Dakota 36 38 970 Traill +North Dakota 36 38 985 Wallace +North Dakota 36 38 987 Wallette +North Dakota 36 38 990 Walsh +North Dakota 36 38 1010 Ward +North Dakota 36 38 1030 Wells +North Dakota 36 38 1050 Williams +North Dakota 36 38 9070 Standing Rock Reservation +Ohio 24 39 10 Adams +Ohio 24 39 30 Allen +Ohio 24 39 50 Ashland +Ohio 24 39 70 Ashtabula +Ohio 24 39 90 Athens +Ohio 24 39 110 Auglaize +Ohio 24 39 130 Belmont +Ohio 24 39 150 Brown +Ohio 24 39 170 Butler +Ohio 24 39 190 Carroll +Ohio 24 39 210 Champaign +Ohio 24 39 230 Clark +Ohio 24 39 250 Clermont +Ohio 24 39 270 Clinton +Ohio 24 39 290 Columbiana +Ohio 24 39 310 Coshocton +Ohio 24 39 330 Crawford +Ohio 24 39 350 Cuyahoga +Ohio 24 39 370 Darke +Ohio 24 39 390 Defiance +Ohio 24 39 410 Delaware +Ohio 24 39 430 Erie +Ohio 24 39 450 Fairfield +Ohio 24 39 470 Fayette +Ohio 24 39 490 Franklin +Ohio 24 39 510 Fulton +Ohio 24 39 530 Gallia +Ohio 24 39 550 Geauga +Ohio 24 39 570 Greene +Ohio 24 39 590 Guernsey +Ohio 24 39 610 Hamilton +Ohio 24 39 630 Hancock +Ohio 24 39 650 Hardin +Ohio 24 39 670 Harrison +Ohio 24 39 690 Henry +Ohio 24 39 710 Highland +Ohio 24 39 730 Hocking +Ohio 24 39 750 Holmes +Ohio 24 39 770 Huron +Ohio 24 39 790 Jackson +Ohio 24 39 810 Jefferson +Ohio 24 39 830 Knox +Ohio 24 39 850 Lake +Ohio 24 39 870 Lawrence +Ohio 24 39 890 Licking +Ohio 24 39 910 Logan +Ohio 24 39 930 Lorain +Ohio 24 39 950 Lucas +Ohio 24 39 970 Madison +Ohio 24 39 990 Mahoning +Ohio 24 39 1010 Marion +Ohio 24 39 1030 Medina +Ohio 24 39 1050 Meigs +Ohio 24 39 1070 Mercer +Ohio 24 39 1090 Miami +Ohio 24 39 1110 Monroe +Ohio 24 39 1130 Montgomery +Ohio 24 39 1150 Morgan +Ohio 24 39 1170 Morrow +Ohio 24 39 1190 Muskingum +Ohio 24 39 1210 Noble +Ohio 24 39 1230 Ottawa +Ohio 24 39 1250 Paulding +Ohio 24 39 1270 Perry +Ohio 24 39 1290 Pickaway +Ohio 24 39 1310 Pike +Ohio 24 39 1330 Portage +Ohio 24 39 1350 Preble +Ohio 24 39 1370 Putnam +Ohio 24 39 1390 Richland +Ohio 24 39 1410 Ross +Ohio 24 39 1430 Sandusky +Ohio 24 39 1450 Scioto +Ohio 24 39 1470 Seneca +Ohio 24 39 1490 Shelby +Ohio 24 39 1510 Stark +Ohio 24 39 1530 Summit +Ohio 24 39 1550 Trumbull +Ohio 24 39 1570 Tuscarawas +Ohio 24 39 1590 Union +Ohio 24 39 1610 Van Wert +Ohio 24 39 1630 Vinton +Ohio 24 39 1650 Warren +Ohio 24 39 1670 Washington +Ohio 24 39 1690 Wayne +Ohio 24 39 1710 Williams +Ohio 24 39 1730 Wood +Ohio 24 39 1750 Wyandot +Oklahoma 53 40 10 Adair +Oklahoma 53 40 30 Alfalfa +Oklahoma 53 40 50 Atoka +Oklahoma 53 40 70 Beaver +Oklahoma 53 40 90 Beckham +Oklahoma 53 40 110 Blaine +Oklahoma 53 40 130 Bryan +Oklahoma 53 40 150 Caddo +Oklahoma 53 40 170 Canadian +Oklahoma 53 40 190 Carter +Oklahoma 53 40 210 Cherokee +Oklahoma 53 40 230 Choctaw +Oklahoma 53 40 250 Cimarron +Oklahoma 53 40 270 Cleveland +Oklahoma 53 40 290 Coal +Oklahoma 53 40 310 Comanche +Oklahoma 53 40 330 Cotton +Oklahoma 53 40 350 Craig +Oklahoma 53 40 370 Creek +Oklahoma 53 40 390 Custer +Oklahoma 53 40 410 Delaware +Oklahoma 53 40 430 Dewey +Oklahoma 53 40 450 Ellis +Oklahoma 53 40 470 Garfield +Oklahoma 53 40 490 Garvin +Oklahoma 53 40 510 Grady +Oklahoma 53 40 530 Grant +Oklahoma 53 40 550 Greer +Oklahoma 53 40 570 Harmon +Oklahoma 53 40 590 Harper +Oklahoma 53 40 610 Haskell +Oklahoma 53 40 630 Hughes +Oklahoma 53 40 650 Jackson +Oklahoma 53 40 670 Jefferson +Oklahoma 53 40 690 Johnston +Oklahoma 53 40 710 Kay +Oklahoma 53 40 730 Kingfisher +Oklahoma 53 40 750 Kiowa +Oklahoma 53 40 770 Latimer +Oklahoma 53 40 790 Le Flore +Oklahoma 53 40 810 Lincoln +Oklahoma 53 40 830 Logan +Oklahoma 53 40 850 Love +Oklahoma 53 40 870 Mcclain +Oklahoma 53 40 890 Mccurtain +Oklahoma 53 40 910 Mcintosh +Oklahoma 53 40 930 Major +Oklahoma 53 40 950 Marshall +Oklahoma 53 40 970 Mayes +Oklahoma 53 40 990 Murray +Oklahoma 53 40 1010 Muskogee +Oklahoma 53 40 1030 Noble +Oklahoma 53 40 1050 Nowata +Oklahoma 53 40 1070 Okfuskee +Oklahoma 53 40 1090 Oklahoma +Oklahoma 53 40 1110 Okmulgee +Oklahoma 53 40 1130 Osage +Oklahoma 53 40 1150 Ottawa +Oklahoma 53 40 1170 Pawnee +Oklahoma 53 40 1190 Payne +Oklahoma 53 40 1210 Pittsburg +Oklahoma 53 40 1230 Pontotoc +Oklahoma 53 40 1250 Pottawatomie +Oklahoma 53 40 1270 Pushmataha +Oklahoma 53 40 1290 Roger Mills +Oklahoma 53 40 1310 Rogers +Oklahoma 53 40 1330 Seminole +Oklahoma 53 40 1350 Sequoyah +Oklahoma 53 40 1370 Stephens +Oklahoma 53 40 1390 Texas +Oklahoma 53 40 1410 Tillman +Oklahoma 53 40 1430 Tulsa +Oklahoma 53 40 1450 Wagoner +Oklahoma 53 40 1470 Washington +Oklahoma 53 40 1490 Washita +Oklahoma 53 40 1510 Woods +Oklahoma 53 40 1530 Woodward +Oklahoma 53 40 9010 Cherokee Nation +Oklahoma 53 40 9030 Chickasaw Nation +Oklahoma 53 40 9050 Choctaw Nation +Oklahoma 53 40 9070 Creek Nation +Oklahoma 53 40 9090 Seminole Nation +Oklahoma 53 40 9120 Modoc Nation +Oklahoma 53 40 9130 Ottawa Nation +Oklahoma 53 40 9140 Peoria Nation +Oklahoma 53 40 9160 Seneca Nation +Oklahoma 53 40 9170 Shawnee Nation +Oklahoma 53 40 9180 Wyandotte Nation +Oklahoma 53 40 9210 Kaw Indian Reservation +Oklahoma 53 40 9230 Apache Kiowa and Comanche Reservation +Oklahoma 53 40 9250 Osage Nation +Oklahoma 53 40 9270 Wichita Reservation +Oregon 72 41 10 Baker +Oregon 72 41 30 Benton +Oregon 72 41 50 Clackamas +Oregon 72 41 70 Clatsop +Oregon 72 41 90 Columbia +Oregon 72 41 110 Coos +Oregon 72 41 130 Crook +Oregon 72 41 150 Curry +Oregon 72 41 170 Deschutes +Oregon 72 41 190 Douglas +Oregon 72 41 210 Gilliam +Oregon 72 41 230 Grant +Oregon 72 41 250 Harney +Oregon 72 41 270 Hood River +Oregon 72 41 290 Jackson +Oregon 72 41 310 Jefferson +Oregon 72 41 330 Josephine +Oregon 72 41 350 Klamath +Oregon 72 41 370 Lake +Oregon 72 41 390 Lane +Oregon 72 41 410 Lincoln +Oregon 72 41 430 Linn +Oregon 72 41 450 Malheur +Oregon 72 41 470 Marion +Oregon 72 41 490 Morrow +Oregon 72 41 510 Multnomah +Oregon 72 41 530 Polk +Oregon 72 41 550 Sherman +Oregon 72 41 570 Tillamook +Oregon 72 41 590 Umatilla +Oregon 72 41 605 Union +Oregon 72 41 610 Umpqua +Oregon 72 41 630 Wallowa +Oregon 72 41 650 Wasco +Oregon 72 41 670 Washington +Oregon 72 41 690 Wheeler +Oregon 72 41 710 Yamhill +Pennsylvania 14 42 10 Adams +Pennsylvania 14 42 30 Allegheny +Pennsylvania 14 42 50 Armstrong +Pennsylvania 14 42 70 Beaver +Pennsylvania 14 42 90 Bedford +Pennsylvania 14 42 110 Berks +Pennsylvania 14 42 130 Blair +Pennsylvania 14 42 150 Bradford +Pennsylvania 14 42 170 Bucks +Pennsylvania 14 42 190 Butler +Pennsylvania 14 42 210 Cambria +Pennsylvania 14 42 230 Cameron +Pennsylvania 14 42 250 Carbon +Pennsylvania 14 42 270 Centre +Pennsylvania 14 42 290 Chester +Pennsylvania 14 42 310 Clarion +Pennsylvania 14 42 330 Clearfield +Pennsylvania 14 42 350 Clinton +Pennsylvania 14 42 370 Columbia +Pennsylvania 14 42 390 Crawford +Pennsylvania 14 42 410 Cumberland +Pennsylvania 14 42 430 Dauphin +Pennsylvania 14 42 450 Delaware +Pennsylvania 14 42 470 Elk +Pennsylvania 14 42 490 Erie +Pennsylvania 14 42 510 Fayette +Pennsylvania 14 42 530 Forest +Pennsylvania 14 42 550 Franklin +Pennsylvania 14 42 570 Fulton +Pennsylvania 14 42 590 Greene +Pennsylvania 14 42 610 Huntingdon +Pennsylvania 14 42 630 Indiana +Pennsylvania 14 42 650 Jefferson +Pennsylvania 14 42 670 Juniata +Pennsylvania 14 42 690 Lackawanna +Pennsylvania 14 42 710 Lancaster +Pennsylvania 14 42 730 Lawrence +Pennsylvania 14 42 750 Lebanon +Pennsylvania 14 42 770 Lehigh +Pennsylvania 14 42 790 Luzerne +Pennsylvania 14 42 810 Lycoming +Pennsylvania 14 42 830 Mckean +Pennsylvania 14 42 850 Mercer +Pennsylvania 14 42 870 Mifflin +Pennsylvania 14 42 890 Monroe +Pennsylvania 14 42 910 Montgomery +Pennsylvania 14 42 930 Montour +Pennsylvania 14 42 950 Northampton +Pennsylvania 14 42 970 Northumberland +Pennsylvania 14 42 990 Perry +Pennsylvania 14 42 1010 Philadelphia +Pennsylvania 14 42 1030 Pike +Pennsylvania 14 42 1050 Potter +Pennsylvania 14 42 1070 Schuylkill +Pennsylvania 14 42 1090 Snyder +Pennsylvania 14 42 1110 Somerset +Pennsylvania 14 42 1130 Sullivan +Pennsylvania 14 42 1150 Susquehanna +Pennsylvania 14 42 1170 Tioga +Pennsylvania 14 42 1190 Union +Pennsylvania 14 42 1210 Venango +Pennsylvania 14 42 1230 Warren +Pennsylvania 14 42 1250 Washington +Pennsylvania 14 42 1270 Wayne +Pennsylvania 14 42 1290 Westmoreland +Pennsylvania 14 42 1310 Wyoming +Pennsylvania 14 42 1330 York +Rhode Island 5 44 10 Bristol +Rhode Island 5 44 30 Kent +Rhode Island 5 44 50 Newport +Rhode Island 5 44 70 Providence +Rhode Island 5 44 90 Washington +South Carolina 48 45 10 Abbeville +South Carolina 48 45 30 Aiken +South Carolina 48 45 50 Allendale +South Carolina 48 45 70 Anderson +South Carolina 48 45 90 Bamberg +South Carolina 48 45 110 Barnwell +South Carolina 48 45 130 Beaufort +South Carolina 48 45 150 Berkeley +South Carolina 48 45 170 Calhoun +South Carolina 48 45 190 Charleston +South Carolina 48 45 210 Cherokee +South Carolina 48 45 230 Chester +South Carolina 48 45 250 Chesterfield +South Carolina 48 45 270 Clarendon +South Carolina 48 45 290 Colleton +South Carolina 48 45 310 Darlington +South Carolina 48 45 330 Dillon +South Carolina 48 45 350 Dorchester +South Carolina 48 45 370 Edgefield +South Carolina 48 45 390 Fairfield +South Carolina 48 45 410 Florence +South Carolina 48 45 430 Georgetown +South Carolina 48 45 450 Greenville +South Carolina 48 45 470 Greenwood +South Carolina 48 45 490 Hampton +South Carolina 48 45 510 Horry +South Carolina 48 45 530 Jasper +South Carolina 48 45 550 Kershaw +South Carolina 48 45 570 Lancaster +South Carolina 48 45 590 Laurens +South Carolina 48 45 610 Lee +South Carolina 48 45 630 Lexington +South Carolina 48 45 650 McCormick +South Carolina 48 45 670 Marion +South Carolina 48 45 690 Marlboro +South Carolina 48 45 710 Newberry +South Carolina 48 45 730 Oconee +South Carolina 48 45 750 Orangeburg +South Carolina 48 45 755 Pendleton +South Carolina 48 45 770 Pickens +South Carolina 48 45 790 Richland +South Carolina 48 45 810 Saluda +South Carolina 48 45 830 Spartanburg +South Carolina 48 45 850 Sumter +South Carolina 48 45 870 Union +South Carolina 48 45 890 Williamsburg +South Carolina 48 45 910 York +South Dakota 37 46 10 Armstrong +South Dakota 37 46 30 Aurora +South Dakota 37 46 50 Beadle +South Dakota 37 46 70 Bennett +South Dakota 37 46 90 Bon Homme +South Dakota 37 46 95 Boreman +South Dakota 37 46 110 Brookings +South Dakota 37 46 130 Brown +South Dakota 37 46 150 Brule +South Dakota 37 46 170 Buffalo +South Dakota 37 46 190 Butte +South Dakota 37 46 210 Campbell +South Dakota 37 46 230 Charles Mix +South Dakota 37 46 235 Cheyenne River +South Dakota 37 46 245 Choteau +South Dakota 37 46 250 Clark +South Dakota 37 46 270 Clay +South Dakota 37 46 290 Codington +South Dakota 37 46 310 Corson +South Dakota 37 46 330 Custer +South Dakota 37 46 350 Davison +South Dakota 37 46 370 Day +South Dakota 37 46 390 Deuel +South Dakota 37 46 410 Dewey +South Dakota 37 46 430 Douglas +South Dakota 37 46 455 Ewing +South Dakota 37 46 450 Edmunds +South Dakota 37 46 470 Fall River +South Dakota 37 46 490 Faulk +South Dakota 37 46 510 Grant +South Dakota 37 46 530 Gregory +South Dakota 37 46 550 Haakon +South Dakota 37 46 570 Hamlin +South Dakota 37 46 590 Hand +South Dakota 37 46 610 Hanson +South Dakota 37 46 630 Harding +South Dakota 37 46 650 Hughes +South Dakota 37 46 670 Hutchinson +South Dakota 37 46 690 Hyde +South Dakota 37 46 710 Jackson +South Dakota 37 46 715 Jayne +South Dakota 37 46 730 Jerauld +South Dakota 37 46 750 Jones +South Dakota 37 46 770 Kingsbury +South Dakota 37 46 790 Lake +South Dakota 37 46 810 Lawrence +South Dakota 37 46 830 Lincoln +South Dakota 37 46 850 Lyman +South Dakota 37 46 870 McCook +South Dakota 37 46 890 McPherson +South Dakota 37 46 910 Marshall +South Dakota 37 46 915 Martin +South Dakota 37 46 930 Meade +South Dakota 37 46 950 Mellette +South Dakota 37 46 955 Meyer +South Dakota 37 46 970 Miner +South Dakota 37 46 990 Minnehaha +South Dakota 37 46 1010 Moody +South Dakota 37 46 1015 Nowlin +South Dakota 37 46 1030 Pennington +South Dakota 37 46 1050 Perkins/Spink +South Dakota 37 46 1055 Pine Ridge Reservation (Alternate) +South Dakota 37 46 1070 Potter +South Dakota 37 46 1075 Pratt +South Dakota 37 46 1078 Presho +South Dakota 37 46 1090 Roberts/Sully +South Dakota 37 46 1110 Sanborn +South Dakota 37 46 1115 Schnasse +South Dakota 37 46 1125 Scobey +South Dakota 37 46 1130 Shannon +South Dakota 37 46 1150 Spink +South Dakota 37 46 1170 Stanley +South Dakota 37 46 1175 Sterling +South Dakota 37 46 1190 Sully +South Dakota 37 46 1210 Todd +South Dakota 37 46 1230 Tripp +South Dakota 37 46 1250 Turner +South Dakota 37 46 1270 Union +South Dakota 37 46 1290 Walworth +South Dakota 37 46 1310 Washabaugh +South Dakota 37 46 1330 Washington +South Dakota 37 46 1350 Yankton +South Dakota 37 46 1370 Ziebach +South Dakota 37 46 9010 Cheyenne River Reservation +South Dakota 37 46 9030 Pine Ridge Reservation +South Dakota 37 46 9050 Rosebud Reservation +South Dakota 37 46 9070 Standing Rock Reservation +South Dakota 37 46 9999 Unorganized Territory, Dakota +Tennessee 54 47 10 Anderson +Tennessee 54 47 30 Bedford +Tennessee 54 47 50 Benton +Tennessee 54 47 70 Bledsoe +Tennessee 54 47 90 Blount +Tennessee 54 47 110 Bradley +Tennessee 54 47 130 Campbell +Tennessee 54 47 150 Cannon +Tennessee 54 47 170 Carroll +Tennessee 54 47 190 Carter +Tennessee 54 47 210 Cheatham +Tennessee 54 47 230 Chester +Tennessee 54 47 250 Claiborne +Tennessee 54 47 270 Clay +Tennessee 54 47 290 Cocke +Tennessee 54 47 310 Coffee +Tennessee 54 47 330 Crockett +Tennessee 54 47 350 Cumberland +Tennessee 54 47 370 Davidson +Tennessee 54 47 390 Decatur +Tennessee 54 47 410 De Kalb +Tennessee 54 47 430 Dickson +Tennessee 54 47 450 Dyer +Tennessee 54 47 470 Fayette +Tennessee 54 47 490 Fentress +Tennessee 54 47 510 Franklin +Tennessee 54 47 530 Gibson +Tennessee 54 47 550 Giles +Tennessee 54 47 570 Grainger +Tennessee 54 47 590 Greene +Tennessee 54 47 610 Grundy +Tennessee 54 47 630 Hamblen +Tennessee 54 47 650 Hamilton +Tennessee 54 47 670 Hancock +Tennessee 54 47 690 Hardeman +Tennessee 54 47 710 Hardin +Tennessee 54 47 730 Hawkins +Tennessee 54 47 750 Haywood +Tennessee 54 47 770 Henderson +Tennessee 54 47 790 Henry +Tennessee 54 47 810 Hickman +Tennessee 54 47 830 Houston +Tennessee 54 47 850 Humphreys +Tennessee 54 47 870 Jackson +Tennessee 54 47 875 James +Tennessee 54 47 890 Jefferson +Tennessee 54 47 910 Johnson +Tennessee 54 47 930 Knox +Tennessee 54 47 950 Lake +Tennessee 54 47 970 Lauderdale +Tennessee 54 47 990 Lawrence +Tennessee 54 47 1010 Lewis +Tennessee 54 47 1030 Lincoln +Tennessee 54 47 1050 Loudon +Tennessee 54 47 1070 McMinn +Tennessee 54 47 1090 McNairy +Tennessee 54 47 1110 Macon +Tennessee 54 47 1130 Madison +Tennessee 54 47 1150 Marion +Tennessee 54 47 1170 Marshall +Tennessee 54 47 1190 Maury +Tennessee 54 47 1210 Meigs +Tennessee 54 47 1230 Monroe +Tennessee 54 47 1250 Montgomery +Tennessee 54 47 1270 Moore +Tennessee 54 47 1290 Morgan +Tennessee 54 47 1310 Obion +Tennessee 54 47 1330 Overton +Tennessee 54 47 1350 Perry +Tennessee 54 47 1370 Pickett +Tennessee 54 47 1390 Polk +Tennessee 54 47 1410 Putnam +Tennessee 54 47 1430 Rhea +Tennessee 54 47 1450 Roane +Tennessee 54 47 1470 Robertson +Tennessee 54 47 1490 Rutherford +Tennessee 54 47 1510 Scott +Tennessee 54 47 1530 Sequatchie +Tennessee 54 47 1550 Sevier +Tennessee 54 47 1570 Shelby +Tennessee 54 47 1590 Smith +Tennessee 54 47 1610 Stewart +Tennessee 54 47 1630 Sullivan +Tennessee 54 47 1650 Sumner +Tennessee 54 47 1670 Tipton +Tennessee 54 47 1690 Trousdale +Tennessee 54 47 1710 Unicoi +Tennessee 54 47 1730 Union +Tennessee 54 47 1750 Van Buren +Tennessee 54 47 1770 Warren +Tennessee 54 47 1790 Washington +Tennessee 54 47 1810 Wayne +Tennessee 54 47 1830 Weakley +Tennessee 54 47 1850 White +Tennessee 54 47 1870 Williamson +Tennessee 54 47 1890 Wilson +Texas 49 48 10 Anderson +Texas 49 48 30 Andrews +Texas 49 48 50 Angelina +Texas 49 48 70 Aransas +Texas 49 48 90 Archer +Texas 49 48 110 Armstrong +Texas 49 48 130 Atascosa +Texas 49 48 150 Austin +Texas 49 48 170 Bailey +Texas 49 48 190 Bandera +Texas 49 48 210 Bastrop +Texas 49 48 230 Baylor +Texas 49 48 250 Bee +Texas 49 48 270 Bell +Texas 49 48 290 Bexar +Texas 49 48 310 Blanco +Texas 49 48 330 Borden +Texas 49 48 350 Bosque +Texas 49 48 370 Bowie +Texas 49 48 390 Brazoria +Texas 49 48 410 Brazos +Texas 49 48 430 Brewster +Texas 49 48 450 Briscoe +Texas 49 48 470 Brooks +Texas 49 48 490 Brown +Texas 49 48 495 Buchel +Texas 49 48 510 Burleson +Texas 49 48 530 Burnet +Texas 49 48 550 Caldwell +Texas 49 48 570 Calhoun +Texas 49 48 590 Callahan +Texas 49 48 610 Cameron +Texas 49 48 615 Cameron-Starr-Webb +Texas 49 48 630 Camp +Texas 49 48 650 Carson +Texas 49 48 670 Cass/Davis +Texas 49 48 690 Castro +Texas 49 48 710 Chambers +Texas 49 48 730 Cherokee +Texas 49 48 750 Childress +Texas 49 48 770 Clay +Texas 49 48 790 Cochran +Texas 49 48 810 Coke +Texas 49 48 830 Coleman +Texas 49 48 850 Collin +Texas 49 48 870 Collingsworth +Texas 49 48 890 Colorado +Texas 49 48 910 Comal +Texas 49 48 930 Comanche +Texas 49 48 950 Concho +Texas 49 48 970 Cooke +Texas 49 48 990 Coryell +Texas 49 48 1010 Cottle +Texas 49 48 1030 Crane +Texas 49 48 1050 Crockett +Texas 49 48 1070 Crosby +Texas 49 48 1090 Culberson +Texas 49 48 1110 Dallam +Texas 49 48 1130 Dallas +Texas 49 48 1150 Dawson +Texas 49 48 1170 Deaf Smith +Texas 49 48 1190 Delta +Texas 49 48 1210 Denton +Texas 49 48 1230 De Witt +Texas 49 48 1250 Dickens +Texas 49 48 1270 Dimmit +Texas 49 48 1290 Donley +Texas 49 48 1310 Duval +Texas 49 48 1330 Eastland +Texas 49 48 1350 Ector +Texas 49 48 1370 Edwards +Texas 49 48 1390 Ellis +Texas 49 48 1410 El Paso +Texas 49 48 1415 Encinal +Texas 49 48 1430 Erath +Texas 49 48 1450 Falls +Texas 49 48 1470 Fannin +Texas 49 48 1490 Fayette +Texas 49 48 1510 Fisher +Texas 49 48 1530 Floyd +Texas 49 48 1550 Foard +Texas 49 48 1570 Fort Bend +Texas 49 48 1590 Franklin +Texas 49 48 1610 Freestone +Texas 49 48 1630 Frio +Texas 49 48 1650 Gaines +Texas 49 48 1670 Galveston +Texas 49 48 1690 Garza +Texas 49 48 1710 Gillespie +Texas 49 48 1730 Glasscock +Texas 49 48 1750 Goliad +Texas 49 48 1770 Gonzales +Texas 49 48 1790 Gray +Texas 49 48 1810 Grayson +Texas 49 48 1830 Gregg +Texas 49 48 1850 Grimes +Texas 49 48 1870 Guadalupe +Texas 49 48 1890 Hale +Texas 49 48 1910 Hall +Texas 49 48 1930 Hamilton +Texas 49 48 1950 Hansford +Texas 49 48 1970 Hardeman +Texas 49 48 1990 Hardin +Texas 49 48 2010 Harris +Texas 49 48 2030 Harrison +Texas 49 48 2050 Hartley +Texas 49 48 2070 Haskell +Texas 49 48 2090 Hays +Texas 49 48 2110 Hemphill +Texas 49 48 2130 Henderson +Texas 49 48 2150 Hidalgo +Texas 49 48 2170 Hill +Texas 49 48 2190 Hockley +Texas 49 48 2210 Hood +Texas 49 48 2230 Hopkins +Texas 49 48 2250 Houston +Texas 49 48 2270 Howard +Texas 49 48 2290 Hudspeth +Texas 49 48 2310 Hunt +Texas 49 48 2330 Hutchinson +Texas 49 48 2350 Irion +Texas 49 48 2370 Jack +Texas 49 48 2390 Jackson +Texas 49 48 2410 Jasper +Texas 49 48 2430 Jeff Davis +Texas 49 48 2450 Jefferson +Texas 49 48 2470 Jim Hogg +Texas 49 48 2490 Jim Wells +Texas 49 48 2510 Johnson +Texas 49 48 2530 Jones +Texas 49 48 2550 Karnes +Texas 49 48 2570 Kaufman +Texas 49 48 2590 Kendall +Texas 49 48 2610 Kenedy +Texas 49 48 2630 Kent +Texas 49 48 2650 Kerr +Texas 49 48 2670 Kimble +Texas 49 48 2690 King +Texas 49 48 2710 Kinney +Texas 49 48 2730 Kleberg +Texas 49 48 2750 Knox +Texas 49 48 2770 Lamar +Texas 49 48 2790 Lamb +Texas 49 48 2810 Lampasas +Texas 49 48 2830 La Salle +Texas 49 48 2850 Lavaca +Texas 49 48 2870 Lee +Texas 49 48 2890 Leon +Texas 49 48 2910 Liberty +Texas 49 48 2930 Limestone +Texas 49 48 2950 Lipscomb +Texas 49 48 2970 Live Oak +Texas 49 48 2990 Llano +Texas 49 48 3010 Loving +Texas 49 48 3030 Lubbock +Texas 49 48 3050 Lynn +Texas 49 48 3070 McCulloch +Texas 49 48 3090 McLennan +Texas 49 48 3110 McMullen +Texas 49 48 3130 Madison +Texas 49 48 3150 Marion +Texas 49 48 3170 Martin +Texas 49 48 3190 Mason +Texas 49 48 3210 Matagorda +Texas 49 48 3230 Maverick +Texas 49 48 3250 Medina +Texas 49 48 3270 Menard +Texas 49 48 3290 Midland +Texas 49 48 3310 Milam +Texas 49 48 3330 Mills +Texas 49 48 3350 Mitchell +Texas 49 48 3370 Montague +Texas 49 48 3390 Montgomery +Texas 49 48 3410 Moore +Texas 49 48 3430 Morris +Texas 49 48 3450 Motley +Texas 49 48 3470 Nacogdoches +Texas 49 48 3490 Navarro +Texas 49 48 3510 Newton +Texas 49 48 3530 Nolan +Texas 49 48 3550 Nueces +Texas 49 48 3570 Ochiltree +Texas 49 48 3590 Oldham +Texas 49 48 3610 Orange +Texas 49 48 3630 Palo Pinto +Texas 49 48 3650 Panola +Texas 49 48 3670 Parker +Texas 49 48 3690 Parmer +Texas 49 48 3710 Pecos +Texas 49 48 3730 Polk +Texas 49 48 3750 Potter +Texas 49 48 3770 Presidio +Texas 49 48 3790 Rains +Texas 49 48 3810 Randall +Texas 49 48 3830 Reagan +Texas 49 48 3850 Real +Texas 49 48 3870 Red River +Texas 49 48 3890 Reeves +Texas 49 48 3910 Refugio +Texas 49 48 3930 Roberts +Texas 49 48 3950 Robertson +Texas 49 48 3970 Rockwall +Texas 49 48 3990 Runnels +Texas 49 48 4010 Rusk +Texas 49 48 4030 Sabine +Texas 49 48 4050 San Augustine +Texas 49 48 4070 San Jacinto +Texas 49 48 4090 San Patricio +Texas 49 48 4110 San Saba +Texas 49 48 4130 Schleicher +Texas 49 48 4150 Scurry +Texas 49 48 4170 Shackelford +Texas 49 48 4190 Shelby +Texas 49 48 4210 Sherman +Texas 49 48 4230 Smith +Texas 49 48 4250 Somervell +Texas 49 48 4270 Starr +Texas 49 48 4290 Stephens/Buchanan +Texas 49 48 4310 Sterling +Texas 49 48 4330 Stonewall +Texas 49 48 4350 Sutton +Texas 49 48 4370 Swisher +Texas 49 48 4390 Tarrant +Texas 49 48 4410 Taylor +Texas 49 48 4430 Terrell +Texas 49 48 4450 Terry +Texas 49 48 4470 Throckmorton +Texas 49 48 4490 Titus +Texas 49 48 4510 Tom Green +Texas 49 48 4530 Travis +Texas 49 48 4550 Trinity +Texas 49 48 4570 Tyler +Texas 49 48 4590 Upshur +Texas 49 48 4610 Upton +Texas 49 48 4630 Uvalde +Texas 49 48 4650 Val Verde +Texas 49 48 4670 Van Zandt +Texas 49 48 4690 Victoria +Texas 49 48 4710 Walker +Texas 49 48 4730 Waller +Texas 49 48 4750 Ward +Texas 49 48 4770 Washington +Texas 49 48 4790 Webb +Texas 49 48 4810 Wharton +Texas 49 48 4830 Wheeler +Texas 49 48 4850 Wichita +Texas 49 48 4870 Wilbarger +Texas 49 48 4890 Willacy +Texas 49 48 4910 Williamson +Texas 49 48 4930 Wilson +Texas 49 48 4950 Winkler +Texas 49 48 4970 Wise +Texas 49 48 4990 Wood +Texas 49 48 5010 Yoakum +Texas 49 48 5030 Young +Texas 49 48 5050 Zapata +Texas 49 48 5070 Zavalla +Utah 67 49 10 Beaver +Utah 67 49 30 Box Elder +Utah 67 49 50 Cache +Utah 67 49 70 Carbon +Utah 67 49 75 Cedar +Utah 67 49 90 Daggett +Utah 67 49 110 Davis +Utah 67 49 130 Duchesne +Utah 67 49 150 Emery +Utah 67 49 170 Garfield +Utah 67 49 190 Grand +Utah 67 49 195 Green River +Utah 67 49 210 Iron +Utah 67 49 230 Juab +Utah 67 49 250 Kane +Utah 67 49 270 Millard +Utah 67 49 290 Morgan +Utah 67 49 310 Piute +Utah 67 49 330 Rich +Utah 67 49 350 Salt Lake +Utah 67 49 370 San Juan +Utah 67 49 390 Sanpete +Utah 67 49 410 Sevier +Utah 67 49 415 Shambip +Utah 67 49 430 Summit +Utah 67 49 450 Tooele +Utah 67 49 470 Uintah +Utah 67 49 490 Utah +Utah 67 49 510 Wasatch +Utah 67 49 530 Washington +Utah 67 49 550 Wayne +Utah 67 49 570 Weber +Vermont 6 50 10 Addison +Vermont 6 50 30 Bennington +Vermont 6 50 50 Caledonia +Vermont 6 50 70 Chittenden +Vermont 6 50 90 Essex +Vermont 6 50 110 Franklin +Vermont 6 50 130 Grand Isle +Vermont 6 50 150 Lamoille +Vermont 6 50 170 Orange +Vermont 6 50 190 Orleans +Vermont 6 50 210 Rutland +Vermont 6 50 230 Washington +Vermont 6 50 250 Windham +Vermont 6 50 270 Windsor +Virginia 40 51 10 Accomack +Virginia 40 51 30 Albemarle +Virginia 40 51 50 Alleghany +Virginia 40 51 70 Amelia +Virginia 40 51 90 Amherst +Virginia 40 51 110 Appomattox +Virginia 40 51 130 Arlington/Alexandria +Virginia 40 51 150 Augusta +Virginia 40 51 155 Barbour +Virginia 40 51 170 Bath +Virginia 40 51 190 Bedford +Virginia 40 51 195 Berkeley +Virginia 40 51 210 Bland +Virginia 40 51 215 Boone +Virginia 40 51 230 Botetourt +Virginia 40 51 235 Braxton +Virginia 40 51 245 Brooke +Virginia 40 51 250 Brunswick +Virginia 40 51 270 Buchanan +Virginia 40 51 290 Buckingham +Virginia 40 51 295 Cabell +Virginia 40 51 310 Campbell +Virginia 40 51 330 Caroline +Virginia 40 51 350 Carroll +Virginia 40 51 360 Charles City +Virginia 40 51 370 Charlotte +Virginia 40 51 410 Chesterfield +Virginia 40 51 430 Clarke +Virginia 40 51 450 Craig +Virginia 40 51 470 Culpeper +Virginia 40 51 490 Cumberland +Virginia 40 51 510 Dickenson +Virginia 40 51 530 Dinwiddie +Virginia 40 51 535 Doddridge +Virginia 40 51 550 Elizabeth City +Virginia 40 51 570 Essex +Virginia 40 51 590 Fairfax +Virginia 40 51 610 Fauquier +Virginia 40 51 615 Fayette +Virginia 40 51 630 Floyd +Virginia 40 51 650 Fluvanna +Virginia 40 51 670 Franklin +Virginia 40 51 690 Frederick +Virginia 40 51 710 Giles +Virginia 40 51 715 Gilmer +Virginia 40 51 730 Gloucester +Virginia 40 51 750 Goochland +Virginia 40 51 770 Grayson +Virginia 40 51 775 Greenbrier +Virginia 40 51 790 Greene +Virginia 40 51 810 Greensville +Virginia 40 51 830 Halifax +Virginia 40 51 850 Hanover +Virginia 40 51 855 Hardy +Virginia 40 51 865 Harrison +Virginia 40 51 870 Henrico +Virginia 40 51 890 Henry +Virginia 40 51 910 Highland +Virginia 40 51 930 Isle Of Wight +Virginia 40 51 935 Jackson +Virginia 40 51 950 James City +Virginia 40 51 955 Jefferson +Virginia 40 51 965 Kanawha +Virginia 40 51 970 King And Queen +Virginia 40 51 990 King George +Virginia 40 51 1010 King William +Virginia 40 51 1030 Lancaster +Virginia 40 51 1050 Lee +Virginia 40 51 1055 Lewis +Virginia 40 51 1065 Logan +Virginia 40 51 1070 Loudoun +Virginia 40 51 1090 Louisa +Virginia 40 51 1110 Lunenburg +Virginia 40 51 1130 Madison +Virginia 40 51 1135 City Of Manchester +Virginia 40 51 1141 Marion +Virginia 40 51 1145 Marshall +Virginia 40 51 1148 Mason +Virginia 40 51 1150 Mathews +Virginia 40 51 1170 Mecklenburg +Virginia 40 51 1175 Mercer +Virginia 40 51 1190 Middlesex +Virginia 40 51 1195 Monongalia +Virginia 40 51 1205 Monroe +Virginia 40 51 1210 Montgomery +Virginia 40 51 1215 Morgan +Virginia 40 51 1230 Nansemond +Virginia 40 51 1250 Nelson +Virginia 40 51 1270 New Kent +Virginia 40 51 1275 Nicholas +Virginia 40 51 1290 Norfolk +Virginia 40 51 1310 Northampton +Virginia 40 51 1330 Northumberland +Virginia 40 51 1350 Nottoway +Virginia 40 51 1355 Ohio +Virginia 40 51 1370 Orange +Virginia 40 51 1390 Page +Virginia 40 51 1410 Patrick +Virginia 40 51 1415 Pendleton +Virginia 40 51 1430 Pittsylvania +Virginia 40 51 1445 Pocahontas +Virginia 40 51 1450 Powhatan +Virginia 40 51 1455 Preston +Virginia 40 51 1470 Prince Edward +Virginia 40 51 1490 Prince George +Virginia 40 51 1510 Princess Anne +Virginia 40 51 1530 Prince William +Virginia 40 51 1550 Pulaski +Virginia 40 51 1555 Putnam +Virginia 40 51 1565 Raleigh +Virginia 40 51 1567 Randolph +Virginia 40 51 1570 Rappahannock +Virginia 40 51 1590 Richmond +Virginia 40 51 1595 Ritchie +Virginia 40 51 1610 Roanoke +Virginia 40 51 1630 Rockbridge +Virginia 40 51 1650 Rockingham +Virginia 40 51 1670 Russell +Virginia 40 51 1690 Scott +Virginia 40 51 1710 Shenandoah +Virginia 40 51 1730 Smyth +Virginia 40 51 1750 Southampton +Virginia 40 51 1770 Spotsylvania +Virginia 40 51 1790 Stafford +Virginia 40 51 1810 Surry +Virginia 40 51 1830 Sussex +Virginia 40 51 1835 Taylor +Virginia 40 51 1850 Tazewell +Virginia 40 51 1859 Tyler +Virginia 40 51 1870 Warren +Virginia 40 51 1875 Warwick +Virginia 40 51 1910 Washington +Virginia 40 51 1915 Wayne +Virginia 40 51 1930 Westmoreland +Virginia 40 51 1935 Wetzel +Virginia 40 51 1945 Wirt +Virginia 40 51 1950 Wise +Virginia 40 51 1955 Wood +Virginia 40 51 1965 Wyoming +Virginia 40 51 1970 Wythe +Virginia 40 51 1990 York +Virginia 40 51 5100 Alexandria City +Virginia 40 51 5200 Bristol +Virginia 40 51 5300 Buena Vista +Virginia 40 51 5400 Charlottesville +Virginia 40 51 5600 Clifton Forge +Virginia 40 51 5900 Danville +Virginia 40 51 6300 Fredericksburg +Virginia 40 51 6500 Hampton +Virginia 40 51 6600 Harrisonburg +Virginia 40 51 6700 Hopewell +Virginia 40 51 6800 Lynchburg +Virginia 40 51 6860 Manchester +Virginia 40 51 6900 Martinsville +Virginia 40 51 7000 Newport News +Virginia 40 51 7100 Norfolk City +Virginia 40 51 7300 Petersburg +Virginia 40 51 7400 Portsmouth +Virginia 40 51 7500 Radford +Virginia 40 51 7600 Richmond City +Virginia 40 51 7700 Roanoke City +Virginia 40 51 7850 South Norfolk +Virginia 40 51 7900 Staunton +Virginia 40 51 8000 Suffolk +Virginia 40 51 8300 Williamsburg City +Virginia 40 51 8400 Winchester +Washington 73 53 10 Adams +Washington 73 53 30 Asotin +Washington 73 53 50 Benton +Washington 73 53 70 Chelan +Washington 73 53 90 Clallam +Washington 73 53 110 Clark +Washington 73 53 130 Columbia +Washington 73 53 150 Cowlitz +Washington 73 53 170 Douglas +Washington 73 53 190 Ferry +Washington 73 53 210 Franklin +Washington 73 53 230 Garfield +Washington 73 53 250 Grant +Washington 73 53 270 Grays Harbor/Cheh +Washington 73 53 290 Island +Washington 73 53 310 Jefferson +Washington 73 53 330 King +Washington 73 53 350 Kitsap +Washington 73 53 370 Kittitas +Washington 73 53 390 Klickitat +Washington 73 53 410 Lewis +Washington 73 53 430 Lincoln +Washington 73 53 450 Mason +Washington 73 53 470 Okanogan +Washington 73 53 490 Pacific +Washington 73 53 510 Pend Oreille +Washington 73 53 530 Pierce +Washington 73 53 550 San Juan +Washington 73 53 570 Skagit +Washington 73 53 590 Skamania +Washington 73 53 610 Snohomish +Washington 73 53 630 Spokane +Washington 73 53 650 Stevens +Washington 73 53 670 Thurston +Washington 73 53 690 Wahkiakum +Washington 73 53 710 Walla Walla +Washington 73 53 730 Whatcom +Washington 73 53 750 Whitman +Washington 73 53 770 Yakima +West Virginia 56 54 10 Barbour +West Virginia 56 54 30 Berkeley +West Virginia 56 54 50 Boone +West Virginia 56 54 70 Braxton +West Virginia 56 54 90 Brooke +West Virginia 56 54 110 Cabell +West Virginia 56 54 130 Calhoun +West Virginia 56 54 150 Clay +West Virginia 56 54 170 Doddridge +West Virginia 56 54 190 Fayette +West Virginia 56 54 210 Gilmer +West Virginia 56 54 230 Grant +West Virginia 56 54 250 Greenbrier +West Virginia 56 54 270 Hampshire +West Virginia 56 54 290 Hancock +West Virginia 56 54 310 Hardy +West Virginia 56 54 330 Harrison +West Virginia 56 54 350 Jackson +West Virginia 56 54 370 Jefferson +West Virginia 56 54 390 Kanawha +West Virginia 56 54 410 Lewis +West Virginia 56 54 430 Lincoln +West Virginia 56 54 450 Logan +West Virginia 56 54 470 McDowell +West Virginia 56 54 490 Marion +West Virginia 56 54 510 Marshall +West Virginia 56 54 530 Mason +West Virginia 56 54 550 Mercer +West Virginia 56 54 570 Mineral +West Virginia 56 54 590 Mingo +West Virginia 56 54 610 Monongalia +West Virginia 56 54 630 Monroe +West Virginia 56 54 650 Morgan +West Virginia 56 54 670 Nicholas +West Virginia 56 54 690 Ohio +West Virginia 56 54 710 Pendleton +West Virginia 56 54 730 Pleasants +West Virginia 56 54 750 Pocahontas +West Virginia 56 54 770 Preston +West Virginia 56 54 790 Putnam +West Virginia 56 54 810 Raleigh +West Virginia 56 54 830 Randolph +West Virginia 56 54 850 Ritchie +West Virginia 56 54 870 Roane +West Virginia 56 54 890 Summers +West Virginia 56 54 910 Taylor +West Virginia 56 54 930 Tucker +West Virginia 56 54 950 Tyler +West Virginia 56 54 970 Upshur +West Virginia 56 54 990 Wayne +West Virginia 56 54 1010 Webster +West Virginia 56 54 1030 Wetzel +West Virginia 56 54 1050 Wirt +West Virginia 56 54 1070 Wood +West Virginia 56 54 1090 Wyoming +Wisconsin 25 55 10 Adams +Wisconsin 25 55 30 Ashland +Wisconsin 25 55 50 Barron/Dallas +Wisconsin 25 55 70 Bayfield/La Point +Wisconsin 25 55 90 Brown +Wisconsin 25 55 110 Buffalo +Wisconsin 25 55 130 Burnett +Wisconsin 25 55 150 Calumet +Wisconsin 25 55 170 Chippewa +Wisconsin 25 55 190 Clark +Wisconsin 25 55 210 Columbia +Wisconsin 25 55 230 Crawford +Wisconsin 25 55 250 Dane +Wisconsin 25 55 270 Dodge +Wisconsin 25 55 290 Door +Wisconsin 25 55 310 Douglas +Wisconsin 25 55 330 Dunn +Wisconsin 25 55 350 Eau Claire +Wisconsin 25 55 370 Florence +Wisconsin 25 55 390 Fond Du Lac +Wisconsin 25 55 410 Forest +Wisconsin 25 55 430 Grant +Wisconsin 25 55 450 Green +Wisconsin 25 55 470 Green Lake +Wisconsin 25 55 490 Iowa +Wisconsin 25 55 510 Iron +Wisconsin 25 55 530 Jackson +Wisconsin 25 55 550 Jefferson +Wisconsin 25 55 570 Juneau +Wisconsin 25 55 590 Kenosha +Wisconsin 25 55 610 Kewaunee +Wisconsin 25 55 630 La Crosse +Wisconsin 25 55 650 Lafayette +Wisconsin 25 55 670 Langlade +Wisconsin 25 55 690 Lincoln +Wisconsin 25 55 710 Manitowoc +Wisconsin 25 55 730 Marathon +Wisconsin 25 55 750 Marinette +Wisconsin 25 55 770 Marquette +Wisconsin 25 55 790 Milwaukee +Wisconsin 25 55 810 Monroe +Wisconsin 25 55 830 Oconto +Wisconsin 25 55 850 Oneida +Wisconsin 25 55 870 Outagamie +Wisconsin 25 55 890 Ozaukee +Wisconsin 25 55 910 Pepin +Wisconsin 25 55 930 Pierce +Wisconsin 25 55 950 Polk +Wisconsin 25 55 970 Portage +Wisconsin 25 55 990 Price +Wisconsin 25 55 1010 Racine +Wisconsin 25 55 1030 Richland +Wisconsin 25 55 1050 Rock +Wisconsin 25 55 1070 Rusk +Wisconsin 25 55 1090 St Croix +Wisconsin 25 55 1110 Sauk +Wisconsin 25 55 1130 Sawyer +Wisconsin 25 55 1150 Shawano +Wisconsin 25 55 1170 Sheboygan +Wisconsin 25 55 1190 Taylor +Wisconsin 25 55 1210 Trempealeau +Wisconsin 25 55 1230 Vernon/Bad Ax +Wisconsin 25 55 1250 Vilas +Wisconsin 25 55 1270 Walworth +Wisconsin 25 55 1290 Washburn +Wisconsin 25 55 1310 Washington +Wisconsin 25 55 1330 Waukesha +Wisconsin 25 55 1350 Waupaca +Wisconsin 25 55 1370 Waushara +Wisconsin 25 55 1390 Winnebago +Wisconsin 25 55 1410 Wood +Wyoming 68 56 10 Albany +Wyoming 68 56 30 Big Horn +Wyoming 68 56 50 Campbell +Wyoming 68 56 70 Carbon +Wyoming 68 56 90 Converse +Wyoming 68 56 95 Converse-Natrona +Wyoming 68 56 110 Crook +Wyoming 68 56 130 Fremont +Wyoming 68 56 150 Goshen +Wyoming 68 56 170 Hot Springs +Wyoming 68 56 190 Johnson +Wyoming 68 56 210 Laramie +Wyoming 68 56 230 Lincoln +Wyoming 68 56 250 Natrona +Wyoming 68 56 270 Niobrara +Wyoming 68 56 290 Park +Wyoming 68 56 310 Platte +Wyoming 68 56 330 Sheridan +Wyoming 68 56 350 Sublette +Wyoming 68 56 370 Sweetwater +Wyoming 68 56 390 Teton +Wyoming 68 56 410 Uinta +Wyoming 68 56 430 Washakie +Wyoming 68 56 450 Weston +Wyoming 68 56 455 Yellowstone +Wyoming 68 56 National Park +Other 97 97 9970 Overseas Military diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/domain.ipynb b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/domain.ipynb new file mode 100644 index 0000000..29cf336 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/domain.ipynb @@ -0,0 +1,101 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "specs = json.load(open('competitor_pack/data/colorado-specs.json'))\n", + "df = pd.read_csv('competitor_pack/data/colorado.csv')\n", + "counties = pd.read_csv('counties.txt', usecols=[3], delimiter='\\t', squeeze=True).dropna().astype(int)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "columns = list(df.columns)\n", + "domain = { }\n", + "\n", + "file = open('competitor_pack/data/codebook.cbk')\n", + "col = ''\n", + "\n", + "for line in file:\n", + " if line == '\\n':\n", + " line = next(file)\n", + " col = line.split()[0]\n", + " if col in columns:\n", + " domain[col] = []\n", + " elif col in columns:\n", + " #line = next(file)\n", + " val = line.split()[0]\n", + " try: domain[col].append(int(val))\n", + " except: pass\n", + " else:\n", + " continue\n", + "\n", + "domain['SCHOOL'] = [1,2]\n", + "domain['CHBORN'] += [99]\n", + "domain['HIGRADE'] += [99]\n", + "domain['EDUC'] += [99]\n", + "domain['CLASSWKR'] += [9]\n", + "domain['CLASSWKRD'] += [98, 99]\n", + "domain['OCC'] = domain['UOCC']\n", + "domain['HRSWORK1'] = [i for i in range(99)]\n", + "domain['WKSWORK1'] = [i for i in range(53)]\n", + "domain['SEI'] = list(range(97))\n", + "domain['OCCSCORE'] = list(range(81))\n", + "#domain['SEA'] = range(503) 0, ..., 502 + 990, ..., 992: better to just use maxval\n", + "domain['IND'] = domain['UIND']\n", + "domain['ERSCOR50'] = list(range(1001)) + [9999]\n", + "domain['EDSCOR50'] = list(range(1001)) + [9999]\n", + "domain['NPBOSS50'] = list(range(1001)) + [9999]\n", + "domain['MIGMET5'] = domain['METAREAD'] + [9999]\n", + "domain['CITY'] += [9999]\n", + "domain['MIGCITY5'] = domain['CITY']\n", + "# domain['DURUNEMP'] = range(251) + [999] # 250 is top code, but some are higher\n", + "domain['COUNTY'] = sorted(list(int(c) for c in pd.unique(counties)))\n", + "domain['MIGCOUNTY'] = domain['COUNTY']\n", + "domain['MIGSEA5'] = list(range(503)) + [990, 991, 992, 996, 997, 998, 999]\n", + "\n", + "json.dump(domain, open('domain.json', 'w'))\n", + "\n", + "extra = ['WARD', 'SEA', 'SUPDIST', 'PRESGL', 'MIGSEA5', 'DURUNEMP']" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/domain.json b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/domain.json new file mode 100644 index 0000000..f0e8d52 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/domain.json @@ -0,0 +1 @@ +{"URBAN": [0, 1, 2], "METRO": [0, 1, 2, 3, 4], "METAREA": [0, 4, 6, 8, 12, 16, 20, 22, 24, 28, 32, 38, 40, 44, 45, 46, 47, 48, 50, 52, 56, 58, 60, 64, 68, 72, 73, 74, 76, 78, 84, 86, 87, 88, 92, 96, 100, 102, 104, 108, 112, 114, 115, 116, 120, 124, 126, 128, 130, 131, 132, 133, 135, 136, 140, 144, 148, 152, 154, 156, 158, 160, 162, 164, 166, 168, 172, 174, 176, 180, 184, 188, 190, 192, 193, 195, 196, 200, 202, 203, 204, 208, 212, 216, 218, 219, 220, 224, 228, 229, 231, 232, 233, 234, 236, 240, 244, 252, 256, 258, 260, 262, 264, 265, 266, 267, 268, 270, 271, 272, 275, 276, 284, 288, 290, 292, 297, 298, 299, 300, 301, 304, 306, 308, 312, 315, 316, 318, 320, 324, 328, 329, 330, 332, 335, 336, 340, 344, 348, 350, 352, 356, 358, 359, 360, 361, 362, 366, 368, 371, 372, 374, 376, 380, 381, 384, 385, 387, 388, 392, 396, 398, 400, 404, 408, 410, 412, 415, 420, 424, 428, 432, 436, 440, 441, 442, 444, 448, 452, 460, 464, 468, 472, 476, 480, 484, 488, 489, 490, 492, 494, 500, 504, 508, 512, 514, 516, 517, 519, 520, 524, 528, 532, 533, 534, 535, 536, 540, 546, 548, 552, 556, 560, 564, 566, 572, 576, 579, 580, 588, 591, 592, 595, 596, 599, 601, 602, 603, 608, 612, 616, 620, 628, 632, 636, 640, 644, 645, 646, 648, 652, 656, 658, 660, 664, 666, 668, 669, 672, 674, 676, 678, 680, 682, 684, 688, 689, 692, 696, 698, 700, 704, 708, 712, 714, 716, 720, 724, 732, 736, 740, 744, 746, 747, 748, 749, 750, 751, 752, 756, 760, 761, 762, 764, 768, 772, 776, 780, 784, 788, 792, 800, 804, 805, 808, 812, 814, 816, 820, 824, 828, 832, 836, 840, 844, 848, 852, 856, 860, 864, 868, 873, 875, 876, 878, 880, 884, 888, 892, 894, 896, 900, 904, 908, 914, 916, 920, 924, 926, 927, 928, 932, 934, 936], "METAREAD": [0, 40, 60, 80, 120, 160, 200, 220, 240, 280, 320, 380, 400, 440, 450, 460, 470, 480, 500, 520, 560, 580, 600, 640, 680, 720, 730, 740, 760, 780, 840, 860, 870, 880, 920, 960, 1000, 1010, 1020, 1040, 1080, 1120, 1121, 1122, 1123, 1140, 1150, 1160, 1200, 1240, 1260, 1280, 1281, 1300, 1310, 1320, 1330, 1350, 1360, 1400, 1440, 1480, 1520, 1521, 1540, 1560, 1580, 1600, 1601, 1602, 1603, 1604, 1620, 1640, 1660, 1680, 1720, 1740, 1760, 1800, 1840, 1880, 1900, 1920, 1921, 1930, 1950, 1960, 2000, 2001, 2020, 2030, 2040, 2080, 2081, 2120, 2121, 2160, 2180, 2190, 2200, 2240, 2281, 2290, 2310, 2320, 2330, 2340, 2360, 2400, 2440, 2520, 2560, 2580, 2600, 2620, 2640, 2650, 2660, 2670, 2680, 2700, 2710, 2720, 2750, 2760, 2840, 2880, 2900, 2920, 2970, 2980, 2990, 3000, 3010, 3040, 3060, 3080, 3120, 3121, 3150, 3160, 3161, 3180, 3200, 3240, 3280, 3281, 3282, 3283, 3290, 3300, 3320, 3350, 3360, 3361, 3400, 3440, 3480, 3500, 3520, 3560, 3580, 3590, 3600, 3610, 3620, 3660, 3680, 3710, 3720, 3740, 3760, 3800, 3810, 3840, 3850, 3870, 3880, 3920, 3960, 3980, 4000, 4040, 4080, 4100, 4120, 4150, 4200, 4240, 4280, 4320, 4360, 4400, 4410, 4420, 4440, 4480, 4481, 4482, 4520, 4600, 4640, 4680, 4720, 4760, 4800, 4840, 4880, 4890, 4900, 4920, 4940, 5000, 5040, 5080, 5120, 5140, 5160, 5170, 5190, 5200, 5240, 5280, 5320, 5330, 5340, 5350, 5360, 5400, 5460, 5480, 5481, 5482, 5520, 5560, 5600, 5601, 5602, 5603, 5604, 5605, 5640, 5660, 5720, 5721, 5722, 5760, 5790, 5800, 5880, 5910, 5920, 5950, 5960, 5990, 6010, 6020, 6030, 6080, 6120, 6160, 6200, 6240, 6280, 6281, 6320, 6360, 6400, 6440, 6441, 6450, 6460, 6480, 6481, 6482, 6520, 6560, 6580, 6600, 6640, 6641, 6660, 6680, 6690, 6720, 6740, 6760, 6761, 6780, 6781, 6800, 6820, 6840, 6880, 6895, 6920, 6960, 6961, 6980, 7000, 7040, 7080, 7120, 7140, 7160, 7161, 7200, 7240, 7320, 7360, 7361, 7362, 7400, 7440, 7460, 7470, 7480, 7490, 7500, 7510, 7520, 7560, 7561, 7600, 7610, 7620, 7640, 7680, 7720, 7760, 7800, 7840, 7880, 7920, 8000, 8040, 8050, 8080, 8120, 8140, 8160, 8200, 8240, 8280, 8320, 8360, 8400, 8440, 8480, 8520, 8560, 8600, 8640, 8680, 8730, 8750, 8760, 8780, 8800, 8840, 8880, 8920, 8940, 8960, 9000, 9040, 9080, 9140, 9160, 9200, 9240, 9260, 9270, 9280, 9320, 9340, 9360], "CITY": [0, 1, 2, 3, 4, 5, 6, 7, 10, 30, 50, 51, 52, 70, 90, 91, 100, 110, 120, 130, 131, 132, 140, 150, 160, 161, 162, 163, 170, 171, 190, 210, 230, 231, 250, 270, 271, 272, 273, 275, 280, 281, 282, 283, 284, 290, 310, 311, 312, 313, 330, 331, 340, 341, 342, 343, 344, 345, 346, 347, 350, 370, 371, 390, 391, 410, 411, 430, 450, 470, 490, 491, 510, 530, 550, 551, 552, 553, 554, 570, 590, 610, 630, 640, 650, 651, 652, 660, 670, 671, 672, 673, 680, 690, 695, 700, 701, 702, 703, 704, 705, 706, 710, 711, 712, 720, 721, 730, 740, 741, 742, 743, 750, 760, 761, 770, 771, 772, 780, 790, 791, 792, 793, 794, 795, 800, 801, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 830, 831, 832, 833, 834, 835, 837, 850, 851, 870, 880, 881, 882, 883, 890, 900, 905, 906, 907, 910, 911, 920, 921, 926, 927, 930, 931, 950, 951, 952, 970, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1010, 1020, 1021, 1023, 1024, 1025, 1026, 1027, 1030, 1050, 1060, 1070, 1090, 1091, 1110, 1130, 1140, 1150, 1170, 1171, 1190, 1191, 1192, 1210, 1230, 1250, 1270, 1290, 1291, 1292, 1310, 1311, 1312, 1330, 1340, 1341, 1350, 1351, 1370, 1371, 1372, 1373, 1374, 1375, 1390, 1400, 1410, 1411, 1412, 1420, 1430, 1450, 1451, 1452, 1470, 1490, 1491, 1492, 1493, 1494, 1495, 1496, 1500, 1510, 1520, 1521, 1522, 1523, 1530, 1540, 1545, 1550, 1551, 1552, 1553, 1570, 1571, 1572, 1590, 1591, 1592, 1610, 1630, 1631, 1650, 1670, 1671, 1680, 1690, 1691, 1692, 1693, 1694, 1695, 1710, 1711, 1713, 1730, 1750, 1751, 1752, 1753, 1754, 1755, 1770, 1790, 1791, 1792, 1800, 1810, 1830, 1831, 1832, 1833, 1834, 1850, 1860, 1870, 1890, 1891, 1892, 1893, 1910, 1930, 1931, 1940, 1950, 1951, 1952, 1970, 1971, 1972, 1973, 1974, 1990, 2010, 2030, 2040, 2050, 2051, 2055, 2060, 2061, 2062, 2070, 2071, 2072, 2073, 2074, 2075, 2076, 2080, 2090, 2091, 2092, 2110, 2130, 2131, 2150, 2170, 2190, 2210, 2211, 2212, 2213, 2214, 2220, 2221, 2222, 2230, 2240, 2241, 2242, 2250, 2260, 2270, 2271, 2273, 2274, 2275, 2280, 2281, 2290, 2300, 2301, 2302, 2303, 2310, 2311, 2330, 2350, 2351, 2352, 2353, 2354, 2355, 2356, 2357, 2358, 2359, 2360, 2370, 2390, 2391, 2392, 2393, 2394, 2400, 2410, 2411, 2430, 2435, 2440, 2441, 2450, 2470, 2471, 2472, 2473, 2489, 2490, 2491, 2510, 2511, 2512, 2513, 2514, 2515, 2516, 2517, 2520, 2530, 2531, 2540, 2541, 2550, 2551, 2570, 2571, 2572, 2573, 2574, 2575, 2576, 2577, 2578, 2579, 2580, 2581, 2582, 2583, 2584, 2590, 2591, 2610, 2630, 2650, 2670, 2680, 2681, 2682, 2683, 2690, 2691, 2692, 2693, 2710, 2711, 2712, 2713, 2725, 2730, 2731, 2740, 2750, 2751, 2752, 2753, 2754, 2755, 2756, 2757, 2770, 2780, 2781, 2790, 2791, 2792, 2810, 2811, 2830, 2850, 2851, 2870, 2871, 2872, 2873, 2874, 2875, 2890, 2891, 2892, 2910, 2930, 2950, 2951, 2960, 2961, 2962, 2963, 2970, 2990, 3010, 3011, 3012, 3013, 3014, 3015, 3020, 3030, 3050, 3051, 3052, 3070, 3071, 3090, 3091, 3110, 3111, 3130, 3131, 3132, 3133, 3134, 3150, 3151, 3160, 3161, 3170, 3190, 3191, 3210, 3230, 3231, 3250, 3260, 3270, 3271, 3272, 3273, 3290, 3291, 3292, 3293, 3294, 3310, 3311, 3312, 3313, 3330, 3350, 3370, 3380, 3390, 3391, 3392, 3393, 3394, 3395, 3396, 3400, 3405, 3410, 3430, 3440, 3450, 3451, 3470, 3471, 3480, 3481, 3482, 3490, 3510, 3511, 3512, 3513, 3520, 3521, 3522, 3530, 3540, 3550, 3551, 3560, 3570, 3590, 3610, 3630, 3631, 3632, 3633, 3634, 3635, 3638, 3639, 3650, 3670, 3680, 3690, 3691, 3692, 3693, 3710, 3730, 3750, 3765, 3770, 3771, 3772, 3790, 3800, 3810, 3830, 3850, 3870, 3871, 3890, 3891, 3910, 3911, 3912, 3913, 3914, 3915, 3929, 3930, 3931, 3932, 3933, 3934, 3940, 3950, 3951, 3952, 3953, 3954, 3955, 3956, 3957, 3958, 3959, 3960, 3961, 3962, 3963, 3964, 3970, 3971, 3990, 3991, 3992, 3993, 4010, 4011, 4030, 4040, 4041, 4050, 4070, 4090, 4110, 4120, 4121, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4130, 4150, 4151, 4160, 4161, 4162, 4163, 4170, 4190, 4210, 4211, 4212, 4213, 4214, 4230, 4250, 4251, 4252, 4253, 4254, 4255, 4256, 4260, 4270, 4290, 4291, 4310, 4311, 4312, 4313, 4330, 4331, 4350, 4351, 4370, 4390, 4410, 4411, 4413, 4414, 4415, 4416, 4420, 4430, 4450, 4451, 4452, 4470, 4490, 4510, 4511, 4530, 4550, 4570, 4571, 4590, 4610, 4611, 4630, 4650, 4670, 4690, 4710, 4730, 4750, 4770, 4771, 4772, 4790, 4791, 4792, 4810, 4811, 4820, 4830, 4831, 4832, 4833, 4834, 4835, 4836, 4837, 4838, 4839, 4840, 4841, 4842, 4843, 4845, 4850, 4860, 4870, 4890, 4900, 4901, 4902, 4905, 4910, 4930, 4950, 4970, 4971, 4972, 4990, 4991, 4992, 4993, 4994, 4995, 4996, 5010, 5011, 5012, 5030, 5040, 5050, 5051, 5070, 5090, 5091, 5092, 5110, 5111, 5112, 5113, 5114, 5116, 5117, 5118, 5119, 5121, 5122, 5123, 5124, 5125, 5130, 5140, 5150, 5170, 5180, 5190, 5210, 5230, 5231, 5232, 5233, 5240, 5250, 5255, 5269, 5270, 5271, 5290, 5291, 5310, 5311, 5330, 5331, 5332, 5333, 5334, 5335, 5341, 5350, 5351, 5352, 5353, 5354, 5370, 5390, 5391, 5409, 5410, 5411, 5412, 5413, 5414, 5415, 5430, 5450, 5451, 5460, 5470, 5471, 5480, 5481, 5490, 5491, 5500, 5510, 5511, 5530, 5550, 5570, 5590, 5591, 5610, 5630, 5650, 5660, 5670, 5671, 5690, 5710, 5730, 5731, 5750, 5751, 5752, 5770, 5790, 5791, 5792, 5810, 5811, 5830, 5850, 5870, 5871, 5872, 5873, 5874, 5890, 5910, 5930, 5931, 5932, 5933, 5950, 5970, 5971, 5972, 5973, 5974, 5990, 5991, 5992, 5993, 5994, 5995, 6010, 6011, 6012, 6013, 6014, 6030, 6050, 6070, 6090, 6110, 6130, 6150, 6170, 6171, 6172, 6190, 6191, 6192, 6210, 6211, 6220, 6230, 6231, 6250, 6260, 6270, 6280, 6281, 6282, 6290, 6300, 6310, 6311, 6312, 6320, 6321, 6322, 6330, 6335, 6340, 6350, 6351, 6352, 6353, 6354, 6360, 6370, 6390, 6410, 6430, 6431, 6432, 6433, 6434, 6435, 6437, 6438, 6440, 6450, 6451, 6452, 6453, 6470, 6471, 6472, 6490, 6500, 6510, 6530, 6550, 6570, 6590, 6591, 6592, 6593, 6594, 6595, 6610, 6611, 6612, 6613, 6614, 6615, 6616, 6617, 6620, 6630, 6640, 6650, 6670, 6690, 6691, 6692, 6693, 6710, 6730, 6731, 6732, 6733, 6734, 6750, 6770, 6771, 6772, 6789, 6790, 6791, 6792, 6793, 6794, 6795, 6796, 6797, 6798, 6799, 6810, 6830, 6831, 6832, 6833, 6850, 6870, 6871, 6872, 6890, 6910, 6911, 6912, 6913, 6930, 6950, 6951, 6952, 6953, 6954, 6960, 6970, 6971, 6990, 6991, 6992, 7000, 7010, 7011, 7030, 7050, 7070, 7071, 7072, 7073, 7074, 7079, 7080, 7081, 7082, 7083, 7084, 7090, 7091, 7092, 7093, 7100, 7110, 7111, 7112, 7120, 7121, 7122, 7123, 7130, 7140, 7150, 7151, 7152, 7153, 7170, 7180, 7190, 7191, 7210, 7230, 7231, 7241, 7242, 7250, 7270, 7290, 7310, 7311, 7312, 7313, 7314, 7315, 7316, 7317, 7318, 7319, 7320, 7321, 7322, 7323, 7324, 7325, 7326, 7327, 7328, 7329, 7330, 7331, 7332, 7333, 7334, 7335, 7340, 7350, 7351, 7352, 7353, 7370, 7371, 7372, 7373, 7374, 7375, 7376, 7377, 7390, 7400, 7401, 7402, 7410, 7430, 7450, 7451, 7460, 7470, 7471, 7472, 7490, 7510, 7511, 7512, 7513, 7514, 7515, 7516, 7530, 7531, 7532, 7533, 7534, 7535, 7550, 7551, 7570, 7571, 7572, 7573, 7590, 7610, 7630, 7631, 7650, 9999], "SIZEPL": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90], "GQ": [0, 1, 2, 3, 4, 5, 6], "GQTYPE": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "GQTYPED": [0, 10, 20, 100, 200, 210, 211, 212, 213, 220, 221, 230, 240, 250, 260, 300, 400, 410, 411, 412, 413, 420, 421, 430, 431, 432, 440, 441, 450, 451, 452, 460, 461, 470, 471, 472, 480, 481, 482, 491, 492, 493, 494, 495, 496, 500, 501, 502, 600, 601, 602, 603, 604, 700, 701, 800, 801, 802, 803, 804, 900, 901, 910, 911, 912, 913, 914, 920, 921, 922, 923, 924, 931, 932, 933, 934, 935, 936, 937, 940, 941, 942, 943, 944, 945, 946, 947, 948, 950, 955, 960, 999], "GQFUNDS": [0, 11, 12, 13, 14, 15, 16, 21, 22, 23, 24, 25, 99], "FARM": [0, 1, 2], "OWNERSHP": [0, 1, 2], "OWNERSHPD": [0, 10, 11, 12, 13, 20, 21, 22], "SPLIT": [0, 1], "SLREC": [1, 2], "RESPONDT": [0, 1, 2], "FAMSIZE": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "NCHLT5": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "SEX": [1, 2], "AGE": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 129, 130, 135], "AGEMONTH": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 98, 99], "MARST": [1, 2, 3, 4, 5, 6], "MARRNO": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "AGEMARR": [0, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99], "CHBORN": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 61, 87, 99], "RACE": [1, 2, 3, 4, 5, 6, 7, 8, 9], "RACED": [100, 110, 120, 130, 140, 150, 200, 210, 300, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 328, 329, 330, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 370, 371, 372, 373, 374, 375, 379, 398, 399, 400, 410, 420, 500, 600, 610, 620, 630, 631, 632, 634, 640, 641, 642, 643, 650, 651, 652, 653, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 698, 699, 700, 801, 802, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 830, 831, 832, 833, 834, 835, 836, 837, 838, 840, 841, 842, 845, 850, 851, 852, 853, 854, 855, 856, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 880, 881, 882, 883, 884, 885, 886, 887, 890, 891, 892, 893, 899, 901, 902, 903, 904, 905, 906, 907, 910, 911, 912, 913, 914, 915, 916, 917, 920, 921, 922, 923, 925, 930, 931, 932, 933, 934, 935, 940, 941, 942, 943, 944, 949, 950, 951, 952, 953, 954, 955, 960, 961, 962, 963, 964, 970, 971, 972, 973, 974, 975, 976, 980, 981, 982, 983, 984, 985, 986, 989, 990, 991, 996], "HISPAN": [0, 1, 2, 3, 4, 9], "HISPAND": [0, 100, 102, 103, 104, 105, 106, 107, 200, 300, 401, 402, 411, 412, 413, 414, 415, 416, 417, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 465, 470, 480, 490, 491, 492, 493, 494, 495, 496, 498, 499, 900], "BPL": [1, 2, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 55, 56, 90, 99, 100, 105, 110, 115, 120, 150, 155, 160, 199, 200, 210, 250, 260, 299, 300, 400, 401, 402, 403, 404, 405, 410, 411, 412, 413, 414, 419, 420, 421, 422, 423, 424, 425, 426, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 465, 499, 500, 501, 502, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 599, 600, 700, 710, 800, 900, 950, 999], "BPLD": [100, 200, 400, 500, 600, 800, 900, 1000, 1100, 1200, 1300, 1500, 1600, 1610, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3510, 3600, 3700, 3800, 3900, 4000, 4010, 4100, 4200, 4400, 4500, 4600, 4610, 4700, 4800, 4900, 4910, 5000, 5100, 5300, 5400, 5500, 5600, 5610, 9000, 9900, 10000, 10010, 10500, 11000, 11500, 11510, 11520, 11530, 12000, 12010, 12020, 12030, 12040, 12041, 12050, 12051, 12052, 12053, 12054, 12055, 12056, 12090, 12091, 12092, 15000, 15010, 15011, 15013, 15015, 15017, 15019, 15020, 15021, 15030, 15031, 15032, 15040, 15050, 15051, 15052, 15060, 15070, 15080, 15081, 15082, 15083, 15500, 16000, 16010, 16020, 16030, 16040, 16050, 16060, 19900, 20000, 21000, 21010, 21020, 21030, 21040, 21050, 21060, 21070, 21071, 21090, 25000, 26000, 26010, 26020, 26030, 26040, 26041, 26042, 26043, 26044, 26045, 26046, 26047, 26048, 26049, 26050, 26051, 26052, 26053, 26054, 26055, 26056, 26057, 26058, 26059, 26060, 26061, 26069, 26070, 26071, 26072, 26073, 26074, 26075, 26076, 26077, 26079, 26080, 26081, 26082, 26083, 26089, 26090, 26091, 26092, 26093, 26094, 26095, 29900, 30000, 30005, 30010, 30015, 30020, 30025, 30030, 30035, 30040, 30045, 30050, 30055, 30060, 30065, 30090, 30091, 40000, 40010, 40100, 40200, 40300, 40400, 40410, 40411, 40412, 40500, 41000, 41010, 41011, 41012, 41020, 41100, 41200, 41300, 41400, 41410, 41900, 42000, 42100, 42110, 42111, 42112, 42200, 42300, 42400, 42500, 42600, 42900, 43000, 43100, 43200, 43300, 43310, 43320, 43330, 43400, 43500, 43600, 43610, 43620, 43630, 43640, 43700, 43800, 43900, 44000, 45000, 45010, 45020, 45030, 45040, 45050, 45060, 45070, 45080, 45100, 45200, 45210, 45211, 45212, 45213, 45300, 45301, 45302, 45303, 45310, 45311, 45312, 45313, 45314, 45315, 45316, 45317, 45318, 45319, 45320, 45321, 45322, 45323, 45324, 45325, 45326, 45327, 45328, 45329, 45330, 45331, 45332, 45333, 45340, 45341, 45342, 45344, 45345, 45346, 45347, 45348, 45349, 45350, 45351, 45352, 45353, 45360, 45361, 45362, 45400, 45500, 45510, 45511, 45520, 45521, 45522, 45523, 45524, 45525, 45526, 45530, 45600, 45610, 45700, 45710, 45720, 45730, 45740, 45750, 45760, 45770, 45780, 45790, 45800, 45900, 46000, 46100, 46200, 46300, 46500, 46510, 46520, 46521, 46530, 46540, 46541, 46542, 46543, 46544, 46545, 46546, 46547, 46548, 46590, 49900, 50000, 50010, 50020, 50030, 50040, 50100, 50200, 50210, 50220, 50900, 51000, 51100, 51200, 51210, 51220, 51300, 51400, 51500, 51600, 51700, 51800, 51900, 51910, 52000, 52100, 52110, 52120, 52130, 52140, 52150, 52200, 52300, 52400, 53000, 53100, 53200, 53210, 53300, 53400, 53410, 53420, 53430, 53440, 53500, 53600, 53700, 53800, 53900, 54000, 54100, 54200, 54210, 54220, 54300, 54400, 54500, 54600, 54700, 54800, 54900, 55000, 59900, 60000, 60010, 60011, 60012, 60013, 60014, 60015, 60016, 60017, 60019, 60020, 60021, 60022, 60023, 60024, 60025, 60026, 60027, 60028, 60029, 60030, 60031, 60032, 60033, 60034, 60038, 60039, 60040, 60041, 60042, 60043, 60044, 60045, 60046, 60047, 60048, 60049, 60050, 60051, 60052, 60053, 60054, 60055, 60056, 60057, 60058, 60059, 60060, 60061, 60062, 60063, 60064, 60065, 60070, 60071, 60072, 60073, 60074, 60075, 60076, 60077, 60078, 60079, 60080, 60081, 60082, 60090, 60091, 60092, 60093, 60094, 60095, 60096, 60099, 70000, 70010, 70011, 70012, 70013, 70014, 70020, 71000, 71010, 71012, 71013, 71014, 71015, 71016, 71017, 71018, 71020, 71022, 71023, 71024, 71025, 71026, 71027, 71028, 71029, 71032, 71033, 71034, 71039, 71040, 71041, 71042, 71043, 71044, 71045, 71046, 71047, 71048, 71049, 71050, 71090, 80000, 80010, 80020, 80030, 80040, 80050, 90000, 90010, 90011, 90020, 90021, 90022, 95000, 99900], "MBPL": [0, 1, 2, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 55, 56, 90, 99, 100, 105, 110, 115, 120, 150, 155, 160, 199, 200, 210, 250, 260, 299, 300, 400, 401, 402, 403, 404, 405, 410, 411, 412, 413, 414, 419, 420, 421, 422, 423, 424, 425, 426, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 465, 499, 500, 501, 502, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 599, 600, 700, 710, 900, 950, 997, 999], "MBPLD": [0, 100, 200, 400, 500, 600, 800, 900, 1000, 1100, 1200, 1300, 1500, 1600, 1610, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3510, 3600, 3700, 3800, 3900, 4000, 4010, 4100, 4200, 4400, 4500, 4600, 4610, 4700, 4800, 4900, 4910, 5000, 5100, 5300, 5400, 5500, 5600, 5610, 9000, 9900, 10000, 10010, 10500, 11000, 11500, 11510, 11520, 11530, 12000, 12010, 12020, 12030, 12040, 12041, 12050, 12051, 12052, 12053, 12054, 12055, 12056, 12090, 12091, 12092, 15000, 15010, 15011, 15013, 15015, 15017, 15019, 15020, 15021, 15030, 15031, 15032, 15040, 15050, 15051, 15052, 15060, 15070, 15080, 15081, 15082, 15083, 15500, 16000, 16010, 16020, 16030, 16040, 16050, 16060, 19900, 20000, 21000, 21010, 21020, 21030, 21040, 21050, 21060, 21070, 21071, 21090, 25000, 26000, 26010, 26020, 26030, 26040, 26041, 26042, 26043, 26044, 26045, 26046, 26047, 26048, 26049, 26050, 26051, 26052, 26053, 26054, 26055, 26056, 26057, 26058, 26059, 26060, 26061, 26069, 26070, 26071, 26072, 26073, 26074, 26075, 26076, 26077, 26079, 26080, 26081, 26082, 26083, 26089, 26090, 26091, 26092, 26093, 26094, 26095, 29900, 30000, 30005, 30010, 30015, 30020, 30025, 30030, 30035, 30040, 30045, 30050, 30055, 30060, 30065, 30090, 30091, 40000, 40010, 40100, 40200, 40300, 40400, 40410, 40411, 40412, 40500, 41000, 41010, 41011, 41012, 41020, 41100, 41200, 41300, 41400, 41410, 41900, 42000, 42100, 42110, 42111, 42112, 42200, 42300, 42400, 42500, 42600, 42900, 43000, 43100, 43200, 43300, 43310, 43320, 43330, 43400, 43500, 43600, 43610, 43620, 43630, 43640, 43700, 43800, 43900, 44000, 45000, 45010, 45020, 45030, 45040, 45050, 45060, 45070, 45080, 45100, 45200, 45210, 45211, 45212, 45213, 45300, 45301, 45310, 45311, 45312, 45313, 45314, 45315, 45316, 45317, 45318, 45319, 45320, 45321, 45322, 45323, 45324, 45325, 45326, 45327, 45328, 45329, 45330, 45331, 45332, 45333, 45340, 45341, 45342, 45343, 45344, 45345, 45346, 45347, 45348, 45349, 45350, 45351, 45352, 45353, 45360, 45361, 45362, 45400, 45500, 45510, 45511, 45520, 45521, 45522, 45523, 45524, 45525, 45526, 45530, 45600, 45610, 45700, 45710, 45720, 45730, 45740, 45750, 45760, 45770, 45780, 45790, 45800, 45900, 46000, 46100, 46200, 46300, 46500, 46510, 46520, 46521, 46530, 46540, 46541, 46542, 46543, 46544, 46545, 46546, 46547, 46548, 46590, 49900, 50000, 50010, 50020, 50030, 50040, 50100, 50200, 50210, 50220, 50900, 51000, 51100, 51200, 51210, 51220, 51300, 51400, 51500, 51600, 51700, 51800, 51900, 51910, 52000, 52100, 52110, 52120, 52130, 52140, 52150, 52200, 52300, 52400, 53000, 53100, 53200, 53210, 53300, 53400, 53420, 53430, 53440, 53410, 53500, 53600, 53700, 53800, 53900, 54000, 54100, 54200, 54210, 54220, 54300, 54400, 54500, 54600, 54700, 54800, 54900, 55000, 59900, 60000, 60010, 60011, 60012, 60013, 60014, 60015, 60016, 60017, 60019, 60020, 60021, 60022, 60023, 60024, 60025, 60026, 60027, 60028, 60029, 60030, 60031, 60032, 60033, 60034, 60038, 60039, 60040, 60041, 60042, 60043, 60044, 60045, 60046, 60047, 60048, 60049, 60050, 60051, 60052, 60053, 60054, 60055, 60056, 60057, 60058, 60059, 60060, 60061, 60062, 60063, 60064, 60065, 60070, 60071, 60072, 60073, 60074, 60075, 60076, 60077, 60078, 60079, 60080, 60081, 60082, 60090, 60091, 60092, 60093, 60094, 60095, 60096, 60099, 70000, 70010, 70011, 70012, 70013, 70014, 70020, 71000, 71010, 71012, 71013, 71014, 71016, 71017, 71018, 71020, 71021, 71022, 71023, 71024, 71025, 71026, 71027, 71028, 71029, 71032, 71033, 71034, 71039, 71040, 71041, 71042, 71043, 71044, 71045, 71046, 71047, 71048, 71049, 71050, 71090, 80000, 80010, 80020, 80030, 80040, 80050, 90000, 90010, 90011, 90020, 90021, 90022, 95000, 99700, 99900], "FBPL": [0, 1, 2, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 55, 56, 90, 99, 100, 105, 110, 115, 120, 150, 155, 160, 199, 200, 210, 250, 260, 299, 300, 400, 401, 402, 403, 404, 405, 406, 410, 411, 412, 413, 414, 419, 420, 421, 422, 423, 424, 425, 426, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 465, 499, 500, 501, 502, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 599, 600, 700, 710, 900, 950, 997, 998, 999], "FBPLD": [0, 100, 200, 400, 500, 600, 800, 900, 1000, 1100, 1200, 1300, 1500, 1600, 1610, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3510, 3600, 3700, 3800, 3900, 4000, 4010, 4100, 4200, 4400, 4500, 4600, 4610, 4700, 4800, 4900, 4910, 5000, 5100, 5300, 5400, 5500, 5600, 5610, 9000, 9900, 10000, 10010, 10500, 11000, 11500, 11510, 11520, 11530, 12000, 12010, 12020, 12030, 12040, 12041, 12050, 12051, 12052, 12053, 12054, 12055, 12056, 12090, 12091, 12092, 15000, 15010, 15011, 15013, 15015, 15017, 15019, 15020, 15021, 15030, 15031, 15032, 15040, 15042, 15050, 15051, 15052, 15060, 15070, 15080, 15081, 15082, 15083, 15500, 16000, 16010, 16020, 16030, 16040, 16050, 16060, 19900, 20000, 21000, 21010, 21020, 21030, 21040, 21050, 21060, 21070, 21071, 21090, 25000, 26000, 26010, 26020, 26030, 26040, 26041, 26042, 26043, 26044, 26045, 26046, 26047, 26048, 26049, 26050, 26051, 26052, 26053, 26054, 26055, 26056, 26057, 26058, 26059, 26060, 26061, 26069, 26070, 26071, 26072, 26073, 26074, 26075, 26076, 26077, 26079, 26080, 26081, 26082, 26083, 26089, 26090, 26091, 26092, 26093, 26094, 26095, 29900, 30000, 30005, 30010, 30015, 30020, 30025, 30030, 30035, 30040, 30045, 30050, 30055, 30060, 30065, 30090, 30091, 40000, 40010, 40100, 40200, 40300, 40400, 40410, 40412, 40500, 40600, 41000, 41010, 41011, 41012, 41020, 41100, 41200, 41300, 41400, 41410, 41900, 42000, 42100, 42110, 42111, 42112, 42200, 42300, 42400, 42500, 42600, 42900, 43000, 43100, 43200, 43300, 43310, 43320, 43330, 43400, 43500, 43600, 43610, 43620, 43630, 43640, 43700, 43800, 43900, 44000, 45000, 45010, 45020, 45030, 45040, 45050, 45060, 45070, 45080, 45100, 45200, 45210, 45211, 45212, 45213, 45300, 45301, 45310, 45311, 45312, 45313, 45314, 45315, 45316, 45317, 45318, 45319, 45320, 45321, 45322, 45323, 45324, 45325, 45326, 45327, 45328, 45329, 45330, 45331, 45332, 45333, 45340, 45341, 45342, 45343, 45344, 45345, 45346, 45347, 45348, 45349, 45350, 45351, 45352, 45353, 45360, 45361, 45362, 45400, 45500, 45510, 45511, 45520, 45521, 45522, 45523, 45524, 45525, 45526, 45530, 45600, 45610, 45700, 45710, 45720, 45730, 45740, 45750, 45760, 45770, 45780, 45790, 45800, 45900, 46000, 46100, 46200, 46300, 46500, 46510, 46520, 46521, 46530, 46540, 46541, 46542, 46543, 46544, 46545, 46546, 46547, 46548, 46590, 49900, 50000, 50010, 50020, 50030, 50040, 50100, 50200, 50210, 50220, 50900, 51000, 51100, 51200, 51210, 51220, 51300, 51400, 51500, 51600, 51700, 51800, 51900, 51910, 52000, 52100, 52110, 52120, 52130, 52140, 52150, 52200, 52300, 52400, 53000, 53100, 53200, 53210, 53300, 53400, 53410, 53420, 53430, 53440, 53500, 53600, 53700, 53800, 53900, 54000, 54100, 54200, 54210, 54220, 54300, 54400, 54500, 54600, 54700, 54800, 54900, 55000, 59900, 60000, 60010, 60011, 60012, 60013, 60014, 60015, 60016, 60017, 60019, 60020, 60021, 60022, 60023, 60024, 60025, 60026, 60027, 60028, 60029, 60030, 60031, 60032, 60033, 60034, 60038, 60039, 60040, 60041, 60042, 60043, 60044, 60045, 60046, 60047, 60048, 60049, 60050, 60051, 60052, 60053, 60054, 60055, 60056, 60057, 60058, 60059, 60060, 60061, 60062, 60063, 60064, 60065, 60070, 60071, 60072, 60073, 60074, 60075, 60076, 60077, 60078, 60079, 60080, 60081, 60082, 60090, 60091, 60092, 60093, 60094, 60095, 60096, 60099, 70000, 70010, 70011, 70012, 70013, 70014, 70020, 71000, 71010, 71012, 71013, 71014, 71016, 71017, 71018, 71020, 71021, 71022, 71023, 71024, 71025, 71026, 71027, 71028, 71029, 71032, 71033, 71034, 71039, 71040, 71041, 71042, 71043, 71044, 71045, 71046, 71047, 71048, 71049, 71050, 71090, 80000, 80010, 80020, 80030, 80040, 80050, 90000, 90010, 90011, 90020, 90021, 90022, 95000, 99700, 99800, 99900], "NATIVITY": [0, 1, 2, 3, 4, 5], "CITIZEN": [0, 1, 2, 3, 4, 5], "MTONGUE": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 96, 97, 99], "MTONGUED": [0, 100, 110, 120, 130, 140, 150, 160, 200, 210, 220, 230, 240, 300, 310, 320, 400, 410, 420, 430, 440, 450, 460, 470, 500, 600, 700, 800, 810, 900, 1000, 1010, 1020, 1030, 1100, 1110, 1120, 1130, 1140, 1150, 1200, 1210, 1220, 1230, 1240, 1250, 1300, 1400, 1500, 1510, 1520, 1530, 1540, 1550, 1560, 1570, 1580, 1590, 1600, 1700, 1800, 1810, 1811, 1820, 1900, 1910, 1920, 1930, 2000, 2010, 2020, 2100, 2110, 2200, 2300, 2310, 2320, 2330, 2331, 2332, 2400, 2500, 2510, 2600, 2610, 2620, 2621, 2630, 2700, 2800, 2900, 2910, 3000, 3010, 3020, 3030, 3040, 3050, 3100, 3101, 3102, 3103, 3110, 3111, 3112, 3113, 3114, 3115, 3116, 3117, 3118, 3119, 3120, 3121, 3122, 3123, 3130, 3140, 3150, 3190, 3200, 3210, 3300, 3400, 3401, 3402, 3500, 3510, 3511, 3520, 3521, 3530, 3600, 3700, 3701, 3702, 3703, 3704, 3705, 3706, 3707, 3708, 3709, 3710, 3711, 3800, 3810, 3900, 4000, 4001, 4002, 4003, 4004, 4005, 4010, 4011, 4100, 4110, 4200, 4300, 4301, 4302, 4303, 4310, 4311, 4312, 4313, 4314, 4315, 4400, 4410, 4420, 4500, 4510, 4600, 4700, 4710, 4720, 4800, 4900, 5000, 5100, 5110, 5120, 5130, 5140, 5150, 5200, 5210, 5220, 5230, 5240, 5250, 5260, 5270, 5280, 5290, 5300, 5310, 5320, 5330, 5340, 5400, 5410, 5420, 5430, 5440, 5450, 5460, 5470, 5480, 5500, 5501, 5502, 5503, 5504, 5505, 5506, 5507, 5508, 5509, 5510, 5511, 5512, 5513, 5514, 5520, 5521, 5522, 5523, 5524, 5525, 5526, 5527, 5528, 5529, 5530, 5590, 5600, 5700, 5710, 5720, 5730, 5740, 5750, 5800, 5810, 5820, 5900, 6000, 6100, 6110, 6120, 6130, 6300, 6301, 6302, 6303, 6304, 6305, 6306, 6307, 6308, 6309, 6310, 6311, 6312, 6313, 6314, 6320, 6321, 6322, 6390, 6400, 7000, 7100, 7110, 7120, 7130, 7140, 7150, 7160, 7200, 7201, 7202, 7203, 7204, 7205, 7206, 7207, 7208, 7209, 7210, 7211, 7212, 7213, 7214, 7215, 7216, 7217, 7218, 7219, 7300, 7301, 7302, 7303, 7304, 7305, 7306, 7307, 7308, 7309, 7310, 7311, 7312, 7313, 7314, 7400, 7401, 7402, 7403, 7404, 7405, 7406, 7407, 7408, 7409, 7410, 7411, 7412, 7413, 7420, 7421, 7422, 7423, 7424, 7430, 7440, 7450, 7490, 7500, 7600, 7610, 7620, 7630, 7700, 7701, 7702, 7703, 7704, 7705, 7706, 7707, 7708, 7709, 7710, 7711, 7712, 7713, 7714, 7715, 7800, 7900, 7910, 7920, 7930, 7940, 7950, 7960, 7970, 7980, 7990, 8000, 8010, 8020, 8030, 8040, 8050, 8060, 8100, 8101, 8102, 8103, 8104, 8105, 8106, 8107, 8108, 8109, 8110, 8111, 8120, 8200, 8210, 8220, 8230, 8240, 8250, 8260, 8300, 8400, 8410, 8420, 8430, 8440, 8450, 8460, 8470, 8480, 8500, 8510, 8520, 8530, 8600, 8601, 8602, 8603, 8604, 8605, 8606, 8607, 8608, 8609, 8610, 8620, 8630, 8631, 8632, 8633, 8640, 8700, 8800, 8810, 8820, 8900, 8910, 9000, 9010, 9020, 9030, 9040, 9050, 9100, 9101, 9110, 9111, 9112, 9120, 9130, 9131, 9140, 9150, 9160, 9170, 9171, 9200, 9210, 9211, 9212, 9213, 9214, 9215, 9220, 9230, 9231, 9240, 9241, 9242, 9250, 9260, 9270, 9271, 9280, 9281, 9282, 9290, 9291, 9292, 9300, 9400, 9410, 9420, 9500, 9600, 9601, 9602, 9700, 9900, 9999], "SPANNAME": [0, 1, 2, 9], "HISPRULE": [0, 1, 2, 3, 4, 5, 6, 7, 8], "SCHOOL": [1, 2], "HIGRADE": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 99], "HIGRADED": [0, 10, 11, 12, 20, 21, 22, 30, 31, 32, 40, 41, 42, 50, 51, 52, 60, 61, 62, 70, 71, 72, 80, 81, 82, 90, 91, 92, 100, 101, 102, 110, 111, 112, 120, 121, 122, 130, 131, 132, 140, 141, 142, 150, 151, 152, 160, 161, 162, 170, 171, 172, 180, 181, 182, 190, 191, 192, 200, 201, 202, 210, 211, 212, 220, 221, 222, 230, 999], "EDUC": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 99], "EDUCD": [0, 1, 2, 10, 11, 12, 13, 14, 15, 16, 17, 20, 21, 22, 23, 24, 25, 26, 30, 40, 50, 60, 61, 62, 63, 64, 65, 70, 71, 80, 81, 82, 83, 90, 100, 101, 110, 111, 112, 113, 114, 115, 116, 999], "EMPSTAT": [0, 1, 2, 3], "EMPSTATD": [0, 10, 11, 12, 13, 14, 15, 20, 21, 22, 30, 31, 32, 33, 34], "LABFORCE": [0, 1, 2], "OCC1950": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 59, 61, 62, 63, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 81, 82, 83, 84, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 123, 200, 201, 203, 204, 205, 210, 230, 240, 250, 260, 270, 280, 290, 300, 301, 302, 304, 305, 310, 320, 321, 322, 325, 335, 340, 341, 342, 350, 360, 365, 370, 380, 390, 400, 410, 420, 430, 450, 460, 470, 480, 490, 500, 501, 502, 503, 504, 505, 510, 511, 512, 513, 514, 515, 520, 521, 522, 523, 524, 525, 530, 531, 532, 533, 534, 535, 540, 541, 542, 543, 544, 545, 550, 551, 552, 553, 554, 555, 560, 561, 562, 563, 564, 565, 570, 571, 572, 573, 574, 575, 580, 581, 582, 583, 584, 585, 590, 591, 592, 593, 594, 595, 600, 601, 602, 603, 604, 605, 610, 611, 612, 613, 614, 615, 620, 621, 622, 623, 624, 625, 630, 631, 632, 633, 634, 635, 640, 641, 642, 643, 644, 645, 650, 660, 661, 662, 670, 671, 672, 673, 674, 675, 680, 681, 682, 683, 684, 685, 690, 700, 710, 720, 730, 731, 732, 740, 750, 751, 752, 753, 754, 760, 761, 762, 763, 764, 770, 771, 772, 773, 780, 781, 782, 783, 784, 785, 790, 810, 820, 830, 840, 910, 920, 930, 940, 950, 960, 970, 979, 980, 981, 982, 983, 984, 985, 986, 987, 990, 991, 995, 997, 999], "IND1950": [0, 105, 116, 126, 206, 216, 226, 236, 239, 246, 306, 307, 308, 309, 316, 317, 318, 319, 326, 336, 337, 338, 346, 347, 348, 356, 357, 358, 367, 376, 377, 378, 379, 386, 387, 388, 399, 406, 407, 408, 409, 416, 417, 418, 419, 426, 429, 436, 437, 438, 439, 446, 448, 449, 456, 457, 458, 459, 466, 467, 468, 469, 476, 477, 478, 487, 488, 489, 499, 506, 516, 526, 527, 536, 546, 556, 567, 568, 578, 579, 586, 587, 588, 596, 597, 598, 606, 607, 608, 609, 616, 617, 618, 619, 626, 627, 636, 637, 646, 647, 656, 657, 658, 659, 667, 668, 669, 679, 686, 687, 688, 689, 696, 697, 698, 699, 716, 726, 736, 746, 756, 806, 807, 808, 816, 817, 826, 836, 846, 847, 848, 849, 856, 857, 858, 859, 868, 869, 879, 888, 896, 897, 898, 899, 906, 916, 926, 936, 946, 976, 979, 980, 982, 983, 984, 986, 987, 991, 995, 997, 998, 999], "CLASSWKR": [0, 1, 2, 9], "CLASSWKRD": [0, 10, 11, 12, 13, 14, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 98, 99], "WKSWORK2": [0, 1, 2, 3, 4, 5, 6], "HRSWORK2": [0, 1, 2, 3, 4, 5, 6, 7, 8], "UOCC": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 98, 99, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 156, 200, 210, 220, 222, 224, 226, 236, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 266, 270, 272, 274, 276, 278, 280, 282, 284, 286, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 327, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358, 260, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382, 384, 386, 388, 390, 392, 394, 396, 398, 400, 402, 404, 406, 408, 410, 412, 414, 416, 418, 420, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 468, 470, 472, 474, 476, 478, 480, 482, 484, 486, 488, 496, 500, 510, 520, 600, 602, 604, 606, 608, 610, 612, 614, 700, 710, 712, 714, 720, 730, 732, 740, 750, 760, 770, 780, 790, 792, 794, 796, 798, 844, 866, 888, 900, 902, 904, 906, 908, 910, 988, 995, 996, 997, 998, 999], "UOCC95": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 59, 61, 62, 63, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 81, 82, 83, 84, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 123, 200, 201, 203, 204, 205, 210, 230, 240, 250, 260, 270, 280, 290, 300, 301, 302, 304, 305, 310, 320, 321, 322, 325, 335, 340, 341, 342, 350, 360, 365, 370, 380, 390, 400, 410, 420, 430, 450, 460, 470, 480, 490, 500, 501, 502, 503, 504, 505, 510, 511, 512, 513, 514, 515, 520, 521, 522, 523, 524, 525, 530, 531, 532, 533, 534, 535, 540, 541, 542, 543, 544, 545, 550, 551, 552, 553, 554, 555, 560, 561, 562, 563, 564, 565, 570, 571, 572, 573, 574, 575, 580, 581, 582, 583, 584, 585, 590, 591, 592, 593, 594, 595, 600, 601, 602, 603, 604, 605, 610, 611, 612, 613, 614, 615, 620, 621, 622, 623, 624, 625, 630, 631, 632, 633, 634, 635, 640, 641, 642, 643, 644, 645, 650, 660, 661, 662, 670, 671, 672, 673, 674, 675, 680, 681, 682, 683, 684, 685, 690, 700, 710, 720, 730, 731, 732, 740, 750, 751, 752, 753, 754, 760, 761, 762, 763, 764, 770, 771, 772, 773, 780, 781, 782, 783, 784, 785, 790, 810, 820, 830, 840, 910, 920, 930, 940, 950, 960, 970, 975, 980, 982, 983, 984, 986, 987, 995, 997, 999], "UIND": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 995, 996, 997, 998, 999], "UCLASSWK": [0, 1, 2, 3, 4, 6, 7, 8], "INCNONWG": [0, 1, 2, 9], "MIGRATE5": [0, 1, 2, 3, 4, 8, 9], "MIGRATE5D": [0, 10, 20, 21, 22, 23, 24, 25, 30, 31, 32, 33, 40, 80, 90], "MIGPLAC5": [0, 1, 2, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 55, 56, 61, 62, 63, 64, 65, 66, 67, 68, 99, 100, 105, 110, 115, 119, 120, 150, 151, 152, 155, 160, 199, 200, 211, 212, 213, 214, 215, 216, 217, 218, 219, 250, 260, 261, 262, 263, 264, 266, 267, 305, 310, 315, 320, 325, 330, 345, 350, 360, 365, 370, 390, 400, 401, 402, 404, 405, 410, 411, 412, 413, 414, 415, 420, 421, 422, 423, 424, 425, 426, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 450, 451, 452, 453, 454, 455, 456, 457, 460, 461, 462, 465, 496, 498, 499, 500, 501, 502, 510, 511, 512, 513, 514, 515, 516, 517, 518, 520, 521, 525, 522, 523, 524, 530, 531, 532, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 548, 599, 600, 610, 612, 670, 690, 694, 699, 700, 701, 702, 710, 715, 800, 900, 911, 912, 990, 999], "MIGTYPE5": [0, 1, 2, 3, 4, 9], "SAMEPLAC": [0, 1, 2, 9], "SAMESEA5": [0, 1, 2, 3, 4, 9], "VETSTAT": [0, 1, 2, 9], "VETSTATD": [0, 10, 11, 12, 13, 20, 21, 22, 23, 99], "VET1940": [0, 1, 2, 8], "VETWWI": [0, 1, 2], "VETPER": [0, 1, 2, 3, 4, 5, 7, 8], "VETCHILD": [0, 1, 2, 8, 9], "SSENROLL": [0, 1, 2], "OCC": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 98, 99, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 156, 200, 210, 220, 222, 224, 226, 236, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 266, 270, 272, 274, 276, 278, 280, 282, 284, 286, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 327, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358, 260, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382, 384, 386, 388, 390, 392, 394, 396, 398, 400, 402, 404, 406, 408, 410, 412, 414, 416, 418, 420, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 468, 470, 472, 474, 476, 478, 480, 482, 484, 486, 488, 496, 500, 510, 520, 600, 602, 604, 606, 608, 610, 612, 614, 700, 710, 712, 714, 720, 730, 732, 740, 750, 760, 770, 780, 790, 792, 794, 796, 798, 844, 866, 888, 900, 902, 904, 906, 908, 910, 988, 995, 996, 997, 998, 999], "HRSWORK1": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98], "WKSWORK1": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52], "SEI": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96], "OCCSCORE": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80], "IND": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 995, 996, 997, 998, 999], "ERSCOR50": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 9999], "EDSCOR50": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 9999], "NPBOSS50": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 9999], "MIGMET5": [0, 40, 60, 80, 120, 160, 200, 220, 240, 280, 320, 380, 400, 440, 450, 460, 470, 480, 500, 520, 560, 580, 600, 640, 680, 720, 730, 740, 760, 780, 840, 860, 870, 880, 920, 960, 1000, 1010, 1020, 1040, 1080, 1120, 1121, 1122, 1123, 1140, 1150, 1160, 1200, 1240, 1260, 1280, 1281, 1300, 1310, 1320, 1330, 1350, 1360, 1400, 1440, 1480, 1520, 1521, 1540, 1560, 1580, 1600, 1601, 1602, 1603, 1604, 1620, 1640, 1660, 1680, 1720, 1740, 1760, 1800, 1840, 1880, 1900, 1920, 1921, 1930, 1950, 1960, 2000, 2001, 2020, 2030, 2040, 2080, 2081, 2120, 2121, 2160, 2180, 2190, 2200, 2240, 2281, 2290, 2310, 2320, 2330, 2340, 2360, 2400, 2440, 2520, 2560, 2580, 2600, 2620, 2640, 2650, 2660, 2670, 2680, 2700, 2710, 2720, 2750, 2760, 2840, 2880, 2900, 2920, 2970, 2980, 2990, 3000, 3010, 3040, 3060, 3080, 3120, 3121, 3150, 3160, 3161, 3180, 3200, 3240, 3280, 3281, 3282, 3283, 3290, 3300, 3320, 3350, 3360, 3361, 3400, 3440, 3480, 3500, 3520, 3560, 3580, 3590, 3600, 3610, 3620, 3660, 3680, 3710, 3720, 3740, 3760, 3800, 3810, 3840, 3850, 3870, 3880, 3920, 3960, 3980, 4000, 4040, 4080, 4100, 4120, 4150, 4200, 4240, 4280, 4320, 4360, 4400, 4410, 4420, 4440, 4480, 4481, 4482, 4520, 4600, 4640, 4680, 4720, 4760, 4800, 4840, 4880, 4890, 4900, 4920, 4940, 5000, 5040, 5080, 5120, 5140, 5160, 5170, 5190, 5200, 5240, 5280, 5320, 5330, 5340, 5350, 5360, 5400, 5460, 5480, 5481, 5482, 5520, 5560, 5600, 5601, 5602, 5603, 5604, 5605, 5640, 5660, 5720, 5721, 5722, 5760, 5790, 5800, 5880, 5910, 5920, 5950, 5960, 5990, 6010, 6020, 6030, 6080, 6120, 6160, 6200, 6240, 6280, 6281, 6320, 6360, 6400, 6440, 6441, 6450, 6460, 6480, 6481, 6482, 6520, 6560, 6580, 6600, 6640, 6641, 6660, 6680, 6690, 6720, 6740, 6760, 6761, 6780, 6781, 6800, 6820, 6840, 6880, 6895, 6920, 6960, 6961, 6980, 7000, 7040, 7080, 7120, 7140, 7160, 7161, 7200, 7240, 7320, 7360, 7361, 7362, 7400, 7440, 7460, 7470, 7480, 7490, 7500, 7510, 7520, 7560, 7561, 7600, 7610, 7620, 7640, 7680, 7720, 7760, 7800, 7840, 7880, 7920, 8000, 8040, 8050, 8080, 8120, 8140, 8160, 8200, 8240, 8280, 8320, 8360, 8400, 8440, 8480, 8520, 8560, 8600, 8640, 8680, 8730, 8750, 8760, 8780, 8800, 8840, 8880, 8920, 8940, 8960, 9000, 9040, 9080, 9140, 9160, 9200, 9240, 9260, 9270, 9280, 9320, 9340, 9360, 9999], "MIGCITY5": [0, 1, 2, 3, 4, 5, 6, 7, 10, 30, 50, 51, 52, 70, 90, 91, 100, 110, 120, 130, 131, 132, 140, 150, 160, 161, 162, 163, 170, 171, 190, 210, 230, 231, 250, 270, 271, 272, 273, 275, 280, 281, 282, 283, 284, 290, 310, 311, 312, 313, 330, 331, 340, 341, 342, 343, 344, 345, 346, 347, 350, 370, 371, 390, 391, 410, 411, 430, 450, 470, 490, 491, 510, 530, 550, 551, 552, 553, 554, 570, 590, 610, 630, 640, 650, 651, 652, 660, 670, 671, 672, 673, 680, 690, 695, 700, 701, 702, 703, 704, 705, 706, 710, 711, 712, 720, 721, 730, 740, 741, 742, 743, 750, 760, 761, 770, 771, 772, 780, 790, 791, 792, 793, 794, 795, 800, 801, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 830, 831, 832, 833, 834, 835, 837, 850, 851, 870, 880, 881, 882, 883, 890, 900, 905, 906, 907, 910, 911, 920, 921, 926, 927, 930, 931, 950, 951, 952, 970, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1010, 1020, 1021, 1023, 1024, 1025, 1026, 1027, 1030, 1050, 1060, 1070, 1090, 1091, 1110, 1130, 1140, 1150, 1170, 1171, 1190, 1191, 1192, 1210, 1230, 1250, 1270, 1290, 1291, 1292, 1310, 1311, 1312, 1330, 1340, 1341, 1350, 1351, 1370, 1371, 1372, 1373, 1374, 1375, 1390, 1400, 1410, 1411, 1412, 1420, 1430, 1450, 1451, 1452, 1470, 1490, 1491, 1492, 1493, 1494, 1495, 1496, 1500, 1510, 1520, 1521, 1522, 1523, 1530, 1540, 1545, 1550, 1551, 1552, 1553, 1570, 1571, 1572, 1590, 1591, 1592, 1610, 1630, 1631, 1650, 1670, 1671, 1680, 1690, 1691, 1692, 1693, 1694, 1695, 1710, 1711, 1713, 1730, 1750, 1751, 1752, 1753, 1754, 1755, 1770, 1790, 1791, 1792, 1800, 1810, 1830, 1831, 1832, 1833, 1834, 1850, 1860, 1870, 1890, 1891, 1892, 1893, 1910, 1930, 1931, 1940, 1950, 1951, 1952, 1970, 1971, 1972, 1973, 1974, 1990, 2010, 2030, 2040, 2050, 2051, 2055, 2060, 2061, 2062, 2070, 2071, 2072, 2073, 2074, 2075, 2076, 2080, 2090, 2091, 2092, 2110, 2130, 2131, 2150, 2170, 2190, 2210, 2211, 2212, 2213, 2214, 2220, 2221, 2222, 2230, 2240, 2241, 2242, 2250, 2260, 2270, 2271, 2273, 2274, 2275, 2280, 2281, 2290, 2300, 2301, 2302, 2303, 2310, 2311, 2330, 2350, 2351, 2352, 2353, 2354, 2355, 2356, 2357, 2358, 2359, 2360, 2370, 2390, 2391, 2392, 2393, 2394, 2400, 2410, 2411, 2430, 2435, 2440, 2441, 2450, 2470, 2471, 2472, 2473, 2489, 2490, 2491, 2510, 2511, 2512, 2513, 2514, 2515, 2516, 2517, 2520, 2530, 2531, 2540, 2541, 2550, 2551, 2570, 2571, 2572, 2573, 2574, 2575, 2576, 2577, 2578, 2579, 2580, 2581, 2582, 2583, 2584, 2590, 2591, 2610, 2630, 2650, 2670, 2680, 2681, 2682, 2683, 2690, 2691, 2692, 2693, 2710, 2711, 2712, 2713, 2725, 2730, 2731, 2740, 2750, 2751, 2752, 2753, 2754, 2755, 2756, 2757, 2770, 2780, 2781, 2790, 2791, 2792, 2810, 2811, 2830, 2850, 2851, 2870, 2871, 2872, 2873, 2874, 2875, 2890, 2891, 2892, 2910, 2930, 2950, 2951, 2960, 2961, 2962, 2963, 2970, 2990, 3010, 3011, 3012, 3013, 3014, 3015, 3020, 3030, 3050, 3051, 3052, 3070, 3071, 3090, 3091, 3110, 3111, 3130, 3131, 3132, 3133, 3134, 3150, 3151, 3160, 3161, 3170, 3190, 3191, 3210, 3230, 3231, 3250, 3260, 3270, 3271, 3272, 3273, 3290, 3291, 3292, 3293, 3294, 3310, 3311, 3312, 3313, 3330, 3350, 3370, 3380, 3390, 3391, 3392, 3393, 3394, 3395, 3396, 3400, 3405, 3410, 3430, 3440, 3450, 3451, 3470, 3471, 3480, 3481, 3482, 3490, 3510, 3511, 3512, 3513, 3520, 3521, 3522, 3530, 3540, 3550, 3551, 3560, 3570, 3590, 3610, 3630, 3631, 3632, 3633, 3634, 3635, 3638, 3639, 3650, 3670, 3680, 3690, 3691, 3692, 3693, 3710, 3730, 3750, 3765, 3770, 3771, 3772, 3790, 3800, 3810, 3830, 3850, 3870, 3871, 3890, 3891, 3910, 3911, 3912, 3913, 3914, 3915, 3929, 3930, 3931, 3932, 3933, 3934, 3940, 3950, 3951, 3952, 3953, 3954, 3955, 3956, 3957, 3958, 3959, 3960, 3961, 3962, 3963, 3964, 3970, 3971, 3990, 3991, 3992, 3993, 4010, 4011, 4030, 4040, 4041, 4050, 4070, 4090, 4110, 4120, 4121, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4130, 4150, 4151, 4160, 4161, 4162, 4163, 4170, 4190, 4210, 4211, 4212, 4213, 4214, 4230, 4250, 4251, 4252, 4253, 4254, 4255, 4256, 4260, 4270, 4290, 4291, 4310, 4311, 4312, 4313, 4330, 4331, 4350, 4351, 4370, 4390, 4410, 4411, 4413, 4414, 4415, 4416, 4420, 4430, 4450, 4451, 4452, 4470, 4490, 4510, 4511, 4530, 4550, 4570, 4571, 4590, 4610, 4611, 4630, 4650, 4670, 4690, 4710, 4730, 4750, 4770, 4771, 4772, 4790, 4791, 4792, 4810, 4811, 4820, 4830, 4831, 4832, 4833, 4834, 4835, 4836, 4837, 4838, 4839, 4840, 4841, 4842, 4843, 4845, 4850, 4860, 4870, 4890, 4900, 4901, 4902, 4905, 4910, 4930, 4950, 4970, 4971, 4972, 4990, 4991, 4992, 4993, 4994, 4995, 4996, 5010, 5011, 5012, 5030, 5040, 5050, 5051, 5070, 5090, 5091, 5092, 5110, 5111, 5112, 5113, 5114, 5116, 5117, 5118, 5119, 5121, 5122, 5123, 5124, 5125, 5130, 5140, 5150, 5170, 5180, 5190, 5210, 5230, 5231, 5232, 5233, 5240, 5250, 5255, 5269, 5270, 5271, 5290, 5291, 5310, 5311, 5330, 5331, 5332, 5333, 5334, 5335, 5341, 5350, 5351, 5352, 5353, 5354, 5370, 5390, 5391, 5409, 5410, 5411, 5412, 5413, 5414, 5415, 5430, 5450, 5451, 5460, 5470, 5471, 5480, 5481, 5490, 5491, 5500, 5510, 5511, 5530, 5550, 5570, 5590, 5591, 5610, 5630, 5650, 5660, 5670, 5671, 5690, 5710, 5730, 5731, 5750, 5751, 5752, 5770, 5790, 5791, 5792, 5810, 5811, 5830, 5850, 5870, 5871, 5872, 5873, 5874, 5890, 5910, 5930, 5931, 5932, 5933, 5950, 5970, 5971, 5972, 5973, 5974, 5990, 5991, 5992, 5993, 5994, 5995, 6010, 6011, 6012, 6013, 6014, 6030, 6050, 6070, 6090, 6110, 6130, 6150, 6170, 6171, 6172, 6190, 6191, 6192, 6210, 6211, 6220, 6230, 6231, 6250, 6260, 6270, 6280, 6281, 6282, 6290, 6300, 6310, 6311, 6312, 6320, 6321, 6322, 6330, 6335, 6340, 6350, 6351, 6352, 6353, 6354, 6360, 6370, 6390, 6410, 6430, 6431, 6432, 6433, 6434, 6435, 6437, 6438, 6440, 6450, 6451, 6452, 6453, 6470, 6471, 6472, 6490, 6500, 6510, 6530, 6550, 6570, 6590, 6591, 6592, 6593, 6594, 6595, 6610, 6611, 6612, 6613, 6614, 6615, 6616, 6617, 6620, 6630, 6640, 6650, 6670, 6690, 6691, 6692, 6693, 6710, 6730, 6731, 6732, 6733, 6734, 6750, 6770, 6771, 6772, 6789, 6790, 6791, 6792, 6793, 6794, 6795, 6796, 6797, 6798, 6799, 6810, 6830, 6831, 6832, 6833, 6850, 6870, 6871, 6872, 6890, 6910, 6911, 6912, 6913, 6930, 6950, 6951, 6952, 6953, 6954, 6960, 6970, 6971, 6990, 6991, 6992, 7000, 7010, 7011, 7030, 7050, 7070, 7071, 7072, 7073, 7074, 7079, 7080, 7081, 7082, 7083, 7084, 7090, 7091, 7092, 7093, 7100, 7110, 7111, 7112, 7120, 7121, 7122, 7123, 7130, 7140, 7150, 7151, 7152, 7153, 7170, 7180, 7190, 7191, 7210, 7230, 7231, 7241, 7242, 7250, 7270, 7290, 7310, 7311, 7312, 7313, 7314, 7315, 7316, 7317, 7318, 7319, 7320, 7321, 7322, 7323, 7324, 7325, 7326, 7327, 7328, 7329, 7330, 7331, 7332, 7333, 7334, 7335, 7340, 7350, 7351, 7352, 7353, 7370, 7371, 7372, 7373, 7374, 7375, 7376, 7377, 7390, 7400, 7401, 7402, 7410, 7430, 7450, 7451, 7460, 7470, 7471, 7472, 7490, 7510, 7511, 7512, 7513, 7514, 7515, 7516, 7530, 7531, 7532, 7533, 7534, 7535, 7550, 7551, 7570, 7571, 7572, 7573, 7590, 7610, 7630, 7631, 7650, 9999], "COUNTY": [5, 10, 30, 35, 50, 55, 70, 75, 90, 95, 110, 115, 130, 135, 150, 155, 170, 190, 195, 210, 215, 230, 235, 245, 250, 255, 270, 279, 285, 290, 295, 310, 315, 330, 335, 350, 360, 370, 390, 410, 415, 430, 435, 450, 455, 470, 475, 490, 495, 510, 530, 535, 550, 555, 570, 590, 595, 605, 610, 615, 630, 635, 650, 655, 670, 690, 710, 715, 730, 735, 750, 755, 770, 775, 790, 810, 830, 835, 850, 855, 865, 870, 875, 890, 910, 915, 930, 935, 950, 955, 965, 970, 975, 985, 987, 990, 1010, 1015, 1030, 1050, 1055, 1065, 1070, 1075, 1078, 1090, 1110, 1115, 1125, 1130, 1135, 1141, 1145, 1148, 1150, 1155, 1170, 1175, 1190, 1195, 1205, 1210, 1215, 1230, 1250, 1270, 1275, 1290, 1310, 1330, 1350, 1355, 1370, 1390, 1410, 1415, 1430, 1445, 1450, 1455, 1470, 1490, 1510, 1530, 1550, 1555, 1565, 1567, 1570, 1590, 1595, 1610, 1630, 1635, 1650, 1655, 1670, 1675, 1690, 1710, 1730, 1750, 1770, 1790, 1810, 1830, 1835, 1850, 1859, 1870, 1875, 1890, 1910, 1915, 1930, 1935, 1945, 1950, 1955, 1965, 1970, 1990, 2010, 2030, 2050, 2070, 2090, 2110, 2130, 2150, 2170, 2190, 2210, 2230, 2250, 2270, 2290, 2310, 2330, 2350, 2370, 2390, 2410, 2430, 2450, 2470, 2490, 2510, 2530, 2550, 2570, 2590, 2610, 2630, 2650, 2670, 2690, 2710, 2730, 2750, 2770, 2790, 2810, 2830, 2850, 2870, 2890, 2900, 2910, 2930, 2950, 2970, 2990, 3000, 3010, 3030, 3050, 3070, 3090, 3100, 3110, 3130, 3150, 3170, 3190, 3200, 3210, 3230, 3250, 3270, 3290, 3300, 3310, 3330, 3350, 3370, 3390, 3410, 3430, 3450, 3470, 3490, 3510, 3530, 3550, 3570, 3590, 3610, 3630, 3650, 3670, 3690, 3710, 3730, 3750, 3770, 3790, 3810, 3830, 3850, 3870, 3890, 3910, 3930, 3950, 3970, 3990, 4010, 4030, 4050, 4070, 4090, 4110, 4130, 4150, 4170, 4190, 4210, 4230, 4250, 4270, 4290, 4310, 4330, 4350, 4370, 4390, 4410, 4430, 4450, 4470, 4490, 4510, 4530, 4550, 4570, 4590, 4610, 4630, 4650, 4670, 4690, 4710, 4730, 4750, 4770, 4790, 4810, 4830, 4850, 4870, 4890, 4910, 4930, 4950, 4970, 4990, 5010, 5030, 5050, 5070, 5100, 5200, 5300, 5400, 5600, 5900, 6300, 6500, 6600, 6700, 6800, 6860, 6900, 7000, 7100, 7300, 7400, 7500, 7600, 7700, 7850, 7900, 8000, 8300, 8400, 9010, 9030, 9050, 9070, 9090, 9120, 9130, 9140, 9160, 9170, 9180, 9210, 9230, 9250, 9270, 9970, 9995, 9997, 9999], "MIGCOUNTY": [5, 10, 30, 35, 50, 55, 70, 75, 90, 95, 110, 115, 130, 135, 150, 155, 170, 190, 195, 210, 215, 230, 235, 245, 250, 255, 270, 279, 285, 290, 295, 310, 315, 330, 335, 350, 360, 370, 390, 410, 415, 430, 435, 450, 455, 470, 475, 490, 495, 510, 530, 535, 550, 555, 570, 590, 595, 605, 610, 615, 630, 635, 650, 655, 670, 690, 710, 715, 730, 735, 750, 755, 770, 775, 790, 810, 830, 835, 850, 855, 865, 870, 875, 890, 910, 915, 930, 935, 950, 955, 965, 970, 975, 985, 987, 990, 1010, 1015, 1030, 1050, 1055, 1065, 1070, 1075, 1078, 1090, 1110, 1115, 1125, 1130, 1135, 1141, 1145, 1148, 1150, 1155, 1170, 1175, 1190, 1195, 1205, 1210, 1215, 1230, 1250, 1270, 1275, 1290, 1310, 1330, 1350, 1355, 1370, 1390, 1410, 1415, 1430, 1445, 1450, 1455, 1470, 1490, 1510, 1530, 1550, 1555, 1565, 1567, 1570, 1590, 1595, 1610, 1630, 1635, 1650, 1655, 1670, 1675, 1690, 1710, 1730, 1750, 1770, 1790, 1810, 1830, 1835, 1850, 1859, 1870, 1875, 1890, 1910, 1915, 1930, 1935, 1945, 1950, 1955, 1965, 1970, 1990, 2010, 2030, 2050, 2070, 2090, 2110, 2130, 2150, 2170, 2190, 2210, 2230, 2250, 2270, 2290, 2310, 2330, 2350, 2370, 2390, 2410, 2430, 2450, 2470, 2490, 2510, 2530, 2550, 2570, 2590, 2610, 2630, 2650, 2670, 2690, 2710, 2730, 2750, 2770, 2790, 2810, 2830, 2850, 2870, 2890, 2900, 2910, 2930, 2950, 2970, 2990, 3000, 3010, 3030, 3050, 3070, 3090, 3100, 3110, 3130, 3150, 3170, 3190, 3200, 3210, 3230, 3250, 3270, 3290, 3300, 3310, 3330, 3350, 3370, 3390, 3410, 3430, 3450, 3470, 3490, 3510, 3530, 3550, 3570, 3590, 3610, 3630, 3650, 3670, 3690, 3710, 3730, 3750, 3770, 3790, 3810, 3830, 3850, 3870, 3890, 3910, 3930, 3950, 3970, 3990, 4010, 4030, 4050, 4070, 4090, 4110, 4130, 4150, 4170, 4190, 4210, 4230, 4250, 4270, 4290, 4310, 4330, 4350, 4370, 4390, 4410, 4430, 4450, 4470, 4490, 4510, 4530, 4550, 4570, 4590, 4610, 4630, 4650, 4670, 4690, 4710, 4730, 4750, 4770, 4790, 4810, 4830, 4850, 4870, 4890, 4910, 4930, 4950, 4970, 4990, 5010, 5030, 5050, 5070, 5100, 5200, 5300, 5400, 5600, 5900, 6300, 6500, 6600, 6700, 6800, 6860, 6900, 7000, 7100, 7300, 7400, 7500, 7600, 7700, 7850, 7900, 8000, 8300, 8400, 9010, 9030, 9050, 9070, 9090, 9120, 9130, 9140, 9160, 9170, 9180, 9210, 9230, 9250, 9270, 9970, 9995, 9997, 9999], "MIGSEA5": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 990, 991, 992, 996, 997, 998, 999]} \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/match3.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/match3.py new file mode 100644 index 0000000..c706e40 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/match3.py @@ -0,0 +1,262 @@ +from mechanism import Mechanism +import mbi +from mbi import Domain, Factor, FactoredInference, GraphicalModel, Dataset +import matrix +import argparse +import numpy as np +from scipy import sparse, optimize +from tensorflow_privacy.privacy.analysis.rdp_accountant import compute_rdp, get_privacy_spent +from functools import reduce +import os + +def transform_data(data, supports): + df = data.df.copy() + newdom = {} + for col in data.domain: + support = supports[col] + size = support.sum() + newdom[col] = int(size) + if size < support.size: + newdom[col] += 1 + mapping = {} + idx = 0 + for i in range(support.size): + mapping[i] = size + if support[i]: + mapping[i] = idx + idx += 1 + assert idx == size + df[col] = df[col].map(mapping) + newdom = Domain.fromdict(newdom) + return Dataset(df, newdom) + +def reverse_data(data, supports): + df = data.df.copy() + newdom = {} + for col in data.domain: + support = supports[col] + mx = support.sum() + newdom[col] = int(support.size) + idx, extra = np.where(support)[0], np.where(~support)[0] + mask = df[col] == mx + if extra.size == 0: + pass + else: + df.loc[mask, col] = np.random.choice(extra, mask.sum()) + df.loc[~mask, col] = idx[df.loc[~mask, col]] + newdom = Domain.fromdict(newdom) + return Dataset(df, newdom) + +def moments_calibration(round1, round2, eps, delta): + # round1: L2 sensitivity of round1 queries + # round2: L2 sensitivity of round2 queries + # works as long as eps >= 0.01; if larger, increase orders + orders = range(2, 4096) + + def obj(sigma): + rdp1 = compute_rdp(1.0, sigma/round1, 1, orders) + rdp2 = compute_rdp(1.0, sigma/round2, 1, orders) + rdp = rdp1 + rdp2 + privacy = get_privacy_spent(orders, rdp, target_delta=delta) + return privacy[0] - eps + 1e-8 + low = 1.0 + high = 1.0 + while obj(low) < 0: + low /= 2.0 + while obj(high) > 0: + high *= 2.0 + sigma = optimize.bisect(obj, low, high) + assert obj(sigma) - 1e-8 <= 0, 'not differentially private' # true eps <= requested eps + return sigma + +class Match3(Mechanism): + + def __init__(self, dataset, specs, iters=1000, weight3=1.0, warmup=False): + Mechanism.__init__(self, dataset, specs) + self.iters = iters + self.weight3 = weight3 + self.warmup = warmup + self.elimination_order = None + + Qa = np.zeros((5,52)) + Qa[0,0] = 1 + Qa[1,1:10] = 1 + Qa[2,10:20] = 1 + Qa[3,20:51] = 1 + Qa[4,51] = 1 + + self.Q_INCWAGE = Qa + + def setup(self): + self.round1 = list(self.domain.attrs) + self.round2 = [('COUNTY', 'SUPDIST'), ('COUNTY', 'SEA'), ('COUNTY', 'MIGSEA5'), ('COUNTY', 'METAREA'), ('COUNTY', 'METAREAD'), ('COUNTY', 'METRO'), ('URBAN', 'URBPOP'), ('CITY', 'INCWAGE_A'), ('CITY', 'CITYPOP'), ('CITYPOP', 'URBPOP'), ('CITYPOP', 'SIZEPL'), ('CITYPOP', 'SUPDIST'), ('CITYPOP', 'ENUMDIST'), ('CITYPOP', 'WARD'), ('CITYPOP', 'FARM'), ('GQ', 'GQFUNDS'), ('GQ', 'OWNERSHPD'), ('GQ', 'SPLIT'), ('GQTYPE', 'GQTYPED'), ('GQTYPED', 'ENUMDIST'), ('OWNERSHP', 'OWNERSHPD'), ('OWNERSHP', 'RENT'), ('OWNERSHP', 'VALUEH'), ('RENT', 'ENUMDIST'), ('ENUMDIST', 'RACED'), ('SLREC', 'UIND'), ('RESPONDT', 'AGE'), ('FAMSIZE', 'MARST'), ('NCHLT5', 'AGE'), ('SEX', 'INCWAGE_A'), ('AGE', 'EMPSTATD'), ('AGE', 'HIGRADED'), ('AGE', 'MARST'), ('AGE', 'MBPLD'), ('AGE', 'SCHOOL'), ('AGE', 'AGEMONTH'), ('MARRNO', 'CHBORN'), ('MARRNO', 'UIND'), ('AGEMARR', 'CHBORN'), ('RACE', 'RACED'), ('HISPAN', 'HISPAND'), ('HISPAND', 'HISPRULE'), ('BPL', 'BPLD'), ('BPLD', 'MBPLD'), ('BPLD', 'CITIZEN'), ('MBPL', 'MBPLD'), ('MBPLD', 'FBPLD'), ('FBPL', 'FBPLD'), ('FBPLD', 'HISPRULE'), ('FBPLD', 'NATIVITY'), ('NATIVITY', 'UOCC'), ('NATIVITY', 'MTONGUED'), ('MTONGUE', 'MTONGUED'), ('SPANNAME', 'HISPRULE'), ('HIGRADE', 'HIGRADED'), ('HIGRADE', 'EDUC'), ('HIGRADED', 'EDUCD'), ('EMPSTAT', 'EMPSTATD'), ('EMPSTATD', 'LABFORCE'), ('EMPSTATD', 'OCC'), ('EMPSTATD', 'HRSWORK1'), ('EMPSTATD', 'DURUNEMP'), ('OCC', 'OCC1950'), ('OCC', 'NPBOSS50'), ('OCC', 'ERSCOR50'), ('OCC', 'PRESGL'), ('OCC', 'EDSCOR50'), ('OCC', 'SEI'), ('OCC', 'IND'), ('OCC', 'CLASSWKRD'), ('OCC', 'INCWAGE_A'), ('OCC1950', 'OCCSCORE'), ('IND', 'IND1950'), ('CLASSWKR', 'CLASSWKRD'), ('WKSWORK1', 'WKSWORK2'), ('WKSWORK1', 'INCWAGE_A'), ('HRSWORK1', 'HRSWORK2'), ('UOCC', 'UIND'), ('UOCC', 'UOCC95'), ('UOCC', 'VET1940'), ('UOCC', 'SSENROLL'), ('UIND', 'UCLASSWK'), ('UIND', 'VETPER'), ('INCWAGE_A', 'INCNONWG'), ('MIGRATE5', 'MIGPLAC5'), ('MIGRATE5D', 'MIGPLAC5'), ('MIGRATE5D', 'SAMEPLAC'), ('MIGRATE5D', 'SAMESEA5'), ('MIGPLAC5', 'MIGSEA5'), ('MIGMET5', 'MIGSEA5'), ('MIGMET5', 'MIGTYPE5'), ('MIGCITY5', 'MIGSEA5'), ('MIGSEA5', 'MIGCOUNTY'), ('VETSTAT', 'VETSTATD'), ('VETSTAT', 'VETPER'), ('VETSTAT', 'VETWWI'), ('VET1940', 'VETCHILD')] + + self.round2 += [('SEX', 'CITY'), ('SEX', 'CITY', 'INCWAGE_A'), ('INCWAGE_A', 'INCWAGE_B')] + self.round2 += [('SUPDIST', 'ENUMDIST'), ('SUPDIST', 'FARM'), ('ENUMDIST', 'WARD'), ('EMPSTATD', 'MARST'), ('EMPSTATD', 'HIGRADED'), ('EMPSTATD', 'RESPONDT'), ('MARST', 'NCHLT5'), ('MARST', 'MBPLD'), ('AGE', 'EMPSTATD', 'MARST'), ('AGE', 'EMPSTATD', 'HIGRADED'), ('AGE', 'EMPSTATD', 'RESPONDT'), ('AGE', 'MARST', 'NCHLT5'), ('AGE', 'MARST', 'MBPLD'), ('AGE', 'FAMSIZE'), ('MARST', 'AGE', 'FAMSIZE'), ('FBPLD', 'AGE'), ('AGE', 'BPLD'), ('MBPLD', 'FBPLD', 'AGE'), ('MBPLD', 'AGE', 'BPLD'), ('LABFORCE', 'AGE'), ('AGE', 'OCC'), ('OCC', 'HRSWORK1'), ('EMPSTATD', 'LABFORCE', 'AGE'), ('EMPSTATD', 'AGE', 'OCC'), ('EMPSTATD', 'OCC', 'HRSWORK1'), ('IND', 'INCWAGE_A'), ('CLASSWKRD', 'INCWAGE_A'), ('EMPSTATD', 'INCWAGE_A'), ('OCC', 'IND', 'INCWAGE_A'), ('OCC', 'CLASSWKRD', 'INCWAGE_A'), ('OCC', 'EMPSTATD', 'INCWAGE_A'), ('SEX', 'OCC'), ('CITY', 'OCC'), ('OCC', 'WKSWORK1'), ('OCC', 'INCNONWG'), ('INCWAGE_A', 'SEX', 'OCC'), ('INCWAGE_A', 'CITY', 'OCC'), ('INCWAGE_A', 'OCC', 'WKSWORK1'), ('INCWAGE_A', 'OCC', 'INCNONWG'), ('MIGMET5', 'COUNTY'), ('COUNTY', 'MIGCITY5'), ('COUNTY', 'MIGCOUNTY'), ('MIGPLAC5', 'MIGCOUNTY'), ('MIGSEA5', 'MIGMET5', 'COUNTY'), ('MIGSEA5', 'COUNTY', 'MIGCITY5'), ('MIGSEA5', 'COUNTY', 'MIGCOUNTY'), ('MIGSEA5', 'MIGPLAC5', 'MIGCOUNTY')] + + def measure(self): + data = self.load_data() + # round1 and round2 measurements will be weighted to have L2 sensitivity 1 + sigma = moments_calibration(1.0, 1.0, self.epsilon, self.delta) + print('NOISE LEVEL:', sigma) + + weights = np.ones(len(self.round1)) + weights[self.round1.index('INCWAGE_A')] *= 2.0 + weights /= np.linalg.norm(weights) # now has L2 norm = 1 + + supports = {} + + self.measurements = [] + for col, wgt in zip(self.round1, weights): + ########################## + ### Noise-addition step ## + ########################## + proj = (col,) + hist = data.project(proj).datavector() + noise = sigma*np.random.randn(hist.size) + y = wgt*hist + noise + + ##################### + ## Post-processing ## + ##################### + + if col in ['INCWAGE_A', 'SEA', 'METAREA', 'COUNTY', 'CITY', 'METAREAD']: + sup = np.ones(y.size, dtype=bool) + else: + sup = y >= 3*sigma + + supports[col] = sup + print(col, self.domain.size(col), sup.sum()) + + if sup.sum() == y.size: + y2 = y + I2 = matrix.Identity(y.size) + else: + y2 = np.append(y[sup], y[~sup].sum()) + I2 = np.ones(y2.size) + I2[-1] = 1.0 / np.sqrt(y.size - y2.size + 1.0) + y2[-1] /= np.sqrt(y.size - y2.size + 1.0) + I2 = sparse.diags(I2) + + self.measurements.append( (I2, y2/wgt, 1.0/wgt, proj) ) + + self.supports = supports + # perform round 2 measurments over compressed domain + data = transform_data(data, supports) + self.domain = data.domain + + self.round2 = [cl for cl in self.round2 if self.domain.size(cl) < 1e6] + weights = np.ones(len(self.round2)) + weights[self.round2.index(('SEX','CITY','INCWAGE_A'))] *= self.weight3 + weights[self.round2.index(('SEX','CITY'))] *= 2.0 + weights[self.round2.index(('SEX','INCWAGE_A'))] *= 2.0 + weights[self.round2.index(('CITY','INCWAGE_A'))] *= 2.0 + weights /= np.linalg.norm(weights) # now has L2 norm = 1 + + for proj, wgt in zip(self.round2, weights): + ######################### + ## Noise-addition step ## + ######################### + hist = data.project(proj).datavector() + if proj == ('SEX', 'CITY', 'INCWAGE_A'): + dom = self.domain.project(proj).shape + I = sparse.eye(dom[0] * dom[1]) + Q = sparse.kron(I, self.Q_INCWAGE).tocsr() + elif proj == ('CITY', 'INCWAGE_A'): + I = sparse.eye(self.domain.size('CITY')) + Q = sparse.kron(I, self.Q_INCWAGE).tocsr() + else: + Q = matrix.Identity(hist.size) + + noise = sigma*np.random.randn(Q.shape[0]) + y = wgt*Q.dot(hist) + noise + self.measurements.append( (Q, y/wgt, 1.0/wgt, proj) ) + + def postprocess(self): + iters = self.iters + domain = self.domain + engine = FactoredInference(domain, + structural_zeros={}, + iters=500, + log=True, + warm_start=True, + elim_order=self.elimination_order) + self.engine = engine + cb = mbi.callbacks.Logger(engine) + + if self.warmup: + engine._setup(self.measurements, None) + oneway = {} + for i in range(len(self.round1)): + p = self.round1[i] + y = self.measurements[i][1] + y = np.maximum(y, 1) + y /= y.sum() + oneway[p] = Factor(self.domain.project(p), y) + marginals = {} + for cl in engine.model.cliques: + marginals[cl] = reduce(lambda x,y: x*y, [oneway[p] for p in cl]) + + theta = engine.model.mle(marginals) + engine.potentials = theta + engine.marginals = engine.model.belief_propagation(theta) + + checkpt = self.save[:-4] + '-checkpt.csv' + for i in range(self.iters // 500): + + engine.infer(self.measurements, engine='MD', callback=cb) + + if i % 4 == 3: + self.synthetic = engine.model.synthetic_data() + self.synthetic = reverse_data(self.synthetic, self.supports) + self.transform_domain() + self.synthetic.to_csv(checkpt, index=False) + + if os.path.exists(checkpt): + os.remove(checkpt) + + self.synthetic = engine.model.synthetic_data() + self.synthetic = reverse_data(self.synthetic, self.supports) + +def default_params(): + """ + Return default parameters to run this program + + :returns: a dictionary of default parameter settings for each command line argument + """ + params = {} + params['dataset'] = 'competitor_pack/data/colorado.csv' + params['specs'] = 'competitor_pack/data/colorado-specs.json' + params['epsilon'] = 1.0 + params['delta'] = 2.2820544e-12 + params['save'] = 'out.csv' + + return params + +if __name__ == '__main__': + + description = '' + formatter = argparse.ArgumentDefaultsHelpFormatter + parser = argparse.ArgumentParser(description=description, formatter_class=formatter) + parser.add_argument('--dataset', help='path to dataset csv file') + parser.add_argument('--specs', help='path to specs json file') + parser.add_argument('--epsilon', type=float, help='privacy parameter') + parser.add_argument('--delta', type=float, help='privacy parameter') + parser.add_argument('--save', help='path to save synthetic data to') + + parser.set_defaults(**default_params()) + args = parser.parse_args() + + if args.epsilon <= 0.3: + iters = 7500 + weight3 = 8.0 + elif args.epsilon >= 4.0: + iters = 10000 + weight3 = 4.0 + else: + iters = 7500 + weight3 = 6.0 + + mech = Match3(args.dataset, args.specs, iters=iters, weight3=weight3, warmup=True) + + mech.run(args.epsilon, args.delta, args.save) diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/matrix.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/matrix.py new file mode 100644 index 0000000..479d37a --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/matrix.py @@ -0,0 +1,17 @@ +import numpy as np +from scipy.sparse.linalg import LinearOperator + +class Identity(LinearOperator): + def __init__(self, n, dtype=np.float64): + self.shape = (n,n) + self.dtype = dtype + + def _matmat(self, V): + return V + + def _transpose(self): + return self + + def _adjoint(self): + return self + diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/mechanism.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/mechanism.py new file mode 100644 index 0000000..fee743e --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/mechanism.py @@ -0,0 +1,194 @@ +import numpy as np +import pandas as pd +import json +from scipy.stats import norm +import pickle +from mbi import Dataset, Domain + +class Mechanism: + """ This class is a template for a mechanism with all the boilerplate code + already implemented. subclasses should implement three functions: + setup, measure, and postprocess + + measure is the only function that is allowed to look at the data, and it must + be privacy-vetted. All other code should not need to be checked with very much scrutiny + """ + def __init__(self, dataset, specs): + self.dataset = dataset + self.specs = json.load(open(specs, 'r')) + domain_info = json.load(open('domain.json')) + + # check consistency for codebook information + for col in list(domain_info): + if domain_info[col][-1] < self.specs[col]['maxval']: + print('Codebook inconsistent for', col) + del domain_info[col] + + ## look at ground truth data to obtain possible values for state-dependent columns + df = pd.read_csv(dataset) + for col in ['SEA', 'METAREA', 'COUNTY', 'CITY', 'METAREAD']: + domain_info[col] = sorted(df[col].unique()) + ## done using ground truth data + + domain = { } + for col in self.specs: + if col in domain_info: + domain[col] = len(domain_info[col]) + else: + domain[col] = self.specs[col]['maxval'] + 1 + + domain['INCWAGE_A'] = 52 + domain['INCWAGE_B'] = 8 + del domain['INCWAGE'] + #domain['INCWAGE'] = 5002 + domain['VALUEH'] = 5003 + + self.domain_info = domain_info + self.domain = Domain.fromdict(domain) + + def setup(self): + """ do any setup needed to run the algorithm here """ + pass + + def load_data(self, path=None): + """ load the data and discretize the integer/float attributes """ + if path is None: + path = self.dataset + df = pd.read_csv(path) + self.column_order = df.columns + + for col in self.domain_info: + vals = self.domain_info[col] + mapping = dict(zip(vals, range(len(vals)))) + df[col] = df[col].map(mapping) + + mapping = { k : k // 100 for k in range(5000) } + mapping[999998] = 51 + mapping.update({ i : 50 for i in range(5000, 999998) }) + df['INCWAGE_A'] = df['INCWAGE'].map(mapping) + + mod_mapping = { k : 0 for k in range(5000, 999999) } + for i in range(5001): + if i % 100 == 0: + mod_mapping[i] = 0 + elif i % 20 == 0: + mod_mapping[i] = 1 + elif i % 50 == 0: + mod_mapping[i] = 2 + elif i % 25 == 0: + mod_mapping[i] = 3 + elif i % 10 == 0: + mod_mapping[i] = 4 + elif i % 5 == 0: + mod_mapping[i] = 5 + elif i % 2 == 0: + mod_mapping[i] = 6 + else: + mod_mapping[i] = 7 + + df['INCWAGE_B'] = df['INCWAGE'].map(mod_mapping) + + mapping = {} + for i in range(9999998): + if i <= 25000: + mapping[i] = i // 5 + else: + mapping[i] = 5000 + + mapping[9999998] = 5001 + mapping[9999999] = 5002 + df['VALUEH'] = df['VALUEH'].map(mapping) + + + return Dataset(df, self.domain) + + + def measure(self): + """ load the data and measure things about it + save the measuremnts taken, but do not save the data + this is the only function that needs to be vetted for privacy + """ + pass + + def postprocess(self): + """ post-process the measurments taken into a synthetic dataset over discrete attributes + """ + + def transform_domain(self): + """ convert the synthetic discrete data back to the original domain + and add any missing columns with a default value """ + df = self.synthetic.df + + mod_mapping = { k : [] for k in range(8) } + for i in range(100): + if i % 100 == 0: + mod_mapping[0].append(i) + elif i % 20 == 0: + mod_mapping[1].append(i) + elif i % 50 == 0: + mod_mapping[2].append(i) + elif i % 25 == 0: + mod_mapping[3].append(i) + elif i % 10 == 0: + mod_mapping[4].append(i) + elif i % 5 == 0: + mod_mapping[5].append(i) + elif i % 2 == 0: + mod_mapping[6].append(i) + else: + mod_mapping[7].append(i) + + def foo(g): + vals = mod_mapping[g.name] + g['INCWAGE_C'] = np.random.choice(vals, g.shape[0]) + return g + df = df.groupby('INCWAGE_B').apply(foo) + + df['INCWAGE'] = df['INCWAGE_A']*100 + df['INCWAGE_C'] + df.loc[df.INCWAGE_A == 50, 'INCWAGE'] = 5000 + df.loc[df.INCWAGE_A == 51, 'INCWAGE'] = 999998 + + for col in self.specs: + if not col in df: + df[col] = 0 + + for col in self.domain_info: + vals = self.domain_info[col] + mapping = dict(zip(range(len(vals)), vals)) + df[col] = df[col].map(mapping) + + mapping = dict(zip(range(5001), range(0,25001,5))) + mapping[5001] = 9999998 + mapping[5002] = 9999999 + df['VALUEH'] = df['VALUEH'].map(mapping) + + self.synthetic = df[self.column_order] + return df + + def run(self, epsilon, delta = 2.2820610e-12, save=None): + """ Run the mechanism at the given privacy level and return teh synthetic data + + :param epsilon: the privacy budget + :param delta: privacy parameter + :param save: location to save the synthetic data + :return: the synthetic data in the same format as original data + """ + self.epsilon = epsilon + self.delta = delta + self.save = save + self.setup() + self.measure() + self.postprocess() + self.transform_domain() + if save is not None: + self.synthetic.to_csv(save, index=False) + return self.synthetic + +if __name__ == '__main__': + from IPython import embed + mech = Mechanism() + df = mech.load_data().df + mech.synthetic = df + mech.transform_domain() + df2 = mech.synthetic + #embed() diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/select-queries.ipynb b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/select-queries.ipynb new file mode 100644 index 0000000..6f5e17b --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/select-queries.ipynb @@ -0,0 +1,191 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import mechanism\n", + "import pandas as pd\n", + "import networkx as nx\n", + "from mbi.junction_tree import JunctionTree\n", + "from mbi import Domain\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from sklearn.cluster import spectral_clustering\n", + "import itertools" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# find the best queries to ask by non-privately analyzing the provisional dataset (assumed to be public)\n", + "\n", + "data = 'competitor_pack/data/colorado.csv'\n", + "specs = 'competitor_pack/data/colorado-specs.json'\n", + "\n", + "M = mechanism.Mechanism(data, specs)\n", + "\n", + "data = M.load_data()\n", + "domain = M.domain" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# calculate mutual info between every pair of attributes\n", + "\n", + "N = data.df.shape[0]\n", + "df = pd.DataFrame(columns=['col1','col2', 'score'])\n", + "idx = 0\n", + "\n", + "for a,b in itertools.combinations(data.domain.attrs, 2):\n", + " A = data.project(a).datavector() / N\n", + " B = data.project(b).datavector() / N\n", + " AB = data.project([a,b]).datavector() / N\n", + " nz = AB > 0\n", + "\n", + " score = np.dot(AB[nz], np.log(AB / np.outer(A,B).flatten())[nz])\n", + " print(a,b,score)\n", + " df.loc[idx] = [a,b,score]\n", + " df.loc[idx+1] = [b,a,score]\n", + " idx += 2\n", + "\n", + "df = df.sort_values('score')\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dom2 = Domain.fromdict(dict(data.df.nunique()))\n", + "nodes = domain.attrs\n", + "weights = df[df.col1.isin(nodes) & df.col2.isin(nodes)]\n", + "weights.loc[(df.col1 == 'SEX') & (df.col2 == 'CITY'), 'score'] += 100\n", + "weights.loc[(df.col1 == 'SEX') & (df.col2 == 'INCWAGE'), 'score'] += 100\n", + "weights.loc[(df.col1 == 'CITY') & (df.col2 == 'INCWAGE'), 'score'] += 100\n", + "weights.loc[(df.col1 == 'CITY') & (df.col2 == 'SEX'), 'score'] += 100\n", + "weights.loc[(df.col1 == 'INCWAGE') & (df.col2 == 'SEX'), 'score'] += 100\n", + "weights.loc[(df.col1 == 'INCWAGE') & (df.col2 == 'CITY'), 'score'] += 100\n", + "\n", + "G = nx.Graph()\n", + "G.add_nodes_from(nodes)\n", + "\n", + "for e1, e2, w in zip(weights['col1'], weights['col2'], weights['score']):\n", + " G.add_edge(e1, e2, weight=w)\n", + " \n", + "mst = nx.maximum_spanning_tree(G)\n", + "print(list(mst.edges))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "res = pd.DataFrame(columns=['A','B','C','size','score'])\n", + "i = 0\n", + "N = data.df.shape[0]\n", + "\n", + "for A in nodes:\n", + " for B, C in itertools.combinations(mst.neighbors(A), 2):\n", + " AB = data.df.groupby([A,B]).size().unstack().fillna(0).values / N\n", + " AC = data.df.groupby([A,C]).size().unstack().fillna(0).values / N\n", + " a,b = AB.shape\n", + " a,c = AC.shape\n", + " AB, AC = AB.reshape(a,b,1), AC.reshape(a,1,c)\n", + " \n", + " ABC2 = AB * AC / AB.sum(axis=1, keepdims=True)\n", + " \n", + " ABC = data.df.groupby([A,B,C]).size().unstack().fillna(0).unstack().fillna(0).stack().stack().values.reshape(a,b,c) / N\n", + "\n", + " score = np.abs(ABC - ABC2).sum()\n", + " #AB = data.project([A,B]).datavector()\n", + " #AC = data.project([A,C]).datavector()\n", + " print(A,B,C)\n", + " res.loc[i] = [A,B,C,a*b*c,score]\n", + " i = i + 1\n", + " \n", + "#res[res.score >= 0.05].sort_values('size',ascending=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print([tuple(row) for row in res.loc[res.score >= 0.10,['A','B','C']].values])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ans = []\n", + "for col in nodes:\n", + " lookup = res.loc[res.A == col]\n", + " G = nx.Graph()\n", + " G.add_nodes_from(mst.neighbors(col))\n", + "\n", + " for e1, e2, w in zip(lookup['B'], lookup['C'], lookup['score']):\n", + " if w >= 0.1:\n", + " G.add_edge(e1, e2, weight=w)\n", + "\n", + " tree = nx.maximum_spanning_tree(G)\n", + " ans += list(tree.edges)\n", + " ans += [(col,) + e for e in tree.edges]\n", + "print(ans)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/writeup.pdf b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/rmckenna/writeup.pdf new file mode 100644 index 0000000000000000000000000000000000000000..79e5404b8479a28898b1734b5a2d60636bad3cfd GIT binary patch literal 98945 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

s5Imkf6U{>ydxM)>gi- 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 CW43Mp literal 0 HcmV?d00001 diff --git a/tools/disassociability/Differentially-Private-Stochastic-Gradient-Descent-DP-SGD/README.md b/tools/disassociability/Differentially-Private-Stochastic-Gradient-Descent-DP-SGD/README.md new file mode 100644 index 0000000..232048c --- /dev/null +++ b/tools/disassociability/Differentially-Private-Stochastic-Gradient-Descent-DP-SGD/README.md @@ -0,0 +1,15 @@ +# Differentially Private Stochastic Gradient Descent (DP-SGD) + +**Brief Description:** Train machine learning models with differential privacy by clipping and noising gradients during stochastic gradient descent. + +**Link to Tool:** [https://github.com/tensorflow/privacy](https://github.com/tensorflow/privacy) + +**Primary Tool Focus Area:** De-identification + +**De-identification Keywords:** Differential Privacy, Machine Learning + +**GitHub User Serving as POC:** @ilyamironov + +**Affiliation/Organization(s) Contributing:** Google + +**Additional Notes:** Paper with full details: [https://arxiv.org/abs/1607.00133](https://arxiv.org/abs/1607.00133) diff --git a/tools/disassociability/Diffprivlib/README.md b/tools/disassociability/Diffprivlib/README.md new file mode 100644 index 0000000..17f22d6 --- /dev/null +++ b/tools/disassociability/Diffprivlib/README.md @@ -0,0 +1,15 @@ +# Diffprivlib + +**Primary Focus Area (select one):** De-identification + +**De-identification Keywords (select any relevant):** Differential Privacy, Machine Learning, Data Analytics + +**Brief Description:** Diffprivlib is a general-purpose Python library for experimenting with, and, building tools for, differential privacy. Diffprivlib includes a number of algorithms for machine learning and data analytics with differential privacy off-the-shelf in the familiar Scikit-learn and Numpy syntax. + +**Additional Notes:** [Introductory whitepaper](https://arxiv.org/abs/1907.02444) + +**GitHub User Serving as POC:** @naoise-h (naoise@ibm.com) + +**Affiliation/Organization(s) Contributing:** IBM Research + +**Tool Link:** https://github.com/IBM/differential-privacy-library diff --git a/tools/disassociability/Duet/README.md b/tools/disassociability/Duet/README.md new file mode 100644 index 0000000..8d90fb8 --- /dev/null +++ b/tools/disassociability/Duet/README.md @@ -0,0 +1,16 @@ +# Duet + +**Primary Focus Area:** De-identification + +**De-identification Keywords:** Differential Privacy, Verification of Algorithms, Machine Learning + +**Brief Description:** Duet is a programming language which automatically derives (and checks) differential privacy bounds for programs written in the language. Duet is designed to support modern machine learning algorithms, and advanced variants of differential privacy in order to add minimal noise to algorithm results in order to ensure privacy. + +**Additional Notes:** +- [paper](https://dl.acm.org/citation.cfm?id=3360598) [[arXiv](https://arxiv.org/abs/1909.02481)] + +**GitHub User Serving as POC:** @jnear + +**Affiliation/Organization(s) Contributing:** University of Vermont, University of California at Berkeley, University of Utah + +**Tool Link:** https://github.com/uvm-plaid/duet diff --git a/tools/disassociability/Ektelo/README.md b/tools/disassociability/Ektelo/README.md new file mode 100644 index 0000000..1c40017 --- /dev/null +++ b/tools/disassociability/Ektelo/README.md @@ -0,0 +1,13 @@ +# Ektelo + +**Brief Description:** Ektelo is a programming framework and system that aids programmers in developing differentially private programs with high utility. Ektelo can be used to author programs for a variety of statistical tasks that involve answering counting queries over a table of arbitrary dimension. + +**Link to Tool:** [https://ektelo.github.io/](https://ektelo.github.io/) + +**Primary Tool Focus Area:** De-identification + +**GitHub User Serving as POC:** @michaelghay + +**Affiliation/Organization(s) Contributing:** UMass Amherst, Duke University, Colgate University + +**Additional Notes:** Ektelo is described in detail in a SIGMOD 2018 paper, titled "EKTELO: A Framework for Defining Differentially-Private Computations." [https://dl.acm.org/citation.cfm?id=3196921](https://dl.acm.org/citation.cfm?id=3196921) diff --git a/tools/disassociability/GUPT/README.md b/tools/disassociability/GUPT/README.md new file mode 100644 index 0000000..ebd5968 --- /dev/null +++ b/tools/disassociability/GUPT/README.md @@ -0,0 +1,17 @@ +# GUPT: Privacy preserving data analysis made easy + +**Primary Focus Area:** De-identification + +**De-identification Keywords:** Differential Privacy, Machine Learning, Database Queries + +**Brief Description:** The tool provides differential privacy guarantees to statistical/machine learning algorithms by treating the underlying algorithm as a black-box, and only relying on input/output signatures. It implements a variant of the celebrated sample and +aggregate framework by Nissim, Rashkhodnikova, and Smith, 2007. The empirical evaluation shows that the system scores well on various +learning tasks (like clustering and regression). + +**Additional Notes:** GUPT is described in detail in a SIGMOD 2012 paper, titled "GUPT: Privacy Preserving Data Analysis Made Easy." A PDF is [available here](https://www.cs.umd.edu/~elaine/docs/gupt.pdf). + +**GitHub POC:** @prashmohan + +**Affiliation/Organization(s) Contributing:** University of California, Berkeley; University of California, Santa Cruz; Cornell University + +**Tool Link:** [https://github.com/prashmohan/GUPT](https://github.com/prashmohan/GUPT) diff --git a/tools/disassociability/Google-DP-Lib/README.md b/tools/disassociability/Google-DP-Lib/README.md new file mode 100644 index 0000000..4563484 --- /dev/null +++ b/tools/disassociability/Google-DP-Lib/README.md @@ -0,0 +1,13 @@ +# Google Differential Privacy Library + +**Primary Focus Area:** De-identification + +**De-identification Keywords:** Differential Privacy + +**Brief Description:** Google's differential privacy library provides a set of building block components that allow developers to build differentially private applications in C++, Java, and Go. Furthermore, Google's DP library offers 'Privacy on Beam', an end-to-end implementation of differential privacy that helps developers perform operations in a differentially private manner. [This codelab](https://codelabs.developers.google.com/codelabs/privacy-on-beam/#0) gives further insight. + +**Email POC:** dp-open-source@google.com + +**Affiliation/Organization(s) Contributing:** Google + +**Tool Link:** https://github.com/google/differential-privacy diff --git a/tools/disassociability/HyFL-Framework/README.md b/tools/disassociability/HyFL-Framework/README.md new file mode 100644 index 0000000..613bf3a --- /dev/null +++ b/tools/disassociability/HyFL-Framework/README.md @@ -0,0 +1,13 @@ +# HyFL Framework for anomaly detection in financial transactions. + +- **Name of Tool:** HyFL framework for financial anomaly detection +- **Primary Focus Area:** De-identification +- **Privacy Risk Assessment Keywords:** Differential Privacy, Information Leakage, Anomaly Detection, Federated Learning, Encryption +- **Brief Desription:** The repository provides a framework HyFL as a tool to detect anomaly in financial transactions. This framework supporst a hybrid federated learning paradigm to offer secure and privacy-aware learning and inference for financial anomaly detection. +- **GitHub User Serving as POC:** hbzhang879@gmail.com +- **Affiliation/Organazations Contributing:** + - Illidan Lab, Michigan State University, USA + - DENOS Lab, University of Calgary, Canada + +# For a Linked Tool +**Tool Link:** https://github.com/illidanlab/HyFL diff --git a/tools/disassociability/MusCAT/README.md b/tools/disassociability/MusCAT/README.md new file mode 100644 index 0000000..6ab09b4 --- /dev/null +++ b/tools/disassociability/MusCAT/README.md @@ -0,0 +1,18 @@ +# MusCAT + +**Primary Focus Area (select one):** De-identification + +**De-identification Keywords (select any relevant):** Differential Privacy, Multiparty Homomorphic Encryption, Machine Learning, Federated Learning + +**Brief Description:** +MusCAT is a multi-scale, hybrid federated system for privacy-preserving epidemic surveillance and risk prediction. +It combines differential privacy, multiparty homomorphic encryption, and federated learning to jointly analyze private data held by multiple federation units with formal privacy guarantees. +This software implements Team MusCAT's solution to the [U.S. PETs Prize Challenge](https://www.drivendata.org/competitions/group/nist-federated-learning/) (Pandemic Forecasting). +Team MusCAT won [first place](https://drivendata.co/blog/federated-learning-pets-prize-winners-phase1) for the white paper (Phase 1) and [second place](https://drivendata.co/blog/federated-learning-pets-prize-winners-phases-2-3) in the final stage (Phase 2) of the Challenge. + + +**GitHub User Serving as POC (or Email Address):** @hhcho + +**Affiliation/Organization(s) Contributing (if relevant):** Broad Institute, MIT, Harvard Business School, UT Austin, University of Toronto + +**Tool Link:** https://github.com/hhcho/muscat diff --git a/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/Count.ipynb b/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/Count.ipynb new file mode 100644 index 0000000..01da4ac --- /dev/null +++ b/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/Count.ipynb @@ -0,0 +1,1641 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a4314281", + "metadata": {}, + "source": [ + "***Disclaimer:** This code was written for the sole educational purpose of\n", + "helping practitioners understand differential privacy concepts.*\n", + "\n", + "***This code is not suitable for use in production environments that process\n", + "sensitive data.***\n", + "\n", + "*One security issues with running this code in a production setting is that it\n", + "is susceptible to side-channel attacks related to the use of floating point\n", + "values. Other security issues surely exist, and we make no effort to enumerate\n", + "all of them.*\n", + "\n", + "---------------------------------------------------------------------------------\n", + "\n", + "# Counting Queries\n", + "\n", + "This Python notebook shows how to perform basic counting queries using the\n", + "differential privacy Laplace mechanism. The notebook covers reading in data from\n", + "a file, preprocessing it, running the query, and invoking the mechanism to\n", + "satisfy differential privacy. In addition to implementing the query, we also\n", + "demonstrate how to quantify and visualize query accuracy.\n", + "\n", + "As a running example, let's answer the following question using a simple dataset\n", + "containing transaction data from a coffee shop:\n", + "\n", + "**How many pumpkin spice lattes have been sold?**\n", + "\n", + "To achieve this using differential privacy, we will complete the following\n", + "steps:\n", + "\n", + "1. Read in the transaction data from the coffee shop\n", + "2. Select the `product` column from the data; this column indicates which\n", + " product was sold in the transaction\n", + "3. Filter the results to include just pumpkin spice lattes\n", + "4. Count the number of rows\n", + "5. Add noise with the Laplace mechanism to achieve differential privacy\n", + "\n", + "Before we start writing code, let's import a few third-party Python packages. We\n", + "will use `csv` for importing datasets encoded in CSV format into Python data\n", + "objects, `numpy` for the ability to sample numbers from the Laplace\n", + "distribution, and `random` for sampling subsets of the data chosen at random." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "43365513", + "metadata": {}, + "outputs": [], + "source": [ + "import csv\n", + "import numpy\n", + "import random\n", + "import matplotlib.pyplot as plt\n", + "import ipywidgets" + ] + }, + { + "cell_type": "markdown", + "id": "1687b0d3", + "metadata": {}, + "source": [ + "## Step 1: Reading in the Data\n", + "\n", + "Our first step is to load the data. We read the CSV file into variable\n", + "`input_data` using regular Python functions, and we also define `col_names` to\n", + "contain the column names for the dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "45bc0606", + "metadata": {}, + "outputs": [], + "source": [ + "filename = \"lattes.csv\"\n", + "with open(filename) as input_file:\n", + " input_data = list(csv.reader(input_file))\n", + " \n", + "col_names = [\"id\", \"product\", \"price\"]" + ] + }, + { + "cell_type": "markdown", + "id": "a6e65493", + "metadata": {}, + "source": [ + "Let's print out the dataset and take a look at what is inside." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cb13d0c3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The data has 3 columns::\n", + "['id', 'product', 'price']\n", + "The data has 1000 rows:\n", + "['1', 'Coffee', '$2.04']\n", + "['2', 'Coffee', '$1.55']\n", + "['3', 'Pumpkin Spice Latte', '$5.94']\n", + "['4', 'Pumpkin Spice Latte', '$3.67']\n", + "['5', 'Latte', '$4.15']\n", + "['6', 'Latte', '$3.47']\n", + "['7', 'Latte', '$1.37']\n", + "['8', 'Coffee', '$5.50']\n", + "['9', 'Latte', '$4.54']\n", + "['10', 'Latte', '$1.32']\n", + "['11', 'Pumpkin Spice Latte', '$5.89']\n", + "['12', 'Latte', '$4.42']\n", + "['13', 'Latte', '$1.08']\n", + "['14', 'Latte', '$2.74']\n", + "['15', 'Latte', '$1.53']\n", + "['16', 'Coffee', '$3.07']\n", + "['17', 'Latte', '$2.31']\n", + "['18', 'Pumpkin Spice Latte', '$1.24']\n", + "['19', 'Pumpkin Spice Latte', '$2.33']\n", + "['20', 'Coffee', '$5.24']\n", + "['21', 'Latte', '$3.08']\n", + "['22', 'Latte', '$4.73']\n", + "['23', 'Pumpkin Spice Latte', '$3.53']\n", + "['24', 'Pumpkin Spice Latte', '$4.46']\n", + "['25', 'Coffee', '$4.86']\n", + "['26', 'Coffee', '$1.72']\n", + "['27', 'Latte', '$2.68']\n", + "['28', 'Coffee', '$2.02']\n", + "['29', 'Pumpkin Spice Latte', '$5.85']\n", + "['30', 'Latte', '$3.72']\n", + "['31', 'Coffee', '$4.63']\n", + "['32', 'Latte', '$2.56']\n", + "['33', 'Latte', '$4.57']\n", + "['34', 'Coffee', '$3.56']\n", + "['35', 'Latte', '$1.09']\n", + "['36', 'Coffee', '$3.58']\n", + "['37', 'Coffee', '$3.48']\n", + "['38', 'Pumpkin Spice Latte', '$1.69']\n", + "['39', 'Latte', '$4.56']\n", + "['40', 'Latte', '$1.03']\n", + "['41', 'Pumpkin Spice Latte', '$4.46']\n", + "['42', 'Pumpkin Spice Latte', '$3.58']\n", + "['43', 'Pumpkin Spice Latte', '$5.74']\n", + "['44', 'Coffee', '$5.78']\n", + "['45', 'Pumpkin Spice Latte', '$2.56']\n", + "['46', 'Latte', '$5.39']\n", + "['47', 'Latte', '$1.79']\n", + "['48', 'Pumpkin Spice Latte', '$3.74']\n", + "['49', 'Pumpkin Spice Latte', '$3.15']\n", + "['50', 'Pumpkin Spice Latte', '$5.36']\n", + "['51', 'Latte', '$3.53']\n", + "['52', 'Pumpkin Spice Latte', '$1.76']\n", + "['53', 'Coffee', '$3.49']\n", + "['54', 'Pumpkin Spice Latte', '$4.83']\n", + "['55', 'Coffee', '$5.25']\n", + "['56', 'Pumpkin Spice Latte', '$2.33']\n", + "['57', 'Pumpkin Spice Latte', '$4.77']\n", + "['58', 'Latte', '$2.85']\n", + "['59', 'Coffee', '$4.88']\n", + "['60', 'Latte', '$5.87']\n", + "['61', 'Pumpkin Spice Latte', '$2.84']\n", + "['62', 'Coffee', '$4.30']\n", + "['63', 'Coffee', '$4.97']\n", + "['64', 'Latte', '$4.07']\n", + "['65', 'Pumpkin Spice Latte', '$1.69']\n", + "['66', 'Latte', '$5.16']\n", + "['67', 'Pumpkin Spice Latte', '$4.36']\n", + "['68', 'Latte', '$5.39']\n", + "['69', 'Latte', '$4.34']\n", + "['70', 'Coffee', '$1.76']\n", + "['71', 'Latte', '$2.90']\n", + "['72', 'Latte', '$1.65']\n", + "['73', 'Pumpkin Spice Latte', '$3.51']\n", + "['74', 'Pumpkin Spice Latte', '$2.38']\n", + "['75', 'Coffee', '$2.66']\n", + "['76', 'Pumpkin Spice Latte', '$3.59']\n", + "['77', 'Coffee', '$5.64']\n", + "['78', 'Pumpkin Spice Latte', '$4.99']\n", + "['79', 'Pumpkin Spice Latte', '$3.92']\n", + "['80', 'Pumpkin Spice Latte', '$3.90']\n", + "['81', 'Coffee', '$1.38']\n", + "['82', 'Coffee', '$2.48']\n", + "['83', 'Pumpkin Spice Latte', '$5.93']\n", + "['84', 'Coffee', '$5.95']\n", + "['85', 'Latte', '$1.58']\n", + "['86', 'Latte', '$1.33']\n", + "['87', 'Latte', '$2.63']\n", + "['88', 'Pumpkin Spice Latte', '$5.81']\n", + "['89', 'Coffee', '$5.98']\n", + "['90', 'Coffee', '$1.79']\n", + "['91', 'Coffee', '$3.19']\n", + "['92', 'Latte', '$2.72']\n", + "['93', 'Coffee', '$5.29']\n", + "['94', 'Coffee', '$3.23']\n", + "['95', 'Pumpkin Spice Latte', '$3.63']\n", + "['96', 'Coffee', '$2.86']\n", + "['97', 'Coffee', '$4.14']\n", + "['98', 'Pumpkin Spice Latte', '$2.29']\n", + "['99', 'Pumpkin Spice Latte', '$4.37']\n", + "['100', 'Latte', '$4.64']\n", + "['101', 'Pumpkin Spice Latte', '$3.78']\n", + "['102', 'Pumpkin Spice Latte', '$1.89']\n", + "['103', 'Coffee', '$4.37']\n", + "['104', 'Pumpkin Spice Latte', '$4.80']\n", + "['105', 'Coffee', '$2.00']\n", + "['106', 'Latte', '$3.30']\n", + "['107', 'Coffee', '$5.16']\n", + "['108', 'Pumpkin Spice Latte', '$3.34']\n", + "['109', 'Latte', '$5.07']\n", + "['110', 'Pumpkin Spice Latte', '$5.04']\n", + "['111', 'Coffee', '$1.24']\n", + "['112', 'Coffee', '$5.46']\n", + "['113', 'Coffee', '$1.30']\n", + "['114', 'Coffee', '$3.81']\n", + "['115', 'Coffee', '$5.08']\n", + "['116', 'Pumpkin Spice Latte', '$2.55']\n", + "['117', 'Coffee', '$4.14']\n", + "['118', 'Pumpkin Spice Latte', '$3.85']\n", + "['119', 'Pumpkin Spice Latte', '$5.73']\n", + "['120', 'Coffee', '$4.76']\n", + "['121', 'Pumpkin Spice Latte', '$5.29']\n", + "['122', 'Pumpkin Spice Latte', '$5.55']\n", + "['123', 'Pumpkin Spice Latte', '$3.75']\n", + "['124', 'Coffee', '$4.93']\n", + "['125', 'Latte', '$2.78']\n", + "['126', 'Latte', '$4.39']\n", + "['127', 'Coffee', '$3.83']\n", + "['128', 'Latte', '$1.84']\n", + "['129', 'Coffee', '$5.85']\n", + "['130', 'Coffee', '$2.09']\n", + "['131', 'Coffee', '$4.52']\n", + "['132', 'Pumpkin Spice Latte', '$4.09']\n", + "['133', 'Latte', '$4.85']\n", + "['134', 'Coffee', '$3.49']\n", + "['135', 'Pumpkin Spice Latte', '$4.33']\n", + "['136', 'Latte', '$5.62']\n", + "['137', 'Latte', '$1.07']\n", + "['138', 'Coffee', '$2.30']\n", + "['139', 'Coffee', '$2.76']\n", + "['140', 'Pumpkin Spice Latte', '$5.68']\n", + "['141', 'Latte', '$5.86']\n", + "['142', 'Latte', '$5.72']\n", + "['143', 'Latte', '$4.95']\n", + "['144', 'Pumpkin Spice Latte', '$5.03']\n", + "['145', 'Latte', '$1.33']\n", + "['146', 'Pumpkin Spice Latte', '$5.63']\n", + "['147', 'Pumpkin Spice Latte', '$3.87']\n", + "['148', 'Pumpkin Spice Latte', '$2.13']\n", + "['149', 'Latte', '$2.78']\n", + "['150', 'Pumpkin Spice Latte', '$3.48']\n", + "['151', 'Coffee', '$5.88']\n", + "['152', 'Pumpkin Spice Latte', '$5.10']\n", + "['153', 'Pumpkin Spice Latte', '$4.27']\n", + "['154', 'Coffee', '$5.47']\n", + "['155', 'Pumpkin Spice Latte', '$3.30']\n", + "['156', 'Coffee', '$5.45']\n", + "['157', 'Coffee', '$5.76']\n", + "['158', 'Latte', '$1.56']\n", + "['159', 'Pumpkin Spice Latte', '$5.30']\n", + "['160', 'Coffee', '$4.88']\n", + "['161', 'Latte', '$1.37']\n", + "['162', 'Pumpkin Spice Latte', '$2.78']\n", + "['163', 'Latte', '$1.20']\n", + "['164', 'Coffee', '$5.61']\n", + "['165', 'Coffee', '$4.05']\n", + "['166', 'Latte', '$1.36']\n", + "['167', 'Pumpkin Spice Latte', '$3.32']\n", + "['168', 'Coffee', '$1.26']\n", + "['169', 'Pumpkin Spice Latte', '$1.36']\n", + "['170', 'Coffee', '$3.35']\n", + "['171', 'Pumpkin Spice Latte', '$5.16']\n", + "['172', 'Coffee', '$1.63']\n", + "['173', 'Coffee', '$1.94']\n", + "['174', 'Pumpkin Spice Latte', '$3.15']\n", + "['175', 'Coffee', '$5.77']\n", + "['176', 'Latte', '$4.60']\n", + "['177', 'Latte', '$2.71']\n", + "['178', 'Pumpkin Spice Latte', '$2.85']\n", + "['179', 'Latte', '$5.66']\n", + "['180', 'Latte', '$3.69']\n", + "['181', 'Pumpkin Spice Latte', '$4.19']\n", + "['182', 'Coffee', '$5.43']\n", + "['183', 'Latte', '$2.09']\n", + "['184', 'Coffee', '$1.08']\n", + "['185', 'Coffee', '$5.71']\n", + "['186', 'Pumpkin Spice Latte', '$5.39']\n", + "['187', 'Latte', '$2.83']\n", + "['188', 'Latte', '$2.58']\n", + "['189', 'Latte', '$5.28']\n", + "['190', 'Coffee', '$2.58']\n", + "['191', 'Pumpkin Spice Latte', '$1.17']\n", + "['192', 'Coffee', '$5.19']\n", + "['193', 'Pumpkin Spice Latte', '$1.06']\n", + "['194', 'Latte', '$2.52']\n", + "['195', 'Latte', '$5.05']\n", + "['196', 'Latte', '$4.55']\n", + "['197', 'Coffee', '$5.05']\n", + "['198', 'Coffee', '$4.51']\n", + "['199', 'Pumpkin Spice Latte', '$3.64']\n", + "['200', 'Latte', '$3.75']\n", + "['201', 'Pumpkin Spice Latte', '$5.55']\n", + "['202', 'Pumpkin Spice Latte', '$4.92']\n", + "['203', 'Coffee', '$3.40']\n", + "['204', 'Pumpkin Spice Latte', '$5.16']\n", + "['205', 'Coffee', '$2.53']\n", + "['206', 'Pumpkin Spice Latte', '$3.29']\n", + "['207', 'Coffee', '$1.96']\n", + "['208', 'Coffee', '$1.52']\n", + "['209', 'Latte', '$4.39']\n", + "['210', 'Latte', '$5.83']\n", + "['211', 'Pumpkin Spice Latte', '$1.25']\n", + "['212', 'Coffee', '$5.75']\n", + "['213', 'Coffee', '$4.35']\n", + "['214', 'Coffee', '$3.23']\n", + "['215', 'Pumpkin Spice Latte', '$1.65']\n", + "['216', 'Pumpkin Spice Latte', '$2.31']\n", + "['217', 'Latte', '$4.80']\n", + "['218', 'Coffee', '$3.69']\n", + "['219', 'Coffee', '$2.44']\n", + "['220', 'Latte', '$2.10']\n", + "['221', 'Pumpkin Spice Latte', '$1.32']\n", + "['222', 'Latte', '$1.71']\n", + "['223', 'Coffee', '$4.66']\n", + "['224', 'Latte', '$1.42']\n", + "['225', 'Latte', '$4.34']\n", + "['226', 'Pumpkin Spice Latte', '$3.12']\n", + "['227', 'Latte', '$2.93']\n", + "['228', 'Coffee', '$1.29']\n", + "['229', 'Coffee', '$2.65']\n", + "['230', 'Latte', '$3.99']\n", + "['231', 'Pumpkin Spice Latte', '$2.65']\n", + "['232', 'Pumpkin Spice Latte', '$4.26']\n", + "['233', 'Latte', '$3.35']\n", + "['234', 'Coffee', '$4.09']\n", + "['235', 'Latte', '$4.93']\n", + "['236', 'Latte', '$1.20']\n", + "['237', 'Latte', '$1.22']\n", + "['238', 'Latte', '$3.76']\n", + "['239', 'Pumpkin Spice Latte', '$2.72']\n", + "['240', 'Latte', '$2.52']\n", + "['241', 'Coffee', '$3.02']\n", + "['242', 'Latte', '$5.74']\n", + "['243', 'Pumpkin Spice Latte', '$2.05']\n", + "['244', 'Latte', '$3.83']\n", + "['245', 'Pumpkin Spice Latte', '$2.44']\n", + "['246', 'Latte', '$3.23']\n", + "['247', 'Latte', '$2.79']\n", + "['248', 'Latte', '$3.93']\n", + "['249', 'Latte', '$5.63']\n", + "['250', 'Coffee', '$2.66']\n", + "['251', 'Coffee', '$1.84']\n", + "['252', 'Coffee', '$4.81']\n", + "['253', 'Coffee', '$5.95']\n", + "['254', 'Coffee', '$1.02']\n", + "['255', 'Latte', '$4.11']\n", + "['256', 'Pumpkin Spice Latte', '$2.31']\n", + "['257', 'Latte', '$1.30']\n", + "['258', 'Pumpkin Spice Latte', '$5.76']\n", + "['259', 'Latte', '$3.89']\n", + "['260', 'Pumpkin Spice Latte', '$2.64']\n", + "['261', 'Coffee', '$1.06']\n", + "['262', 'Coffee', '$4.27']\n", + "['263', 'Pumpkin Spice Latte', '$4.94']\n", + "['264', 'Pumpkin Spice Latte', '$2.75']\n", + "['265', 'Latte', '$4.67']\n", + "['266', 'Coffee', '$1.86']\n", + "['267', 'Coffee', '$5.18']\n", + "['268', 'Latte', '$5.91']\n", + "['269', 'Latte', '$2.47']\n", + "['270', 'Latte', '$5.78']\n", + "['271', 'Latte', '$4.18']\n", + "['272', 'Pumpkin Spice Latte', '$1.52']\n", + "['273', 'Pumpkin Spice Latte', '$4.10']\n", + "['274', 'Coffee', '$5.18']\n", + "['275', 'Pumpkin Spice Latte', '$5.70']\n", + "['276', 'Coffee', '$1.88']\n", + "['277', 'Pumpkin Spice Latte', '$3.64']\n", + "['278', 'Pumpkin Spice Latte', '$4.53']\n", + "['279', 'Coffee', '$2.48']\n", + "['280', 'Pumpkin Spice Latte', '$5.13']\n", + "['281', 'Pumpkin Spice Latte', '$5.52']\n", + "['282', 'Pumpkin Spice Latte', '$1.71']\n", + "['283', 'Coffee', '$5.44']\n", + "['284', 'Coffee', '$4.65']\n", + "['285', 'Coffee', '$3.07']\n", + "['286', 'Latte', '$5.83']\n", + "['287', 'Pumpkin Spice Latte', '$2.90']\n", + "['288', 'Latte', '$4.23']\n", + "['289', 'Pumpkin Spice Latte', '$2.62']\n", + "['290', 'Latte', '$5.70']\n", + "['291', 'Coffee', '$2.78']\n", + "['292', 'Latte', '$3.07']\n", + "['293', 'Coffee', '$2.11']\n", + "['294', 'Pumpkin Spice Latte', '$1.30']\n", + "['295', 'Latte', '$2.40']\n", + "['296', 'Latte', '$5.16']\n", + "['297', 'Latte', '$4.14']\n", + "['298', 'Pumpkin Spice Latte', '$3.19']\n", + "['299', 'Pumpkin Spice Latte', '$5.32']\n", + "['300', 'Coffee', '$2.21']\n", + "['301', 'Latte', '$3.29']\n", + "['302', 'Pumpkin Spice Latte', '$5.72']\n", + "['303', 'Pumpkin Spice Latte', '$3.96']\n", + "['304', 'Coffee', '$2.39']\n", + "['305', 'Latte', '$1.45']\n", + "['306', 'Coffee', '$3.64']\n", + "['307', 'Coffee', '$1.72']\n", + "['308', 'Latte', '$3.47']\n", + "['309', 'Pumpkin Spice Latte', '$1.75']\n", + "['310', 'Coffee', '$2.33']\n", + "['311', 'Coffee', '$3.81']\n", + "['312', 'Latte', '$2.72']\n", + "['313', 'Coffee', '$5.94']\n", + "['314', 'Pumpkin Spice Latte', '$2.39']\n", + "['315', 'Latte', '$4.31']\n", + "['316', 'Coffee', '$1.60']\n", + "['317', 'Pumpkin Spice Latte', '$5.14']\n", + "['318', 'Pumpkin Spice Latte', '$1.83']\n", + "['319', 'Latte', '$5.02']\n", + "['320', 'Coffee', '$2.65']\n", + "['321', 'Pumpkin Spice Latte', '$2.15']\n", + "['322', 'Coffee', '$2.23']\n", + "['323', 'Pumpkin Spice Latte', '$2.79']\n", + "['324', 'Coffee', '$5.82']\n", + "['325', 'Latte', '$5.33']\n", + "['326', 'Latte', '$2.24']\n", + "['327', 'Pumpkin Spice Latte', '$1.64']\n", + "['328', 'Latte', '$2.63']\n", + "['329', 'Pumpkin Spice Latte', '$5.00']\n", + "['330', 'Coffee', '$5.31']\n", + "['331', 'Coffee', '$5.76']\n", + "['332', 'Pumpkin Spice Latte', '$4.06']\n", + "['333', 'Pumpkin Spice Latte', '$5.65']\n", + "['334', 'Coffee', '$4.98']\n", + "['335', 'Coffee', '$3.91']\n", + "['336', 'Latte', '$2.28']\n", + "['337', 'Coffee', '$5.05']\n", + "['338', 'Latte', '$2.60']\n", + "['339', 'Coffee', '$5.57']\n", + "['340', 'Pumpkin Spice Latte', '$5.82']\n", + "['341', 'Latte', '$1.29']\n", + "['342', 'Pumpkin Spice Latte', '$5.19']\n", + "['343', 'Latte', '$5.27']\n", + "['344', 'Pumpkin Spice Latte', '$6.00']\n", + "['345', 'Latte', '$1.00']\n", + "['346', 'Latte', '$3.69']\n", + "['347', 'Coffee', '$2.17']\n", + "['348', 'Pumpkin Spice Latte', '$4.89']\n", + "['349', 'Coffee', '$4.52']\n", + "['350', 'Coffee', '$3.58']\n", + "['351', 'Latte', '$4.05']\n", + "['352', 'Pumpkin Spice Latte', '$2.42']\n", + "['353', 'Latte', '$1.41']\n", + "['354', 'Latte', '$3.03']\n", + "['355', 'Coffee', '$4.73']\n", + "['356', 'Coffee', '$2.89']\n", + "['357', 'Pumpkin Spice Latte', '$5.22']\n", + "['358', 'Pumpkin Spice Latte', '$2.51']\n", + "['359', 'Coffee', '$2.56']\n", + "['360', 'Latte', '$1.37']\n", + "['361', 'Pumpkin Spice Latte', '$4.33']\n", + "['362', 'Coffee', '$5.21']\n", + "['363', 'Coffee', '$3.04']\n", + "['364', 'Latte', '$4.66']\n", + "['365', 'Latte', '$4.58']\n", + "['366', 'Coffee', '$2.51']\n", + "['367', 'Coffee', '$1.21']\n", + "['368', 'Coffee', '$4.61']\n", + "['369', 'Latte', '$4.78']\n", + "['370', 'Coffee', '$5.78']\n", + "['371', 'Latte', '$3.14']\n", + "['372', 'Pumpkin Spice Latte', '$4.66']\n", + "['373', 'Coffee', '$1.82']\n", + "['374', 'Pumpkin Spice Latte', '$2.63']\n", + "['375', 'Pumpkin Spice Latte', '$2.39']\n", + "['376', 'Latte', '$1.38']\n", + "['377', 'Latte', '$2.16']\n", + "['378', 'Latte', '$1.26']\n", + "['379', 'Coffee', '$5.59']\n", + "['380', 'Coffee', '$3.03']\n", + "['381', 'Latte', '$4.56']\n", + "['382', 'Latte', '$3.21']\n", + "['383', 'Latte', '$1.89']\n", + "['384', 'Latte', '$5.00']\n", + "['385', 'Pumpkin Spice Latte', '$3.38']\n", + "['386', 'Latte', '$3.32']\n", + "['387', 'Latte', '$2.32']\n", + "['388', 'Latte', '$1.44']\n", + "['389', 'Latte', '$3.25']\n", + "['390', 'Pumpkin Spice Latte', '$3.31']\n", + "['391', 'Coffee', '$4.80']\n", + "['392', 'Latte', '$3.05']\n", + "['393', 'Latte', '$3.73']\n", + "['394', 'Pumpkin Spice Latte', '$4.70']\n", + "['395', 'Pumpkin Spice Latte', '$3.68']\n", + "['396', 'Coffee', '$5.78']\n", + "['397', 'Pumpkin Spice Latte', '$3.50']\n", + "['398', 'Latte', '$5.77']\n", + "['399', 'Coffee', '$5.27']\n", + "['400', 'Pumpkin Spice Latte', '$3.09']\n", + "['401', 'Coffee', '$4.34']\n", + "['402', 'Coffee', '$2.55']\n", + "['403', 'Coffee', '$5.06']\n", + "['404', 'Latte', '$3.84']\n", + "['405', 'Latte', '$3.63']\n", + "['406', 'Coffee', '$2.44']\n", + "['407', 'Coffee', '$3.06']\n", + "['408', 'Pumpkin Spice Latte', '$5.62']\n", + "['409', 'Coffee', '$5.66']\n", + "['410', 'Latte', '$5.43']\n", + "['411', 'Pumpkin Spice Latte', '$4.82']\n", + "['412', 'Coffee', '$2.35']\n", + "['413', 'Pumpkin Spice Latte', '$2.28']\n", + "['414', 'Latte', '$3.92']\n", + "['415', 'Pumpkin Spice Latte', '$3.39']\n", + "['416', 'Coffee', '$4.32']\n", + "['417', 'Coffee', '$5.13']\n", + "['418', 'Coffee', '$4.21']\n", + "['419', 'Coffee', '$3.40']\n", + "['420', 'Latte', '$4.90']\n", + "['421', 'Pumpkin Spice Latte', '$2.70']\n", + "['422', 'Coffee', '$5.48']\n", + "['423', 'Latte', '$1.94']\n", + "['424', 'Latte', '$1.42']\n", + "['425', 'Coffee', '$3.25']\n", + "['426', 'Pumpkin Spice Latte', '$5.42']\n", + "['427', 'Pumpkin Spice Latte', '$5.17']\n", + "['428', 'Latte', '$4.50']\n", + "['429', 'Pumpkin Spice Latte', '$5.49']\n", + "['430', 'Coffee', '$1.88']\n", + "['431', 'Pumpkin Spice Latte', '$1.80']\n", + "['432', 'Pumpkin Spice Latte', '$1.14']\n", + "['433', 'Latte', '$1.44']\n", + "['434', 'Pumpkin Spice Latte', '$2.91']\n", + "['435', 'Latte', '$2.74']\n", + "['436', 'Latte', '$5.83']\n", + "['437', 'Coffee', '$1.04']\n", + "['438', 'Coffee', '$4.56']\n", + "['439', 'Latte', '$5.58']\n", + "['440', 'Pumpkin Spice Latte', '$5.02']\n", + "['441', 'Latte', '$2.49']\n", + "['442', 'Pumpkin Spice Latte', '$1.17']\n", + "['443', 'Pumpkin Spice Latte', '$1.03']\n", + "['444', 'Latte', '$1.28']\n", + "['445', 'Latte', '$4.56']\n", + "['446', 'Pumpkin Spice Latte', '$3.06']\n", + "['447', 'Coffee', '$5.46']\n", + "['448', 'Pumpkin Spice Latte', '$3.50']\n", + "['449', 'Coffee', '$2.54']\n", + "['450', 'Latte', '$4.21']\n", + "['451', 'Pumpkin Spice Latte', '$4.98']\n", + "['452', 'Coffee', '$5.88']\n", + "['453', 'Latte', '$1.58']\n", + "['454', 'Pumpkin Spice Latte', '$4.69']\n", + "['455', 'Pumpkin Spice Latte', '$4.02']\n", + "['456', 'Pumpkin Spice Latte', '$4.29']\n", + "['457', 'Pumpkin Spice Latte', '$4.16']\n", + "['458', 'Pumpkin Spice Latte', '$5.35']\n", + "['459', 'Coffee', '$1.38']\n", + "['460', 'Pumpkin Spice Latte', '$1.43']\n", + "['461', 'Coffee', '$2.17']\n", + "['462', 'Latte', '$5.42']\n", + "['463', 'Pumpkin Spice Latte', '$3.44']\n", + "['464', 'Latte', '$5.62']\n", + "['465', 'Latte', '$5.47']\n", + "['466', 'Latte', '$3.82']\n", + "['467', 'Latte', '$4.39']\n", + "['468', 'Latte', '$3.94']\n", + "['469', 'Pumpkin Spice Latte', '$6.00']\n", + "['470', 'Coffee', '$1.62']\n", + "['471', 'Coffee', '$4.81']\n", + "['472', 'Latte', '$1.50']\n", + "['473', 'Coffee', '$3.40']\n", + "['474', 'Latte', '$2.13']\n", + "['475', 'Coffee', '$1.56']\n", + "['476', 'Pumpkin Spice Latte', '$2.09']\n", + "['477', 'Latte', '$4.67']\n", + "['478', 'Latte', '$2.59']\n", + "['479', 'Latte', '$1.94']\n", + "['480', 'Pumpkin Spice Latte', '$4.28']\n", + "['481', 'Coffee', '$2.72']\n", + "['482', 'Coffee', '$5.16']\n", + "['483', 'Latte', '$4.41']\n", + "['484', 'Pumpkin Spice Latte', '$3.09']\n", + "['485', 'Pumpkin Spice Latte', '$2.61']\n", + "['486', 'Coffee', '$3.69']\n", + "['487', 'Pumpkin Spice Latte', '$3.71']\n", + "['488', 'Coffee', '$1.06']\n", + "['489', 'Pumpkin Spice Latte', '$5.10']\n", + "['490', 'Coffee', '$2.57']\n", + "['491', 'Pumpkin Spice Latte', '$1.43']\n", + "['492', 'Latte', '$2.26']\n", + "['493', 'Coffee', '$3.99']\n", + "['494', 'Pumpkin Spice Latte', '$5.54']\n", + "['495', 'Latte', '$1.49']\n", + "['496', 'Latte', '$1.28']\n", + "['497', 'Coffee', '$4.57']\n", + "['498', 'Coffee', '$2.26']\n", + "['499', 'Coffee', '$5.72']\n", + "['500', 'Coffee', '$5.66']\n", + "['501', 'Coffee', '$5.45']\n", + "['502', 'Pumpkin Spice Latte', '$3.35']\n", + "['503', 'Coffee', '$4.56']\n", + "['504', 'Pumpkin Spice Latte', '$5.14']\n", + "['505', 'Pumpkin Spice Latte', '$1.31']\n", + "['506', 'Latte', '$2.83']\n", + "['507', 'Latte', '$3.13']\n", + "['508', 'Coffee', '$1.78']\n", + "['509', 'Coffee', '$3.72']\n", + "['510', 'Pumpkin Spice Latte', '$4.85']\n", + "['511', 'Pumpkin Spice Latte', '$2.86']\n", + "['512', 'Pumpkin Spice Latte', '$1.48']\n", + "['513', 'Pumpkin Spice Latte', '$1.43']\n", + "['514', 'Pumpkin Spice Latte', '$1.01']\n", + "['515', 'Pumpkin Spice Latte', '$2.09']\n", + "['516', 'Coffee', '$5.02']\n", + "['517', 'Coffee', '$1.97']\n", + "['518', 'Coffee', '$3.90']\n", + "['519', 'Coffee', '$1.40']\n", + "['520', 'Latte', '$2.03']\n", + "['521', 'Latte', '$4.49']\n", + "['522', 'Latte', '$1.42']\n", + "['523', 'Pumpkin Spice Latte', '$4.29']\n", + "['524', 'Pumpkin Spice Latte', '$3.07']\n", + "['525', 'Pumpkin Spice Latte', '$4.22']\n", + "['526', 'Latte', '$4.62']\n", + "['527', 'Coffee', '$4.29']\n", + "['528', 'Pumpkin Spice Latte', '$4.34']\n", + "['529', 'Coffee', '$3.56']\n", + "['530', 'Latte', '$1.98']\n", + "['531', 'Latte', '$4.05']\n", + "['532', 'Latte', '$3.19']\n", + "['533', 'Coffee', '$5.14']\n", + "['534', 'Latte', '$2.70']\n", + "['535', 'Pumpkin Spice Latte', '$2.28']\n", + "['536', 'Coffee', '$5.12']\n", + "['537', 'Latte', '$1.55']\n", + "['538', 'Coffee', '$2.88']\n", + "['539', 'Latte', '$1.62']\n", + "['540', 'Pumpkin Spice Latte', '$1.75']\n", + "['541', 'Coffee', '$4.33']\n", + "['542', 'Latte', '$5.96']\n", + "['543', 'Latte', '$4.02']\n", + "['544', 'Coffee', '$3.51']\n", + "['545', 'Pumpkin Spice Latte', '$3.59']\n", + "['546', 'Pumpkin Spice Latte', '$2.54']\n", + "['547', 'Pumpkin Spice Latte', '$5.63']\n", + "['548', 'Coffee', '$2.32']\n", + "['549', 'Latte', '$4.14']\n", + "['550', 'Pumpkin Spice Latte', '$5.43']\n", + "['551', 'Coffee', '$4.06']\n", + "['552', 'Coffee', '$3.90']\n", + "['553', 'Pumpkin Spice Latte', '$1.16']\n", + "['554', 'Pumpkin Spice Latte', '$3.39']\n", + "['555', 'Pumpkin Spice Latte', '$1.23']\n", + "['556', 'Pumpkin Spice Latte', '$3.66']\n", + "['557', 'Latte', '$5.40']\n", + "['558', 'Coffee', '$1.46']\n", + "['559', 'Coffee', '$2.21']\n", + "['560', 'Latte', '$4.90']\n", + "['561', 'Pumpkin Spice Latte', '$2.87']\n", + "['562', 'Latte', '$5.09']\n", + "['563', 'Latte', '$4.11']\n", + "['564', 'Latte', '$5.14']\n", + "['565', 'Latte', '$2.01']\n", + "['566', 'Latte', '$2.36']\n", + "['567', 'Coffee', '$1.54']\n", + "['568', 'Latte', '$4.77']\n", + "['569', 'Latte', '$5.11']\n", + "['570', 'Coffee', '$1.14']\n", + "['571', 'Pumpkin Spice Latte', '$5.01']\n", + "['572', 'Coffee', '$3.12']\n", + "['573', 'Coffee', '$1.21']\n", + "['574', 'Pumpkin Spice Latte', '$3.68']\n", + "['575', 'Coffee', '$3.05']\n", + "['576', 'Coffee', '$4.23']\n", + "['577', 'Coffee', '$1.62']\n", + "['578', 'Pumpkin Spice Latte', '$1.36']\n", + "['579', 'Latte', '$4.65']\n", + "['580', 'Latte', '$2.22']\n", + "['581', 'Pumpkin Spice Latte', '$4.66']\n", + "['582', 'Latte', '$4.00']\n", + "['583', 'Latte', '$3.13']\n", + "['584', 'Coffee', '$3.90']\n", + "['585', 'Coffee', '$3.91']\n", + "['586', 'Latte', '$3.23']\n", + "['587', 'Coffee', '$3.18']\n", + "['588', 'Coffee', '$2.62']\n", + "['589', 'Latte', '$1.74']\n", + "['590', 'Latte', '$2.31']\n", + "['591', 'Coffee', '$1.97']\n", + "['592', 'Latte', '$5.03']\n", + "['593', 'Coffee', '$2.04']\n", + "['594', 'Pumpkin Spice Latte', '$3.33']\n", + "['595', 'Coffee', '$3.86']\n", + "['596', 'Latte', '$4.74']\n", + "['597', 'Pumpkin Spice Latte', '$1.91']\n", + "['598', 'Coffee', '$4.91']\n", + "['599', 'Pumpkin Spice Latte', '$2.30']\n", + "['600', 'Latte', '$1.49']\n", + "['601', 'Pumpkin Spice Latte', '$3.33']\n", + "['602', 'Pumpkin Spice Latte', '$2.58']\n", + "['603', 'Pumpkin Spice Latte', '$4.39']\n", + "['604', 'Coffee', '$3.93']\n", + "['605', 'Pumpkin Spice Latte', '$4.51']\n", + "['606', 'Pumpkin Spice Latte', '$2.76']\n", + "['607', 'Pumpkin Spice Latte', '$3.03']\n", + "['608', 'Coffee', '$2.32']\n", + "['609', 'Coffee', '$2.98']\n", + "['610', 'Latte', '$5.77']\n", + "['611', 'Latte', '$3.58']\n", + "['612', 'Pumpkin Spice Latte', '$1.95']\n", + "['613', 'Coffee', '$4.86']\n", + "['614', 'Pumpkin Spice Latte', '$5.70']\n", + "['615', 'Coffee', '$5.91']\n", + "['616', 'Coffee', '$4.80']\n", + "['617', 'Pumpkin Spice Latte', '$1.79']\n", + "['618', 'Pumpkin Spice Latte', '$5.16']\n", + "['619', 'Pumpkin Spice Latte', '$2.88']\n", + "['620', 'Latte', '$2.01']\n", + "['621', 'Pumpkin Spice Latte', '$5.44']\n", + "['622', 'Latte', '$5.11']\n", + "['623', 'Pumpkin Spice Latte', '$3.51']\n", + "['624', 'Latte', '$5.50']\n", + "['625', 'Latte', '$1.11']\n", + "['626', 'Latte', '$4.72']\n", + "['627', 'Pumpkin Spice Latte', '$2.92']\n", + "['628', 'Pumpkin Spice Latte', '$5.70']\n", + "['629', 'Latte', '$5.64']\n", + "['630', 'Pumpkin Spice Latte', '$1.21']\n", + "['631', 'Coffee', '$5.62']\n", + "['632', 'Pumpkin Spice Latte', '$1.65']\n", + "['633', 'Coffee', '$4.62']\n", + "['634', 'Coffee', '$5.73']\n", + "['635', 'Coffee', '$3.90']\n", + "['636', 'Pumpkin Spice Latte', '$5.68']\n", + "['637', 'Pumpkin Spice Latte', '$1.30']\n", + "['638', 'Pumpkin Spice Latte', '$5.95']\n", + "['639', 'Latte', '$3.09']\n", + "['640', 'Pumpkin Spice Latte', '$2.75']\n", + "['641', 'Latte', '$3.86']\n", + "['642', 'Pumpkin Spice Latte', '$5.58']\n", + "['643', 'Pumpkin Spice Latte', '$3.34']\n", + "['644', 'Latte', '$1.81']\n", + "['645', 'Latte', '$2.42']\n", + "['646', 'Latte', '$3.63']\n", + "['647', 'Coffee', '$5.50']\n", + "['648', 'Coffee', '$4.55']\n", + "['649', 'Pumpkin Spice Latte', '$5.85']\n", + "['650', 'Coffee', '$2.02']\n", + "['651', 'Pumpkin Spice Latte', '$2.52']\n", + "['652', 'Latte', '$2.58']\n", + "['653', 'Pumpkin Spice Latte', '$3.38']\n", + "['654', 'Latte', '$3.27']\n", + "['655', 'Pumpkin Spice Latte', '$3.00']\n", + "['656', 'Coffee', '$2.67']\n", + "['657', 'Pumpkin Spice Latte', '$3.72']\n", + "['658', 'Pumpkin Spice Latte', '$4.28']\n", + "['659', 'Latte', '$1.41']\n", + "['660', 'Pumpkin Spice Latte', '$5.67']\n", + "['661', 'Coffee', '$1.52']\n", + "['662', 'Latte', '$4.97']\n", + "['663', 'Pumpkin Spice Latte', '$4.01']\n", + "['664', 'Pumpkin Spice Latte', '$5.78']\n", + "['665', 'Pumpkin Spice Latte', '$1.67']\n", + "['666', 'Pumpkin Spice Latte', '$2.23']\n", + "['667', 'Latte', '$1.17']\n", + "['668', 'Coffee', '$5.86']\n", + "['669', 'Pumpkin Spice Latte', '$5.56']\n", + "['670', 'Coffee', '$5.91']\n", + "['671', 'Pumpkin Spice Latte', '$4.33']\n", + "['672', 'Coffee', '$5.67']\n", + "['673', 'Latte', '$1.96']\n", + "['674', 'Pumpkin Spice Latte', '$2.47']\n", + "['675', 'Pumpkin Spice Latte', '$1.62']\n", + "['676', 'Latte', '$3.70']\n", + "['677', 'Coffee', '$3.76']\n", + "['678', 'Coffee', '$4.72']\n", + "['679', 'Coffee', '$4.22']\n", + "['680', 'Coffee', '$5.16']\n", + "['681', 'Coffee', '$1.93']\n", + "['682', 'Coffee', '$5.85']\n", + "['683', 'Latte', '$4.20']\n", + "['684', 'Pumpkin Spice Latte', '$1.78']\n", + "['685', 'Pumpkin Spice Latte', '$5.26']\n", + "['686', 'Pumpkin Spice Latte', '$5.06']\n", + "['687', 'Coffee', '$5.96']\n", + "['688', 'Latte', '$1.10']\n", + "['689', 'Coffee', '$4.50']\n", + "['690', 'Coffee', '$1.60']\n", + "['691', 'Pumpkin Spice Latte', '$3.65']\n", + "['692', 'Coffee', '$5.30']\n", + "['693', 'Pumpkin Spice Latte', '$1.02']\n", + "['694', 'Coffee', '$5.33']\n", + "['695', 'Latte', '$3.53']\n", + "['696', 'Pumpkin Spice Latte', '$2.77']\n", + "['697', 'Pumpkin Spice Latte', '$4.46']\n", + "['698', 'Pumpkin Spice Latte', '$5.68']\n", + "['699', 'Coffee', '$2.92']\n", + "['700', 'Coffee', '$4.91']\n", + "['701', 'Pumpkin Spice Latte', '$5.68']\n", + "['702', 'Latte', '$3.11']\n", + "['703', 'Pumpkin Spice Latte', '$3.21']\n", + "['704', 'Pumpkin Spice Latte', '$5.71']\n", + "['705', 'Coffee', '$4.93']\n", + "['706', 'Latte', '$5.63']\n", + "['707', 'Coffee', '$5.40']\n", + "['708', 'Coffee', '$3.76']\n", + "['709', 'Latte', '$4.65']\n", + "['710', 'Coffee', '$1.73']\n", + "['711', 'Coffee', '$3.54']\n", + "['712', 'Coffee', '$1.79']\n", + "['713', 'Latte', '$1.17']\n", + "['714', 'Coffee', '$5.43']\n", + "['715', 'Latte', '$2.39']\n", + "['716', 'Pumpkin Spice Latte', '$5.79']\n", + "['717', 'Coffee', '$3.82']\n", + "['718', 'Latte', '$4.22']\n", + "['719', 'Latte', '$5.18']\n", + "['720', 'Pumpkin Spice Latte', '$4.63']\n", + "['721', 'Coffee', '$4.80']\n", + "['722', 'Latte', '$2.96']\n", + "['723', 'Coffee', '$3.67']\n", + "['724', 'Coffee', '$5.17']\n", + "['725', 'Pumpkin Spice Latte', '$4.42']\n", + "['726', 'Coffee', '$4.67']\n", + "['727', 'Pumpkin Spice Latte', '$2.90']\n", + "['728', 'Pumpkin Spice Latte', '$4.20']\n", + "['729', 'Latte', '$4.77']\n", + "['730', 'Pumpkin Spice Latte', '$3.72']\n", + "['731', 'Coffee', '$1.90']\n", + "['732', 'Pumpkin Spice Latte', '$3.21']\n", + "['733', 'Latte', '$1.36']\n", + "['734', 'Latte', '$4.05']\n", + "['735', 'Coffee', '$1.85']\n", + "['736', 'Pumpkin Spice Latte', '$2.48']\n", + "['737', 'Latte', '$5.45']\n", + "['738', 'Coffee', '$3.31']\n", + "['739', 'Pumpkin Spice Latte', '$3.70']\n", + "['740', 'Latte', '$4.12']\n", + "['741', 'Pumpkin Spice Latte', '$4.96']\n", + "['742', 'Coffee', '$4.22']\n", + "['743', 'Pumpkin Spice Latte', '$4.77']\n", + "['744', 'Pumpkin Spice Latte', '$3.77']\n", + "['745', 'Pumpkin Spice Latte', '$4.82']\n", + "['746', 'Coffee', '$5.38']\n", + "['747', 'Latte', '$3.65']\n", + "['748', 'Latte', '$1.40']\n", + "['749', 'Coffee', '$3.41']\n", + "['750', 'Pumpkin Spice Latte', '$1.28']\n", + "['751', 'Coffee', '$1.66']\n", + "['752', 'Pumpkin Spice Latte', '$4.33']\n", + "['753', 'Latte', '$3.85']\n", + "['754', 'Pumpkin Spice Latte', '$1.73']\n", + "['755', 'Coffee', '$5.96']\n", + "['756', 'Latte', '$4.94']\n", + "['757', 'Coffee', '$2.42']\n", + "['758', 'Latte', '$2.35']\n", + "['759', 'Pumpkin Spice Latte', '$4.07']\n", + "['760', 'Pumpkin Spice Latte', '$1.51']\n", + "['761', 'Pumpkin Spice Latte', '$1.34']\n", + "['762', 'Latte', '$3.76']\n", + "['763', 'Latte', '$5.13']\n", + "['764', 'Coffee', '$3.68']\n", + "['765', 'Pumpkin Spice Latte', '$5.32']\n", + "['766', 'Coffee', '$2.29']\n", + "['767', 'Pumpkin Spice Latte', '$3.60']\n", + "['768', 'Coffee', '$1.89']\n", + "['769', 'Coffee', '$3.86']\n", + "['770', 'Latte', '$1.57']\n", + "['771', 'Coffee', '$4.43']\n", + "['772', 'Coffee', '$5.41']\n", + "['773', 'Coffee', '$4.47']\n", + "['774', 'Latte', '$2.85']\n", + "['775', 'Latte', '$3.32']\n", + "['776', 'Latte', '$5.55']\n", + "['777', 'Coffee', '$5.84']\n", + "['778', 'Latte', '$4.90']\n", + "['779', 'Pumpkin Spice Latte', '$4.58']\n", + "['780', 'Pumpkin Spice Latte', '$3.12']\n", + "['781', 'Coffee', '$2.99']\n", + "['782', 'Coffee', '$1.65']\n", + "['783', 'Latte', '$1.93']\n", + "['784', 'Coffee', '$2.51']\n", + "['785', 'Coffee', '$1.45']\n", + "['786', 'Pumpkin Spice Latte', '$5.38']\n", + "['787', 'Coffee', '$1.19']\n", + "['788', 'Coffee', '$3.50']\n", + "['789', 'Coffee', '$2.80']\n", + "['790', 'Latte', '$1.64']\n", + "['791', 'Pumpkin Spice Latte', '$5.04']\n", + "['792', 'Latte', '$4.92']\n", + "['793', 'Coffee', '$1.60']\n", + "['794', 'Pumpkin Spice Latte', '$1.32']\n", + "['795', 'Pumpkin Spice Latte', '$5.37']\n", + "['796', 'Pumpkin Spice Latte', '$5.19']\n", + "['797', 'Coffee', '$5.81']\n", + "['798', 'Latte', '$4.43']\n", + "['799', 'Coffee', '$2.02']\n", + "['800', 'Pumpkin Spice Latte', '$2.21']\n", + "['801', 'Latte', '$2.41']\n", + "['802', 'Pumpkin Spice Latte', '$1.86']\n", + "['803', 'Latte', '$4.43']\n", + "['804', 'Latte', '$1.32']\n", + "['805', 'Pumpkin Spice Latte', '$2.37']\n", + "['806', 'Latte', '$1.49']\n", + "['807', 'Pumpkin Spice Latte', '$5.46']\n", + "['808', 'Latte', '$1.92']\n", + "['809', 'Latte', '$4.34']\n", + "['810', 'Pumpkin Spice Latte', '$3.52']\n", + "['811', 'Pumpkin Spice Latte', '$1.70']\n", + "['812', 'Coffee', '$4.70']\n", + "['813', 'Coffee', '$2.36']\n", + "['814', 'Latte', '$5.90']\n", + "['815', 'Latte', '$1.42']\n", + "['816', 'Coffee', '$5.25']\n", + "['817', 'Coffee', '$4.38']\n", + "['818', 'Coffee', '$1.49']\n", + "['819', 'Pumpkin Spice Latte', '$4.64']\n", + "['820', 'Pumpkin Spice Latte', '$3.93']\n", + "['821', 'Pumpkin Spice Latte', '$1.65']\n", + "['822', 'Latte', '$4.28']\n", + "['823', 'Latte', '$1.82']\n", + "['824', 'Latte', '$3.90']\n", + "['825', 'Latte', '$2.47']\n", + "['826', 'Latte', '$3.75']\n", + "['827', 'Coffee', '$4.10']\n", + "['828', 'Pumpkin Spice Latte', '$1.87']\n", + "['829', 'Pumpkin Spice Latte', '$4.45']\n", + "['830', 'Pumpkin Spice Latte', '$1.35']\n", + "['831', 'Coffee', '$4.82']\n", + "['832', 'Latte', '$3.16']\n", + "['833', 'Coffee', '$2.10']\n", + "['834', 'Coffee', '$5.57']\n", + "['835', 'Latte', '$3.57']\n", + "['836', 'Coffee', '$3.67']\n", + "['837', 'Pumpkin Spice Latte', '$1.76']\n", + "['838', 'Latte', '$3.44']\n", + "['839', 'Pumpkin Spice Latte', '$3.61']\n", + "['840', 'Pumpkin Spice Latte', '$3.24']\n", + "['841', 'Pumpkin Spice Latte', '$5.45']\n", + "['842', 'Pumpkin Spice Latte', '$4.28']\n", + "['843', 'Latte', '$2.54']\n", + "['844', 'Coffee', '$2.82']\n", + "['845', 'Latte', '$2.63']\n", + "['846', 'Latte', '$5.78']\n", + "['847', 'Pumpkin Spice Latte', '$1.24']\n", + "['848', 'Pumpkin Spice Latte', '$1.04']\n", + "['849', 'Pumpkin Spice Latte', '$3.11']\n", + "['850', 'Pumpkin Spice Latte', '$5.16']\n", + "['851', 'Latte', '$5.92']\n", + "['852', 'Latte', '$4.38']\n", + "['853', 'Latte', '$2.41']\n", + "['854', 'Pumpkin Spice Latte', '$2.00']\n", + "['855', 'Latte', '$2.58']\n", + "['856', 'Latte', '$3.49']\n", + "['857', 'Pumpkin Spice Latte', '$1.65']\n", + "['858', 'Latte', '$4.71']\n", + "['859', 'Coffee', '$4.37']\n", + "['860', 'Latte', '$1.23']\n", + "['861', 'Latte', '$5.43']\n", + "['862', 'Latte', '$3.58']\n", + "['863', 'Latte', '$5.99']\n", + "['864', 'Latte', '$1.79']\n", + "['865', 'Coffee', '$1.88']\n", + "['866', 'Pumpkin Spice Latte', '$1.22']\n", + "['867', 'Pumpkin Spice Latte', '$4.98']\n", + "['868', 'Pumpkin Spice Latte', '$2.95']\n", + "['869', 'Latte', '$5.41']\n", + "['870', 'Coffee', '$2.14']\n", + "['871', 'Latte', '$5.76']\n", + "['872', 'Coffee', '$3.74']\n", + "['873', 'Pumpkin Spice Latte', '$3.41']\n", + "['874', 'Latte', '$1.47']\n", + "['875', 'Pumpkin Spice Latte', '$3.53']\n", + "['876', 'Coffee', '$4.36']\n", + "['877', 'Latte', '$5.18']\n", + "['878', 'Pumpkin Spice Latte', '$4.63']\n", + "['879', 'Coffee', '$5.83']\n", + "['880', 'Pumpkin Spice Latte', '$4.59']\n", + "['881', 'Latte', '$4.90']\n", + "['882', 'Coffee', '$2.43']\n", + "['883', 'Coffee', '$4.46']\n", + "['884', 'Latte', '$2.50']\n", + "['885', 'Coffee', '$3.21']\n", + "['886', 'Coffee', '$1.97']\n", + "['887', 'Pumpkin Spice Latte', '$1.01']\n", + "['888', 'Latte', '$5.90']\n", + "['889', 'Coffee', '$1.57']\n", + "['890', 'Pumpkin Spice Latte', '$2.77']\n", + "['891', 'Coffee', '$1.07']\n", + "['892', 'Coffee', '$2.23']\n", + "['893', 'Latte', '$3.85']\n", + "['894', 'Latte', '$2.98']\n", + "['895', 'Pumpkin Spice Latte', '$4.22']\n", + "['896', 'Pumpkin Spice Latte', '$5.14']\n", + "['897', 'Coffee', '$4.90']\n", + "['898', 'Coffee', '$3.43']\n", + "['899', 'Coffee', '$5.06']\n", + "['900', 'Coffee', '$4.12']\n", + "['901', 'Latte', '$3.94']\n", + "['902', 'Coffee', '$4.78']\n", + "['903', 'Pumpkin Spice Latte', '$3.80']\n", + "['904', 'Latte', '$5.65']\n", + "['905', 'Coffee', '$4.68']\n", + "['906', 'Latte', '$1.03']\n", + "['907', 'Pumpkin Spice Latte', '$2.74']\n", + "['908', 'Coffee', '$2.55']\n", + "['909', 'Latte', '$2.45']\n", + "['910', 'Pumpkin Spice Latte', '$1.23']\n", + "['911', 'Coffee', '$3.92']\n", + "['912', 'Latte', '$2.92']\n", + "['913', 'Coffee', '$2.98']\n", + "['914', 'Latte', '$4.38']\n", + "['915', 'Pumpkin Spice Latte', '$3.42']\n", + "['916', 'Latte', '$5.78']\n", + "['917', 'Coffee', '$1.96']\n", + "['918', 'Pumpkin Spice Latte', '$2.37']\n", + "['919', 'Coffee', '$4.89']\n", + "['920', 'Coffee', '$4.19']\n", + "['921', 'Coffee', '$4.40']\n", + "['922', 'Latte', '$1.60']\n", + "['923', 'Pumpkin Spice Latte', '$5.64']\n", + "['924', 'Latte', '$1.60']\n", + "['925', 'Latte', '$4.55']\n", + "['926', 'Coffee', '$2.28']\n", + "['927', 'Coffee', '$2.54']\n", + "['928', 'Pumpkin Spice Latte', '$4.38']\n", + "['929', 'Pumpkin Spice Latte', '$2.28']\n", + "['930', 'Latte', '$5.39']\n", + "['931', 'Latte', '$2.70']\n", + "['932', 'Latte', '$1.92']\n", + "['933', 'Pumpkin Spice Latte', '$2.73']\n", + "['934', 'Pumpkin Spice Latte', '$5.61']\n", + "['935', 'Latte', '$4.76']\n", + "['936', 'Coffee', '$4.03']\n", + "['937', 'Coffee', '$5.05']\n", + "['938', 'Coffee', '$1.71']\n", + "['939', 'Coffee', '$1.59']\n", + "['940', 'Coffee', '$5.24']\n", + "['941', 'Latte', '$4.63']\n", + "['942', 'Coffee', '$1.13']\n", + "['943', 'Latte', '$2.78']\n", + "['944', 'Latte', '$3.98']\n", + "['945', 'Pumpkin Spice Latte', '$2.12']\n", + "['946', 'Coffee', '$3.45']\n", + "['947', 'Coffee', '$2.73']\n", + "['948', 'Pumpkin Spice Latte', '$2.34']\n", + "['949', 'Coffee', '$2.51']\n", + "['950', 'Pumpkin Spice Latte', '$5.57']\n", + "['951', 'Pumpkin Spice Latte', '$3.94']\n", + "['952', 'Coffee', '$4.41']\n", + "['953', 'Latte', '$2.11']\n", + "['954', 'Coffee', '$3.63']\n", + "['955', 'Latte', '$1.91']\n", + "['956', 'Latte', '$5.74']\n", + "['957', 'Pumpkin Spice Latte', '$1.51']\n", + "['958', 'Coffee', '$3.37']\n", + "['959', 'Coffee', '$3.13']\n", + "['960', 'Coffee', '$5.67']\n", + "['961', 'Latte', '$4.75']\n", + "['962', 'Coffee', '$3.94']\n", + "['963', 'Coffee', '$1.44']\n", + "['964', 'Pumpkin Spice Latte', '$2.19']\n", + "['965', 'Latte', '$4.43']\n", + "['966', 'Latte', '$3.31']\n", + "['967', 'Latte', '$2.36']\n", + "['968', 'Latte', '$3.55']\n", + "['969', 'Pumpkin Spice Latte', '$2.87']\n", + "['970', 'Coffee', '$4.21']\n", + "['971', 'Latte', '$3.07']\n", + "['972', 'Pumpkin Spice Latte', '$4.90']\n", + "['973', 'Coffee', '$1.91']\n", + "['974', 'Latte', '$5.29']\n", + "['975', 'Pumpkin Spice Latte', '$3.77']\n", + "['976', 'Pumpkin Spice Latte', '$1.15']\n", + "['977', 'Latte', '$5.56']\n", + "['978', 'Latte', '$4.45']\n", + "['979', 'Pumpkin Spice Latte', '$4.67']\n", + "['980', 'Latte', '$4.15']\n", + "['981', 'Coffee', '$5.24']\n", + "['982', 'Coffee', '$4.64']\n", + "['983', 'Coffee', '$5.16']\n", + "['984', 'Coffee', '$4.43']\n", + "['985', 'Coffee', '$4.90']\n", + "['986', 'Pumpkin Spice Latte', '$4.66']\n", + "['987', 'Latte', '$4.77']\n", + "['988', 'Latte', '$4.06']\n", + "['989', 'Pumpkin Spice Latte', '$2.07']\n", + "['990', 'Pumpkin Spice Latte', '$1.27']\n", + "['991', 'Coffee', '$3.43']\n", + "['992', 'Pumpkin Spice Latte', '$5.74']\n", + "['993', 'Pumpkin Spice Latte', '$4.13']\n", + "['994', 'Coffee', '$3.62']\n", + "['995', 'Latte', '$4.05']\n", + "['996', 'Latte', '$2.92']\n", + "['997', 'Coffee', '$4.61']\n", + "['998', 'Pumpkin Spice Latte', '$4.28']\n", + "['999', 'Latte', '$2.24']\n", + "['1000', 'Latte', '$4.84']\n" + ] + } + ], + "source": [ + "print(f\"The data has {len(col_names)} columns::\")\n", + "print(col_names)\n", + "print(f\"The data has {len(input_data)} rows:\")\n", + "for row in input_data:\n", + " print(row)" + ] + }, + { + "cell_type": "markdown", + "id": "0da357e6", + "metadata": {}, + "source": [ + "## Step 2: Selecting the Product Column\n", + "\n", + "Our second step is to select the `product` column of the data. We do this using\n", + "standard python functions and a list comprehension expression, and call this\n", + "operation `query1`. After defining the function we print out the first five\n", + "results." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ea921091", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The product type for transactions 1–5:\n", + "Coffee\n", + "Coffee\n", + "Pumpkin Spice Latte\n", + "Pumpkin Spice Latte\n", + "Latte\n" + ] + } + ], + "source": [ + "def query1(data):\n", + " return [ row[col_names.index(\"product\")] \n", + " for row\n", + " in data \n", + " ]\n", + "\n", + "print(\"The product type for transactions 1–5:\")\n", + "for x in query1(input_data)[:5]:\n", + " print(x)" + ] + }, + { + "cell_type": "markdown", + "id": "8692d934", + "metadata": {}, + "source": [ + "## Step 3: Filter and Count\n", + "\n", + "Now we're ready to count the number of pumpkin spice lattes. The results tell us\n", + "(without differential privacy) how many pumpkin spice lattes were sold. To\n", + "create this query, we reuse `query1` from before that selects the `\"product\"`\n", + "column, and count up the elements that are pumpkin spice lattes. We call this\n", + "operation `query2`, and after defining the function we print out the results." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ffbb0ebc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The number of 'Pumpkin Spice Latte' product types:\n", + "331\n" + ] + } + ], + "source": [ + "def query2(data): \n", + " return sum([ 1 if product == \"Pumpkin Spice Latte\" else 0 \n", + " for product \n", + " in query1(data) ])\n", + " \n", + "print(\"The number of 'Pumpkin Spice Latte' product types:\")\n", + "print(query2(input_data))" + ] + }, + { + "cell_type": "markdown", + "id": "99e53a11", + "metadata": {}, + "source": [ + "## Step 4: Laplace Mechanism\n", + "\n", + "The last step is to apply the Laplace mechanism in order to achieve differential\n", + "privacy. To apply the Laplace mechanism we sample noise from the Laplace\n", + "distribution and add it to the query count (the\n", + "output of `query2`).\n", + "\n", + "In order to achieve the correct quantity of privacy we need to use the correct\n", + "scale parameter when sampling from the Laplace distribution. This correct scale\n", + "parameter depends on the *sensitivity* of the query whose result we are adding\n", + "noise to (`query2` in this case), as well as the desired *privacy budget*\n", + "$\\epsilon$ we would like to obtain. The formula for the Laplace scale parameter\n", + "is $sensitivity / \\epsilon$. In our case the query has $sensitivity = 1$, and we\n", + "have decided to pick $\\epsilon = 0.1$ for the privacy budget, so we use a scale\n", + "of $1/0.1 = 10$ for the noise parameter.\n", + "\n", + "We call this operation `query3`, and after defining the function we print out\n", + "the results. Note that the result is based on sampling random noise, so the\n", + "output will be different every time the function is called." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5a24c5b7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(Running multiple times will give different results each time.)\n", + "A differentially private count of Pumpkin Spice Lattes in the dataset:\n", + "323.57855412344014\n" + ] + } + ], + "source": [ + "sensitivity = 1\n", + "epsilon = 0.1\n", + "noise_scale = sensitivity / epsilon\n", + "\n", + "def query3(data):\n", + " return query2(data) + numpy.random.laplace(0, noise_scale)\n", + "\n", + "print(\"(Running multiple times will give different results each time.)\")\n", + "print(\"A differentially private count of Pumpkin Spice Lattes in the dataset:\")\n", + "print(query3(input_data))" + ] + }, + { + "cell_type": "markdown", + "id": "87058ea1", + "metadata": {}, + "source": [ + "## Accuracy\n", + "\n", + "Let's try to get a sense of how *accurate* the differentially private results\n", + "are. We expect that when called multiple times, the average of all results\n", + "(i.e., the mean) will be equal to the true count. And we are interested on how\n", + "spread out the results are in general (i.e., the variance). Let's test this\n", + "empirically by performing one-thousand trials and computing the mean and\n", + "variance." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "3db99caf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(Running multiple times will give different results.)\n", + "Empirical mean and variance for 1000 samples:\n", + "mean: 330.4299119957137\n", + " var: 179.0723055181324\n" + ] + } + ], + "source": [ + "trials = [ query3(input_data) \n", + " for _ \n", + " in range(0, 1000) \n", + " ]\n", + "\n", + "print(\"(Running multiple times will give different results.)\")\n", + "print(\"Empirical mean and variance for 1000 samples:\")\n", + "print(f\"mean: {numpy.mean(trials)}\")\n", + "print(f\" var: {numpy.var(trials)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "67dcf7f0", + "metadata": {}, + "source": [ + "In fact, because we can characterize the exact distribution of values the\n", + "results are drawn from—the Laplace distribution—we can also just calculate the\n", + "mean and variance without doing any trials. The formula for variance is\n", + "$variance = 2 * scale^2$. We call these the *analytic* mean and variance because\n", + "they are calculated directly, as opposed to the *empirical* mean and variance,\n", + "which are estimated based on random trials. Notice that the analytic mean and\n", + "variance are very likely to be slightly different than the empirical ones. As we\n", + "perform more and more random trials to compute the empirical mean, we expect\n", + "them to come closer and closer together." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9b506d50", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Analytic mean and variance:\n", + "mean: 331\n", + "var: 200.0\n" + ] + } + ], + "source": [ + "analytic_mean = query2(input_data)\n", + "analytic_variance = 2 * noise_scale ** 2\n", + "\n", + "print(\"Analytic mean and variance:\")\n", + "print(f\"mean: {analytic_mean}\")\n", + "print(f\"var: {analytic_variance}\")" + ] + }, + { + "cell_type": "markdown", + "id": "881607e9", + "metadata": {}, + "source": [ + "Now we know the variance of the result is 200. However, interpreting this number\n", + "(the distribution variance) isn't very intuitive. A common method for making\n", + "distribution variance more interpretable is to convert the quantity to a *95%\n", + "confidence interval* (95% CI) represented as a numeric range (lower bound and\n", + "upper bound). 95% of the time, samples from the distribution will fall within\n", + "the 95% CI range, and the 95% CI for a dataset with $N$ elements is computed\n", + "using the formula $CI = 1.96 * \\frac{\\sqrt{variance}}{\\sqrt{N}}$.\n", + "\n", + "We would like to visualize the 95% CI range for a number of different choices\n", + "for $\\epsilon$. To do this we will create a line and error bar plot with choices\n", + "for $\\epsilon$ on the $x$ axis, the mean of `query3` (for that choice of\n", + "$\\epsilon$) on the $y$ axis, and the 95% CI as error bars. The mean of `query3`\n", + "is independent from the choice of $\\epsilon$, so the line will be a straight\n", + "horizontal line, but the 95% CI (calculated from the variance) will change for\n", + "different choices of $\\epsilon$." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "7607857b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(330.0, 332.0)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "

" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "def analytic_variance_from_epsilon(epsilon):\n", + " sensitivity = 1\n", + " noise_scale = sensitivity / epsilon\n", + " return 2 * noise_scale ** 2\n", + "\n", + "def ci_from_variance(variance, samples):\n", + " return 1.96 * numpy.sqrt(variance) / numpy.sqrt(samples)\n", + "\n", + "# plot 200 epsilon values from 0.1 to 2.0 on x-axis\n", + "epsilons = numpy.linspace(0.1, 2.0, 200)\n", + "\n", + "# for each epsilon (x-axis), plot the analytic mean and associated 95% CI\n", + "plt.errorbar(epsilons,\n", + " [analytic_mean for epsilon in epsilons],\n", + " [ci_from_variance(analytic_variance_from_epsilon(epsilon), \n", + " len(input_data)) \\\n", + " for epsilon in epsilons])\n", + "plt.grid()\n", + "plt.xlabel(\"Privacy parameter ε\")\n", + "plt.ylabel(\"Mean with 95% CI\")\n", + "plt.ylim((330,332))" + ] + }, + { + "cell_type": "markdown", + "id": "3ceefc4b", + "metadata": {}, + "source": [ + "Another nice way to interpret the variance of a distribution is to just\n", + "visualize the distribution itself in a histogram plot. Let's create a histogram\n", + "of `query3` output for 20 different choices of $\\epsilon$. This will create an\n", + "interactive histogram plot where changing the slider will recreate the histogram\n", + "for each value of $\\epsilon$." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8d905991", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f5db54aa9fb54dd4bd8125b0b13eb2b5", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=9, description='i', max=19), Output()), _dom_classes=('widget-interact',…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def many_query3_from_epsilon(epsilon):\n", + " sensitivity = 1\n", + " noise_scale = sensitivity / epsilon\n", + " return numpy.random.laplace(0, noise_scale, size=1000) + query2(input_data)\n", + "\n", + "# run query 3 for epsilon values between 0.01 and 2 \n", + "epsilons = numpy.linspace(0.01, 2, 20)\n", + "query_results = [many_query3_from_epsilon(epsilon) for epsilon in epsilons]\n", + "\n", + "def plot_hist(i):\n", + " plt.hist(query_results[i], 100, range=(analytic_mean-50, analytic_mean+50))\n", + " plt.title(f\"ε = {epsilons[i]:.2f}\")\n", + " plt.xlabel(\"count of pumpkin spice lattes\")\n", + " plt.ylabel(\"probability density\")\n", + " plt.ylim((0, 400))\n", + "\n", + "# construct an interactive widget showing the histogram for each epsilon\n", + "ipywidgets.interact(plot_hist, i=(0, len(epsilons)-1));" + ] + }, + { + "cell_type": "markdown", + "id": "5c020e97", + "metadata": {}, + "source": [ + "Let's explore one final way to interpret the distribution variance as it relates\n", + "to choices of epsilon. There is another setting where distribution variance\n", + "appears, and that is from dataset *subsampling*. The idea is to compare the\n", + "variance of the result when computed on the entire dataset to the variance of\n", + "the result when computed on a random subsample of the dataset. For this\n", + "experiment we will run `query2`, the raw count of pumpkin spice lattes, and\n", + "measure the variance of the result based on the size of subsample." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9993d4b5", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "def subsample_variance(data, ratio):\n", + " # convert the ratio into a subsample size\n", + " size = round(len(data)*ratio)\n", + " trials = [ # run query2 on a subsample of the dataset\n", + " query2(random.sample(data, size))\n", + " # rescale the result by the size of the subsample\n", + " * len(data) / size\n", + " for _ \n", + " # do this 1000 times\n", + " in range(0, 1000) \n", + " ]\n", + " # compute the variance of all subsample trials\n", + " return numpy.var(trials)\n", + "\n", + "subsample_sizes = numpy.linspace(0.01, 0.99, 30)\n", + "plt.errorbar(subsample_sizes,\n", + " [analytic_mean for _ in subsample_sizes],\n", + " [ci_from_variance(subsample_variance(input_data, size), len(input_data) * size) \\\n", + " for size in subsample_sizes])\n", + "plt.grid()\n", + "plt.xlabel(\"Subsample size (fraction of data)\")\n", + "plt.ylabel(\"Mean with 95% C.I\")\n", + "plt.ylim((330,332));" + ] + }, + { + "cell_type": "markdown", + "id": "39789ead", + "metadata": {}, + "source": [ + "This plot shows that running `query2` (the raw query) on a subsample introduces\n", + "noise into the result, and we can visualize this variance as a 95% CI. In a\n", + "previous plot we visualized in a similar way running `query3` (the\n", + "differentially private query) which also introduces noise into the result. Our\n", + "final question is then: given that subsampling and differential privacy both\n", + "introduce noise into the result, which choices of subsample size (for `query2`)\n", + "are equivalent to which choices of $epsilon$ (for `query3`)? Let's make a plot\n", + "that relates these two values, and for different sizes of the original dataset.\n", + "\n", + "(Creating the next plot takes more than a few seconds to generate.)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "dbcbd318", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "def epsilon_from_variance(variance):\n", + " sensitivity = 1\n", + " return sensitivity / numpy.sqrt(variance / 2)\n", + "\n", + "input_data_large = sum([input_data for _ in range(0, 5)], [])\n", + "\n", + "# Create a plot with:\n", + "# - 10 points between 0.01 and 0.10\n", + "# - 10 points between 0.10 and 0.9\n", + "# - 10 points between 0.9 and 0.99\n", + "subsample_sizes = numpy.concatenate([numpy.linspace(0.01, 0.10, 10),\n", + " numpy.linspace(0.10, 0.9, 10),\n", + " numpy.linspace(0.9, 0.99, 10)])\n", + "\n", + "def graph_size(size):\n", + " ys = [epsilon_from_variance(subsample_variance(input_data_large[0:(size-1)], x)) \\\n", + " for x in subsample_sizes]\n", + " plt.plot(subsample_sizes, ys, label=f\"Data size={size}\")\n", + "\n", + "graph_size(100)\n", + "graph_size(1000)\n", + "graph_size(5000)\n", + "\n", + "plt.title(\"subsample size vs epsilon with equal variance\")\n", + "plt.xlabel(\"Subsample size (fraction of data)\")\n", + "plt.ylabel(\"Privacy parameter ε\")\n", + "plt.ylim((0, 2))\n", + "plt.grid()\n", + "plt.legend();" + ] + }, + { + "cell_type": "markdown", + "id": "c0246fd2", + "metadata": {}, + "source": [ + "As you can see, for the small (size = 100) dataset in the experiment, an\n", + "$\\epsilon$ value of 1 introduces the same amount of noise as taking a subsample\n", + "of 90% of the dataset. For the large (size = 5000) dataset in the experiment, an\n", + "$\\epsilon$ value of 1 introduces the same amount of noise as dropping a\n", + "negligibly small number of rows (just 1 or 2) from the dataset.\n", + "\n", + "Note that this experiment is likely *highly* dependent on the specific query\n", + "*and* the dataset in question, which in this case is just a simple counting\n", + "query performed over naively generated synthetic data. For this reason, these\n", + "results should *not* be interpreted as relating distribution variance due to\n", + "$\\epsilon$ and subsample size choice in general." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/HistogramBias.ipynb b/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/HistogramBias.ipynb new file mode 100644 index 0000000..bcd1639 --- /dev/null +++ b/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/HistogramBias.ipynb @@ -0,0 +1,1018 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b7967df1", + "metadata": {}, + "source": [ + "# Histograms, Differential Privacy and Bias\n", + "\n", + "This Python notebook shows how differential privacy can magnify bias in simple\n", + "data queries like histograms. The notebook covers reading in data from a file,\n", + "preparing the data, computing a histogram, and observing issues with bias.\n", + "\n", + "As a running example, let's generate a simple histogram of subpopulations in the\n", + "Diverse Communities Data Excerpts dataset curated by NIST and drawn from the US\n", + "Census Bureau's American Communities Survey (ACS).\n", + "\n", + "## Step 1: Loading the Data\n", + "\n", + "Before we start writing code, let's import a few third-party Pyhon packages. We\n", + "will use `pandas` for creating and manipulation data frames, `numpy` for basic\n", + "operations over vectorized datatypes, and `matplotlib` for visualizing results." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f42a514d", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "id": "59df6096", + "metadata": {}, + "source": [ + "Our first step is to load the data. To do this we read the CSV file into a\n", + "dataframe object using `pandas`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "73b5e96b", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv('https://media.githubusercontent.com/media/usnistgov/SDNist/main/nist%20diverse%20communities%20data%20excerpts/massachusetts/ma2019.csv')" + ] + }, + { + "cell_type": "markdown", + "id": "12c8a842", + "metadata": {}, + "source": [ + "## Step 2: Preparing the Data\n", + "\n", + "Let's display the data frame to get a sense for what the data looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7c5a4fff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PUMAAGEPSEXMSPHISPRAC1PNOCNPFHOUSING_TYPEOWN_RENT...PINCPPINCP_DECILEPOVPIPDVETDREMDPHYDEYEDEARPWGTPWGTP
025-00503181601NN30...5000.01NN2222720
125-00703212601NN30...0.00NN222260
225-00503222606NN30...18000.03NN2222800
325-01300581601NN20...0.00NN1222570
425-00703182601NN30...3300.01NN2222240
..................................................................
762925-0130081N012311...NN501N22224541
763025-00703141N011311...NN501N2222115114
763125-0050331N062411...NN347NNN226975
763225-0050312N062411...NN347NNN226475
763325-0280001N011311...NN365NNN22107145
\n", + "

7634 rows × 24 columns

\n", + "
" + ], + "text/plain": [ + " PUMA AGEP SEX MSP HISP RAC1P NOC NPF HOUSING_TYPE OWN_RENT \\\n", + "0 25-00503 18 1 6 0 1 N N 3 0 \n", + "1 25-00703 21 2 6 0 1 N N 3 0 \n", + "2 25-00503 22 2 6 0 6 N N 3 0 \n", + "3 25-01300 58 1 6 0 1 N N 2 0 \n", + "4 25-00703 18 2 6 0 1 N N 3 0 \n", + "... ... ... ... .. ... ... .. .. ... ... \n", + "7629 25-01300 8 1 N 0 1 2 3 1 1 \n", + "7630 25-00703 14 1 N 0 1 1 3 1 1 \n", + "7631 25-00503 3 1 N 0 6 2 4 1 1 \n", + "7632 25-00503 1 2 N 0 6 2 4 1 1 \n", + "7633 25-02800 0 1 N 0 1 1 3 1 1 \n", + "\n", + " ... PINCP PINCP_DECILE POVPIP DVET DREM DPHY DEYE DEAR PWGTP WGTP \n", + "0 ... 5000.0 1 N N 2 2 2 2 72 0 \n", + "1 ... 0.0 0 N N 2 2 2 2 6 0 \n", + "2 ... 18000.0 3 N N 2 2 2 2 80 0 \n", + "3 ... 0.0 0 N N 1 2 2 2 57 0 \n", + "4 ... 3300.0 1 N N 2 2 2 2 24 0 \n", + "... ... ... ... ... ... ... ... ... ... ... ... \n", + "7629 ... N N 501 N 2 2 2 2 45 41 \n", + "7630 ... N N 501 N 2 2 2 2 115 114 \n", + "7631 ... N N 347 N N N 2 2 69 75 \n", + "7632 ... N N 347 N N N 2 2 64 75 \n", + "7633 ... N N 365 N N N 2 2 107 145 \n", + "\n", + "[7634 rows x 24 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(df)" + ] + }, + { + "cell_type": "markdown", + "id": "e2b2974b", + "metadata": {}, + "source": [ + "As you can see, the data uses numeric codes for various attributes. We'd like to\n", + "create histogram plot of race category subpopulations, encoded as the `RAC1P`\n", + "column in the data. To make the results easier interpret, lets first convert\n", + "those numeric codes to textual descriptions." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cf6713ae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PUMAAGEPSEXMSPHISPRAC1PNOCNPFHOUSING_TYPEOWN_RENT...PINCPPINCP_DECILEPOVPIPDVETDREMDPHYDEYEDEARPWGTPWGTP
025-0050318160WhiteNN30...5000.01NN2222720
125-0070321260WhiteNN30...0.00NN222260
225-0050322260AsianNN30...18000.03NN2222800
325-0130058160WhiteNN20...0.00NN1222570
425-0070318260WhiteNN30...3300.01NN2222240
..................................................................
762925-0130081N0White2311...NN501N22224541
763025-00703141N0White1311...NN501N2222115114
763125-0050331N0Asian2411...NN347NNN226975
763225-0050312N0Asian2411...NN347NNN226475
763325-0280001N0White1311...NN365NNN22107145
\n", + "

7634 rows × 24 columns

\n", + "
" + ], + "text/plain": [ + " PUMA AGEP SEX MSP HISP RAC1P NOC NPF HOUSING_TYPE OWN_RENT \\\n", + "0 25-00503 18 1 6 0 White N N 3 0 \n", + "1 25-00703 21 2 6 0 White N N 3 0 \n", + "2 25-00503 22 2 6 0 Asian N N 3 0 \n", + "3 25-01300 58 1 6 0 White N N 2 0 \n", + "4 25-00703 18 2 6 0 White N N 3 0 \n", + "... ... ... ... .. ... ... .. .. ... ... \n", + "7629 25-01300 8 1 N 0 White 2 3 1 1 \n", + "7630 25-00703 14 1 N 0 White 1 3 1 1 \n", + "7631 25-00503 3 1 N 0 Asian 2 4 1 1 \n", + "7632 25-00503 1 2 N 0 Asian 2 4 1 1 \n", + "7633 25-02800 0 1 N 0 White 1 3 1 1 \n", + "\n", + " ... PINCP PINCP_DECILE POVPIP DVET DREM DPHY DEYE DEAR PWGTP WGTP \n", + "0 ... 5000.0 1 N N 2 2 2 2 72 0 \n", + "1 ... 0.0 0 N N 2 2 2 2 6 0 \n", + "2 ... 18000.0 3 N N 2 2 2 2 80 0 \n", + "3 ... 0.0 0 N N 1 2 2 2 57 0 \n", + "4 ... 3300.0 1 N N 2 2 2 2 24 0 \n", + "... ... ... ... ... ... ... ... ... ... ... ... \n", + "7629 ... N N 501 N 2 2 2 2 45 41 \n", + "7630 ... N N 501 N 2 2 2 2 115 114 \n", + "7631 ... N N 347 N N N 2 2 69 75 \n", + "7632 ... N N 347 N N N 2 2 64 75 \n", + "7633 ... N N 365 N N N 2 2 107 145 \n", + "\n", + "[7634 rows x 24 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "dict_race = {\n", + " 1: \"White\",\n", + " 2: \"Black or African American\",\n", + " 3: \"American Indian\",\n", + " 4: \"Alaska Native\",\n", + " 5: \"American Indian and Alaska Native tribes specified; or American Indian or Alaska Native, not specified and no other races\",\n", + " 6: \"Asian\",\n", + " 7: \"Native Hawaiian and Other Pacific Islander alone\",\n", + " 8: \"Some Other Race\",\n", + " 9: \"Two or More Races\"\n", + " }\n", + "df = df.replace({'RAC1P': dict_race})\n", + "display(df)" + ] + }, + { + "cell_type": "markdown", + "id": "d9125bd2", + "metadata": {}, + "source": [ + "As you can see, the values in the `RAC1P` column of the data have been replaced\n", + "with textual descriptions.\n", + "\n", + "## Step 3: Creating the Histogram\n", + "\n", + "Next, let's focus our attention on a smaller region of the dataset. The `PUMA`\n", + "column in the data is an identifier for the geographical area where each person\n", + "is located, so let's focus on just that geographical area, group the\n", + "results by the race attribute, and then count the total number of people in each\n", + "group." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "96ba8978", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "RAC1P\n", + "American Indian 1\n", + "Some Other Race 12\n", + "Black or African American 32\n", + "Two or More Races 52\n", + "Asian 261\n", + "White 1150\n", + "dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "results = df[df['PUMA'] == '25-00503'].groupby('RAC1P').size().sort_values()\n", + "display(results)" + ] + }, + { + "cell_type": "markdown", + "id": "bf988f8c", + "metadata": {}, + "source": [ + "## Step 4: Observing Issues with Bias\n", + "\n", + "To make these query results differentially private we would add noise to each\n", + "subpopulation count with scale inversely proportional to the desired epsilon. To\n", + "characterize the effect this noise will have on the raw counts, let's compute\n", + "the 95% Confidence Interval (CI) for the result. The formula for this is\n", + "$-\\ln(0.05)/\\epsilon$. Let's calculate the 95% CI for $\\epsilon=1$." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "fb968390", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.995732273553991\n" + ] + } + ], + "source": [ + "print(-np.log(0.05)/1)" + ] + }, + { + "cell_type": "markdown", + "id": "9e049ee5", + "metadata": {}, + "source": [ + "Let's plot a histogram with error bars to visualize the distribution generated\n", + "by the differentially private result. In this plot, the histogram bar represents\n", + "the distribution's mean, and the error bars represent the distribution's 95% CI.\n", + "\n", + "Note that this plot will be in *log scale*. This means instead of the axis\n", + "stepping between a linear sequence of values like 10, 20, 30, 40, etc., it will\n", + "instead step between exponentially increasing values like 1, 10, 100, 1000, etc.\n", + "In particular, notice that the error bars all represent the same range of\n", + "values, but the visual presentation of the error bar will look longer for low\n", + "y-axis values and shorter for high y-axis values.\n", + "\n", + "Here is the plot for $\\epsilon = 1$." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7861057b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "epsilon = 1\n", + "err_95 = -np.log(0.05)/epsilon\n", + "\n", + "plt.figure(figsize=(5,6))\n", + "results.plot(\n", + " kind='bar', \n", + " yerr=err_95, \n", + " error_kw=dict(ecolor='red', lw=5, capsize=10, capthick=3))\n", + "plt.xticks(rotation=70)\n", + "plt.yscale('log')\n", + "plt.xlabel('')\n", + "plt.ylabel('Count (log scale)')\n", + "plt.ylim((.5, 1300))\n", + "pass" + ] + }, + { + "cell_type": "markdown", + "id": "38bd4f67", + "metadata": {}, + "source": [ + "As you can see, for minority populations like `American Indian`, the scale of\n", + "the noise (visualized by the 95% CI error bar) could result in a significant\n", + "over-counting or under-counting of that subpopulation with respect to the raw\n", + "counts. By contrast, for majority subpopulations like `White`, the scale of the\n", + "noise is negligible with respect to the raw count.\n", + "\n", + "Let's generate a new plot for $\\epsilon = 10$." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "8def6a41", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "epsilon = 10\n", + "err_95 = -np.log(0.05)/epsilon\n", + "\n", + "plt.figure(figsize=(5,6))\n", + "results.plot(\n", + " kind='bar', \n", + " yerr=err_95, \n", + " error_kw=dict(ecolor='red', lw=5, capsize=10, capthick=3))\n", + "plt.xticks(rotation=70)\n", + "plt.yscale('log')\n", + "plt.xlabel('')\n", + "plt.ylabel('Count (log scale)')\n", + "plt.ylim((.5, 1300))\n", + "pass" + ] + }, + { + "cell_type": "markdown", + "id": "bccada5b", + "metadata": {}, + "source": [ + "As you can see, for higher epsilon values like $\\epsilon = 10$ the magnitude of\n", + "the bias effect is reduced, but still present." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/MachineLearningBias.ipynb b/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/MachineLearningBias.ipynb new file mode 100644 index 0000000..fa26d15 --- /dev/null +++ b/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/MachineLearningBias.ipynb @@ -0,0 +1,686 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1be92077", + "metadata": {}, + "source": [ + "# Machine Learning: Differential Privacy Magnifies Bias\n", + "\n", + "This Python notebook shows how differential privacy can magnify bias\n", + "in machine learning tasks like regression. The notebook covers reading\n", + "in data from a file, preparing the data for training, training the\n", + "classifier using IBM's Diffprivlib library, and observing issues with\n", + "bias.\n", + "\n", + "As a running example, let's train a classifier for the Diverse Communities Data\n", + "Excerpts dataset curated by NIST and drawn from the US Census Bureau's American\n", + "Communities Survey (ACS).\n", + "\n", + "## Step 1: Loading the Data\n", + "\n", + "Before we start writing code, let's import a few third-party Python packages. We\n", + "will use `pandas` for creating and manipulating data frames, `numpy` for basic\n", + "operations over vectorized datatypes, `diffprivlib` for training a classifier\n", + "with differential privacy, and `matplotlib` for visualizing results." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "93c5dd1d", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import diffprivlib.models as dp\n", + "import warnings\n", + "\n", + "warnings.filterwarnings('ignore')" + ] + }, + { + "cell_type": "markdown", + "id": "c8699fcd", + "metadata": {}, + "source": [ + "Our first step is to load the data. To do this we read the CSV file into a\n", + "dataframe object using `pandas`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4ebeaf72", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv('https://media.githubusercontent.com/media/usnistgov/SDNist/main/nist%20diverse%20communities%20data%20excerpts/massachusetts/ma2019.csv')" + ] + }, + { + "cell_type": "markdown", + "id": "3781c29e", + "metadata": {}, + "source": [ + "Let's display the data frame to get a sense for what the data looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f9b987ad", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PUMAAGEPSEXMSPHISPRAC1PNOCNPFHOUSING_TYPEOWN_RENT...PINCPPINCP_DECILEPOVPIPDVETDREMDPHYDEYEDEARPWGTPWGTP
025-00503181601NN30...5000.01NN2222720
125-00703212601NN30...0.00NN222260
225-00503222606NN30...18000.03NN2222800
325-01300581601NN20...0.00NN1222570
425-00703182601NN30...3300.01NN2222240
..................................................................
762925-0130081N012311...NN501N22224541
763025-00703141N011311...NN501N2222115114
763125-0050331N062411...NN347NNN226975
763225-0050312N062411...NN347NNN226475
763325-0280001N011311...NN365NNN22107145
\n", + "

7634 rows × 24 columns

\n", + "
" + ], + "text/plain": [ + " PUMA AGEP SEX MSP HISP RAC1P NOC NPF HOUSING_TYPE OWN_RENT \\\n", + "0 25-00503 18 1 6 0 1 N N 3 0 \n", + "1 25-00703 21 2 6 0 1 N N 3 0 \n", + "2 25-00503 22 2 6 0 6 N N 3 0 \n", + "3 25-01300 58 1 6 0 1 N N 2 0 \n", + "4 25-00703 18 2 6 0 1 N N 3 0 \n", + "... ... ... ... .. ... ... .. .. ... ... \n", + "7629 25-01300 8 1 N 0 1 2 3 1 1 \n", + "7630 25-00703 14 1 N 0 1 1 3 1 1 \n", + "7631 25-00503 3 1 N 0 6 2 4 1 1 \n", + "7632 25-00503 1 2 N 0 6 2 4 1 1 \n", + "7633 25-02800 0 1 N 0 1 1 3 1 1 \n", + "\n", + " ... PINCP PINCP_DECILE POVPIP DVET DREM DPHY DEYE DEAR PWGTP WGTP \n", + "0 ... 5000.0 1 N N 2 2 2 2 72 0 \n", + "1 ... 0.0 0 N N 2 2 2 2 6 0 \n", + "2 ... 18000.0 3 N N 2 2 2 2 80 0 \n", + "3 ... 0.0 0 N N 1 2 2 2 57 0 \n", + "4 ... 3300.0 1 N N 2 2 2 2 24 0 \n", + "... ... ... ... ... ... ... ... ... ... ... ... \n", + "7629 ... N N 501 N 2 2 2 2 45 41 \n", + "7630 ... N N 501 N 2 2 2 2 115 114 \n", + "7631 ... N N 347 N N N 2 2 69 75 \n", + "7632 ... N N 347 N N N 2 2 64 75 \n", + "7633 ... N N 365 N N N 2 2 107 145 \n", + "\n", + "[7634 rows x 24 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(df)" + ] + }, + { + "cell_type": "markdown", + "id": "5ae704ab", + "metadata": {}, + "source": [ + "## Step 2: Preprocess the Data for Training\n", + "\n", + "To train a classifier, we need to separate the dataset into *features* and a\n", + "*label*. A well trained model will be able to predict the *label* for an entry\n", + "in the dataset based on the *features*. We call the feature dataset `X` and the\n", + "label dataset `y` following standard convention.\n", + "\n", + "We have decided to use `'AGEP'` (age), `'SEX'` (sex), `'RAC1P'` (race) and\n", + "`'HOUSING_TYPE'` (single housing unit or group quarters such as dorms, barracks,\n", + "or nursing homes) as our features, and `'OWN_RENT'` (own or rent housing) as the\n", + "label for the model to predict." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "20257449", + "metadata": {}, + "outputs": [], + "source": [ + "X_train = df[['AGEP', 'SEX', 'RAC1P', 'HOUSING_TYPE']].to_numpy()\n", + "y_train = df['OWN_RENT'].to_numpy()" + ] + }, + { + "cell_type": "markdown", + "id": "636d9240", + "metadata": {}, + "source": [ + "To observe bias, we are interested in comparing the accuracy of the\n", + "trained classifier for minority groups to its accuracy for the\n", + "majority group. For this purpose, we will consider the White\n", + "race—encoded as 1—as a majority group, and all other races—Black or\n", + "African American, American Indian, Alaska Native, Asian, Native\n", + "Hawaiian and Other Pacific Islander, some other race, and two or more\n", + "major race groups, encoded as 2–9 respectively—as minority groups." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a6a3d4b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "There are 6658 records in the majority race category.\n", + "There are 976 records in the minority race categories.\n" + ] + } + ], + "source": [ + "majority_races = df['RAC1P'] == 1\n", + "minority_races = df['RAC1P'].isin([2,3,4,5,6,7,8,9])\n", + "print(f'There are {majority_races.sum()} records in the majority race category.')\n", + "print(f'There are {minority_races.sum()} records in the minority race categories.')" + ] + }, + { + "cell_type": "markdown", + "id": "1d73e8f6", + "metadata": {}, + "source": [ + "## Step 3: Train a classifier\n", + "\n", + "To train a classifier we use the Gaussian Naive Bayes (GaussianNB) with\n", + "differential privacy implementation from IBM's `diffprivlib` library. We pick\n", + "the privacy parameter $\\epsilon = 1$, fit the model, and then print out the\n", + "mean accuracy of the model." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f5a9dd80", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.7991878438564317\n" + ] + } + ], + "source": [ + "dp_clf = dp.GaussianNB(epsilon=1.0)\n", + "dp_clf.fit(X_train, y_train)\n", + "print(dp_clf.score(X_train, y_train))" + ] + }, + { + "cell_type": "markdown", + "id": "716f240c", + "metadata": {}, + "source": [ + "## Step 4: Observe Accuracy for Minority Majority Groups\n", + "\n", + "Our primary interest is to compare the accuracy of the model when predicting\n", + "labels for majority groups vs minority groups." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "36d9a88e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The observed accuracy for records with minority races is 0.820967257434665.\n", + "The observed accuracy for records with minority races is 0.6506147540983607.\n" + ] + } + ], + "source": [ + "majority_accuracy = dp_clf.score(X_train[majority_races], y_train[majority_races])\n", + "minority_accuracy = dp_clf.score(X_train[minority_races], y_train[minority_races])\n", + "print(f'The observed accuracy for records with minority races is {majority_accuracy}.')\n", + "print(f'The observed accuracy for records with minority races is {minority_accuracy}.')" + ] + }, + { + "cell_type": "markdown", + "id": "ea737831", + "metadata": {}, + "source": [ + "As you can see, the accuracy is lower for minority groups than for the\n", + "majority group. This effect is due to the fact that the dataset\n", + "contains less data for the minority groups than it does for the\n", + "majority group, and it can be seen even when training without\n", + "differential privacy. However, differential privacy *magnifies* the\n", + "bias we see in the trained classifier, because the noise affects\n", + "smaller groups more than it does larger ones.\n", + "\n", + "## Step 5: Observe Accuracy Difference when Varying Epsilon\n", + "\n", + "To see the impact of differential privacy on the classifier's bias,\n", + "we'll vary the value of $\\epsilon$. We plot the accuracy for majority\n", + "groups vs minority groups on the y-axis, with the $\\epsilon$ used to\n", + "train the model on the x-axis.\n", + "\n", + "(Creating the raw data for the plot takes more than a few seconds to generate.)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e13ceacf", + "metadata": {}, + "outputs": [], + "source": [ + "def experiment(epsilon):\n", + " brs = []\n", + " srs = []\n", + " for _ in range(100):\n", + " dp_clf = dp.GaussianNB(epsilon=epsilon)\n", + " dp_clf.fit(X_train, y_train)\n", + " brs.append(dp_clf.score(X_train[majority_races], y_train[majority_races]))\n", + " srs.append(dp_clf.score(X_train[minority_races], y_train[minority_races]))\n", + " return np.mean(brs), np.std(brs), np.mean(srs), np.std(srs)\n", + "\n", + "xs = np.linspace(0.01, 0.2, 30)\n", + "results = [experiment(epsilon) for epsilon in xs]" + ] + }, + { + "cell_type": "markdown", + "id": "2614e34e", + "metadata": {}, + "source": [ + "Let's plot the results." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9008c84e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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gwYNAIiPj6d379507NiRqVOnsnHjRgC+//57HnjgAQACAgKoWbMm3333HatWraJbt27ExMTw3XffsWvXLlq0aMGuXbsYO3YsX3/9daGF5jzhSXfO70TkZmCOKaywz9kSgURjzK/O49nAeGPModwVROQ9YEER9qmUKksenJl7U35lmc8VFBR05r4nacrTssyhoaH079+fefPmMXPmTFatWpXner1792bBggX89ttvXHLJJdx6663ExMQwcuRIPvvsM6Kjo5k8efKZSV/yYoxhxIgRPPfcc+c9t27dOhYuXMjbb7/NzJkzS1yWwpM2/tHALCBDRE6KyCkRKXRKHmPMQWCviLRxFvUDNomI+4XhG4H4ogatlPIP+ZVl9kTv3r2ZMWMGLpeLpKQkfvrpJ7p3717gNnmVZR41ahTjxo2jW7duREZGFrh98+bNGT9+PP/+978BW3K5YcOGZGVlnSkRDdCvXz/eeustAFwuF8nJyfTr14/Zs2dz+PBhwE4duXv3bo4cOUJOTg4333wzzzzzDKtXry7ye3GuQhO/MaaGMaaKMaaaMSbceezpd42x2HIP67FNO/8HvCAiG5xlfYE/FTd4pVTlll9ZZk/ceOONdOrUiejoaK644gpeeOGFM00w+enUqRMBAQFER0efuYjatWtXwsPDParHDzBmzBh++uknEhIS+Ne//sXFF19Mr169aNv29ynLX331VX744Qc6duxI165d2bRpE+3ateOZZ55hwIABdOrUif79+3PgwAH27dtHnz59iImJYdiwYXl+IygqT8oyX5bXcmPMTyV+dQ9pWWalyk5FLsvsDfv376dPnz5s2bKFKlU8aSTxjdIuy/yY2/1goDu2h84VJQlSKaXKuw8//JAnn3ySl19+uVwn/aLypEjbde6PReQC4BVvBaSUUuXF8OHDGT688hUjLs4hLBHQ74FKKVVBFXrGLyKvAbkXAqpgL9KW/LKyUqrcMsacmbtWlX9F62nvWRu/+1XVbGC6MaZ8F6JQShVbcHAwR48epXbt2pr8KwBjDEePHiU4ONjjbTxJ/LOBdGOMC0BEAkQk1BiTWsw4lVLlWJMmTUhMTCQpKcnXoSgPBQcH06RJE4/X92jkLnAlkDvzVgjwDdCzyNEppcq9wMBAj8oZqIrLk4u7we7TLTr3Q70XklJKKW/yJPGniEiX3Aci0hVI815I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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "maj_avgs = [x[0]*100 for x in results]\n", + "min_avgs = [x[2]*100 for x in results]\n", + "maj_stds = [x[1] for x in results]\n", + "min_stds = [x[3] for x in results]\n", + "plt.plot(xs, maj_avgs, label='Majority Race')\n", + "plt.plot(xs, min_avgs, label='Minority Races')\n", + "\n", + "plt.legend()\n", + "plt.xlabel('Privacy Parameter (ε)')\n", + "plt.ylabel('Accuracy (%)')\n", + "pass" + ] + }, + { + "cell_type": "markdown", + "id": "195807b0", + "metadata": {}, + "source": [ + "As you can see, the disparity between accuracy for the minority and\n", + "majority groups gets worse as $\\epsilon$ gets smaller—because noise\n", + "has more impact on smaller groups, and more noise makes this disparity\n", + "larger. Both results are noisy, due to the noise required by differential\n", + "privacy.\n", + "\n", + "This general effect—where the accuracy for minority groups is lower than for\n", + "majority groups—is actually *always* present, even if we didn't train the model\n", + "with differential privacy. However, what this plot shows is that using\n", + "differential privacy *amplifies* the disadvantage for minority groups. As the\n", + "$\\epsilon$ is increased, the graph shows accuracy improving on average for\n", + "majority groups, but very little improvement on average for minority groups." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/lattes.csv b/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/lattes.csv new file mode 100644 index 0000000..00fd2ca --- /dev/null +++ b/tools/disassociability/NIST-SP-800-226-SupplementalMaterial/lattes.csv @@ -0,0 +1,1000 @@ +1,Coffee,$2.04 +2,Coffee,$1.55 +3,Pumpkin Spice Latte,$5.94 +4,Pumpkin Spice Latte,$3.67 +5,Latte,$4.15 +6,Latte,$3.47 +7,Latte,$1.37 +8,Coffee,$5.50 +9,Latte,$4.54 +10,Latte,$1.32 +11,Pumpkin Spice Latte,$5.89 +12,Latte,$4.42 +13,Latte,$1.08 +14,Latte,$2.74 +15,Latte,$1.53 +16,Coffee,$3.07 +17,Latte,$2.31 +18,Pumpkin Spice Latte,$1.24 +19,Pumpkin Spice Latte,$2.33 +20,Coffee,$5.24 +21,Latte,$3.08 +22,Latte,$4.73 +23,Pumpkin Spice Latte,$3.53 +24,Pumpkin Spice Latte,$4.46 +25,Coffee,$4.86 +26,Coffee,$1.72 +27,Latte,$2.68 +28,Coffee,$2.02 +29,Pumpkin Spice Latte,$5.85 +30,Latte,$3.72 +31,Coffee,$4.63 +32,Latte,$2.56 +33,Latte,$4.57 +34,Coffee,$3.56 +35,Latte,$1.09 +36,Coffee,$3.58 +37,Coffee,$3.48 +38,Pumpkin Spice Latte,$1.69 +39,Latte,$4.56 +40,Latte,$1.03 +41,Pumpkin Spice Latte,$4.46 +42,Pumpkin Spice Latte,$3.58 +43,Pumpkin Spice Latte,$5.74 +44,Coffee,$5.78 +45,Pumpkin Spice Latte,$2.56 +46,Latte,$5.39 +47,Latte,$1.79 +48,Pumpkin Spice Latte,$3.74 +49,Pumpkin Spice Latte,$3.15 +50,Pumpkin Spice Latte,$5.36 +51,Latte,$3.53 +52,Pumpkin Spice Latte,$1.76 +53,Coffee,$3.49 +54,Pumpkin Spice Latte,$4.83 +55,Coffee,$5.25 +56,Pumpkin Spice Latte,$2.33 +57,Pumpkin Spice Latte,$4.77 +58,Latte,$2.85 +59,Coffee,$4.88 +60,Latte,$5.87 +61,Pumpkin Spice Latte,$2.84 +62,Coffee,$4.30 +63,Coffee,$4.97 +64,Latte,$4.07 +65,Pumpkin Spice Latte,$1.69 +66,Latte,$5.16 +67,Pumpkin Spice Latte,$4.36 +68,Latte,$5.39 +69,Latte,$4.34 +70,Coffee,$1.76 +71,Latte,$2.90 +72,Latte,$1.65 +73,Pumpkin Spice Latte,$3.51 +74,Pumpkin Spice Latte,$2.38 +75,Coffee,$2.66 +76,Pumpkin Spice Latte,$3.59 +77,Coffee,$5.64 +78,Pumpkin Spice Latte,$4.99 +79,Pumpkin Spice Latte,$3.92 +80,Pumpkin Spice Latte,$3.90 +81,Coffee,$1.38 +82,Coffee,$2.48 +83,Pumpkin Spice Latte,$5.93 +84,Coffee,$5.95 +85,Latte,$1.58 +86,Latte,$1.33 +87,Latte,$2.63 +88,Pumpkin Spice Latte,$5.81 +89,Coffee,$5.98 +90,Coffee,$1.79 +91,Coffee,$3.19 +92,Latte,$2.72 +93,Coffee,$5.29 +94,Coffee,$3.23 +95,Pumpkin Spice Latte,$3.63 +96,Coffee,$2.86 +97,Coffee,$4.14 +98,Pumpkin Spice Latte,$2.29 +99,Pumpkin Spice Latte,$4.37 +100,Latte,$4.64 +101,Pumpkin Spice Latte,$3.78 +102,Pumpkin Spice Latte,$1.89 +103,Coffee,$4.37 +104,Pumpkin Spice Latte,$4.80 +105,Coffee,$2.00 +106,Latte,$3.30 +107,Coffee,$5.16 +108,Pumpkin Spice Latte,$3.34 +109,Latte,$5.07 +110,Pumpkin Spice Latte,$5.04 +111,Coffee,$1.24 +112,Coffee,$5.46 +113,Coffee,$1.30 +114,Coffee,$3.81 +115,Coffee,$5.08 +116,Pumpkin Spice Latte,$2.55 +117,Coffee,$4.14 +118,Pumpkin Spice Latte,$3.85 +119,Pumpkin Spice Latte,$5.73 +120,Coffee,$4.76 +121,Pumpkin Spice Latte,$5.29 +122,Pumpkin Spice Latte,$5.55 +123,Pumpkin Spice Latte,$3.75 +124,Coffee,$4.93 +125,Latte,$2.78 +126,Latte,$4.39 +127,Coffee,$3.83 +128,Latte,$1.84 +129,Coffee,$5.85 +130,Coffee,$2.09 +131,Coffee,$4.52 +132,Pumpkin Spice Latte,$4.09 +133,Latte,$4.85 +134,Coffee,$3.49 +135,Pumpkin Spice Latte,$4.33 +136,Latte,$5.62 +137,Latte,$1.07 +138,Coffee,$2.30 +139,Coffee,$2.76 +140,Pumpkin Spice Latte,$5.68 +141,Latte,$5.86 +142,Latte,$5.72 +143,Latte,$4.95 +144,Pumpkin Spice Latte,$5.03 +145,Latte,$1.33 +146,Pumpkin Spice Latte,$5.63 +147,Pumpkin Spice Latte,$3.87 +148,Pumpkin Spice Latte,$2.13 +149,Latte,$2.78 +150,Pumpkin Spice Latte,$3.48 +151,Coffee,$5.88 +152,Pumpkin Spice Latte,$5.10 +153,Pumpkin Spice Latte,$4.27 +154,Coffee,$5.47 +155,Pumpkin Spice Latte,$3.30 +156,Coffee,$5.45 +157,Coffee,$5.76 +158,Latte,$1.56 +159,Pumpkin Spice Latte,$5.30 +160,Coffee,$4.88 +161,Latte,$1.37 +162,Pumpkin Spice Latte,$2.78 +163,Latte,$1.20 +164,Coffee,$5.61 +165,Coffee,$4.05 +166,Latte,$1.36 +167,Pumpkin Spice Latte,$3.32 +168,Coffee,$1.26 +169,Pumpkin Spice Latte,$1.36 +170,Coffee,$3.35 +171,Pumpkin Spice Latte,$5.16 +172,Coffee,$1.63 +173,Coffee,$1.94 +174,Pumpkin Spice Latte,$3.15 +175,Coffee,$5.77 +176,Latte,$4.60 +177,Latte,$2.71 +178,Pumpkin Spice Latte,$2.85 +179,Latte,$5.66 +180,Latte,$3.69 +181,Pumpkin Spice Latte,$4.19 +182,Coffee,$5.43 +183,Latte,$2.09 +184,Coffee,$1.08 +185,Coffee,$5.71 +186,Pumpkin Spice Latte,$5.39 +187,Latte,$2.83 +188,Latte,$2.58 +189,Latte,$5.28 +190,Coffee,$2.58 +191,Pumpkin Spice Latte,$1.17 +192,Coffee,$5.19 +193,Pumpkin Spice Latte,$1.06 +194,Latte,$2.52 +195,Latte,$5.05 +196,Latte,$4.55 +197,Coffee,$5.05 +198,Coffee,$4.51 +199,Pumpkin Spice Latte,$3.64 +200,Latte,$3.75 +201,Pumpkin Spice Latte,$5.55 +202,Pumpkin Spice Latte,$4.92 +203,Coffee,$3.40 +204,Pumpkin Spice Latte,$5.16 +205,Coffee,$2.53 +206,Pumpkin Spice Latte,$3.29 +207,Coffee,$1.96 +208,Coffee,$1.52 +209,Latte,$4.39 +210,Latte,$5.83 +211,Pumpkin Spice Latte,$1.25 +212,Coffee,$5.75 +213,Coffee,$4.35 +214,Coffee,$3.23 +215,Pumpkin Spice Latte,$1.65 +216,Pumpkin Spice Latte,$2.31 +217,Latte,$4.80 +218,Coffee,$3.69 +219,Coffee,$2.44 +220,Latte,$2.10 +221,Pumpkin Spice Latte,$1.32 +222,Latte,$1.71 +223,Coffee,$4.66 +224,Latte,$1.42 +225,Latte,$4.34 +226,Pumpkin Spice Latte,$3.12 +227,Latte,$2.93 +228,Coffee,$1.29 +229,Coffee,$2.65 +230,Latte,$3.99 +231,Pumpkin Spice Latte,$2.65 +232,Pumpkin Spice Latte,$4.26 +233,Latte,$3.35 +234,Coffee,$4.09 +235,Latte,$4.93 +236,Latte,$1.20 +237,Latte,$1.22 +238,Latte,$3.76 +239,Pumpkin Spice Latte,$2.72 +240,Latte,$2.52 +241,Coffee,$3.02 +242,Latte,$5.74 +243,Pumpkin Spice Latte,$2.05 +244,Latte,$3.83 +245,Pumpkin Spice Latte,$2.44 +246,Latte,$3.23 +247,Latte,$2.79 +248,Latte,$3.93 +249,Latte,$5.63 +250,Coffee,$2.66 +251,Coffee,$1.84 +252,Coffee,$4.81 +253,Coffee,$5.95 +254,Coffee,$1.02 +255,Latte,$4.11 +256,Pumpkin Spice Latte,$2.31 +257,Latte,$1.30 +258,Pumpkin Spice Latte,$5.76 +259,Latte,$3.89 +260,Pumpkin Spice Latte,$2.64 +261,Coffee,$1.06 +262,Coffee,$4.27 +263,Pumpkin Spice Latte,$4.94 +264,Pumpkin Spice Latte,$2.75 +265,Latte,$4.67 +266,Coffee,$1.86 +267,Coffee,$5.18 +268,Latte,$5.91 +269,Latte,$2.47 +270,Latte,$5.78 +271,Latte,$4.18 +272,Pumpkin Spice Latte,$1.52 +273,Pumpkin Spice Latte,$4.10 +274,Coffee,$5.18 +275,Pumpkin Spice Latte,$5.70 +276,Coffee,$1.88 +277,Pumpkin Spice Latte,$3.64 +278,Pumpkin Spice Latte,$4.53 +279,Coffee,$2.48 +280,Pumpkin Spice Latte,$5.13 +281,Pumpkin Spice Latte,$5.52 +282,Pumpkin Spice Latte,$1.71 +283,Coffee,$5.44 +284,Coffee,$4.65 +285,Coffee,$3.07 +286,Latte,$5.83 +287,Pumpkin Spice Latte,$2.90 +288,Latte,$4.23 +289,Pumpkin Spice Latte,$2.62 +290,Latte,$5.70 +291,Coffee,$2.78 +292,Latte,$3.07 +293,Coffee,$2.11 +294,Pumpkin Spice Latte,$1.30 +295,Latte,$2.40 +296,Latte,$5.16 +297,Latte,$4.14 +298,Pumpkin Spice Latte,$3.19 +299,Pumpkin Spice Latte,$5.32 +300,Coffee,$2.21 +301,Latte,$3.29 +302,Pumpkin Spice Latte,$5.72 +303,Pumpkin Spice Latte,$3.96 +304,Coffee,$2.39 +305,Latte,$1.45 +306,Coffee,$3.64 +307,Coffee,$1.72 +308,Latte,$3.47 +309,Pumpkin Spice Latte,$1.75 +310,Coffee,$2.33 +311,Coffee,$3.81 +312,Latte,$2.72 +313,Coffee,$5.94 +314,Pumpkin Spice Latte,$2.39 +315,Latte,$4.31 +316,Coffee,$1.60 +317,Pumpkin Spice Latte,$5.14 +318,Pumpkin Spice Latte,$1.83 +319,Latte,$5.02 +320,Coffee,$2.65 +321,Pumpkin Spice Latte,$2.15 +322,Coffee,$2.23 +323,Pumpkin Spice Latte,$2.79 +324,Coffee,$5.82 +325,Latte,$5.33 +326,Latte,$2.24 +327,Pumpkin Spice Latte,$1.64 +328,Latte,$2.63 +329,Pumpkin Spice Latte,$5.00 +330,Coffee,$5.31 +331,Coffee,$5.76 +332,Pumpkin Spice Latte,$4.06 +333,Pumpkin Spice Latte,$5.65 +334,Coffee,$4.98 +335,Coffee,$3.91 +336,Latte,$2.28 +337,Coffee,$5.05 +338,Latte,$2.60 +339,Coffee,$5.57 +340,Pumpkin Spice Latte,$5.82 +341,Latte,$1.29 +342,Pumpkin Spice Latte,$5.19 +343,Latte,$5.27 +344,Pumpkin Spice Latte,$6.00 +345,Latte,$1.00 +346,Latte,$3.69 +347,Coffee,$2.17 +348,Pumpkin Spice Latte,$4.89 +349,Coffee,$4.52 +350,Coffee,$3.58 +351,Latte,$4.05 +352,Pumpkin Spice Latte,$2.42 +353,Latte,$1.41 +354,Latte,$3.03 +355,Coffee,$4.73 +356,Coffee,$2.89 +357,Pumpkin Spice Latte,$5.22 +358,Pumpkin Spice Latte,$2.51 +359,Coffee,$2.56 +360,Latte,$1.37 +361,Pumpkin Spice Latte,$4.33 +362,Coffee,$5.21 +363,Coffee,$3.04 +364,Latte,$4.66 +365,Latte,$4.58 +366,Coffee,$2.51 +367,Coffee,$1.21 +368,Coffee,$4.61 +369,Latte,$4.78 +370,Coffee,$5.78 +371,Latte,$3.14 +372,Pumpkin Spice Latte,$4.66 +373,Coffee,$1.82 +374,Pumpkin Spice Latte,$2.63 +375,Pumpkin Spice Latte,$2.39 +376,Latte,$1.38 +377,Latte,$2.16 +378,Latte,$1.26 +379,Coffee,$5.59 +380,Coffee,$3.03 +381,Latte,$4.56 +382,Latte,$3.21 +383,Latte,$1.89 +384,Latte,$5.00 +385,Pumpkin Spice Latte,$3.38 +386,Latte,$3.32 +387,Latte,$2.32 +388,Latte,$1.44 +389,Latte,$3.25 +390,Pumpkin Spice Latte,$3.31 +391,Coffee,$4.80 +392,Latte,$3.05 +393,Latte,$3.73 +394,Pumpkin Spice Latte,$4.70 +395,Pumpkin Spice Latte,$3.68 +396,Coffee,$5.78 +397,Pumpkin Spice Latte,$3.50 +398,Latte,$5.77 +399,Coffee,$5.27 +400,Pumpkin Spice Latte,$3.09 +401,Coffee,$4.34 +402,Coffee,$2.55 +403,Coffee,$5.06 +404,Latte,$3.84 +405,Latte,$3.63 +406,Coffee,$2.44 +407,Coffee,$3.06 +408,Pumpkin Spice Latte,$5.62 +409,Coffee,$5.66 +410,Latte,$5.43 +411,Pumpkin Spice Latte,$4.82 +412,Coffee,$2.35 +413,Pumpkin Spice Latte,$2.28 +414,Latte,$3.92 +415,Pumpkin Spice Latte,$3.39 +416,Coffee,$4.32 +417,Coffee,$5.13 +418,Coffee,$4.21 +419,Coffee,$3.40 +420,Latte,$4.90 +421,Pumpkin Spice Latte,$2.70 +422,Coffee,$5.48 +423,Latte,$1.94 +424,Latte,$1.42 +425,Coffee,$3.25 +426,Pumpkin Spice Latte,$5.42 +427,Pumpkin Spice Latte,$5.17 +428,Latte,$4.50 +429,Pumpkin Spice Latte,$5.49 +430,Coffee,$1.88 +431,Pumpkin Spice Latte,$1.80 +432,Pumpkin Spice Latte,$1.14 +433,Latte,$1.44 +434,Pumpkin Spice Latte,$2.91 +435,Latte,$2.74 +436,Latte,$5.83 +437,Coffee,$1.04 +438,Coffee,$4.56 +439,Latte,$5.58 +440,Pumpkin Spice Latte,$5.02 +441,Latte,$2.49 +442,Pumpkin Spice Latte,$1.17 +443,Pumpkin Spice Latte,$1.03 +444,Latte,$1.28 +445,Latte,$4.56 +446,Pumpkin Spice Latte,$3.06 +447,Coffee,$5.46 +448,Pumpkin Spice Latte,$3.50 +449,Coffee,$2.54 +450,Latte,$4.21 +451,Pumpkin Spice Latte,$4.98 +452,Coffee,$5.88 +453,Latte,$1.58 +454,Pumpkin Spice Latte,$4.69 +455,Pumpkin Spice Latte,$4.02 +456,Pumpkin Spice Latte,$4.29 +457,Pumpkin Spice Latte,$4.16 +458,Pumpkin Spice Latte,$5.35 +459,Coffee,$1.38 +460,Pumpkin Spice Latte,$1.43 +461,Coffee,$2.17 +462,Latte,$5.42 +463,Pumpkin Spice Latte,$3.44 +464,Latte,$5.62 +465,Latte,$5.47 +466,Latte,$3.82 +467,Latte,$4.39 +468,Latte,$3.94 +469,Pumpkin Spice Latte,$6.00 +470,Coffee,$1.62 +471,Coffee,$4.81 +472,Latte,$1.50 +473,Coffee,$3.40 +474,Latte,$2.13 +475,Coffee,$1.56 +476,Pumpkin Spice Latte,$2.09 +477,Latte,$4.67 +478,Latte,$2.59 +479,Latte,$1.94 +480,Pumpkin Spice Latte,$4.28 +481,Coffee,$2.72 +482,Coffee,$5.16 +483,Latte,$4.41 +484,Pumpkin Spice Latte,$3.09 +485,Pumpkin Spice Latte,$2.61 +486,Coffee,$3.69 +487,Pumpkin Spice Latte,$3.71 +488,Coffee,$1.06 +489,Pumpkin Spice Latte,$5.10 +490,Coffee,$2.57 +491,Pumpkin Spice Latte,$1.43 +492,Latte,$2.26 +493,Coffee,$3.99 +494,Pumpkin Spice Latte,$5.54 +495,Latte,$1.49 +496,Latte,$1.28 +497,Coffee,$4.57 +498,Coffee,$2.26 +499,Coffee,$5.72 +500,Coffee,$5.66 +501,Coffee,$5.45 +502,Pumpkin Spice Latte,$3.35 +503,Coffee,$4.56 +504,Pumpkin Spice Latte,$5.14 +505,Pumpkin Spice Latte,$1.31 +506,Latte,$2.83 +507,Latte,$3.13 +508,Coffee,$1.78 +509,Coffee,$3.72 +510,Pumpkin Spice Latte,$4.85 +511,Pumpkin Spice Latte,$2.86 +512,Pumpkin Spice Latte,$1.48 +513,Pumpkin Spice Latte,$1.43 +514,Pumpkin Spice Latte,$1.01 +515,Pumpkin Spice Latte,$2.09 +516,Coffee,$5.02 +517,Coffee,$1.97 +518,Coffee,$3.90 +519,Coffee,$1.40 +520,Latte,$2.03 +521,Latte,$4.49 +522,Latte,$1.42 +523,Pumpkin Spice Latte,$4.29 +524,Pumpkin Spice Latte,$3.07 +525,Pumpkin Spice Latte,$4.22 +526,Latte,$4.62 +527,Coffee,$4.29 +528,Pumpkin Spice Latte,$4.34 +529,Coffee,$3.56 +530,Latte,$1.98 +531,Latte,$4.05 +532,Latte,$3.19 +533,Coffee,$5.14 +534,Latte,$2.70 +535,Pumpkin Spice Latte,$2.28 +536,Coffee,$5.12 +537,Latte,$1.55 +538,Coffee,$2.88 +539,Latte,$1.62 +540,Pumpkin Spice Latte,$1.75 +541,Coffee,$4.33 +542,Latte,$5.96 +543,Latte,$4.02 +544,Coffee,$3.51 +545,Pumpkin Spice Latte,$3.59 +546,Pumpkin Spice Latte,$2.54 +547,Pumpkin Spice Latte,$5.63 +548,Coffee,$2.32 +549,Latte,$4.14 +550,Pumpkin Spice Latte,$5.43 +551,Coffee,$4.06 +552,Coffee,$3.90 +553,Pumpkin Spice Latte,$1.16 +554,Pumpkin Spice Latte,$3.39 +555,Pumpkin Spice Latte,$1.23 +556,Pumpkin Spice Latte,$3.66 +557,Latte,$5.40 +558,Coffee,$1.46 +559,Coffee,$2.21 +560,Latte,$4.90 +561,Pumpkin Spice Latte,$2.87 +562,Latte,$5.09 +563,Latte,$4.11 +564,Latte,$5.14 +565,Latte,$2.01 +566,Latte,$2.36 +567,Coffee,$1.54 +568,Latte,$4.77 +569,Latte,$5.11 +570,Coffee,$1.14 +571,Pumpkin Spice Latte,$5.01 +572,Coffee,$3.12 +573,Coffee,$1.21 +574,Pumpkin Spice Latte,$3.68 +575,Coffee,$3.05 +576,Coffee,$4.23 +577,Coffee,$1.62 +578,Pumpkin Spice Latte,$1.36 +579,Latte,$4.65 +580,Latte,$2.22 +581,Pumpkin Spice Latte,$4.66 +582,Latte,$4.00 +583,Latte,$3.13 +584,Coffee,$3.90 +585,Coffee,$3.91 +586,Latte,$3.23 +587,Coffee,$3.18 +588,Coffee,$2.62 +589,Latte,$1.74 +590,Latte,$2.31 +591,Coffee,$1.97 +592,Latte,$5.03 +593,Coffee,$2.04 +594,Pumpkin Spice Latte,$3.33 +595,Coffee,$3.86 +596,Latte,$4.74 +597,Pumpkin Spice Latte,$1.91 +598,Coffee,$4.91 +599,Pumpkin Spice Latte,$2.30 +600,Latte,$1.49 +601,Pumpkin Spice Latte,$3.33 +602,Pumpkin Spice Latte,$2.58 +603,Pumpkin Spice Latte,$4.39 +604,Coffee,$3.93 +605,Pumpkin Spice Latte,$4.51 +606,Pumpkin Spice Latte,$2.76 +607,Pumpkin Spice Latte,$3.03 +608,Coffee,$2.32 +609,Coffee,$2.98 +610,Latte,$5.77 +611,Latte,$3.58 +612,Pumpkin Spice Latte,$1.95 +613,Coffee,$4.86 +614,Pumpkin Spice Latte,$5.70 +615,Coffee,$5.91 +616,Coffee,$4.80 +617,Pumpkin Spice Latte,$1.79 +618,Pumpkin Spice Latte,$5.16 +619,Pumpkin Spice Latte,$2.88 +620,Latte,$2.01 +621,Pumpkin Spice Latte,$5.44 +622,Latte,$5.11 +623,Pumpkin Spice Latte,$3.51 +624,Latte,$5.50 +625,Latte,$1.11 +626,Latte,$4.72 +627,Pumpkin Spice Latte,$2.92 +628,Pumpkin Spice Latte,$5.70 +629,Latte,$5.64 +630,Pumpkin Spice Latte,$1.21 +631,Coffee,$5.62 +632,Pumpkin Spice Latte,$1.65 +633,Coffee,$4.62 +634,Coffee,$5.73 +635,Coffee,$3.90 +636,Pumpkin Spice Latte,$5.68 +637,Pumpkin Spice Latte,$1.30 +638,Pumpkin Spice Latte,$5.95 +639,Latte,$3.09 +640,Pumpkin Spice Latte,$2.75 +641,Latte,$3.86 +642,Pumpkin Spice Latte,$5.58 +643,Pumpkin Spice Latte,$3.34 +644,Latte,$1.81 +645,Latte,$2.42 +646,Latte,$3.63 +647,Coffee,$5.50 +648,Coffee,$4.55 +649,Pumpkin Spice Latte,$5.85 +650,Coffee,$2.02 +651,Pumpkin Spice Latte,$2.52 +652,Latte,$2.58 +653,Pumpkin Spice Latte,$3.38 +654,Latte,$3.27 +655,Pumpkin Spice Latte,$3.00 +656,Coffee,$2.67 +657,Pumpkin Spice Latte,$3.72 +658,Pumpkin Spice Latte,$4.28 +659,Latte,$1.41 +660,Pumpkin Spice Latte,$5.67 +661,Coffee,$1.52 +662,Latte,$4.97 +663,Pumpkin Spice Latte,$4.01 +664,Pumpkin Spice Latte,$5.78 +665,Pumpkin Spice Latte,$1.67 +666,Pumpkin Spice Latte,$2.23 +667,Latte,$1.17 +668,Coffee,$5.86 +669,Pumpkin Spice Latte,$5.56 +670,Coffee,$5.91 +671,Pumpkin Spice Latte,$4.33 +672,Coffee,$5.67 +673,Latte,$1.96 +674,Pumpkin Spice Latte,$2.47 +675,Pumpkin Spice Latte,$1.62 +676,Latte,$3.70 +677,Coffee,$3.76 +678,Coffee,$4.72 +679,Coffee,$4.22 +680,Coffee,$5.16 +681,Coffee,$1.93 +682,Coffee,$5.85 +683,Latte,$4.20 +684,Pumpkin Spice Latte,$1.78 +685,Pumpkin Spice Latte,$5.26 +686,Pumpkin Spice Latte,$5.06 +687,Coffee,$5.96 +688,Latte,$1.10 +689,Coffee,$4.50 +690,Coffee,$1.60 +691,Pumpkin Spice Latte,$3.65 +692,Coffee,$5.30 +693,Pumpkin Spice Latte,$1.02 +694,Coffee,$5.33 +695,Latte,$3.53 +696,Pumpkin Spice Latte,$2.77 +697,Pumpkin Spice Latte,$4.46 +698,Pumpkin Spice Latte,$5.68 +699,Coffee,$2.92 +700,Coffee,$4.91 +701,Pumpkin Spice Latte,$5.68 +702,Latte,$3.11 +703,Pumpkin Spice Latte,$3.21 +704,Pumpkin Spice Latte,$5.71 +705,Coffee,$4.93 +706,Latte,$5.63 +707,Coffee,$5.40 +708,Coffee,$3.76 +709,Latte,$4.65 +710,Coffee,$1.73 +711,Coffee,$3.54 +712,Coffee,$1.79 +713,Latte,$1.17 +714,Coffee,$5.43 +715,Latte,$2.39 +716,Pumpkin Spice Latte,$5.79 +717,Coffee,$3.82 +718,Latte,$4.22 +719,Latte,$5.18 +720,Pumpkin Spice Latte,$4.63 +721,Coffee,$4.80 +722,Latte,$2.96 +723,Coffee,$3.67 +724,Coffee,$5.17 +725,Pumpkin Spice Latte,$4.42 +726,Coffee,$4.67 +727,Pumpkin Spice Latte,$2.90 +728,Pumpkin Spice Latte,$4.20 +729,Latte,$4.77 +730,Pumpkin Spice Latte,$3.72 +731,Coffee,$1.90 +732,Pumpkin Spice Latte,$3.21 +733,Latte,$1.36 +734,Latte,$4.05 +735,Coffee,$1.85 +736,Pumpkin Spice Latte,$2.48 +737,Latte,$5.45 +738,Coffee,$3.31 +739,Pumpkin Spice Latte,$3.70 +740,Latte,$4.12 +741,Pumpkin Spice Latte,$4.96 +742,Coffee,$4.22 +743,Pumpkin Spice Latte,$4.77 +744,Pumpkin Spice Latte,$3.77 +745,Pumpkin Spice Latte,$4.82 +746,Coffee,$5.38 +747,Latte,$3.65 +748,Latte,$1.40 +749,Coffee,$3.41 +750,Pumpkin Spice Latte,$1.28 +751,Coffee,$1.66 +752,Pumpkin Spice Latte,$4.33 +753,Latte,$3.85 +754,Pumpkin Spice Latte,$1.73 +755,Coffee,$5.96 +756,Latte,$4.94 +757,Coffee,$2.42 +758,Latte,$2.35 +759,Pumpkin Spice Latte,$4.07 +760,Pumpkin Spice Latte,$1.51 +761,Pumpkin Spice Latte,$1.34 +762,Latte,$3.76 +763,Latte,$5.13 +764,Coffee,$3.68 +765,Pumpkin Spice Latte,$5.32 +766,Coffee,$2.29 +767,Pumpkin Spice Latte,$3.60 +768,Coffee,$1.89 +769,Coffee,$3.86 +770,Latte,$1.57 +771,Coffee,$4.43 +772,Coffee,$5.41 +773,Coffee,$4.47 +774,Latte,$2.85 +775,Latte,$3.32 +776,Latte,$5.55 +777,Coffee,$5.84 +778,Latte,$4.90 +779,Pumpkin Spice Latte,$4.58 +780,Pumpkin Spice Latte,$3.12 +781,Coffee,$2.99 +782,Coffee,$1.65 +783,Latte,$1.93 +784,Coffee,$2.51 +785,Coffee,$1.45 +786,Pumpkin Spice Latte,$5.38 +787,Coffee,$1.19 +788,Coffee,$3.50 +789,Coffee,$2.80 +790,Latte,$1.64 +791,Pumpkin Spice Latte,$5.04 +792,Latte,$4.92 +793,Coffee,$1.60 +794,Pumpkin Spice Latte,$1.32 +795,Pumpkin Spice Latte,$5.37 +796,Pumpkin Spice Latte,$5.19 +797,Coffee,$5.81 +798,Latte,$4.43 +799,Coffee,$2.02 +800,Pumpkin Spice Latte,$2.21 +801,Latte,$2.41 +802,Pumpkin Spice Latte,$1.86 +803,Latte,$4.43 +804,Latte,$1.32 +805,Pumpkin Spice Latte,$2.37 +806,Latte,$1.49 +807,Pumpkin Spice Latte,$5.46 +808,Latte,$1.92 +809,Latte,$4.34 +810,Pumpkin Spice Latte,$3.52 +811,Pumpkin Spice Latte,$1.70 +812,Coffee,$4.70 +813,Coffee,$2.36 +814,Latte,$5.90 +815,Latte,$1.42 +816,Coffee,$5.25 +817,Coffee,$4.38 +818,Coffee,$1.49 +819,Pumpkin Spice Latte,$4.64 +820,Pumpkin Spice Latte,$3.93 +821,Pumpkin Spice Latte,$1.65 +822,Latte,$4.28 +823,Latte,$1.82 +824,Latte,$3.90 +825,Latte,$2.47 +826,Latte,$3.75 +827,Coffee,$4.10 +828,Pumpkin Spice Latte,$1.87 +829,Pumpkin Spice Latte,$4.45 +830,Pumpkin Spice Latte,$1.35 +831,Coffee,$4.82 +832,Latte,$3.16 +833,Coffee,$2.10 +834,Coffee,$5.57 +835,Latte,$3.57 +836,Coffee,$3.67 +837,Pumpkin Spice Latte,$1.76 +838,Latte,$3.44 +839,Pumpkin Spice Latte,$3.61 +840,Pumpkin Spice Latte,$3.24 +841,Pumpkin Spice Latte,$5.45 +842,Pumpkin Spice Latte,$4.28 +843,Latte,$2.54 +844,Coffee,$2.82 +845,Latte,$2.63 +846,Latte,$5.78 +847,Pumpkin Spice Latte,$1.24 +848,Pumpkin Spice Latte,$1.04 +849,Pumpkin Spice Latte,$3.11 +850,Pumpkin Spice Latte,$5.16 +851,Latte,$5.92 +852,Latte,$4.38 +853,Latte,$2.41 +854,Pumpkin Spice Latte,$2.00 +855,Latte,$2.58 +856,Latte,$3.49 +857,Pumpkin Spice Latte,$1.65 +858,Latte,$4.71 +859,Coffee,$4.37 +860,Latte,$1.23 +861,Latte,$5.43 +862,Latte,$3.58 +863,Latte,$5.99 +864,Latte,$1.79 +865,Coffee,$1.88 +866,Pumpkin Spice Latte,$1.22 +867,Pumpkin Spice Latte,$4.98 +868,Pumpkin Spice Latte,$2.95 +869,Latte,$5.41 +870,Coffee,$2.14 +871,Latte,$5.76 +872,Coffee,$3.74 +873,Pumpkin Spice Latte,$3.41 +874,Latte,$1.47 +875,Pumpkin Spice Latte,$3.53 +876,Coffee,$4.36 +877,Latte,$5.18 +878,Pumpkin Spice Latte,$4.63 +879,Coffee,$5.83 +880,Pumpkin Spice Latte,$4.59 +881,Latte,$4.90 +882,Coffee,$2.43 +883,Coffee,$4.46 +884,Latte,$2.50 +885,Coffee,$3.21 +886,Coffee,$1.97 +887,Pumpkin Spice Latte,$1.01 +888,Latte,$5.90 +889,Coffee,$1.57 +890,Pumpkin Spice Latte,$2.77 +891,Coffee,$1.07 +892,Coffee,$2.23 +893,Latte,$3.85 +894,Latte,$2.98 +895,Pumpkin Spice Latte,$4.22 +896,Pumpkin Spice Latte,$5.14 +897,Coffee,$4.90 +898,Coffee,$3.43 +899,Coffee,$5.06 +900,Coffee,$4.12 +901,Latte,$3.94 +902,Coffee,$4.78 +903,Pumpkin Spice Latte,$3.80 +904,Latte,$5.65 +905,Coffee,$4.68 +906,Latte,$1.03 +907,Pumpkin Spice Latte,$2.74 +908,Coffee,$2.55 +909,Latte,$2.45 +910,Pumpkin Spice Latte,$1.23 +911,Coffee,$3.92 +912,Latte,$2.92 +913,Coffee,$2.98 +914,Latte,$4.38 +915,Pumpkin Spice Latte,$3.42 +916,Latte,$5.78 +917,Coffee,$1.96 +918,Pumpkin Spice Latte,$2.37 +919,Coffee,$4.89 +920,Coffee,$4.19 +921,Coffee,$4.40 +922,Latte,$1.60 +923,Pumpkin Spice Latte,$5.64 +924,Latte,$1.60 +925,Latte,$4.55 +926,Coffee,$2.28 +927,Coffee,$2.54 +928,Pumpkin Spice Latte,$4.38 +929,Pumpkin Spice Latte,$2.28 +930,Latte,$5.39 +931,Latte,$2.70 +932,Latte,$1.92 +933,Pumpkin Spice Latte,$2.73 +934,Pumpkin Spice Latte,$5.61 +935,Latte,$4.76 +936,Coffee,$4.03 +937,Coffee,$5.05 +938,Coffee,$1.71 +939,Coffee,$1.59 +940,Coffee,$5.24 +941,Latte,$4.63 +942,Coffee,$1.13 +943,Latte,$2.78 +944,Latte,$3.98 +945,Pumpkin Spice Latte,$2.12 +946,Coffee,$3.45 +947,Coffee,$2.73 +948,Pumpkin Spice Latte,$2.34 +949,Coffee,$2.51 +950,Pumpkin Spice Latte,$5.57 +951,Pumpkin Spice Latte,$3.94 +952,Coffee,$4.41 +953,Latte,$2.11 +954,Coffee,$3.63 +955,Latte,$1.91 +956,Latte,$5.74 +957,Pumpkin Spice Latte,$1.51 +958,Coffee,$3.37 +959,Coffee,$3.13 +960,Coffee,$5.67 +961,Latte,$4.75 +962,Coffee,$3.94 +963,Coffee,$1.44 +964,Pumpkin Spice Latte,$2.19 +965,Latte,$4.43 +966,Latte,$3.31 +967,Latte,$2.36 +968,Latte,$3.55 +969,Pumpkin Spice Latte,$2.87 +970,Coffee,$4.21 +971,Latte,$3.07 +972,Pumpkin Spice Latte,$4.90 +973,Coffee,$1.91 +974,Latte,$5.29 +975,Pumpkin Spice Latte,$3.77 +976,Pumpkin Spice Latte,$1.15 +977,Latte,$5.56 +978,Latte,$4.45 +979,Pumpkin Spice Latte,$4.67 +980,Latte,$4.15 +981,Coffee,$5.24 +982,Coffee,$4.64 +983,Coffee,$5.16 +984,Coffee,$4.43 +985,Coffee,$4.90 +986,Pumpkin Spice Latte,$4.66 +987,Latte,$4.77 +988,Latte,$4.06 +989,Pumpkin Spice Latte,$2.07 +990,Pumpkin Spice Latte,$1.27 +991,Coffee,$3.43 +992,Pumpkin Spice Latte,$5.74 +993,Pumpkin Spice Latte,$4.13 +994,Coffee,$3.62 +995,Latte,$4.05 +996,Latte,$2.92 +997,Coffee,$4.61 +998,Pumpkin Spice Latte,$4.28 +999,Latte,$2.24 +1000,Latte,$4.84 diff --git a/tools/disassociability/PPMLHuskies/README.md b/tools/disassociability/PPMLHuskies/README.md new file mode 100644 index 0000000..0bee4ad --- /dev/null +++ b/tools/disassociability/PPMLHuskies/README.md @@ -0,0 +1,19 @@ +# PPMLHuskies + +**Primary Focus Area (select one):** De-identification + +**De-identification Keywords (select any relevant):** Differential Privacy, Homomorphic Encryption, Machine Learning, Federated Learning, + +**Brief Description:** +We propose a cross-silo federated architecture in which a payment network system (PNS) denoted by $\mathcal{S}$ has labeled data to train a model $\mathcal{M}$ for detection of anomalous payments. The other entities in the federation are banks $\mathcal{B}_1, \mathcal{B}_2, \ldots, \mathcal{B}_n$ that collaborate with $\mathcal{S}$ to create feature values to improve the utility of $\mathcal{M}$. To jointly extract feature values in a privacy-preserving manner, $\mathcal{S}$ and the banks engage in cryptographic protocols to perform computations over their joint data, without the need for $\mathcal{S}$ and the banks to disclose their data in an unencrypted manner to each other, i.e. our solution provides _input privacy_ through encryption, with mathematically verifiable guarantees. To the best of our knowledge, such joint privacy-preserving feature extraction in a federation with horizontally and vertically partitioned data is novel. + +Furthermore, to prevent the model from memorizing instances from the training data, the model is trained with a machine learning (ML) algorithm that provides Differential Privacy (DP). Our overall solution therefore provides both _input privacy_, as none of the entities in the federation ever sees the data of any of the other entities in an unencrypted manner, and _output privacy_, as the model and any inferences with that model avoid information leakage about the underlying training data under DP guarantees. + +For the privacy-preserving feature extraction we propose a custom protocol based on elliptic curve-based ElGamal and oblivious key-value stores (OKVS). The model is a neural network trained with DP-SGD. We prove that our overall solution is secure in the honest-but-curious setting. Experimental results demonstrate that our solution is efficient and scalable, and that it yields accurate models while preserving input and output privacy. + + +**GitHub User Serving as POC (or Email Address):** golobs@uw.edu + +**Affiliation/Organization(s) Contributing (if relevant):** University of Washington Tacoma, Universidade de Brasilia, TU Delft + +**Tool Link:** https://github.com/steveng9/PETsChallenge diff --git a/tools/disassociability/PixelDP/README.md b/tools/disassociability/PixelDP/README.md new file mode 100644 index 0000000..668493a --- /dev/null +++ b/tools/disassociability/PixelDP/README.md @@ -0,0 +1,22 @@ +# PixelDP + +**Primary Focus Area:** De-identification + +**De-identification Keywords:** Differential Privacy, Verification of Algorithms, Machine Learning, Adversarial Examples + +**Brief Description:** Adversarial examples that fool prediction models are a +new class of attacks introduced by machine learning +deployments. PixelDP is the first certified defense that both offers provable +guarantees of robustness against these attacks and +scales to large models and datasets, such as Google’s Inception on the ImageNet +dataset. PixelDP's design relies on a novel use of +differential privacy at prediction time. + +**Additional Notes:** This [IEEE S&P 2019 research paper](https://arxiv.org/pdf/1802.03471.pdf) describes +PixelDP. + +**GitHub POC:** @matlecu + +**Affiliation/Organization(s) Contributing:** Columbia University + + **Tool Link:** PixelDP's code can be found on [GitHub](https://github.com/columbia/pixeldp). diff --git a/tools/disassociability/Privacy-Protection-Application-PPA/README.md b/tools/disassociability/Privacy-Protection-Application-PPA/README.md new file mode 100644 index 0000000..3c59dc4 --- /dev/null +++ b/tools/disassociability/Privacy-Protection-Application-PPA/README.md @@ -0,0 +1,15 @@ +# Privacy Protection Application (PPA) + +**Brief Description:** The Privacy Protection Application de-identifies databases that contain sequential geolocation data, sometimes called moving object databases. A record of a personally-owned vehicle’s route of travel is an example, but the tool can process other types of geolocation sequences. The application has a graphical user interface and operates on Linux, OS X, and Windows. Location suppression is the de-identification strategy used, and decisions about which locations to suppress are based on information theory. This strategy does not modify the precision of retained location information. One of the objectives is to produce data usable for vehicle safety analysis and transportation application development. + +**Link to Tool:** [https://github.com/usdot-its-jpo-data-portal/privacy-protection-application](https://github.com/usdot-its-jpo-data-portal/privacy-protection-application) + +**Primary Tool Focus Area:** De-identification + +**Keywords:** K-Anonymity, Anonymization, Information Leakage, Algorithmic Fairness, Database Queries, Location Data + +**Email POC:** carterjm@ornl.gov + +**Affiliation/Organization(s) Contributing:** U.S. Department of Transportation + +**Additional Notes:** This tool treats static databases and has two versions. The main GUI versions uses a very efficient map matching strategy that may identify false roads for certain types of road structures. The tagged version ([https://github.com/usdot-its-jpo-data-portal/privacy-protection-application/releases/tag/hmm-mm](https://github.com/usdot-its-jpo-data-portal/privacy-protection-application/releases/tag/hmm-mm)) uses a Hidden Markov Model map matching algorithm that is more accurate, but less efficient. This version is a command line tool that runs in Docker. Additionally, a streaming de-identification tool was developed for a USDOT Safety Pilot Study. This tool uses geofencing to identify locations that can be retained. It can also be found on GitHub: [https://github.com/usdot-jpo-ode/jpo-cvdp](https://github.com/usdot-jpo-ode/jpo-cvdp) diff --git a/tools/disassociability/Private-Aggregation-of-Teacher-Ensembles-PATE/README.md b/tools/disassociability/Private-Aggregation-of-Teacher-Ensembles-PATE/README.md new file mode 100644 index 0000000..bf87654 --- /dev/null +++ b/tools/disassociability/Private-Aggregation-of-Teacher-Ensembles-PATE/README.md @@ -0,0 +1,15 @@ +# Private Aggregation of Teacher Ensembles (PATE) + +**Brief Description:** The PATE framework achieves differentially private learning by carefully coordinating the activity of several different ML models. + +**Link to Tool:** [https://github.com/tensorflow/privacy/tree/master/research](https://github.com/tensorflow/privacy/tree/master/research) + +**Primary Tool Focus Area:** De-identification + +**De-identification Keywords:** Differential Privacy, Machine Learning + +**GitHub User Serving as POC:** @npapernot + +**Affiliation/Organization(s) Contributing:** Google + +**Additional Notes:** Papers with full details: [https://arxiv.org/abs/1610.05755](https://arxiv.org/abs/1610.05755) and [https://arxiv.org/abs/1802.08908](https://arxiv.org/abs/1802.08908) diff --git a/tools/disassociability/README.md b/tools/disassociability/README.md new file mode 100644 index 0000000..d2e0c34 --- /dev/null +++ b/tools/disassociability/README.md @@ -0,0 +1,112 @@ +# Disassociability Tools + +Contributions are listed in alphabetical order. + +## Approximate Minima Perturbation (AMP) + +**Keywords:** Differential Privacy, Machine Learning + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/AMP)** | **[Link to Tool](https://github.com/sunblaze-ucb/dpml-benchmark)** + +## ARX Data Anonymization Tool + +**Keywords:** Differential Privacy, K-Anonymity, Anonymization, Machine Learning + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/ARX)** | **[Link to Tool](https://arx.deidentifier.org/)** + +## Chorus + +**Keywords:** Differential Privacy + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Chorus)** | **[Link to Tool](https://github.com/uvm-plaid/chorus)** + +## Differential Privacy Synthetic Data Challenge Algorithms + +**Keywords:** Differential Privacy, Synthetic Data Generation + +**Algorithms:** DP_WGAN-UCLANESL, DPSyn, rmckenna + +**[Check out the Algorithms](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Differential-Privacy-Synthetic-Data-Challenge-Algorithms)** + +## Differentially Private Stochastic Gradient Descent (DP-SGD) + +**Keywords:** Differential Privacy, Machine Learning + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Differentially-Private-Stochastic-Gradient-Descent-DP-SGD)** | **[Link to Tool](https://github.com/tensorflow/privacy)** + +## Diffprivlib + +**Keywords:** Differential Privacy, Machine Learning, Data Analytics + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Diffprivlib)** | **[Link to Tool](https://github.com/IBM/differential-privacy-library)** + +## Duet + +**Keywords:** Differential Privacy, Verification of Algorithms, Machine Learning + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Duet)** | **[Link to Tool](https://github.com/uvm-plaid/duet)** + +## Ektelo +**Keywords:** Differential Privacy + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Ektelo)** | **[Link to Tool](https://ektelo.github.io/)** + +## GUPT: Privacy preserving data analysis made easy + +**Keywords:** Differential Privacy, Machine Learning, Database Queries + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/GUPT)** | **[Link to Tool](https://github.com/prashmohan/GUPT)** + +## Google Differential Privacy Library + +**Keywords:** Differential Privacy + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Google-DP-Lib)** | **[Link to Tool](https://github.com/google/differential-privacy)** + +## MusCAT + +**Keywords:** Differential Privacy, Multiparty Homomorphic Encryption, Machine Learning, Federated Learning + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/MusCAT)** | **[Link to Tool](https://github.com/hhcho/muscat)** + +## PixelDP + +**Keywords:** Differential Privacy, Verification of Algorithms, Machine Learning, Adversarial Examples + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/PixelDP)** | **[Link to Tool](https://github.com/columbia/pixeldp)** + +## PPMLHuskies + +**Keywords:** Differential Privacy, Homomorphic Encryption, Machine Learning, Federated Learning + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/PPMLHuskies)** | **[Link to Tool](https://github.com/steveng9/PETsChallenge)** + +## Privacy Protection Application (PPA) + +**Keywords:** K-Anonymity, Anonymization, Information Leakage, Algorithmic Fairness, Database Queries, Location Data + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Privacy-Protection-Application-PPA)** | **[Link to Tool](https://github.com/usdot-its-jpo-data-portal/privacy-protection-application)** + +## Private Aggregation of Teacher Ensembles (PATE) + +**Keywords:** Differential Privacy, Machine Learning + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Private-Aggregation-of-Teacher-Ensembles-PATE)** | **[Link to Tool](https://github.com/tensorflow/privacy/tree/master/research)** + +## puffle + +**Keywords:** Differential Privacy, Machine Learning, Federated Learning, Model Personalization + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/puffle)** | **[Link to Tool](https://github.com/kenziyuliu/pets-challenge)** + +## Scarlet-PETs: Anomaly Detection via Privacy-Enhanced Two-Step Federated Learning + +**Keywords:** Differential Privacy, Anonymization, Information Leakage, Federated Learning, Anomaly Detection, SMC + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Scarlet-PETs)** | **[Link to Tool](https://github.com/idsla/Scarlet-PETs)** + +## Visa Pets Federated Learning + +**Keywords:** Differential Privacy, Machine Learning, Privacy Preservering Computation, MPC + +**[More Information](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/visa-pets-FL)** | **[Link to Tool](https://github.com/Visa-Research/visa-pets-FL)** diff --git a/tools/disassociability/Scarlet-PETs/README.md b/tools/disassociability/Scarlet-PETs/README.md new file mode 100644 index 0000000..7ca173d --- /dev/null +++ b/tools/disassociability/Scarlet-PETs/README.md @@ -0,0 +1,17 @@ +# Scarlet-PETs + +**Name of Tool: **Anomaly Detection via Privacy-Enhanced Two-Step Federated Learning + +**Primary Focus Area (select one):** De-identification + +**De-identification Keywords (select any relevant):** Differential Privacy, Anonymization, Information Leakage, Federated Learning, Anomaly Detection, SMC + +**Brief Description: ** We developed a novel privacy-preserving (PP) two-step federated learning approach to identify anomalous financial transactions. In the first step, we performed PP feature mining for account-level banks’ data, followed by their augmentation to the payment network’s data using a PP encoding scheme. In the second step, a classifier is learned by the messaging network from the augmented data. A key benefit of our approach is that the performance in the federated setting is comparable to the performance in the centralized setting, and there is no significant drop in accuracy. Furthermore, our approach is extremely flexible since it allows the messaging network to adapt its model and features to build a better classifier without imposing any additional computational or privacy burden on the banks. + +**Additional Notes:** https://rutgers.box.com/s/q84zjo3edv5d1e1eu67ypihiw8cb2djq + +**GitHub User Serving as POC (or Email Address):** @[h-asif], @[sitaomin1994] + +**Affiliation/Organization(s) Contributing (if relevant):** Rutgers University + +**Tool Link:** https://github.com/idsla/Scarlet-PETs diff --git a/tools/disassociability/puffle/README.md b/tools/disassociability/puffle/README.md new file mode 100644 index 0000000..1cb8063 --- /dev/null +++ b/tools/disassociability/puffle/README.md @@ -0,0 +1,15 @@ +# puffle + +**Primary Focus Area (select one):** De-identification + +**De-identification Keywords (select any relevant):** Differential Privacy, Machine Learning, Federated Learning, Model Personalization + +**Brief Description:** +This tool contains the solution of team puffle at the [US/UK PETs Prize Challenge](https://www.drivendata.org/competitions/group/nist-federated-learning/) that won [1st place](https://drivendata.co/blog/federated-learning-pets-prize-winners-phases-2-3) in the Pandemic Forecasting Track. Our solution is a simple, general, and easy-to-use multi-task learning (MTL) framework that balances the interplay between privacy, utility, and data heterogeneity in private cross-silo federated learning. Our framework involves three key components: (1) model personalization for capturing data heterogeneity across data silos, (2) local noisy gradient descent for silo-specific, node-level differential privacy in contact graphs, and (3) model mean-regularization to balance privacy-heterogeneity trade-offs and minimize the loss of accuracy. Combined together, our framework can provide differential privacy with flexible data granularity and improved privacy-utility tradeoffs; has high adaptability to gradient-based learning algorithms; and is simple to implement and tune. Our solution is in part based on our NeurIPS'22 [paper](https://arxiv.org/abs/2206.07902) studying privacy and personalization in cross-silo federated learning. + + +**GitHub User Serving as POC (or Email Address):** @kenziyuliu + +**Affiliation/Organization(s) Contributing (if relevant):** Carnegie Mellon University, School of Computer Science + +**Tool Link:** https://github.com/kenziyuliu/pets-challenge diff --git a/tools/disassociability/visa-pets-FL/LICENSE.txt b/tools/disassociability/visa-pets-FL/LICENSE.txt new file mode 100644 index 0000000..fe463e0 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/LICENSE.txt @@ -0,0 +1,408 @@ +Attribution-NonCommercial 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + j. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + k. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + l. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/tools/disassociability/visa-pets-FL/README.md b/tools/disassociability/visa-pets-FL/README.md new file mode 100644 index 0000000..2a2c3a0 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/README.md @@ -0,0 +1,24 @@ +# Visa-Pets-FL + +Copyright 2023 Visa + +This code is meant only as a reference for the Privacy-enhancing technologies (PETs) challenge organized by NIST & NSF on behalf of the U.S. government. The code is specifically written for the competition's test harness. The current implementation would have to be modified to run in a real-world environment. This is not a Visa Product and Visa doesn't guarantee the code is maintained or bug free. + +Our solution folder consists of three components, a centralized solution, a federated solution, and the source code for an oblivious transfer shared library, which we use in our federated solution. We describe the files below: + +* *DataObjects.py:* Defines the train and test datasets +* *DataPrepUtils.py:* Data pre-processing code +* *DNNCentralizedModule.py:* The main centralized code logic +* *NeuralNet.py:* Defines the neural network configuration +* *solution_centralized.py:* The exposed APIs for the execution harness. After pre-processing the data, both fit() and predict() call the corresponding functions in the DNNCentralizedModule.py +* *DNN.py:* Helper functions for the training and inference flows, including flattening per-sample gradient updates into 1d tensors, masking gradients, converting gradients to and from integers. +* *libwrapper.so:* The shared library exposes the apis for us to do oblivious transfer, saving symmetric keys to disk and symmetric encryption & decryption process. +* *ot.py:* Loads and exposes the api of the oblivious transfer library. +* *solution_federated.py:* Our federated solution. The solution contains Client and Strategy functions for both the train and test phases: + +## Instructions + +Traverse into the ot_library & build the library based upon the readme file present inside the folder. The library would be created under ./out/build//wrapper/. Copy the dynamic library libwrapper into the federated folder. + +See the below repo for instructions on how to use the runtime container to run the solution: +https://github.com/drivendataorg/pets-prize-challenge-runtime/tree/main/runtime \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/centralized/DNNCentralizedModule.py b/tools/disassociability/visa-pets-FL/centralized/DNNCentralizedModule.py new file mode 100644 index 0000000..a2d135e --- /dev/null +++ b/tools/disassociability/visa-pets-FL/centralized/DNNCentralizedModule.py @@ -0,0 +1,204 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +from pathlib import Path +import numpy as np +import pandas as pd + +### Libraries for Algorithms +from sklearn.model_selection import train_test_split, KFold, ShuffleSplit, StratifiedKFold, StratifiedShuffleSplit +from sklearn.model_selection import cross_val_score +from sklearn import metrics +from sklearn.metrics import classification_report, confusion_matrix, accuracy_score +from sklearn.preprocessing import StandardScaler, MinMaxScaler + +import torch +from torch.utils.data import Dataset, DataLoader +from torch import nn +import torch.nn.functional as F +from src.NeuralNet import NeuralNetwork +from src.DataObjects import CustomDatasetTrain, CustomDatasetTest +import time + + +BATCH_SIZE_TRAIN = 4096 +USE_BANK_INFO = True +device = "cuda" +print("Using {} device".format(device)) + + +def get_flags(idx, X, BankFlagDict): + # Get bank flag by the status of the receiver bank. + # The 2nd and 4th item list in idx contains the bid, uid of the receiver bank. + bankflags = [BankFlagDict[item] if item in BankFlagDict else False for item in zip(idx[1], idx[3])] + bankflags = [int(item) for item in bankflags] + bankflags = np.array(bankflags).reshape([-1, 1]) + bankflags = torch.tensor(bankflags) + return torch.concat([X, bankflags], axis=1) + + +def norm_clipping(X, max_norm=1.0): + # Norm clipping + X_norm = torch.norm(X, dim=1) + X_norm = torch.clamp(X_norm/max_norm, min=1.0) + X_norm = X_norm.unsqueeze(1).repeat(1, (X.shape)[1]) + return X/X_norm + +def to_int(X, factor=1e6, dtype=torch.int32): + return (X*factor).to(dtype) + +def to_float(X, factor=1e6, dtype=torch.float): + return (X.to(dtype))/factor + +def get_aggregated_grads(X): + return torch.sum(X, axis=0) + +def train(dataloader, model, loss_fn, optimizer, bank_flag_dict=None): + size = len(dataloader.dataset) + model.train() + for batch, (idx, X, y) in enumerate(dataloader): + if USE_BANK_INFO: + X = get_flags(idx, X, bank_flag_dict) + X, y = X.to(device), y.to(device) + pred = model(X.float()) + loss =loss_fn(pred, y) + + optimizer.zero_grad() + loss.backward() + optimizer.step() + + if (batch%(len(dataloader)//10)) == 0: + loss, current = loss.item(), batch*len(X) + print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]") + return model + +def test(dataloader, model, loss_fn, bank_flag_dict=None): + size = len(dataloader.dataset) + num_batches = len(dataloader) + model.eval() + test_loss, correct = 0, 0 + with torch.no_grad(): + for idx, X in dataloader: + if USE_BANK_INFO: + X = get_flags(idx, X, bank_flag_dict) + X = X.to(device) + pred = model(X.float()) + probs = F.softmax(pred, dim=1) + + return probs + +import opacus +import time +from opacus.grad_sample import GradSampleModule +from opacus.grad_sample import register_grad_sampler +from sklearn import metrics + +def fit(processor_path, bank_path, model_path, final_model_name, n_epochs=1): + ### Setup Dataset Path + train_data_path = processor_path + "train_data_processor.csv" + train_label_path = processor_path + "train_label.csv" + bank_flag_dict_path = bank_path + "bank_flag_dict" + scaler_path = model_path + "scaler" + + # load bank flag dictionary + import pickle + with open(bank_flag_dict_path, "rb") as f: + BankFlagDict = pickle.load(f) + + dataset_train = CustomDatasetTrain([train_data_path, train_label_path, scaler_path]) + train_dataloader = DataLoader(dataset_train, batch_size=BATCH_SIZE_TRAIN, shuffle=True) + print("Done! Loading Payment Processor Training") + + ### Retrieve Input Dimension for Neural Nets + X = dataset_train.data + USE_BANK_INFO = True + INPUT_DIM = (X.shape)[1] + if USE_BANK_INFO: + INPUT_DIM +=1 + print("NN Input Dimension is {}".format(INPUT_DIM)) + + ### Specify GPU/CPU + device = "cuda" + print("Using {} device".format(device)) + model = NeuralNetwork(INPUT_DIM).to(device) + loss_fn = nn.CrossEntropyLoss() + + for t in range(n_epochs): + print(f"Epoch {t+1}\n-------------------------") + lr_init = (5e-2/8192)*BATCH_SIZE_TRAIN + lr = lr_init/np.sqrt(t+1) + optimizer = torch.optim.SGD(model.parameters(), lr=lr, weight_decay=5e-4) + t_start = time.time() + model = train(train_dataloader, model, loss_fn, optimizer, BankFlagDict) + t_end = time.time() + print("Training time", t_end - t_start) + + # test model save/load + modelname = model_path+"model"+str(t) + torch.save(model.state_dict(), modelname) + pred_model = NeuralNetwork(INPUT_DIM).to(device) + pred_model.load_state_dict(torch.load(modelname)) + pred_model.to(device) + + import gc + gc.collect() + + torch.save(model.state_dict(), final_model_name) + print("Done") + + +def predict(processor_path, bank_path, model_path, model_name, res_path, format_path): + ### Setup Dataset Path + test_data_path = processor_path + "test_data_processor.csv" + test_label_path = None + bank_flag_dict_path = bank_path + "bank_flag_dict" + scaler_path = model_path + "scaler" + # load bank flag dictionary + import pickle + with open(bank_flag_dict_path, "rb") as f: + BankFlagDict = pickle.load(f) + + dataset = CustomDatasetTest([test_data_path, test_label_path, scaler_path]) + test_dataloader = DataLoader(dataset, batch_size=len(dataset), shuffle=False) + print("Done! Loading Payment Processor Test") + + + ### Retrieve Input Dimension for Neural Nets + X = dataset.data + USE_BANK_INFO = True + INPUT_DIM = (X.shape)[1] + if USE_BANK_INFO: + INPUT_DIM +=1 + print("NN Input Dimension is {}".format(INPUT_DIM)) + + ### Specify GPU/CPU + device = "cuda" + print("Using {} device".format(device)) + + loss_fn = nn.CrossEntropyLoss() + + labelpath = test_data_path + label_df = pd.read_csv(labelpath) + + label_format = pd.read_csv(format_path) + + pred_model = NeuralNetwork(INPUT_DIM).to(device) + pred_model.load_state_dict(torch.load(model_name)) + pred_model.to(device) + pred_model.eval() + probs = test(test_dataloader, pred_model, loss_fn, BankFlagDict) + y_score = probs[:, 1].cpu().numpy().tolist() + label_df["Score"] = y_score + print(label_df.columns) + final_res = label_format.merge(label_df, on="MessageId", how="left") + print(label_df.columns, label_format.columns, final_res.columns) + final_res = final_res.fillna(1e-8) + final_res = final_res.rename(columns={"Score_y":"Score"}) + print(final_res.columns) + final_res[["MessageId", "Score"]].to_csv(res_path, index=False) + print("Done") + diff --git a/tools/disassociability/visa-pets-FL/centralized/DataObjects.py b/tools/disassociability/visa-pets-FL/centralized/DataObjects.py new file mode 100644 index 0000000..845f505 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/centralized/DataObjects.py @@ -0,0 +1,82 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import numpy as np +import pandas as pd +pd.set_option("display.max_columns", None) +from sklearn.preprocessing import StandardScaler, MinMaxScaler +import torch +from torch.utils.data import Dataset, DataLoader +import pickle + +class CustomDatasetTrain(Dataset): + def __init__(self, paths): + [train_data_path, train_label_path, scaler_path] = paths + raw_data = pd.read_csv(train_data_path, index_col="MessageId") + col = raw_data.columns + print(col) + + id_cols = ["Sender", "Receiver", "OrderingAccount", "BeneficiaryAccount"] + ids = raw_data[id_cols] + self.ids = ids.values.tolist() + data = raw_data.drop(columns=id_cols) + self.data = data.values + print(self.data.shape) + + scaler = StandardScaler() + scaler.fit(self.data) + self.data = scaler.transform(self.data) + with open(scaler_path, "wb") as f: + pickle.dump(scaler, f) + + label = pd.read_csv(train_label_path, index_col="MessageId") + self.label = label.values + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + ids = self.ids[idx] + tensor = torch.from_numpy(self.data[idx]) + class_id = torch.tensor(self.label[idx][-1]) + return ids, tensor, class_id + + +class CustomDatasetTest(Dataset): + def __init__(self, paths): + [test_data_path, test_label_path, scaler_path] = paths + raw_data = pd.read_csv(test_data_path, index_col="MessageId") + col = raw_data.columns + print(col) + + id_cols = ["Sender", "Receiver", "OrderingAccount", "BeneficiaryAccount"] + ids = raw_data[id_cols] + self.ids = ids.values.tolist() + data = raw_data.drop(columns=id_cols) + self.data = data.values + print(self.data.shape) + + with open(scaler_path, "rb") as f: + scaler = pickle.load(f) + self.data = scaler.transform(self.data) + if test_label_path: + label = pd.read_csv(test_label_path, index_col="MessageId") + self.label = label.values + self.is_submission = False + else: + self.is_submission = True + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + ids = self.ids[idx] + tensor = torch.from_numpy(self.data[idx]) + if self.is_submission: + return ids, tensor + class_id = torch.tensor(self.label[idx][-1]) + return ids, tensor, class_id diff --git a/tools/disassociability/visa-pets-FL/centralized/DataPrepUtils.py b/tools/disassociability/visa-pets-FL/centralized/DataPrepUtils.py new file mode 100644 index 0000000..ddf40b5 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/centralized/DataPrepUtils.py @@ -0,0 +1,387 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +### Libraries for Data Handling + +from pathlib import Path +import numpy as np +import pandas as pd +import copy + + +pd.set_option("display.max_columns", None) + +### Libraries for Algorithms +from sklearn.model_selection import train_test_split, KFold, ShuffleSplit, StratifiedKFold, StratifiedShuffleSplit +from sklearn.model_selection import cross_val_score +from sklearn import metrics +from sklearn.metrics import classification_report, confusion_matrix, accuracy_score +from sklearn.preprocessing import StandardScaler, MinMaxScaler +import sklearn.utils +import pickle +import time + +def prepare_data_train(path, work_dir): + + # Load train/test data from csv + train = pd.read_csv(path) + train.to_csv(work_dir+'train_copy.csv', index=False) + train["Timestamp"] = train["Timestamp"].astype("datetime64[ns]") + + train = train.set_index("UETR") + train = train.sort_values(by=['Timestamp']) + + # Frequency for each sender + + senders = train["Sender"].unique() + + # Frequency for each receiver + receivers = train["Receiver"].unique() + + # If instructed/settled in the same currency + train["same_currency"] = train.apply(lambda row: int(row['InstructedCurrency']==row['SettlementCurrency']), axis=1) + + # Normalize the transaction amount by exchange rate + exchange_rate = {'GBP':1.22,'EUR':1.08, 'USD':1, 'JPY':0.0078, + 'CAD':0.75, 'INR':0.012, 'AUD':0.7, 'NZD':0.64, + 'DKK':0.14,'IDR':0.000066,'MXN':0.054,'ZAR':0.059, + 'KES':0.0081,'ILS':0.29,'THB':0.03,'TND':0.33, + 'TRY':0.053, 'SGD':0.76, 'MAD':0.098, 'AED':0.27, + 'SEK':0.096, 'CHF':1.08, 'SAR':0.27, 'PLN':0.23, + 'BHD':2.65, "CZK":0.045, "NAD":0.059, "NOK":0.10, + "HKD":0.13, "HUF":0.0027, "MYR":0.23, "LKR":0.0027, + "CNY":0.15, "EGP":0.034, "KRW":0.00081, "HRK":0.143122, + "BDT":0.0096, "PHP":0.018, "BOB":0.14, "RON":0.22, + "OMR":2.6, "KWD":3.28, "NPR":0.0076, "FJD":0.46, + "MUR":0.023, "JOD":1.41, "VND":0.000043, "ISK":0.0070, + "BRL":0.2, "TWD":0.033, "QAR":0.27, "XOF":0.0017, + "RSD":0.0092, "COP":0.00021, "BGN":0.55, "RUB":0.015, + "BWP":0.078, "TZS":0.00043, "BAM":0.55 + } + + + train["exchange_rate"] = train["InstructedCurrency"].map(exchange_rate) + train["NormalizedAmount"] = train["InstructedAmount"]*train["exchange_rate"] + train.drop(columns="exchange_rate", inplace=True) + + # Hour + train["hour"] = train["Timestamp"].dt.hour + train["day"] = train["Timestamp"].dt.day + + + join_keys = ['UETR', 'Timestamp'] + + start_time = time.time() + + keys = ["OrderingName", "BeneficiaryName", "InstructedCurrency", "SettlementCurrency"] + fields = ["MessageId", "Timestamp"] + df = train.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_avg_count" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + train["sr_currency_pair_avg_count"] = train["sr_currency_pair_avg_count"]/train["day"] + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + df = train.groupby(keys)[fields].rolling('1H', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_hour_freq" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + fields = ["SettlementAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1 , on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_settlement_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + fields = ["InstructedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_instructed_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["OrderingName", "BeneficiaryName"] + fields = ["MessageId", "Timestamp"] + df = train.groupby(keys)[fields].rolling('7D', closed="left", on="Timestamp").count() + rename_dict = {item:item+"sr_pair_count_last_7d" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["OrderingName"] + fields = ["NormalizedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"sender_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["BeneficiaryName"] + fields = ["NormalizedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"receiver_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + print(train.columns[train.isna().any()].tolist()) + + train.drop(columns=["day", "hour"], inplace=True) + train = train.fillna(0) + # Exclude below categorical columns for training and testing + columns_to_drop = [ + #"UETR", + "TransactionReference", + "OrderingName", + "OrderingStreet", + "OrderingCountryCityZip", + "BeneficiaryName", + "BeneficiaryStreet", + "BeneficiaryCountryCityZip", + "SettlementDate", + "SettlementCurrency", + "InstructedCurrency", + "Timestamp" + ] + + train = train.drop(columns_to_drop, axis=1) + + print("train columns are", train.columns) + train[train.select_dtypes(include=['number']).columns] = train[train.select_dtypes(include=['number']).columns].clip(-1e20, 1e20) + + pd_train = train + pd_train['Label'].to_csv(work_dir+"train_label.csv") + + from sklearn.utils import resample + pd_majority = pd_train[pd_train.Label==0] + pd_minority = pd_train[pd_train.Label==1] + + print(len(pd_majority), len(pd_minority)) + + pd_minority_upsampled = resample(pd_minority, + replace=True, + n_samples=int(len(pd_majority)), + random_state=42) + + print(len(pd_majority), len(pd_minority_upsampled)) + pd_train = pd.concat([pd_majority, pd_minority_upsampled]) + DOWNSAMPLE_FACTOR = 1 + + pd_train_subset = resample(pd_train, + replace=False, + n_samples=int(len(pd_train)/DOWNSAMPLE_FACTOR), + random_state=42) + + pd_train_temp = pd_train_subset + pd_train_temp = pd_train_temp.fillna(0) + columns_to_drop = ["Label"] + pd_train_temp[['MessageId', 'Label']].to_csv(work_dir+'train_label.csv', index=False) + pd_train_temp.drop(columns=columns_to_drop, inplace=True) + pd_train_temp.to_csv(work_dir+'train_data_processor.csv', index=False) + + + +def prepare_data_test(path, work_dir): + + TEST_MODE = False + test = pd.read_csv(path) + actual_test = copy.deepcopy(test) + train = pd.read_csv(work_dir+'train_copy.csv') + test["Timestamp"] = test["Timestamp"].astype("datetime64[ns]") + train["Timestamp"] = train["Timestamp"].astype("datetime64[ns]") + actual_test["Timestamp"] = actual_test["Timestamp"].astype("datetime64[ns]") + if TEST_MODE: + test = pd.concat([train, test.drop(columns=["Label"])]) + else: + test = pd.concat([train, test]) + test = test.set_index('UETR') + test["Timestamp"] = test["Timestamp"].astype("datetime64[ns]") + + test = test.sort_values(by=['Timestamp']) + test["same_currency"] = test.apply(lambda row: int(row['InstructedCurrency']==row['SettlementCurrency']), axis=1) + + exchange_rate = {'GBP':1.22,'EUR':1.08, 'USD':1, 'JPY':0.0078, + 'CAD':0.75, 'INR':0.012, 'AUD':0.7, 'NZD':0.64, + 'DKK':0.14,'IDR':0.000066,'MXN':0.054,'ZAR':0.059, + 'KES':0.0081,'ILS':0.29,'THB':0.03,'TND':0.33, + 'TRY':0.053, 'SGD':0.76, 'MAD':0.098, 'AED':0.27, + 'SEK':0.096, 'CHF':1.08, 'SAR':0.27, 'PLN':0.23, + 'BHD':2.65, "CZK":0.045, "NAD":0.059, "NOK":0.10, + "HKD":0.13, "HUF":0.0027, "MYR":0.23, "LKR":0.0027, + "CNY":0.15, "EGP":0.034, "KRW":0.00081, "HRK":0.143122, + "BDT":0.0096, "PHP":0.018, "BOB":0.14, "RON":0.22, + "OMR":2.6, "KWD":3.28, "NPR":0.0076, "FJD":0.46, + "MUR":0.023, "JOD":1.41, "VND":0.000043, "ISK":0.0070, + "BRL":0.2, "TWD":0.033, "QAR":0.27, "XOF":0.0017, + "RSD":0.0092, "COP":0.00021, "BGN":0.55, "RUB":0.015, + "BWP":0.078, "TZS":0.00043, "BAM":0.55 + } + + test["exchange_rate"] = test["InstructedCurrency"].map(exchange_rate) + test["NormalizedAmount"] = test["InstructedAmount"]*test["exchange_rate"] + test.drop(columns="exchange_rate", inplace=True) + + test["hour"] = test["Timestamp"].dt.hour + test["day"] = test["Timestamp"].dt.day + + + join_keys = ['UETR', 'Timestamp'] + + keys = ["OrderingName", "BeneficiaryName", "InstructedCurrency", "SettlementCurrency"] + fields = ["MessageId", "Timestamp"] + df = test.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_avg_count" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + test["sr_currency_pair_avg_count"] = test["sr_currency_pair_avg_count"]/test["day"] + + + df = test.groupby(keys)[fields].rolling('1H', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_hour_freq" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + fields = ["SettlementAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_settlement_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + fields = ["InstructedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_instructed_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + keys = ["OrderingName", "BeneficiaryName"] + fields = ["MessageId", "Timestamp"] + df = test.groupby(keys)[fields].rolling('7D', closed="left", on="Timestamp").count() + rename_dict = {item:item+"sr_pair_count_last_7d" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + keys = ["OrderingName"] + fields = ["NormalizedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"sender_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + keys = ["BeneficiaryName"] + fields = ["NormalizedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"receiver_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + test.drop(columns=["day", "hour"], inplace=True) + test = test.fillna(0) + + print(test.columns[test.isna().any()].tolist()) + + test = test.reset_index() + test = test.rename(columns={'index':'UETR'}) + test = actual_test[['UETR']].merge(test, left_on=["UETR"], right_on=["UETR"], how="left") + + # Exclude below categorical columns for training and testing + columns_to_drop = [ + "UETR", + "TransactionReference", + "OrderingName", + "OrderingStreet", + "OrderingCountryCityZip", + "BeneficiaryName", + "BeneficiaryStreet", + "BeneficiaryCountryCityZip", + "SettlementDate", + "SettlementCurrency", + "InstructedCurrency", + "Timestamp" + ] + + test = test.drop(columns_to_drop, axis=1) + + print("test columns:", test.columns) + test[test.select_dtypes(include=['number']).columns] = test[test.select_dtypes(include=['number']).columns].clip(-1e20, 1e20) + print(test.columns) + + from sklearn.utils import resample + DOWNSAMPLE_FACTOR = 1 + pd_test = test + pd_test = resample(pd_test, + replace=False, + n_samples=int(len(pd_test)/DOWNSAMPLE_FACTOR) + ) + pd_test = pd_test.fillna(0) + + if TEST_MODE: + pd_test.to_csv(work_dir+'test_data_processor.csv', index=False) + else: + columns_to_drop = ["Label"] + pd_test[['MessageId','Label']].to_csv(work_dir+'test_label.csv', index=False) + pd_test.drop(columns=columns_to_drop, inplace=True) + pd_test.to_csv(work_dir+'test_data_processor.csv', index=False) + + +def prepare_bank_data(bank_path, work_dir): + ### Bank Data + data_bank = pd.read_csv(bank_path) + data_bank.columns + data_bank.head(1) + + BankFlagDict = {} + for i in range(len(data_bank)): + row = data_bank.loc[i] + BankFlagDict[(row["Bank"], row["Account"])] = (row["Flags"]==0) + + import pickle + with open(work_dir+"bank_flag_dict", "wb") as f: + pickle.dump(BankFlagDict, f) + + + +def explore_dataset(datapaths): + [train_path, test_path] = datapaths + train = pd.read_csv(train_path) + test = pd.read_csv(test_path) + print(train.columns, test.columns) + print(train["SettlementDate"].unique(), test["SettlementDate"].unique()) + print(train["InstructedCurrency"].unique(), test["InstructedCurrency"].unique()) + print(train["SettlementCurrency"].unique(), test["SettlementCurrency"].unique()) + lst1 = (train["Sender"]+train["OrderingAccount"]).unique().tolist() + lst2 = (test["Sender"]+test["OrderingAccount"]).unique().tolist() + print(len(lst1), len(lst2)) + lst_diff = list(set(lst2)-set(lst1)) + print(len(lst_diff)) + lst_diff = list(set(lst1)-set(lst2)) + print(len(lst_diff)) diff --git a/tools/disassociability/visa-pets-FL/centralized/NeuralNet.py b/tools/disassociability/visa-pets-FL/centralized/NeuralNet.py new file mode 100644 index 0000000..71033ec --- /dev/null +++ b/tools/disassociability/visa-pets-FL/centralized/NeuralNet.py @@ -0,0 +1,28 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import torch +from torch import nn + +class NeuralNetwork(nn.Module): + def __init__(self, input_dim): + super(NeuralNetwork, self).__init__() + self.flatten = nn.Flatten() + self.linear_relu_stack = nn.Sequential( + nn.Linear(input_dim, 128), + #nn.ReLU(), + #nn.Linear(256, 64), + nn.ReLU(), + nn.Linear(128, 16), + nn.ReLU(), + nn.Linear(16, 2) + ) + + def forward(self, x): + x = self.flatten(x) + logits = self.linear_relu_stack(x) + return logits diff --git a/tools/disassociability/visa-pets-FL/centralized/solution_centralized.py b/tools/disassociability/visa-pets-FL/centralized/solution_centralized.py new file mode 100644 index 0000000..996ca30 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/centralized/solution_centralized.py @@ -0,0 +1,47 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +from pathlib import Path + +import pandas as pd + +import src.DNNCentralizedModule as DNNUtils + +from src.DataPrepUtils import (prepare_data_train, + prepare_data_test, + prepare_bank_data) + +def fit(processor_data_path: Path, bank_data_path: Path, model_dir: Path): + prepare_data = True + processor_data_path, bank_data_path, model_dir = str(processor_data_path), str(bank_data_path), str(model_dir) + if prepare_data: + prepare_data_train(processor_data_path, model_dir) + prepare_bank_data(bank_data_path, model_dir) + n_epochs = 20 + final_model_name = model_dir + "model" + DNNUtils.fit(model_dir, model_dir, model_dir, final_model_name, n_epochs=n_epochs) + + +def predict( + processor_data_path: Path, + bank_data_path: Path, + model_dir: Path, + preds_format_path: Path, + preds_dest_path: Path, +): + prepare_data = True + processor_data_path = str(processor_data_path) + bank_data_path = str(bank_data_path) + model_dir = str(model_dir) + preds_format_path = str(preds_format_path) + preds_dest_path = str(preds_dest_path) + if prepare_data: + prepare_data_test(processor_data_path, model_dir) + prepare_bank_data(bank_data_path, model_dir) + model_name = model_dir + "model" + DNNUtils.predict(model_dir, model_dir, model_dir, model_name, preds_dest_path, preds_format_path) + diff --git a/tools/disassociability/visa-pets-FL/federated/DNN.py b/tools/disassociability/visa-pets-FL/federated/DNN.py new file mode 100644 index 0000000..3ef7e61 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/federated/DNN.py @@ -0,0 +1,126 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import numpy as np +import torch +from torch import nn +import torch.nn.functional as F + +from .NeuralNet import NeuralNetwork +from opacus.grad_sample import GradSampleModule + +def pad_flags(X, flag=0): + # Pad a column of one or zero bank flag. + n_elem = (X.shape)[0] + bankflags = np.ones((n_elem, 1))*flag + bankflags = torch.tensor(bankflags) + return torch.concat([X, bankflags], axis=1) + + +def norm_clipping(X, max_norm=1.0): + # Norm clipping + X_norm = torch.norm(X, dim=1) + X_norm = torch.clamp(X_norm/max_norm, min=1.0) + X_norm = X_norm.unsqueeze(1).repeat(1, (X.shape)[1]) + return X/X_norm + +def to_int(X, factor=1e6, dtype=torch.int32): + return (X*factor).to(dtype) + +def to_float(X, factor=1e6, dtype=torch.float): + return (X.to(dtype))/factor + +def get_aggregated_grads(X): + return torch.sum(X, axis=0) + +def get_noise(shape, device, dtype=torch.int32): + if dtype==torch.int32: + low, high = -2147483648, 2147483648 + return torch.randint(low, high, shape, dtype=dtype, device=device) + +def flatten_grads(model): + grads_flat = [] + param_shapes = [] + for i, p in enumerate(model.parameters()): + param_shapes.append(p.grad.shape) + grads_flat.append(torch.flatten(p.grad_sample, start_dim=1)) + grads_flat = torch.cat(grads_flat, axis=1) + grads_flat = torch.nan_to_num(grads_flat) + return grads_flat, param_shapes + + +def expand_flattened_grads(model, grads_flat, param_shapes): + curr_pos = 0 + for i, p in enumerate(model.parameters()): + param_shape = param_shapes[i] + n_param = np.prod(param_shape) + end_pos = curr_pos+n_param + p.grad = grads_flat[curr_pos:end_pos].reshape(param_shape) + curr_pos = end_pos + +def clear_per_sample_grad(model): + for i, p in enumerate(model.parameters()): + p.grad_sample = None + + +def prepare_serialization(g, device, scale=1e3, dtype=torch.int32): + + # Convert the input to integer value + g = to_int(g, factor=scale, dtype=dtype) + + # Split the gradients with flag 0 and 1 + s = (g.shape)[0]//2 + g0, g1 = g[:s, :], g[s:, :] + + eps = get_noise(g0.shape, device=device) + eps_sum = torch.sum(eps, axis=0, dtype=torch.int32) + g0 = (g0+eps).to(torch.int32) + g1 = (g1+eps).to(torch.int32) + + + return g0, g1, eps + +def prepare_serialization_with_rounding(g, device, dtype=torch.int32): + + # Split the gradients with flag 0 and 1 + s = (g.shape)[0]//2 + g0, g1 = g[:s, :], g[s:, :] + g0 = torch.argmax(g0, dim=1) + g1 = torch.argmax(g1, dim=1) + + eps = get_noise(g0.shape, device=device) + #eps = torch.zeros(g0.shape) + eps_sum = torch.sum(eps, axis=0, dtype=torch.int32) + g0 = (g0+eps).to(torch.int32) + g1 = (g1+eps).to(torch.int32) + + + return g0, g1, eps + +def complete_deserialization(g, eps, + scale=1e3, + dtype_int=torch.int32, + dtype_float=torch.float): + + # Subtract the random int used for OT + g = (g-eps).to(dtype_int) + # Convert back to real value after OT + g = to_float(g, factor=scale, dtype=dtype_float) + return g + +def init_learning(device, + batch_size, + i_epoch, model=None, + INPUT_DIM=9): + if model==None: + model = GradSampleModule(NeuralNetwork(INPUT_DIM).to(device), force_functorch=True) + loss_fn = nn.CrossEntropyLoss() + batch_size = batch_size + lr_init = (5e-2/8192)*batch_size + lr = lr_init/np.sqrt(i_epoch+1) + optimizer = torch.optim.SGD(model.parameters(), lr=lr, weight_decay=1e-5) + return model, optimizer, loss_fn \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/federated/DataObjects.py b/tools/disassociability/visa-pets-FL/federated/DataObjects.py new file mode 100644 index 0000000..845f505 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/federated/DataObjects.py @@ -0,0 +1,82 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import numpy as np +import pandas as pd +pd.set_option("display.max_columns", None) +from sklearn.preprocessing import StandardScaler, MinMaxScaler +import torch +from torch.utils.data import Dataset, DataLoader +import pickle + +class CustomDatasetTrain(Dataset): + def __init__(self, paths): + [train_data_path, train_label_path, scaler_path] = paths + raw_data = pd.read_csv(train_data_path, index_col="MessageId") + col = raw_data.columns + print(col) + + id_cols = ["Sender", "Receiver", "OrderingAccount", "BeneficiaryAccount"] + ids = raw_data[id_cols] + self.ids = ids.values.tolist() + data = raw_data.drop(columns=id_cols) + self.data = data.values + print(self.data.shape) + + scaler = StandardScaler() + scaler.fit(self.data) + self.data = scaler.transform(self.data) + with open(scaler_path, "wb") as f: + pickle.dump(scaler, f) + + label = pd.read_csv(train_label_path, index_col="MessageId") + self.label = label.values + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + ids = self.ids[idx] + tensor = torch.from_numpy(self.data[idx]) + class_id = torch.tensor(self.label[idx][-1]) + return ids, tensor, class_id + + +class CustomDatasetTest(Dataset): + def __init__(self, paths): + [test_data_path, test_label_path, scaler_path] = paths + raw_data = pd.read_csv(test_data_path, index_col="MessageId") + col = raw_data.columns + print(col) + + id_cols = ["Sender", "Receiver", "OrderingAccount", "BeneficiaryAccount"] + ids = raw_data[id_cols] + self.ids = ids.values.tolist() + data = raw_data.drop(columns=id_cols) + self.data = data.values + print(self.data.shape) + + with open(scaler_path, "rb") as f: + scaler = pickle.load(f) + self.data = scaler.transform(self.data) + if test_label_path: + label = pd.read_csv(test_label_path, index_col="MessageId") + self.label = label.values + self.is_submission = False + else: + self.is_submission = True + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + ids = self.ids[idx] + tensor = torch.from_numpy(self.data[idx]) + if self.is_submission: + return ids, tensor + class_id = torch.tensor(self.label[idx][-1]) + return ids, tensor, class_id diff --git a/tools/disassociability/visa-pets-FL/federated/DataPrepUtils.py b/tools/disassociability/visa-pets-FL/federated/DataPrepUtils.py new file mode 100644 index 0000000..b62f4ad --- /dev/null +++ b/tools/disassociability/visa-pets-FL/federated/DataPrepUtils.py @@ -0,0 +1,454 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +### Libraries for Data Handling + +from pathlib import Path +import numpy as np +import pandas as pd +import copy +import torch + + +pd.set_option("display.max_columns", None) + +### Libraries for Algorithms +from sklearn.model_selection import train_test_split, KFold, ShuffleSplit, StratifiedKFold, StratifiedShuffleSplit +from sklearn.model_selection import cross_val_score +from sklearn import metrics +from sklearn.metrics import classification_report, confusion_matrix, accuracy_score +from sklearn.preprocessing import StandardScaler, MinMaxScaler +import sklearn.utils +import pickle +import time + +RESAMPLE_RATIO = 10 + +def prepare_data_train(path, work_dir): + + # Load train/test data from csv + train = pd.read_csv(path) + train.to_csv(Path.joinpath(work_dir,'train_copy.csv'), index=False) + train["Timestamp"] = train["Timestamp"].astype("datetime64[ns]") + + train = train.set_index("MessageId") + train = train.sort_values(by=['Timestamp']) + + train['uniq'] = train['BeneficiaryAccount'] + train['Receiver'] + df4 = train[~train['uniq'].isin(train.loc[train['Label'].eq(0), 'uniq'])] + unknown_accounts = df4[['Receiver','BeneficiaryAccount']].drop_duplicates() + #print(len(df4)) + #unknown_accounts = train[['Receiver','BeneficiaryAccount']].drop_duplicates() + unknown_accounts.to_csv(Path.joinpath(work_dir, "unknown_accounts.csv"), index=False) + #train.drop(['uniq'], axis=1) + + + # Frequency for each sender + + senders = train["Sender"].unique() + + # Frequency for each receiver + receivers = train["Receiver"].unique() + + # If instructed/settled in the same currency + train["same_currency"] = train.apply(lambda row: int(row['InstructedCurrency']==row['SettlementCurrency']), axis=1) + + # Normalize the transaction amount by exchange rate + exchange_rate = {'GBP':1.22,'EUR':1.08, 'USD':1, 'JPY':0.0078, + 'CAD':0.75, 'INR':0.012, 'AUD':0.7, 'NZD':0.64, + 'DKK':0.14,'IDR':0.000066,'MXN':0.054,'ZAR':0.059, + 'KES':0.0081,'ILS':0.29,'THB':0.03,'TND':0.33, + 'TRY':0.053, 'SGD':0.76, 'MAD':0.098, 'AED':0.27, + 'SEK':0.096, 'CHF':1.08, 'SAR':0.27, 'PLN':0.23, + 'BHD':2.65, "CZK":0.045, "NAD":0.059, "NOK":0.10, + "HKD":0.13, "HUF":0.0027, "MYR":0.23, "LKR":0.0027, + "CNY":0.15, "EGP":0.034, "KRW":0.00081, "HRK":0.143122, + "BDT":0.0096, "PHP":0.018, "BOB":0.14, "RON":0.22, + "OMR":2.6, "KWD":3.28, "NPR":0.0076, "FJD":0.46, + "MUR":0.023, "JOD":1.41, "VND":0.000043, "ISK":0.0070, + "BRL":0.2, "TWD":0.033, "QAR":0.27, "XOF":0.0017, + "RSD":0.0092, "COP":0.00021, "BGN":0.55, "RUB":0.015, + "BWP":0.078, "TZS":0.00043, "BAM":0.55 + } + + + train["exchange_rate"] = train["InstructedCurrency"].map(exchange_rate) + train["NormalizedAmount"] = train["InstructedAmount"]*train["exchange_rate"] + train.drop(columns="exchange_rate", inplace=True) + + # Hour + train["hour"] = train["Timestamp"].dt.hour + train["day"] = train["Timestamp"].dt.day + + #train.drop(columns=["day", "hour"], inplace=True) + + + join_keys = ['MessageId', 'Timestamp'] + + start_time = time.time() + + keys = ["Sender", "Receiver", "InstructedCurrency", "SettlementCurrency"] + fields = ["InstructedAmount", "SettlementAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").max() + rename_dict = {item:item+"bank_pair_max" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + df = train.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").min() + rename_dict = {item:item+"bank_pair_min" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + df = train.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").mean() + rename_dict = {item:item+"bank_pair_mean" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + keys = ["OrderingName", "BeneficiaryName", "InstructedCurrency", "SettlementCurrency"] + fields = ["UETR", "Timestamp"] + df = train.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_avg_count" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + train["sr_currency_pair_avg_count"] = train["sr_currency_pair_avg_count"]/train["day"] + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + df = train.groupby(keys)[fields].rolling('1H', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_hour_freq" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + fields = ["SettlementAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1 , on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_settlement_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + fields = ["InstructedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_instructed_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["OrderingName", "BeneficiaryName"] + fields = ["UETR", "Timestamp"] + df = train.groupby(keys)[fields].rolling('7D', closed="left", on="Timestamp").count() + rename_dict = {item:item+"sr_pair_count_last_7d" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["OrderingName"] + fields = ["NormalizedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"sender_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + + keys = ["BeneficiaryName"] + fields = ["NormalizedAmount", "Timestamp"] + df = train.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"receiver_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + train = train.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + #print("Feature takes {}".format(time.time()-start_time)) + start_time=time.time() + + train = train.reset_index() + train = train.rename(columns={'index':'MessageId'}) + + print(train.columns[train.isna().any()].tolist()) + + train.drop(columns=["day", "hour"], inplace=True) + train = train.fillna(0) + # Exclude below categorical columns for training and testing + columns_to_drop = [ + "UETR", + "TransactionReference", + "OrderingName", + "OrderingStreet", + "OrderingCountryCityZip", + "BeneficiaryName", + "BeneficiaryStreet", + "BeneficiaryCountryCityZip", + "SettlementDate", + "SettlementCurrency", + "InstructedCurrency", + "Timestamp", + "uniq" + ] + + train = train.drop(columns_to_drop, axis=1) + + #print("train columns are", train.columns) + train[train.select_dtypes(include=['number']).columns] = train[train.select_dtypes(include=['number']).columns].clip(-1e20, 1e20) + + pd_train = train + pd_train['Label'].to_csv(Path.joinpath(work_dir,"train_label.csv")) + + from sklearn.utils import resample + pd_majority = pd_train[pd_train.Label==0] + pd_minority = pd_train[pd_train.Label==1] + + print(len(pd_majority), len(pd_minority)) + + pd_minority_upsampled = resample(pd_minority, + replace=True, + n_samples=int(len(pd_majority)/RESAMPLE_RATIO), + random_state=42) + + #print(len(pd_majority), len(pd_minority_upsampled)) + pd_train = pd.concat([pd_majority, pd_minority_upsampled]) + DOWNSAMPLE_FACTOR = 1 + + pd_train_subset = resample(pd_train, + replace=False, + n_samples=int(len(pd_train)/DOWNSAMPLE_FACTOR), + random_state=42) + + pd_train_temp = pd_train_subset + pd_train_temp = pd_train_temp.fillna(0) + columns_to_drop = ["Label"] + pd_train_temp[['MessageId', 'Label']].to_csv(Path.joinpath(work_dir,'train_label.csv'), index=False) + pd_train_temp.drop(columns=columns_to_drop, inplace=True) + pd_train_temp.to_csv(Path.joinpath(work_dir,'train_data_processor.csv'), index=False) + + + +def prepare_data_test(path, work_dir): + + TEST_MODE = True + test = pd.read_csv(path) + actual_test = copy.deepcopy(test) + + unknown_accounts = test[['Receiver','BeneficiaryAccount']].drop_duplicates() + unknown_accounts.to_csv(Path.joinpath(work_dir, "unknown_accounts.csv"), index=False) + + + train = pd.read_csv(Path.joinpath(work_dir,'train_copy.csv')) + test["Timestamp"] = test["Timestamp"].astype("datetime64[ns]") + train["Timestamp"] = train["Timestamp"].astype("datetime64[ns]") + actual_test["Timestamp"] = actual_test["Timestamp"].astype("datetime64[ns]") + if TEST_MODE: + test = pd.concat([train.drop(columns=["Label"]), test]) + else: + test = pd.concat([train, test]) + + #print(test.columns) + #print(test.index.is_unique) + #print(test.index) + #print(test.columns.is_unique) + test = test.set_index('MessageId') + print(test.index.is_unique) + test["Timestamp"] = test["Timestamp"].astype("datetime64[ns]") + + test = test.sort_values(by=['Timestamp']) + test["same_currency"] = test.apply(lambda row: int(row['InstructedCurrency']==row['SettlementCurrency']), axis=1) + + exchange_rate = {'GBP':1.22,'EUR':1.08, 'USD':1, 'JPY':0.0078, + 'CAD':0.75, 'INR':0.012, 'AUD':0.7, 'NZD':0.64, + 'DKK':0.14,'IDR':0.000066,'MXN':0.054,'ZAR':0.059, + 'KES':0.0081,'ILS':0.29,'THB':0.03,'TND':0.33, + 'TRY':0.053, 'SGD':0.76, 'MAD':0.098, 'AED':0.27, + 'SEK':0.096, 'CHF':1.08, 'SAR':0.27, 'PLN':0.23, + 'BHD':2.65, "CZK":0.045, "NAD":0.059, "NOK":0.10, + "HKD":0.13, "HUF":0.0027, "MYR":0.23, "LKR":0.0027, + "CNY":0.15, "EGP":0.034, "KRW":0.00081, "HRK":0.143122, + "BDT":0.0096, "PHP":0.018, "BOB":0.14, "RON":0.22, + "OMR":2.6, "KWD":3.28, "NPR":0.0076, "FJD":0.46, + "MUR":0.023, "JOD":1.41, "VND":0.000043, "ISK":0.0070, + "BRL":0.2, "TWD":0.033, "QAR":0.27, "XOF":0.0017, + "RSD":0.0092, "COP":0.00021, "BGN":0.55, "RUB":0.015, + "BWP":0.078, "TZS":0.00043, "BAM":0.55 + } + + test["exchange_rate"] = test["InstructedCurrency"].map(exchange_rate) + test["NormalizedAmount"] = test["InstructedAmount"]*test["exchange_rate"] + test.drop(columns="exchange_rate", inplace=True) + + test["hour"] = test["Timestamp"].dt.hour + test["day"] = test["Timestamp"].dt.day + + + join_keys = ['MessageId', 'Timestamp'] + + keys = ["Sender", "Receiver", "InstructedCurrency", "SettlementCurrency"] + fields = ["InstructedAmount", "SettlementAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").max() + rename_dict = {item:item+"bank_pair_max" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + df = test.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").min() + rename_dict = {item:item+"bank_pair_min" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + df = test.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").mean() + rename_dict = {item:item+"bank_pair_mean" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + keys = ["OrderingName", "BeneficiaryName", "InstructedCurrency", "SettlementCurrency"] + fields = ["UETR", "Timestamp"] + df = test.groupby(keys)[fields].rolling('28D', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_avg_count" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + test["sr_currency_pair_avg_count"] = test["sr_currency_pair_avg_count"]/test["day"] + + + df = test.groupby(keys)[fields].rolling('1H', closed="left", on="Timestamp").count() + rename_dict = {item:"sr_currency_pair_hour_freq" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + fields = ["SettlementAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_settlement_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + fields = ["InstructedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, on="Timestamp").mean() + rename_dict = {item:item+"sr_pair_avg_instructed_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + + keys = ["OrderingName", "BeneficiaryName"] + fields = ["UETR", "Timestamp"] + df = test.groupby(keys)[fields].rolling('7D', closed="left", on="Timestamp").count() + rename_dict = {item:item+"sr_pair_count_last_7d" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + keys = ["OrderingName"] + fields = ["NormalizedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"sender_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + keys = ["BeneficiaryName"] + fields = ["NormalizedAmount", "Timestamp"] + df = test.groupby(keys)[fields].rolling(20, min_periods=1, closed="left", on="Timestamp").mean() + rename_dict = {item:item+"receiver_avg_last_20" for item in fields if item not in ["Timestamp"]} + df = df.rename(rename_dict, axis="columns") + test = test.merge(df, left_on=join_keys, right_on=join_keys, how="left") + + test.drop(columns=["day", "hour"], inplace=True) + test = test.fillna(0) + + #print(test.columns[test.isna().any()].tolist()) + + test = test.reset_index() + test = test.rename(columns={'index':'MessageId'}) + test = actual_test[['MessageId']].merge(test, left_on=["MessageId"], right_on=["MessageId"], how="left") + + # Exclude below categorical columns for training and testing + columns_to_drop = [ + "UETR", + "TransactionReference", + "OrderingName", + "OrderingStreet", + "OrderingCountryCityZip", + "BeneficiaryName", + "BeneficiaryStreet", + "BeneficiaryCountryCityZip", + "SettlementDate", + "SettlementCurrency", + "InstructedCurrency", + "Timestamp" + ] + + test = test.drop(columns_to_drop, axis=1) + + #print("test columns:", test.columns) + test[test.select_dtypes(include=['number']).columns] = test[test.select_dtypes(include=['number']).columns].clip(-1e20, 1e20) + + #print(test.columns) + + from sklearn.utils import resample + DOWNSAMPLE_FACTOR = 1 + pd_test = test + pd_test = resample(pd_test, + replace=False, + n_samples=int(len(pd_test)/DOWNSAMPLE_FACTOR) + ) + pd_test = pd_test.fillna(0) + + if TEST_MODE: + pd_test.to_csv(Path.joinpath(work_dir, 'test_data_processor.csv'), index=False) + else: + columns_to_drop = ["Label"] + pd_test[['MessageId','Label']].to_csv(Path.joinpath(work_dir,'test_label.csv'), index=False) + pd_test.drop(columns=columns_to_drop, inplace=True) + pd_test.to_csv(Path.joinpath(work_dir,'test_data_processor.csv'), index=False) + + +def prepare_bank_data(bank_path, work_dir): + #bank_path, work_dir = str(bank_path), str(work_dir) + ### Bank Data + data_bank = pd.read_csv(bank_path) + + banks = data_bank.Bank.drop_duplicates().to_list() + #print("NUMBER OF BANKS: " + str(len(banks))) + #data_bank.columns + #data_bank.head(1) + + #BankFlagDict = {} + #for i in range(len(data_bank)): + # row = data_bank.loc[i] + # BankFlagDict[(row["Bank"], row["Account"])] = (row["Flags"]==0) + # + bankFlagDict = dict(zip(zip(data_bank.Bank, data_bank.Account), data_bank.Flags.eq(0))) + torch.save(bankFlagDict, Path.joinpath(work_dir, "bank_flag_dict")) + return banks + + +def explore_dataset(datapaths): + [train_path, test_path] = datapaths + train = pd.read_csv(train_path) + test = pd.read_csv(test_path) + print(train.columns, test.columns) + print(train["SettlementDate"].unique(), test["SettlementDate"].unique()) + print(train["InstructedCurrency"].unique(), test["InstructedCurrency"].unique()) + print(train["SettlementCurrency"].unique(), test["SettlementCurrency"].unique()) + lst1 = (train["Sender"]+train["OrderingAccount"]).unique().tolist() + lst2 = (test["Sender"]+test["OrderingAccount"]).unique().tolist() + print(len(lst1), len(lst2)) + lst_diff = list(set(lst2)-set(lst1)) + print(len(lst_diff)) + lst_diff = list(set(lst1)-set(lst2)) + print(len(lst_diff)) diff --git a/tools/disassociability/visa-pets-FL/federated/NeuralNet.py b/tools/disassociability/visa-pets-FL/federated/NeuralNet.py new file mode 100644 index 0000000..71033ec --- /dev/null +++ b/tools/disassociability/visa-pets-FL/federated/NeuralNet.py @@ -0,0 +1,28 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import torch +from torch import nn + +class NeuralNetwork(nn.Module): + def __init__(self, input_dim): + super(NeuralNetwork, self).__init__() + self.flatten = nn.Flatten() + self.linear_relu_stack = nn.Sequential( + nn.Linear(input_dim, 128), + #nn.ReLU(), + #nn.Linear(256, 64), + nn.ReLU(), + nn.Linear(128, 16), + nn.ReLU(), + nn.Linear(16, 2) + ) + + def forward(self, x): + x = self.flatten(x) + logits = self.linear_relu_stack(x) + return logits diff --git a/tools/disassociability/visa-pets-FL/federated/ot.py b/tools/disassociability/visa-pets-FL/federated/ot.py new file mode 100644 index 0000000..fc4e419 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/federated/ot.py @@ -0,0 +1,161 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +import ctypes as ct +from pathlib import Path +import sys +import torch + +clib_path = Path(sys.modules[__name__].__file__).parent.joinpath("libwrapper.so") +clib = ct.CDLL(str(clib_path)) + +def senderSetup1(stateDir, bankID): + #return 0, 0 + clib.senderSetup1.argtypes = [ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_int), ct.POINTER(ct.c_longlong)] + #clib.senderSetup1.restype = ct.c_char_p + clib.senderSetup1.restype = ct.POINTER(ct.c_char) + senderSetup1ByteSize = ct.c_int() + senderSetup1State = ct.c_longlong() + + senderSetup1Bytes = clib.senderSetup1(stateDir.encode('utf-8'), bankID.encode('utf-8'), ct.byref(senderSetup1ByteSize), ct.byref(senderSetup1State)) + + # ct.cast(senderSetup1ReturnValue , ct.) + # uchar_p = ct.cast(senderSetup1ReturnValue, ct.POINTER(ct.c_ubyte)) + #print(ct.string_at(senderSetup1ReturnValue, senderSetup1ReturnSize.value) , end='') + # print(bytes(uchar_p[:size]), end='') + + print(senderSetup1ByteSize.value) + p = ct.string_at(senderSetup1Bytes, senderSetup1ByteSize.value) + clib.deleteState(senderSetup1State) + return p, senderSetup1ByteSize.value + +def receiverSetup(stateDir, bankID, p, p_size): + #return 0, 0 + b_stateDir = stateDir.encode('utf-8') + b_bankID = bankID.encode('utf-8') + #### Receiver Setup1 starts here + clib.recverSetup.argtypes = [ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_char), ct.c_int, ct.POINTER(ct.c_int), ct.POINTER(ct.c_longlong)] + clib.recverSetup.restype = ct.POINTER(ct.c_char) + + recverSetupBytesSize = ct.c_int() + recverSetupState = ct.c_longlong() + + recverSetupBytes = clib.recverSetup(b_stateDir, b_bankID, p, p_size, ct.byref(recverSetupBytesSize), ct.byref(recverSetupState) ) + print(recverSetupBytesSize.value) + q = ct.string_at(recverSetupBytes, recverSetupBytesSize.value) + clib.deleteState(recverSetupState) + return q, recverSetupBytesSize.value + +def senderSetup2(stateDir, bankID, q, q_size): + #return 0, 0 + b_stateDir = stateDir.encode('utf-8') + b_bankID = bankID.encode('utf-8') + #### Sender Setup2 starts here + + clib.senderSetup2.argtypes = [ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_char), ct.c_int] + clib.senderSetup2( b_stateDir, b_bankID, q, q_size) + return + +def receiverGenKeys(stateDir, bankID, choices, accountIds, max_length): + #d = dict(zip(accountIds, choices)) + #torch.save(d, stateDir+"ot.pl") + #return 0, 0 + + b_stateDir = stateDir.encode('utf-8') + b_bankID = bankID.encode('utf-8') + b_bankHashTableFilePath = (stateDir + "/" + bankID).encode('utf-8') + #### Receiver passing the choices & generating the keys + clib.recverGenerateKeys.argtypes = [ct.c_char_p, ct.c_char_p,ct.c_char_p, ct.POINTER(ct.c_uint), ct.c_int, ct.POINTER(ct.c_int), ct.POINTER(ct.c_longlong), ct.c_char_p, ct.POINTER(ct.c_int), ct.c_int] + clib.recverGenerateKeys.restype = ct.POINTER(ct.c_char) + + + cChoices = (ct.c_uint * len(choices)) (*choices) + accountIdLengths = [len(accountId) for accountId in accountIds] + assert(max(accountIdLengths) <= max_length) + cAccountIdLengths = (ct.c_int * len(accountIdLengths)) (*accountIdLengths) + b_accountIds = "".join(accountIds).encode('utf-8') + + recverGenKeySize = ct.c_int() + recverGenKeyState = ct.c_longlong() + + cAccountIdLengths = (ct.c_int * len(accountIdLengths)) (*accountIdLengths) + recverGenKeyBytes = clib.recverGenerateKeys(b_stateDir, b_bankID, b_bankHashTableFilePath, cChoices, len(choices), ct.byref(recverGenKeySize), ct.byref(recverGenKeyState), b_accountIds, cAccountIdLengths , max_length) + r = ct.string_at(recverGenKeyBytes, recverGenKeySize.value) + clib.deleteState(recverGenKeyState) + return r, recverGenKeySize.value + +def senderGenKeys(stateDir, bankID, accountIds, r, r_size, max_length): + #return + b_stateDir = stateDir.encode('utf-8') + b_bankID = bankID.encode('utf-8') + b_hashTableFilePath = (stateDir+ "/PROCESSOR" + bankID).encode('utf-8') + + accountIdLengths = [len(accountId) for accountId in accountIds] + cAccountIdLengths = (ct.c_int * len(accountIdLengths)) (*accountIdLengths) + b_accountIds = "".join(accountIds).encode('utf-8') + + clib.senderGenerateKeys.argtypes = [ct.c_char_p, ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_char), ct.c_int, ct.c_int, ct.c_char_p, ct.POINTER(ct.c_int), ct.c_int] + clib.senderGenerateKeys( b_stateDir, b_bankID, b_hashTableFilePath, r, r_size, len(accountIds), b_accountIds, cAccountIdLengths, max_length) + return + +def senderEncrypt(accountIds, g0, g1, stateDir: str, partitionID, gradient_length, max_length): + #return g0, g1 + + b_hashTableFilePath = (stateDir + "/PROCESSOR" + partitionID).encode('utf-8') + assert(g0.shape == g1.shape) + samples, grad_len = g0.shape + assert(grad_len == gradient_length) + assert(samples == len(accountIds)) + + encryptedZeroGradient = torch.zeros( len(accountIds) * (gradient_length+4)) + encryptedOneGradient = torch.zeros( len(accountIds) * (gradient_length+4)) + g0 = torch.flatten(g0) + g1 = torch.flatten(g1) + print(g0.shape) + + g0.to(torch.int32) + g1.to(torch.int32) + encryptedZeroGradient = encryptedZeroGradient.to(torch.int32) + encryptedOneGradient = encryptedOneGradient.to(torch.int32) + + accountIds = [accountId[0] + accountId[1] for accountId in accountIds] + accountIdLengths = [len(accountId) for accountId in accountIds] + b_accountIds = "".join(accountIds).encode('utf-8') + cAccountIdLengths = (ct.c_int * len(accountIdLengths)) (*accountIdLengths) + + + + clib.encrypt.argtypes = [ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_int), ct.c_int, ct.c_int, ct.c_longlong, ct.c_longlong, ct.c_int, ct.c_longlong, ct.c_longlong] + clib.encrypt(b_hashTableFilePath, b_accountIds, cAccountIdLengths, max_length, len(accountIds), g0.data_ptr(), g1.data_ptr(), gradient_length, encryptedZeroGradient.data_ptr(), encryptedOneGradient.data_ptr()) + shape = (len(accountIds), gradient_length+4) + return torch.reshape(encryptedZeroGradient, shape), torch.reshape(encryptedOneGradient, shape) + +def receiverDecrypt(accountIds, enc0, enc1, stateDir: str, partitionID, gradient_length, choices, max_length): + #accountIds = [accountId[0] + accountId[1] for accountId in accountIds] + #d = torch.load(stateDir+"ot.pl") + #unencryptedGradient = [enc0[i] if d[accountIds[i]] == 0 else enc1[i] for i in range(len(accountIds))] + #unencryptedGradient = torch.stack(unencryptedGradient, axis=0) + #return unencryptedGradient + + + b_bankHashTableFilePath = (stateDir + "/" + partitionID).encode('utf-8') + + accountIds = [accountId[0] + accountId[1] for accountId in accountIds] + accountIdLengths = [len(accountId) for accountId in accountIds] + b_accountIds = "".join(accountIds).encode('utf-8') + cAccountIdLengths = (ct.c_int * len(accountIdLengths)) (*accountIdLengths) + cChoices = (ct.c_uint * len(choices)) (*choices) + unencryptedGradient = torch.zeros( len(accountIds) * gradient_length) + unencryptedGradient = unencryptedGradient.to(torch.int32) + enc0 = torch.flatten(enc0) + enc1 = torch.flatten(enc1) + + clib.decrypt.argtypes = [ct.c_char_p, ct.c_char_p, ct.POINTER(ct.c_int), ct.c_int, ct.c_int, ct.c_longlong, ct.c_longlong, ct.c_int, ct.c_longlong, ct.POINTER(ct.c_uint)] + clib.decrypt(b_bankHashTableFilePath, b_accountIds, cAccountIdLengths, max_length, len(accountIds), enc0.data_ptr(), enc1.data_ptr(), gradient_length, unencryptedGradient.data_ptr(), cChoices ) + + unencryptedGradient = torch.reshape(unencryptedGradient, (len(accountIds), gradient_length)) + return unencryptedGradient diff --git a/tools/disassociability/visa-pets-FL/federated/solution_federated.py b/tools/disassociability/visa-pets-FL/federated/solution_federated.py new file mode 100644 index 0000000..050f02b --- /dev/null +++ b/tools/disassociability/visa-pets-FL/federated/solution_federated.py @@ -0,0 +1,830 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +from pathlib import Path +from typing import Dict, List, Optional, Tuple + +import flwr as fl +from flwr.common import FitIns, FitRes, Parameters +from flwr.server import ClientManager +from flwr.server.client_proxy import ClientProxy +from loguru import logger +import numpy as np +import pandas as pd +import pickle +import itertools +import traceback +import torch +import os +from torch.utils.data import DataLoader +from hashlib import sha256 +import torch.nn.functional as F + +from .DataPrepUtils import ( + prepare_data_train, + prepare_data_test, + prepare_bank_data +) + +from .DataObjects import ( + CustomDatasetTest, + CustomDatasetTrain +) + +from .DNN import * + +from .ot import ( + senderSetup1, + receiverSetup, + senderSetup2, + receiverGenKeys, + senderGenKeys, + senderEncrypt, + receiverDecrypt +) + +STAGE_UPCYCLE = 1 +STAGE_DOWNCYCLE = 2 +STAGE_SENDING = 3 +STAGE_WAIT_FOR_GRADIENT = 4 +STAGE_FINISHED_SENDING = 5 +STAGE_DONE = 6 + +SAMPLES_PER_ROUND_LIMIT = 2400 +DEFAULT_FLAG = 0 + +TRAIN_ROUNDS = 250 #350 +TEST_ROUNDS = 2 + +NOISE_MEAN = 0 +NOISE_SCALE = .1 +NOISE_MULTIPLIER = 1e3 + +DEVICE = "cpu" +#DEVICE = "cuda" +TRAIN_BATCH_SIZE = 8192*10 + +def empty_parameters() -> Parameters: + """Utility function that generates empty Flower Parameters dataclass instance.""" + return fl.common.ndarrays_to_parameters([]) + + +def df_to_ndarrays( + df: pd.DataFrame, labels: bool = True +) -> List[np.ndarray]: + """Utility function that converts a pandas DataFrame of data to a list of + numpy arrays, which the expected format for communication between a Flower + NumPyClient and a Flower Strategy.""" + cols = [ + "FinalReceiver", + "BeneficiaryAccount", + ] + if labels: + cols.append("Label") + # Need .astype("U") because pandas uses 'object' for string columns by default + # 'object' arrays are not serializable without pickle. + return [ + # Index + df.index.values.astype("U"), + # Transactions: Join keys and label + df[cols].values.astype("U"), + ] + + +def ndarrays_to_df( + index: np.ndarray, transactions: np.ndarray, labels: bool = True +) -> pd.DataFrame: + """Utility function that converts a list of numpy arrays, which the expected format + for communication between a Flower NumPyClient and a Flower Strategy, back to a + pandas DataFrame""" + cols = [ + "FinalReceiver", + "BeneficiaryAccount", + ] + if labels: + cols.append("Label") + return pd.DataFrame( + data=transactions, + index=pd.Series(index, name="MessageId"), + columns=cols, + ) + +def checkTensor(tensor): + return (tensor[0], tensor[1], tensor[-1], tensor[-2]) + +class TrainingClient(fl.client.NumPyClient): + def __init__( + self, cid: str, data_path: Path, client_dir: Path + ): + super().__init__() + self.cid = cid + self.data_path = data_path + self.client_dir = client_dir + self.ot_dir = Path.joinpath(self.client_dir, "ot") + if not os.path.isdir(self.ot_dir): + os.mkdir(self.ot_dir) + + def init_stage(self, n_batches): + stage = STAGE_UPCYCLE + batch_index = 0 + epoch_index = 0 + d = {"stage": stage, "batch": batch_index, "epoch": epoch_index, "n": n_batches} + torch.save(d, Path.joinpath(self.client_dir, "stage.pl")) + + def load_stage(self): + d = torch.load(Path.joinpath(self.client_dir, "stage.pl")) + return (d["stage"], d["batch"], d["epoch"], d["n"]) + + def save_stage(self, stage, batch_index, epoch_index, n_batches): + d = {"stage": stage, "batch": batch_index, "epoch": epoch_index, "n": n_batches} + torch.save(d, Path.joinpath(self.client_dir, "stage.pl")) + + def fit(self, parameters: List[np.ndarray], config: dict): + round = config["round"] + #logger.info("CID: {}, ROUND: {}", self.cid, round) + metrics = {} + try: + if round == 1: + prepare_data_train(self.data_path, self.client_dir) + train_data_path = Path.joinpath(self.client_dir, 'train_data_processor.csv') + train_label_path = Path.joinpath(self.client_dir, 'train_label.csv') + scaler_path = Path.joinpath(self.client_dir, "scaler") + dataset_train = CustomDatasetTrain([train_data_path, train_label_path, scaler_path]) + train_dataloader = DataLoader(dataset_train, batch_size=TRAIN_BATCH_SIZE, shuffle=True) + print("Done! Loading Payment Processo Training") + for batch, (idx, X, y) in enumerate(train_dataloader): + torch.save({"idx":idx, "X":X, "y":y}, Path.joinpath(self.client_dir, str(batch))) + n_batches = len(train_dataloader) + self.init_stage(n_batches) + return [], 1, metrics + elif round == 2: + partitions:Dict = pickle.loads(config["banks"]) + bank_dict = {} + partition_ids = [] + setups = {} + for partition, banks in partitions.items(): + partition_ids.append(partition) + setups[partition] = senderSetup1(str(self.ot_dir), partition) + bank_dict.update(zip(banks, itertools.repeat(partition))) + torch.save(partition_ids, Path.joinpath(self.client_dir, "pids.pl")) + torch.save(bank_dict, Path.joinpath(self.client_dir, "bank_dict.pl")) + unknown_accounts = pd.read_csv(Path.joinpath(self.client_dir, "unknown_accounts.csv")) + maxAccountLen = max((unknown_accounts["Receiver"] + unknown_accounts["BeneficiaryAccount"]).str.len()) + #logger.info("MAX ACCOUNT LEN: {}", maxAccountLen) + torch.save(maxAccountLen, Path.joinpath(self.client_dir, "max.pl")) + unknown_accounts['partition'] = unknown_accounts['Receiver'].map(bank_dict) + #logger.info(unknown_accounts.head()) + accounts = {k: zip(df["Receiver"], df["BeneficiaryAccount"]) for k, df in unknown_accounts.groupby("partition")} + for partition_id in partition_ids: + if(accounts.get(partition_id)): + metrics[partition_id] = pickle.dumps((setups[partition_id][0], setups[partition_id][1], accounts.get(partition_id), maxAccountLen)) + for account in accounts.keys(): + accounts[account] = set(accounts[account]) + torch.save(accounts, Path.joinpath(self.client_dir, "unknown_accounts.pl")) + + return [], 1, metrics + elif round == 3: + return [], 1, {} + elif round == 4: + partition_ids = torch.load(Path.joinpath(self.client_dir, "pids.pl")) + accounts = torch.load(Path.joinpath(self.client_dir, "unknown_accounts.pl")) + maxAccountLen = torch.load(Path.joinpath(self.client_dir, "max.pl")) + + for partition_id in partition_ids: + if(config.get(partition_id)): + q, q_size, r, r_size = pickle.loads(config[partition_id]) + senderSetup2(str(self.ot_dir), partition_id, q, q_size) + acc = sorted(accounts[partition_id]) + acc = [a[0] + a[1] for a in acc] + + senderGenKeys(str(self.ot_dir), partition_id, acc, r, r_size, maxAccountLen) + + stage, batch_index, epoch_index, n_batches = self.load_stage() + self.n_batches = n_batches + if stage == STAGE_WAIT_FOR_GRADIENT: + stage = STAGE_DOWNCYCLE + if stage == STAGE_FINISHED_SENDING: + stage = STAGE_WAIT_FOR_GRADIENT + if stage == STAGE_DOWNCYCLE: + noisy_gradient = pickle.loads(config["noisy_gradient"]) + self.train_down_cycle(noisy_gradient, batch_index, epoch_index) + batch_index += 1 + if batch_index >= n_batches: + batch_index = 0 + epoch_index += 1 + stage = STAGE_UPCYCLE + if stage == STAGE_UPCYCLE: + self.train_up_cycle(batch_index, epoch_index) + stage = STAGE_SENDING + if stage == STAGE_SENDING: + finished, can_send = self.send() + if finished: + stage = STAGE_FINISHED_SENDING + else: + stage = STAGE_SENDING + for p in can_send: + metrics[p] = pickle.dumps(can_send[p]) + metrics["stage"] = stage + self.save_stage(stage, batch_index, epoch_index, n_batches) + return [], 1, metrics + + except Exception: + traceback.print_exc() + + return [], 1, {} + + def send(self): + partition_results: dict = torch.load(Path.joinpath(self.client_dir, "tosend.pl")) + can_send = {} + sending = 0 + finished = True + to_pop = [] + for p in partition_results.keys(): + ids, g0enc, g1enc, grad_len = partition_results[p] + if(len(ids) > SAMPLES_PER_ROUND_LIMIT - sending): + sendingIds = ids[:SAMPLES_PER_ROUND_LIMIT - sending] + sending0 = g0enc[:SAMPLES_PER_ROUND_LIMIT - sending] + sending1 = g1enc[:SAMPLES_PER_ROUND_LIMIT - sending] + ids = ids[SAMPLES_PER_ROUND_LIMIT - sending:] + g0enc = g0enc[SAMPLES_PER_ROUND_LIMIT - sending:] + g1enc = g1enc[SAMPLES_PER_ROUND_LIMIT - sending:] + partition_results[p] = (ids, g0enc, g1enc, grad_len) + can_send[p] = (sendingIds, sending0, sending1, grad_len) + finished = False + break + sending += len(ids) + can_send[p] = (ids, g0enc, g1enc, grad_len) + to_pop.append(p) + for p in to_pop: + partition_results.pop(p) + if not finished: + torch.save(partition_results, Path.joinpath(self.client_dir, "tosend.pl")) + return finished, can_send + + + + def train_up_cycle(self, + i_batch=0, + i_epoch=0, + input_dim=18): + PATH = str(self.client_dir)+'tmp' + #TODO: Load partition_ids and bank_dict + model, optimizer, loss_fn = init_learning(device=DEVICE, batch_size=TRAIN_BATCH_SIZE, i_epoch=i_epoch, INPUT_DIM=input_dim, model=None) + if i_batch != 0 or i_epoch != 0: + checkpoint = torch.load(PATH) + model.load_state_dict(checkpoint["model_state_dict"]) + model = model.to(DEVICE) + model.train() + data = torch.load(Path.joinpath(self.client_dir, str(i_batch))) + idx, X, y = data["idx"], data["X"], data["y"] + batch_size = (X.shape)[0] + X0, X1 = pad_flags(X, 0), pad_flags(X, 1) + X = torch.concat([X0, X1], axis=0) + y = torch.concat([y, y], axis=0) + X, y = X.to(DEVICE), y.to(DEVICE) + pred = model(X.float()) + loss = loss_fn(pred, y) + + optimizer.zero_grad() + loss.backward() + + # Flatten the gradients + grads_flat, param_shapes = flatten_grads(model) + + # Norm clipping + MAX_NORM = 1000 + grads_flat = norm_clipping(grads_flat, max_norm=MAX_NORM) + + # Prepare for serialization + g0, g1, eps = prepare_serialization(grads_flat, device=DEVICE) + eps_sum = torch.sum(eps, axis=0, dtype=torch.int32) + #g0, g1 = g0.to("cpu"), g1.to("cpu") # May need to convert to cpu tensor for serialization + data_ptr0, data_ptr1 = g0.data_ptr, g1.data_ptr + + bid_uid_lst = [item for item in zip(idx[1], idx[3])] + #logger.info("SAMPLES TO ENC: {}", len(bid_uid_lst)) + partition_bid_uids = {} + partition_grads0 = {} + partition_grads1 = {} + partition_results = {} + partition_ids = torch.load(Path.joinpath(self.client_dir, "pids.pl")) + bank_dict = torch.load(Path.joinpath(self.client_dir, "bank_dict.pl")) + maxAccountLen = torch.load(Path.joinpath(self.client_dir, "max.pl")) + + for partition_id in partition_ids: + partition_bid_uids[partition_id] = [] + partition_grads0[partition_id] = [] + partition_grads1[partition_id] = [] + + unknown_accounts = torch.load(Path.joinpath(self.client_dir, "unknown_accounts.pl")) + + skipped = 0 + for i, id in enumerate(bid_uid_lst): + p = bank_dict[id[0]] + if id not in unknown_accounts[p]: + eps_sum = torch.subtract(eps_sum, g0[i]) + skipped += 1 + continue + partition_bid_uids[p].append(id) + partition_grads0[p].append(g0[i]) + partition_grads1[p].append(g1[i]) + grad_len = len(g0[0]) + #logger.info("GRADIENT LENGTH: {}", grad_len) + #logger.info("ABLE TO SKIP {} SAMPLES", skipped) + + for partition_id in partition_ids: + if(len(partition_grads0[partition_id]) == 0): + continue + #logger.info("PARTITION {}: {} SAMPLES", partition_id, len(partition_grads0[partition_id])) + g0 = torch.stack(partition_grads0[partition_id], axis=0) + g1 = torch.stack(partition_grads1[partition_id], axis=0) + ids = partition_bid_uids[partition_id] + #logger.info("SHAPE: {}", g0.shape) + + #logger.info("PARTITION: {}, ID: {}, G0: {}, G1: {}", partition_id, ids[0], g0[0], g1[0]) + g0enc, g1enc = senderEncrypt(ids, g0, g1, str(self.ot_dir), partition_id, grad_len, maxAccountLen) + partition_results[partition_id] = (ids, g0enc, g1enc, grad_len) + + torch.save(partition_results, Path.joinpath(self.client_dir, "tosend.pl")) + torch.save({"model_state_dict": model.state_dict(), + "optimizer_state_dict": optimizer.state_dict(), + "loss": loss, + "grads_flat": grads_flat, + "eps_sum": eps_sum, + "param_shapes": param_shapes + }, PATH) + return + + def train_down_cycle(self, noisy_gradient, i_batch=0, i_epoch=0, + input_dim=18): + + model, optimizer, loss_fn = init_learning(i_epoch=i_epoch, INPUT_DIM=input_dim, device=DEVICE, batch_size=TRAIN_BATCH_SIZE) + PATH = str(self.client_dir)+'tmp' + checkpoint = torch.load(PATH) + model.load_state_dict(checkpoint["model_state_dict"]) + optimizer.load_state_dict(checkpoint["optimizer_state_dict"]) + loss = checkpoint["loss"] + grads_flat = checkpoint["grads_flat"].to(DEVICE) + eps_sum = checkpoint["eps_sum"].to(torch.int32) + param_shapes = checkpoint["param_shapes"] + model = model.to(DEVICE) + model.train() + #end_time_load = time.time() + #print("Save/load for one batch takes {}".format(end_time_load-start_time_save)) + + # Get the gradients from Aggregator + #grads_flat = get_aggregated_grads(g0.to(device)) + #grads_flat = grads_flat.to(device) + + # Deserialize the grads for backprop + grads_flat = complete_deserialization(noisy_gradient, eps_sum) + batch_size = TRAIN_BATCH_SIZE + grads_flat /= batch_size + + # Reshape the flattened per-sample gradient back to gradient matrices. + expand_flattened_grads(model, grads_flat, param_shapes) + + optimizer.step() + clear_per_sample_grad(model) + + loss = loss.item() + logger.info("LOSS AND CURRENT BATCH: {} {}/{}, EPOCH {}", loss, i_batch, self.n_batches, i_epoch) + + torch.save({"model_state_dict":model.state_dict()}, PATH) + + + +class TrainingBankClient(fl.client.NumPyClient): + def __init__( + self, cid: str, data_path: Path, client_dir: Path + ): + super().__init__() + self.cid = cid + self.data_path = data_path + self.client_dir = client_dir + self.ot_dir = Path.joinpath(client_dir, "ot") + if not os.path.isdir(self.ot_dir): + os.mkdir(self.ot_dir) + + def fit( + self, parameters: List[np.ndarray], config: dict + ) -> Tuple[List[np.ndarray], int, dict]: + round = config["round"] + logger.info("CID: {}, ROUND: {}", self.cid, round) + metrics = {} + try: + # Round 1: Send banks in this partition to strategy + if round == 1: + bank_ids = prepare_bank_data(self.data_path, self.client_dir) + #logger.info("{} has {} banks", self.cid, len(bank_ids)) + metrics["banks"] = pickle.dumps(bank_ids) + return [], 1, metrics + elif round == 3: + if config.get("setup"): + p, p_len, unknown_accounts, maxAccountLen = pickle.loads(config["setup"]) + q, q_size = receiverSetup(str(self.ot_dir), self.cid, p, p_len) + bank_dict = torch.load(Path.joinpath(self.client_dir, "bank_flag_dict")) + unknown_accounts = sorted(set(unknown_accounts)) + choices = [bank_dict.get(a, 0) for a in unknown_accounts] + unknown_accounts = [a[0] + a[1] for a in unknown_accounts] + #logger.info("{} ACC HASH: {}", self.cid, sha256(''.join(unknown_accounts).encode("utf-8")).digest()) + r, r_size = receiverGenKeys(str(self.ot_dir), self.cid, choices, unknown_accounts, maxAccountLen) + torch.save(maxAccountLen, Path.joinpath(self.client_dir, "max.pl")) + metrics["qr"] = pickle.dumps((q, q_size, r, r_size)) + return [], 1, metrics + else: + if config.get("grads"): + ids, g0enc, g1enc, grad_len = pickle.loads(config["grads"]) + bank_dict = torch.load(Path.joinpath(self.client_dir, "bank_flag_dict")) + maxAccountLen = torch.load(Path.joinpath(self.client_dir, "max.pl")) + choices = [bank_dict.get((x, y), 0) for (x, y) in ids] + #logger.info(len(choices)) + #logger.info("SHAPE: ", g0enc.shape) + grad = receiverDecrypt(ids, g0enc, g1enc, str(self.ot_dir), self.cid, grad_len, choices, maxAccountLen) + #logger.info("PARTITION: {}, ID: {}, HASH: {}, CHOICE: {}", self.cid, ids[0], grad[0], choices[0]) + grad = torch.sum(grad, dim=0) + metrics["grad"] = pickle.dumps(grad) + return [], 1, metrics + + except Exception: + traceback.print_exc() + return [], 1, {} + +def train_client_factory(cid, data_path: Path, client_dir: Path): + if cid == "swift": + return TrainingClient( + cid, data_path=data_path, client_dir=client_dir + ) + else: + return TrainingBankClient( + cid, data_path=data_path, client_dir=client_dir + ) + + +class TrainStrategy(fl.server.strategy.Strategy): + def __init__(self, server_dir: Path): + self.server_dir = server_dir + self.labels_for_banks = None + self.banks_dict = {} + self.processor_param_dict = {} + self.processor_config_dict = {} + self.bank_config_dict = {} + self.banks = [] + self.current_grad = None + super().__init__() + + def initialize_parameters(self, client_manager: ClientManager) -> Parameters: + """Do nothing. Return empty Flower Parameters dataclass.""" + client_dict: Dict[str, ClientProxy] = client_manager.all() + self.banks = [cid for cid in client_dict.keys() if cid != "swift"] + return empty_parameters() + + def configure_fit( + self, server_round: int, parameters: Parameters, client_manager: ClientManager + ) -> List[Tuple[ClientProxy, FitIns]]: + client_dict: Dict[str, ClientProxy] = client_manager.all() + config_dict = {"round": server_round} + fit_config: List[Tuple[ClientProxy, FitIns]] = [] + processor_config = dict(config_dict) + processor_parameters = empty_parameters() + bank_cids = [cid for cid in client_dict.keys() if cid != "swift"] + bank_parameters = {} + bank_config = {} + for cid in bank_cids: + bank_parameters[cid] = empty_parameters() + bank_config[cid] = dict(config_dict) + if server_round == 1: + pass + elif server_round == 2: + processor_config["banks"] = pickle.dumps(self.bank_config_dict) + elif server_round == 3: + for cid in bank_cids: + bank_parameters[cid] = self.processor_param_dict.get(cid, empty_parameters()) + bank_config[cid].update(self.processor_config_dict.get(cid, {})) + elif server_round == 4: + processor_config.update(self.bank_config_dict) + else: + if self.stage == STAGE_WAIT_FOR_GRADIENT: + noise = np.random.laplace(NOISE_MEAN, NOISE_SCALE, self.current_grad.shape) + self.current_grad = self.current_grad.add(torch.multiply(torch.tensor(noise), NOISE_MULTIPLIER).int()) + processor_config["noisy_gradient"] = pickle.dumps(self.current_grad) + self.current_grad = None + if self.stage == STAGE_SENDING or self.stage == STAGE_FINISHED_SENDING: + for cid in bank_cids: + bank_config[cid].update(self.processor_config_dict.get(cid, {})) + + processor_fit_ins = FitIns(parameters=processor_parameters, config=processor_config) + fit_config += [(client_dict["swift"], processor_fit_ins)] + for cid in bank_cids: + this_bank_fit_ins = FitIns( + parameters=bank_parameters[cid], + config=bank_config[cid], + ) + fit_config.append((client_dict[cid], this_bank_fit_ins)) + return fit_config + + def aggregate_fit( + self, server_round: int, results: List[Tuple[ClientProxy, FitRes]], failures + ) -> Tuple[Optional[Parameters], dict]: + if (n_failures := len(failures)) > 0: + raise Exception(f"Had {n_failures} failures in round {server_round}") + self.processor_config_dict = {} + self.processor_param_dict = {} + self.bank_config_dict = {} + if server_round == 1: + for client, result in results: + result_ndarrays = fl.common.parameters_to_ndarrays(result.parameters) + if client.cid != "swift": + self.bank_config_dict[client.cid] = pickle.loads(result.metrics["banks"]) + elif server_round == 2: + for client, result in results: + result_ndarrays = fl.common.parameters_to_ndarrays(result.parameters) + if client.cid == "swift": + for cid in self.banks: + if result.metrics.get(cid): + self.processor_config_dict[cid] = {} + self.processor_config_dict[cid]["setup"] = result.metrics.get(cid) + elif server_round == 3: + for client, result in results: + result_ndarrays = fl.common.parameters_to_ndarrays(result.parameters) + if client.cid != "swift": + if result.metrics.get("qr"): + self.bank_config_dict[client.cid] = result.metrics["qr"] + else: + for client, result in results: + result_ndarrays = fl.common.parameters_to_ndarrays(result.parameters) + if client.cid == "swift": + self.stage = result.metrics["stage"] + if self.stage == STAGE_SENDING or self.stage == STAGE_FINISHED_SENDING: + for cid in self.banks: + if result.metrics.get(cid): + self.processor_config_dict[cid] = {} + self.processor_config_dict[cid]["grads"] = result.metrics.get(cid) + else: + if result.metrics.get("grad"): + if self.current_grad == None: + self.current_grad = pickle.loads(result.metrics["grad"]) + else: + self.current_grad = torch.add(self.current_grad, pickle.loads(result.metrics["grad"])) + + return None, {} + + def configure_evaluate(self, server_round, parameters, client_manager): + """Not running any federated evaluation.""" + return [] + + def aggregate_evaluate(self, server_round, results, failures): + """Not aggregating any evaluation.""" + return None + + def evaluate(self, server_round, parameters): + """Not running any centralized evaluation.""" + return None + + +def train_strategy_factory(server_dir: Path): + training_strategy = TrainStrategy(server_dir=server_dir) + num_rounds = TRAIN_ROUNDS + return training_strategy, num_rounds + +def test_client_factory( + cid: str, + data_path: Path, + client_dir: Path, + preds_format_path: Path, + preds_dest_path: Path, +): + if cid == "swift": + return TestProcessorClient( + cid, + data_path=data_path, + client_dir=client_dir, + preds_format_path=preds_format_path, + preds_dest_path=preds_dest_path, + ) + else: + #logger.info("Initializing bank client for {}", cid) + return TestBankClient(cid, data_path=data_path, client_dir=client_dir) + + +class TestProcessorClient(fl.client.NumPyClient): + def __init__( + self, + cid: str, + data_path: Path, + client_dir: Path, + preds_format_path: Path, + preds_dest_path: Path, + ): + super().__init__() + self.cid = cid + self.data_path = data_path + self.client_dir = client_dir + self.preds_format_path = preds_format_path + self.preds_dest_path = preds_dest_path + self.ot_dir = Path.joinpath(client_dir, "ot_test") + if not os.path.isdir(self.ot_dir): + os.mkdir(self.ot_dir) + + def init_stage(self): + stage = STAGE_UPCYCLE + d = {"stage": stage} + torch.save(d, Path.joinpath(self.client_dir, "stage.pl")) + + def load_stage(self): + d = torch.load(Path.joinpath(self.client_dir, "stage.pl")) + return d["stage"] + + def save_stage(self, stage): + d = {"stage": stage} + torch.save(d, Path.joinpath(self.client_dir, "stage.pl")) + + def fit( + self, parameters: List[np.ndarray], config: dict + ) -> Tuple[List[np.ndarray], int, dict]: + round = config["round"] + #logger.info("CID: {}, ROUND: {}", self.cid, round) + metrics = {} + try: + if round == 1: + prepare_data_test(self.data_path, self.client_dir) + return [], 1, metrics + elif round == 2: + flags = pickle.loads(config["flags"]) + self.test_up(flags) + return [], 1, metrics + + except Exception: + traceback.print_exc() + + return [], 1, {} + + + def test_up(self, flags): + test_data_path = Path.joinpath(self.client_dir, 'test_data_processor.csv') + scaler_path = Path.joinpath(self.client_dir, "scaler") + dataset_test = CustomDatasetTest([test_data_path, None, scaler_path]) + + model = GradSampleModule(NeuralNetwork(18).to(DEVICE), force_functorch=True) + PATH = str(self.client_dir)+'tmp' + checkpoint = torch.load(PATH) + model.load_state_dict(checkpoint["model_state_dict"]) + model = model.to(DEVICE) + model.eval() + idx, X = dataset_test[:] + #logger.info("SOME IDS: {}", idx[:5]) + with torch.no_grad(): + batch_size = (X.shape)[0] + #X = get_flags(idx, X, bank_flag_dict) + X0, X1 = pad_flags(X, 0), pad_flags(X, 1) + X = torch.concat([X0, X1], axis=0) + X = X.to(DEVICE) + pred = model(X.float()) + + s = (pred.shape)[0]//2 + pred0, pred1 = pred[:s, :], pred[s:, :] + + bid_uid_lst = [(item[1], item[3]) for item in idx] + + missed = 0 + chosen = [] + for i, id in enumerate(bid_uid_lst): + flag = flags.get(id, -1) + if(flag == -1): + missed += 1 + flag = 0 + if flag == 0: + p = pred0[i] + else: + p = pred1[i] + p = F.softmax(p, dim=0) + chosen.append(p) + + probs = torch.stack(chosen, axis=0) + logger.info("PROBS SHAPE: {}", probs.shape) + logger.info("MISSED: {}", missed) + + test_data_path = Path.joinpath(self.client_dir, 'test_data_processor.csv') + raw_data = pd.read_csv(test_data_path, usecols=["MessageId"]) + raw_data["Score"] = probs[:, 1].numpy().tolist() + + res = pd.read_csv(self.preds_format_path) + res = res.merge(raw_data, on="MessageId", how="left") + res = res.rename(columns={"Score_y":"Score"}) + res[["MessageId", "Score"]].to_csv(self.preds_dest_path, index=False) + + return + + + +class TestBankClient(fl.client.NumPyClient): + def __init__(self, cid, data_path: Path, client_dir: Path): + super().__init__() + self.cid = cid + self.data_path = data_path + self.client_dir = client_dir + self.ot_dir = Path.joinpath(client_dir, "ot_test") + if not os.path.isdir(self.ot_dir): + os.mkdir(self.ot_dir) + + def fit( + self, parameters: List[np.ndarray], config: dict + ) -> Tuple[List[np.ndarray], int, dict]: + ## Round 1: Send banks in this partition to strategy + round = config["round"] + #logger.info("CID: {}, ROUND: {}", self.cid, round) + metrics = {} + try: + # Round 1: Send banks in this partition to strategy + if round == 1: + bank_ids = prepare_bank_data(self.data_path, self.client_dir) + flagDict = torch.load(Path.joinpath(self.client_dir, "bank_flag_dict")) + metrics["flags"] = pickle.dumps(flagDict) + return [], 1, metrics + except Exception: + traceback.print_exc() + return [], 1, {} + +def test_strategy_factory(server_dir: Path): + test_strategy = TestStrategy(server_dir=server_dir) + num_rounds = TEST_ROUNDS + return test_strategy, num_rounds + + +class TestStrategy(fl.server.strategy.Strategy): + def __init__(self, server_dir: Path): + self.server_dir = server_dir + self.labels_for_banks = None + self.banks_dict = {} + self.processor_param_dict = {} + self.processor_config_dict = {} + self.bank_config_dict = {} + self.banks = [] + self.indexes = [] + self.preds = [] + self.flags = {} + super().__init__() + + def initialize_parameters(self, client_manager: ClientManager) -> Parameters: + """Do nothing. Return empty Flower Parameters dataclass.""" + client_dict: Dict[str, ClientProxy] = client_manager.all() + self.banks = [cid for cid in client_dict.keys() if cid != "swift"] + return empty_parameters() + + def configure_fit( + self, server_round: int, parameters: Parameters, client_manager: ClientManager + ) -> List[Tuple[ClientProxy, FitIns]]: + client_dict: Dict[str, ClientProxy] = client_manager.all() + config_dict = {"round": server_round} + fit_config: List[Tuple[ClientProxy, FitIns]] = [] + processor_config = dict(config_dict) + processor_parameters = empty_parameters() + bank_cids = [cid for cid in client_dict.keys() if cid != "swift"] + bank_parameters = {} + bank_config = {} + for cid in bank_cids: + bank_parameters[cid] = empty_parameters() + bank_config[cid] = dict(config_dict) + if server_round == 1: + pass + elif server_round == 2: + processor_config["flags"] = pickle.dumps(self.flags) + + processor_fit_ins = FitIns(parameters=processor_parameters, config=processor_config) + fit_config += [(client_dict["swift"], processor_fit_ins)] + for cid in bank_cids: + this_bank_fit_ins = FitIns( + parameters=bank_parameters[cid], + config=bank_config[cid], + ) + fit_config.append((client_dict[cid], this_bank_fit_ins)) + return fit_config + + def aggregate_fit( + self, server_round: int, results: List[Tuple[ClientProxy, FitRes]], failures + ) -> Tuple[Optional[Parameters], dict]: + if (n_failures := len(failures)) > 0: + raise Exception(f"Had {n_failures} failures in round {server_round}") + + if server_round == 1: + for client, result in results: + result_ndarrays = fl.common.parameters_to_ndarrays(result.parameters) + if client.cid != "swift": + self.flags.update(pickle.loads(result.metrics["flags"])) + return None, {} + + def configure_evaluate(self, server_round, parameters, client_manager): + """Not running any federated evaluation.""" + return [] + + def aggregate_evaluate(self, server_round, results, failures): + """Not aggregating any evaluation.""" + return None + + def evaluate(self, server_round, parameters): + """Not running any centralized evaluation.""" + return None \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/figure.png b/tools/disassociability/visa-pets-FL/figure.png new file mode 100644 index 0000000000000000000000000000000000000000..491b0c54389f8c29763f2149b811e8708ddcd4b4 GIT binary patch literal 143190 zcmbUJ1z42b_CAgeT_U9*J%j;Lf^-d{bcu=}rBWi@A&r6}Eg=mG0!j)}BA_VUAR%4S zDM-U_J)Uzu=hgRn{m(gnuL~V#o|$>}-fORQulv3?Pc+ozNr~x*Q79CtqQYfO6p9cV zg~EPCh!4*Uo8=XtP{%Olva%YAva)CmdmB@8OA{1I;YsvO0_|&!)XBP$;h1}A%0wTC zSOtBe$ZyRuew0(den@GAe%#-A1+R~Xx*+WhE3p=4@$ua|R~J2HE??21ArZPh!j9!Q ze6vb;=lh8CQQ}T}oP*i&5Ef?oqC9((+zqS-itkU1{Pt%jpGD54o1p@wQH!Xg+SZd7 zUcUG=Ce7z@^uW!V1l_ggB%a+XJ$Phsb#l$29EHA0p1E#cE=tafkIEkl`s{`x�W+ zkcc=@OMLM&ALcQQvRwtkO$EEx3@fn%pT88Kt?@B3PiiSLaZ)`_u5Wl)i}h$M`}fN< zP!VEjq{%KOJrjr~up|Dg)ggylcs(Uz*I#XS^x`%7i=v4e6kNWy1ck>xKiw(EDwGw{{c_DH zz$4Ac{4TY_?&4z>4?bQt9_?i2 zoVezQFJ$$8`qLzFYqetgLu+?xjmFmwaHfpvlYcx~E0h&uzT)O}VZBP^N))4@aah7? zk$-w6(dV}EokCg9wTBWSEYrKL@#lCs)tJs2JZ~g=5?Sw`kGkW7w~xiBa9b7cn#>vU z;?3TzPu}Y>m>R6R;m5aaZ#2b?Gd+1r%~DzCcWL$PWsh+2u6Fg_CW4*tBe73S`Oaw7 z-gw@H2)yJ=i8z;Jg!XwOHYr10l~L+ww(UTd<2ib#Y=m;UetNf)6^+uKG5!>As zE}U_|H#qWLW0monln0!H=iUcrDL5P$c}*3*3Y|(2>{>7K?j;+T`S@VAJ*NNeB6+W0 z(np@V^@Z-3dbK;G+UK1w1<9NCQ4K88{iJ4ldX{9K@NnE_yHQCXZb4(IV{-M%NK8F% zc-2cRi`Vx8w8N7ldA=7kWHY4W+O}v$L>ZRctwmDZW+hKyrwxS?C!X z8!KGVYv4c~FZ(#{y&-P)#YZ=g6-5(je|o}0^z3i}3aje$^)aPxynvb$+8@_Muy2kL zdB~s$u_DmN*?k-Nc8`g)f4MF89PhjTT@7L_pLe&}`msy=hi@~B5YB(Jv&By&i2ZoO zmTD-lD3iVt*KdH<+D}jn*LTLA}G&ryh%X67a%qmi;1QQULV} zl1`IZ_hQQ7r@d)r9nG_<#W*s`kC&XqAHe*EYv(e@>C++j6iTh2q+AW(R+Z%wU|>_{$^a)YP@mkc|B zFF9Q8x!SoiQ54RUq2z5J-}R`vsya zgri~!JFh1D6WJ!Vlh1_0e@3cB+Z1mzJPkA_s4QnRjdaTr92D4j8pGLg&(un%4LS=YLHwexbP z{)JmtQ$^J6K1_IhAqh$fN{Q-;wv5tNqKal$YL0T|NYbi!vk{||aZAH9yRk=Np8x*I zO|DIf&6oVD0|s%qE!UFY=;i5UdZ_M16YG4@dz8(ulcABJG9BJ@GTPwA+t-gcm7`yZ z$-mRv%$m3Q>LnQeP5Yg~JBD`|_aF4|yq3IqHd{A~AnQjD&1;*R(r-qt)ZTk_kvc?K z7c(d$F2B%=zOQ}1K#*ThN${t6vH6DiYYQ=pGxw-SPXw zUAs9}sjWTVEqFWgI;&nC$e2Zi<{vlAyqu{u^Qz5EzrxzbI%ZjE*nK$Ay(~Gzpm@ab zL#zADhP%9Z@H)d6#vAl^D8=D)Z?kR;;b_Io>T@&mQ)_k61C{`*)DCRcG zUJK%=z1Zp)V*boLB`!jzY=Eoie$j*P#ts2)0Xt$psDF@A$520^e#$k^ovb^r{g#`A z`wcg<_SIaEoL4$GZVeZlxh0TOlq0NT_)f-3&ywG=$tth^MM+)>_MmFX(o3S3!}28Z zH$$teGL7qtn@iu6EEM;>B`e~6n_ucJSZArS{yf^~R-K1~tzCn2|G2Gp&TfBr;T0qL z`YO9(yD704@e$h>Vt8V$PWNrymn;`rTt*f>>K}clTz=b-(K!9)O~RZ0cXjXTTU)rl zKW}uhSv;}SJXz$R4h!-IPm_6PVrCaUwJ%A5JO-gv`{Sl zoq5V|Vw5_SiJZCM!r0YR#oO+;oo*Y3D|fV{c7J?0)-e|Sv2+06lIMNUFwF!tzj(Un zb&iWdjz+VSdcYA3GvQ;a9|nGPB6&Pmcr(VEbHz>c~7 za?Hup_IpeVnNEbx`?vT-R7DmJC+s!tLy5mW()si~JK!40tCFXdSH8RmHD~JXRa>A6 z408F-TK>$Qe*NNDpvRlfO6yP41xCfIC5|RH9?k_4_YgnC%N4LR<%(O&;VW1zjoVgP zZh56sSMOuTycN1rkTX`TF_8(x-bm-g|K z8_9bwqTP%N?Q(5Bj|UMmFSwtseewHZrJNXJslA^2@TPrsPg-%#MziK}mvVLJfzv_0 ze{)yNJ8l>KDmOg4N48&bqovNfLul!3V)ml@v}SK|Wp5WeDV_UfI@&6zd#ohNdZ+S6!Dd@Ys+ChT zb+pOglffUQ(!&u2FQ#*US16agF>+~H{?1wY`nBZlgP5Byzedg{f1uhj z7@n!lPpiu_)y>tin)50!e0%i8HwvfpcmR{7*k_UJ4*mPKUnsssM2e+b%jp|>%6ZDm zYRc|)hQCw|Ne+<@9bg`G8rpWcoNAZyh|Pp*w6d*J({GE!DRzp=MQ_62B~aZhQ^28yoC3b7ar_v;V-v8 zz_@CxYi!;bU^|eH_SQbSIynB#?nQTX(Wc$vzUB@ zur1u5dY%`?uWTuIHv5iDNPj)daW&bTUp1`qe)ltEWxAVZox4f|+X-R}>AqLk@*_jxQmp=J9H#2js;0}R z7g?y!Rc!it+NgxVZ4R2=Lh0oAF){6&2;>d|Lv83f8$@?sr~PF^79Gu z{q?PXdFju$-f%Fnm$k8i?{t*<_j>*F&3}FIpKp}lMTY*Dsrbh{|9TcCT8dbL_x~16 ziujG&uXSWFzpQ>89zm2Lf3U8>U+4buFY-7pD(GXuXA}y9QoJm4-4$zbn5gpj^l1Is zXyJ-(NyDgGR_2$;p!HJr?CT{DH6LcNE51#CNXg0aSz76*=F#;_ltJM+L?)w3jG;?o z3=#+5CAK8CTH-|a?AwP@+&&z($1TK{o8>KdChdtDw7TS-pIDbpC}8nHVH2YN!(TqU zxU7WO+jkk}aR2kgDC_`~49bgw9D~Bb{SSYwM!==aq-XSH{`0jMA2g~O=Re*n3};)x z2kjGv|19r6jSCq%hTLcLKi(%Y6c2U`d5yfk;?4gsreDKG6V|N!7ZZ!ZmZC%xj=lVt ze*V817&-2k%YQLZC@f|oa@^I<+g&IB(*htjONhNH{vQJ1*HC=I;L`1Td~?MAO%UK> zZ8QEg0(@0y?scU4UY3(Hbr-*f!ZskE{rV=>ad~KfL^UJiZ=(-a#Ky(qCrTbUX_O^n{~5fBnvqO1&iB{@6qS;D>a*9xX$` zx*AqjHCn@$+Q?xQM4G&l8uyaB%qqtVk$@4J-trLeZ0!#o2t{CvcMudRTs_xP|s%c)|A~O-Jcr zeK0NC!QNKv^XJbmR!h;S82Yka32K<_d^sH<&qP5=^lQtGvyu~Hx1>lL-YT)Az1 zQ=XYLtgTBK=4Ff-ZfTzm9bSf#@%bDgg7dfSF@eNP0unRZ3Yb2vJpK12iPGL9@*jxX z^QIb~$(L9Ub51wMlBuKILr~av&SJ>XivfwpGhqv3hk|BRnIoN$E-L9#WQkIxbA ziA!c@5p(HQ6>fZ{E@dvE(H1ZGX8P2l7MhM}dgtXK4x7rgfW`j8+OAX~ zt8Wd7q7Gb6Cy>>vCWhc&Xz0o>K7l3-kBQeK`Evl?Byh#nc9%FVIUyTX5^1VwYy1nc z7^m*d>4b~stSO#9<6xQ32eVfWd=)TjYsf7f;Y<*+;uqI|=n}P`daPqgfCN|(WH0G3 z9#7@-Fk|MY8=d}44Xf2~#ZlM=$;&8TK8okk-c`lc!`~LILK#GjC+k8qb8iV2g(h`S z7lfsi{2cq}uUX+Z*I_M^D+|llh0GQm+MZ|>x!VKsZka!0wo3)Bh`(yj0xzH)@Eq^& z%%pgaSYEF3*fBmjJkTt&z4PnSw-2^PGc+@lV&uFIHv8=B8C|sJ&mp$i&UjfWiXP(p6#By30U@ta7 zz^4y|st1HY6tB`+xCi}_k1_X=E40Gf*Q}x5kaEoAY0vJy`i5JaU@Hu!@V>W7XGkxZ;Lm3il>DifdN zjH(izoJaByKFnOhZCmRZMfk*I1|d4wg@64fAIvM5xrP(Y*2tu!>y0%;uxS>Vs&=N( zz8iKK^?ZB#BlZtxv-V`c5skj=#Q}bU)capw*DaD=c}m^7o!}sktZxz|fpjXa*v)8& zaYNFxNq^RGv_3My=Xq)5k!aSs4M|Y5)LLKEZQb~rfk?!g58D$@`7QhTe&i^}@+iV~ zm>#sL7F_6W)b>@1=4|}-&LYEh{1fd1qvZhPE}y|VI{Hn&twJj1oAGpk-$sO7@dmkK zdU^txf^S5#wOij?6PjOKdCEo88hQHZ{(|9=Qf&4F8!Kx%EbscyK^x`ep>po6kf5MQ z9{q}jmFgp&SYE>thL_>a*!SsaX*mLqGg2Nf5{`&D&Z3d!o)sxX@R9)y+`%SA`T;Eq zY#X+ya)RGy7lnNY5jz%UmreuQ1|Qqd`{3P>+e}hS@_emZZoXl4RYtPk5w2-lBFEBT zX;@!ju5PJP2%X@>*K58o4p&u(;y$_HpQZq$d|EVSlK&j3>ovGSxIRRS%?EReSmJPd zlHa81M9~Z#_BKRty7SUEQ%3Pa>W%^Qa`_C?laLTW@z>QeC)-j&n*Ie-yrxMFGTzpT?BgDZANIZ4!r%cXbIWZ zxGhoCG>#EMujUQxph_|OVsSQzFynNgtxJETmYPB23Q4bGCM?`f3aQ9)cdJXsNiGi* zWd_p=w{aCwF=5l;_E)+U4Gh4mjg@XcTKL6bo_%h@N4h#q-E)zHGSZ-6wfqQ{yioAVT;?uiY<=rs?W@;Ro>5scvik6`g+JA4MEwUWt&`9-} zYE9@fu;Q?wcMN)v$*mm2rCEIMY9o}i?(vKHgXIpHDhWcWS~=QxR7&%gICb8p_hes_ zzg23@9YQZmZFS9FDCymJEtz!G*EcttAqNMC#qj9gvi`8CHP?~mKihIVdjK|@Tv$7+ z59Sk4hqFBMT!;;s((bRl#3+r)KUg;Xsz z@e3WHx$!iqa61S_Na=;5WCM?v3vsZrUSe_`!-^Aics11!QENZ)Eu^? zu{`?xyFWI%)t~-sHShlEvOJ_C7eaR$kA&J$?29H1BO|xXhs~HZpU+G2yS_Ar9#`>? zGn^}Tm?`m)%Et3UDMn97Kpl>UebT{rffVcK^04kJwIt46;p@VrIJ1kF^Q(4W7cy7m zZryAK>}nw~NBa9>qOj+XEg z>BH@pLmod`iKJ@C67$K*N&)4ZZ#Nv z?%+~mlL=5(yl6X~YO{4#NC1OG^5Oo@6T0lR>Z1djo$m=Yloh!P7yM9p7a3qV!jQ%L zfYnnSM=xX<4$z8DI_Xw_)>SrIapxS0DF-n%{cjMN3cBifNLu_YGE*Qjx7{4)=mD51 zp)+Jq`jlwz!+m>5bL?)j^auJCj)jSDY-Sf~7t$@o5^7$lCQQx0$uCJ9DYY@2Tyj^= zP?j;H?l2)-EFvyTOwO6lP>yYaf{--Ug8!4md9nZMEA^D+Z^<_e+^!gS?U?-FR@|Fe zGSS~1+w3zGHq|a*R?bl&M_*tG1t5oPq=&r?bJfhI?aACpVovr+l4m>a>Qm4DXqTq( zR7kqoyobBYXe~{I{c!Mcx8a|`sL2BhGwtj_2}r9!!#4^at2jc;caKzBow!3q{>9F$#cb4e2=$Chw_aeCIkZ*Pd7!|kAK4Z!9c1xThRc+&CsFg z?>vm+H8gOX^RIb@6_8+E(KwsmJ2!YF_QuCk+;+?-%gtWM57pQ?p$8Eei9{qVw?6UM zM*`+3-s*k+kJWo0xqIUrHf&1qiLX`ctFCVLyC7M1@e=p%%LuFb87d6<=tDZB&a(g7 zhsWo-P|^n*&lbEp`T~a`AuANDEcE6o+djeaMCXxx9TrLo@*a3=LdnRu4Vu+q19UT*2WyB}@b9MhVU!gt57aJf)ttv$5u9-sYG!x*R0sCmi5 z!-Ku%)$v;H8L`&KsmesfmIGb(26thnJt4AVz{0IWfcg)4wOH)!-G%%N=*ZsMj7rtx zpFF1l>Mo`hTP-1U^8HHH`O+c#r`>nvHIdB~?c)J&{Vmn_@<9Nr8b}olT1E z`hLs2d$;qYe6vHlq?v&V4uQDKaujqv&z&l$sHoV4hiv^V2TNLWc%j3qjKkfy9mXUX zOu*zzV@k^67aa9N2+pBl#9h{s|q&O1$tF>#Ug!cQWXhm3~aAQuj z%fDqxSeHd4z7O(&SVWJV1e}%4%%RUTj%);8$IY)dn*n^Be(-bARR1=BL#aBP2#k*R zeGJ<5J1WZVajpF?t6C$sp1DB%Z!@ZSQb*Y`tTxU%J(HPAr z5ok3k&mr?Jrz(p=FsN#R(33Xev0|sWt}7#6dsdCl&W71(<5)9_J4f+WZGW0`?BuIi zDjl`v;=OIi)3Anf{fr_c_Q>ZVM1QOU7VaShP!;)D5P@S9zXp*NYv`pDaE$h@K^BzW z^@aRuv8vpxYd;C3)7vB10u#ku)HXsI#&Y2%_-#fcia2dXD(#^Nxcd73m}!5Ck9C|$ z;$HG#mFI)F3q}DtYE8#weA3U^9S2;4RG06;--^wD(6u z%wZJdT{MxB1ht9YMh3qxSO2}%d|y5<4UZn*8z=zCnR!=l%bCLT&w9_}to118E7 z{#tE>gMaL?Fp19~!Nw?!r27V+DVeQ!=G`wrb&(Rj(|AK|FGutN;=U)9|1E7G(OUy4 zsAgUUiDW_@q$+H%9DcoN$PMX2rPyZoXNuISbx&5r^iYcT;ee~`h~}JTLy~yuh2g{M z!%g01TEG)pk)vMuIz_K?468@hcZxY@adsf_ywCGpbY%A2&Ldk|!XlEqDT@bLHvGmL zEm6;&cX?__Vshs$$>KKx%wm^+>Hv95889ZlX>kAU=!*o)S?VM)*f9D_Zig0dPICOvd_?4zruNmZu^w)lU3dXZjq$06z>- z6=5js5efx!a!lrnzS09h^6)4tU_91)uBq#&eYLG&bQ!vn?Qnj!b{#;P0(1~unX`iR zA@pXyp0u+nZ6gow9gGt&3)Ufk1w^^RH-AhB*s!Q}>7$`FIXe8F40}iXR6jftvE*kxO-A*h zjcj2OtWT-A?GLsBnfh*zK-r1tX|^AchUwWB{2gE-oqjrkUS6M=K{}~8uJA6jkC5(Y zW%rz?f97YG`Ov(;na}&~a@HQ%Ks&?cSyAS6hQM=A}?)u^;y*BJS z@3lQ1=${b&YHx3AMK}h#gJ7S?YUjsHtf<5H=hybnI_9lsJMQX^cfA>%CQOA(GiA%&u+bfbE2S)8qdhMs=Gl`5&#tSeoytI$Zkev%`%$J=|> zUi@S846dPa>{O>Wvz|k%U}Lf;dRuI~Se+c5D3?%$u#|trV&P&)GW{{x4NqbMh|*AyFO2cjr>2#^p;P+j}e3 z(~w~WV{U3>VrFGV-&U?SozJ*a8^CWrrC=nTw!rQfjK*9NV*s9s_ty+`E{uG5uaY7) zylInvsJ}DyOg*@Ie0PhJTlhe{%x+Syd?xw9R7aX@Xlz@8a6Y7Gc?AE}PhP(TRZv^< z+}~sbpZCBDxX_0p_>|>zcsv@T;KYtg?*KIxwnxZX_|!Lx{F4WU_e&wT8p5Ox?^TYw zPKF56DHEjsjNf$wJ}s?55X$Tc_=5DMEX$Ef9oRPa_qpxg+e6P8La-`^qvmc6^ei9K z&!ZXXBfAf(-LF(bOo$+b&A1;Y(5Cx;5&H@ifXvc$?ash*g=Zr>I9l4+T8$iS9N-rK zYek1l2faqpxas$nxULLknsVE5zsW03G4W(l+ZZ}w>qS?I51eK#&so$ZH>2+H)tEQO z31lKn>~h5dPb2UQ-5hre>w{5uj?SZgj|rHGzk&(=n`1G$))CMVC4|gE59a}~4E^m9 z$t)!q(k4MQf1czy6(Mg774t8RucoxUSzBTR5K%XwU1TXNEDWXT2|W7oz4h6pN5rB` z>g`JUoM?W2O*+B*?9gr4SKj!!IFJD)u+AR;A=U&H1@2>;OMmNw2t{Dzw$5qPNMaTB z-CKUIj>Kz`{>k%NQHEYSR{$j5=`EbJ!SZy~OuK~lBq)eq9EcGOS`pjSfAXQPVQzau zhG5#J=Q2}bfZPx_P#7&?zHs-U<3g|M`c&hjtD4-6WHBctxljfUq&ZvQcG5`#0HfR* zFR0&6RF8v;^~tVhPT;rQ1T)7$1u%?XSPJRJjmv5hY^pfgrS@{%Mjt;WEdS9aJ`Egk zgkHIQ+~(r;HB9E!Ug2}zhdXAS!kjpRk*AgUJ?_uceAsyc?@rHvyNohVmi9KN3uUx; z_-P0d$AIyO_mOAzwM?}p>qxbfuGLSIVbVN+Z=EXr5BKEue)b!MDfZ^*Ft!twDgoLM zn%JQE155cvA)!;wn1Uch{_B`g{QRpg1CU)^nIC|aSG9ZJyuTnVic6;}$E@BEo8UM@ zI26zN2O64rdZGg*R=TgM2j+OK5cV$)K`}e)-bV+eNW=NoqK~JKtkj}U%@pD;O|110 z-WYQGWdQb239w|%>(k9U54On2Wl*J~l=FYWBTNl6QZ+o6Xpx1z;|sg5QI<^d#3$}_ zbK2A4;x5)Zz=Tb|O0aIq(zRzsIu3-lV-LH@yj;EdiNt=kgQcE~nnzC?X{=ilgvoYH z9s)%lQt;`Ty5woGTHo$h%9B8J3Rb|2`iw04;Ec zZy>2Ti$vu)-$!i{ZD#y}N3lLUSZNEVp|2OK6jQT-62Aorf|B{JKK59f_)669dl&KK zM9Bxvb3h|eoN58`W>PfGAbUIj=)+S2xc5+9=5Gu>R3OkE5aFa_f1eXBmWO1e+2h^%_utUjg8X>Q2Hd=M>uYmFF(|&SGz|tE7k}x zQ*iO^=U=}`hn!xQ5V^BHU6d$`1)sI*4{3MnY7fDL7<~sca?t!Xt$haW zzj1%(N62ZTjZu`r$Xz2N69B1QuWy{UBax)$zI7ehjbT!|S+Vcq7fYyc6@+M+|18{S zOQhdf>gcwEZ%L>rxKkUfgN=RA1WG`mb-|(WkARx-+Q1CQxvrQxuid%Kt`}EGlo|05 z#1;m#m?-ZAl0+B6Gq6;5)35J#RGT!%tk==oC_rgT;+^RH2I-<%pk0CuV5#$>Z<|j? z_O-LLkUI42zMKr5+0Knb`;5v(9sZT^KoV@Y7kqqId(3%+Pp3s1RN3(4uu0y!Z{9Z_EXie%aP4qkJyHUeDT>CxRVLZ{&~0s(3l++^pQ&j^;JWsO z#$>3>?pG^)3#b4S;7{y9IT2^tx^@~ApO7=2f8)A6kusIfdlTWh`4L?v;A-Yz2s}s6 zO#@;a8Sw4fM>1U*cQ?ilk)Gzlg$qaC=zIB)vg#7ncgcrV5&~FDEC*PjuM&*8WIu0{ zr&n&gI`*+E-%xrqfVFrO=1?w0?9W|AZi~?2>6aK`CdOo7$1pKm)O|*T-H$m3OrSRC zz>n#dA@?gnyKD@L9y*x(!Boe#kcldrLzxvRAas9?k{nL;V$i2-*KP1nCAd%-^0Yj)ZkKb$^jrSTK|1H*I-W zKtJ~LJsdycfeDG2`115=6~)ZVQu2f43%%HF$&wE$t>k zk-`fKRKCz3(UBb&vg}8L)S~vx!hMGobcq;_Yp=8`QZ3GrF^FhDGBwWLvS$I7#C{>S zl+J=}0Y^XxNhIpn<0o(*2M1qdQsOnJ?6)HS9oVpE$WW!o?u}$iB|DJbnWAz7 z>^0RAC(yW)mEZnh?p1;@V3ZiC5rC=dKw;|6?`wAGV1bHb-aFvB`&u9It4zeK{^9DUWm8Qjw;E)@Q_XOTUxWaVUm3OC!y%B|%tyW0c5X2m}SY zo_l`-J#lQXu^fiV&m(D$)fZ{#UD+hAF#ntj1R8RA_&vSNHlB+e9&JF4a1yR5|Y=IXw0yZ#L13q<8mNtTE^N=cThZU9D2$n=JG z@x6$JYMgkG?2)_ybWwVze)kbSWTDaL;(ykO{}W(AW2>U+2>eDSxe=a`a?{ZBUCZ+e zviEy)%(FAcq>%`10-=S$hI8(nMV}H7#s__O+#e!7hOs_$4fK%g(5{qSmX{X~%~m_A zc3r!RD80swkzdvrIIo?to$ETivppG>fou$4rA$?`ew~8bzT+#75CXVX zbQ1z_Hk6bb5&pTbcbN-;pSJ3q^DY~C?~;FlOS>b-sD2b16x3%3#RU7u2%gN_-Zw%&=u=lya;vv&8P;n8mF&A?WwrNil1!*qw4wzsCO zboD>)qhxF80C~iK_$8K1#Tku@b;*SN-rvN`G+v0pjrzybNQw%;0JAX#4W_W5W6(G# zG4UqO`@E?(0$OjhR6%$&an@qC1e?tlt|9=kgswMgctM6Bz75Exu6P6TuPwuI{kHGm zREPx?ON=R#-xJq;YuSpc+VNdq)2@T z`TtoT$b+zdqA~5sk_dcA>%Xr4PH}Wd!hItvQN-?(wzLR{W18R~X$0~uXfKV7283HO zq>su=BlFCwTvuuYt5Q%t?RKgpVF!S-IAI%uF+OPoz_t^+N)oPu3qTn-0TaL^FMLs% zONI4J&xA)@WrI#|OgKKTI7TVrv0dP}GNLbawC_+aKCg)dVt#2$qNu=B3^7zO5##HB zmKh%(1Thkvc#Tjq8YdKL@?3Jx*0eD`irEW~3+XnhL21H1Nl;`T}36z4O91dn1 zo%v_FP!{wqv>_Y}p{pQasxLpOvC~3tD`B*#8x80625oNxf0Sh!8=q;*Deyr&UZ}Rp ztC;6B+DQ^$E@K-wVp7OApu|<+)yaE`#Kf<__{U#hAp#l9MrDoyWU%XKK^Y&Q(=P*g zz`0xUV(=B&>LyDIpusE5vjjFX?I|>#KRlpsf3at+n=Ij$4)?(CG~eUu>jMqA0(8|T zu%3A~O7l*et!|#FNJXj`KJ#^R_tkfZ6ib!t{BitDRU8x|P^_(1LCI9h)Sf@w|DkRq zISUZ?bbmD{nvFR+Md{jwcVpVmzJuMAt(h1IGCbAlU2;rcJXwzT%2ckma zIa(Yj`qimsWu2>kdJ_8S5sx3L2*zLtGrN3R#wS?gJJWA*3}L-33$3bjU4#L&s;3Ck zFA}C+6w5gB+!{VB_Q>v1Le)W@&1(qZQTzC+EBVSK9;+V-)|es*QnJXZz{I8lVEb}q z>9CU`YDw1E*mzZbV#$IK6MMGo=QoQ2ZB5ywl{E1?BptRw_FFk1QM{+`EJR}h#XjFNfguMO^ncNC02vCn5Zj( zB6}g@GiC*}R#%akI%2bVlc^HNy35BRN-yIxbjfxz^h4F1W&fgOp zKoO+lYVpRJm4a`ON57Lnt!!L8xb@m*cWY&Iv{DB0naS4jupHs1oc-z~UF-5M+hqH` z?)nBYqm;!qNT&~Olhz#Q7T((#K-Oy!8}1~Khq6TNnQ*bdoDfrs`c3Faqr)w8U3;;y z(AWNa{s_=TXnKWL<>Ao}dYj5lZPU6nc23FDsaOj7)LDSUerdubG*DR$T*vX6tGOA} zRce7XGk@He@%vVW9OSu!=!T3|L9TOBudt?ter|GZA~ zt=oJ2(<5YuYqvcBUcqE{V=hLFyCq4y&s8H`$`{!;b%XxCZ<)Fae5!8H(bLms#lVJZ z&k{HvSs+V}CQ?ZBLt!VMAdE)W07D!?P_-LAx|C)E-Ka4XMx>KfztufEMS^?C*aLd= zK*1gVNj&?RHtyY(N1*;c!N=(`Vj~P^daXzG=h(fGvCousorkffVeb2(Ec)_J9+1_G zz4N@3pk}@YWvlK%g1&Tjg_DKRt)Yif`Sr;j>xy9FbD2nT2DT&{+P4MS=o$jY`zE9e zVDseEh&GkpR6PKup^ilMqRq(AJqOr^SIkIH30XeZt8i?rv;hLy1gd%{na4%FG%~-% z1-S(kEX3es$C&g8!+dI>6quSPJnG?zP~q*1MSx_jmD`fhI6INySnyz5rf!k^sDm?1>%A)~H4 z_=lvZ5Os01sxNRJq03*eR`jmHT4z9#jB&V`Z@`ThIWpREQ?E@f;4DLrcuUP~XFArT zA)Ez(!X=>Y5O_4l4+3e%AXsIa{k^OLf9jWx3Gamjc^`%y%5MP%dV-I=n1GKj=Dc_g z?9|1~N~`hE67t93bp$H?mS^Hv8f)_x{Xzdo%iD*TZM9n|C(|?yXX{`enpR+kWUT4W_*R* zMupP?_6?%LN+_i!;4+FFBKI+fLSy`j8G!#MBQZ85zYYGJy3um*MLy6De-#+-6Tk|b z2hUA?ryKEtu)zOxZvVC~J)qaB*^*TufFbZAL%4SYH)I6eudHA9x&>jX>)OOyFcxrV z7oln68rkL%F{4Y zEeUg@V!vk^Rdu*)(Md~{D74vu`U2&b7vjqq{C1f#B;cB>WiLIP6Wo;PgI4o$EJmSG zjvRgZz6U-8BRV`z26gbh8E4 zxUv9C1?AK1%lM%_(k+5bE(MogfG{<5w=@-!cwGyRmwg6r99!y<_1i0*1ks^F;vVJf7>Beayq!WbzM7V3kZT3v0K}zEe zACU3!X#+z5_WS+I=r7ofaaYfY3bPP~GjO;4&7NP05dK@+-4Eca!jZN=cyk^{)fx!& zmSjosUnXF^O4m+pGgruIrZ&tVTE~ewz5bULh{=LxWM2M#EnrxTYp}qFzd5M55re@a z);wih#kv0J78WzY%$=MV28kqF*>o& zhLAQo@11jpAa}0UGka@`l>kRl4y4~d50&sD1!E$s^LsGj2kV>|BKxzXtRPn`_jlSu zTO#1w9LuW=vWy8dJ8SMX!pEd}>_z?`E zAJ0f(a1K)7DnTj?Eutl5meRvNb}Brg^@&S9!p8tZ%noGYTBwOL9+=sgu$Eyle!DFn z7YGAavz&Vz@ceB;mH3N_fE3u^O)~5%cu;cAP@Pc;2b1=rY}7DFDbmP|bwC(^A=dI0 znL2Wo27r+L(!0J}-}vEh!5ml_W^+(+<#YCO%-;Kjg;T1w;upuXs=Yh`qm$;fMI8u_ zdUO-28aK64m2t@U;M$7(IaerbYKR@`0#qwC0FJ`8Aaf02ipyw)*6!y=J1)^(!kVS;B z7YjpXTBp$J$r9_Zm7YlPIe)7QW42LoOVPQ5N*b0kJMTK?OOP0E#zhO&l0ejDIdO7r zBd-UQ!L!owQl6|6gUPZ2iqq=yJ+j{;5HjO==%*a(17eUA(g=!LI9Fb{L>!n_j+2C3 zLirw}Tm?Xi$sj9y`SK;smB|^X{^{o3uVN~INNOgN-baJusJLg4q~b&eKqgk`$%V;o zWNFCs<{R3?;D{qi<7ZQx3hKy8rh<8HuuBTm zf+yu*aZk*{`U2%cXMPcnlmp@547dgF_k@G4@O<-1(1{4(ryjTYMRic0Jb8NFgv@MK zQyJW9?>qo|R#G+ztdQdt9HT)5*xw^b0kI!qi!dNmekF3IRr%%gkd_~+ji(0`Ls!CW z4#KgEcR_rK0F%XleHMtvZ-ErM?2kg6?Q7%`B7~_a!{AJucF*QWl=2iC;kr~I5u(Uc*FYPc?!Gzi z+w3-RRtBYhj4I@B6@nd67e4cK5W*r?2&^K4XXs>JJ9_^@wccf z08+2usRnXx^^#sH7^c@j!&6XEQF&Y%^*tjN10=;%^i2bb1O3}bC%eyzOceWnJy9%N zd|+0F9ji)M8ve>83NupaW&+)dCV=ALiql(&p5Ju??o*ss#z%zS55(Z1>eCE=%KRuS z#Mbtn&;*Ic%Xk1^Ip7RbBs9%&+`*3@heI|{1~E7T(5tDW_#tIK5EH=s%7KDlJf}U^ zl`&m9>h%m>)tTo&5SamT08{EdF4lx4AvWmRM=`dz|HKuyQ5p&V`dtA3-olHpK#QzV zPy&3(Pa>S-G&p?=i}Sn=cGsa(3Cg2y|8C>k(4fG!S_rNoW%=rz@AQc8C?gcixc?Ba zmtO*)|L-D{@TxBXlPiXrOZ#?-ni&cz2?-1Q2CO4-`7owvXb*zgj#Km_23XM3$xi&T zSGj5-tKC8IU$6G{|Fgm6T7Xy!T3jtx(WJL8Fu};7xXZD`gqC3v>yLr4;?|fti;P0& z*FKZ~A4UP;-UtU&jZ5-CSmU=I5-Dm?b0ySs8FqT>_QSNO2(drIG4i;7X=$()%%cBo zCcr!fV5Ly)ix3dAM{qV4>8D~KwJAc>YI@Z6_iG#d41m_2wl7O7H@+v|&=8K3Mow7Z zODT|}(I3?TU?(8?g5oqGxtT0EGJ&NJ*?!w~NG7`tr$TPiqy@v{hgk?|A(pWZUONP1 z+5>*AX9COOHS4M!({kYP8N*V*W(U5lbKpJ*t$zpo%nSRiVHaial`ymql-a$!@J2>O zC3w=#QzLJydZ}cPAuQ8m`U%xpfC8 z?KMCvmv?}c;>x5xucad9vdo1zB7q5MvgZ=T`|C*<)K(|!|28n#P|;?|ZgOx4F!A=v z`b;}7lG`XxD`~fhd+rur&o|HqC&%q<7fz&ATZg+)0vN~@_%6?SxFSX@(=Pz)dwU8m zw6@i7ye?bZZ9V;>Wq-%p9>4BKMP}_hqaHs()`Y6N*TyP9t#2jd1J?#Sa!5$(vv!q7 z`E{^YjTEL|hgJRe*^#m^y8j_HXaZvj)gI)b1jxz_Y6e# zTaaKbq7ZtV{-f`h1E`?LVK*(?=!Gh^=6nJ2^e@+Ue-nzX;PWU|GlFe^mRE3& zTO*$Csd$S#ioF(W>zM`rVC44|0P#JfW|w=Ga7?KdlE^9?-$!j>vHuS2@Lck zS4OqfExQR;#16axQF3Kk_ZP_g6389SPqiiKme`Ck*(e?S^R&*)&kM>Qq#V`f{oR`h zo@tx{u@3PYubIksR9TQ;ioIH310v;1Bo8_Up!QzPyy*i+=3-zA+P~ZW7#wem0V62q zz#S+!YtOTxFsC3EA7+LBVf!J<$nXFhqAa%PYtt?rv2sIsitDdj02Jfey}KYN7kr8Z zu?Wo0-s2XthQ92XS&F9?fH>+ESFqY4#O2!uTh{e~OS@Q}zolh5J{Oz()Dxj!%vN`RfGhsqtk@7&b0 zdvxp+9|qwKJIQOnuX`3@D1Z)1n3-(@FZVNs0z!{)GzR0B0y#|>$!Tm#|3gme!Nn?8 z1|JzEc-Fs(dlII=+0)58m4k{1`ed?v&)5!Xtecl<9jwkpht^0_W`f`HP)B*H!$_?A6VQ}6nNQk+5LLXM*@pp+!kUM+q(Kc+!kVHCToH7hG_7oRk)>r^7*9d zAis#bzqHD8uL-m&uCHPVz6V3UjuHW-_M;L2R}-ApF^%zed3`;b6_OVJ8#qrWg&077 zmm`oIpY=Z!NxxF!Gl| znI!K++kp^5KxhbST=bHIlS)9xb34_CF~|P%m|Tnd!6M?9u-9}3!|#^qHJmHINk&jL(rVJZfIUW9_3&Ap34_tyBRR96M|Ja~8ag^Cov$gP6_ z5MKloU_JJT0}BwVb(NT6Eeze{d^%wS=NI-Xpktl=xK!pzh_#5X^ATC@zyo)g4k115 zKaidv2%u7Hz|h?U6cc;jO<3g~=?=hBl^zwx)wv7t%`o?5uIbE(>e;$ZC^_@M+wBCY zP0oY@Zx;6xsgB>Lamp(RTY44Nvt49`tqpCx)3-kEOJ@FC@9$-bnj~bpxAn1)M( z#T zLW4$iX$LXX0JeP_J6YO&0?InZV*e#3&9v@g6?8PA9&y(cK!wayV8X8I>uG;i)g1Xe zA~+OrjL1H7ew`nyXp{5PRm_Y_1ZIQxHWS{P3g6ei<~j~%$T8~&ucaE;F z%t@N2c$OW%q!kWE@h00dVDy-`f~$EtM=7kvA_k|w!!R#JUn4>Pt)EtI0JDkX%pMB7 zV~%!v>W+ar>WA21=nBoKKlVCWf(UZUD(zijZV5hJ%wRj^%fpb;H=R5a%(@ z0)I$I{y%7iaJs6KASejub5*L*yfnz|uGaO}7Lp_tKxk?rD@B96B?j^+t&g}r@9ona z%J-3BIn>`-KbqQ~f1ki&fQ}SdxNzZz<&tC#qykZYNND-L?giU4M6GKPj3R7O)9Upf z9W=VM#Lyfj6B0!Aoz`~^Qd^a+Kt%nGcDU?*bI=}H2xXhMFoh~KMe%` z^NO5HfZ~JARFg5z+WqGC1vy4eoO5og!tffM z#&u{8-JU~~P`?fTRtfF&DS#W}Q+k!@1!4y}aNs~mX8#kZ)KDn(8eI(!%Q7An7tgbO z*8wwh@;>oBzQn7CZS;m)#|M7~p4bM7|Gpou5$Qw-Z7Hw{zkaEuU7uCG9GsiSeTQD06!wno5 zD-g3>imkS3CEWZ_W(8q1QfA1@0Z=iXb!PX-}H@XFK37!2BJNmatyK~c2 zD-z8R%t2^;_}|Vb<2mA1YA#@rj|A$KH5d7KTfiSd4K5JOqFKyhW-HtcOhKVG zk)FY0X48LRkXs{(3=wJs`2Tj0#E1j9OHf+KF?3Cuq|G8&h#Th-0vtlpOpHq{rjqy{ zh6!MZ0r8b&s1Vp0VonoaE1^d|c_3P);?90e%|$>rE#3bl^!6|J05JmU{%a%P0$!5v zr!rAb6OgNC3({;}YmlhFmv0%XRd-P&B{ja?`>qWy?3uUaFg29mcnc^ovGkI>Dp}`b z{_RFkaGeQy?e=sVy?v{EKFYwVzYCFuQV7}$J8|N~$Ms(WeTY+IL)~BpWGM1KysP+& z#9IIl!~tE^+N;Q`-wNC z`Nc=AFxTn~yGUJx24qEel07Js&P`CYit_P@FHV)EKvsOp`}yqx&K=>yFB86C^RT%z>m6K*7lDX;ioly^JD_sfRfhAb z)RCarWYhaKUz>15UP1z0ar}Eupcb$JC~PgeBpvBchJXb%*y5kL697N(j6~Eo{Oc7X z#PPK*-;5loaO{#hnh8mmfJBUj+6Tme3n6(f-sZR@9kGGIf&^JwyzN*`;EW!~lw+aT zfxKV?9p0n1moD7-j!<#FYo`mcfm;XzvVu8??x`yrGvxmJ`IPuLo4Fre$Q#I&p#Cj| zmC-({o)v+ukptl*+z4?qoOi$eh(^5O&ILn?2R|6YIC(kQekr^cB(2o<70irVI6w-@ zGqT)9VGsY84g(JfY}k8%QVjtjIr9EbMEu9Vf@rbob~LcS#0}pBRakbbpo&LU5Acyo zetLcZ05b$jJ-ilM;9p;B(lxQgKg%h0h4Y%yI;Was_(aT_??QsU_q|2mg?s+P)I`DX zn`n?KgB&EXM-7nai@>X!{lbbfh=YbC^Tf7rCo1vPr#hf{FDb2^OQ5tz07I2F8A8Gh zX_7wzpk|}ib{3OMdjOp!0c0<`*bXR3`8|JLKvF;X6JNxSI+)G=rNe3~3sMM*fFxV* zen7m#7T1ms(jboJUE6@c-mP#V3cL87 zwDp$dF9QdFO1lUxrh4hV&~!oUH#{b9B(gl+JCU%2=f1&ia$Ih%ds%GpnTR`pRhM=y z{~lLEAc{-cw4sj>vqT6J3&+mZ^*0W^v&$sePOzOUu7Ckd$9hgX5o5pADzYfK;h^C@ z=BIjr{L{k?Wx}R1UKrkg_1-2Yz~=$j=k|*S26EMcpvP6rU>LcFILsK zkZd4f^th9zy+j*D3`|MowZnjFHvx@u`UivA6g#C0W!O4p(>>uH$|duE=Ep5%tYTO} z#9*t|ip@wx(v?l~<5v}9zkA&0>H;XcHa zq^w{#&9~yw;b~fRYo=$m!wdLmSNNbsY+m~13N4|-o_%jEVdTsXH9ZDc6yl?ZzncaF z$D=9Vbmm=##-s*}o3vI-U4A|>vs}t~CV5(Dt*LuY^PcQsXvi@HFo=iEU)iwK+%0N9 z^yV=$8e6ZNELa(s6d->UxPu5;>R6mLOrkm5aBzGy{}enkT4_%QALVGT^FXxi(_rEC z8|uuterVO{R|YO7feLUyum0d%;wluKu`_Pjo2jFNv}Rxd(yxvMx7#wVWoC5N7{T5e zP`r`^AwQ~%{P<`M(pmh)^>O{4`8XvLBSb|gG3NjYyu>A3?{I5EoH>%Jgu5KA`pLt5 z=tlh{!s6d*xS{C*wG2%43lAYcyOA*?ul`DPtnJ)3qikSicueL{LNj61;(N7Vbvit6 zH~$*dOww9E8#>@6EV!rW63_kQ8f%(rm5>D;ihGN|SUS;rb!;Rei-lL)cD%}Pu63#6 zYb&8c`s)!7eMnx32HIzRC=`C?k+@E|gP7iR6n2#=d_^LeMI%Pydx$MOwF zS97v6iHH>=LVFS?v%#Xyrq)S^!4}L#=EL&xW3LpfD`Mn0nO4^N(W8oeL)i)9N7*dj z>pnqRb>oyCJAEPV)rz;4H}Cfu?fZNcA)hNvTcCbl1a7Dlxxxg`#kuXr_|4(?-;`C} zV#==zEJqf}%G+`%r|Ke76cT}2^IQiZj{Xht$k{|tPqso0qohb-qWo}HYr(j3ax;V4(0=-IMl3|N^>Txl`{_5O-7%+=yvlH0j|1*&Xcjqw( z;-#l~>0AUq3;P<}c z)?s>Zcg2(GGvj|}D7YZ}<5eWGTrRV*pA3NV`GQUOuoqhkz4O#tn3vt0eb@{cJJ|dk zx#?iVDP|p36&0s>nrm}2Azj3-+ruSW6qAj(tMQTvIgCic)ZyhZQ|ZswVVaIDt0s8E zH6q-J>wE}S&X1Logr_g4h)LiREeDb~e5J$QO|>wN1gJYLBDC@y-QQtK>x=j{rjh&nv@P{hNcmrt;)l*UDmRTO_!W5=EDbgH!0V@0?0C+j=EO}o z+l8byN3yUD7s0KdvA!>}fT<{uiwpi1ixYM4^zDx^q*S`%usXiVU^`Mx)r)3SL#3U< z+M3Iqi!Vw}P_bk1?U8#HHI0qw)4hV&&Ge!DQ2 zVTOGVa3C|_UHeU1`0dKdsDIO#@A=Bc)5~Dl?gR=&g4Ne+n#ac}LTr$TUMmr_Sp*~U z&swvDDeHVf{PV|;i%?7yRcoj|N3W1)nHeHUPsUFazYS^8>wJI&^S|F$BSn~|sjHt# z%V$DFp=tl?R4?9)>b<8%T=nuyx&IThSXpv^MAASmlI&gpI7vPJv-!+}(wz^uyp|FF zfX7gyP=}k@jTy|08ayKt=Z}%;B6=vvzG^FBhj1PU1KsTDm+*T(A%&?&*TY*lH3Zg; z^kx3t-Kzu7EDHg0<9UM+ng3mPDQEY$I7 zOp1^wu87|@dE571PZMxv*jcB^WZn!ipXFDflle2aP}WQpziBk4_8CmGLu=Dkh+);< zKE@|Aaqg|IgTv-`)kYA`;>XG^KfFVka2_wXDMiQiTrw7j`Tb*$=oII^uB2oy-osXJdtrK>Cy%JzjPW4_Rh`U^^dZg)#WP z$Mk)Q4Hc`{fF6o9w9zZ86_L&x6|s`IJcqoM%FQG3^aQ!+iKzqIPZi-4v9!YjSfUl>5~d=)IWG`{L8x zWW@hW6P|11x$~Qc@fD(|_on+i&T#`n4Qh8WqY+8_4v^Y($UkM~GN_g+2~us}#_Fe* zjb~4$nIE3e9F?*W-B>IcaEUY1-Y4JIVG%t7AIN>s zJvV@fDKXP3J28IdnAzks@u}gRL5NNU-wd+gj@N3TOXUYqpVzEb8Ux0`B5`N&M2Y6h zN){a3c16<7V6xzxD<4P@5+#8gDSK8PJ_->y!N!eNnhWg*b!pVmIYvj zeL2t!uTPX%=N0H#sdOD+-0bP3pji6$8v4E|1`5e;6Y~=RVHMG`y~qsiW{9ExumLfy zW2l2r#;i!rw+59a5c3{n#>YrHmu3@Dt+z@mfWemu1D+jU9cz?{uGBiqp!M;^juXB& zF4ek>hXW8EC2pQ&XBm8lFvL%p85VC=qN9%tTThNJ!_4*xd{61%RX`R;w%J$@wB0O< zCQQtZi@=MK+auIwp@$JCl58guF?adBkD}%2F=INq; zlsIUwgw9nM&b_XI`yi+}t$XAqt$;FoYJKzUZTh5W}D)hv7y z;ghI|@ufjf(pXCD&mRFiQK8E_t&=dc`NASEib=l z;w+Re!KW6btwi`R*DM78&2d#E8c04_>{{`DOju2`X(h306MD(aFM>}gYPEF1poF$7 zwoScGgai>>=|R?JL|iV^uZ@~C4W4-2o1Gm*n%gc32B|2VHwV8Z(TpyGV?@DGe2~XF zd35P``dY)9u84+%@+BL>rxSTniF_lLm-p&?Lvl#LR&K>(KT_+SH;eF0ZnK zNtM5g%_f`5AyLIWq|`2ys_T_UiiK`$mcey35r&OulHW2xe0&+m95+zuo5Vml}u{MX)j9E89->bP0_jjEYDJDuyQ*tL(eyCbs#)>c3|#-lrsK0Pa^VC<;kF9K zr+yTd^`sLmM2c({Jbetm(;>JjC1X8jk{R9U;zZ6HSW6w0%4A zJDxPdSYpl8fe-rmCr$z*(`(eKD|JJV)MQR5KWo3N8h$%t)Xke37<}z9d$no;6V?qr zb|*>+%cDJ{xsq5kr_g3miQlk62vT$mLFzl_=ivYGc*AvWxxy{ zK&5WeJ>M7M_XErXhyI~gqEw0^dXT;N#z{G{bDzx0nMA}@sI0_rofc7*H}ZnRNk$w`1c-VGQnj(g?7;JAm*t7hn6j9p1)J!+@S7?XORxeyx6YeSCan_x z=&T5zMu^SsJXfTaz_WLXC zH<0Hkq>bZuj-orXF3iFi#n<~rP`=w>$BId+b{P~Bt4qmVM6A)I@vYq+w;gD@2ZEDU z=dUE;gK;nh9S9mfIDEPepK>0nq${^E&_F4IF3UJjt`dSvC+TwEMsm*C`?_rMIS0ov zovV>agi@3nsAIA8={=ZX+A%c4LWyI)hYvp5PF!yPzZ`T*c5#11hAl1xX>I|Y{m48y zD;?Mtqzi%D-E}K50!QMFm{|$ci)FXT32KIS2(zrLKPSl4iw9mrDJ^nM@&fo1;4G32IFy$9~oE7ODqN;WGAvyu$MAGHO>IlSR~abRu-lqg&g_sV>oR-t-Bpk6SGrgin0B z?+{rgI!RP1gx!twrWV06Ijcdr5$>@&%RKr!jRKABg@n2f=Cs5%4Oav{@S7UQsrY_b z)ZvDc^|od|jam++dNOv0z{~qiTZSj<76c*2@;+pE61IO7@2z6(`4BWRc_mSe7=3wwKe7N;464ENtrF~ zs2aT1BQnQ3$JCNMBbk6JU>SdQ~!pcSK0^Mm=sw znU2^TXU8%`Hwg8?Zo15CsDGJxNpRv|sm65}-BeBstdeDSAi0qDz_(z?m*c=wLJnT` znznm6iFl{P-kdb6&oQfxrn_H}z2qgc5g&84!(+iBfT4`UJ^;;H2#o&IpXna`KkG&p zDAZV8zqR*aP$%-92h3cwsnYRCMc+a1pS!B_aFC!y`~#qcix2=Ea&gub_tu2YdM@vk z>#r;7GW|O7#{Vr-i7|Ox_e;pmV=84!bUlf9Y7*ii9t;Dn`9W&{P^)Qe$#=Cg8!yfv z8M%-WROv|d0->C$yINOOl#!N8bozXSB}cR#5EJEBu>gh{O3gIHzTJy4nZCSu*?Q|! z4~}KYK0x-ED1jUxMvHLojYgbPmm(|UUdl%d#IYZC!{Ae^!kPc$sg0($&WCv3$`sxu zKdoJ#MXciRve&d9zA>fC_o#=#FxrKaUw^; zR@p(b;Z}n^VJ5Dn&0D5NQu{FRcg)tWu!71@{eGsF?3LrF`w7QO%F>H+2z7+c#cagO z+}|-e($N|I!Xq|vaBhD8!)0*q+CP>OtOmQSHx3wucbNkKkd(@M?*{rMeUPGf#i|iN zR|19C2RPfl>LP*kg<_EhLIi4Tl2SsdW+Li?w(mms7vvFGXyDnzwZ8mHA~;Y z&>4A8Tll#Hvf@k!PQ}bL)?xv(EVioP>=3>~^Rczo_D9o^)d**B+KHQx9Q%=9 za9WqIiD^a_M-eU`4d3zw(dsjH9k{)5tlvb0B{=dOh$Q`H?#P(bZ$HxSB@x2(es_?5 zBe;LDv1jjVVOA#GBz0vdB3~|%r1KLvRKR$nHpn{_Am&3~Z$A#OSy1w$($Ls8=3rV zv^Uk86K)uN*&!_CclAi7@w`>{bR;zv>>+ES;&+hfRi|jR0h8DRM{>&4jdCo6J^DvU zr$a~950NMWFHEIBZYRoIbE02o1PRk6I}8Tt8yxa6bw<_=#olA~zG16$WoW|MgZ9bg%_yr(9HgN`1*B}6FPo!N&%Wb=s-P2c-_4@Vz4WX9^R+zXH!kK2!t?6QSeVNbfd9t=R97=7qyFS`HZ!+fY1SLwSD zaueibTHIeS+OpM~zYk*JlZS`igDPyBrtdCk7r&m?z|-%5%hYy~#NL{5?!&pe9~@f- z=tB2E)RaGleN0?5-k~HRthP+#gp>@c~vl%eT%SZAZMer7M#q3jvCP~S!_dTVd*WuyuybO4IZ)+J3koek` zq-;1)ejNjzMT7UhY1pVvT(C52OqbU6*UO2(Oj}RmUFQQ>%kloUpNL-ZFPgD>Nw~$( z@gXQWRn|{{d|?CSz2f$nxHY7HZA@)%Jldj&jr3-m)Hft)pesM>xyVRx9C%Uvd!K23 zGdSGdJ8Z7_womBi&cjgJBu&4Q4fmY)P-BxT($aE*#mtW68w&0H`0>P|v(0HWswqE$ zQn&8JeJfI?`iUFUlSFjC>P!J^Vz_$g(gWO%`*zWU6J z2mm}kB&(dqu^Z6t{iiFsDtvK+oMf?C-H(ii`@gO2xyE(eF5|6OvDy2oyAP|l-eZt^ zl2S4_5WSR;c*=Vq%$t3**KG7+T1wxC8&$%g_BtILtT)<{2A}{`1*CfQCN;%6^d9Ty z_Xpr_4atU556!}Bz<7u|tfyn1KV1diA7)ZGXV-z32_^?uBOAz<3x_tZF|#{N%ghrH zr_x-Z8P=2;Z(D=E4X@vyCMT)itDAZslhgYo`z%@7+ZUIArBFTk^Gn4dd-^-Qh$k&{ zhxX#!)BN(1BwJzA@J%7QyaKvRkw_VjAGvky?m3CW*i2Uuui#gl1&HdE+#0dfvvo&3 zaW+>ds2o0(#HU+x1%M!K11<@>Dq|ar@@AgWFDUeQ&GPkWwygYm{Uc{Icud)tif`Gmuq?ZMBJ zxK1ChjUJ{E3ze_#G`ZDm%Ybh=gv6L)TtiEdoGl-q!8*;|!Tve|7k)Wf6vF*qZYqA3N>Q!?=AbMBz0nW6pECzlEmfhqw_3ex>>j*D=8=Aiouj(vl2Jsy`v$TGVB+` zTbW!m7#R4})Hkft-FmatD_tSTDZ4&ke#ld}r|)r3nRVX|3En;Wn>VkGh`QO-%ck6` z=sDP9?hJAs+lUFE|q8oE4id=)KCj zkX_urpOPCPEpv4(a{0tqr*pjsTVlwQ@xi^#@dtuqH{Q|p9-#WE{8D3Eq-DxR5jNwW z{>CV&>cePXX8v*cH#@I;jNn8d?R~SiD1Y>4ejqe>rEJ2y(~u7FP>PAwRv+qG=pTc= zUn&ZTT|2f?Oyl>mA**E&j=A!Ks)A(Fs1eK&e9R}FK72gN;EjAS0YanwkCnB5;()}~ zRqy?{eeFo&+Cm&UE4p+pBmZ(WE@3H3h`Yl79Pmf&FNw7$xk@!t1pyCOQMl?$)Q;CT z5XdyW?&pUeAsOH_uhE}%fe<9I@4bzA_dt1R8s6l2LfB^>>xp+8=bFscR%(e+$^AAj z;KVl~x39MUR62sqGyq7zHP(AK`sBq#Eh-lPLw(L_mH)MSj$<~mMl2xotRP+cFE0LQ00k2CO7=0?_q}Ux6-+ey*4MJ#g)i z@PX{6OX}YbdVPQW{h1_bn41(a4XGzDey&9UV+XVT%71-1ZEYA5E}u+Q4EKU;B*rAq0YiX;~TF1MyoFR1zn8YE&C}Lb=FP2F>w< z33@H${m{(1Evwk`z|V(Ey_S>YY1v=Np@INEc&49V^mWf9S?C4Ssu2>ke(0i?{4Rmb zk9j$9z0=G$xLH_b>3zh;LFJqE#12V=%a&P{)4IJDsYJQfSl1t$CZm>v^Xc6}To^3n zx_WQ%iB8-PC))1r&a(dD3?;edd6NC#uZ~7lB8RjPJGa#xto;kVE?1{vR*XYmiu@h` zeSK(REg2Pl6{B;Pq^|`FqxQ;r|3jssVg5Mo3j_6;*Whdq=2?a)Ywh-i?m;N<`?A_@ z<`yze72vipe4f8!vmn7y2Y-FI|0nJ&sGni!K3d&%k?Hnp1dkQruJNP!AMdXi?K?O+ zhu~~t%Jg^H@ggd!!t$rP_5vu7!ZI&}GT+(fN(&&Pap{*2BjS0iyD<%#+K&wcijQzE zSd*3cnfKgdcVn-CCiUDbrVXVT4aZ?7FXzUf0+zLkd;xE*T6(Kxk{yGUEFJD02K2#s z`C2eDLK>ye59Y;rjj01khEKea{$S4qEd_Rn>>KT`u8uhO6TLEaOE?dXEDP-K_r5uV zedUsNRc~fbkT2xMO0wJQ^>#thihW;vrhV<`_q9dqrV3VmN#88su%REv{>y16^WU$9 z32pFz*^5#nGYxzyle;9l2l^}rlJwhNt#uaIA^*TvW%bbxbC2`_oRn3iFI^mTzQW+! z3S~1fv*Jz`hV(Gye)vS4-`5DQZFpZ1=MAn{DnCsr&{C`oO=dcTl&gXLnxi(Ewl}lI zJcirMIx0O7nyt?L(JlX{$xh@rn5W`#IPQD;x8mgwsq5a43|8d_5P_R0^Bj_`yW*$W zU`5!I{Nj$529nJTI&!W@t786u)(J-coA2sy=f)u3k^!j-ZJ@($BE#z1tESxuomcPq5r5+b_avuG9dozxwQwQHg zx3P1_6YKt_Wo6R{f~Dk6y zLrqIdyT5lS_xyP8w6z*$rlc$G9?S9{&e#Fm09MrgqPg1Pq7SYTzmHSUfSz}D z*HNKgB42EkGeRrEzP!GzH6&oL|3oVD38xcRy5w1~Z1z-H$hVV|R8tiP$(mQ(8F+@l z_YJr)?ajN|@lMQftI;YDD|K~c?FBNm5n?(AahRLs1uG+GSJ35RR?SALhSIh_eHnUzk-)OM!yl4H->SqTykK&sO!PMAzOD zweogQlAJJ=b{?F3rn1t&6s+5AWcYoOC*cgltku1Q1$0ls8Kz$BZ>cSAZ}*3pZe9>} z<(y`DRn9}7bwlU+x~jSaGjRq{5pb#8Cdl==H@or9aJtNSp|I|ATx}f)C;s$dc@Os# z4%{}X)(1h_d+6En$No6Xi2Up_=BsxBeBVr~oX>oM>oZ~CA1goh-yH0}b+5K3rrw_M2vEqE$7b6>V81C6nEhc3HKSW z1QKgYJpy9BIYklOdEeu+TKakt_nCwPI3ab@RZ`pBySI7|?pO(s{88g8Yx9&o!WhSa zTTYy-o(`sa7cfp0kw2R>ea-i0D~4O2K7?cDZ_i5*tUo%*?vc{q&fnwWZaxRD_3km& zBsy5atawGX@~+_?LGjn|fqf2dhl%yfWAN4084l_rWJ7MW8|)o;ymrK)`{_uv`gFnK zK3+>~GW#`-;jXke z&yq)*DPibL_L3xZ`JIq&4`?~W3o(wOt609siRRBZBA%DC8)sP!RZ3SnD^g!N zMQ;Ao#~eh|mG?D~M+iAy@3K~(&@OWj+>tsx2Ym*x-%u=>&Tuf9pAnV0KKk;Umz*K* zM;vW9p$xjei|veCJG}S+)<->TqFls7^X}2MvNPE1o*#m67t@+6Z8Y2Uy~AT0nO8t7 ziIH`|s27$^ytXFlT9Z*<1aWAcrbGwky0XodXX8#u#TK~?Ca-0OJ3n?JFAsey5)@^N zi*_}_`EZ)*ba9X7^+GmJ=Ox~^Zt*2I=@sEJF}?QvfMxwcssW#Q!~0+v?m`)G^ZF)c zrTTlq>D)^#g5tx!DS?iqQS+mo_hWDf|A`pTKwP6G#?|-&%IT%BPxKudXUmnnl{<>y zW2@`_S>;$n4ts<**|m`*S`OD0rd@iPE=r|60BJc{Uz^B4(m5LHUzjB6wBV1o;z-h_ zOF9##jWd`wd`GN}KcXu{Q`s)#>ZNM96O8nO!c5&4A3)}mJ-D;29%?H4XeZZb`JJOx z?$;4j8rWaH+#adS49H={wq5lo5JGS;m+#i8Z30x061HD{@q{%>DW45JDfU(?fi^JL zi0brEsLoe$+UefCI&a}eYyeysk+tR%2i(UUkm}iPTMy-C#-dH9txA>KvP&UzulS1+ zp9Z&q)`?WvTOhL+YV4~jV5=Zt`4N6T;c-k1QmYX#g^}bQ0YY}Vn)ZV)HLD@$5Ew(0 z>mCB8Ms6XnNU>P4Et=e{K>R#=5W-r2dEZ%fVuc{c;5TvmhnO9SHT0Bh#D)tXYQ*$p zjCP#(p#(2NM+A6Z>5_*O5zU6wyU&%Bw-*QI5jVlnHDQnas!ZHWF5|M?l~5CkV9~zv zc=4Mxafp5lMArD@v$n2TZVacyp7huHN!eU;(cS-aAY+tbau)L@x{2AnxFZQ8&llN$ z`+y{0Lb%y?0UUy1pFgN|{DuH<0C=xc@K>blM0&*oPC(OM8Aexke&N}`KL4Q0+17Cv zWKLYNz$56Rl+ogL`q*cI`NAbGtb-YH3`!MJDR4q6VF(9nPF(vm1BZC+vaFz$-2V$u zm(8mdn5UIs&rk69L0FL3C4YEY5vQE({aU*H`S+}t16o3P`g#Q-*MS-S4Tz*;SEBdV zzm!)5;2fVoRZNnpOYCl}#&2|*AjC4mFx z9?#;SzDL~e9It_=W&y;o7wsoqfV|cmI_5hj!7Mom{((Ejo=$o}^t~nCZyQ>pjhVNM zs=SbALU`mZjy1#}n(^(?x5{7W>fe+i-J>o=>H7sM>D$sBXP?lIYz*<%z{oy=-k#yw+9JwT8t+F{y4~1o)r?g47}G( z(90cz5unllVlPS#+La|cpvg0LD`!n)%@Pu$@j>c<7l zN##dQIu>mqdwZfWD_srw9pm8ZjLyZ}mDzMr1{86AU1T+(lQ0iKs8kpW?xTYN$Uin> zP6YG&Hw52#elenQ3lZ&92(uT@*;St}AFAFH=wjPQZ6gO~%XaNa!*0W{3{z=cq<>U# zG^#D&iZ+A!@U!Ql4T*!OzO_eqBsV=x+VtK^c)IVTwH-W#mW`%=bDdOK5h~-8xpIwO z8NnHT6FUof(j#bEl5piJ-N~-R`4t(kh_2V}{7n0Q)vs3QG@Qi#B6kZ3d5%L{aZ|F0 zb0Jf7*L5=B(FGkzHYo3>FUbp-7x5XZ(eaYg_^G7ng8Oa)iDQ@e&i6DFpWFkGgw_}8 zG+9YtBWKshkz&6E8FdQThkfFwk#(;|lu37+J;(#h9Zzt6zLQvt_o2u1Fg22;!)8vfUy&{=Z15X+y&2r`1mE>mjXs8PGF3Ufz#!l zZ{Ld7y$VAw}eZw+7rq3yqQiVYDY-fM%g$)sfyg~~o9(_#T$ z^Zg5wX7=#>-6I&F9vX|jNQd=63<}^LNgkEaffs?uWghE9W8u3xKk?D6p4J8V~z_?AtMqQTePm66~8h%Ncrj znG(kA+;EEw`5C4!CRKOs;LCG`bMrcncNeg7Ef5$P`m#1qzRLKn$Fsm+XZBw;ja5m# zgsHn@th|rz#Q2TAq>_k$3WHeU1;dT3{>V6RoLG#u0IMP3TD_-bzLoDL5jqB0a$NUN zX>%NAdM%@gHdz@h5aVhF%BP91dJOydde9ZQuBY23-`=#-F8#HUx0Bmfg5Ew^G-F~t zlyYK45L&5CS0wt-ojV3A7Lh0#P_R?^|3lR0&{_rthS5>Vz^$lX`IWy$Pkqn(a3JGx z_c{4u{?+`q+roCs^)YMSay!jGi&y71P2y5D%kABpH||@1``R&UPkRZYQtILG0q!ToIeBZiB?Qcqp;J=Vc z{!kXucoRzLJ`cJ)AnF5=X}7hE)H#uQuX(6!I?~G<))Sgi|3YuooWJX=BwuaBKN3L! zQ@GjigKb;q-;+7M3SU{Y5XxA*hhD4NTtZ-*r5e-FO2I5D+eh_V$EgnJQPACbwm*8# zz$A!@n@ju-(gz;wh!3}a@R=>d#9bndnzHTOB~4lIL$6?=442a8}f?s9W|NfolKyBQ@5cLvNgAK zldj>R+-&ia>n4^&Te4bcD>gFh33*i_L~62ivYF1vX6^@8Igqv}-AV=z%bGM>|74XV zGr>hS-o(hzU+v~*Iy95?P3;$N)*7J$Mo%N(Po8H6CTlT_t1M;nTly^0W}%Jf;9FD3 zyh*ZQ-Zrk-R-yo^mw#rZ9GyI3s0QSlZTq(A(qBnz#UPGsDyA>b4QMl@S~7I8H3lt` zvQdKTPjY9ISDAJaeDqh9R21hdUrA>(M7=YsQ5Wg>7o+vyezCuN{v3BPSv{9yX%zaa zawPX>&1T-cg3r6neIjHJcJWU2*<|dmz(iI` zdu2-)KoCBw4T<9!bZq9WX5y7Jla|6*o^-cC9x8F5mTbh3E^{t*}X3f&x~m4x?9{bSA{0x+`@ zOaZlYngna(!-}`|q5PyaRfe04Y@y+(uh+3M1R6 z6X=qFynxm^jJ?B8v_`-t$v%1bf0 zuVPZ$B}!<^@Zg@-sdz=PBsC>JNRYwO=0o>oUxn(%oWw)9nKJ5hnTM|aW*Au_Mjr8p zwvtLqR->jP`AqR^QCOdhy~Z&#_aX|Xg(w{PQ!Z2xxsdbjT*a6yWkhKfYCmBbiYN-j z#9WiG*hQFC%1btILbId?F+m`QuKe8N$H^;ST&Bbnx~SNJ46jrK3qQPFH6dgpTDVi= zS21bKnA?yoconiM@D9CS)YmAkd*<{kQnSc0)Vx7%%5k-o=o0%uN)REdaSsj)ZEMHGd*R$^)+LB$YjZ*wA6+4iw&v zN&hHIhVz@#=s#)O(VC>_#x?ZmP$PR?f|VEtJ39GWyhno^&Hny^^!`n(U^bD1Z1Y^l z7)}Yzs9EN2vmv!XAh1$&h-B?PePT>6nNUH&67mTTy&IGr7^ivSs)Xra^hJ2?SXr%` zZh;>*n;xH>P>epx_e4(Tip3R@S9b{4XS@rm!Nj~JdPc6DW*I-3t;U_W@@DdoDQ%>! z`^OW62)&|6O?O%2azJe4cVRBsvSa&sF%YqQLzc*&YFf!WP>9b~ClPXlO6fpNpN2VT zBPeV{#%}v`+c$kuSfl3CpK6wxA_}+Iuhrn9^8gD<&t|BXBHaijn^Q=@UzhDL09s0a zsQ{616Z#&Et7sXwzc!vt#6IShO#4~x$Alq7E`ZAB8PjfP%s~sKHe4E{dz03eEMBSG z3qE0`ztSUoDKD!{8VM$QAqQ(gqOBigqY^C{_uuUNgnm4qRu6Q&OdA4T9cHD2~&+yiE2X1tkJaj8Um9mAJ zm}88#9O+gWYmP;4A&plO-l=6%r%WYNNjsnL=UzY~BbQ?nDWwyXDvQCYZCb6vL0b8x zyM8&3F|5f@atW9?XQTxpv+UlloJJoOj|Tozogc852oZ_(_=k@w5a5GJ_jx*TzUZ^> ztAWwQhtBRATP3{cNz7m25+a0UwDFI#YT*nvXQB?w;&Gic;iLi`fAnkN?&>i{ZXrS> zx_wgu894+3zYp~YXz^slLe@X#n}O*r`2=t-($*HTN)kTDKtxf`;(5uoeLG%9kiPK= zivKB$9m(SR77@ik#6pPznHYOB8_0a~F!bkZhP1q`03M#%r>;St5_IRE&if`4Ht3xb z>qTBU66!tYk7`9D;Y6!mp{2etLJbdR)rZLa(QC>+5XC%sUBV2~>wBc8A5uPp=!P{B z3@Or^vv;YZu$1+MxqlX}CFIQ=mQbd@LRxe-9R~kY(v9szgVNv++M7YnVi^tMPQN?+ zci*XrgfaA-w(X?9k{~2lr7A$S+2Adpa>!emP)TIgWD-eImf-xe@lx_C+eKnN{gp1^ z%hx3<>FaADsd>W+ofH0KK%K0A9sZMQ%w&dWk7yF~S2}A!-rf}=ETk{(WVr}dmaecP z<4bucvGkADX-G)Qly**e)tDTgg$AvdbVK~n`Ep!d#At+MKikQYZ;cG*_-CsAnKdIa zOHXw!%%oSGMs|CB9WgG__!**~b!|~!j;k!;@zqKg`(}I{G(r`*Q%hRtj6~f<8lUDW zOSBLjA~9VSiTDZ-`RKPPcI<>YfnQjOY@h^-jETHb43@&zPF+k^jIJj7m0o<}>Sm0g zySlZK?+@9cLu6LEL*v+)UQGi3vD?iHBEp+C9slfSaG1w*35ul5ls~4NfmxDB>+Rc% z{phPimvl17;;&#b2TG({?Ch&4x!q{dFfvPA!gjkxdxWL<^ll+9rk1;SM5W47< zZQMhDMI-X3iOqp*vs777SC()dAFS(B<_h;iGr14L0t{oU_Tpq>s7B$ij7bgkJ z%`Bq|^N;}*&Og!cC)Ie!S?3tP-(U1jQP=n`-}mTpt?N7Kk=+;i`dVK=6wW|Ezl{`fc1eaWgmBsw-;Nj8{WYYkbl0APwV{QykT6Wp;H*D?^y zNN4>aOlASsl7432y(l$%QIXg0ivF2M4zd_T3f<_h)Ry=nTJSRb8mS3QZEx?!-DNEY zRyOwY`UZMe%E^v=c1=I4FFBev#NQe_fYL|zYond`ibs7n@{vB5EWo`ik@|U>GTFr> zHcCtLD%}c4r4Uv2u*}!vRC1)1;8dZ@%88@aAxEg~&MU`stdollA?zg*0Zdton<_pI z0`IS_K5sa%l8F(r)kplnm}WPzkK>PF+-kFZfnZ#n#IRLmz`W?G z%LA6Vmyr}&ZjA{Kk<|>o-5_76<%a2yq1fIhc8)BGN&5bD-|x>?zYuUJp2;cw0-&w7 zWX2`;dn!1!!iwp=pvi)Gl8qaZJqY`E^u_i4InOp|D?VV31D$gzGnrL}I@eqBk|5}V zJC7iIbBPn;Rppl`^Nbx(TFK{U4U+b~h+6fkIr}J}z7YuNeMe%LED-#wc(+VpSJJ7F zwjn5FIRgo6=nOKRU1>xSPAuGLKE?EQw^TIJ2D}zN} zudsd12NYU*%e&0fqx!7tKHeDyqZyXGhc`RJFAn*K_b58c zQ4R0)g=6}m$jJgAftr$;?%}#Om$_{S09h^`bx5_gC3uYHl|&S@e~oa76&oJy4$Hsn zH$$m}gy1Pe_EwqI86U1cZUGG0S>@-?ACdFI&MPrvJTsoYB}8R6v^QxbuPBYoaEKhx zRvGw|W0T#Qn3ThN7RVwala@oWbP`n(jl*+@-?I-`b6Y^5G@Rfb4ae;OpbA;fY_Gk2;Xup~;MzUEL z9?)wgYCB~!^I7G-olcMVEGu;zfkXdbIE6$Y*(zPVFvgE#`_lfD#nu(0h^<r9Mb697C{m9ikVQrBXaT^HJr-bwb z`YCKW&tb&7N9p;TQyGkJtq}ya@iTx7iGypj>1)aN5tc#u;n>$PEB8N{?Sw41ktA}g zPcEe&x2xpxDZ8hU%geUlpEBfV=CfVC8^A?HM6k-^E=Srx*Ew&2$OWryiFsbZKI+dC zV#y*<a3w#z;{ zR(D|dN1Y<>U~r6ek^iV2f4TrSZrcMsp>OzU^r4YW!j1Qdk1;{1iQi9>kxie=)*YW{ayVFLmY^Oy#IFJIpt?|nYH>~XMaazas>&Vv;b{n_yecB z#xAKNxODHI@~@kifUfMinv)q_u;|>Hy$bIRthkF{86~0QGRbl<(bs3WDm~@vEf5xI zr>_vM{R6Q=PJXp;m&+r`u~Yk-(;T+T?i-|Yss3&f%3lq~xR<`k$riog^Ct}FCmnPS{MZgykFXabrKb-U27}4N) zHaV{CHEFVWke>D%FR>oRUDpocYpjec__pkn`T+0Z)bEHByo`XE91HKBi+`Or`vBl` z`PZ~p+V+P8ms9tD!WDK0iF4Cn_L1`&=QzA!Kv3kiZ5|8hssr^qSyw8JRx>V$Z&kPx z@6>okag$r%)%0#}#9}Q+6qX#SJCwEdTM-CoJJ{E5E4Jk_cbg>jOY@q+~q% zhJWp<^cf$&>U^lykf9pi>m`^YR6jx@DMFM)X#PZ!eJ82ph>&d}+rrbT%rl14@(`$Q z0VX^n(6nzc6K<5s&qfxCvQ+P3+eK0{%C1}j8lGtTRg zi@PpKXVml{#zlJmIW~IxZ1#wi5na@IiakX+owviW=7D! z-ZwyKe@9SiIPxHbBTs{(bZVjhxmBO%IUxd4N$3{8KbFvuY1cdN`}pWzwq=QAo(%wZ zfT3fl=(N3ZU)q4gHGi{nAE#xkfmE+6pc9|XLvQPn*m0+(iw2jHFV=Ju8HAO7r1Y@B z*arNDNOS%V?n0qW*B~zy6xM6K(WEB!NjS4`A5}Gv+rWB<;}#S@UKieFNG~3aK=zfk z%;$Fu>=ee*=RLNN{|_0(z{D!dfV)GCwtPe&r)-$9Q4cZuXEiFT7F)6kvFEC%#JNsLC8g-O9l~xr!*1wjk%07+XDWI}>=1tP`O496%Mr8?$R}^3yX%RJ zu#Z?~lUwF{jUO4am-&X1EG3@@+fvU-zs2UZ$dZ#P#mi1-xL{&sjCi#GNh6j4rx`X- z+u>z7g80x!7I%PORZ>*T{d!09YnPFmDPU)xkymA@wIH={XZD5&1Y+8ji5YwmMz%kv z9fnYz9x@{i#U5L*xl<9R;$=h|Qc6UaNjg%!P&E#U`?xbWSV-aXi^cEe^V4w*594nq>TClA&`^@PT zxG2|BS=IVUbP>X2s!Bes2!8|6_P1oFXHl!}?iePa54r22wWt1luC$|{v$beHwzBh+ zOYasv)p1zY5r=Cp@(r!79Zz&+;SMu(zS<3#>W^qW(QM_QX%WWfdwmdg_E6Bmj991t zb``+hr7}9?^a}U3uduEVS>!MfBz4mY(Q_h-uGep0Nb^swTqJ#OvG_lHsz&@B>Ia3N zgz+)SO6R||h|C^b11bMGGk@6bU2E*g3gE{}n?|emo#W$_;8Eh~+5jKDGO$MSOon6_$wgPMG;UnwH=g7aET^b4uXueg5`xkzsp^rSgnE&LI zZ}&YB)n$Pw3yZ6seZFI^F*%!$SAHS&SgH`(CD}v@fW8b*> zx#!tqPb|GM1chiCydZH_v$fKa8f2YPdD%HIkPO2;$9Ze?LMuP5cJHye3pB2qM5>ox z=6grYUEW6pIg01fm9=Ja_?;>s@BQ-V2ZCmR+}v5oQ2-Qp$A?ib1RPaGZIu>py`Shk zI%3iNsq}W-Qzf5b2A-ZWgcsd*=+2vU>X%=sd^(fSfcKNdahI=-^u&EeG^89!wD8p8 zv7nnAA`i}GBX;Khu=SQvQFUSeFyKgvG$Mk8gtQVO9nwg{&>)?nv>+WKAc#@|(jqZ* zH;B?5Ga%hDq;&J{b3f1jjkVrSevq|@b7t@B`en+O*S|;ml*Fj-et`XV=|TgtWHh*? zx}2A^j!+~qirR#=>_+ZL9qnorxh1Ubzp13_1I5=U`u z8?x0zCkSZy?~??y+1hw4T#ut3auo<(%yGs^ioB0pu|F`R}DfaZ;S}LaE~5*KH*G zpRoj-NB#8ly+YnN8jmZh86z>u`#gdFM&Qxrho4FD-u&eF|8KILDg?s_Mr?~22rUR; z%c}j(gs%tmAqFuH8=Nd49pJnPqz^?`?YH2kI%x z@HXzB0$cds%j=VtHx~zQLEB?7CuCE0)UVP)A(%2|z&IGu(Y^G)^CB4P_+tZVW*<9X zfLWyM3|ejyXP`x3HPzF^6$omD9G>-azIZe;0G{Hnp#Ny^|Gh%}0Ddu#L+KMFIg0BD zx@K2_(B37sHr?;sZgfANWx*dDHP*#-t1h`)rN8g4g5Zx5@E&(Sb4HZU7&}wDcW{pj zxKWcWqJ`w>`k^BZ1ytiFN&fA4wmLrwv7nCMRWSYWgU(y7he?nAJD7(&z`<86mXmF{K*5ivYg{xZ{7HO>pCIZn$VvUSk-2% z(gDvTSwS~2eFZGd|JthpXxJOh!BJzk3dOT*7TClUZi$|+Xa59^=?m9Pe&EylwJU-5 z{|09QE1{+KnDWPEbO1}0)$s-Bb9d(W{%pogE4zE!WdR%-p4W5BL?+e>DI}HUjlc@e zZFeRTzq{|<2F>4K^xAyc-17r1as7{|qK~)t8z>`c5bmw9&oKi_EFaXI4vqYdKUGuh z4Xpe?EXozaFtv!nfxU*s0Tk34R8OF%*=MsNxecB<6_Nb!dS~fb|8ldatb@9WWo1C? zqD!=75)Dp%4=YoJJIdaG@qk69ueRp3d@y7 z{g%8|IC}3>lRttUu`R@XBV)%J;%3*tG}JT^Yjtf(8O>d{{57Fzt)246AJsbi2{7#U zO_W(g&AKiH&E5m10KW>5Th>lHfD{K{p}iA3fcW^60%{s_9W4o8<>w^eyF$;kA6T`F z(uFt7E5Wr8GKllv@$|pT5(((OP!I|P*CcQ0tB4=yfjn`p52h#jRP2+>*mfFSxqqu-DD*%~i`DjX(F4{?>xJ1dg%ke;8jB z(|o!iDqi_fY`>#{hl@mMR$JDfH!YY*I)k9`E8J!eY+yL{o}0}Xw!V}grr{pzzj!+r z%x$!GXIFiRy^AUn#QFiW71TPDi@SOsZ~e_I_s9$pv2MqpJ!bu<3`c*Fsg8prTmLiF zS_WcJVs1GSGc>^}?G+H+oly_g;CS6(>tM=qyeoeCN;e-&ubpn2D6Z-t;a!yB8~09j zV>8+m^`um|atzwDW(@#|u@*{{^vO z&|J3*Ptz^0zX914{2q%_R*(JBkel6Sm2gr7RL<-AC)p-`HaMF#d=+@xC>6A(zb;gu zf5xg${4u#)r$MkcDEM;N;Av=C+bvhHzjY+!POIu;svG7{@2iw}%N9}j0AsA&5z^j% zr3#cNCn%55Le9XbVumyB!awaHruravMxL6ZKJPIRqZEK>jf26w8C^!^u|v&sFMSF6 z%dj(p2jL<8+{d%va9Xq-xXuRqfT4A}&`f{W+!1+n{nT-yG-B?lIhoyW)h3Z6&^gsb z<7yPMf`Aa1E}x3aXKPPik^C>09%xPceDUL>J}|tleMLS0n#Ln;+bBJl4f>J)u3s^A zLjxVJ#s5;K(6`rAp(n7%M#q?9fiJ1kvYP|RW(*-~29R&;o#^rND*YDbfsiF#Q!G5YU- zB_q|4kS7w`&no%I?!jtF{C<0zzD+7cM+G-sgselCpT9AbakYB5#ni%4Qb_PepW?q9 zHCvsQ{qkFPDb|(-hrJf{xl7B-X&9o%r6oC@xf7a`WKep42LwqRW2-BIJXOCFN*QUb zSHz!-ynG)yH`)CUvK~hG-aJnC<~3gz`xo=#E?(iQN?S22yA9wzR+bAiG{Wb^s-$}U z7o49-Os{5?KO|5b-KpaCX!ABW@!pw$A_w1^YD!qW>>$W*{BFi6)+u01SCNuKb>8o(V6BDHXnme}O6Uuk%)d zUTfVAT^FT(X9xmFOf*)Z`AOoQl2Oe`*;wa?Y%w{(Z{HvC+f)-@ehLizni`Q0M>8s0 zuD9%~C*G0rojxZO(x$dd7)n0@-m3($D7^Ucj~~@=W8($leg{adoxW%2AG0N)SlgdYXm(k?IyZpacB>tgZ!ZUQn1gGD=pqE%SP(=JYenBH%Ych;*>}jd>pIb%jy3>WQt04h4>Z6K>sS~KDXG)O-P|3-}2EsD5 zj%AL{dp&a=*ctH(^Fg(TfvWesZ77TyQk!?s6UfeHEzedG&$0)tFDW5;%sp*!fB z5t{jDP~?>J<(_l^e>Jrg;M=~+&F}ptB1-4d*!-A$Ies8(5pIVI9tpLcmfZij(k7K9 z55!PKTCa@d!gievp6iFNEFUK{Ueyi$WSmHx)((4R{mQ!UyV-K$1+caAL)+$;H}4wi z$0s6Nkt)|rD0KB}5mCRA(R4!Bgo2sF&^XiP81a3XvZGjTDVjm!9|R))}+C(O#=tP41Qb z!lOM!;*!47sd7EN7<5@<$Pg&P?{oUrg7wEzR-NxYp(ik5Kd0N>ud(mn5SiNnr|0K7 zum$&lB}@0vh<3uUqQ4IScba9UC$)c21`b>ntgSN)4Vp80U*uU^^7f~So`cAV?1q7* zk?<*-H{YFVQu72oG=;g9W-bct#45m36)~-X8}E)(kX=USQ*hPpqMCyLf|MPRl^pM) zmg{q;777unoSeDW`57L%;tz|H zBXgu3gjnppJrASvvvmSEbOnfC(q4I^eoe4)qxLHz^;6Y{VTPGDo>K#ErnVC~VkiT# zpSxkpd@CgN3lAt90I!G~$$TC1%MXgnWPoGMt*4Hvx_OG8w!DetuFtzK`mR)HKH%1L zgTLdCxZTRd*C$RnCmc7kdU`ofx{=(jlbHA6W6`*}U39O3RNni}u_`HWAtcMU5+$CA zyB@eOuE5D4xTVaTFR`eD22`{dXFtV{!m)K6Hkfd>_*lfPTXklY^AL#=dGl|3k;!7_ z#Y)lSSWP!sr(xRPcLhFY>O;sj2FKomm3s{UnN-CqOS zd?HOBs8{V5fJE+3;0dm&X^Jy%FSi&nXX)^2hTA%@UIlX-*TW1Hp8`x#k|S%B((<+> z>JC_iDkQJh0E^e(sw&q|?X?x}xtnsCVXCz6Dx=CBT@e)nwBHC$p zyVH8RBmWk^D=d-ujY1>s)K|+eePZ?c{i?}A`qg?kln)rHKMmrs%u==K^aVZ=XlxR~ z`$+G#Z&w6G&x1^|Y(&`FkD}DKJl?7cm=~?Mz2sH=8dN1?o^{zDS4#%jYkE%l_y*&e zOq6npj*so8x+gR>W}d>~39>RF)VQxmGO%sG0~w*VOu!n7=-c6J9mshK9rV4q{Oq(n z?;n#JzXw;i&sjC5$(%$3a?z5?1CD=F{QJ$yiFMf;q;^EIhq~PqFEisg_GUDlbJ4C) zCIJVjHY!f5JAMD=>K9k`h{B7+l|*)+@v4$8mbOYQCrJogD2?wcVrNPNZ)&z{M>T2$AIZ()Qg}ZY8yYRdy4rj%aU9`w{G8 z@=@YuxIs|g{(0k4HK~a z84}zPlfGbjmvDaxtPtf?J$*a6bn9R;alT4^E{;A>$m8%-(mw z%=+A}s=ojw*qy#p@cE}il0qVZbkfUoVfTUu!NbdS1InX%K|SajZ4Z@W#O_bOcuEWf zpBAtc;PCPkh&LPEvd_|4k~QHRK8G`QS`&YaF45z7&rMKAigax^2q|P&HdaPa+v%+F zv%5Un%;en=lu}@U)+;ra&L-CKl1K7De|OoW#f~dEkAvmn?+X?L%I%xZnQ;ItowOQw zo->e$3t{DxW92QQ-zmXfsXV%kl5u_lEc^8YZE7J7%#AmNzj%wHPv)wQI97Fy-I1-y zOhh6Tio0C}w+eKS$)%MnPek-Y)tj{6n0j6j`xsXpN!%$zPu0BH1H+1Nv|@>CP74ym zHgOeZV`D_##(OLpsmalI#qRTZDF_=oE}F$d`KjYr#0B5L^GBRDJpKzFAS=sRmaA?s zvE*TtWY#>fs~vuGs(*vn5v?0mueE-ttu)^FqEm8#!lbE0*lsBXAJepNitB z)Z1#hgfk~R?oosdjD$X;5O=0eFbI4(p*d_2dC@_Ed1e^Xmajn` zah&NdeZ2N7ocnlP_`WT{0Bt8D=ELlsint5T&ZxQeXHu|6;(1nyGsPe!S&b2;*3XfT zfb^3<(+PJuIDiwYz;t&_v*0h(%78$>DSGoemTHpvmsU)_fK9Vimm<^z9UHABsfaj&SXt9|Zuf`L70S z_1uD4dISy%$CtPW!4q5!ETL}5pKDiV<>bQ!8OURCQ|DoNO`{=tEq;@n;JPY&%q)%_ z0b*0NQ?L^T0~l*upF@MzS1il0%0^%Tq)QNtYRH!wbzOug35nB`L;;#?veuar=(W-$ z)6xwMtfqj2YEOvH*L29{$n5Z%&b>lA&9>UYHfLwh|N0=SZ*2DqM-hL05C_u5Z!v$? z6RGL-mU9Z`b7N{4g){wru*pfM&-1DJu@!ijN6 zhCDfqaliNB|0?t8gJImwr;D$^42Cy1e%Vkz*K_*>fYlTr!YA97ra$T-5LA>UH1mBd zbd1;JfT`oCa`;wdwY<$TOcf|1*1*wOowAyJ4P~Y9Ga#Z7*rAuQ+0p4N{c6Jhm~!`3 z?d9RA@cbiYzaf10x@YR0+Wvkn{vhCp2oA!BUjYUX55R$BU3Wf^U!4!q4 ze&6`qWK|nCt80*19jTMKaN$mNytRB&tw*d&rQ0N`9AeQG=%QTXRqN}T0j z&Hb=}Ffr(ds|>Nz4laQ-O`E@32cnsply^z&{~ys=97D|bS^}IgVc#?N#E-D=Jza;@ zlT~!-td&4`W^ngimaU<$Y%!}qb1*b9S&l5l?&OOR*(m+c}K z#GpZi%}%$^ASS(#F0WZwKG+I<`4B)kNqe_eMJ&!U5qw&lUGXx9*2LT2?Q)t}YRREg zy(cD5%K{d?5SaU(rkq{hGX^WDO^ZP{u4ga@`#3`K_}*M=M%sE7B&c+C9z-?;wTgSm);o28_T(*#7bK2rOB+D0%&4@T9G1 z_8)(ltEs{9_3^y0Dw`OH;G8mxP?i<4U_xtfHL+ zDoHqoPp+C_zT5{Qx`!_>>bzJewx$Kf=%-Uux#I1vitfMC&>ayD6M203WWlE zVvV(H50b$VR)YhYSJO=Q>ys ztSv!E7@n$|30s*me^aAMp3LUJ+A5@v6|Mep2gtO$MqQP6Lvs2i6lv&nG4=Tz=K36r zP+yn#Reaml-|HV%!0Nr#Ub+IbeifxG_ZK<%mEMTaA}ZMSC4=y@thtyD{N*4Ge`^27 z3N(uO-i=`*s^hCR(A?k=aB`TsTu8b+2AZ4E(qV z`-vNP3)DM(7wkhPDZYAGG z=gS+_`7{9m=CIujz{j`W1aroN5jMdfwqBC{3U*iwP@b^FtMii&O&z{KWMYFe0tyX|Q4^fA(~;LMAU5XLwC(-W%*{x5s84HB^i{W0#gEA? zTEdi8Oy%dMm3TOS=(2c~{!RyBhyX{OWqgvxP&H`O*Zu8F;7VcJ428q*nQT-)~%M5|OK1 zU(}wf=#pKcgI(H))2-83w0%#dMR~6FY%YlG`aqs z!`3{|=Q;adogcYIKB>fz`gbX&yu@}c2B|*+Lyr9JFe<`ybfXCKZwkuDC88HM5!*m$ zbstI}9jH&CR9EPNatvXST-AEdj~ADEkw4BeA(ZQ6ok_rr!YaHzzR;uG1defeWV+=& zj`gU0_)T`w;6HCPNm%bV0fQt>>hbOi+@b4dU7C28-%bGIk&t7*QqAr1n$N5aHts)m z*#!#MryD*|HjvQAxM6|t*kZB+`3tJ+R2jqmzxHO8GEMQ=T%?J&18jwd2g_MX=3dT9 z+lOKR$zc%R#wu%jmChskESGb^QDatZp4DLq9_+p5dStkky!yT5fjQgksGyozZ;mgt zdJ;)3Ksxv#RX5&vb>ptsq59fjpS#NqtKZ|Txb;pTd`X1O)VmWPFVVF;|vI=>!b<;;vywes^Aq9fCX;KI&4EcFQjr;EK(d-62@Ckud*!a!jx z>k0(GhAU5QBjj<3`*qurm4ZU>nhU9MIViPxc01J;qwJ-%X*7E%Wcm9WUxK5gZ*3J) z<@t0h1IGtx*SV`QnH3-!x1W4jouj+-0MaRLHtGJ&Dvj4o>444HOYNabWsPYZhZR$_CVm`Wn<{ zo>nf8H;>Sp1r2D;MBEO?xP0|W2!ZKrXIrZ8Wh*H^u9rXGyR^x;iC^^rXsDlCGLp#i z=h0AqJ36yPARma+xD83|e8Vn{2=xxZ02)ie1Ju=zuk)La2ngDP3SnKBKhfC^8n^7h zu@xf=XJNg zW})Yn)Gyu|He{K!upY@*1Kq|+%Kp-0VO_E%5d7uggdgicq;+N$xC4NFx7btm5;}q} z4OM_@mKXx4#N`D}Z$v@xKy0LJRJ`p!8MixfJGb0gxUH$hNAIacaOghwDz*tUx(g?> zB$QsWqsi0zFa6i1iO!XsvL@jwDo@_1`TFliP+_imal!zz^?zWQ4+99vXfm}S{5{&Y zeNbcFuh{vTY2b}jn5aO0Sis9u2dD9t_kUhP>4a;mO=&)s#s*861~B5pu;bUDiXo74 zd6fYyv86!JfvxFei)BhHM3~n7BkE=I8J@HIJpPT_%P2&5GjoDooBO&Ct4F*J+!^tm z!}#`e4}px8X08$0$7ZsEZ7{Ub7zr&Zt?jptixe9z?j^QdGRM>%eW4#{9ox_6H%@N?>Yv__)c% zdfDCRUx63#dk&oB04VBp2IE6drW6$(T$?8+*A4k0r*F73-jst%G!mc(ow6IHYJzff zbK^NLLfD&a+e6iqdDwZ2^7c2W<1^$t`D^pzch3tqO~4JjJmi@q#7==G!Ggp}W`CEl zGr#Tu4!w@IKPc{4ulXNR=h!AYZbz=3mu~LH-aoP zA5y4N@PxmhABUBnPLGJxzT`(Y8Si2``x}^@0}6Asemh0qtIqr}@+>jkwOzw7hXGhp zUT`ose1cTld|^Bc0Uer^$e`eg0a1?AMN`w~TIbg%8AToU`D0i(g3;MtiCBI#vc3ZP zpGRL+O7O73_%ngBX1C!sNpo{Sh2IIamN(cpXL|ft-<2K#xp4Y%ca*~ChhozI0?>s| z@{?7BTLxVcAts<^0U8!QG9JX>pnqccl_p{++m6C7;ah9&qXYMqAmN9z zux1|lI)`HIWNK};O5Tip1*6QnWkGPWLU8Gt3Ar035cP=N0q>AP%>1Tqe`KuBq=NcF z5~Y)XBuhmswR!<_-mFr^7&^Gkt>Ss6rk;Fr&Hv_6$m1@DN}|#XwSK+si*sad0DKM}NuowUvpr!bGAl$AfI%LxKn>4B?(tJBXRL9>CKY-2gnU14mTiuj~PuASzI~R#^7+Z#?i;08xj}1k6?Q;U#nyx!K1E#%`lMLiM*IYbrltRLmm5Tda7RR}E+q#&OEM=ZhZa4VdStDkKES zkf7rncuiAFYgOanmJYniesVtwxbHyu*Fe9)GMUYR1`F_d8%@*$qb5B z-uAp_aqa=2o8gbjSKSm|{v^9j#1X3NQ=#`nl?w5o5u5*to`tZd0wZL7_aiEE7YvKrE(0ATO$(88#YxL+y zvI!1F4&3kqU@wwW~AT}K3;A!I@IO7nVNRK;p6{zE=-I(mi{{@&?`4{W_vie z-B!f%1;Vo%->_2SZZpt3n9i?0bsAS#n^)OgzY`No!W zW7467@Tadp(QhiHxLEPXP?nc}2qG~;-A1mAEk0cPQ8(Q zsFKDgihxQjzqh;>_(m;nnEw}uS}$H*K3bY-je@(Zs|5p0TO7!YZ=D5?DtY@y55(fj zDE)h${;uCKKxq_?qaE814c@5}>!0OlHC4zVRHyhiBi$OQm@UU9E|4CiG`wZ@*mFk- znO6ZG&}Q;PHpgda(JR&oy=*s>{e%YeB8m>p}~VsQt&@nqF6^S!yh!J z+|s|TU?20i<3^6(PG33ZJzn~UF^BE{>bCTvAq{ka`43;|oPr+GIx8>+{ueR>;)O=e zr$-u)6uL$v#f*o$M8icanfyYM3Y3^thHMg{q(+FDul!{jti@1!7U#D(76BL({8$`w za>7>E9&_JSFk$biQQT zI6If89-0QqkMAPS{qIg4lE$1_*1Z|4 zO)6f{TmYJV!V(7KP3HxmQpI1==B?%0^gqJI^toSQ0|);$ndNKy8RJ- zbso;_8*Wc?gXxq|=tgI0cegm$!OWuNQ+bDYgrE?lrXNPzoK1h#_tnQz&qdyBi24Gl zttY*6Ao-HwX5%@5$_g~y1_Lel)2(+Gk(x&_b0RaRgCLrJ9+U;*LsD1C*fGrpAjqCx zxFxlgcZ)+8pEvBDVE6Vx-vuh=fX?zNOePpx@{a~%pme&+@Vn4-C`i8g#Z&ip3qn!X z*Ikc3hgij^)!F}k+DWrCkj`tRRF(Top!uan5Bb+=e#j|oM$6nBleYvKi(FRO5o{s4 zm&$Hq#q&7tnML!T{cxAgne--YfdYLh69ns|fb(z!tOD;d3YpG4_W=ezTvdJvaN!;; zt)B(O`(E5%9NoT_meN`=ID$Oo@VrESK^febkE;q+3{3ncK!z@@8%|9${O1%fH!nDX zhr8eUqVz&o6tfwzuB;DhfE*c?d^xrl`6DVPZwm+zJ(idXYz}L#027H)S#`M6g=4CV zd_kaOxoYf$VaYi5S;$T*w2(3HMiE5Q>9ZV-4}tdAhlXd6ShPKVEI!N6lXrJ+XMVFC zrrBV~DDEGE@y}?&@PFbHl+pSIchI{FlPL2u!F=~@qYcIkp$@3D2E18^$)Eg8Kx^i$ zegrdH*WzE$n4?wMhsOC1kjQX%T0OR2fRDSGb@#Qf*ds}_atA#7+?+QK0L{1wU+tPs z;Fc6f-Yv98xE``fq$Z1wMQLT0jvBJ{S$Ak9Y1`n@1C8JLE5IhNyEzxAYL4YKlZtlv zq>iCOX-&#VDuCsnPSG(( zC2T8yW<{|BbZ0!1322*cEWKVIN? zUm`|F+R~T<7vt{Rv<~WJB^4Bf09>QCG;4-Q=VAD}Wu>yW>Y8&z!t3-k6VmS5AEO%- zEQ|lKa1{(uMcfzyuqagk6K1mQG=o|z*;Mp(72B*DqUY{HXGPNiF^qGV$ad^2NhhT# z+RF<9=>U%!8J}z5psy>fc?K!=mlDP*(g52~N|bFAH*3XYJ67Px_343xU!!Yg>e#+k zu_N(N!y)j+-8YH6ZepxQPDlB#sTvd=H3$Luo>D@KVU3eO{~%LNaXuxM&&jrD;ALeA zP_Jc58>aFZznbj1Y|(YPP)(_Bu0Xs)k3qynvFrMqIC~o3V}C_E&Y3>~F^~PNGzlF> zOx?q;Ga759j>c?|rvB$!_w#;VfG$v$cVK@oqK(yTVM<&AAeASJsepP$N*m}hGd{>D z?n~=BOqo>43W8(T1*tcaGT4T`5Ri)S)c+EL!5Dt6iO5$LaE2TQoiBN`sQDdq_Grbb z68qdxq$AHVHI-?Fa-kft$}}M%X?h;m$o{k@om8w6DN1hccXqmf9p|`ogt7t-W>x%b zol>|6Q)M#{#Z4J7gkZdGPY>?{(tAXLyO?~ZoEuTBfRu`oy}CZag8$zg{&vC}hs4xpp^%0Y<*Lqn^CNl>@2DM$R3m=CZzdb(Q8GMCPkoZRQaec9HaU%sx9 zEJqyke1u2zXxzvRaUH~2tt)a5`LSFl<-=_EGF;r?dfi4}`T_9iRdus4`hgw3-r3IF zNPOmp!T9nIVJmc;9BXHGI@0!h=Z0LDY|;A+o{P-Qzndz4(IQhB#}(3(oe^Egk|mI| z#d?x`&vL%7d@_Z+P1wIh!gYYzJ`C*wG zFgk5hs;wJ*j!v;HbplHx5ok`9fVsr~bWkn6b@@Dk&U6UQVX>T`ej*x`{rEMY7CLz* z71gZN>%g*@Em@4JBxW+uy{fKyD~*M#kFfnblxTJporW0(4N6~7716aH$X6g zK((2GftYv=l!fuEZSWNeAc4p{&^@E`j_72U%iA-yRi83-nY3p&v4IaXSIA24UXf;z z*E5}rr&O0q9|JG@GxpwuAK9T9=zHYH_kEhiJf8z+pVr#-Q!ts$q3h=>HO~N7;+%k4 zIXNTvwfb6(a76;~8!b}8yZOvzSwWCZ+_mFnO^--8`0}TFM`ED7FX%R<9z761i5Hkw zIwha#HdU#iNXG28&3xmcX+;u#Df(kpIV0{SPRfJG^GaK5Uu#5pejuiQFs>`&kohl~C)e*j=U@ zBz19ls#sP8yA?*`@-fAdha6))ZKLzaq`q#v)KUBtR-N}0AXki<@cl>A0_oI-QR9Zr zNBT!d>97A#f4I3#C;t{~4;sdn8+`iV@Paa^^0^%#k>1W(u}LoBthB}JnrtlgCOU}( z8DZpIQhy#X{q7*EAHu{0rMOyFzJMzQ64P^V-LF0Q(&m{AJkbTCt_l}EqWvVGpPnbM z<>qR4r)knuH`K7n5RtClEuzt6W!G_rp85`Wjs@x2w)2e!%D$h9{7K#n0n`SB+3^q1 z6Wxb(o?+3kF0F5_jv=9f($-MZdr!eYHVMx;)thHA>w!alScBphg$QJQred5#mR0xc#QZty+h?=d5BIeYb3S4W-DK=XW09^?ko52US*!@G{3U(< zAeRc`HrR%k;~_gm@d01zkLDK%CGqevmDguLjMP9e=uGRsJ^JSr!q^w!+p?QBB1A_| z-AvzuvuXzIQTKb#;?cFiiN&4khHAg`>IfbOyU!BWwR{KpbCDy+ba2Kn#c1nmn-(oM z18n4bof@tO=e$&K zv4c5zG!CQ=yn>thx?A*HJPQpFFtjTy7?=pGsvoTp&-`@cY`68NsEp+`TJ;!|oVpNAyYV`AI;m`%IuO#}7Mc$GQJWP10Rvr!}pi>|%J-=4s6Z7ZV)JhHm( z&^#Zy17yb&iVL-mx0Hnffur|6#}L2Ak@bW>XCbsLns1?%DuAJg@}d2yX9MHkbze8& z>+QhTrcM*bTMZ6K%lBX$osbf?@C|A>X#lYV_0I9;u&gD<`V;+etd8Q4XQ-o$2c|>c zJ{@lkhOG)&i!15MYC-)rNQx+~$Gr97jE&6BhG^@a}ZXwiKIiy3Sx zj_>R0op4lGW4TnT0hP;OTrW?{^b@(*sAsP|xcG~+Pb`gj>UGKJ)rl}2dZzR?nLFz1 zc~MPjVv5dADR_D$$z_NuW41y9J3&_K_Q+HvD`WDM{}}9+)G>ajEj;%*NXQk!@=ixh zgq=5el({&%Z60?`#27|YoCyO z7`B7w8WQ>DfBXz-KKqImzUsBziMFgdw4)$M;lXCb7&IJo5MUNs!E{fA*1nD6(O{*3 z<$EF!_*W{UB?r^Q`azk|n`g7{mJ+Oz|KP0yAv^$hvJQh?zN?E9!Ocs1TE3glYw|GS&_6c77sL!rhA>wrUMbv| z)Sr-HF{&zNo1Mvd6>u?hhIK%2kQ@9;f7d&?kqOFTDrq`S{tJ|Q{Xhms^**9X+Xrl{ws;T$&A%)yXz+?knTbNR6o!M>Mm2mPMe z7pIVM_;bKbzalRE)4^zwor%W2>qM8YEZWH?`n=!+3R0_Iq+Wb-0xx0(IFUPCVsw6R zdT^U_q-3WmdO3CPhs14x2N^=$NH43E%$ldCob{%q# z5?miNT+wKeyQo42MS!$|B&ZkOI{I(X%C8W8U2P$Owy-N`mqTRtI??vo|Vx zob>kwh+0SWaXFqn%!n`;4|Gb?Kj>G)uOr3E%g*g~W1B(hUjb;f?QS8sFLLYu2H2fO7? z@wYmeqx($i!}bK5_dX}|GGG#Z?`|{7zldRxtzyq5DNe&Dc?3nPop|G(cE1HUSMl`j zI&~sN8o-FgiYT>AWwS82l!nAKZw=z3=ek~AlgQJULD~fyfX-)KrLEg|YCgEmH*pEf zZ~^gOzl&i7HP_nLKp@mr2r8wa1cHI`tU^U4nhcABK|%jobEjh=>s|GlXWF5hKoIyH zgr>xdet4r<1#~=kj*+pc&z;R}lT;%O1e`Rtnv{3n0-MxJy|(oW)7n@f;*Sg&XOR%8 zazti+7m8P#;HpPmxS9m5+Q~=LY;b`jM?pgbtLKd_-`agx_&I=32zBLk;)3B*0 zfdVOXP+ei5aW{z`-7+qL z`L7QToT#9h$Us_hR|^tmGW&m04H0bL#C^Trf{3`QX^!|{PA%o+6xyh`T)+aQT_)t zX-wsr6Ax#?_ErsN&_VSVzHx|?od6(rllC2=_=k<@|n%JWqH8_r= zypVQJSABp_@85gRAYWO7L=q~Nhcdl*M16=oyN%we8?5%baMo`B^yW>`E4!+2a^rIB zCv%Ml{Yr!9E0FWOn{nQMvt23Y{pT9uA&?1YK3p)F9ugm~L-DOEpFTh~K$~CdCq7%% z>#-jwAbmx~=~O2LC5-e`9@HIy(4wTMqKk(|WVn!l{tRzSDWKT6;TgLkppidhGG(&k zPLOaOfS4^PdTwCaecagT@d%BVHo638zXLIl(bJ~r?}nD2vYL%9T@~ZhmtvU8Gp%N!Z_Rlk=Tv3B67zlh#k8M@3Z2hUbyHSb;eRd28k4Xh>f z&Af(!xFp=p6*Yr@5XVeagTLlLVLiHlkb_kEzlVd5XX_~KPrX?Sw&{Mh-0$YI04;ed z71|+On1f%FA-!nrtEQx$BrxGSX_s5Kk9deq)Q_a?C{E++r1NYfgLQ5BGiQ#78#CLH zfC3isR#|gU8g+*xGpbhB+&~59hqmY*Rh@~1BNm}ENv|n;vddH!|D2TVY_q8#oega z_C^`6ukfb2&hYte*`y6O*WLz+Pl~1Bd5O4sRAYV*7in1JEJa~_x5MF}!nGbKWt9m! z-Y`mPHv~y}w-A0rg+!kuf6~Q&P?NXbzf3>=OVE0?QL#ZMHJ_p_n%eWorB? z{^%FzUZ^>a`eNk?myTv{; ziSyzu;5l)48uz68FDLEH>sF;*%@^_Jsp3} zNWIbxgI_fkAJ(ZhqWjP`q2mAqCezb6A-d;vOMHPk1Cv-Wiyg~@K{}%h@2Uwrwg0+w zCuHv~oQBbo-$~Fj51&6ARU_Ie_;$A#Kho+4zW|ECHH+GGcvq%)q_CC#9l*}oBxX`T zHFgV*$8hL_lRXP2VN)r?o4O}FIX3{wsCOodf9h>o7UUo}un+j^;QKdKOX4@mxzp88 zh?+q(%-N$SewXhA6C8Etg%clxkZ}hHh*f*o>>uS2;8s(&`s1=eh3#MqU;zqLgZR5M z4Z8IXsv9X%lCGa1ZRoqAQ-|I++ik0_K(bJ=#L%b9ikP1i>M+Ox9l;y-o{l5W$xz4D zhNRV`QFXyQT*N(c8e~OVWV`!WKHpkJC=y|AaA$2 zf~p=<{k%O$&2(+Of~cG-!zXT+9Z_fwrIpz14}1JfMPk-HDV5;Ym0QVirGQe|nktM+ zL9DD$BGxd!@1!HRXDx~Ij!x{_l`=yEfIYUPpr_K+%1Y-pvr7RmO@eUT`0%!#t6}i! zuA#G#1mK3XWI@X^3zONNxGFm}yEWM%DKN2)NK1n;QwNw=3T@RDgs12mD%Sc6@Z(JY#Pb!DFk%D$!iHN9>xE7f_R_v*$+r4}cjKLtxC%e)x3snLd@+TmV?g=|VpV zwgJiDo=wL6qG|Uawb{}kH`mv5D;o~w68a6!fY(VeQ8X<`aZLxW`Rtl)MCFMeSo)Y^ zAM6ekO@ZYL6i`?w&9J9;Lw9}7cc;$}j;6d7uk4(tC{^WOEgNV9&(Cq+9V9wIO(wgv zs(UnN2Jgec7XsC6=iHXLfkz29ppt!zJ9_cQ1YjQTDJd2UI&J6ZsNwtf4jTf&o~|!x z^brO3p&ggrGZ7h$ZqAS**}5f(7)Xd8-Usce7$98GK2fVChmpfxX#Cr~Ic;<&c?Fap znTa>oCU4EQK8aQX*}3uU#gtw3B6s!RGf4h{m@OLgb}}I`@E~&X?uwFM((I&>1k&0{ zfjkX}--d7RLo(Yiw3?avNNT+@5H2g$6YRIt+z-vxV|J_mUGp$ZQ2?)V12MR;NuKWIj#>IucTJir+5?#amb zr^k-PHT9#<^1ZqM?d0k2IA2HD7K$I*e5(~;Sup@B!k^7=Qc`^M3#Ks;-aXQ}KGLCf z_qT8kpLYa)2J!T#U}Goz;JfQSU4!7nJ&5VvZu3Qslyw{H3V5-5G3srP+`qu6yqBhq z-==Jx(SN-;$9!u`zH9!E@VPGC?s^--Y#zI;z~>Y=J{~;o8hHF8nwU~MH|5*S!TLk8 zR^Z0SO45?<@qAr7$9!O%%VZ2N5VUZ*qd%kg>A-0jGkVO@k?21H>U)z=;y4Bb}J96L;U>$2tvQr=A3Y&3kdkTbyJqla}EoOuwke@`yzX zcRtR+!kmJW93l4mHt9WxcDfTArk#TSJT7I`T?8nlb$m-xtopOE7awaXJ%H{*;E7{RMM=cK=${R{17s7W2 zfyaY#>|>4miizqW2)`EBE7c<6TC*{;0g=ol69dgC2KNE7dlN-Xs4r_O{=o1}m!YrQ zrOPK@lWETD88~65xy1KjK2G_Z=_d6FHaLKft7TWRj$l2gfuxZ7Myw2~9Nf(LCmN38D&B zV%@Ch!TXLpV|Sv>W3DAIR-f!lm@K>0m2&ge$Qnb&hQUp@_R!{~r4?J$Ejj8FQ(ybG z$BTELWqkhO@)pnLx$f#G26E09Xbf1-^K-6Pcwxux`oQSw;-KqbpVR;>gh-%Vi4%3GiQ24{a-RfJ{%l}9P?nojMEWQjB$l0(G|&4q-Z$)2%hzFL$SAaM)5V+cQPh^b`@chYV=F9jjGS&itI?4 z{h_m`&RLB}`TG~DpM9c& z?tb@PYrGEFyErt>rSdSzO$qOW4Db5=>V6i|36UoW&YAmu{}dB|l?M-b83vK3y)z$w zHV3dPzwfXgT=4z9{a@k>F9y&|{%Ae2FlZG(rfz-5jZ8g{WPA6ji*CJyJySk+1S}xV zV~o!i(R{VDp<`W`>~?@-5g@+U;L3Y5pE;3|;(O(_*p7L_^5cpvdS^MXB2mNkDH%`J zb;ZNYhQvPbvm%4wEqaD!BeWAQijR31b5m0&(=&c(uqoW(jlK^R*)9O=<*?q{4W2vZ{P{Lu9R6EScA;N4$4Tw97F6oDzQ>X1Xv9|)S-#y?2F z91fGyn%$ds{qy?bCir+3Yvx-)_l0xzS&l&naCRPD zviq-Z>!P{y5<^X1vnVC7v*4#)(kUt`DJ2s`y)Nv=jhec?Zv8%5a3U>}-FzZtw;T9r zuv)8P?6cLET7S={SB)oZp#;pJ)bECyryfF|Q#Zb~6JaS+03HTZ&6&n= zq0t{G{Dn%&Ltrmr8H7|u=p=}Fv`WssS#`V=>&?25>lMIX(gJnpH={@w zeS)1gl`NQEQ?l`|ei~etwceiW$wXC$c|Q&Qwf;lR5Why@ zU7k`C7FGZb`+ZSAd|V-|kq0q0;XG{@odI`#S%&+Q_s7W~3~^E%u!O&9M(_*Sq-gg> zl=$9u72|GeY8Pc*-c2Q)9lJ1WX&nh9HfNilrC_>Tl|O0Ve=`wtk|6&cDD4#ITzaX> z!wNZRZMYQYl7x5|#V{8y9@iwWwk|U6c4NX| zHNBv|-GUs=jPKb-;&nv&qziv`D?L;`{&&`!?t#8M(+(a8_W4J0R;qAiPmL?oNB3;p znbsjo>W#0qB3#CLYuaTdE(5Mkb2#0j6#jp_; zYV557s=Gv=qe=%!{s4db`|*+Q&Bs+*0liXdNdKULz>1NP`f{8>J#FTcO$bPAw)~D* zTAZB)t$NZ~p6GzC{)d=e28pb5T1Dj4VU3+v8OloDU>8gs@5r#7K%UB@RcTD|V?egc zQN5RgWK-NV@!po?jiugCfKGq;C=hqKDk{hl2OB~Iq1-Fwx=Cifxmr8}-*OO#;gw=) zEenN5)kiD~m@o@;#oAiI5uX1JS1ANMD0$}id1Ch8%GbrWom~8lF&^kYUVKQ1QxAnC zms$t}rjcloAjd6aNok?H4}O3jsdK!Cc^Hy+s(1$({gjXAtss0OF4-raK`OCBS;FdI zu0njsAnydkQkG&x8VCo^(f@$KMBLr$b`9dP^SK|?{3!Q)wDh-w*#VvUMBL&>22N86 z186}@M#~~oMEWC9;|M_=VvWfJl{VQ~_nZR;(VnWcg6K@Zm74z~>**`^RJoP{o&2LO zUd5<&@QgeB6_HSs_MLOt7|O|49Yp=34CT-;e)aVDq<<17ScHombi?v*9FSyzuh_<& z@Xq=@D!l0>p_pBGbBRtsB#rwlPA^#$@KA3-`*Z$qeL6XQOsE6IhcLkQVmjav?%0Fk zX{ZBdjrsZSG>N4auoOxjw0aubZ{2oS2VYkkpQ8I7(pUrhslz_s!s$l>pxPWP52me? zG)o$m2l~MW4~RuE=&3_5V?@&CEy+Z$1WxkCZZ*v#RFba?z-F*toap__LVurV7LWtanq)?dL}2=s^~;YGwU|d_Q7a_K1goYn z78%4DO-?;E%lA?yi5IEZ#}Mdr@v0;Lev63F*K49085!@fgfB&Xma8R6z`Hz}sf zt|fy2^dSLvDu}`ef1c{EqatJ|-t4CdAqONx=&ev0W}_+u0vwd7*b0QClp&*=KxMB1 z8e&OZ%h8u#{PG91X!?o!by|Vk7Z?B@`-sOe+r?N#P|9fnE3)KHPyieWx$L%X=V=B; zcr7rx^GO_o9)zAGrZsh5y0oFblg}-p^QA}BGjk~xu~a^gTD6#;^%`W(H|E|daCDa* zgu*aM03%b@HB^-8`uN?lE6KH8mv8-0qKcz`YlU%I zKtntyJo#|30*-pQFSqS>DHZnx`!t^-rlFI+&8L8;@nw|e;3fDn)0AXEjQeXk7A7sm zstm4SXZ>DgZMl#MkvH3E)CWYt$JM!ixMxzg`=hot(Az`rOaz_<=j@~GCdH4E24>Vzp3VKXN1huUl`{&UG)VGt zCsOw=U@2;~XLx>qB2 zFd-%yRcqqUHD~yPi!_-L@m4#)W4aEE%`d09MUsJHc4faa)1bNQopcx6D!B2>{yh6% zFUIRyY+iHNowHCziFy@n0Tc$_VKG5drf?MZxl$H@%J#1V+@A)C>>iSJ=y{Yqj!KQ$ z05diT(PxOrl#_xqwU1B_Us>h7MB3(;39Dyv0vEkfs4V~h@ACl0rpz~3I(u8!DN}K$ z!ZO3RiSY@^5YPJYt~|&VYOx?oZ0=w~t3W41s8aZ0Jl@D5aCc9uVAizR`K4mOl2zLW z*^k3Jr373wh}29S=^Fgy-M6xC#iO?#eS0dCAb&(jQ@TqnvtyK-1~s$(QajF@M96Rn z5crZk6<*4ISFrhAa8+h>xZ3_~ahHtQ9Owmt9e~20Z zNvcUYdc7i)m)@DM=K%y7zNCaIaIU`$nIqfOM~ZYSJ*uQ=wJ(FRw6)Vg8u!kcWk{Y} zA9EYbTD2^lTt_He{#fBy;X)rY+B#bgK;}mY>lcqG(wuyx?HQH>Tf5i}MY31op9jo9 z;WWQW$pi863E#oWeC5x2wCUz?=BYNVCDMETZqL)b&9vn}yq^dH?^HYLN?Q&x%ORx@ z;es|$b|=ao=95g7@7s1*ZukANJ9{sAWmPSkAaOvc7}Qy!#II3ct~m&clC_4PcCYRxaXf%_@!)58i5jkD5i4g#eFfTkmPFG{z-c`#8vS+w)RJkOD@0|ddiiC&rV9`s!V4eahH+#D#- zpLfY6`*ZJNQ=P_QN$){!u>4vmDj_*UdJ7!d4;c4!cZdfz8IJEFAsyFYzbb++o#XQ> zLvb_ktKuKu{kcoBOgZO#)}X;%@zC9f;Oo$O-~VR;Q0~t$$&x8DqfQQtF+}wK-rFTY zP~rq)A>`%gkM}j8sOIA5fvxN>z7Y(_@6Q@t-4yvTNu(V;SF@d9zl+^7U!1X z=@&Mf2bov!y1ALhQFt-^{PdQ|;{u$Wn*5?Ni>(ld(1%M>Knxo70Gt^PQjr0|d${S% zQtm4>x5F8aNaHSql5$8?CMTK*C}>HMEEkX8n1aEX35PWHzFn7?gpa;0ryyb9>JX z0+}z?dOkzNoBeBU;~MZnlhb9$_`myJ>-(1SGA8HlQWi6Z`Y4oqsyOn)!qq8#Xrhfk zke*xFzeS=wz=F2@G+`7T+>&YXtq+*yT-bE7sf4)RI3q~&Zf=2`U>?jLPPkvDGawAB zF)MN_#E{RCE#XJcAUsx2UDHgx)cC&CBpQwG)qd&fe#jlczYL19I+qI&bdVKX_J<&40!h`pn^QrTUsA%gwmz6`@ zq*BN(#o^*r6e(poK7KbsuY*pVP^-^&38$I0g5R+QMK2ki3tPPYdmapc<>E~hsN(6d z+yw(^gpp)l72S|dPE{x&Y)))c`=-dF&y98HYX-n{tx}w;`udb9VK*2!tQGRZXa+|< z5J5d42fmEDj)fqWRA*+nTDHNvUMXa_5Lx!pCdB1W zIaaMz6Q6w0p^AdPA0f#R;El@lxf%7@5iyT^X~BJRXozz*f)dHjwn=n@vAu1-1fI%4 zkx(Py=mD{IcKqxdn`r^Tubt#nkq}!l8yP~Yo^-dJULW+Uwwc7V1APL`Ue~z{b4#Xj zjVCLZg+K*hS9&`4o|U6yP$)=*>{;!mCky+MmB1#>v12xrc@u{b{PcuU;Jpsr{$2Zr zBlsUhZa3Qe&&b*@)BN8Qx=*-~kmkqEN(~U$p=vAl-wfFFM}sd}7~0!61Q5ilc`LlI zr!zk~h15%pQqr94>O-LC!B;Tccz4Vn;tIldxFMt@@o@o9{)E4go_pPd)zIPHdhu7o zEUg&4YH5C604_zdIH;P3*jPt13%3;%gKA3nV6@t2_mp+`BiJ zN_UxDq%TU5Lsf}MnGu2yu^ryk#f8Fg-*`37sm{=EpJ26wYdWEf;TbsJd_|Fv5gD1n zumpdBKkRpT=fa)2DHV~&OVJs-s@pk-BL=(GtI{oF~eyeSYm^_j0F z`{q6UZtA&K?#i7s42o0Sq=&REDIBFZ5wOQ=Ni8R{dwj@8b`+A>sw82pB*9*5~36zP!p%@cd!<1FT=pz$B)ax-`W8WDTG3%KbiFj z{k&-xZ=aU zQXE7$AVD{^|0Oc{V*$jCU_21B5Bx>b^!mqn3bI0X=}wtu!c)@j#S=^fgs1_pgPfX7 z8o4ulLS6k#JN{bkf!dKr>l}XxA&yO?k>tF}!?v&*fIiQnhYA{u3r5G`C3V;1@GXnf zGx>m*Sq`!ES{_{R7fsXq2~kI3s}RArq?EY$8iEM8w20B>lNxZ{TEx}Tb2rLaf=@e` zImdez=q#XP327$4T;Ub6?(jmi`xQW^W;D`k2>74I-JHff(pt1HqyyhWH_&Z(cS-{~ z(~;LVy7H~W+Tu>M`B{Ves=mX12`!r|I}c>=094GDFQi(x zkrd#a`c!|_9K2ILiEIT4DEsaLNOTXqOOtEL#2??2#yPoH?tM1Q)7cRZkY1y2{X+{N zSo&F#kermQ}xN_~&+}VvjdGQPUYTw|skP)7mJ(EoZJz zVbf@^*YJr9fNZ0&NMv;~&b)m|zDh)4IRdJ5ToZ}U$t>bsG)6&B z?T0^)4`Iw7RPVP8DJ*GW8*Y!$gKXMn&mv4dND#1m=;Cer@FT3CW(qiTMNxU8UY8H+ zsej9u17^GJaX-y?>rF@U1~7Hz_Luzy3#2bwcDPk(?fBpF4Bm!3@XvT>B#RpPcR`G3 z7LDoT-rN7;3kK$^!0Gt;$5V)t#Q^au7@?O@X|B~bEq!Pe>5+s4^Wxmi@yR8SNrzDn zzJO%2m8iB0f<=vNjAn!~pEZh)J!&ZJLy&?}S-X|URf39A(QBbgRpDG$G(XPdbuE|hBflAnDziI(p*rFH zq}0K8Lw2v1w}UM+jnV;d@d{0N85r-tmpkRID@wb%*Gb6|Xv%{0)dWV)=UznXduH zp)V74q%jUpyrh~S^LdCUeGQOhOIPJ~eJN0a@!B5_JpfRK>Gx^9%uc1%NdbAes> zyBe;J!ymdk*UvXFfjHF?W~c49G8-H=mfD^iVSMx}+d>ro0^8YfOFq7d)COA6_lz`w zPcb7v>$ITbX>)020!f9jy@Tm{AFTsqG~9<1?(MN6gQB!F-Aj7Q>4%sRa*R@5QuTX&-}M*KQ~!YI+>(zE@iRu zk&MY{SwmUv4O)vAq_MrlnR@+;0{5fMHONUPomwbRs~GF}QN(fnum?u(%pT2Yc1mf* z!fT;3aRoj592ci6UOi<(o9!{VLyU?%{@jIX#IphHlK z+CA)d2^tyiJ6JyMT_#ATpy9$}&nkK$nf9zqxgqx^gJScsHBY@^r(?B*saW!nSF`+T zT+RNEjY2b@+rOpOa2zhcn0ajc2pHe04W55Zb9orB@ml-_?3W~hL3muv=wtP#RixA3 zq+7imq__4uev40-B-Pj#ej>R>ic43q8{PWw$$3IQg+~ihj2bCHo%q$e-Ta8AEo{s2 zGwci}JSc#A$(Hi!#vknN8W*(LuZvgsYFdReRbVnfdhf7{bv;Gh4bSi(YI_D{R1L$( z*m4MKxpC_s^@C1SH6JHUJn>%q;Z-k+<3Y^ZzcSbRF8!H=DzFpKZ{xw_tpQQ@x6sPt5Q_k?=HCPU*x^W>Ij_4 zkP^boQN<5rDRl(?~Gr&DJ8{3=<#mguWV&#Wvu(}p*M8V4X zXLuovB0~FpT7*G2mtI%iJe68cIki-BVTF5tHzInUljSjQ+~KCDhJrWTjf?Imn5_vI zlj2UGo-|V3Hy;!L@NnBJ{Aj~~(y2RfDebM-%N1wCbMl$M74Pv!H*TxFFIj}jYrZ;* ziWcYOib=omw8iQ{f_P(})C(+!Xc)xSsr~~$i=RgT*LR$UwMpGzF;N)1&w4m;Vh`}GWp=?bC}Ug8%hUBGR9|q_9)9CouGs*Ci>n3 zeG@urg=V1vj%I%->_wf9(F23k>Dnhp^dp`&Yt*eKySb0> zS&&`Zj|1~bNG$+QRrH`kZz^b1?uwEoZC9VK7swY>Hs7^LKuk6Cd$95CRcju=ZT=a- zPm}4`X+zf8ecL*g%N#HX&U(0V%fMek;}a^sr5ts}ybo*Y7HpD5!c1N)?7RYH8ZDxE zPWc%9t*-fw!^92j)ek)px+G6oPpvC+a73fk&CrQ2H5Gj&o?r^7GjPhsIj!wU_kGiC z3cOro9(1Hu!d_y*+E8t&27|{8vPW=tRsPs~+YvE_fWs-ia82L6u+T-)VSd2bw) zw!;KIXN>wnpqvkYA-P)ZXRZwd>c?rNtYieOjg8haFsgT~XTF8GnD_C!@sVFjue>St zmy+j{DpdRyf?|cd6d_)J3m>LsWJH~Xk#%Y_#BdR^pco#xDokpA>*f8N-WvP(j2J80 zMTT+bLa)cD**-0T|AtsQF*#vEUmbRp1Zx+NEf zU~oyAcTvaTrj-iorkoUReV`YVztAq3 zW}bJsN*@H=+|$_j5_~>?yYI!1Sg=O>eH_7Bg%BB<7ruu^&M!ymqOHD=-ArRL(SjQI z1;1a0$%|sY!^9F)U+e<|QSJGc*;4C!lCwUAJ+DE-3>1@txgW*H+5;n{5TA6PcAIyAM zBlfE)#mQR1vqHe<*-*q9XD$*pcVifEG#Go?D20nTAOp<4r({;LQz>zeXZzZOTK-X0 z2#s<+blq5ih#}uV>?ofrDtZ6YM+Wr27R^jf%U!$7CkgCE;?*q2Y|+o|VH+#P<6zS~ zAV=}*x*6Jpk|Wy1_pzUt8Je`j2_P z+{QJCoW({HpZ^>P+`tx#q*5&t9G6rPqHYl^>!K~}Gl_(X0D}lC#geEYbNiRJ!Ju=| zq`Jdc`5!u=#e61yb(-Utib%V6)!Xrn!3Q0fF&h8jpZvCP z4@1s!F_UnsJ$M+kM541W`P+>voF`%P+9ED#VGqmDdacw2_JCrohSqdG) zrdT)pbSXyXAR%vqk{1A4T1{rFGL+-(aLw*B-{4P!7{7l>ti&|8T3#l5eEP;p!8N8z zr`EM#XffVqO5=YvDOrGGt5aGB2B=4tzk8SPRuwL$1`|!fIPegk{FC2A&^|$?{rh8x zymErgZU?WW@xL)AqA11u=;%_3jD)#mLK-#z-?oc5;S{|V;7J?8Tj;BHAPEL z6{*3#e~zk5Ln6E;NQ(Sx*$BsW)gzJpw4x6TZ{=0FR6}8pH%dW@i>y6YKw?#UkYG2F zH4)1;(BReR=`>~+s))A7wO6x)b9#&nlWup+&&!L@gy3k{{+I!KPUq{TTs(@u!GCu= z`(XSijh#g?@d1?Wv8k4lDMJ;?@F=z+N^FUt`$3D_g28V~K|_8u;pBP7B^5qu(VegZ-3Es^&V6ZojC9+hFra5r{dJ7gkkwwY##=G6JZQq%xND7!`UY zl|@z+LCmAiQ`!b*VRluMe#&lfiPrh!E1BobK@|y`LTN7x4Sw$&1bxm*M3c0%K_!CF4t8&CYCho@CK>yA2 zOWEPGteN}QY?9(?BqO+4Otx(%dMt&QnOryJ7qlG zkt?0R0-bwdQq7=9OS=9n#)e0Q&~(A~ZQuNQi+i&CTi05zuL6}ziR#aon;C@HazWz_YMY&}>BA>Yja8@9u3`=__i6yQ z5U~s&NAl+t+rR-%)MB&jmmW}&mg;_n;AaPgFm&1RuDv1w{kQShNhR+5lec%lfsTt` zQ-V2}pT!CMdB@R0yUc16^k&G}fZE|X%lA#2KPeeoa;K41K?5At+r341Wb!ty3T0b5 zacKV&m}r(RC_Uvgq1KEbWk=HtY!7@gJ?SPtHMN>fV@zAx|9e>GyIa3VT>UH3H#p6S zh3$E1iv&wL0*cnfz7=hF+-_r)aRNHP>ScFuHI&yf`tH8Cl{BV_l^{kZFlXS(2%XHi1iQN^=5EDrLcMwz+z*9Q7~WpSm@2aUIkFc( zRCXFB?aOL5-^KW_Y$*_vay#y%!ZJUEL*-MRQ}V+s#>UsHKpLGjPv$|(#g2mv`fF4B z!pYmI;@x!{ew(U>0t_WebJi?XY5a~*MnlrR2NX*8Fisr1RaN6}`p&A%Yfnh4L9eeN zR-s;-c;(#cpL=vTXy0}a!~E>;L`mF6<166U(;tV|*jMB$1jIFI#eG}_suZ2?1S9~Q zc?YPnSRsmEYgS*nU@);EE&)vciTM@%xFH4BOIU;e-?5tI+{AOB6w(t4eG{VGQ*L^& zk`zgjj3fUqouK`G-0JEgyd#1XT_!px68tJTM5%~qz=nurQKfA&Lbybe`t=N#xv!6Z zjtY~;%?nj+8|S#7e`fQ*javDQTRCVNlX68EfQ*KqeH-sh8aML$&_u762F=`>A9?4h z8y*^jzi!8Qlt<;MiVLpcuPj($xsK8++``ouEYuRqy`CB2u5bA1$inSJCH{k#s+MDQ zww!+<*gkWh+J7wYpY)!m=#C$A?)~}fLyi{F1VnqJV50iJQg6bWt5>`f*+*tb!R(F_ zE=hN7z^Wpn6u;PR%;nMPR#!}I!^Y8It5$2DXzc7}sY#lZ5UWf*Ik#zMcc`=BPN%`?{E%z~B@ipv#{l@0qt6OCwN(s+#U_xM-JhyJyem2Dy}x3*onGgL*>vbRZWxr^^{mB$RrXXcnGA%==P|7{PDD4A z64qlK`$O5hiR(E!VWV7ULg5XbuDbJq!zGiX$tk5I5{ZooFb@TH?*4C>LwR zr-ma4jREzmfyj3L-mUEInwvl7H?|tAy`IFspoREYYC*lattdG=uOPn`TjA*23gsYp zn*Tt#OR<7ov`h z6yy}Po34*421UlK7Fo>Am44owQS#<|UlUwhp}nX+c6}14cl}?>c&KYJO2vy=NW{_$ ziFYA|stmVM2nnKeMV4)2nlx|>H1CE^;^Vfx-#KbaPQ9LoSPQFOTtQ$@tYeeFa{x@U ziLk=!zb~aKOPxV;KmkT%|8<7atsDtfB9yPpu1Zg?#DXoNNvzA*Zp+LorXn9RpCQ~B zNs@s{%(x1L&iXwA?w+#XWwemOabuT8s*>3ftSr_hbFK`bBUwec#B1nrUux-Uuii0Q zZ2D$JvellNz~om*dyeUap;HkN%-5)}Fy6OIW>&YyIB%J#ZYDq4*7>DWtq{D%d>-#la>>?17?2r!|j3 z4Xy&owP#*@GAx{5nznqjvL4HqVwm;z27$j{mwnO$q9VW^W`b3kFYLbtfB$69RLI0^ zo&+G?=8H0WS{|YVODI=XLX*OOT4!oY2?t1EZ|`ZGwz^jh2M4b8OESvyW@8^|`3G%u zaOJ>5R^}4@q-v}{JdeF>A>LUw@l012DNfB1vO?=}2ZIM%`5d_~EFs*pLSZsaz z%26^C0Ks@W>=%NK@5(J^Bo$WIsn4xZd`-SNF58EOX|o5M*gubl$3L{J8UgFnSz%?` z?s}7I^qEwMMTl#DG=d+HwJk zLvV%z7(Y5=Ew0Z(y3)eR8L0>}T96Gg*{c<`7tX!;zGGER= zJd~1_dQZ7GhlM81;Fd6g2F0tXI)}u-%)3Q;a`%MQ=#<7GiKtz!076k0AGyZ`S*_16 zi5FXGY2h~Yf=HLdskI^^O3vzeri0iSL=jjV>aZ5~H-G_-gd( ziTs3;@>;^L2$+-`c?C9G)yCxH;am?g%>8kpI{%iD1xv^wU{%n9FO8u_+V75;lt>`HaTuC3S%Yc3~1fi`mg~?#87?Ji}gP z-wF!Syn*+AjC!;1==&L991h zI8z$e%{+etecOA>-IA!kwvWDkP@0Ja8JbofVG=3vvGpl;m#Wd(J1y>)leq12(@NCs z&zc3gly*YNZ$QF>gcR{vK2a4Zr4qy*LP5617Db1|!nlHYjfhUHpGo&Py@q=8%-sp( zF(#?{^nzYwjlhY#L;IBw_3Cqz7rlWm#l?1Z^JA!e?5G~qS#2pxxJAY<^LDRC^4aE=6SdUQYj1=bYDm5 zs_Nv);CB~1x>wR~(*522I~M&LBjYrSFI6NMz29k(5i+|@T-G?uEYt-JD#t5IbY3#qYv`3&kBfsM_wUV zIAcSrzPygh4S@Ih1_o^(y)s=naH#0`!%4EH)&Lb!p-z^!U$VLsYDO2T?thZ^D%t^P z=#uxl4_LMT$~r)>Wj9>{H;u#d;P8J={{ANJZShta&%s8VP>uZH_q1Ygx;L z6_)AzC?HI=^25W$o8p)f5Q>APCn`MY+dQyq6#25V|HZ1VDBxG|gIlJfUiXRH-IP8b z+e*tJA);6Cf~&l?)5jLct;T<4Qy?%8<>F)C|MaM1m+lvHZzfdMNrsZnEN7d1Z9Pfe zNCb$p_)EYboGAo+Nr*OsiDlBIZO(cVXhx(4Qx-Y1QvO8U;cs>N!Pei8_>Aa|+E1`b zFkBY+pA^bggq-dnc_Zs5)^}oMFq43jAF8SxwZkz;`uUH@!cQclDy(0B8!Q+bD<@d$t zDb+Bs?g2QdMf_;Y_t&s(9!NR{xZ;#+AGLd%3+KKGj?Gs*VpCR!y$nJKbg8^KpTN-z z!#t+rCzG&FFCjRU=cgg&k7Qlvi%jSVK07cMkrxDQ_=&AB?~1k-qgbw+Wg`EvgRqd- z9ot_4HS0Fr_bl!5EBbe{n1Zyg@|i7nsvx*$=bNDT#7f)GFJjzC-MQZSDQg>mZd%uL zsXAU~4m=2T5FQ~4+HV@{WXMUI9NF;gJ*?8m7~JI9t$TFfrlnmjl~+i*XB2gYSfpfR zm+d~Sr}$U0KpSt4i{f7n3@2`ehd@Mv8WG%;cACMxEw2V>-oFdVHH`WiBgmO?uQ&K& zbI#9Rz86!6CFQkoUpz}S8VGL~U;U-EuO+iBhKK)P+>D!rG+T~-w&MRL1s3s?G|RjC zk{(F&Pr+S9Vw*2b1z4)jz6x~oT zsM98}&SuN=g+55{P#fQiPnCZY;iZYv?KKQwIuK4dlXCH9xygIaJo+HGIi_w^c#*|r zigUv4dHVhdiUC1%cQ26N0n2C}`6&pEW`M&hFP5?#^9T=>wL;Au@EEGws5qJPgNspN zt}J>TrDB&u-Yo~&{5tJLxtrD(G-nu*XrTmxc3Kf`z^2)G_iZg3bz*QJv=}dwNiEf= zZYyx)oBh3yrSh4l_WU<=YIfht!oRonUbf3MM3wcTulPbGnDkHJ&U`Mla(42i6);#$ zzht%~;$fwHkGP_8HLPTr1o)JWW4+H+E^7w3Uy#+LmwPK_Wzq*_HJenH-xwHxQFB0 zV`2L~pS7P>Aviq;M}^o{zs0_4h+0nc?m+wx$Ou{)lm4Q zf!*27`lCF9VBtKDdk-^yRz3(h>FAnwIyG!EjqA}4Udm*-8LYVJLNXso{F4S}Bn<;X zckw*MPFMEtwVRP+pRRhSs|3rbSK-l3xU@6K3oD&%S7$830i{_`A% zG|%`^?Z7QX_nDt$H`bFh%^wn;#Lf=ap4rOp6 z_Dx$3qIqA$2&I0W}yDwZ6G!cDDludaC$ttnVNj)ky05-xaa?<9O&viG)xj z3907aFodsi^Pwq9PyprnlKcD{sD?Gy=vk_s=h3(ADTTL$K(@-`8`4qEF^P)c6&m@Y z^Yn-KiEM?ahX|Wpq$8!`6*dif8$`(D2rC*YqL7Ty^Pa}sy4nvSzks^0tRBu~i0{fQ z2fXL#*s$678aK9iXI(LS=EUzs1^@1P&WaO7;f%u7;Rq4WPL0*D@b0wl8Sr{~j;Z$E zTTRlIZ(yxRTc-@YuK?!m+eRa~RV436f=el!T({TXyT`wSkO-+X2yKjd&5!_vgA*8q zo_=82Li?tkf|Z!8%lX32WL;nu(o>oKaF<(XlSk;&+!tqN6EJ`+ezF+=%%d!dn{`%) zJ1n0SokJ?sI3ucOuEonjHZQ-K%eWBj6157>H5-*fKlhU+<%jjD$LC+}lLVuq)Y5Q4om*`Cp zQZXYTEhItQASa|4ZesBNs5L#?3kvgXc&m-S$7DM1>R zLTPCb?fHkdOo?xkHo_4JC)dgz>jlUd^vtbN@P}1zFJW zOd^SuxL5aIIDPC3e#MSBJY5J3Y~-8#_56|_4GPu?;Kqq|$&t$px7EC$ttOhjlHzA) zgfNbQfHaHFfQ1(u3QG&z5bPPCPO^2pHUEB-RNKT-Te*+2^;p$crwU9T%w3~Y;Zbj8 z3T1-heR_OwE#8fQ;dq9XD66WDr7T^Or2nIM?EqB<>7%14K3{(_9y!_J{rZAmh#%@E zKY_tILsNVSohvu&cLIa(^m>+yMNY~5A%nm8c6s{G~=)8rc ze#@1iwYs0vy!?CDh)< zxZ{C4FFJId%WzZlW6wHG^Ef?VnG2`YgGlB$?#ED7QoT$rvvrX=Eb<{2=9(dKV8DNx zh_9eUzI-{soA$6OO*v7lX^B8JBH`-;?XhW6j25tOVHJoxZH|ZM6iVi3gAy5tB9w!4 zZk$DE)vJUj_FepzX=$C8v`~(7wWp`Td6Rr#)0dgb=di9=r)A)0O2moGtSI0MHbQ&I zfjQvmo@yf3MI;tb-FN#DOBODb{BO#)C16wCbb7H^%k1qD<$Tm07wrYON-|dpO8$Pm zrk6e2Zx&ONhef^x^axfCs|2I%=%Go>W<;R%JkwO~C+j}n6G7P;!MV{UBk$PiOaGy;>rF6|<);oUM6o+m*VIRC!D8w7 zd^y&)vd3>w?(SMm@A=%u>z_i3nD5J0bu}eeaLCM9w)~G@eLndl@oZv9BPO`%vBL8q z`u{{b>Offa*aNNGj}LY zV19+igr-p=t1=ejBt(_Z^pDP9 zn@{7dF>|zt3-t=czJd8QWg#y*M)) z?RVdwL-MyEqvA)}Gr=A&%C`j@Z?I@O5E`7}5Il{-8-FO%ec}5yxPD=ERLV2tF`FO2 zc5K!!D<}>PBl=DRr{NGp2BxDZZhdUZg$jBIlD7NlDdsagnua^?PLysS#@+Q7znBBI ziju9d8Yr~Zw6yTu#(Djb=!DoV+$68fCyKc=$uWRaGa}NZ+bqwWf;(y`bLZ zC6P?Khkub=*MDB5hFm23Zb_m-?&mKj6+-F)bJ3!%Pbe{dG9lmUNKWJ6_RHc%{q=MZ z)Zze=tk9POM?kq-jy-%mKsE5}eB1j_z8>tUKRX8faPdYus3UByYeF8`2xP4k_u$3G zet{T?fG@4N)AUS{;M~n)qfX`B?yN$O(oD>*N=-S;HHK88r6M)wMd;H zW{mgPk~}_Rv{2Tqa(utjsnr3eWO~({)+&C zkD;>JX=JyXU;|lBcYmzw0VZ3I^p_oHTm5<}hTnPqaU`=IMd4qCYElJEPs*NGCsPO|ZMIETgl zjRlsI7emKb0cyKo4<>RVk(h~Rg4PT-X|9Z6<&ukyZ#mlAsyfvmJFR9FlzD+ScNxmx zhFP(D6Evkc(%-C6Pk3(S$z~)|JU*olpZebU*GW@_+e}GvGcRHL=3`*trk&t4m{oCM zD8I4IPszn)*_lvs49L{$RkGY+=TD%3!c*u$+N|o@$7y$gT?|keFslXWTsuDr9Rs3s z!Le5vlmR%mKNVk~V@E=6d85?u`L_Keq_&qPf2bn4Z@nd(B&43{89Ir~h`{a$U1UJT z15#^zs<7L36<5&E&t2Xx{?h<)_r zL%MnzX;DIOgZPTRgfXBS>CJmGR}`lo*eCo9k$u5(xqiIwu+ zaj-T6=?P=7B(PPD00ES4*u{0%Zk)dYmk z=f3C)d(~nyGQuSRjl6_>g6f5~EU*!9{u9w1^Ls`MMu=~-kXbx1VFzdMZc(*;EM|xa zg^u6?#1yNDjHfzV0BJ^3waM;gbLR_eWG@^wi+2?<0sq%EJ;^3_(G%*sVg6f!h;!fR zr$sK-C2s2|2meq5i`P)!CH<|9q{0v!BD;oKE-3`zx}hZ~A}-p;eg<6a*fPjdG26zZ zJ@{)0+*6)8YoMRN54_d<`b?J zC>xVEq2e^bPHCuYT3{pMySElxJu^2QN{w>0INeWKV%Dni_}U&k*%d~?2#I26#D!y| z_?-hVQ$~npnl|NXmOz0(jNGl4LxS)gR}`PH>27P1`rTpKoyEX|`&sE=pzs;;ftFo!+!v(p+7B-&#UOmqNOq%a{jJ@sTxsn>S+yv zFKaPEzbqful)U4(?y~xM)X9x)hJ(0|Pz3V&&S+R}fp*(N0XI@JE*OIg0(vhr5(Z{E zb-+pNI0y>D$Y`qDXlmZxM=q#6a{$MG_M#Xu1SBEzwJ@*Ual#b^`wH}) zR~9!fIeTq)PUH(gcP6gC^|QdxqQ|hh$>2(3rH~@Q5;sw@aGZ)(e{V+zQ)q(z4|~;i zW#4D+=mF~PpvTOMEhLjMp!z6rs^ge5-~~!D;Q8?ooQjY$jBVZxN41Oxc}O7eJ8u~u zVDvl+;jbMsnstoXDezmd*9x+ML-?St2@USkimSsvFYJja6IB5H<(c7=L?YZM5scxg zHY^Qm&^PUk(LHB`;P>VQCpOyrv;~j?12r$9U#yrN7Nz-CA_>+jq@&O1Qf>}}ITD4| zg7|k>EA_EXyo^&c9o)?bGtsJEE411QXd9^c2!(~w!W5=N*Hem(oItE>n{qv3}6J;e)td|P-ejRev8_Bq1ln^GHJ7#Ww;%k z)IDV+;jSsFo^UU-c1K|Iu-}Jn2n4plJ3OIg{T4Tpq{O_Kqb%ef zZL={A?!FP#6P`sePT{3L(vfdyy5H7gnx!MfLiY?MY~AQlfp6W}(FNRl3B)f4ovFx!0e5^Wc7+f;g$*Ti&;O9HYGT;Hk1c&m zF#L_HPMP=xZ{L~w3H}ZB32Cm%f5xnSX$YrJI`uEx zCN58N4Q(n1Hmy!MBK&7PY3C9G_)M}v`=4q$NYsUiOCMS^x=kOor;4L1x~iIK>@D10 zzP*|k?dQ2dGAc8F`RKc-|Qi>%E?03z3!_;de1t|@E z*TbcB{*Co)AvQ_HkbenhQ!~7l2x>u*c%AEH4Q~I>TuWieJY7=VN1)G;+yiPZQaGDz zC)k+8PwPzb>zE~yarz0RFkgFu1*|V2DMv0J21Wr5_CC<~|Ae^sO@e^=Ibxd&YpmzA>i9*O}n1YR*I;0RV~B$B(^^=y@LIP*@*|EU%_8dXp&q2DCc z``XLL^h=MEeA1M!uF0s%9^dk@n*nim_mkcM3_#*9a-uMk&L~TKFgDZ^tg&|auT(6K z{gBv-=J1sOxdhg_I_ik8}J-R$;$Zw1&VI^1Jc$>6u~@wWG#L zG-?^x53G(%#1M!9PKbi&9B;Qm-az2aVMlYDb`~uoq82?xFUbr4?fODxz+VVV+!|C6 z{oEKZ>|v=JTLR)Nt8OdK&J^Ypk`!jDM$`zvz-F4$TKEFTT4E4<(5BZ(9PcAIKKF-( zoPMSg9JP@;Gq=)-Xut10^Bn;ZR8L+F`)=VUBUkZB1xKzO4zYV|>bDN(Vni4!GRooe zUC1AG&xQ$nA4DoN_6#=~%mXNZ$j}$DA#RRDVF!=FU1+_F9Yofw2da&>{Fmx2avGBG zvVPxDGzOfE&xbk-<=SR{$T*2)&&`sE#<)EnkUs9XuX8Iq^@G%0oP+;49MUrL9}%rD zJ=8PXkMG(~oP)F(7t~0X`N(qr+DkgEWL#kxsa+EM=}{5CaNY1dUk#NPEe`<5=P-yH%?%ee>Exk)gvy-mNZ!o-Grh|>A!>Q_ktO=}BU zg6hb!M?wZ250;khbD^cEbwst);wX;eVgsr_DpRz7E1{Kpc~X}E9Z}EC0~>axzRxn> z!vx1cf4xSnOfe>IcZS+l6R!^23o>lkcw@b~2j3fIgjp)9zUPojxwg3}EyN%;BTkH! z4E+de&(StF`FUJ;clw7~m-8}puYBz9*_)+%=3X59c8rjp=EQ5;d54}^y3$Q!A2Lww z!Hu@rix$J1sBGh@|Ep4`fHwH*h6*Jgt(W*i;V1xAL5GJo<~0{2z$3(W-bce=Vj^W` z0rWnNCQ_dqxLnGjK7Qy=6=E~>*tEVN(z?!;XH{T;z!r0eF%W$u;cq;{i(yvM1D9%n zdo2!3#EA4=6q|fpvye@BZp^)Jpmqg?`G&!U!+<4(FHETy44!+9aO*76D`_3egMN=( zY0?fe;61Oav;s04@#!F_+GULt77|5x|MFgA`tN>CAC-@VJt=*U*gOcZmH=oCE@FAM z27*IbUQ!e`$9e_+-+XQ}AIT0>X}TAFb>n)5Px#t$e$DCKk;~l3#N1qI?g8@wrJpzu7HtC^#@hJOdAl*N&F#zkT7J!d*D8D z=i1A(#9V|~2l9)$gZ5LLyqnL4&=z5t^Nc^!gYsnxQ_tEne|-6st2y0?FWz`Mw-a>s zBRSE0+9MyOKuyYk{&sYi%o@N^D#dma3u)IYnoB4CTpIgCkx zEuP|wgF;5t2QT@8Xb^h1=W zmH&<2?_7TOk1t*eRG>jAUfN}=iMBZ(fU>40o+t6*Ce58W;+01QTEJel%`1=Am4ll! zzv|ZchJUFpzTDysQNcAcP$*D>=>1FRd-AR26_y+=X8{Q|!YCUZmgtI;A?*L)J1H@8 z0rybId4&`4-1AQWmQdJi4<}qmQcN8U8-f4+c5HL??U5|A?ayY2QzJe`YA;}ZSo!B& zzeOjDqjjp*VUf$x`aegh2nvOSe{wSuDvldfZ?pTJ2bY6G%EI;@`aGCjO&n?%1?H%< zcmMzWu&9A^a58D?LMq!&i3NvHO`%o*RJv5pY&+6=9uTZ{qNXiPr_&B!yz35`D}X}U z#v>S|81UF`+-s?y3Oa-^jIam%Tie{|>sxIkWD(ktP<&r`9>G=G4XAo*zZ|6$QQ~*8 z^9DXs48EZly%deD!uwHzYX>?M5(!304jzPb-C*jo{`oK$9R|sPSP$LAe?2-?Rs8yE zP<1bO)Iaz{th+YA1&X$N@u3fX0l4qN?M0)qtSQ+-2>e1%8INhu0uS0eL1vOK+fG6f zFlFzuc&Y~x6d4vZ{w2~5{bw@k56vMDQcrzjqps zWQ|`$5y;Z~2PR5{wI9xX{w9w}oE6xMiE@qKv?>W11|Rhj^#>x2(6~W!MD7wRy}IKP z=ot&km2wq3Nq-U#m@&Q zdh8Qpv{_)PdVaKWf_8xl46r`rnD}0o_$X5onrQQCdebrea7hlq;pFzb=L_YUif8TwaJ%*eSD#_#DB zm*6F8!Fo6etlyYxpFBY3Zx)8sOeQd3BQ8}8_T+GvfoP@rsg2OCSj$0ljoavvSG``v zq$k)?t=?S^-IWZ_@&7_FmKQAEoJd~NbY21$)pJycsgXc%)F%NK0KLCJS^hB|?+cn> zRn$I_DLuXfzu=b{I%za?Ai0LKFAYVc)Ig2=ZjUm>P7pt&Ml@yAlR{d)``QH zOoT)9rAH}lHTq=BAwOT@+piQaV39p1B1C8ZC-3#r#Ep;&;qpiZqQP}EvJ2*BY5E}5 zlrvqHz;J^c`!$#luA)pxlb^&{2pVJKx-3COWL7#-JvX`e)Bz z0I0Cs7S(TkJ|-{vvxUxQUazomy%f?UTwzBD_BNShNERgwknNMD{0bcXGM@;zTvHL9 zn*VGBuF;dIHVa2nZkhDuW5yY|bQ$a;YqF^Ysk{gvq{j)8;a?L(M7+fdn6v|h5LK}c zz}{B=q)3Q;I0Kf8jt+O;Il?$V>74>zzww;r;D5{rpSnOv@X7$Xf?%lCk5U{cs8uV( ziEx2v1685#*pd??dpdYkt{bw4!?NFC{-XYa+Jb1z-vDNln9Zki00QgMBCf zmxzL+by5@*SRtDR1lMj&Z?BX6+E2t%?EO-hZR0kGk}$NX5@OMm@k0L@@A)L3)b_`c zqxFl+0onS5tp6f%TS$XX0N0XAK;N$2uB>`Qt&_}kp|3i!rz_|u18aK21_(A_>)v zmWjQ)J-EKQyJ;m(YBCKM@BdLMx8_cJ`x>x#w-W9$voZ zChK#}_=or_4Bf&te=P8p%lh0&d;R;5=fTXY28!ahIMTQUW^e-okut}Q86qXZ) zM!)7h^0+5)oyw5uxAPey{LU1a*UJf{UcR2ta6Ik>_}^8#U;;GRPR*h4fH=y3<}b(; zJsuf%Q_MCXCY4Oyou$7Ot$^#hWL7-&2&41penD{5MDH7TO*35;0Ql8c12kyP)Dq4h zHWml#ZCGcrX+XUU8#m6mTaK}4#eF~q!PH#zZWxD6yPCj?6V4}<=l||uRx)!xZI}&f z3dXN<_?L$?zYp+_565#$v*4a&R4j~0DAs-L0dGWdN-#4%B}M@*_F`@m*f!CK)}4#R z0T`iXJ{W-rsRIu1vF4ot4m#oheh`$(V5@CblJZB#v3(I$bq50fwpf*m)J}DR052-w zCtrh2E{Z^dpvcn0TDwfhD4ib1`=Bh0Pi8vL$u^PyOgiw^uek4@RhZ-rg4&HWjtY}P zSe)G@2IyJ`2@@&P+8O^oMSfhLcj<&3Y0w|~1n`;0$`D03Cd!Q|W_~aamG2cRMypbb zdIQpHD&|fS4_veQ!CG86%diMdVqF+aS2;3v>#>?*qn|g`QT= zcH3=pxs`br)tr(oA$eVnvb5ydo$Zmi&+d*gZygUwj2=#Sey%Eo-ABZyT<_m}e|L1g z{kJQfiT7%8+&0{HF(mZN7gC@#Y#?Y+@c!_`d3y6|Myc&&0DZRk59YW|A z)vZ9K(j1liy=$I`Y-Je#BB76R18suQ->3tf4`_11c2T4@e%^&IGBv>CI@!9p=`2Y0 zY1sO~v0glQ^xyWt2_gBe7H?9FIODclbag#S7r=13AmK!bl&FJ2+{s!g% zka5(ip5#AgkV&t2(wQf;j18I`KG5U<+e!)o(yw1)4<#B$JEC{{H01rFJ(}N-Eaep= zB9_X`XzrH=C>13CpHd+g7u^d}aRRGhopFudo-#1r`~2aNwVswiK3S`3%TfZ>kB4g< zA1YKWFxjfvvNcD-*zIcr;&bX6_zDUoT|5*Fr%y6hR3z-h{7Ih5=qN`$B?*oX3D*)F zP3k`Dx0M$9(4)?l>FP=Xo9XpVU!g>Eww8&mbIiPrRIO@|j!m{qqwhWNeMzm$CY&WJ zU^4!_vQ*DKj+a&1e#v^ZNIeN1NbU2Z#iqr1j$QDc`=3e{B`;2wAI$-b_403S$jdDp%nKk^J|gl7Csou*rfaiSOn1H z^cB65pk5TGGQm%qmSN;|uE&G-W-o10t>a4upLw!#VHmp4?cw;5EffpdowV8(+n)Zcg;#mT#7gs7$CC`6+Wx{zIxAF>Ey3`|q zzE*t+FhP_Cs)tntM33*%}Zcm|)gRGyyvcp#VwXhwxE z!^9FW#u^+?dVX5PTf@Fw#eLP*q~(kk)p)^RmjG}0aTgo-3QI|Ico#e&H5U^{+yI}V zs>humYV=IFxVA^+LLx0A{c`Js8P7Z>C^@eFMvYL}d&Lp}2=f>PS`h19emzHsz>oPs z4~Z+?!>;?^^jCCORD0*C)h5v2{uXPgkIIw?5QIv6F;dYic)UV@nw;e!xZCpXF3avJ zFEn>!E=#_j{dHCSQZw)4a7C!yhhoVr(3R;pze^BBn`WteRUcY$F}O!0xn|gXNiGrH zm)s{>H*~+(Pi_ivjae^q#r7BR)o5jue+O=e>sU1)+8Laf`l6V*YnKcizovfhNIWYdRTvbRrg6%IoOBkagyhpuCr%jA zhfv4ZOEybW%eh!+b3Uxc)PC^mWa-C|jeSaM!r`y}^T-EZhljK98*$G4$cT|~6BdId zblMZ!%6BMYHMT<;rJM8iNQZvL*@u(>?zG7sCIFk7I=)anS&C=^LYeNyn&(YVOYURf0;0Io|7%Cf|cM zp*6RlEnm*zMnY}^5B~EBZ`|?FWYGNK3H(K_|7Z903T1v8!p)<3SrXD00{dvgvC2h1 zb3|gP1OMrl(5GE^w4bi5CL46G|667wR+j9!7dN?u_dSUShO<6yS*h`!e>(}3eWB{T z5zt1f9eM=2N@_k`;Rn-}S_hNzwJykv^$6T~pC>|B1u)Oph+znO>2ygd6)| zQvOOYx6HY}g%y1GoSEU`aY0KZL1RjXoRu={5!I74iiGHPCgekGdV^pXS`L!`1v)%N zXZQ519!-n`ePw6W|N0jZ=OS;{OhQht(K!FQFU^icPv}Lm#C9;y_$vMAzh5rrx z*AW4HC^xYp#p|{bjN~LElwyl}U$-hJ3DX#q;;p?Wxo00A~SoXbX=_7%~WqXZe+1Wv9_fd&{5xjx4 zAL<1bQQpJ+A#V9AZJFtOnEP{4II=r=cKilf0egjKu_?lGy>BieS1Q(7V%J^B6DIq- zjcPX6t-bk{E0Qgv_PmFD1zSp=Ox>Mfz;QRiRawcl*jq!U#YO{osWw7OR}R+U8U~3n z(hi9-{NIm1%HCUMLS7NlZ5Vnp9D!KQZG$`aAp7P@g2Atb;ohVa=TyvuN(xPAH_d3v z-?YsKH^|f&k>6|QB_BvFUDNk*gBcAdbMn z*|o0;GTQ3^$7rXp0pTu=Z^J>$Vk3m=6t6}zf50J+J1ro65cW6nwu^l9`Q==}=|Mpk zu1XRc9@$Dme^4!kd3;**kN3~OHu z(;P2@;p4N5)-T-AGOcnBso$z>qxoYlHzT=j)05_1b`xAcCtU&lEt?*au!!~17`pn7 zV^HxP$sYV}4_U|gZ0qQ8)xT6s*naepXNq?W@@!!)9F%NB%R-3?$8 z1cz4L&{0kP2kgE3`yWgZjK$h@(GGE}sM#ZyKIsS}6{T8-Y7s|=4I0dRjnnrw(1gFc zlx*1K@rs!ltN5G9HQr7Q^jwawvaeu3BA({XmsQ3F3|W@dj@L%}$vX3mayvh7(Ku@; z8k#w2*t!6DprhQRgLoTyjzx>9aIMtqepjv$2>wQB0Shr?x+3-61%q`!z4y;6HI0Q0 z7mmFL=?-Kr`@*ev3k$UTxh1W-f#r;hZv8d7#rFf{*1-Vz(McTx8I?3fC=?r6bZ+AQ zS?&+Mzy@eu?M!#=DvetHT=&*~n`cXXUo-F;G1ogly}er&28P4rK`sGDUu<~7>YA`1 z+4O2kG8vUSF1S7SdsCAl`x$#_pykibuS z4hZ102y#NmQtDC3Jq_eO%28Xx_-eA$&v*@}`v)LQt3n@+w8J~3h8LvxOL0Frua&2w z!^#`Gsa5_8CT{vksc?uBF9H?*k5r0Jfs;54R|A+g?*jgxlQaE)PfqaBL<#jr z+id`AVU>GhUCvdtv?6+HDpr~$2ZROJ z{ek788J0|h{dvIQSgSrz&)qi&>BDIXU`U9Kl+I{+^-d|M>UyS1tcuD`DAP`CniFr*!g|2v>s+BNPK0iLFB> zGV|#!h{zAQvZpKmUXmEnhqQ*{?77`vDDruoiqUxTugzMh4hz{VPyKo?+?Tqnu^JbF zVWNfQHO#s1PhSHdQ0Bf&zcWGOrTgD@$q!_W44HLbz9x7WPF>;C0cU~2;;+&3LDnHx z&#W;CuB2*3dY+$_Aa&vj8josRLI?76PE_$6KtvK`K`ag1g_QcGw!V9>iW12pqbhZJ z$Kxg32fyV~pR2!Qf9PMg^ZBiSk*XeSq?fBKEpPF$EWLo`c-=}npFPy#tecM*K9a-u z9gL<_v#|OmWAYM%=ofBJYZE*U3}^D>v3O+Tsbq?)Xb_qkVPsdE;FYXp!B`xiI+c+( zs|5hmE+j%p8*JXc`U_DE;2p3SP%ZMO6*}PoH*Qk0AdDjMiN@A!XJIYtKdpYd&w+Q> z58G@S{_Qq8YA6h|3Dl~t5qy8L(NmuinER%%)9M|+Kez{fC`OiOPAvxjiTrR7>}gRR zF#9*txkj%$ttsHlI=%)5{ckonHOavj3j%S?y{SuWGs7q}%%7lb49+XV)PYMn5Nn zZ3pjb(L|(N7~GnVIsiWL!=Mm<+sBOn^EQIQERp#~X`*IYxle-L+&EX%0xm?RR zUBnql%S$PoQB)Mb3tB-w##+Yo6zTn|Zh|<2$L1fcR{6gJ(8Xr`kbmi%VkuG}6@D`< z{xMHjvs=;#Q-N-uXc}(b)YSJV?tS-}&4^9N!&PM#2S&M8MVHLmONle=>gTtZ^$VM| zjae)2?h;qoue#|j*KU@lXLybh)>gY4fvVz*v>ljGY7AY^+!Nye0py#FND>S60kuyXI487@d6mC21E7v$<7#h@I?3IHEEklyZ00RXMm%^tW?P*4#`AIK1dn7eT;vS%Q zx2rr-wuAFPcDQ{%y&W89mv!s(>DP8gry)NTKkugw`LTlZa}!ls&QTzz6w7Olaz-u6 zbycBOOWhrPj9L0GLC4;kSFkSYa`|N9dw*~+uHU-R(3X2IZu0>{z#OOS-*D!5qya%~ z>v2Bc)aQl=6|s2#(?Lx>W!Tv~@w&afC{W=Y{iwv4kP^eM%?P3Mz{F(zC0Cl)Hk+uS z+q3_%jJBYFT)rq%>7E*P%G1QIF&xtO+E2QDu89;;<-|U@og0af+O>-$uE#Flj#6az=o^A{+H19jL};MU zxe8t({AIeP%{O`iH(4=)q4nzsQTE$Ah+xr8N{)p zNd)yq$>;+-AxIq<{abHZ^ZBL*RS}+>w%Kf}A$axQHRCeE1xk!8SA!KJYbu{8@hhB>&G+jCb3o?u@PRNI0%7zE zO+i-ldDgL##1oUtF#c%wZyxEsdjA$c9*Q3t`fs*moVq5+5~C|kFX>}8hUIJ{v46%0 z?QZ4U^}yS%6?PLMI5yc;H$yM-C0Q2nB1Z4!G#T|hH4g*y$Cc%Fu8g}6Q~(}Y-BxDz zr0+eV$9@GidO63H{*|af`wTD|9KPuOL6^VNo@O>wi0j29gwXF(^7$>{5g>-YK0%MH zBs5p!wtD{G(izA!`JvE)grA^DVv*~;6)b-szYF8u1W}?|m0M5eYrN;$peK9LT-7h3 z(3DKN6@=@D`lNE8i7fyVRKbDYodhe_xnv-AM@@_Cl7=jW1{aWKJR4#-i6Nm5-0#SU z4RFbYbYrI^IC2KAW!FTZ`meD;LWhsw6W8V@k;HhHJ64G(|#+8&v`U*)*V(~T$nh<2C(QaR9MVd z4?yPpK#Kjl-C)^}a|ZIKkK}@32{{lq>Q+i96y%bm32G%fKr)lns@`OT@{QX-h1^K? zMf&Z}Lr+}k)y<|$&ZF`VWPgjzm+8qPo4BiOI$KL(E#ux4dw{bFRB%qXQCFT^C|Yws zC@Own_|8zcNd$T4fcBm)g)Qmb{OrL}R|ZJ#rSFGnSZgpPS4Nw7`}XP(t?h$I%@=OJ z!%EtSoJ70XGQ2EUC?D`0;s=pwP&{~zShbGsl3-o_F)9P{`4;@d96WuJFo>kO(z8Qs zo(EVl!g_2BhI^mxzo2)Tu%qq!mYX_}tWXdi&A8VVB^OX^UnU*r?+8Ydta#=c$D~u( zJ1CXE0b@`^Op3GeE+dz*0UX+=63~l-ZFci_>R8Po@L$#gtQ~pJj6x6aHJ~}-598@4 zmE84AewDfG|4i#c==z<`oxo(5Oqk#^;a@spVq{^00XZK3^MN?dAV##+xV(i+v`cN)a?|`9s?4My+2Eu1v){k7kcm2oi;`R8pC{^KZR=0? z_!u3;el-lX(@gz4O*wHElmF7emgsakw^2~4=dC<0g8T!4n#H}K@>^kM5D0YPR~SU* zJfe)BOW~l;5^Ca)EG^n^>R{)!V&cUMa)~OHdrMUTG%%7!t_iV}_moLODf6)wVO!3g z$Z7@v0w>TAb>{}827@(mPMcWCke5RWl%HKB$?oP=un4=OVg>RB8J}VG4smQed&k` zKPUYF)?Ifr^!)(&z*L@^Jy(gEfT8Bw|53H(pz^FhpLush14n@U&K(7TC&B))wH>P= z``($`t~+y=*#Pc7{S?#{%L->YJL898cj7p&8)H)aAa_DH0Ya;{d&I=%CW6Qsf+kC0 zG12Qv{eq*UCssi{30s3>*WaFel=n7eb#0jJBvFM|fx()kBAf7#N!@&)7Zd03vaJqf zk}ILhWo0-#?G6N}Z`$Ec(!5$QG6W3AOj)8JV8ev(pMTau0uHvSb?J15L94?*b_TUA zFD&I{Ia0~_!SvD<=^G51obd!VZXC6n;#Go0{`1 zo+RR-pvWt2`;kZ4k_(r7rx%0GLw=c3X})`bZc?N4f$P#|89#u)W@#0cu+<$*33b#7 zKq8>5#LPs8Qs%rqc*Sl_;zd_d4x{7Qyqir#a@WIrcn(Kpt+&(o!4pRPMIq8#KEA(I&mk56PTH<%57FAtL2M7q)>j0Iqs<%RB@mKWqu!RiDS&%XO# zou1CTOxttNpBV4s!HI+0fWNW8Ljxnb^Ui+&A%I5QkyV5;LN_q8RFGGNow>Ci+->n8 z#;{{-QZ~(~6uYoei;+CljE3?vcbe`mb>3Qf%*4b8Lc#sMj1Ie{Bb@< zDbjBaZ#uY;cf-Cs3wDz0>{tGmG*ULpMWKbCbpzpCw}g2_v-!vM3w=UElU9?Kl0T*$ zcATFg0*93Kyup_H;hV3|i}h~3sk-xZa~{AY=Xg#(p_3>gLLn7y`48aR+Y!biI1$8; zMND$u!>~)U!Vn*xU(r+p{t~V=EpgEU`{%GsGHyq{^=n_fHnT`zSF}jq0+}>xXjSPJ*IZOF~XbV9uqj&9a zU&Emfr__!p6dPsq4g0|Cpmn6U_@4X<8H6Uo-mNXx8RW_zfCMR-b=bo=19ZU~&&W{7uRs%fY`7xgubfgz+dafCltIH)v1WI)FgENp zL^Bz2#>*WyT-PhAv%9W8ho;wUxrEL-m|hAq+)yn)WKhl;e$JRZ`wj!Ief>7pIX2ANGLO? z-v{->iYp>OqPkF*zI4>^qCw{kh}P_9p(?CDLTTcq(k&ysL&{lbU?Dp(<#G?>{9wZ9 z;pyHDG_dWE2mgrll^^0;kAHifQ<}C4MC+vP2`Gzi+_LHUQ8;=^svv(&Sy?_3@&MOD@b@jRD+mUK} zT^&ovZ>+X5O82@D|LQ_MNDk>x4#jZ}@A-Bb4QDstD6j`(5>$#ld;KYUi0IQ2b7&;4 z+l2lxrK<7hhnFLfosDs?o)>S@e!nd|$+=aEr1(HX)Lr*xxx{&;i)xJ1bZpCbjO$&I zCp4|*x+Vd7SM(G<*4-`(gmRi6RYI<^`J9w^yI>EAJz!hkOrCvZk$mH@pku!0(r}_> zoD;#R@qK3Z?BQG|v3lTv|4fO2&~F^6wmn;JoYvZ1Uqk&Y^7;tJzs6SL+;Et^2IFX8 zEu)-WcyYK?9%iQ?=vjTcV(W!=*BhRE`O6?Qp zhf-1U^cNB04E~T*3|HOq!0sbZf`x|~M-I{-RnVy7S+OJwTYV||UDG4IYvepbKr0$v zdjE7)I9Y|DLW#U-m!GEw735xY(1=ej{X+O!^W|}BIzAh;NvqzIbA$Hnc<$m2MIfc- zB=ja&GbWq{E!+pIyj8?VvEHD{rU!rlc(=xTkJ&YCgZA(?xV13@O^2VuwQ|jGHP*A0 z(e>yXa8KT{qr%DUUcjVk6N(5i?y%l+*s1FqMuhq%f~rd4tw0#jGez#+@O#XUqKQhR z59LpFh~DKy@|T#hmnB^0m^VJMgh%>3AQpLDE;$^-OI*>)wt+q-gp|7(K023u7OOXs zuTH6>1q%phJ&4;~l+tNeityX==K$VW(l>o zeNQ~gJI|*NPqw+X+E^{phB3sPXa5Y^+d(7_yP26wm#^qrRUhwq4p|qOG&kIsEK*&X zZ=8-74a|#)q?K!ANKaM1Vz;<*szAHJYW4Oa8s%^G*b2g;CpJDdYo&VyH*5W9r?0s8 z$AvQ>>}UKwZO}z9S81JsiT}xX`P;}FMkvW+CMdz(YG7c!&3JHf^Iqd}Q%&36la(Ni z(xtaYy(TM1UYTLr^cv@~0YL2VuF!REvfMHUv+drb>(yL%w{{2GhIv-z8yx@l_&VA@ zXTSTCe zOJ86~gD2N*#dz3^FA!c22q8GT9hKy`r;O*i13Rg0ypAQf1ecRMU?tAof(Fh?lsOZGknKF z@qHl;d5la*7ss}z=%K(z2#ysi)Cax$szpH=Ax(N+8j@V;#oEo(c3D9*1zQ@!Rr{7T z@LMiXUlH*xf?`ur1_u7jM|vt7UNYzgc9y>OYT~A3R_5zV^bJO0A00|VuUFcZm*^|+ zx1;wkjS)-FOO2+EDbByBNcd@oac(>2%N%Yo5ooa;G~<$Up8Jq(=$7YTN|J1F&axGi zwLxA-?IW<65#!}W3{KfD*yQDoQ-c47K94&zQHC{dw9PObGuG{1=3%PN|DrVA9!L>r zcfq<*`0`@u!>26yviJ?!pG-|aAWwfRzFDLal#Jz7iZ1jzd=jfcl$C@rXoUnZ8`Wy= zgZ?;81#(7*qa`N4CnO$gxy~c*wSFL#gNc1_q3qRLiZ@Y*=6H1QSV&uAw?9d@CMXby zul%+2VM%@iS$?KvY?x}O#oqPmi`7%90( zGW07x7!uj=?i~xO05KQ#&o|ny-C@i%#2pzvTO6c`>4>O?r2v;d<>ncsan-kJX_C003BJ*s7zL3i`yjSRAM7pAQ47qZ+U=dWIi`m_S3I(a9a59!~Rz;u- zM#2m3oCSp82buabuwmk?bFF(0-T#ZP_ke10?Y2gf5Fnw3-XV0PgGeu-gVI$%DN;r0 z2uP6cTpD{o*!-%l;R4*A-|SAi`!#M@vFSY?I3w#HW*)+ki0xFq(A zeP=S~G~O*vb6mnP##I_9yz6r%S%0nmock>|{SD051s`#PKjB~0RpSPp+?zS%7kg-{ zoP-q6J4U~C4A~xyYSdE4iqE+H7P*cbK-rXEoE?Ix;7c{%#Ra@)Y{hdD4FD7^3spLP zk#+EOuUn`wN9ob+P8KtUD*YEyyXjrE4QeJd6g6#(UWbX zqJ#E@-$S4j9x=mO`$S84vvXww5AJ;$^A6{L%Wv4$fSS{AbrZ_Ba&k{?jy=G zbo^k#-SdnzyC4%B44-9lDD)%a%8t}G6wMKN(>7CE{>`U^Id#wX6A=R(LFi;#HKfGq z?ER*nk43D}Wj_aqa_Uz1O-n49=Exzw>Nrg@MMhe**qX+SEn#WNZ$;l-r?baGIH|&o zvp+M{k3NXXKM1}=a0kGruI~;q@gUFf--sjQ=_A}Z3*WZ(g)*$X_-4hXs5e_r({I*5 zliIb$J`z$N4)Zw(X_#;f=E1<`(8;S`Ex`FW_tsK};4$(gJMzC&Sc64Cko-C1G^oy< zBidxspRztaroeGkxsR{iA5-53b{>hdh#_q-S(op){Pu(142kKZ-GxRXa^^9(a>?8o zMLdIeSRSWJ&;l&~jhBVqho8tzUacmn&4z+V)7=3os9Bq{Eb*cB5bz~i7osGs>E*WM@wyEntc=+LJ zO~XeI*^42zWV79Js?;52@-daNa6L2rDo^gwh5&CvdwKIlgL}RfpQEI~QZd&V*!0h& zij39{X#wnOYeXK((iQ_r_f>WAIbdmFo&4E+W>scbV6otx5e8MHLVHN(R8thNy$eWnaZ!9jr)l;yt($46U%Z%@dQ0d-m(P`WtjTX)A4^TYZ?$3KWHfgp!+Kf z=LUDe{)p*4v9DC(lg*95hBOD4b=tMYQZ0`o<;ij4xjh)U;jlIe^IR+;oj4q8(7tMt z%x;i>F?7cz2Kl4S5BJ}HpTpiT;z`&a_K|}89 zAi^9#3HoH=kIz7^>NJ4k>!D5y^;wVMI_S$thvRK}eCav)0|hFzw$M0pb!+&`65H_# z$ErAvcyzctB~Cjx%e=<(wfOi*DrK58M*L(Zb0!>Tox_nQ(Wta5&cm_({S+0wZm*&tna3KQmbQH0 zhO|zxF~fMCh`8`UsS#rMHxlB34~z9R&wW}1sxpSZf;B;B{vHOikw%F16+A_0(HQ6( z=}G*0=g&;&ALX-y>)RkKU$LbV+O)cbh%u-kBS{#GaglFrinwwLDuP}Dh$S+SO<`pI zrxoZIle4_2KYoc_T8p%80+aHcP4__sRaKtr(N_Z@BHBSgs)q+hwgpIenc9h6g84N% z=zLz7Rr#!%d^(Sj42y=`54k*krU)K*lDw`&a)b_ytg5ry=sh!m|Eg;FF%y`~lsVTL0{U0E@J%xDvsh0$cCiQcolQ ztDqlz&q?2?c-2ud5(E8IiQU0RZ(yO3GLZ6s;Ft_|!vO3On?XR1So0z8tkgtY{w$1skdK$+-O*Zh)e{4(&FvG=U*UbGARogPX=AmBL6H^tcYf?K5To z5t4bX;TdGh^Uu4W`@g=6lxg?)u08zZ<(4y6xDw)hEMbiQLlpcCO{8Yc->>T>^@2Z<2?S+P7^IN@A~GagO}I}zIck7JQ^4{NZ7q2`A8F)- zfN-+xP7TNmQ*96k&rnSup6?roC$V!RU2sDF3>kGxKE?1+<5?QL2B%f=_cs_}@Y*Ic z_W$SD=PF>oKqnGeWPS+-;jZN7dj^u?jDdtAyA9go%O3}tr85x}xPF-khbe*D}K z7o6(tnBCXnmTe6u70b<1m5aO{IjSxkjTzIzxom-ok@OL6ul{2Ze~qREVkwr8`KYgr zi!y;&p5xCg@zAhQP{C{i#k;Khwc?sa=p5%PkS6!OaX3T(P!uy}r`iLiJ1noKhr3QG zlS9K>YYbssocPC$zvS~{6?ef|*`dw_+{&Nm{|-h@klCsXjgGgPA8%K>KAS?m9^Sv^GeZZhUe{kgKSo{E0OnJYWou)>c;tMh_<=^=%Oe7Io4G( zGdB`denvwInPnz;L8YS@rgt#~xS-uvoc{shutO0xq*k4PxLEyJWY%warqD=KW!`}O zd9c6I-)~IU?e^<)*CRkPxY4Q5Yb@r=cm-17B+9UMVDj`ns(Ucl)3VNIH5U13l);SN z;xjb3yP257)tU}yiI4U_;C#5mJ+U!i0I&WEkNa@5=-4+;aV-)GZ9#BRwm^1mYV5hdiMEhN_Ni- z9W1;e1^jEfuiJRS!^-j+QIr{fQ_ETC_H-k@XwWc!t6mcX8YlHJ30oRv?2I(%s?$W7 zY23|H>nXeMsslb+gsRZb1t)zd$n)Qx0c0+I=3muyd|UXBVa;?L=F3T}az7@kYAaB; zvv)|$g3I1&n@74iKke55#cU&7GFXlG(YC9zwt+f!c+Y8_C%Nl?*rve$VVgL`V5)40 z->)ArirZlQIyqyG>L_vSxx|Gx3ZKb@oF2t?JuqVrB*Te{d-(!4UIsfKj*TjiEdN`l zerUMMR?39$OLdNy21Hu$fqeX6QTi7U>x=L@+70!5g2sU!v9GRy>A2s6U!%!cZg)Zs z*z9fh60|?v{hm-=&maY#O;Lq)@q=@^c)5R(9%R!lzjS-(Ew*cP=EUh`!NEq)v3=7nH?!YVs%r9-tCs_yvz1lSC-T*s#IW0+CC~ zr#oQ9aN{3`k>Xo&>0gA;Karj|J zkeS19Sj{-ce;%1BVIN)jcwe-zvf=anAuV?Rv5os-51HM>@uenz7*Ho}e>wYYG5FYG z@TS5zGVIM01!y9Z&<7N4b{Wl0oz}(>Q=R{M5ldha&})qak$3}n&BFd>so(~5_jCajLWM4MBqqfWA_J%7_j-8A3;CRN*f=|CU79}{z`8>(PG)8=O%Ol2 zLXjj}+P?4r>@r5Sv$w;?ZdIdwAXuNa>yJ zB)vwVI8jV8;6=x0UJbp|7!BKI!P!^`EYS@P#J^WjZR}*o9xZJ^DIArkXF0>sM_FzI zxnZB(CQPMV!?L~4R4sI}WIUvm`oPG~OU!{yeYGx~7N73wpPai4mp$F6I&miq;kO{X zb_iX7W26UT*1Bd5TtKn6fp7x%ojB^3dwTa z&qY{_^?RO+Xnyn4eA%;!VTia^?LNgNO3tBddHeG{Dz6e*;yx%Q z6L_*vbpA;V1ruD-MAAfy8Ma7dVAb-tO?Id;)@HjpLgrtrBEZC8w5lhqxXz(2*g6ymW>#W5JZl_v2sFg#}l?J z^`?%v#lNQ7P3T=DuQ-vKf%wfrGC07?64gxZlC+#W#u$nDtFM3f@ zuTw2593FqfgoP82q#E4&UTTKo5jk6DYx+3Q(VYWao=AA!QcpT*s!JIp3`?d-zb1OC z;%#M2m5m%_7~R!(&Obk7s$z^(SB~vA&nD=P6CX2}+q@C^ya~5BKo;H4^=tqwR*PWF zWs>>X!rDpx{%JZu(^&|ImdLBpQ>8vkLV)a8GAMt=lN#eGsX zg(^I_*?}Q7)K&*iNO%x5f3dmw1NeaG6w@lsM6K!~qi>>MAmK~Zydr~T=)972jQiNq z%%jZL2YOI$@82`G`$PR#^ux}fz%S$Bh_t0wA;*P!uau{JSgxguUO7XS)Hb-sm#e-C zv&ZR`We^lyToDc{coew*9wC-w9<_MD-pL~T_5Kjw(7PskWC9fk?5WLMI-asNkM>zlvAY0oxa>BtB!B{FZ6q$H2!9hJ>->qBrml_?o@m#D{V5vJik>7W3jo<8UNuvCZGi8L3TIron7ADk~;= zpMuZMKLzJ4KTtH0q{P@>1ANK^Ti5A1MhrSdM1RA^mXL3{yZ}}g8~&_Rdr&^vGoj1h z|CYuPrLa%aJjpO=dR|6Nj3{A1u6A=CG0wO6jtMyc13 z4O)pWQCeZg{LO6>JnuJ0>NbIm4JvyeaT6Kx2XnMf0K4VWS~kPL1wttubu0|B`(P4@ z5t~~V$<+!ICA?Htz$Mlg(X(D9usj+ic*75B7(xo>OTDNcyzMSq|vsg{qW`60-ks3h-QPXT;EL?ti#WCH=?{NWtok6NC7ZkwY z8+dT>q0{MZJpgsRC*8(RF~%TJUpc1qKAI?*CzPR z*O1^U@~GH?@^yVMD|HwBBaxYHal6bWOcBgK0;Ja0#fNgQSI}8EN-S@c{bCmi^1g<$m?izIJ9F@jcF#*W z@7^|*$*xVXhwd(!Cz!I(4ZL6;gA!-+41FhoVfiy-1OGVCeSez$=?3^jO1h8L~t9WtYtxxZV3yi<1Au%!Y z)IM~B$0Yg0UIBU4gMUG#bB=j6^wC0QX!{iKl_ApLiG-g2UA|fFK2=8_0%3Two~Zm= zXqMuitBS>6RftkV7PuxvGnN#Ia1vecOrqdv*Q5aefbnkRzf4AQdMvaj{;2l269C3) zIRd$sKAmFaX~(TptM zc+{MYa%WT)9(WGY1RU|1RUWF;-p?^!EX25=@EYfNmiDWzb%iG?enJYufR574j(CZ+sjj zmrYn@VEBwdAzh|)T_C9+%rFrG7+sucB!C}tr%!QTZ}nGY(W=+p&^y{;1MAIwMdYFu zq@SlHn1WFpGkdrXxY?%+*8Zx3ro|Z+4|BM~Sn>QC0n(5*0zBe4Xp*KbS{vyy8I~wdqU2lTL~vT$!peqQqu z6@a|Q|! zpc61pelBA1ocr9xddXk;3taX!^_`sJO%4#oXHzgB9tglrr>@f88p+bde@|V%%fTJ6 zDMIQ4Zcr#a14QywykW%q+cw{{7aJ%_jdNMX!EPlzKiGD3vP99%B0g|S^8*xH=Mgyh z+;z|7V%o~pqF_g`A?FxJC@mi6H!Y}W~up;he zH!ozdMYRE#_rfSQizRT?BrN`!b;X4*1Kujf_aCxBM!Pr_GJJG{OEVR-?qE0(B0k)sw6&hZF&^=ob;26JQPo(A-a*@-|L{? z;&q*q#MgMUjLB{4a3Ryh(4A)D5lTcX%5>_j0$h!scK&77K+@J{8vlqDtJ+z)>g~NZ zjlU{gdcewIJ^<}&wy7YayG#;xk&34~-(WK9_gIjo`)1Yw@9-limV?n_+GZ-%JVur< zJezF9WK@UIjqm}QZ|7oHiW|o+$Lq4k2H^YG)`qkOFjhC^LW(dM&kR9O>2k^;7Xd+q zWlOR{2Uf_5`aSaz&^JB6N(|;!7ILc2Ij7hTf4+865L(v}xW;4WK`YNMX74FYGJkHNAIaSdHz$TK3Cvao3u1tLY-}3 z3fsGJ@6hMj(DmkiWx?3MBOLsFet4eW4_*EMFwY<|zvM)TPUkGBGbua!s8hGY@fqnx17q&HFXwe=yv1z@p{f}u#LD! z1GdYP%PG7u{xe+PEdV{(63KZIdJMQVof4LMZqfII>=e7$mjIXgJ~fz8nA@u0+D z{y95J*p+y|U1H($y6E1o7He~z3b@>eQ$N-}%B`C!dj?exjCis6MX3va!B7b+FctQN zQcMIfrKq^r5AQ(M?8|!k;V9aJskv^=m2Ux7 zI&+0ofd)$5Rh#dH@P4jH>-Tz@lGa($Zi=1d&JGx*t6UruBb)bQjkaeaD63Y!0Oo^A zW0)J8-=0LZ;*Wv-r5Y&9{w-WMntEEX%Z#Lr!$x~ouIQ?#(F&?u#fN z4KP*zgAqZS<^8gkfzhHjYFq9e0}767P1|B{+a%!UySW+f)So+g0gg$liVn%MN1$A{ zCQUZvw=EY?#*1d0vPM-quyV3;&dPC9m{x3PiDzGey9dX4Zr$j z-{-J#G*^|N)a2b&b|A`mM?(zCd|@B!Gq=W;ygO>11oN}nSKOE?)}HhpHS~2{xlg8W zS?UBm&oSju;3OoOicY z)+eexW=Z@~^wGWG#@ucGJ+BQ;+OB=q3-IE&kdc4=ifq3{pPwW?8eNgDPlQaKeFcLy zxi}W#9D^uyGxk<+2^`oJIE2jy#671$FX&ofwc3!ItQ8^O1tkzq45zlUN;K8K2Y`N^ z(y#t4XH#%r9@&_^f%4gfy_)@+<-Xd7Wl`=#?TBVVP?VIcfL5NJKIM}QR#L$Mj>GnQ zDW5yzB_y6=xDW^Hqy3@op+|Euv3$7}uE1H?D#xCT3Be~T4mN8cK_IEnrZMiPV3zUN zdgD(jo?^b{tSi&}C1C%q2hft(O&97P2f5nPV(E?++Mj&-`5VP`Ww{m^m*IoAp(+VH z!ovc?h4Fy3=)MTeyN{`Pi`mT>;5RqwDf!N(lq2mKS(<2|v~tXb{Sy_A*C;3qV%g*# zyuI~{Scy9K35}uR;B|)Y?557dQ8TP`yKp4!Q%Ld9>joqrs{&y!7$!&Gu(y`;n-qD3Mnk8MH}+*K zWK9z~P7(()l9R8B6<|X6sAFZIIF@pMSigu*Of%dNDx1`%rN?7r4$y;u@TFxTGW^qz zrrrlGQG7nz_#^WVX_|bZ6f<g+%VI0v2`$}sT@cVNhVl$X=0k;ooUuhYbD zRNoYJ8?>fq2;wR-)LiDXv zP`vioRWEojZW2x$2rW<`TXkl4J2=uo-}}^6^(5l)E?z539y2T7w0r(W&5tN)khIZ| zFC9A#I5nx0X<Ql|*+R=doNs*AM?~hq-zU?H+}3z0)E5>_ zOlu{+Wfu0O#G<-SrO(A_Fi#T%!?f6*j8G6uwz>Lv*U&{|P&7tO;D*e@-|8_OUjPgk zXOQMJX9wQ=Junia=TiPlJDQAIWTZ2phnmRR^{2*FZZmw&o9-z&K!q#|u7pTOKvK=b z2<}3#4AP2>;&6eR6~h;#FP&W=P??OEJ8>evm{dX_kHjRZpSO`(21%A`;v-l+G{Vj>AQ!rINuZm*sC&VZOuR{iJi?QMVNv3 zpUeJj$6PZGk5x5i$Ays&(6F)#_xm;CSx4c!Vl>E9u{+ko*Ph#`NoGEdhIGCCUWI|k zBlBNjMFCZZln&bg{Kat{tzMae6$zPru7&CYN$zf9hMU4xbvLBQvV*}QHHNIQ@EoFe zDH~5hHpE)1h7$dC+|`glx{*9~CO1+-%cbr#{ZZ=ScQqkEXIdZslppM2&oMf{F&y>S z{E!jN+}`9PQ1WWzK7b-^XvjAPz{&X|kfe`ySnDIVJr(y=2`lQ`MUab{AX5Z9o%?H! z|1$K>!s@V0{2!Ii82)4QxzEcJ`7(;= z8ycl%6Lqr-a2fplDi;kiP6sUXyWg{9{zRE0dTeo*kK0q_;C=Gz!WPD1REM}h_2EYQ zk_d&1)BVkD5{4^$IcG{sFP`$JsG&D#1a-rjwB`-KC-Bnl1|={2oVRI4M45!gNE{!V zdSm70uR)FxLAi#_aAi}4Z$_-ssMn90G!#u?_cO?ht5247MP;l=y0!WJO}b^7@w{;NEBRPVssT4zd6nGr$_?D0~64?KD&(esr0i^3&etw zl7y>uT@$aD<_*|cQ+Mj&3AVE@?~6Dg@VCXf&S6`tk#+ersX4ckEG$JEm#Q>h${oUH z3thYoql^usT8+H^dau8lUQFV%=vAV#`>tbYCJOoQK0ZVc7?pW z>%q07daqE!u@4g+lNGy`)X2yyBM+#l$_n8qe{h30J=ppg{vrNaQvioCubblQC>SNJ;l=n5+*2)5$GKGMQe=!t+!nlW6UvlaZ)h@O1D>V@c;g3+eyhK{^$OHE%KdlkN&$|2AL`jHUPf`5-QFtJy@c&-+TF7ZYvp_L_jZ=z0D{U$Cnk1S9`&I)&J+d0^b`j=DG9wxn5{v zqLSluJFmEmWJJjni%6Zn%=fBk0RHC|1<*Qon(nRlc7ebHfC1RZI3gD+Lzh0K%pWPOM9V1kJFm>Oy;@*BlLBvtq)_@q8T^@UeWo`11qEQ=Rc) z+`d$+E7#5Z&*{wTzX@8~>m0Atq_DzX8tGE3xVl6|L&hI=$XQeCutU$?d6TYKT)hJ> zgwD((hLId~71UYSG~}2#sEpDB!ma(}AlDB|(ZU47@O$CwACT`z(qteV%)uRt8(_$y zt2M;(s|1m7*?=d}bcJJ&Yl3_4nAUzSMH|(UvQm0WdnsnkibKyws zsEIs$C1!k2!b27R$WWZt{9(O#op@~9>%STYio{cY>k?j$C&;In*#oLn7qGK%fh3&< zY>Q)mLB6`>DwmB;m-AjW50a(3y~%9X$Ej@4LoXuI-&Y@bS((*6Em}GmV1wJo2dmuj z%^qhR->>@xJ(z#Y-wjAd0cNhI`SndegyfZ6(y-D)*y)ZGUf1qUy<}g$ePc)egn zw%;~jUw3t01+gm`Sj%p?-n^;fFf<@%3X(XS2Jvm1eqMJWg_Fx9e4DYTKOW-n@w%bX z&&9Haz1BeX2UEj^*EQ7%VwqRlZo|z0ce#NPg3@OdtGBq21 zxHYKFkp@=BT=yYtT%|RF9*_gx;TZ*+{MeUh%Y)J=LW%a+?L#0Tjm=OTFST2h+i4Ec z{lYUAVVQKj?EX4X&Fa6-$jq*vqVMVZWLsHP-vIq4Jwq(BoTk|%u6Li3Uh_2%s`r*HrCIu23))s)D2`byPVYs6xiX2E0Sw= zTwyJ!d(#cI9zUOmLu+|YH{lDSA?JTrLZq)C%3O{n?5>JJON#YGw_WcivO_DD47^|B zn_>!2r^1Qf0Ge{ylkKBg#szz8aD_nkWymc&VSc%KVfWM}PaEt1!@+9cGREMW-z>P% z+e)B)zN+O811e(+E}x?GXL4wWkeHqtB2R>4@no-~EB4G$4z5#Lu^oOVt3;xP`M?>` z_0GU7T;%-yPsXMemW}cbc4^Vb#A4v~AeLj?6KfC!DPyM)k1z`!jD|XS%0C6W+`spM zDTej|bPEC00?8017;S7UQyd1%4vmAv-vU%D-jTl{^PC-eBmAnSNwdL7iItE?k*2ljBoD0mP?;TzvdA8JsEC<_vG7g$W!t7Uym#)Fe|l| zyxYrY{Dv@8IN*?ULBy)=^Gm7dBN{Zw zdhMiszX0-(+`OebF}}<1-n|DG&zQ+4EZ>FB&#?K6;+gF4DFFvpSl3(JQ_|)Jl`?#& zKH)`A|Et4{*76TG7`;TR33{pUrgN%vBZ{R6pJ9zj@bK~72PyCEcJ!L;4C9`DyGXI^Fq`ev>-4=6%quWgs94aN-2?>B#7%%K#| znN()%9zFoA@H)MAlIDmYi}qjI&e^8@zd_wd~s`jH(x$(eAiC#QCyJV7*g1@?%}53=0tUR@bB>s(S@Kf>f@sD_lE&M zp4uC}ZrBBCZJW{S+p(ZY?LbmK=ncI}G>}Lu|Uelo$ zXAgiKFhfQ+m2#PKOMCSDLN7mrjm@-^({?E{dWp96*BL0}35)77baG`ugvpgXFqeJ- z;-v=7oCLl-$zL$MBku;k|H6-f4XUXcP@WLZXcig(8>zJ6esc(BG#CVAt=>YSQht{^ z&S^iF z+6G>$zXL+p7YK`3)9!~3Ca4XWXAju|VWoK08fQiw9LmZ`A!Yrl_HTq2Xme=jAx@_Q z{JuDM8~|rph_cRwv6|D#yE0Hkmy8WFp+Br>c3@Cgoo=iTmctKYmUJBxSD~ED0^nC& z@J7Mxs|ABSL=Ax0m7UKk9)nV87Y$+LoIF}{HulbD2>NQ!ha11j zM}-w8v>#*fXc$b_UbvzR5OBQ1?-7EjrV$Yf$D`P}aFmDny;+}2x$_5?`cP;wlY#0< zV9Qu$r-{=_CCy@o&MYyqwC71oxHXvTh3_|S|D6qSw{MaYF2Gt00$sx#Z^ONufmkNr zT4_sIxttF1;X)`;=?%PU>P&tNkGlqtcN=f#o+`k!h#$Z#0$xm{OFQgdFX?;v#+??@ z2VfSAmJdOPxy_5U$N@s}X)-?6R}Z=`HDA9p9^>^J>@IM7qQK6kW1$Ywi(Rl))sr`` zl5M7IVc`2#4x{%{2GfEs5XJl;Z7ci*rI_0x*L=GAfcMB0BmhvIJ)w`z&AK_vrz6dr z2stP_cMAxj$plS%!lOvv0#DA}QId5rGGvK%DA9q*Qan3RR~di z5?Dhh_~Q4(FiKZ4_}FKLroJ9KbA@q?Xo=KMT}^sKzlEv>*mk)d8RT>sU8N%ty{-` zwmr6(=IAL_(8ad#bUk8?2hUytp9|kcv@te{kg|aTZ(>Bhtc(1jrn5cpI^xzSt7jAR z%T6LOgZnt=K?sO~e2Z^b+sWe}CJx!Qh}RI(M2(@zw^AT@>%f2g@Zh~Z)cP5RPB8q} zZL!kZWXv@iIBI&&JqEg&Tl3;TLvN-eS-fB_cUTFuF8%({dpxb-0EP>FVQ*t1w=vcP z&O~7q(0G=Pq7R1g?b#lQAEp#8*Fxqt!;yi>`~50+X6b3;FO@wYA<<>51{vcQE!MMZ zwLLW@f7Sz_k*;Oi?Pzfn!zQh+~V*#X_%_Q%~DOiJ1>?%^vzskx00!0|Wk z`&I7O1}`MS++USL#@+I&B0&?d+C#+6GA&xvet-`0zt;WUJt+m==0@b(qlmw07A;&p zL1uj~<%ZJJ9`vLac#A*7bYAigPBOb{p^oiJ;U}F;=B{Fa8}T3X9JVK_OPL64B-uud*hkg@w6Df9wZ8>FrDyvs6+ffBh){0vp9@LUB^G*UzR9gB zE_UZjLmRpt=xLjjLUl9v4yGO4o1|`_y`5Eb5|f4?4l130#xdIZuu(At06O_Ue!TF; z5m^F9-+u%^rT~i+d4Wh6CIeH`OI5|*JIH0xb$Oj+-82i%mP6$x$bKn#XBQXFBl#;H03?I*?$ln>A<04Xdo&kaac z?ZgXh^E9ah#HkY?*(`zG^(ntMC$i?SH;+8B-Cs>?aABR^0K?&Xk4Qh{X(AA36p?t> zC+Yd6sh$UlE{{CC?w_?usC z;$^U`s-?SdH<_52%RKDqxUT%1nBBk0v{S*%t8DQL*h8a-@ex|HsuiESmf!)*DL;Vp z@~ORJEZJrQ(z5_Als108IU?RCmFkRRYYk6<()_X|{Omp@FBHGasR*(Bt4f8^vt@o% zc4v|w7>EA#^AE-LkNc`E9US`kp35vQESK*l=pWfvP|Fhp{RRsA&=s4X6nv6a4+5b$ zj#%D*@{+bjz_>LZk3ti{Z;x_(# zXB(gTE9^E{9rh5iNsum=ry`C7_}lo+YMBxLEN3}j_gxTJMh5k0vZsK8%9dgYMGdXBXg#iY&JQ=0q4TIqcN{QI)9{IDvZ8UKL51_hm0U$G^}2 z`Ju+^(~sZw)Lo7O&rx#8;JrJmIRZNX5uHT)@Br1lI*Q)h0W5(Lm79X>Vfc3eQ8M=S z0^d;~)_|pg`cMxHf^V$+`o}w)XAePHQ-xaqtjL3JlHX!1+-uwTJVAHGor@q(V*8G8 zk_1v~+Ga=~f24Wq%pBOo&BOoFGyG0#`JSOC{{nLMZ_6hY&sj`pu|FU1F3fRw#{gr4 z4Jfw1la#rq4R^sz?a1F(WOKC3_OYCs&Z*HrIru~z@*9p_2b!ACP5tC8$WcrEjipQP^s z*5Xlh0jv?Tq1##?;|1}#WWX_7)w$qvDdPs-VX*{=n;mruHHIP3GwE$c?g9I|%41|E zNpm1zo;?Mk^h_4duNW4;Vw^|d{pCqTI>Zua=hy-1DI(w*escu78B4lHULR$Yl8A=LuRdI@e3T zikh!dgcUh-J$nGocjkn&9hMn-*?mOjnQOL@ghj_akI9VR1QwTcL zdpPtVc#ia6iFf}di%TPzJZFfT9|FU0w;4_=p!J!WFq#pD-UQshk198d0N}UXl z*x~s1{RJ0U2cM*PppscSkMK|N#3Ex4itm)L4*BaCYE0;JX$KD34g)WL|Ab+f9CIgJ z*Q;hcW(&dz%Z+aiC{FGOCycyPY+3u@i#+n{9?vEw2}tMWvQ%VG{d*-k%tP@_s6&qz z9T8Y}JE|_A@d>$%mQ?O?iP`zuOSFNuoP{Kw&K{Mn>;yjSSC+`fH6YHr%~3R@aN$w- z`!{41?4d$CHY5b8pa8zcEaT4F&>Q^kZy+EQMo3C;J|^T;a2pp6;4B;+ksYv{xy<`j zJOZDMMm}2fbUx(+i+z4Wv4z`M5|52 z(Lc*N@R41!)*LW@l=FJD&MaJ4GdD5~#gboE_?DBN3kRwAGrF z*_=(FnQ!T-Haiv0#0G(dCqXjqb>u~W%PHj(4cRG?Df;&Pa`>B!e2UvqN8UV%#0A*e351LluS61aCeA-#v4ST#HY%S-k0N z%6DUIke+wRDQLbMKfLB&$#M|h{)Ej%tEM@5WiY`1r4rocZQ|d#a1)Skn&Rk9ad^E? z!ft*V>5XbfC&=513Z&YqodKXO&glmp``yBLk{#-hwZi-t`PedKDUJia4SF!H0wUR! zl#^ful>}ma$)~Ku5aN!w2Rfa^>$w0I_TbxB;u*xGT|yF=c6V&1DAMoNrk&*zo&U!t z10V4*p5kxabKJ~S#XmzYj_YzH{C#SHaFsE zIm<(Mauzgq*~F8IQ`Bg5#NoE4>aP9WTXM|L_Pe^Y!}TlroH!VtA00q}{GLTMTAe~p z17AY#NZ}W+9xo&c6cYn!yI$lyb_-pbjRe&8Tyrl#TJV6Ab^FP~vWMUwsixS!t z3(KeYXPm$&(E^uo`kAO|XTloJDvSoE$$Mg>s$pa;Lc)$f0Cn@44msV`zJrPskEwYW zdr<{ijiS2a*z6+)8rMc|{&?@kLL-14;Ifsxc$;D%`Nz(PWGIo7AywjotgtI1qLN&(sSReERN#Z$|g2x1U=VYvHuBjT(9fS~kvf(?zX% zhtO&i@|3_W2D2h*kWfnxM?pNb*M0#0)T4Y*;q$i59LYBa#O&sd!)nF5DrXXQdI<_1 zfnLyD!-Aphk95-6Yz{u^Iodg0>y{$Tyi5of7gNi5;zXviWXqp-bgP{<`WawxC{ z*{7V3)pfZeiem2*L#qHPGpW0A|C5#J$LSEF_wCYh&z-`iO_Q zN4yTmvdfPoFzNUn>sruNtCwEkm)RIRMb%EK)D1kau6y*P{5Dc^=FLT1YB%vc5issp z@kE)Zf^i=#MC~4CC3Xd2&HcgjBXdo+)xSe(Y<3f{zAu8Rukz3Sn2T+f9_J2l%Vroq zR9(;o5fzx{U(hKi64dxJmW5y;7Voao;q9GnQ5X2GOrgcm(mP@c9Z50_{&-vA_Q-dr zns5VtXqBbE+`6Bi2$=-}b%g%H7TE`g|G!f$H9e`9xWT^J=aZn(*<^b$kPquIQ*H$c zbbgdS@SyU7am4Y+FDM%cEk+cY2VzP<>mV^N)6VwqkBrY_5PTkk2p09qg2&!jWAWku z+U)Ca@_Ta$tZ4+Zad8kIlASXDWVw+EaXe}kjX zmwGSvrvmEbR)H34%zZmnd0JsaSP8LE<>*HszOvZ^KSjwTSfIMhNmW!d5Xz{8?mGeh zs|Wy+64@k^WqG2OF;J+F03OE#(C-K>{^KhDcXb%nl)!X(amY|5QM^xI?UO5)1{|F} z43vz;jFw)f{EwWF$XZaxcCz_R+CJYvbz6pG%&J^($pLrg_dWU!)Z%jgE05=s%|8!N zCQuU5Ik4oFo+iXgRIjCog`p~=Hzt|9+76|aeN+B8{&6s(v`kvdRk`=F4k|KSjx0s67(9`&g-1EbY6D*B^Blp%y1XA@{}Wxp*W$#;TZVN>{nXe0&11g|5>a5l%U&a=mA~e z8!(L{gUb!*uu@Y&dxo*<(*1}UB zC7ggY!vcHKgxcfTY}HVSyZ#zjPB?vb(`>Uy1BR{UNf6kl8@p;(Qnx0!W*m;H{omk$ z2Xz~E-wgq2CfnU)4?Zx)CmIeew9W*wXs|`#iG-~+n4J~?JZg)l`~69tNgUZK=1zjY zr(_%01Y5<=jA*#mv$h5;=Puf;YFgftTd?$%fLJe=2#}5j+^)Vub<<7yPa^J$F^`lKk zuM2s3Fr1d!QfZUlpLv)AJPE=&L4H=+9fwD;ceT(|$* zI8sP5Lm_*FB4p39cf-mil@PLJmOYb|WJ}1(CY#L4-YYA6Z*Sv%zPi4j>r>b7`}^GY zU-$jb_j(7hz6Z#;>BIx2Pe9A@zYNnnAo58j5J@XMQ)>^iKEF$O5B5yw3h}ZIT{TKS_QQZuAUh} z&t_b3?emNgA==-ce~GEkhE=eT#P9n#Le#Zkn!gV7Yn+e8 z4|79_;bM!c5fHFep7@|Qli9llaAS+7e_0kP#*O&cK_zmu)x80FC0t=nX=bn2!pD14 zr^iSL<%t9tg9Mh0gD+!+{sjyD6}-B9Yk(J$3UuS*H33)WZ}C21Xg|HdLP4AROfxog zG zFo^HKG1B71UC_8$oI(43S3HTrNcy)s|8JT|^0(;xaTrXs>$P+N5P#aBqx>pV16tGU zTnHo8lt6fS@d$qmEw2W{`E+!biP1+J~&M&uyKr z-7p+$x5y$tMXF{#UOZ|mrR zkc8C0bj2`!l6#0C(Te3w;BP++$%>{I6B@ltRPBx&4HZHlq*}Riu+ypZc5ws8{kk`W zQkqf#%J|5^+MD8$RjjG;`H*1L@40n?7A;rJZ#e`-QgrudNxafalTL7)&vBpP3CL=Zsb`V(Fq6PaD$-`RCO^YlMbITSK_>&L7i= zRu#}Mfe9~S_N9xhpduy>0IuUde*1s;mKN#$UX794|KlI|EoJ|nzW@5#2;l*0rhftj z`QN?6|LzN~2&j8*IvWpM{bP0idyoIU*?;@O4?*mVk{bW{xc`TsZJPSza*WdAvZmg9N|`JoJe<#4VaLTPM84}J;c>VxRLjmR856bVai;HGC`eWXaG5}O6 z-+BAVKFIMN0BPa)5tKiO_jZPQ_WjKAM*H6jO~_DOUgB;C{=hV90~zHLE;&`u?>__4 zj{-c_Nom9N$uDWKn#JZZJWoe7#{kwzbv&~stYY8?qMnwUCA+{ddz)0JEVN8gvfgyR z6M7)TG&~%ffK<~|5)cIw!u@OpPhEDV1s*+t2=$Gba(b`}#!ledOC3>PNhy>~YUpW+es zi}&zU)hWXVDxdF$6lLCw)~m*AK<}u(wpYjJq zmI5@RQ+%Oc$YEvfhL+vJXkqZMN?o%hKXL$B`-8F5#Zt&#KaD(;+MqWtk&8Ra;K6SJ zAaj5VuzQ(!jGpDX>~EZoQh4A4JZJfZzHBok+m#V5L=z>+`^fHA&1E6`tnu$~&=gy= z&NKge+4p-L4WtgCiDR`Y+Jz-63$|v56$75@7!l}LRJO0aRNfLLzfluR)5!g}gT_;Q ziD;`Q0c1|r@XjoUSRQ+1)L@W)o^jVo-mnT%abyj2pSg@dgvFa~Gc5*hn_o-O#DR};i55PRS zFBqEJzFo~%>29@aXv90R$e{@rWv2VpW4KIQB+!wgMlkq{j$#>Xd=XeR}Dx0u;u>h};#!Ww?>-$mJoJtlI4GA645x~UnN;H0 zw-@_MjvgFt!W>@yv;8vN+q2=cFc)8=^TQm)I>)SYQ*5FVjfpq%v`9c-ja972Y<@B91{F3VdU4oQEPLCg{=)R$E1=0}LB+ zAbl59)ovUEJumRN7-+n)5I-CphfzB&+qsULz0@CR5pd`Mph4l1mv~8@pna9Zutp}+ z{NjIUaR2V+Ud>=Z1CCj210MF7c`pR8t)nmkDM)594tCEnWzhmr@eb65nq{~O#dYia~7Za#- z;Vs+e*I)`p7c#oBf#m`p>4qMj|u?$f~Vsg^J{?aJ^r;8HIXS3XrBw< zIBHt)I>G`^b_8fE^BZZWEveBI%Nw zlYb#S1#wVy^Zxr#deQE5I0=6U^ed z>pTqWq1;^tsQl8f9(YlbS`th_F%eaxjc&9=1tdoVJrkHu4d0;TB4IIZ3<|>+-4o}O z2m}Jh5nNe@3jO))`35q0Tu5-50jbZJ1n^JbdT*i1gj{??50KHeuT$rDazo69qPHG!)7Dz=EMC z)3NeIj;vL02mANa^6!9Dxqn7rea+GHxd$p_y&S(WXcJ+^L-@Uq;`g0hgy_$c5&^+_ ze>a)My}w+_(zg@K4^~ZzE};2{(3qGGzFRjO~(M~ubZT3dSX?Q+b;CT9qcSqn5lVR z)3^t0lD39d-L#wKm~p444O5|(l!Mv82;*=Rs~W2c6(MPQ1&C*uZr%LuwSqN9LK|J* zjPXnjz+C!;`daumu8g~%ROW_S9pIV=rszwIf|tZTn0PrTR(s6iaJd4RG8LX46JHdY z#(6E9R7N6WB6Mw2ZqE7pDH&(qAlkn=u!A;LKFp0uGAe@uW6TedONLb7Z*agMa6jn7xxiCo|Izq?w;_y6AfE%y63U&ss z_0_&Ok8NhZAww!uAY2NA{2s|?Z?K`*8|+@yN2Obl-lbwK-Lp%~ft=uoREQtKO$cXC zcB@aE4)Q7QQslf+CPmpTNX81qb!D>>9EutqLi@vN>u%d6kev0IYyf<)YP!iBaxI37 z%YLHoj=)4Dyuu*C7NcuLW{q;evO6ufQ}MM z^tGNf+V+DrKA4tP`w2gxBnZ<#Jy6FJ`6wP}LNYyMlzcvEHE~-uG;E zN>E~P9y7Ue__Ix!g&k4%-TJvVVILn!R4X-?x5%~&Zw8I0QQUP~E*I_`c5G=7oOqWa z)@gA%l-2jni{dwB$6Amgh>*!`@Qn;IP@fx(4mzGh)|W(F%4uF$s%$Nqf5kP^}6uh zpGIk3H|!xMiB3?G-z4gD!u3aPk5+`Z+rMsptw1eRjfYnVZGIg&h(QSp%>uXLTTZv= zl6O^BX^r7)-AB-*C-{YL6`2%O#)vwMpJ@#s9xB>7v$f7YAxaoWcS((kxpmWxYlpy` zZtGkBa{KT=VBY$Bb|+;M-j`{-?Q=h_*ILFE`EMlCOFZmnDX=jNO%K1_^jvqaoEd|T z1|x+4JLQ489w7lb;hXQ6hYGX-53~_Hek1xDeF%RtXHpXb4admw7pnKWX1^q~XmLQ>f%0-l z`!U#aQFt=bGs~65sqdOllmJE|O(%7Pk-{SxN_2Ako&EHbkhcc;LOE;kejg{8ucWFG z=U5C&o){9AL^dxL^VZyuW^8U^3Ezb>(u_AGgE@}HGTCp`u3y*5sfSqOrsCIFK90pj za3MTIpzP>W-vAxe@_R)YUimX*;l`Pf^s2f;?{C;2t3?>e>j(Mf}SLxB__;C;(uya zoK6Z)oLygW`*J};ajCFdc^@UPib4|b!o`NjRqtn$0jo)^rr{ac^y1@!*Tcis3dOn; z_%|q*%BfTYW+HAnjJs@p^C!PSGwWAn3$yf}&U8DB@d+G!MRK+l`bnO1tyRiQOO;Bv z6oDl;oz>(qi5(@zAp#GLK4urU+F9xA>Fg({45WN6K=U-S#%=Kjvjx(H#@i2H)m)Jd zWSPEo(zIeI$hT~~kuYmyk&!RNSok*IV&5@D&tHm=$8aNF&XFr8iPu~;T0fM+Jm#iC;wQ+e~AP$ah{G zy9^lmMyAzwjtHHg?30+&icg(gCoWBGidSWBy8PVS6MyP6`)f4bi>(D>q}Zu9b?p=A zdEe%*5-#r)vyl4OUY#2mD0O+x|L8@6Ur+|8lX9w9Li{1SWCrmnwzyt_nXMs*5qgj% z{%w*rhNlXevKo(!d799xxcOh*!!fQ&KGX4mYu>pcx;M!T*-@Nl(FHJqZmKv*iDRbS zKo=YA?=$05t8q-a`Wzk0>e9gz&G+#wH=kQef1<>?{-tI}ZTGckdZk;}U@Nz!8OgO= z#gsM2+UEls17T$QTGIg!72q;`SP3Iyp2aIW*-~YCQ)@xBMG@&Dht@+ z&Q%m@0ldoWRW2ketn*VPjTCP)haNB264tu0wNauE9M|l=e;QOkt(RtUn7m}xtf@`YI<3;Uk+|ZM0YT-OA1|$ zSBZ{yZ~Xca)&Q!tq3RL4G&zGLZlV~hQ`g{K35zaQ_kD@f{|wb7Y}+C}?|)SMgJ0{f>Ujqs^L|g33QGxDD|H(0jNKe#lyN znHBQxt0S||npS4!xb4wAb&|Q`T%SJK{fYX3ruOr|tf`UedH}c6^}HT`?h)!N(oD53CLhT!F8zUGqg z$n3qS?vp^kq3vlff3TOm&PM8XI7O|k7AaFjAJo=ENTbUr(cC2Q42;KT#u>>8oNm(n z7TIYqx?^h6cW{hn?VfCP3hK6Rq}pwU=#ln*?TV&4vUfJ;=qpKeW-XV$JAaqCn0Ek^ zaXHflQ#R#e*kzd|LAmW)Oi67Gp{H9djP(aW{Am*xT;Ua$OWgRX-!kSg2Ql69?f9cG z=HRWlO%Y(hINjgK`z9X4`CV+kMTN90f7viHHSpFQU23Yqq2^C5ZytUMn5orDG23tC zs|t%&5dm&dLk+8D)&qpY*V;7p;_s~()HRC1?*^gdISuzC2)M*iEE?`W$fOjTxmD`!I1U_H?* z`kK7y2o=bP_leiS7wW50xh0y89axs_URP=I#`!6>Db`2!J1Ku~LLH2JpcmJDQ;>OF zlYWh-Y-ZDNFP2=^C&qSH9_K8X-iF<4XIlI_OFpJ|GY5K|UUv8O5LdCbVi;~H&d+F; z)WzkQiC%5hOyJ6*N3+;4Bs1Nl^`*Qs)Pro0+p1A56uxJIYjfPqkQ%CvJJIs+e10&F zbTu-G>Tw#=pLcewPxXetN)v`1Pf;$y z!1d<6p*y+~CznY%dRN8+3R$}4`x7=#Hq-Opzc-wXm8N3vwzei(YxmmBxApdE#9wk+ z-F&aSWqMGY;A@8|TXHoqSgkm%YDub3!(8I956`GxJtW>FlB$)C?4ZzP7d)F!aEjGk zK`+bflB!g!AX^eRXH%hFZAXICDc7*srD@l(RQ?JR9WQENKv55(B^jg5$D-&U^0%$ELqYxc8$vJZ`Rr2s7SgtRP*dNJCuP48gMd=c5) zNKQT_r)d(Yw$VP{!f7!C=8AQv$#hB-uA3k^C824SU9@48vQE=*VP$`| zoValVw3?d?*PSK&KH_b(+Y`lk*1`HQrY`tYOP&!9s5?i4m7CVFO_Hv4em#+OFBgH9zByA_$?j_a}DX`xzC9PNL3t=Xb@qI%Otmafz zI&0tuqn85Z--{+mgL1;TR?!K&g*j}36%cbsvrI+l(<)PvbtS8OSVehbuH4eGqx{Z4 z6|~d}(=!5P!FdLU64c#=WMvMeZ&OJAy_~z4B$gr*-2#+VvSeauH#Wf*U-0S_Jkhbs z2V<3)l8*i<`-uHVLhHs!!LFyZ)og#}8Rc7ho)?krSiB50Np>w;F8u2+W>VR&Gr0S8 z&TlLZP*m-G;rN-*9p3YkklFE;ZIo=dHkLb>imt+Y z4Q#OMYa+Z8pPL-=*d}6{A~&3@t+~Xq3cKX@XEVde!>(tQP6l`nTYvN=pnpf2vu?-= zCHft6y@8`VLJ>&BW#6tz<6~dnkcy93UtYdre@bW!Fmp?4g;0QEGyr*h?uV<+^nTu^ zLM2m?40(tKBpAAm#OdddZ2#sd=49Z56~h3&ZI(JWQ}E|%ZVRlQ-xn}J5txpA9F|VZ zp)%DhoO`Sq?IORjeSt7Bqu)u3d9dW&(P~9>g$S{_2jo}1Qa>M zUZoPE`!U>Rt}nc@z4nmNJr2HK`-phhuA!u=cTLSQ1mV{@doY(96B|4tX5_X4LOCzx82}i%vXR@RMEhqO%<#daF69 z(l(1i36#K^&?QjgS}d_;e5+Y7wP{ek$7CvrRvi2h-eIl$DwmwV*t52)CM}nAjfX3E zU9;ue)L-hh<`JSlK4s52uIy&n%HX==>cW@&vQyF)Ery0G)iOMe`)T*ARFIsTBNYw|D)Z~Qw4l!D#@mm{vehUT4m%qj6Lo;VmE z3T;?vfS~e_UHbG^bwnOkM{o?2{cP(bcBVaLd-SY89uV*nb zoyJxTE)S+h8^_A^AmknMHB5=J-5`H?dz>qP9_?&qFmVV=he=lny|{k$jYFSz)Ob{5 zveT%=cDc<^_e1(sH!Z(fa;gU}#y_XITSv<3#s72f5zWR#^gBNRDAY36JsTHDu=sFFR_owXM`P9Nr=d;i_)6PC3eiL8 zSr)GR7^)hr)+91$#gEoz)40c&}>PoLc-K&e~JyV%*i7? zDAA=E(+^?Ebib|vtW4i=ANp`UZEJ%f`vb0EhbzQ!8Hwxz3d~LLoCivVTQ8D@LZZPm zxY@m`fX%EfbaK#NtxPSmqZNX75_rF}$cXIL{H$@rqdai_RZd(gSfx8dYZ)Yt&Lfq( zMQdjZH{<~aBE0WNVEqs#N`;=bP{Q;y?z$)XGY~Tk+7ygma^DJ}xT4V&d@JI0h)-|; z;U6jW3O#40Yu%-V?QGrh;h8pcbbX3 z#VyBukPnDXD)Dz+X~(&GLJzkO#5LBqFGysj+C>Gi+EY!psv0kxi{W&=#!mW}4jiER zQdfD2_e{Dz(0i0Ts*9|KNx(BB7)gY`?dl_}Ia_n(Of36tEdR@4(vOOq-M3PxwW9nZ z%%Z16awVCOo!5a5y__xDZ7HNp5W{dR8CIZaO5^C14p93kDyEmJ#&tMcnR;3{6cbkDR(I4smSnCNqg&RU;urMy0(Sd4>ury+xvVE z^@g9zIKLN}>+-&UcY6l=)zg#ag*8L(<-Lk{g`RrrSGINK8F<{IMiEt=OFp3fK3a{Pm~SD_31D=Q4fN z?inVqUVyW9R4jyCbloC%!MBG;LKX!Ud-<=#Mq^h1l42X-K>etnPI+^lsL|Vcx>lORLZsCdvUxYwJEOs=nQXq>4RECZQFz$-G^6oCxc{fNZ3ZyW-qj%r_A-HqfYd)D(Q@u@y+H??>#P@cuc8;}`jNV8BCL~il#4>=Y_$^f$q zsxp2U;=_fW<2dT372e4d#{Bn&EUgYyI8vpbS(GzRewH6Kr5*YHMHDMfTk zzu9sHxv6{sL^vx@5RnZj7B;87&)& zh=B}6PxB6Z&&o9&bsY=@_dd19^@;1v0j^?aEX>g2dx6OzxIVh-=I|mtCpAe-Eim8Z zBv0*r%vf1U`})da|F0JlBoA*Xh3K9eRDz}oH7b`kH~Q~k>zi=AqC9GF)4B*%frW`P z)AVB*VgWeoYV(<@ZKFhEEulxt^Aq922DghTJXYEJ0k7l=;fX6UgtQ; zJi>{MO0oST$>p&SkoSpBpjMdOhrM|CWDK8|l=$=M+xH5DR7Qo;qxyD{H7)++QB74B z57En+?6+Gx#)cgYW(O7ILdP-AkJfmyPrgcCJE`o{>ZhMVEDMw( zo>xY!tyP^M2k*7tdF}>!%m+?)WA9j`o#BI8yd3*YRcVh?!m~`c0ki@!@@|Q0j`^tY zsQA@0CY(R5ALeanMsUeCFS;9F{K(`Q?9e(ZU>N=y*^M>nnA6yttP(TOIJ_ywEFLfl zJp8hbCAKGFeBM+uL&B*$Z+oomGoXD5K|gv3q+t?lR44-;5Or7< z-t@e*jWAI*q;e{VO-6lB!#`B>`h^Lak3(U400CncjY|%ksoAY$y@X~0ySJm?GWg1* zi@Vk*PTBLIm3illFX6Wl$NJ5bCpbU@Bjev7_s3{&`q z;)Zi2*5{WA44X62aVN*S)g03t3fKE8ClH{R~j)%UsU zD=BV9Ck8IEzlu9ejAxkSQSUaOz)*aS!J?SPQaHG~wAHQBz8O~iJ%w~trJC#AmRs`M z-nj}AK0^KPH~MwD85CAcD`c*^(o6Q|O``g3KVvS?Rg{Uygm7ka=@4+Ss}anYI` zSvOwv=JF@fV9xmVGV(y}#n`F^XQ8Vq!>L#_X%3}r*Cn#5lYxLPo~o+@Z}YapX2JGp zy+lv|7Hr=2-a#8bKLg_NKK$aNkKt8S6pdeo4A~CbhMp~Emyni*H<9WiwpW89ntOg| zsrwMXG}Lw6@S4t1Q1g$cc5OSlgUE+zJ0;XQZ0A-2psJ+07~jTiNp;UG#2~5WoYW5= zil@!`UsW~Vwt}8MY_@=G5ZJO7ckD?7Hy3sTs5lZ7H}XS8_G37y7~HtR4{@FKE$S9G zxt<=C&{N))k6r4VLA_J{*)6k!Z%M~uoMv@jC>)Tt1{{sJtErL zGs~Oj1e-W9+-(}u7dJU~4;`9HW>DiNZl>1r3q*~lOnT&=_|>5`j=fNOVc1mSZA0?q z-oZikdI(yNMS$IXvGxaGS>&Ys@!XA!DNJdch^>28hx}_br#3g{5+wgX+9eH)j5JYR zkD?dKDi*nMG>Vz|1s|6)qB(16uYc5w<^Ni8RZK}m_K>TRn0ZI=16FIy**JbXqNd{7 zB0&kUE#lp2!y67UhikrKU$--$XDwuJN&P|uje|N3Z^Avx2XxhndFX=!!@x0Uef^-F zDUM(ayl0;<_uq}ha%Ni2Buf6~Bogb^9?O@@Q>MSA%IhC=?Q8LiD&gWfW6OhkUn#KxUpeNbZn~An7x6wd<_gg1ZpMcLF;`T@m;>KMbw3NU&O*LRmS#yx3?cre^JdB z+xxkensM`lx-YlJ=0`U^m@aRo3tnG3Gw)m&H+%QJ#;5%8f>evFJB?>NUY-e0)C;92 zdKrCfnN9r~@sMe$<4_Hzh~VY$dAAD!_OZ4^l&A~lL3YkwxVxQXVQIYhOfzkmpA|E0 zx~1QZBcr;d$Hg=Y$KNd7?y4)geG_WzwvVU9Rmn@pv!mlZMG@^1`n4~dtfLc#1qS7U z#=FzuD&N%`jE4<+1^R=y*%ykRE}=e4?0(wR+)^?lvuHT;@~Y1`W3_?D9I<3rVd1f? z=sRjyfC}}F#<%eTgY!-sm26DqwmSLP+g~5Mg7wK;zwcUzQpwStKF)y#vL^$e98jH3 zQu)!mqH6t(YuQ@_itr6OQ5pDkuT&g87rsSUs0f~&+2X`i6jd9B=AwAW+tV(s}v%EeSDC5@@Qr#;bWNhlzWE>OFiM-yJlIf=Y`Bh)MgWz%S>1~?Amu6981x2$K7KU_V3jNhL9uhQIy?pW3=Ukz^ z)%544g;b`SB$(&!1rh7}h&2(9H@b&&MZ&D~w^3mlF0#%zxsJN%EU$m^J|9#yG2XIF ziRB5nwZmgy^IWqe<1JcqeFNvmncZFZ-SvtexJ15M_CGvQ+Wg?8h-LnfI5tN{YRo!y-+oB@{8Me?v@ z)yHl48T4qw7ubvs@2a>%wD9zqqbxg0sdhda+D)DFNO=*A1qW?@qIP=DOngw~ zeyDeylrY)-RWwSnr^YF9#c7JavuDsWV*TmSjBKG?BZmdh1b#9wUrbP+MOF`|UyTui zpP0*E69P|KY{~Wk0_KJMcL;XxN4%M>;2*z3t32YJ?&w|WESgoZ(P{|Q1ulhoo`fbG zZHPM~#mL)`XJr^;64lbh4n%ctlnM1@gPkTArWI(ff8muhVPvn`n>3NLyGrDjG<5S` zP{C|1x7L%4_jLfYQBPvpYFdA}fE%>O!Q*`Pky9TyALrGbWg}Txyv2=SeMgwmwnIW| zGJVdAzm}30WD`NIS~%CUz{o%2LmgO>6HT^8+&fBg}>w}d=1b(LkLsn7WeJ8?B zLYfXe2A}x0s?gh?@Y%8p@9fRVh+Ud&aL|0#!nnCLW!u$jsWd{txu#7LOzpHgdG<9L zsx*MP0w&_^{A+QVfH`jRD5*GBf&aB_BcGtIS4rivh39w<(ler5&F_bFAu!b6pBh`b z4sX}wATsAwfbGfLP{ZSA%n7V-UNr|rh9OkoYzkw0L%?;s85H#9eJEk~O&yUj{#>Fy zV)0NZeJPnh;$g$!8-3W+`^_?fy^)LBock*v3nNShmpn-7=^vc}+JhvGk+oBan{ z=O?;?gxWAM^IHRFI#57K{nnR#%hwHAAn+XcxoG$W za%@m?R3AQjSnoHy>PyDx=9gKmFkMn*m}s#G9^)V^QlU?gb0MC zFu_P8cI7G`v32boT@@QOgj?Ej^QjrZmqd&4tY3B*CQ)hK+_N zN4xme?&W=oZw^DBADZl2Is4Q0+>gE;DG`FE_P|#6kI*A&FoGRa;96JkR>;8LfgJUGp~UI(D&Oz@u1L~3lP z9^#h2_gV`RDxCg3kg6eNC$CNba@IUmhI8utGH|Yv=xmhA6ZRqFR^gH5k-Fep{=sXvN1d8wa5oKF&~aWgP)DZ}2_H(8UTe_E5jBDg>^o+&JU)ud6cr zbye#b8Q)Q4HJ^vuJfc{RiFd^3&IAEOKnD#*LuTieWGQV zZPz}tyUZ)-w?2Ib=Lhcx%(q;zlfoor_e4f6I<}osTJ&SvIS$^jPs^{k%-y2CG>{Gi zSOJ#_Tz%}4QG4yEC}Hx*C~z6{bc$s{aOk>+u@+IxKnPo|2lOh&z0d#8x8Z>uMB!;& zph8YfNBRT%`;PaVn>)nW_31h-g;ZnHV(go4&tdUVEak4TdPb$Ykjde0;ysT|hK^ zhAoWIhbNeV#Gcpb)_^SLJ!(IERR=<{)5n3<7m-KQCwoox`EA!?^-tQ}kBKk4?M;~D zB3YAzboz)i-UmU-ypzE*F-+iq9bAhh z%=A(BC*no_9XJ09pfxX;IF)yB+Km9BT7Kfu0!{@R?k;^?j(W5BQ9@~UaMRGnuF>J!pwb{HQ1#X0>>V=&{M7znnjU7WloxG z5nGHw>qs?qoYm*!s5*G9aa8^od9`OK?nQAg1w%o?J_2yWAnB<#_5wvsBpHLrLHvc? zRreF2iuFc{3BdTdL%VEHG(Ix>k5@EZnJ$|5`rdn{L7A`44DN0%HOXfcs)4#6HU@TC63=dKjwr@K=3xBt0I{;|G(|F(vN04Nb8;_4Uv!!Q2l u|Ke0bkVUmrwa|ZtqyO+P|KGf&J+V!L*&;9bB}X*)Pfki%GUukji~j|Xl4@N5 literal 0 HcmV?d00001 diff --git a/tools/disassociability/visa-pets-FL/ot_library/.gitignore b/tools/disassociability/visa-pets-FL/ot_library/.gitignore new file mode 100644 index 0000000..dfaea16 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/.gitignore @@ -0,0 +1,227 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates +*.vs +out/ + +diskhash/* + +CMakeFiles/* +*/CMakeFiles/* +*cmake_install.cmake + +CMakeCache.txt +*/CMakeCache.txt + +*.a +*.args.json + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*.bin +frontend/My*/ +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +## TODO: Comment the next line if you want to checkin your +## web deploy settings but do note that will include unencrypted +## passwords +#*.pubxml + +# NuGet Packages Directory +packages/* +## TODO: If the tool you use requires repositories.config +## uncomment the next line +#!packages/repositories.config + +# Enable "build/" folder in the NuGet Packages folder since +# NuGet packages use it for MSBuild targets. +# This line needs to be after the ignore of the build folder +# (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml +/WeGarbleTests__ +/thirdparty +kProbe_* + +CodeDB +LinuxFrontEnd/VisualGDBCache +*.opendb +*.pdf +*.db +*.sln + +mpsi.VC* + +/psir_8s.txt +/psis_8s.txt + +testout.txt +online.txt +offline.txt +Makefile + +[path to project]/node_modules/ \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/ot_library/.vscode/launch.json b/tools/disassociability/visa-pets-FL/ot_library/.vscode/launch.json new file mode 100644 index 0000000..7acb2f4 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/out/build/osx/frontend/frontend", + "args": ["-tut"], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/ot_library/.vscode/settings.json b/tools/disassociability/visa-pets-FL/ot_library/.vscode/settings.json new file mode 100644 index 0000000..5d38dc1 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/.vscode/settings.json @@ -0,0 +1,82 @@ +{ + "C_Cpp.default.configurationProvider": "vector-of-bool.cmake-tools", + "files.associations": { + "system_error": "cpp", + "__config": "cpp", + "type_traits": "cpp", + "iosfwd": "cpp", + "__bit_reference": "cpp", + "__bits": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "array": "cpp", + "atomic": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "exception": "cpp", + "coroutine": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ostream": "cpp", + "queue": "cpp", + "random": "cpp", + "ratio": "cpp", + "set": "cpp", + "span": "cpp", + "sstream": "cpp", + "stack": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "thread": "cpp", + "tuple": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "variant": "cpp", + "vector": "cpp", + "algorithm": "cpp" + } +} \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/ot_library/CMakeLists.txt b/tools/disassociability/visa-pets-FL/ot_library/CMakeLists.txt new file mode 100644 index 0000000..211c194 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required (VERSION 3.15) +project(dropOt VERSION 0.5.0) + +include(cmake/preamble.cmake) +include(cmake/buildOptions.cmake) +include(cmake/findDependancies.cmake) + +message(STATUS "Option: CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}\n\tRelease\n\tDebug\n\tRELWITHDEBINFO") +message(STATUS " cryptoTools_INC = ${cryptoTools_INC}") +message(STATUS " cryptoTools_LIB = ${cryptoTools_LIB}") +message(STATUS " ENABLE_BOOST = ${ENABLE_BOOST}\n\n") + +configure_file(drop-ot/config.h.in drop-ot/config.h) + +add_subdirectory(drop-ot) +add_subdirectory(frontend) +add_subdirectory(wrapper) +add_subdirectory(diskhash) + + +include(cmake/install.cmake) \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/ot_library/CMakePresets.json b/tools/disassociability/visa-pets-FL/ot_library/CMakePresets.json new file mode 100644 index 0000000..f20ae86 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/CMakePresets.json @@ -0,0 +1,82 @@ +{ + "version": 2, + "configurePresets": [ + { + "name": "linux", + "displayName": "Linux ", + "description": "Target the Windows Subsystem for Linux (WSL) or a remote Linux system.", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "HYDRA_FETCH_AUTO": true, + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "DROP_OT_FETCH_CRYPTOTOOLS": true, + "DROP_OT_ENABLE_PIC": true, + "DROP_OT_ENABLE_RELIC": true, + "DROP_OT_ENABLE_SODIUM": false + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Linux" ] }, + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" } + } + }, + { + "name": "osx", + "displayName": "macOS", + "description": "Target the Windows Subsystem for Linux (WSL) or a remote Linux system.", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "HYDRA_FETCH_AUTO": true, + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "DROP_OT_FETCH_CRYPTOTOOLS": true, + "DROP_OT_ENABLE_PIC": true + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "macOS" ] }, + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" } + } + }, + { + "name": "x64-Debug", + "displayName": "Windows x64 Debug", + "description": "Target Windows with the Visual Studio development environment.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "HYDRA_ENABLE_BOOST": true, + "HYDRA_FETCH_AUTO": true, + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "DROP_OT_FETCH_CRYPTOTOOLS": true, + "DROP_OT_ENABLE_RELIC": true, + "DROP_OT_ENABLE_SODIUM": false + }, + "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Windows" ] } } + }, + { + "name": "x64-Release", + "displayName": "Windows x64 Release", + "description": "Target Windows with the Visual Studio development environment.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "HYDRA_FETCH_AUTO": true, + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "DROP_OT_FETCH_CRYPTOTOOLS": true + }, + "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Windows" ] } } + } + ] +} \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/ot_library/cmake/Config.cmake.in b/tools/disassociability/visa-pets-FL/ot_library/cmake/Config.cmake.in new file mode 100644 index 0000000..ef259d4 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/cmake/Config.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/dropOtTargets.cmake") + +include("${CMAKE_CURRENT_LIST_DIR}/findDependancies.cmake") + + +get_target_property(dropOt_INCLUDE_DIRS visa::dropOt INTERFACE_INCLUDE_DIRECTORIES) + +get_target_property(dropOt_LIBRARIES visa::dropOt LOCATION) + +message("dropOt_INCLUDE_DIRS=${dropOt_INCLUDE_DIRS}") +message("dropOt_LIBRARIES=${dropOt_LIBRARIES}") diff --git a/tools/disassociability/visa-pets-FL/ot_library/cmake/buildOptions.cmake b/tools/disassociability/visa-pets-FL/ot_library/cmake/buildOptions.cmake new file mode 100644 index 0000000..1e5cd19 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/cmake/buildOptions.cmake @@ -0,0 +1,41 @@ + +macro(EVAL var) + if(${ARGN}) + set(${var} ON) + else() + set(${var} OFF) + endif() +endmacro() + +option(DROP_OT_FETCH_AUTO "automatically download and build dependencies" OFF) +option(DROP_OT_ENABLE_SSE "build with sse" ON) +option(DROP_OT_ENABLE_AVX "build with avx" ON) +option(DROP_OT_ENABLE_PIC "build with PIC" OFF) +option(DROP_OT_ENABLE_ASAN "build with asan" OFF) +option(DROP_OT_ENABLE_RELIC "build with Relic" ON) +option(DROP_OT_ENABLE_SODIUM "build with Sodium" OFF) + +if(NOT DEFINED DROP_OT_STD_VER) + set(DROP_OT_STD_VER 14) +endif() + +#option(DROP_OT_FETCH_CRYPTOTOOLS "download and build CRYPTOTOOLS" OFF)) +EVAL(DROP_OT_FETCH_CRYPTOTOOLS_AUTO + (DEFINED DROP_OT_FETCH_CRYPTOTOOLS AND DROP_OT_FETCH_CRYPTOTOOLS) OR + ((NOT DEFINED DROP_OT_FETCH_CRYPTOTOOLS) AND (DROP_OT_FETCH_AUTO))) + + + +message(STATUS "dropOt options\n=======================================================") + +message(STATUS "Option: DROP_OT_FETCH_AUTO = ${DROP_OT_FETCH_AUTO}") +message(STATUS "Option: DROP_OT_FETCH_CRYPTOTOOLS = ${DROP_OT_FETCH_CRYPTOTOOLS}") +message(STATUS "Option: DROP_OT_ENABLE_SSE = ${DROP_OT_ENABLE_SSE}") +message(STATUS "Option: DROP_OT_ENABLE_AVX = ${DROP_OT_ENABLE_AVX}") +message(STATUS "Option: DROP_OT_ENABLE_PIC = ${DROP_OT_ENABLE_PIC}") +message(STATUS "Option: DROP_OT_ENABLE_ASAN = ${DROP_OT_ENABLE_ASAN}") +message(STATUS "Option: DROP_OT_ENABLE_RELIC = ${DROP_OT_ENABLE_RELIC}") +message(STATUS "Option: DROP_OT_ENABLE_SODIUM = ${DROP_OT_ENABLE_SODIUM}") +message(STATUS "Option: DROP_OT_STD_VER = ${DROP_OT_STD_VER}\n") + + diff --git a/tools/disassociability/visa-pets-FL/ot_library/cmake/findDependancies.cmake b/tools/disassociability/visa-pets-FL/ot_library/cmake/findDependancies.cmake new file mode 100644 index 0000000..9388ad1 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/cmake/findDependancies.cmake @@ -0,0 +1,66 @@ +include(${CMAKE_CURRENT_LIST_DIR}/preamble.cmake) + +message(STATUS "DROP_OT_THIRDPARTY_DIR=${DROP_OT_THIRDPARTY_DIR}") + + +set(PUSHED_CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH}) +set(CMAKE_PREFIX_PATH "${DROP_OT_THIRDPARTY_DIR};${CMAKE_PREFIX_PATH}") + + +####################################### +# CRYPTOTOOLS + +macro(FIND_CRYPTOTOOLS) + set(ARGS ${ARGN}) + set(COMP) + + if(DROP_OT_ENABLE_RELIC) + set(COMP ${COMP} relic) + else() + #set(COMP ${COMP} no_relic) + endif() + if(DROP_OT_ENABLE_SODIUM) + set(COMP ${COMP} sodium) + else() + #set(COMP ${COMP} no_sodium) + endif() + #if(DROP_OT_ENABLE_PIC) + # set(COMP ${COMP} pic) + #else() + # set(COMP ${COMP} no_pic) + #endif() + message(STATUS "COMP=${COMP}") + #explicitly asked to fetch CRYPTOTOOLS + if(FETCH_CRYPTOTOOLS) + list(APPEND ARGS NO_DEFAULT_PATH PATHS ${DROP_OT_THIRDPARTY_DIR}) + endif() + + find_package(cryptoTools ${ARGS} COMPONENTS ${COMP}) + + if(TARGET oc::cryptoTools) + set(CRYPTOTOOLS_FOUND ON) + else() + set(CRYPTOTOOLS_FOUND OFF) + endif() +endmacro() + +if(DROP_OT_FETCH_CRYPTOTOOLS_AUTO) + FIND_CRYPTOTOOLS(QUIET) + include(${CMAKE_CURRENT_LIST_DIR}/../thirdparty/getCryptoTools.cmake) +endif() + +FIND_CRYPTOTOOLS(REQUIRED) + + + +####################################### +# diskhash + +if(NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/../diskhash/Makefile) + + include(${CMAKE_CURRENT_LIST_DIR}/../thirdparty/getDiskHash.cmake) + +endif() + +# resort the previous prefix path +set(CMAKE_PREFIX_PATH ${PUSHED_CMAKE_PREFIX_PATH}) diff --git a/tools/disassociability/visa-pets-FL/ot_library/cmake/install.cmake b/tools/disassociability/visa-pets-FL/ot_library/cmake/install.cmake new file mode 100644 index 0000000..57fec0c --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/cmake/install.cmake @@ -0,0 +1,67 @@ + + + +############################################# +# Install # +############################################# + + +configure_file("${CMAKE_CURRENT_LIST_DIR}/findDependancies.cmake" "findDependancies.cmake" COPYONLY) + +# make cache variables for install destinations +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + + +# generate the config file that is includes the exports +configure_package_config_file( + "${CMAKE_CURRENT_LIST_DIR}/Config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/dropOtConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/dropOt + NO_SET_AND_CHECK_MACRO + NO_CHECK_REQUIRED_COMPONENTS_MACRO +) + +if(NOT DEFINED dropOt_VERSION_MAJOR) + message("\n\n\n\n warning, dropOt_VERSION_MAJOR not defined ${dropOt_VERSION_MAJOR}") +endif() + +set_property(TARGET dropOt PROPERTY VERSION ${dropOt_VERSION}) + +# generate the version file for the config file +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/dropOtConfigVersion.cmake" + VERSION "${dropOt_VERSION_MAJOR}.${dropOt_VERSION_MINOR}.${dropOt_VERSION_PATCH}" + COMPATIBILITY AnyNewerVersion +) + +# install the configuration file +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/dropOtConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/dropOtConfigVersion.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/findDependancies.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/dropOt +) + +# install library +install( + TARGETS dropOt + DESTINATION ${CMAKE_INSTALL_LIBDIR} + EXPORT dropOtTargets) + +# install headers +install( + DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/../dropOt" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/" + FILES_MATCHING PATTERN "*.h") + +# install config +install(EXPORT dropOtTargets + FILE dropOtTargets.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/dropOt + NAMESPACE visa:: +) + export(EXPORT dropOtTargets + FILE "${CMAKE_CURRENT_BINARY_DIR}/dropOtTargets.cmake" + NAMESPACE visa:: +) \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/ot_library/cmake/preamble.cmake b/tools/disassociability/visa-pets-FL/ot_library/cmake/preamble.cmake new file mode 100644 index 0000000..d7a828b --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/cmake/preamble.cmake @@ -0,0 +1,87 @@ + +if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") + + ############################################ + # If top level cmake # + ############################################ + if(MSVC) + else() + set(COMMON_FLAGS "-Wall -Wfatal-errors") + + if(NOT DEFINED NO_ARCH_NATIVE) + set(COMMON_FLAGS "${COMMON_FLAGS} -march=native") + endif() + + SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") + SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO " -O2 -g -ggdb") + SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb") + #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") + + endif() + + + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMMON_FLAGS}") + + + ############################################ + # Build mode checks # + ############################################ + + # Set a default build type for single-configuration + # CMake generators if no build type is set. + if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE Release) + endif() + + if( NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Release" + AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" + AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" ) + + message(WARNING ": Unknown build type - \${CMAKE_BUILD_TYPE}=${CMAKE_BUILD_TYPE}. Please use one of Debug, Release, or RelWithDebInfo. e.g. call\n\tcmake . -DCMAKE_BUILD_TYPE=Release\n" ) + endif() +endif() + +if(MSVC) + set(DROP_OT_CONFIG_NAME "${CMAKE_BUILD_TYPE}") + if("${DROP_OT_CONFIG_NAME}" STREQUAL "RelWithDebInfo" OR "${DROP_OT_CONFIG_NAME}" STREQUAL "") + set(DROP_OT_CONFIG_NAME "Release") + endif() + set(DROP_OT_CONFIG "x64-${DROP_OT_CONFIG_NAME}") +elseif(APPLE) + set(DROP_OT_CONFIG "osx") +else() + set(DROP_OT_CONFIG "linux") +endif() + +if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/install.cmake) + set(DROP_OT_IN_BUILD_TREE ON) +else() + set(DROP_OT_IN_BUILD_TREE OFF) +endif() + +if(DROP_OT_IN_BUILD_TREE) + + # we currenty are in the vole psi source tree, vole-psi/cmake + if(NOT DEFINED DROP_OT_BUILD_DIR) + set(DROP_OT_BUILD_DIR "${CMAKE_CURRENT_LIST_DIR}/../out/build/${DROP_OT_CONFIG}") + get_filename_component(DROP_OT_BUILD_DIR ${DROP_OT_BUILD_DIR} ABSOLUTE) + endif() + + if(NOT (${CMAKE_BINARY_DIR} STREQUAL ${DROP_OT_BUILD_DIR})) + message(WARNING "incorrect build directory. \n\tCMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}\nbut expect\n\tDROP_OT_BUILD_DIR=${DROP_OT_BUILD_DIR}") + endif() + + if(NOT DEFINED DROP_OT_THIRDPARTY_DIR) + set(DROP_OT_THIRDPARTY_DIR "${CMAKE_CURRENT_LIST_DIR}/../out/install/${DROP_OT_CONFIG}") + get_filename_component(DROP_OT_THIRDPARTY_DIR ${DROP_OT_THIRDPARTY_DIR} ABSOLUTE) + endif() +else() + # we currenty are in install tree, /lib/cmake/vole-psi + if(NOT DEFINED DROP_OT_THIRDPARTY_DIR) + set(DROP_OT_THIRDPARTY_DIR "${CMAKE_CURRENT_LIST_DIR}/../../..") + get_filename_component(DROP_OT_THIRDPARTY_DIR ${DROP_OT_THIRDPARTY_DIR} ABSOLUTE) + endif() +endif() + diff --git a/tools/disassociability/visa-pets-FL/ot_library/drop-ot/CMakeLists.txt b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/CMakeLists.txt new file mode 100644 index 0000000..e971d34 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/CMakeLists.txt @@ -0,0 +1,31 @@ + +project(dropOt) + + +############################################# +# Build dropOt # +############################################# + +file(GLOB_RECURSE SRCS *.cpp) + + +add_library(dropOt ${SRCS}) + +target_include_directories(dropOt PUBLIC + $ + $) +target_include_directories(dropOt PUBLIC + $ + $) + + +target_link_libraries(dropOt oc::cryptoTools) + + + +if(MSVC) + target_compile_options(dropOt PUBLIC $<$:/std:c++${DROP_OT_STD_VER}>) + +else() + target_compile_options(dropOt PUBLIC $<$:-std=c++${DROP_OT_STD_VER}> -pthread) +endif() diff --git a/tools/disassociability/visa-pets-FL/ot_library/drop-ot/Defines.h b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/Defines.h new file mode 100644 index 0000000..d3dd15c --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/Defines.h @@ -0,0 +1,116 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#pragma once +#include "drop-ot/config.h" +#include "cryptoTools/Common/Defines.h" +#include "cryptoTools/Common/Version.h" +#include "cryptoTools/Crypto/PRNG.h" +#include "cryptoTools/Network/Channel.h" +#include "cryptoTools/Common/BitVector.h" +#include +#include + + +#if CRYPTO_TOOLS_VERSION < 10502 +static_assert(0, "please update cryptoTools"); +#endif + +#ifdef DROP_OT_ENABLE_RELIC +#include "cryptoTools/Crypto/RCurve.h" + +#ifndef ENABLE_RELIC +static_assert(0, "please enable relic in cryptoTools"); +#endif + +#if !defined(MULTI) || ((MULTI != PTHREAD) && (MULTI != OPENMP)) +static_assert(0,"Relic must be built with -DMULTI=PTHREAD or -DMULTI=OPENMP"); +#endif +#endif +#ifdef DROP_OT_ENABLE_SODIUM +#include "cryptoTools/Crypto/SodiumCurve.h" + +#endif + +namespace dropOt +{ + using u64 = oc::u64; + using i64 = oc::i64; + using u32 = oc::u32; + using i32 = oc::i32; + using u16 = oc::u16; + using i16 = oc::i16; + using u8 = oc::u8; + using i8 = oc::i8; + + template + using span = oc::span; + + using block = oc::block; + inline block toBlock(u8* data) { return oc::toBlock(data); } + inline block toBlock(u64 low_u64) { return oc::toBlock(low_u64); } + inline block toBlock(u64 high_u64, u64 low_u64) { return oc::toBlock(high_u64, low_u64); } + + +#ifdef DROP_OT_ENABLE_RELIC + using Number = oc::REccNumber; + using Point = oc::REccPoint; + using Curve = oc::REllipticCurve; +#else + + using Number = oc::Sodium::Prime25519; + using Point = oc::Sodium::Rist25519; + //using Curve = oc::REllipticCurve; + struct Curve { Curve() {} }; +#endif + +#ifdef ENABLE_BOOST + using Channel = oc::Channel; +#endif + + using BitVector = oc::BitVector; + using PRNG = oc::PRNG; + + std::string hex(span d); + + [[ noreturn ]] inline void panic(const std::string& msg) + { + std::cout << "Panic, " << msg << std::endl; + std::terminate(); + } + + inline u64 roundUpTo(u64 val, u64 step) { return ((val + step - 1) / step) * step; } + + enum class Role { + R0, R1 + }; + + + + enum class Op + { + Default, Add, Mult + }; + + inline void Agg(Point& agg, Point& r, Op op) + { + if (op == Op::Mult) + panic("logic error"); + + agg += r; + } + + inline void Agg(Number& agg, Number& r, Op op) + { + if (op == Op::Add) + agg += r; + else + agg *= r; + } +} + + diff --git a/tools/disassociability/visa-pets-FL/ot_library/drop-ot/IknpOtExt.cpp b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/IknpOtExt.cpp new file mode 100644 index 0000000..211628f --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/IknpOtExt.cpp @@ -0,0 +1,443 @@ +#include "IknpOtExt.h" + +#include +#include + + +#include "cryptoTools/Network/IOService.h" +#include "cryptoTools/Network/Session.h" +#include "cryptoTools/Common/TestCollection.h" +#include "Tools.h" +#include +#include + +#define OTE_RANDOM_ORACLE 1 +#define OTE_DAVIE_MEYER_AES 2 +#define OTE_KOS_HASH OTE_DAVIE_MEYER_AES + +namespace dropOt +{ + + + void IknpOtExtSender::setUniformBaseOts(span baseRecvOts, const BitVector & choices) + { + mPrngIdx = 0; + mBaseChoiceBits = choices; + mGens.setKeys(baseRecvOts); + } + + void IknpOtExtSender::sendRoundOne_r( + span> messages, + PRNG& prng, + span& buffer) + { + + if (hasBaseOts() == false) + panic("IknpOtExtSender has no base OTs"); + + auto numOtExt = u64{}; + auto numBlocks = u64{}; + auto step = u64{}; + auto blkIdx = u64{}; + auto t = oc::AlignedUnVector{ 128 }; + auto u = oc::AlignedUnVector(128 * commStepSize); + auto choiceMask = oc::AlignedArray{}; + auto delta = block{}; + auto recvView = span{}; + auto mIter = span>::iterator{}; + auto uIter = (block*)nullptr; + auto tIter = (block*)nullptr; + auto cIter = (block*)nullptr; + auto uEnd = (block*)nullptr; + + // round up + numOtExt = roundUpTo(messages.size(), 128); + numBlocks = (numOtExt / 128); + //u64 numBlocks = numBlocks * superBlkSize; + + + delta = *(block*)mBaseChoiceBits.data(); + + for (u64 i = 0; i < 128; ++i) + { + if (mBaseChoiceBits[i]) choiceMask[i] = oc::AllOneBlock; + else choiceMask[i] = oc::ZeroBlock; + } + + mIter = messages.begin(); + uEnd = u.data() + u.size(); + uIter = uEnd; + + for (blkIdx = 0; blkIdx < numBlocks; ++blkIdx) + { + tIter = (block*)t.data(); + cIter = choiceMask.data(); + + if (uIter == uEnd) + { + step = std::min(numBlocks - blkIdx, (u64)commStepSize); + step *= 128 * sizeof(block); + recvView = span((u8*)u.data(), step); + uIter = u.data(); + + std::copy(buffer.begin(), buffer.begin() + recvView.size(), recvView.begin()); + buffer = buffer.subspan(recvView.size()); + } + + mGens.ecbEncCounterMode(mPrngIdx, tIter); + ++mPrngIdx; + + // transpose 128 columns at at time. Each column will be 128 * superBlkSize = 1024 bits long. + for (u64 colIdx = 0; colIdx < 128 / 8; ++colIdx) + { + uIter[0] = uIter[0] & cIter[0]; + uIter[1] = uIter[1] & cIter[1]; + uIter[2] = uIter[2] & cIter[2]; + uIter[3] = uIter[3] & cIter[3]; + uIter[4] = uIter[4] & cIter[4]; + uIter[5] = uIter[5] & cIter[5]; + uIter[6] = uIter[6] & cIter[6]; + uIter[7] = uIter[7] & cIter[7]; + + tIter[0] = tIter[0] ^ uIter[0]; + tIter[1] = tIter[1] ^ uIter[1]; + tIter[2] = tIter[2] ^ uIter[2]; + tIter[3] = tIter[3] ^ uIter[3]; + tIter[4] = tIter[4] ^ uIter[4]; + tIter[5] = tIter[5] ^ uIter[5]; + tIter[6] = tIter[6] ^ uIter[6]; + tIter[7] = tIter[7] ^ uIter[7]; + + cIter += 8; + uIter += 8; + tIter += 8; + } + + // transpose our 128 columns of 1024 bits. We will have 1024 rows, + // each 128 bits wide. + transpose128(t.data()); + + + auto mEnd = mIter + std::min(128, messages.end() - mIter); + + tIter = t.data(); + if (mEnd - mIter == 128) + { + for (u64 i = 0; i < 128; i += 8) + { + mIter[i + 0][0] = tIter[i + 0]; + mIter[i + 1][0] = tIter[i + 1]; + mIter[i + 2][0] = tIter[i + 2]; + mIter[i + 3][0] = tIter[i + 3]; + mIter[i + 4][0] = tIter[i + 4]; + mIter[i + 5][0] = tIter[i + 5]; + mIter[i + 6][0] = tIter[i + 6]; + mIter[i + 7][0] = tIter[i + 7]; + mIter[i + 0][1] = tIter[i + 0] ^ delta; + mIter[i + 1][1] = tIter[i + 1] ^ delta; + mIter[i + 2][1] = tIter[i + 2] ^ delta; + mIter[i + 3][1] = tIter[i + 3] ^ delta; + mIter[i + 4][1] = tIter[i + 4] ^ delta; + mIter[i + 5][1] = tIter[i + 5] ^ delta; + mIter[i + 6][1] = tIter[i + 6] ^ delta; + mIter[i + 7][1] = tIter[i + 7] ^ delta; + + } + + mIter += 128; + + } + else + { + while (mIter != mEnd) + { + (*mIter)[0] = *tIter; + (*mIter)[1] = *tIter ^ delta; + + tIter += 1; + mIter += 1; + } + } + } + + + { + +#ifdef IKNP_SHA_HASH + RandomOracle sha; + u8 hashBuff[20]; + u64 doneIdx = 0; + + + u64 bb = (messages.size() + 127) / 128; + for (u64 blockIdx = 0; blockIdx < bb; ++blockIdx) + { + u64 stop = std::min(messages.size(), doneIdx + 128); + + for (u64 i = 0; doneIdx < stop; ++doneIdx, ++i) + { + // hash the message without delta + sha.Reset(); + sha.Update((u8*)&messages[doneIdx][0], sizeof(block)); + sha.Final(hashBuff); + messages[doneIdx][0] = *(block*)hashBuff; + + // hash the message with delta + sha.Reset(); + sha.Update((u8*)&messages[doneIdx][1], sizeof(block)); + sha.Final(hashBuff); + messages[doneIdx][1] = *(block*)hashBuff; + } + } +#else + + oc::mAesFixedKey.hashBlocks((block*)messages.data(), messages.size() * 2, (block*)messages.data()); + } +#endif + } + + IknpOtExtReceiver::IknpOtExtReceiver(span> baseOTs) + { + setUniformBaseOts(baseOTs); + } + + void IknpOtExtReceiver::setUniformBaseOts(span> baseOTs) + { + mPrngIdx = 0; + for (u64 j = 0; j < 2; ++j) + { + block buff[gOtExtBaseOtCount]; + for (u64 i = 0; i < gOtExtBaseOtCount; i++) + buff[i] = baseOTs[i][j]; + + mGens[j].setKeys(buff); + } + + mHasBase = true; + } + + void IknpOtExtReceiver::receiveRoundOne_s( + const BitVector& choices, + span messages, + PRNG& prng, + std::vector& buffer) + { + if (hasBaseOts() == false) + panic("base OTs for receiver not set"); + + if (choices.size() != messages.size()) + throw RTE_LOC; + + auto numOtExt = u64{}; + auto numBlocks = u64{}; + auto blkIdx = u64{}; + auto step = u64{}; + auto choiceBlocks = span{}; + auto t0 = oc::AlignedUnVector{ 128 }; + auto mIter = span::iterator{}; + auto uIter = (block*)nullptr; + auto tIter = (block*)nullptr; + auto cIter = (block*)nullptr; + auto uEnd = (block*)nullptr; + auto uBuff = oc::AlignedUnVector{}; + + // we are going to process OTs in blocks of 128 * superBlkSize messages. + numOtExt = roundUpTo(choices.size(), 128); + numBlocks = (numOtExt / 128); + + choiceBlocks = { choices.blocks(), choices.sizeBlocks() }; + + // the index of the OT that has been completed. + //u64 doneIdx = 0; + + mIter = messages.begin(); + + step = std::min(numBlocks, (u64)commStepSize); + uBuff.resize(step * 128); + + // get an array of blocks that we will fill. + uIter = (block*)uBuff.data(); + uEnd = uIter + uBuff.size(); + + // NOTE: We do not transpose a bit-matrix of size numCol * numCol. + // Instead we break it down into smaller chunks. We do 128 columns + // times 8 * 128 rows at a time, where 8 = superBlkSize. This is done for + // performance reasons. The reason for 8 is that most CPUs have 8 AES vector + // lanes, and so its more efficient to encrypt (aka prng) 8 blocks at a time. + // So that's what we do. + for (blkIdx = 0; blkIdx < numBlocks; ++blkIdx) + { + + // this will store the next 128 rows of the matrix u + + tIter = (block*)t0.data(); + cIter = choiceBlocks.data() + blkIdx; + + mGens[0].ecbEncCounterMode(mPrngIdx, tIter); + mGens[1].ecbEncCounterMode(mPrngIdx, uIter); + ++mPrngIdx; + + for (u64 colIdx = 0; colIdx < 128 / 8; ++colIdx) + { + uIter[0] = uIter[0] ^ cIter[0]; + uIter[1] = uIter[1] ^ cIter[0]; + uIter[2] = uIter[2] ^ cIter[0]; + uIter[3] = uIter[3] ^ cIter[0]; + uIter[4] = uIter[4] ^ cIter[0]; + uIter[5] = uIter[5] ^ cIter[0]; + uIter[6] = uIter[6] ^ cIter[0]; + uIter[7] = uIter[7] ^ cIter[0]; + + uIter[0] = uIter[0] ^ tIter[0]; + uIter[1] = uIter[1] ^ tIter[1]; + uIter[2] = uIter[2] ^ tIter[2]; + uIter[3] = uIter[3] ^ tIter[3]; + uIter[4] = uIter[4] ^ tIter[4]; + uIter[5] = uIter[5] ^ tIter[5]; + uIter[6] = uIter[6] ^ tIter[6]; + uIter[7] = uIter[7] ^ tIter[7]; + + uIter += 8; + tIter += 8; + } + + if (uIter == uEnd) + { + // send over u buffer + auto begin = buffer.size(); + buffer.resize(begin + uBuff.size() * sizeof(block)); + std::copy((u8*)uBuff.data(), (u8*)(uBuff.data() + uBuff.size()), buffer.begin() + begin); + + u64 step = std::min(numBlocks - blkIdx - 1, (u64)commStepSize); + + if (step) + { + uBuff.resize(step * 128); + uIter = (block*)uBuff.data(); + uEnd = uIter + uBuff.size(); + } + } + + // transpose our 128 columns of 1024 bits. We will have 1024 rows, + // each 128 bits wide. + transpose128(t0.data()); + + + auto mEnd = mIter + std::min(128, messages.end() - mIter); + + + tIter = t0.data(); + + memcpy(mIter, tIter, (mEnd - mIter) * sizeof(block)); + mIter = mEnd; + +#ifdef IKNP_DEBUG + ... fix this + u64 doneIdx = mStart - messages.data(); + block* msgIter = messages.data() + doneIdx; + chl.send(msgIter, sizeof(block) * 128 * superBlkSize); + cIter = choiceBlocks.data() + superBlkSize * blkIdx; + chl.send(cIter, sizeof(block) * superBlkSize); +#endif + //doneIdx = stopIdx; + } + + { + +#ifdef IKNP_SHA_HASH + RandomOracle sha; + u8 hashBuff[20]; + u64 doneIdx = (0); + + u64 bb = (messages.size() + 127) / 128; + for (u64 blockIdx = 0; blockIdx < bb; ++blockIdx) + { + u64 stop = std::min(messages.size(), doneIdx + 128); + + for (u64 i = 0; doneIdx < stop; ++doneIdx, ++i) + { + // hash it + sha.Reset(); + sha.Update((u8*)&messages[doneIdx], sizeof(block)); + sha.Final(hashBuff); + messages[doneIdx] = *(block*)hashBuff; + } + } +#else + oc::mAesFixedKey.hashBlocks(messages.data(), messages.size(), messages.data()); +#endif + + } + } + + + + + namespace tests + { + + + void OtExt_Iknp_Buff_test() + { + PRNG prng0(toBlock(4253465, 3434565)); + PRNG prng1(toBlock(233465, 334565)); + + u64 numOTs = 1<<17; + + std::vector recvMsg(numOTs), baseRecv(128); + std::vector> sendMsg(numOTs), baseSend(128); + BitVector choices(numOTs), baseChoice(128); + choices.randomize(prng0); + baseChoice.randomize(prng0); + + for (u64 i = 0; i < 128; ++i) + { + baseSend[i][0] = prng0.get(); + baseSend[i][1] = prng0.get(); + baseRecv[i] = baseSend[i][baseChoice[i]]; + } + + IknpOtExtSender sender; + IknpOtExtReceiver recv; + recv.setUniformBaseOts(baseSend); + sender.setUniformBaseOts(baseRecv, baseChoice); + + recv.mPrngIdx = 42; + sender.mPrngIdx = 42; + + std::stringstream rss, sss; + recv.serialize(rss); + recv = {}; + recv.deserialize(rss); + if (recv.mPrngIdx != 42) + throw RTE_LOC; + + + sender.serialize(rss); + sender = {}; + sender.deserialize(rss); + if (sender.mPrngIdx != 42) + throw RTE_LOC; + + std::vector buffer; + recv.receiveRoundOne_s(choices, recvMsg, prng0, buffer); + + span bSpan = buffer; + sender.sendRoundOne_r(sendMsg, prng1, bSpan); + + for (u64 i = 0; i < choices.size(); ++i) + { + u8 choice = choices[i]; + const block& revcBlock = recvMsg[i]; + const block& senderBlock = sendMsg[i][choice]; + + if (neq(revcBlock, senderBlock)) + throw oc::UnitTestFail(); + + if (eq(revcBlock, sendMsg[i][1 ^ choice])) + throw oc::UnitTestFail(); + } + } + } +} + diff --git a/tools/disassociability/visa-pets-FL/ot_library/drop-ot/IknpOtExt.h b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/IknpOtExt.h new file mode 100644 index 0000000..d9ed0cf --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/IknpOtExt.h @@ -0,0 +1,237 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#pragma once +#include "drop-ot/Defines.h" +#include +#include "cryptoTools/Crypto/RandomOracle.h" + +namespace dropOt { + + + const u64 commStepSize(512); + const u64 gOtExtBaseOtCount(128); + + class IknpOtExtSender + { + public: + + u64 mPrngIdx = 0; + oc::MultiKeyAES mGens; + BitVector mBaseChoiceBits; + + IknpOtExtSender() = default; + IknpOtExtSender(const IknpOtExtSender&) = delete; + IknpOtExtSender(IknpOtExtSender&&) = default; + + IknpOtExtSender( + span baseRecvOts, + const BitVector& choices) + { + setUniformBaseOts(baseRecvOts, choices); + } + + void operator=(IknpOtExtSender&& v) + { + mGens = std::move(v.mGens); + mBaseChoiceBits = std::move(v.mBaseChoiceBits); + } + + // return true if this instance has valid base OTs. + bool hasBaseOts() const + { + return mBaseChoiceBits.size() > 0; + } + + // Returns a independent instance of this extender which can + // be executed concurrently. The base OTs are derived from the + // original base OTs. + //IknpOtExtSender split(); + + // Sets the base OTs which must be peformed before calling split or send. + // See frontend/main.cpp for an example. + void setUniformBaseOts( + span baseRecvOts, + const BitVector& choices); + + + // The first round of the OT-sender protocol. This will receive a + // message and send a message. + // This function can return early with a code::suspend error in which + // case the caller should perform io and call the function again. + // @messages, output: the random messages that will be returned. + // @prng, input: the randomness source. + // @chl, input: the io buffer/socket. + void sendRoundOne_r( + span> messages, + PRNG& prng, + span& ioBuffer); + + static constexpr auto header = "iknp-sender"; + void serialize(std::ostream& out) + { + out.write(header, std::strlen(header)); + bool hasBase = hasBaseOts(); + + out.write((char*)&hasBase, sizeof(hasBase)); + out.write((char*)&mPrngIdx, sizeof(mPrngIdx)); + + if (hasBase) + { + out.write((char*)mBaseChoiceBits.data(), mBaseChoiceBits.sizeBytes()); + + for (u64 i = 0; i < mGens.mAESs.size(); ++i) + { + auto k = mGens.mAESs[i].getKey(); + out.write((char*)&k, sizeof(k)); + } + } + } + + void deserialize(std::istream& in) + { + std::vector buff(std::strlen(header)); + in.read((char*)buff.data(), buff.size()); + + if (std::memcmp(buff.data(), header, buff.size())) + { + std::cout << header << " failed to deserialize. Bad header. " LOCATION << std::endl; + throw RTE_LOC; + } + + bool hasBase; + + in.read((char*)&hasBase, sizeof(hasBase)); + in.read((char*)&mPrngIdx, sizeof(mPrngIdx)); + + if (hasBase) + { + mBaseChoiceBits.resize(gOtExtBaseOtCount); + in.read((char*)mBaseChoiceBits.data(), mBaseChoiceBits.sizeBytes()); + + for (u64 i = 0; i < mGens.mAESs.size(); ++i) + { + block k; + in.read((char*)&k, sizeof(k)); + mGens.mAESs[i].setKey(k); + } + } + } + + }; + + + class IknpOtExtReceiver + { + public: + + + bool mHasBase = false; + oc::AlignedArray, 2> mGens; + u64 mPrngIdx = 0; + + IknpOtExtReceiver() = default; + IknpOtExtReceiver(const IknpOtExtReceiver&) = delete; + IknpOtExtReceiver(IknpOtExtReceiver&&) = default; + IknpOtExtReceiver(span> baseSendOts); + + void operator=(IknpOtExtReceiver&& v) + { + mHasBase = std::move(v.mHasBase); + mGens = std::move(v.mGens); + v.mHasBase = false; + } + + // returns whether the base OTs have been set. They must be set before + // split or receive is called. + bool hasBaseOts() const + { + return mHasBase; + } + + // sets the base OTs. + void setUniformBaseOts(span> baseSendOts); + + // returns an independent instance of this extender which can securely be + // used concurrently to this current one. The base OTs for the new instance + // are derived from the orginial base OTs. + //IknpOtExtReceiver splitBase(); + + // The first round of the OT-receiver protocol. This will send a message. + // This function can return early with a code::suspend error in which + // case the caller should perform io and call the function again. + // @choices, input: the choice bits that the receiver choose. + // @messages, output: the random messages that will be returned. + // @prng, input: the randomness source. + // @chl, input: the io buffer/socket. + void receiveRoundOne_s( + const BitVector& choices, + span messages, + PRNG& prng, + std::vector& ioBuffer); + + + static constexpr auto header = "iknp-recver"; + + void serialize(std::ostream& out) + { + out.write(header, std::strlen(header)); + + out.write((char*)&mHasBase, sizeof(mHasBase)); + out.write((char*)&mPrngIdx, sizeof(mPrngIdx)); + + if (mHasBase) + { + for (u64 i = 0; i < mGens[0].mAESs.size(); ++i) + { + auto k0 = mGens[0].mAESs[i].getKey(); + auto k1 = mGens[1].mAESs[i].getKey(); + + out.write((char*)&k0, sizeof(k0)); + out.write((char*)&k1, sizeof(k1)); + } + } + } + + void deserialize(std::istream& in) + { + std::vector buff(std::strlen(header)); + in.read((char*)buff.data(), buff.size()); + + if (std::memcmp(buff.data(), header, buff.size())) + { + std::cout << header << " failed to deserialize. Bad header. " LOCATION << std::endl; + throw RTE_LOC; + } + + in.read((char*)&mHasBase, sizeof(mHasBase)); + in.read((char*)&mPrngIdx, sizeof(mPrngIdx)); + + if (mHasBase) + { + for (u64 i = 0; i < mGens[0].mAESs.size(); ++i) + { + block k0; + block k1; + in.read((char*)&k0, sizeof(k0)); + in.read((char*)&k1, sizeof(k1)); + mGens[0].mAESs[i].setKey(k0); + mGens[1].mAESs[i].setKey(k1); + } + } + } + + }; + + + namespace tests + { + + void OtExt_Iknp_Buff_test(); + } +} + diff --git a/tools/disassociability/visa-pets-FL/ot_library/drop-ot/MasnyRindal.cpp b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/MasnyRindal.cpp new file mode 100644 index 0000000..069e66d --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/MasnyRindal.cpp @@ -0,0 +1,224 @@ +#include "MasnyRindal.h" + + +#include +#include +#include +#include + +#include "cryptoTools/Network/IOService.h" +#include "cryptoTools/Network/Session.h" +#include "cryptoTools/Common/TestCollection.h" + +#include +#include + +namespace dropOt +{ + + void MasnyRindal::receiveRoundOne( + BitVector choices_, + PRNG& prng, + std::vector& ioBuffer) + { + if (mState != State::RoundOne) + panic("bad state, " LOCATION); + + + mChoices = std::move(choices_); + + auto n = mChoices.size(); + Curve curve; + std::array r; + auto pointSize = Point::size; + + Point hPoint; + std::vector hashBuff(roundUpTo(pointSize, 16)); + mSk = {}; + mSk.reserve(n); + + auto begin = ioBuffer.size(); + ioBuffer.resize(begin + Point::size * 2 * n); + span buff = { ioBuffer.begin() + begin, ioBuffer.end() }; + // loop over the input and generate the private + // keys. + for (u64 i = 0; i < n; ++i) + { + // We process things in batches of max size step. We + // then send those off before the next step is started. + auto& rrNot = r[mChoices[i] ^ 1]; + auto& rr = r[mChoices[i]]; + + rrNot.randomize(prng); + rrNot.toBytes(hashBuff.data()); + hPoint.fromHash(hashBuff.data(), pointSize); + + mSk.emplace_back(prng); + rr = Point::mulGenerator(mSk[i]); + rr -= hPoint; + + r[0].toBytes(buff.subspan(0, Point::size).data()); buff = buff.subspan(Point::size); + r[1].toBytes(buff.subspan(0, Point::size).data()); buff = buff.subspan(Point::size); + } + + // progress to the next round. + mState = State::RoundTwo; + } + + void MasnyRindal::receiveRoundTwo( + span messages, + PRNG& prng, + span& recvBuff) + { + if (mState != State::RoundTwo) + panic("bad state, " LOCATION); + + Curve curve; + Point Mb, k; + auto n = mChoices.size(); + auto pointSize = Point::size; + + if (recvBuff.size() != Point::size) + throw RTE_LOC; + + // Compute the random OT messages. + Mb.fromBytes(recvBuff.data()); + std::vector hashBuff(Point::size); + oc::RandomOracle ro(sizeof(block)); + for (u64 i = 0; i < n; ++i) + { + k = Mb; + k *= mSk[i]; + + k.toBytes(hashBuff.data()); + ro.Reset(); + ro.Update(i * 2 + mChoices[i]); + ro.Update(hashBuff.data(), Point::size); + ro.Final(messages[i]); + } + + resetState(); + } + + void MasnyRindal::sendRoundOne( + u64 n, + PRNG& prng, + std::vector& buff) + { + if (mState != State::RoundOne) + panic("bad state, " LOCATION); + + Curve curve; + auto pointSize = Point::size; + mSk = {}; + mSk.emplace_back(); + mSk[0].randomize(prng); + + Point Mb = Point::mulGenerator(mSk[0]); + + auto begin = buff.size(); + buff.resize(begin + Point::size); + Mb.toBytes(buff.data() + begin); + + mState = State::RoundTwo; + } + + + + void MasnyRindal::sendRoundTwo( + span> messages, + PRNG& prng, + span& buff) + { + if (mState != State::RoundTwo) + panic("bad state, " LOCATION); + + u64 n = static_cast(messages.size()); + Curve curve; + auto pointSize = Point::size; + oc::RandomOracle ro(sizeof(block)); + + std::vector hashBuff(roundUpTo(pointSize, 16)); + Point pHash, r; + + if (buff.size() != n * Point::size * 2) + throw RTE_LOC; + + for (u64 i = 0; i < n; ++i) + { + std::array, 2> buffIters{ + buff.subspan(0,Point::size), + buff.subspan(Point::size,Point::size) + }; + buff = buff.subspan(2 * Point::size); + + for (u64 j = 0; j < 2; ++j) + { + + r.fromBytes(buffIters[j].data()); + pHash.fromHash(buffIters[j ^ 1].data(), int(pointSize)); + r += pHash; + r *= mSk[0]; + + r.toBytes(hashBuff.data()); + ro.Reset(); + ro.Update(i * 2 + j); + ro.Update(hashBuff.data(), Point::size); + ro.Final(messages[i][j]); + } + } + + resetState(); + } + + namespace tests + { + + void Bot_MasnyRindal_Buff_test() + { + + PRNG prng0(oc::ZeroBlock); + PRNG prng1(oc::OneBlock); + + u64 numOTs = 50; + std::vector recvMsg(numOTs); + std::vector> sendMsg(numOTs); + BitVector choices(numOTs); + choices.randomize(prng0); + + MasnyRindal baseOTs0, baseOTs1; + + std::vector buff0, buff1; + + baseOTs0.receiveRoundOne(choices, prng0, buff0); + baseOTs1.sendRoundOne(numOTs, prng1, buff1); + + std::stringstream ss0; + baseOTs0.serialize(ss0); + baseOTs0.resetState(); + baseOTs0.deserialize(ss0); + + //std::stringstream ss1; + //baseOTs1.serialize(ss1); + //baseOTs1.resetState(); + //baseOTs1.deserialize(ss1); + + + span span1 = buff1; + span span0 = buff0; + baseOTs0.receiveRoundTwo(recvMsg, prng0, span1); + baseOTs1.sendRoundTwo(sendMsg, prng1, span0); + + for (u64 i = 0; i < numOTs; ++i) + { + if (neq(recvMsg[i], sendMsg[i][choices[i]])) + { + std::cout << "failed " << i << " exp = m[" << int(choices[i]) << "], act = " << recvMsg[i] << " true = " << sendMsg[i][0] << ", " << sendMsg[i][1] << std::endl; + throw oc::UnitTestFail(); + } + } + } + + } + +} diff --git a/tools/disassociability/visa-pets-FL/ot_library/drop-ot/MasnyRindal.h b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/MasnyRindal.h new file mode 100644 index 0000000..48b1bc7 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/MasnyRindal.h @@ -0,0 +1,183 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#pragma once +#include "Defines.h" + +namespace dropOt +{ + // This is the Base OT protocl of Masny, Rindal 2019. + // See https://eprint.iacr.org/2019/706. + class MasnyRindal + { + public: + + enum State + { + RoundOne, + RoundTwo + }; + + // The secret keys of each party. + std::vector mSk; + + // receiver choice bits. + BitVector mChoices; + + // The current state of the protocol. + State mState = State::RoundOne; + void resetState() { + mState = State::RoundOne; + mSk.resize(0); + mChoices.resize(0); + } + + + + static constexpr auto header = "MasnyRindal"; + + void serialize(std::ostream& out) + { + Curve curve; + + out.write(header, std::strlen(header)); + out.write((char*)&mState, sizeof(mState)); + + u64 size = mSk.size(); + out.write((char*)&size, sizeof(size)); + + if (size) + { + std::vector buff; + size = mSk[0].sizeBytes(); + + out.write((char*)&size, sizeof(size)); + buff.resize(size); + for (auto& sk : mSk) + { + sk.toBytes(buff.data()); + out.write((char*)buff.data(), buff.size()); + } + } + + size = mChoices.size(); + out.write((char*)&size, sizeof(size)); + if (size) + { + out.write((char*)mChoices.data(), mChoices.sizeBytes()); + + } + } + + + void deserialize(std::istream& in) + { + std::vector buff(std::strlen(header)); + in.read((char*)buff.data(), buff.size()); + + if (std::memcmp(buff.data(), header, buff.size())) + { + std::cout << header << " failed to deserialize. Bad header. " LOCATION << std::endl; + throw RTE_LOC; + } + + in.read((char*)&mState, sizeof(mState)); + + if (mState != State::RoundOne && mState != State::RoundTwo) + { + std::cout << header << " failed to deserialize. Bad state. " LOCATION << std::endl; + throw RTE_LOC; + } + + Curve curve; + u64 size; + in.read((char*)&size, sizeof(size)); + mSk.resize(size); + + if (size) + { + u64 size2; + in.read((char*)&size2, sizeof(size2)); + if (size2 != mSk[0].sizeBytes()) + { + std::cout << header << " failed to deserialize. Bad sk key size. " LOCATION << std::endl; + throw RTE_LOC; + } + } + for (auto i = 0; i < size; ++i) + { + buff.resize(mSk[i].sizeBytes()); + in.read((char*)buff.data(), buff.size()); + mSk[i].fromBytes(buff.data()); + } + + + in.read((char*)&size, sizeof(size)); + if (size) + { + mChoices.resize(size); + in.read((char*)mChoices.data(), mChoices.sizeBytes()); + } + } + + + // Perform round 1 of the OT-receiver + // protocol. Will return a dropOt::code + // which encodes {success, error, ...}. + // @choices, input: the choice bits of the messages. + // @prng, input: the source of randomness. + // @chl, input: the location the io should be send/recv. + void receiveRoundOne( + BitVector choices, + PRNG& prng, + std::vector& ioBuffer); + + // Perform round 2 of the OT-receiver + // protocol. Will return a dropOt::code + // which encodes {success, suspend, error, ...}. + // If suspend is returned the caller should + // perform io and call the function again. + // @choices, input: the choice bits of the messages. + // @messages, output: the location that the random messages will be written to. + // @prng, input: the source of randomness. + // @chl, input: the location the io should be send/recv. + void receiveRoundTwo( + span messages, + PRNG& prng, + span& ioBuffer); + + // Perform round 1 of the OT-sender + // protocol. Will return a dropOt::code + // which encodes {success, error, ...}. + // @n, input: the number of OTs to perfom. + // @prng, input: the source of randomness. + // @chl, input: the location the io should be send/recv. + void sendRoundOne( + u64 n, + PRNG& prng, + std::vector& ioBuffer); + + + // Perform round 2 of the OT-sender + // protocol. Will return a dropOt::code + // which encodes {success, suspend, error, ...}. + // If suspend is returned the caller should + // perform io and call the function again. + // @messages, output: the location that the random messages will be written to. + // @prng, input: the source of randomness. + // @chl, input: the location the io should be send/recv. + void sendRoundTwo( + span> messages, + PRNG& prng, + span& ioBuffer); + }; + + namespace tests { + void Bot_MasnyRindal_Buff_test(); + } + +} diff --git a/tools/disassociability/visa-pets-FL/ot_library/drop-ot/Tools.cpp b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/Tools.cpp new file mode 100644 index 0000000..74cafe2 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/Tools.cpp @@ -0,0 +1,942 @@ +#include "Tools.h" +#include +#include +#include + +#ifdef OC_ENABLE_SSE2 +#ifndef _MSC_VER +#include +#endif +#include +#endif +#ifdef OC_ENABLE_AVX2 +#include +#endif + +#include +#include +#include "cryptoTools/Common/Aligned.h" +using std::array; + +namespace osuCrypto { + + + + void eklundh_transpose128(block* inOut) + { + const static u64 TRANSPOSE_MASKS128[7][2] = { + { 0x0000000000000000, 0xFFFFFFFFFFFFFFFF }, + { 0x00000000FFFFFFFF, 0x00000000FFFFFFFF }, + { 0x0000FFFF0000FFFF, 0x0000FFFF0000FFFF }, + { 0x00FF00FF00FF00FF, 0x00FF00FF00FF00FF }, + { 0x0F0F0F0F0F0F0F0F, 0x0F0F0F0F0F0F0F0F }, + { 0x3333333333333333, 0x3333333333333333 }, + { 0x5555555555555555, 0x5555555555555555 } + }; + + u32 width = 64; + u32 logn = 7, nswaps = 1; + +#ifdef TRANSPOSE_DEBUG + stringstream input_ss[128]; + stringstream output_ss[128]; +#endif + + // now transpose output a-place + for (u32 i = 0; i < logn; i++) + { + u64 mask1 = TRANSPOSE_MASKS128[i][1], mask2 = TRANSPOSE_MASKS128[i][0]; + u64 inv_mask1 = ~mask1, inv_mask2 = ~mask2; + + // for width >= 64, shift is undefined so treat as h special case + // (and avoid branching a inner loop) + if (width < 64) + { + for (u32 j = 0; j < nswaps; j++) + { + for (u32 k = 0; k < width; k++) + { + u32 i1 = k + 2 * width*j; + u32 i2 = k + width + 2 * width*j; + + // t1 is lower 64 bits, t2 is upper 64 bits + // (remember we're transposing a little-endian format) + u64& d1 = ((u64*)&inOut[i1])[0]; + u64& d2 = ((u64*)&inOut[i1])[1]; + + u64& dd1 = ((u64*)&inOut[i2])[0]; + u64& dd2 = ((u64*)&inOut[i2])[1]; + + u64 t1 = d1; + u64 t2 = d2; + + u64 tt1 = dd1; + u64 tt2 = dd2; + + // swap operations due to little endian-ness + d1 = (t1 & mask1) ^ ((tt1 & mask1) << width); + + d2 = (t2 & mask2) ^ + ((tt2 & mask2) << width) ^ + ((tt1 & mask1) >> (64 - width)); + + dd1 = (tt1 & inv_mask1) ^ + ((t1 & inv_mask1) >> width) ^ + ((t2 & inv_mask2)) << (64 - width); + + dd2 = (tt2 & inv_mask2) ^ + ((t2 & inv_mask2) >> width); + } + } + } + else + { + for (u32 j = 0; j < nswaps; j++) + { + for (u32 k = 0; k < width; k++) + { + u32 i1 = k + 2 * width*j; + u32 i2 = k + width + 2 * width*j; + + // t1 is lower 64 bits, t2 is upper 64 bits + // (remember we're transposing a little-endian format) + u64& d1 = ((u64*)&inOut[i1])[0]; + u64& d2 = ((u64*)&inOut[i1])[1]; + + u64& dd1 = ((u64*)&inOut[i2])[0]; + u64& dd2 = ((u64*)&inOut[i2])[1]; + + //u64 t1 = d1; + u64 t2 = d2; + + //u64 tt1 = dd1; + //u64 tt2 = dd2; + + d1 &= mask1; + d2 = (t2 & mask2) ^ + ((dd1 & mask1) >> (64 - width)); + + dd1 = (dd1 & inv_mask1) ^ + ((t2 & inv_mask2)) << (64 - width); + + dd2 &= inv_mask2; + } + } + } + nswaps *= 2; + width /= 2; + } +#ifdef TRANSPOSE_DEBUG + for (u32 k = 0; k < 128; k++) + { + for (u32 blkIdx = 0; blkIdx < 128; blkIdx++) + { + output_ss[blkIdx] << inOut[offset + blkIdx].get_bit(k); + } + } + for (u32 k = 0; k < 128; k++) + { + if (output_ss[k].str().compare(input_ss[k].str()) != 0) + { + cerr << "String " << k << " failed. offset = " << offset << endl; + exit(1); + } + } + std::cout << "\ttranspose with offset " << offset << " ok\n"; +#endif + } + + + + void eklundh_transpose128x1024(std::array, 128>& inOut) + { + + + for (u64 i = 0; i < 8; ++i) + { + std::array sub; + for (u64 j = 0; j < 128; ++j) + sub[j] = inOut[j][i]; + + eklundh_transpose128(sub.data()); + + for (u64 j = 0; j < 128; ++j) + inOut[j][i] = sub[j]; + } + + } + + + + // load column w,w+1 (byte index) + // __________________ + // | | + // | | + // | | + // | | + // row 16*h, | #.# | + // ..., | ... | + // row 16*(h+1) | #.# | into u16OutView column wise + // | | + // | | + // ------------------ + // + // note: u16OutView is a 16x16 bit matrix = 16 rows of 2 bytes each. + // u16OutView[0] stores the first column of 16 bytes, + // u16OutView[1] stores the second column of 16 bytes. + void sse_loadSubSquare(block* in, array& out, u64 x, u64 y) + { + static_assert(sizeof(array, 2>) == sizeof(array), ""); + static_assert(sizeof(array, 128>) == sizeof(array), ""); + + array, 2>& outByteView = *(array, 2>*)&out; + array* inByteView = (array*)in; + + for (int l = 0; l < 16; l++) + { + outByteView[0][l] = inByteView[16 * x + l][2 * y]; + outByteView[1][l] = inByteView[16 * x + l][2 * y + 1]; + } + } + + + + // given a 16x16 sub square, place its transpose into u16OutView at + // rows 16*h, ..., 16 *(h+1) a byte columns w, w+1. + void sse_transposeSubSquare(block* out, array& in, u64 x, u64 y) + { + static_assert(sizeof(array, 128>) == sizeof(array), ""); + + array* outU16View = (array*)out; + + + for (int j = 0; j < 8; j++) + { + outU16View[16 * x + 7 - j][y] = in[0].movemask_epi8(); + outU16View[16 * x + 15 - j][y] = in[1].movemask_epi8(); + + in[0] = (in[0] << 1); + in[1] = (in[1] << 1); + } + } + + + void transpose(const MatrixView& in, const MatrixView& out) + { + MatrixView inn((u8*)in.data(), in.bounds()[0], in.stride() * sizeof(block)); + MatrixView outt((u8*)out.data(), out.bounds()[0], out.stride() * sizeof(block)); + + transpose(inn, outt); + } + + void transpose(const MatrixView& in, const MatrixView& out) + { + // the amount of work that we use to vectorize (hard code do not change) + static const u64 chunkSize = 8; + + // the number of input columns + int bitWidth = static_cast(in.bounds()[0]); + + // In the main loop, we tranpose things in subBlocks. This is how many we have. + // a subblock is 16 (bits) columns wide and 64 bits tall + int subBlockWidth = bitWidth / 16; + int subBlockHight = static_cast(out.bounds()[0]) / (8 * chunkSize); + + // since we allows arbitrary sized inputs, we have to deal with the left overs + int leftOverHeight = static_cast(out.bounds()[0]) % (chunkSize * 8); + int leftOverWidth = static_cast(in.bounds()[0]) % 16; + + + // make sure that the output can hold the input. + if (static_cast(out.stride()) < (bitWidth + 7) / 8) + throw std::runtime_error(LOCATION); + + // we can handle the case that the output should be truncated, but + // not the case that the input is too small. (simple call this function + // with a smaller out.bounds()[0], since thats "free" to do.) + if (out.bounds()[0] > in.stride() * 8) + throw std::runtime_error(LOCATION); + + union TempObj + { + //array blks; + block blks[chunkSize]; + //array < array, chunkSize> bytes; + u8 bytes[chunkSize][16]; + }; + + TempObj t; + + + // some useful constants that we will use + auto wStep = 16 * in.stride(); + auto eightOutSize1 = 8 * out.stride(); + auto outStart = out.data() + (7) * out.stride(); + auto step = in.stride(); + auto + step01 = step * 1, + step02 = step * 2, + step03 = step * 3, + step04 = step * 4, + step05 = step * 5, + step06 = step * 6, + step07 = step * 7, + step08 = step * 8, + step09 = step * 9, + step10 = step * 10, + step11 = step * 11, + step12 = step * 12, + step13 = step * 13, + step14 = step * 14, + step15 = step * 15; + + + // this is the main loop that gets the best performance (highly vectorized). + for (int h = 0; h < subBlockHight; ++h) + { + // we are concerned with the output rows a range [16 * h, 16 * h + 15] + + for (int w = 0; w < subBlockWidth; ++w) + { + // we are concerned with the w'th section of 16 bits for the 16 output rows above. + + auto start = in.data() + h * chunkSize + w * wStep; + + auto src00 = start; + auto src01 = start + step01; + auto src02 = start + step02; + auto src03 = start + step03; + auto src04 = start + step04; + auto src05 = start + step05; + auto src06 = start + step06; + auto src07 = start + step07; + auto src08 = start + step08; + auto src09 = start + step09; + auto src10 = start + step10; + auto src11 = start + step11; + auto src12 = start + step12; + auto src13 = start + step13; + auto src14 = start + step14; + auto src15 = start + step15; + + // perform the transpose on the byte level. We will then use + // sse instrucitions to get it on the bit level. t.bytes is the + // same as a but in a 2D byte view. + t.bytes[0][0] = src00[0]; t.bytes[1][0] = src00[1]; t.bytes[2][0] = src00[2]; t.bytes[3][0] = src00[3]; t.bytes[4][0] = src00[4]; t.bytes[5][0] = src00[5]; t.bytes[6][0] = src00[6]; t.bytes[7][0] = src00[7]; + t.bytes[0][1] = src01[0]; t.bytes[1][1] = src01[1]; t.bytes[2][1] = src01[2]; t.bytes[3][1] = src01[3]; t.bytes[4][1] = src01[4]; t.bytes[5][1] = src01[5]; t.bytes[6][1] = src01[6]; t.bytes[7][1] = src01[7]; + t.bytes[0][2] = src02[0]; t.bytes[1][2] = src02[1]; t.bytes[2][2] = src02[2]; t.bytes[3][2] = src02[3]; t.bytes[4][2] = src02[4]; t.bytes[5][2] = src02[5]; t.bytes[6][2] = src02[6]; t.bytes[7][2] = src02[7]; + t.bytes[0][3] = src03[0]; t.bytes[1][3] = src03[1]; t.bytes[2][3] = src03[2]; t.bytes[3][3] = src03[3]; t.bytes[4][3] = src03[4]; t.bytes[5][3] = src03[5]; t.bytes[6][3] = src03[6]; t.bytes[7][3] = src03[7]; + t.bytes[0][4] = src04[0]; t.bytes[1][4] = src04[1]; t.bytes[2][4] = src04[2]; t.bytes[3][4] = src04[3]; t.bytes[4][4] = src04[4]; t.bytes[5][4] = src04[5]; t.bytes[6][4] = src04[6]; t.bytes[7][4] = src04[7]; + t.bytes[0][5] = src05[0]; t.bytes[1][5] = src05[1]; t.bytes[2][5] = src05[2]; t.bytes[3][5] = src05[3]; t.bytes[4][5] = src05[4]; t.bytes[5][5] = src05[5]; t.bytes[6][5] = src05[6]; t.bytes[7][5] = src05[7]; + t.bytes[0][6] = src06[0]; t.bytes[1][6] = src06[1]; t.bytes[2][6] = src06[2]; t.bytes[3][6] = src06[3]; t.bytes[4][6] = src06[4]; t.bytes[5][6] = src06[5]; t.bytes[6][6] = src06[6]; t.bytes[7][6] = src06[7]; + t.bytes[0][7] = src07[0]; t.bytes[1][7] = src07[1]; t.bytes[2][7] = src07[2]; t.bytes[3][7] = src07[3]; t.bytes[4][7] = src07[4]; t.bytes[5][7] = src07[5]; t.bytes[6][7] = src07[6]; t.bytes[7][7] = src07[7]; + t.bytes[0][8] = src08[0]; t.bytes[1][8] = src08[1]; t.bytes[2][8] = src08[2]; t.bytes[3][8] = src08[3]; t.bytes[4][8] = src08[4]; t.bytes[5][8] = src08[5]; t.bytes[6][8] = src08[6]; t.bytes[7][8] = src08[7]; + t.bytes[0][9] = src09[0]; t.bytes[1][9] = src09[1]; t.bytes[2][9] = src09[2]; t.bytes[3][9] = src09[3]; t.bytes[4][9] = src09[4]; t.bytes[5][9] = src09[5]; t.bytes[6][9] = src09[6]; t.bytes[7][9] = src09[7]; + t.bytes[0][10] = src10[0]; t.bytes[1][10] = src10[1]; t.bytes[2][10] = src10[2]; t.bytes[3][10] = src10[3]; t.bytes[4][10] = src10[4]; t.bytes[5][10] = src10[5]; t.bytes[6][10] = src10[6]; t.bytes[7][10] = src10[7]; + t.bytes[0][11] = src11[0]; t.bytes[1][11] = src11[1]; t.bytes[2][11] = src11[2]; t.bytes[3][11] = src11[3]; t.bytes[4][11] = src11[4]; t.bytes[5][11] = src11[5]; t.bytes[6][11] = src11[6]; t.bytes[7][11] = src11[7]; + t.bytes[0][12] = src12[0]; t.bytes[1][12] = src12[1]; t.bytes[2][12] = src12[2]; t.bytes[3][12] = src12[3]; t.bytes[4][12] = src12[4]; t.bytes[5][12] = src12[5]; t.bytes[6][12] = src12[6]; t.bytes[7][12] = src12[7]; + t.bytes[0][13] = src13[0]; t.bytes[1][13] = src13[1]; t.bytes[2][13] = src13[2]; t.bytes[3][13] = src13[3]; t.bytes[4][13] = src13[4]; t.bytes[5][13] = src13[5]; t.bytes[6][13] = src13[6]; t.bytes[7][13] = src13[7]; + t.bytes[0][14] = src14[0]; t.bytes[1][14] = src14[1]; t.bytes[2][14] = src14[2]; t.bytes[3][14] = src14[3]; t.bytes[4][14] = src14[4]; t.bytes[5][14] = src14[5]; t.bytes[6][14] = src14[6]; t.bytes[7][14] = src14[7]; + t.bytes[0][15] = src15[0]; t.bytes[1][15] = src15[1]; t.bytes[2][15] = src15[2]; t.bytes[3][15] = src15[3]; t.bytes[4][15] = src15[4]; t.bytes[5][15] = src15[5]; t.bytes[6][15] = src15[6]; t.bytes[7][15] = src15[7]; + + // get pointers to the output. + auto out0 = outStart + (chunkSize * h + 0) * eightOutSize1 + w * 2; + auto out1 = outStart + (chunkSize * h + 1) * eightOutSize1 + w * 2; + auto out2 = outStart + (chunkSize * h + 2) * eightOutSize1 + w * 2; + auto out3 = outStart + (chunkSize * h + 3) * eightOutSize1 + w * 2; + auto out4 = outStart + (chunkSize * h + 4) * eightOutSize1 + w * 2; + auto out5 = outStart + (chunkSize * h + 5) * eightOutSize1 + w * 2; + auto out6 = outStart + (chunkSize * h + 6) * eightOutSize1 + w * 2; + auto out7 = outStart + (chunkSize * h + 7) * eightOutSize1 + w * 2; + + for (int j = 0; j < 8; j++) + { + // use the special movemask_epi8 to perform the final step of that bit-wise tranpose. + // this instruction takes ever 8'th bit (start at idx 7) and moves them into a single + // 16 bit output. Its like shaving off the top bit of each of the 16 bytes. + *(u16*)out0 = t.blks[0].movemask_epi8(); + *(u16*)out1 = t.blks[1].movemask_epi8(); + *(u16*)out2 = t.blks[2].movemask_epi8(); + *(u16*)out3 = t.blks[3].movemask_epi8(); + *(u16*)out4 = t.blks[4].movemask_epi8(); + *(u16*)out5 = t.blks[5].movemask_epi8(); + *(u16*)out6 = t.blks[6].movemask_epi8(); + *(u16*)out7 = t.blks[7].movemask_epi8(); + + // step each of out 8 pointer over to the next output row. + out0 -= out.stride(); + out1 -= out.stride(); + out2 -= out.stride(); + out3 -= out.stride(); + out4 -= out.stride(); + out5 -= out.stride(); + out6 -= out.stride(); + out7 -= out.stride(); + + // shift the 128 values so that the top bit is now the next one. + t.blks[0] = (t.blks[0] << 1); + t.blks[1] = (t.blks[1] << 1); + t.blks[2] = (t.blks[2] << 1); + t.blks[3] = (t.blks[3] << 1); + t.blks[4] = (t.blks[4] << 1); + t.blks[5] = (t.blks[5] << 1); + t.blks[6] = (t.blks[6] << 1); + t.blks[7] = (t.blks[7] << 1); + } + } + } + + // this is a special case there we dont have chunkSize bytes of input column left. + // because of this, the vectorized code above does not work and we instead so thing + // one byte as a time. + + // hhEnd denotes how many bytes are left [0,8). + auto hhEnd = (leftOverHeight + 7) / 8; + + // the last byte might be only part of a byte, so we also account for this + auto lastSkip = (8 - leftOverHeight % 8) % 8; + + for (int hh = 0; hh < hhEnd; ++hh) + { + // compute those parameters that determine if this is the last byte + // and that its a partial byte meaning that the last so mant output + // rows should not be written to. + auto skip = hh == (hhEnd - 1) ? lastSkip : 0; + auto rem = 8 - skip; + + for (int w = 0; w < subBlockWidth; ++w) + { + + auto start = in.data() + subBlockHight * chunkSize + hh + w * wStep; + + t.bytes[0][0] = *(start); + t.bytes[0][1] = *(start + step01); + t.bytes[0][2] = *(start + step02); + t.bytes[0][3] = *(start + step03); + t.bytes[0][4] = *(start + step04); + t.bytes[0][5] = *(start + step05); + t.bytes[0][6] = *(start + step06); + t.bytes[0][7] = *(start + step07); + t.bytes[0][8] = *(start + step08); + t.bytes[0][9] = *(start + step09); + t.bytes[0][10] = *(start + step10); + t.bytes[0][11] = *(start + step11); + t.bytes[0][12] = *(start + step12); + t.bytes[0][13] = *(start + step13); + t.bytes[0][14] = *(start + step14); + t.bytes[0][15] = *(start + step15); + + + auto out0 = outStart + (chunkSize * subBlockHight + hh) * 8 * out.stride() + w * 2; + + out0 -= out.stride() * skip; + t.blks[0] = (t.blks[0] << int( skip)); + + for (int j = 0; j < rem; j++) + { + *(u16*)out0 = t.blks[0].movemask_epi8(); + + out0 -= out.stride(); + + t.blks[0] = (t.blks[0] << 1); + } + } + } + + // this is a special case where the input column count was not a multiple of 16. + // For this case, we use + if (leftOverWidth) + { + for (int h = 0; h < subBlockHight; ++h) + { + // we are concerned with the output rows a range [16 * h, 16 * h + 15] + + auto start = in.data() + h * chunkSize + subBlockWidth * wStep; + + std::array src { + start, start + step01, start + step02, start + step03, start + step04, start + step05, + start + step06, start + step07, start + step08, start + step09, start + step10, + start + step11, start + step12, start + step13, start + step14, start + step15 + }; + + memset(t.blks, 0,sizeof(t)); + for (int i = 0; i < leftOverWidth; ++i) + { + t.bytes[0][i] = src[i][0]; + t.bytes[1][i] = src[i][1]; + t.bytes[2][i] = src[i][2]; + t.bytes[3][i] = src[i][3]; + t.bytes[4][i] = src[i][4]; + t.bytes[5][i] = src[i][5]; + t.bytes[6][i] = src[i][6]; + t.bytes[7][i] = src[i][7]; + } + + auto out0 = outStart + (chunkSize * h + 0) * eightOutSize1 + subBlockWidth * 2; + auto out1 = outStart + (chunkSize * h + 1) * eightOutSize1 + subBlockWidth * 2; + auto out2 = outStart + (chunkSize * h + 2) * eightOutSize1 + subBlockWidth * 2; + auto out3 = outStart + (chunkSize * h + 3) * eightOutSize1 + subBlockWidth * 2; + auto out4 = outStart + (chunkSize * h + 4) * eightOutSize1 + subBlockWidth * 2; + auto out5 = outStart + (chunkSize * h + 5) * eightOutSize1 + subBlockWidth * 2; + auto out6 = outStart + (chunkSize * h + 6) * eightOutSize1 + subBlockWidth * 2; + auto out7 = outStart + (chunkSize * h + 7) * eightOutSize1 + subBlockWidth * 2; + + if (leftOverWidth <= 8) + { + for (int j = 0; j < 8; j++) + { + *out0 = t.blks[0].movemask_epi8(); + *out1 = t.blks[1].movemask_epi8(); + *out2 = t.blks[2].movemask_epi8(); + *out3 = t.blks[3].movemask_epi8(); + *out4 = t.blks[4].movemask_epi8(); + *out5 = t.blks[5].movemask_epi8(); + *out6 = t.blks[6].movemask_epi8(); + *out7 = t.blks[7].movemask_epi8(); + + out0 -= out.stride(); + out1 -= out.stride(); + out2 -= out.stride(); + out3 -= out.stride(); + out4 -= out.stride(); + out5 -= out.stride(); + out6 -= out.stride(); + out7 -= out.stride(); + + t.blks[0] = (t.blks[0] << 1); + t.blks[1] = (t.blks[1] << 1); + t.blks[2] = (t.blks[2] << 1); + t.blks[3] = (t.blks[3] << 1); + t.blks[4] = (t.blks[4] << 1); + t.blks[5] = (t.blks[5] << 1); + t.blks[6] = (t.blks[6] << 1); + t.blks[7] = (t.blks[7] << 1); + } + } + else + { + for (int j = 0; j < 8; j++) + { + *(u16*)out0 = t.blks[0].movemask_epi8(); + *(u16*)out1 = t.blks[1].movemask_epi8(); + *(u16*)out2 = t.blks[2].movemask_epi8(); + *(u16*)out3 = t.blks[3].movemask_epi8(); + *(u16*)out4 = t.blks[4].movemask_epi8(); + *(u16*)out5 = t.blks[5].movemask_epi8(); + *(u16*)out6 = t.blks[6].movemask_epi8(); + *(u16*)out7 = t.blks[7].movemask_epi8(); + + out0 -= out.stride(); + out1 -= out.stride(); + out2 -= out.stride(); + out3 -= out.stride(); + out4 -= out.stride(); + out5 -= out.stride(); + out6 -= out.stride(); + out7 -= out.stride(); + + t.blks[0] = (t.blks[0] << 1); + t.blks[1] = (t.blks[1] << 1); + t.blks[2] = (t.blks[2] << 1); + t.blks[3] = (t.blks[3] << 1); + t.blks[4] = (t.blks[4] << 1); + t.blks[5] = (t.blks[5] << 1); + t.blks[6] = (t.blks[6] << 1); + t.blks[7] = (t.blks[7] << 1); + } + } + } + + //auto hhEnd = (leftOverHeight + 7) / 8; + //auto lastSkip = (8 - leftOverHeight % 8) % 8; + for (int hh = 0; hh < hhEnd; ++hh) + { + auto skip = hh == (hhEnd - 1) ? lastSkip : 0; + auto rem = 8 - skip; + + // we are concerned with the output rows a range [16 * h, 16 * h + 15] + auto w = subBlockWidth; + + auto start = in.data() + subBlockHight * chunkSize + hh + w * wStep; + + std::array src{ + start, start + step01, start + step02, start + step03, start + step04, start + step05, + start + step06, start + step07, start + step08, start + step09, start + step10, + start + step11, start + step12, start + step13, start + step14, start + step15 + }; + + + t.blks[0] = ZeroBlock; + for (int i = 0; i < leftOverWidth; ++i) + { + t.bytes[0][i] = src[i][0]; + } + + auto out0 = outStart + (chunkSize * subBlockHight + hh) * 8 * out.stride() + w * 2; + + out0 -= out.stride() * skip; + t.blks[0] = (t.blks[0] << int( skip)); + + for (int j = 0; j < rem; j++) + { + if (leftOverWidth > 8) + { + *(u16*)out0 = t.blks[0].movemask_epi8(); + } + else + { + *out0 = t.blks[0].movemask_epi8(); + } + + out0 -= out.stride(); + + t.blks[0] = (t.blks[0] << 1); + } + } + } + } + + + + + void sse_transpose128(block* inOut) + { + array a, b; + + for (int j = 0; j < 8; j++) + { + sse_loadSubSquare(inOut, a, j, j); + sse_transposeSubSquare(inOut, a, j, j); + + for (int k = 0; k < j; k++) + { + sse_loadSubSquare(inOut, a, k, j); + sse_loadSubSquare(inOut, b, j, k); + sse_transposeSubSquare(inOut, a, j, k); + sse_transposeSubSquare(inOut, b, k, j); + } + } + } + + + + + inline void sse_loadSubSquarex(array, 128>& in, array& out, u64 x, u64 y, u64 i) + { + typedef array, 2> OUT_t; + typedef array, 128> IN_t; + + static_assert(sizeof(OUT_t) == sizeof(array), ""); + static_assert(sizeof(IN_t) == sizeof(array, 128>), ""); + + OUT_t& outByteView = *(OUT_t*)&out; + IN_t& inByteView = *(IN_t*)∈ + + auto x16 = (x * 16); + + auto i16y2 = (i * 16) + 2 * y; + auto i16y21 = (i * 16) + 2 * y + 1; + + + outByteView[0][0] = inByteView[x16 + 0][i16y2]; + outByteView[1][0] = inByteView[x16 + 0][i16y21]; + outByteView[0][1] = inByteView[x16 + 1][i16y2]; + outByteView[1][1] = inByteView[x16 + 1][i16y21]; + outByteView[0][2] = inByteView[x16 + 2][i16y2]; + outByteView[1][2] = inByteView[x16 + 2][i16y21]; + outByteView[0][3] = inByteView[x16 + 3][i16y2]; + outByteView[1][3] = inByteView[x16 + 3][i16y21]; + outByteView[0][4] = inByteView[x16 + 4][i16y2]; + outByteView[1][4] = inByteView[x16 + 4][i16y21]; + outByteView[0][5] = inByteView[x16 + 5][i16y2]; + outByteView[1][5] = inByteView[x16 + 5][i16y21]; + outByteView[0][6] = inByteView[x16 + 6][i16y2]; + outByteView[1][6] = inByteView[x16 + 6][i16y21]; + outByteView[0][7] = inByteView[x16 + 7][i16y2]; + outByteView[1][7] = inByteView[x16 + 7][i16y21]; + outByteView[0][8] = inByteView[x16 + 8][i16y2]; + outByteView[1][8] = inByteView[x16 + 8][i16y21]; + outByteView[0][9] = inByteView[x16 + 9][i16y2]; + outByteView[1][9] = inByteView[x16 + 9][i16y21]; + outByteView[0][10] = inByteView[x16 + 10][i16y2]; + outByteView[1][10] = inByteView[x16 + 10][i16y21]; + outByteView[0][11] = inByteView[x16 + 11][i16y2]; + outByteView[1][11] = inByteView[x16 + 11][i16y21]; + outByteView[0][12] = inByteView[x16 + 12][i16y2]; + outByteView[1][12] = inByteView[x16 + 12][i16y21]; + outByteView[0][13] = inByteView[x16 + 13][i16y2]; + outByteView[1][13] = inByteView[x16 + 13][i16y21]; + outByteView[0][14] = inByteView[x16 + 14][i16y2]; + outByteView[1][14] = inByteView[x16 + 14][i16y21]; + outByteView[0][15] = inByteView[x16 + 15][i16y2]; + outByteView[1][15] = inByteView[x16 + 15][i16y21]; + + } + + + + inline void sse_transposeSubSquarex(array, 128>& out, array& in, u64 x, u64 y, u64 i) + { + static_assert(sizeof(array, 128>) == sizeof(array, 128>), ""); + + array, 128>& outU16View = *(array, 128>*)&out; + + auto i8y = i * 8 + y; + auto x16_7 = x * 16 + 7; + auto x16_15 = x * 16 + 15; + + block b0 = (in[0] << 0); + block b1 = (in[0] << 1); + block b2 = (in[0] << 2); + block b3 = (in[0] << 3); + block b4 = (in[0] << 4); + block b5 = (in[0] << 5); + block b6 = (in[0] << 6); + block b7 = (in[0] << 7); + + outU16View[x16_7 - 0][i8y] = b0.movemask_epi8(); + outU16View[x16_7 - 1][i8y] = b1.movemask_epi8(); + outU16View[x16_7 - 2][i8y] = b2.movemask_epi8(); + outU16View[x16_7 - 3][i8y] = b3.movemask_epi8(); + outU16View[x16_7 - 4][i8y] = b4.movemask_epi8(); + outU16View[x16_7 - 5][i8y] = b5.movemask_epi8(); + outU16View[x16_7 - 6][i8y] = b6.movemask_epi8(); + outU16View[x16_7 - 7][i8y] = b7.movemask_epi8(); + + b0 = (in[1] << 0); + b1 = (in[1] << 1); + b2 = (in[1] << 2); + b3 = (in[1] << 3); + b4 = (in[1] << 4); + b5 = (in[1] << 5); + b6 = (in[1] << 6); + b7 = (in[1] << 7); + + outU16View[x16_15 - 0][i8y] = b0.movemask_epi8(); + outU16View[x16_15 - 1][i8y] = b1.movemask_epi8(); + outU16View[x16_15 - 2][i8y] = b2.movemask_epi8(); + outU16View[x16_15 - 3][i8y] = b3.movemask_epi8(); + outU16View[x16_15 - 4][i8y] = b4.movemask_epi8(); + outU16View[x16_15 - 5][i8y] = b5.movemask_epi8(); + outU16View[x16_15 - 6][i8y] = b6.movemask_epi8(); + outU16View[x16_15 - 7][i8y] = b7.movemask_epi8(); + + } + + + // we have long rows of contiguous data data, 128 columns + void sse_transpose128x1024(array, 128>& inOut) + { + array a, b; + + for (int i = 0; i < 8; ++i) + { + for (int j = 0; j < 8; j++) + { + sse_loadSubSquarex(inOut, a, j, j, i); + sse_transposeSubSquarex(inOut, a, j, j, i); + + for (int k = 0; k < j; k++) + { + sse_loadSubSquarex(inOut, a, k, j, i); + sse_loadSubSquarex(inOut, b, j, k, i); + sse_transposeSubSquarex(inOut, a, j, k, i); + sse_transposeSubSquarex(inOut, b, k, j, i); + } + } + + } + + + } + +#ifdef OC_ENABLE_AVX2 + // Templates are used for loop unrolling. + + // Base case for the following function. + template + static OC_FORCEINLINE typename std::enable_if::type + avx_transpose_block_iter1(__m256i* inOut) {} + + // Transpose the order of the 2^blockSizeShift by 2^blockSizeShift blocks (but not within each + // block) within each 2^(blockSizeShift+1) by 2^(blockSizeShift+1) matrix in a nRows by 2^7 + // matrix. Only handles the first two rows out of every 2^blockRowsShift rows in each block, + // starting j * 2^blockRowsShift rows into the block. When blockRowsShift == 1 this does the + // transposes within the 2 by 2 blocks as well. + template + static OC_FORCEINLINE typename std::enable_if< + (j < (1 << blockSizeShift)) && (blockSizeShift > 0) && (blockSizeShift < 6) && + (blockRowsShift >= 1) + >::type avx_transpose_block_iter1(__m256i* inOut) + { + avx_transpose_block_iter1(inOut); + + // Mask consisting of alternating 2^blockSizeShift 0s and 2^blockSizeShift 1s. Least + // significant bit is 0. + u64 mask = ((u64) -1) << 32; + for (int k = 4; k >= (int) blockSizeShift; --k) + mask = mask ^ (mask >> (1 << k)); + + __m256i& x = inOut[j / 2]; + __m256i& y = inOut[j / 2 + (1 << (blockSizeShift - 1))]; + + // Handle the 2x2 blocks as well. Each block is within a single 256-bit vector, so it works + // differently from the other cases. + if (blockSizeShift == 1) + { + // transpose 256 bit blocks so that two can be done in parallel. + __m256i u = _mm256_permute2x128_si256(x, y, 0x20); + __m256i v = _mm256_permute2x128_si256(x, y, 0x31); + + __m256i diff = _mm256_xor_si256(u, _mm256_slli_epi16(v, 1)); + diff = _mm256_and_si256(diff, _mm256_set1_epi16(0xaaaa)); + u = _mm256_xor_si256(u, diff); + v = _mm256_xor_si256(v, _mm256_srli_epi16(diff, 1)); + + // Transpose again to switch back. + x = _mm256_permute2x128_si256(u, v, 0x20); + y = _mm256_permute2x128_si256(u, v, 0x31); + } + + __m256i diff = _mm256_xor_si256(x, _mm256_slli_epi64(y, (u64) 1 << blockSizeShift)); + diff = _mm256_and_si256(diff, _mm256_set1_epi64x(mask)); + x = _mm256_xor_si256(x, diff); + y = _mm256_xor_si256(y, _mm256_srli_epi64(diff, (u64) 1 << blockSizeShift)); + } + + // Special case to use the unpack* instructions. + template + static OC_FORCEINLINE typename std::enable_if< + (j < (1 << blockSizeShift)) && (blockSizeShift == 6) + >::type avx_transpose_block_iter1(__m256i* inOut) + { + avx_transpose_block_iter1(inOut); + + __m256i& x = inOut[j / 2]; + __m256i& y = inOut[j / 2 + (1 << (blockSizeShift - 1))]; + __m256i outX = _mm256_unpacklo_epi64(x, y); + __m256i outY = _mm256_unpackhi_epi64(x, y); + x = outX; + y = outY; + } + + // Base case for the following function. + template + static OC_FORCEINLINE typename std::enable_if::type + avx_transpose_block_iter2(__m256i* inOut) {} + + // Transpose the order of the 2^blockSizeShift by 2^blockSizeShift blocks (but not within each + // block) within each 2^(blockSizeShift+1) by 2^(blockSizeShift+1) matrix in a nRows by 2^7 + // matrix. Only handles the first two rows out of every 2^blockRowsShift rows in each block. + // When blockRowsShift == 1 this does the transposes within the 2 by 2 blocks as well. + template + static OC_FORCEINLINE typename std::enable_if<(nRows > 0)>::type + avx_transpose_block_iter2(__m256i* inOut) + { + constexpr size_t matSize = 1 << (blockSizeShift + 1); + static_assert(nRows % matSize == 0, "Can't transpose a fractional number of matrices"); + + constexpr size_t i = nRows - matSize; + avx_transpose_block_iter2(inOut); + avx_transpose_block_iter1(inOut + i / 2); + } + + // Base case for the following function. + template + static OC_FORCEINLINE typename std::enable_if::type + avx_transpose_block(__m256i* inOut) {} + + // Transpose the order of the 2^blockSizeShift by 2^blockSizeShift blocks (but not within each + // block) within each 2^matSizeShift by 2^matSizeShift matrix in a 2^(matSizeShift + + // matRowsShift) by 2^7 matrix. Only handles the first two rows out of every 2^blockRowsShift + // rows in each block. When blockRowsShift == 1 this does the transposes within the 2 by 2 + // blocks as well. + template + static OC_FORCEINLINE typename std::enable_if<(blockSizeShift < matSizeShift)>::type + avx_transpose_block(__m256i* inOut) + { + avx_transpose_block_iter2< + blockSizeShift, blockRowsShift, (1 << (matRowsShift + matSizeShift))>(inOut); + avx_transpose_block(inOut); + } + + static constexpr size_t avxBlockShift = 4; + static constexpr size_t avxBlockSize = 1 << avxBlockShift; + + // Base case for the following function. + template + static OC_FORCEINLINE typename std::enable_if::type + avx_transpose(__m256i* inOut) + { + for (size_t i = 0; i < 64; i += avxBlockSize) + avx_transpose_block<1, iter, 1, avxBlockShift + 1 - iter>(inOut + i); + } + + // Algorithm roughly from "Extension of Eklundh's matrix transposition algorithm and its + // application in digital image processing". Transpose each block of size 2^iter by 2^iter + // inside a 2^7 by 2^7 matrix. + template + static OC_FORCEINLINE typename std::enable_if<(iter > avxBlockShift + 1)>::type + avx_transpose(__m256i* inOut) + { + assert((u64)inOut % 32 == 0); + avx_transpose(inOut); + + constexpr size_t blockSizeShift = iter - avxBlockShift; + size_t mask = (1 << (iter - 1)) - (1 << (blockSizeShift - 1)); + if (iter == 7) + // Simpler (but equivalent) iteration for when iter == 7, which means that it doesn't + // need to count on both sides of the range of bits specified in mask. + for (size_t i = 0; i < (1 << (blockSizeShift - 1)); ++i) + avx_transpose_block(inOut + i); + else + // Iteration trick adapted from "Hacker's Delight". + for (size_t i = 0; i < 64; i = (i + mask + 1) & ~mask) + avx_transpose_block(inOut + i); + } + + void avx_transpose128(block* inOut) + { + avx_transpose((__m256i*) inOut); + } + + // input is 128 rows off 8 blocks each. + void avx_transpose128x1024(block* inOut) + { + assert((u64)inOut % 32 == 0); + AlignedArray buff; + for (u64 i = 0; i < 8; ++i) + { + + + //AlignedArray sub; + auto sub = &buff[128 * i]; + for (u64 j = 0; j < 128; ++j) + { + sub[j] = inOut[j * 8 + i]; + } + + //for (u64 j = 0; j < 128; ++j) + //{ + // buff[128 * i + j] = inOut[i + j * 8]; + //} + + avx_transpose128(&buff[128 * i]); + } + + for (u64 i = 0; i < 8; ++i) + { + //AlignedArray sub; + auto sub = &buff[128 * i]; + for (u64 j = 0; j < 128; ++j) + { + inOut[j * 8 + i] = sub[j]; + } + } + + } +#endif +} + + + diff --git a/tools/disassociability/visa-pets-FL/ot_library/drop-ot/Tools.h b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/Tools.h new file mode 100644 index 0000000..4e67823 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/Tools.h @@ -0,0 +1,47 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#pragma once +#include +#include +#include +namespace osuCrypto { + + + + + void eklundh_transpose128(block* inOut); + inline void eklundh_transpose128(std::array& inOut) { eklundh_transpose128(inOut.data()); } + +#ifdef OC_ENABLE_AVX2 + void avx_transpose128(block* inOut); +#endif +#ifdef OC_ENABLE_SSE2 + void sse_transpose128(block* inOut); + inline void sse_transpose128(std::array& inOut) { sse_transpose128(inOut.data()); }; +#endif + void transpose(const MatrixView& in, const MatrixView& out); + void transpose(const MatrixView& in, const MatrixView& out); + + + // Input must be given the alignment of an AlignedBlockArray, i.e. 32 bytes with AVX or 16 bytes + // without. + inline void transpose128(block* inOut) + { +#if defined(OC_ENABLE_AVX2) + assert((u64)inOut % 32 == 0); + avx_transpose128(inOut); +#elif defined(OC_ENABLE_SSE2) + assert((u64)inOut % 16 == 0); + sse_transpose128(inOut); +#else + eklundh_transpose128(inOut); +#endif + } + + inline void transpose128(std::array& inOut) { transpose128(inOut.data()); }; +} diff --git a/tools/disassociability/visa-pets-FL/ot_library/drop-ot/UnitTests.cpp b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/UnitTests.cpp new file mode 100644 index 0000000..4eb60d0 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/UnitTests.cpp @@ -0,0 +1,11 @@ +#include "UnitTests.h" +#include "IknpOtExt.h" +#include "MasnyRindal.h" + +namespace dropOt +{ + oc::TestCollection unitTests([](oc::TestCollection& tests) { + tests.add("Bot_MasnyRindal_Buff_test ", tests::Bot_MasnyRindal_Buff_test); + tests.add("OtExt_Iknp_Buff_test ", tests::OtExt_Iknp_Buff_test); + }); +} \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/ot_library/drop-ot/UnitTests.h b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/UnitTests.h new file mode 100644 index 0000000..ba3aa84 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/UnitTests.h @@ -0,0 +1,15 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#pragma once + +#include "cryptoTools/Common/TestCollection.h" + +namespace dropOt +{ + extern oc::TestCollection unitTests; +} \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/ot_library/drop-ot/config.h.in b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/config.h.in new file mode 100644 index 0000000..92b1c8f --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/drop-ot/config.h.in @@ -0,0 +1,18 @@ +#pragma once + +#include "cryptoTools/Common/config.h" + +#ifdef ENABLE_BOOST +#define OC_ENABLE_BOOST +#endif + + +// enable integration with boost for networking. +#cmakedefine ENABLE_BOOST @ENABLE_BOOST@ +#cmakedefine DROP_OT_ENABLE_RELIC @DROP_OT_ENABLE_RELIC@ +#cmakedefine DROP_OT_ENABLE_SODIUM @DROP_OT_ENABLE_SODIUM@ + + +#if defined(ENABLE_BOOST) != defined(OC_ENABLE_BOOST) +static_assert(0, "cryptoTools does not define ENABLE_BOOST the same as hydra"); +#endif \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/ot_library/frontend/CMakeLists.txt b/tools/disassociability/visa-pets-FL/ot_library/frontend/CMakeLists.txt new file mode 100644 index 0000000..a181ba3 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/frontend/CMakeLists.txt @@ -0,0 +1,15 @@ + +project(frontend) + + +############################################# +# Build frontend # +############################################# + +file(GLOB_RECURSE SRC_FRONTEND ${CMAKE_SOURCE_DIR}/frontend/*.cpp) +include_directories(${CMAKE_SOURCE_DIR}/frontend/) + +add_executable(frontend ${SRC_FRONTEND}) + +target_link_libraries(frontend diskhash) +target_link_libraries(frontend dropOt) diff --git a/tools/disassociability/visa-pets-FL/ot_library/frontend/main.cpp b/tools/disassociability/visa-pets-FL/ot_library/frontend/main.cpp new file mode 100644 index 0000000..e9027f7 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/frontend/main.cpp @@ -0,0 +1,243 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#include "drop-ot/UnitTests.h" +#include "cryptoTools/Network/IOService.h" +#include "cryptoTools/Network/Session.h" +#include "cryptoTools/Common/Timer.h" +#include "cryptoTools/Common/CLP.h" +#include "cryptoTools/Common/Log.h" +#include "cryptoTools/Common/Matrix.h" +#include +#include "drop-ot/IknpOtExt.h" +#include "drop-ot/MasnyRindal.h" +#include + +using namespace dropOt; + +// void tutorial(oc::CLP& cmd); +void networked(oc::CLP& cmd); +// void help(oc::CLP& cmd); + + + +struct Ot{ + + + +// The first function to be called. This generates a message to be sent to the +// other party. Intermediate state is written to stateDir directory. +std::vector sender_Setup1(std::string stateDir, std::string bankID) +{ + MasnyRindal mr; + PRNG prng(oc::sysRandomSeed()); + + std::vector ioBuffer; + + // we use random choices here. + oc::BitVector choices(128); + choices.randomize(prng); + + // run the first part of the protocol. + mr.receiveRoundOne(choices, prng, ioBuffer); + + // serialize the state. + std::ofstream out; + out.open(stateDir + "/senderState" + bankID +".bin", std::ios::trunc | std::ios::binary | std::ios::out); + + // char* var = "adsad"; + // out.open(var + "/senderState.bin", std::ios::trunc | std::ios::binary | std::ios::out); + mr.serialize(out); + + + // return the message to be sent. + return ioBuffer; +} + + +// The first function to be called by the sender. It take as input the stateDir +// and the message generated by recver_Setup(...). After this the sender is +// setup and ready to generate OT keys. Intermediate state is written to stateDir directory. +void sender_Setup2( + std::string stateDir, + std::string bankID, + span ioBuffer) +{ + MasnyRindal mr; + PRNG prng(oc::sysRandomSeed()); + + std::ifstream in; + in.open(stateDir + "/senderState" + bankID +".bin", std::ios::binary | std::ios::in); + mr.deserialize(in); + in.close(); + + auto choices = mr.mChoices; + std::remove((stateDir + "/senderState" + bankID +".bin").c_str()); + + std::vector keys(choices.size()); + mr.receiveRoundTwo(keys, prng, ioBuffer); + + IknpOtExtSender sender; + sender.setUniformBaseOts(keys, choices); + std::ofstream out; + out.open(stateDir + "/senderState" + bankID +".bin", std::ios::trunc | std::ios::binary | std::ios::out); + sender.serialize(out); +} + + + +// The setup function for the receiver. It take as input the stateDir +// and the message generated by sender_Setup1(...). After this the receiver is +// setup and ready to generate OT keys. Intermediate state is written to stateDir directory. +// It returns a message to be sent to the sender to complete their setup. +std::vector recver_Setup( + std::string stateDir, + std::string bankID, + span inMessage) +{ + MasnyRindal mr; + PRNG prng(oc::sysRandomSeed()); + + std::vector> keys(128); + + std::vector ioBuffer; + mr.sendRoundOne(128, prng, ioBuffer); + mr.sendRoundTwo(keys, prng, inMessage); + + IknpOtExtReceiver recver; + recver.setUniformBaseOts(keys); + + + std::ofstream out; + out.open(stateDir + "/recverState" + bankID +".bin", std::ios::trunc | std::ios::binary | std::ios::out); + recver.serialize(out); + + return ioBuffer; +} + + +// Perform the main protocol for the OT receiver. This generates useable OT keys. It takes as input stateDir, +// where the receiver state is serialized, and it takes as input choices which are the +// user provided choices values for the OTs. It writes the output OT keys to recverKeys. +// It returns a message to be sent to the sender. +std::vector recver_generateKeys(std::string stateDir, std::string bankID, std::vector& recverKeys, BitVector& choices) +{ + if (recverKeys.size() != choices.size()) + throw RTE_LOC; + + std::ifstream in; + in.open(stateDir + "/recverState" + bankID +".bin", std::ios::binary | std::ios::in); + + IknpOtExtReceiver recver; + recver.deserialize(in); + + + + PRNG prng(oc::sysRandomSeed()); + std::vector ioBuffer; + recver.receiveRoundOne_s(choices, recverKeys, prng, ioBuffer); + + std::ofstream out; + out.open(stateDir + "/recverState" + bankID +".bin", std::ios::trunc | std::ios::binary | std::ios::out); + recver.serialize(out); + + return ioBuffer; +} + + + +// Perform the main protocol for the OT sender. This generates useable OT keys. It takes as input stateDir, +// where the sender state is serialized. It writes the output OT keys to senderKeys. +void sender_generateKeys(std::string stateDir, std::string bankID, spaninMessages, std::vector>& senderKeys) +{ + std::ifstream in; + in.open(stateDir + "/senderState" + bankID +".bin", std::ios::binary | std::ios::in); + + IknpOtExtSender sender; + sender.deserialize(in); + + PRNG prng(oc::sysRandomSeed()); + sender.sendRoundOne_r(senderKeys, prng, inMessages); + + std::ofstream out; + out.open(stateDir + "/senderState" + bankID +".bin", std::ios::trunc | std::ios::binary | std::ios::out); + sender.serialize(out); +} + + + +}; + + +// int main(int argc, char** argv) +// { +// oc::CLP cmd(argc, argv); + +// if (cmd.isSet("u")) +// unitTests.runIf(cmd); +// else if (cmd.isSet("tut")) +// tutorial(cmd); +// else +// help(cmd); + +// return 0; +// } + +// void help(oc::CLP& cmd) +// { +// std::cout << "-u to run unit tests" << std::endl; +// std::cout << "-tut to run tutorial" << std::endl; + +// } + +// // The overall flow is shown here. +// void tutorial(oc::CLP& cmd) +// { +// // Ot ot; + +// // auto stateDir = cmd.getOr("stateDir", "."); +// // auto n = cmd.getOr("n", 100); +// // auto trials = cmd.getOr("t", 2); + + +// // ////////////////////////////// +// // // setup + +// // auto buffer = ot.sender_Setup1(stateDir); + +// // buffer = ot.recver_Setup(stateDir, span(buffer)); + +// // ot.sender_Setup2(stateDir, span(buffer)); + + +// // ////////////////////////////// +// // // main + +// // for (u64 t = 0; t < trials; ++t) +// // { + +// // // these will be input in the real code... +// // oc::BitVector choices(n); +// // for (u64 i = 0; i < choices.size(); ++i) +// // choices[i] = i % 2; + +// // // output keys +// // std::vector recverKeys(n); + +// // buffer = ot.recver_generateKeys(stateDir, recverKeys, choices); + +// // std::vector> senderKeys(n); +// // ot.sender_generateKeys(stateDir, span(buffer), senderKeys); + + +// // std::cout << "generated OT keys" << std::endl; +// // for (u64 i = 0; i < n; ++i) +// // { +// // std::cout << "sender {" << senderKeys[i][0] << ", " << senderKeys[i][1] << "}, recver{" << recverKeys[i] << ", " << choices[i] << "}" << std::endl; +// // } +// // } +// } \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/ot_library/readme.md b/tools/disassociability/visa-pets-FL/ot_library/readme.md new file mode 100644 index 0000000..eb211a2 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/readme.md @@ -0,0 +1,12 @@ + # drop-ot + ===== + +## Building + +For `` in `linux,osx,x64-Release,x64-Debug` + +``` +cmake -S . --preset +cmake --build out/build// +``` +Run the program, `./out/build//frontend/frontend` (or similar), to see the help info. \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/ot_library/wrapper/CMakeLists.txt b/tools/disassociability/visa-pets-FL/ot_library/wrapper/CMakeLists.txt new file mode 100644 index 0000000..39802b1 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/wrapper/CMakeLists.txt @@ -0,0 +1,8 @@ +file(GLOB_RECURSE SRCS *.cpp) +#file(GLOB_RECURSE SRCS *.c) + +include_directories(${CMAKE_SOURCE_DIR}/wrapper/) + +add_library(wrapper SHARED ${SRCS}) + +target_link_libraries(wrapper diskhash dropOt) diff --git a/tools/disassociability/visa-pets-FL/ot_library/wrapper/CWrapper.cpp b/tools/disassociability/visa-pets-FL/ot_library/wrapper/CWrapper.cpp new file mode 100644 index 0000000..2500425 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/ot_library/wrapper/CWrapper.cpp @@ -0,0 +1,385 @@ +/* +* Copyright 2022-2023 Visa +* +* This code is licensed under the Creative Commons +* Attribution-NonCommercial 4.0 International Public License +* (https://creativecommons.org/licenses/by-nc/4.0/legalcode). +*/ +#include +#include "frontend/main.cpp" +#include +#include +#include "diskhash/diskhash.hpp" + + +extern "C" +{ + + using HTBlock = std::array; + + + void decrypt(char* hashTableFilePath, char* accountIds, int* accountIdLengths, int maxAccountIdLength, int totalNumberOfAccountIds, int* encryptedZeroGradList_, int* encryptedOneGradList_, int tensorsPerAccount, int* unencryptedGradList_, int *choicesArray) + { + dht::DiskHash ht(hashTableFilePath, maxAccountIdLength+1, dht::DHOpenRW); + + std::vector buffer; + + buffer.resize( ( (tensorsPerAccount * sizeof(oc::block) ) / sizeof(int32_t) )); + + + + auto unencryptedGradList = span(unencryptedGradList_, totalNumberOfAccountIds * tensorsPerAccount); + + auto encryptedZeroGradList = span(encryptedZeroGradList_, totalNumberOfAccountIds * (tensorsPerAccount+4)); + auto encryptedOneGradList = span(encryptedOneGradList_, totalNumberOfAccountIds * (tensorsPerAccount+4)); + + auto unencIter = unencryptedGradList.begin(); + + auto encZeroIter = encryptedZeroGradList.begin(); + auto encOneIter = encryptedOneGradList.begin(); + + std::string subbuff; + for (u64 i = 0; i < totalNumberOfAccountIds; ++i) + { + + // Getting the two keys + subbuff.clear(); + subbuff.append(accountIds, accountIdLengths[i]); + accountIds += accountIdLengths[i]; + + // std::cout << "The subbuff is " << subbuff << std::endl; + + auto ptr = ht.lookup(subbuff.c_str()); + if(ht.lookup(subbuff.c_str()) == nullptr) + { + std::cout << "missing key in hash table: " << subbuff << std::endl; + throw RTE_LOC; + } + + + block tempKey; + memcpy(&tempKey, ptr, sizeof(block)); + + // Decryption Starts Here + oc::AES aes(tempKey); + + block idx; + memcpy(&idx, encZeroIter, sizeof(block) ); + + encZeroIter = encZeroIter + 4; + encOneIter = encOneIter + 4; + + aes.ecbEncCounterMode(idx, buffer.size(), buffer.data()); + + int32_t* xorPointer = ( int32_t *) &buffer[0]; + + for(int j=0; j> ht(hashTableFilePath, maxAccountIdLength+1, dht::DHOpenRW); + + + std::vector bufferZeroGrad, bufferOneGrad; + + bufferZeroGrad.resize( ( (tensorsPerAccount * sizeof(oc::block) ) / sizeof(int32_t) )); + bufferOneGrad.resize( ( (tensorsPerAccount * sizeof(oc::block) ) / sizeof(int32_t) )); + + auto zeroGradList = span(zeroGradList_, totalNumberOfAccountIds * tensorsPerAccount); + auto oneGradList = span(oneGradList_, totalNumberOfAccountIds * tensorsPerAccount); + + auto encryptedZeroGradList = span(encryptedZeroGradList_, totalNumberOfAccountIds * (tensorsPerAccount+4)); + auto encryptedOneGradList = span(encryptedOneGradList_, totalNumberOfAccountIds * (tensorsPerAccount+4)); + + auto zeroIter = zeroGradList.begin(); + auto oneIter = oneGradList.begin(); + + auto encZeroIter = encryptedZeroGradList.begin(); + auto encOneIter = encryptedOneGradList.begin(); + + std::string subbuff; + + + for (u64 i = 0; i < totalNumberOfAccountIds; ++i) + { + + // Getting the two keys + subbuff.clear(); + subbuff.append(accountIds, accountIdLengths[i]); + accountIds += accountIdLengths[i]; + + std::array tempKeys; + + auto ptr = *ht.lookup(subbuff.c_str()); + if(ht.lookup(subbuff.c_str()) == nullptr) + { + std::cout << "missing key in hash table: " << subbuff << std::endl; + throw RTE_LOC; + } + + + tempKeys[0] = ptr[0]; + tempKeys[1] = ptr[1]; + + // Encryption starts here + oc::AES aesZeroGrad(tempKeys[0]); + oc::AES aesOneGrad(tempKeys[1]); + + PRNG prng(oc::sysRandomSeed()); + block idx = prng.get(); + + aesZeroGrad.ecbEncCounterMode(idx, bufferZeroGrad.size(), bufferZeroGrad.data()); + aesOneGrad.ecbEncCounterMode(idx, bufferOneGrad.size(), bufferOneGrad.data()); + + int32_t* xorZeroPointer = ( int32_t *) &bufferZeroGrad[0]; + int32_t* xorOnePointer = ( int32_t *) &bufferOneGrad[0]; + + // Pushing the counter mode index + *encZeroIter++ = idx.get(0); + *encOneIter++ = idx.get(0); + + *encZeroIter++ = idx.get(1); + *encOneIter++ = idx.get(1); + + *encZeroIter++ = idx.get(2); + *encOneIter++ = idx.get(2); + + *encZeroIter++ = idx.get(3); + *encOneIter++ = idx.get(3); + + + + for(int j=0; j " << x << " + " << v << " -> "; + // Encrypting Zero Gradient + *encZeroIter++ = *xorZeroPointer ^ *zeroIter++; + xorZeroPointer++; + + + // Encrypting One Gradient + *encOneIter++ = *xorOnePointer ^ *oneIter++; + xorOnePointer++; + + } + + } + } + + void deleteState( long long state) + { + auto pointer = (std::vector*) state; + delete pointer; + } + + unsigned char* senderSetup1(char* stateDirPointer, char* bankIDPointer, int* returnedPointerSize, long long* state ) + { + Ot ot; + + std::string stateDir(stateDirPointer); + std::string bankID(bankIDPointer); + + std::vector* returnvalue = new std::vector{ ot.sender_Setup1(stateDir,bankID ) } ; + // std::cout << "senderSetup1 called completed" << " BankID " << bankID << "\n"; + + *returnedPointerSize = returnvalue->size(); + + + oc::RandomOracle hash(16); + hash.Update(returnvalue->data(), returnvalue->size()); + oc::block h; + hash.Final(h); + + + // std::cout << "In Sender Setup 1 Byte Hash is " << " " << h << " BankID " << bankID << "\n"; + + *state = (long long)returnvalue; + + return returnvalue->data(); + + } + + + unsigned char* recverSetup(char* stateDirPointer, char* bankIDPointer, char* senderSetup1ReturnValue, int senderSetup1ReturnSize, int* returnedPointerSize, long long* state) + { + Ot ot; + + std::string stateDir(stateDirPointer); + std::string bankID(bankIDPointer); + + span input{(u8*)senderSetup1ReturnValue, size_t(senderSetup1ReturnSize) }; + + oc::RandomOracle hash(16); + hash.Update(senderSetup1ReturnValue, size_t(senderSetup1ReturnSize)); + oc::block h; + hash.Final(h); + + // std::cout << "In Receiver Setup 1 Byte Hash is " << " " << h << " BankID " << bankID << "\n"; + + std::vector* returnvalue = new std::vector{ ot.recver_Setup(stateDir, bankID, input ) } ; + // std::cout << "recverSetup called completed" << " BankID " << bankID << "\n"; + + + *returnedPointerSize = returnvalue->size(); + *state = (long long)returnvalue; + + + oc::RandomOracle hash1(16); + hash1.Update(returnvalue->data(), returnvalue->size()); + oc::block h1; + hash1.Final(h1); + + // std::cout << "In Receiver Setup 1 Byte Hash is " << " " << h1 << " BankID " << bankID << "\n"; + + return returnvalue->data(); + } + + void senderSetup2(char* stateDirPointer, char* bankIDPointer, u8* recverSetupReturnValue, int recverSetupReturnSize) + { + Ot ot; + std::string stateDir(stateDirPointer); + std::string bankID(bankIDPointer); + + // std::cout << "recverSetupReturnSize is " << recverSetupReturnSize << std::endl; + oc::RandomOracle hash(16); + hash.Update(recverSetupReturnValue, size_t(recverSetupReturnSize)); + oc::block h; + hash.Final(h); + + // std::cout << "In Sender Setup2 Byte Hash is " << " " << h << " BankID " << bankID << "\n"; + + span input{recverSetupReturnValue, size_t(recverSetupReturnSize) }; + + ot.sender_Setup2(stateDir, bankID, input ); + // std::cout << "senderSetup2 called completed" << " BankID " << bankID << "\n"; + + } + + + unsigned char* recverGenerateKeys(char* stateDirPointer, char* bankIDPointer, char* hashTableFilePath,int *choicesArray, int choiceslength ,int* returnedPointerSize, long long* state, char* accountIds, int* accountIdLengths, int maxAccountIdLength) + { + + Ot ot; + std::string stateDir(stateDirPointer); + std::string bankID(bankIDPointer); + + // std::cout << "In Receiver Key Gen for BankID = " << bankID << std::endl; + + // output keys + std::vector recverKeys(choiceslength); + + // Construct a zero initialized BitVector of size choiceslength + oc::BitVector choices(choiceslength); + + // std::cout << "Choices length is" << choiceslength << std::endl; + + for (u64 i=0; i < choiceslength; i++) + { + + if(choicesArray[i] == 1) + choices[i] = 1; + // std::cout << choices[i] << " " << std::endl; + } + + // std::cout << "Choices BitVector is initiazed" << std::endl; + + std::vector* returnvalue = new std::vector{ ot.recver_generateKeys(stateDir, bankID, recverKeys, choices) } ; + // std::cout << "Receiver Keys Generator completed" << std::endl; + + + *returnedPointerSize = returnvalue->size(); + *state = (long long)returnvalue; + + + oc::RandomOracle hash1(16); + hash1.Update(returnvalue->data(), returnvalue->size()); + oc::block h1; + hash1.Final(h1); + + dht::DiskHash ht(hashTableFilePath, maxAccountIdLength+1, dht::DHOpenRW); + + std::string subbuff; + // subbuff.reserve(maxAccountIdLength); + for (u64 i = 0; i < choiceslength; ++i) + { + subbuff.clear(); + subbuff.append(accountIds, accountIdLengths[i]); + accountIds += accountIdLengths[i]; + + // MyFile << "recver{" << recverKeys[i] << ", " << choices[i] << "} " << subbuff << std::endl; + + const bool isInserted = ht.insert(subbuff.c_str(), recverKeys[i].get()); + if(isInserted == 0) + { + std::cout << " subbuff is not inserted " << subbuff <data(); + } + + void senderGenerateKeys(char* stateDirPointer, char* bankIDPointer, char* hashTableFilePath, u8* recverGenKeyBytes, int recverGenKeySize, int choiceslength, char* accountIds, int* accountIdLengths, int maxAccountIdLength) + { + + Ot ot; + std::string stateDir(stateDirPointer); + std::string bankID(bankIDPointer); + + // std::cout << "recverGenKeySize is " << recverGenKeySize << " BankID " << bankID << "\n"; + oc::RandomOracle hash(16); + hash.Update(recverGenKeyBytes, size_t(recverGenKeySize)); + oc::block h; + hash.Final(h); + // std::cout << "In Sender Key Gen Byte Hash is " << " " << h << " BankID " << bankID << "\n"; + + + span input{recverGenKeyBytes, size_t(recverGenKeySize) }; + + + std::vector> senderKeys(choiceslength); + dht::DiskHash> ht(hashTableFilePath, maxAccountIdLength+1, dht::DHOpenRW); + + ot.sender_generateKeys(stateDir, bankID, input, senderKeys); + + std::string subbuff; + for (u64 i = 0; i < choiceslength; ++i) + { + subbuff.clear(); + subbuff.append(accountIds, accountIdLengths[i]); + accountIds += accountIdLengths[i]; + + std::array tempBlock { + senderKeys[i][0].get(), + senderKeys[i][1].get() + }; + + ht.insert(subbuff.c_str(),tempBlock); + + } + + } + +} \ No newline at end of file diff --git a/tools/disassociability/visa-pets-FL/solution.md b/tools/disassociability/visa-pets-FL/solution.md new file mode 100644 index 0000000..cfdad51 --- /dev/null +++ b/tools/disassociability/visa-pets-FL/solution.md @@ -0,0 +1,179 @@ +# Problem statement + +The setting considers payment network systems handling transactions. In this setting, there is a number of financial institutions (or Banks) denoted by $B_i$ which route their transactions through a central hub $S$. The transaction messages include the following fields [[1]](https://www.drivendata.org/competitions/98/nist-federated-learning-1/page/524/): + + +1. `MessageId` - Globally unique identifier within this dataset for individual transactions +2. `UETR` - The Unique End-to-end Transaction Reference—a 36-character string enabling traceability of all individual transactions associated with a single end-to-end transaction +3. `TransactionReference` - Unique identifier for an individual transaction +4. `Timestamp` - Time at which the individual transaction was initiated +5. `Sender` - Institution (bank) initiating/sending the individual transaction +6. `Receiver` - Institution (bank) receiving the individual transaction +7. `OrderingAccount` - Account identifier for the originating ordering entity (individual or organization) for end-to-end transaction, +8. `OrderingName` - Name for the originating ordering entity +9. `OrderingStreet` - Street address for the originating ordering entity +10. `OrderingCountryCityZip` - Remaining address details for the originating ordering entity +11. `BeneficiaryAccount` - Account identifier for the final beneficiary entity (individual or organization) for end-to-end transaction +12. `BeneficiaryName` - Name for the final beneficiary entity +13. `BeneficiaryStreet` - Street address for the final beneficiary entity +14. `BeneficiaryCountryCityZip` - Remaining address details for the final beneficiary entity +15. `SettlementDate` - Date the individual transaction was settled +16. `SettlementCurrency` - Currency used for transaction +17. `SettlementAmount` - Value of the transaction net of fees/transfer charges/forex +18. `InstructedCurrency` - Currency of the individual transaction as instructed to be paid by the Sender +19. `InstructedAmount` - Value of the individual transaction as instructed to be paid by the Sender + +As those transaction messages are communicated through $S$ in plaintext, $S$ is able to build a rich dataset that includes all of the above attributes. Naturally, for identifying anomalous transactions, $S$ would be the best choise to deploy machine learning techniques towards anomaly detection. Towards building a classification model, $S$ is provided with a training dataset $T$ which includes a number of transactions with the above fields, plus an additional `Label` Boolean attribute which indicates whether a transaction is anomalous or not. + +Banks participating in the network payment system each has a dataset with the following fields: + +1. `Bank` - Identifier for the bank +2. `Account` - Identifier for the account +3. `Name` - Name of the account +4. `Street` - Street address associated with the account +5. `CountryCityZip` - Remaining address details associated with the account +6. `Flag` - Enumerated data type indicating potential issues or special features that have been associated with an account, with values as follows: + `00` - No flag + `01` - Account closed + `03` - Account recently opened + `04` - Name mismatch + `05` - Account under monitoring + `06` - Account suspended + `07` - Account frozen + `08` - Non-transaction account + `09` - Beneficiary deceased + `10` - Invalid company ID + `11` - Invalid individual ID + +As $S$ already knows all of the information held by Banks except the `Flag` associated with an account, the goal is to augment the machine learning model, taking this attribute into account. However due to privacy regulations and laws (e.g. [GDPR](https://eur-lex.europa.eu/EN/legal-content/summary/general-data-protection-regulation-gdpr.html)), this additional attribute needs to be shared with $S$ in a privacy-preserving way, namely enabling $S$ to enhance its model without directly learning `Flag`. In general, the primary goal is to enable $S$ to decide if a transaction is anomalous or not with high accuracy, while protecting against private data leakage from the respective data holders, while $S$ should be the only entity which learns the model's prediction. + +# Solution overview + +## Key observations + +Based on the synthetic training datasets provided, we observed the following: + +1. For all transactions, the sender account `Flag` in the corresponding bank dataset is always `00`. In other words, a transaction cannot spend funds from an account with an "abnormal" state. Therefore, only the receiving account `Flag` is of interest. +2. If the `Flag` (receiving) account attribute has a value other than `00`, then a transaction sending to this account is always marked as anomalous in the training dataset. The contrapositive is that a transaction marked as non-anomalous, then the receiving account `Flag` is always `00`. + + +## Our approach + +Our tailored solution leverages the above two observations. To incorporate the extra `Flag` information into the model maintained by $S$, we train it with a secret input distinguishing between normal and abnormal accounts. +During training, $S$ selects a batch of transactions and computes updates to the model for both possible inputs for each transaction in the batch. For each transaction, $S$, the receiving bank, and a third party referred to as the *aggregator* perform an instance MPC. $S$ provides the two possible updates for the transaction, and the receiver bank provides a bit indicating whether the receiver account was normal or abnormal. +The MPC ensures that the parties *obliviously* select the correct update, based on the bit provided by the receiver bank. All such selected updates are then aggregated. Finally, the aggregator also adds enough noise to hide contributions of individual transactions to protect against inference attacks by $S$. The MPC ensures that this aggregated and noised update for the entire batch is revealed to $S$ alone. The magnitude of noise used is a hyperparameter that depends on the batch size, the clipping bound, and the desired level of privacy. So, neither the aggregator nor the banks can gain insight into the transaction details. + + +During inference, when $S$ receives a transaction and wants to classify it, $S$ runs that transaction through the model with both inputs to obtain two possible model outputs, or labels. $S$ and the receiving bank perform an instance of secure two-party computation. $S$ provides the two possible labels for the transaction, and the receiver bank provides a bit indicating whether the receiver account was normal or abnormal. The computation ensures that the parties *obliviously* select the correct label and reveal it to $S$ alone. + + +![](figure.png) + + +## Implementing our approach + +In our approach, $S$ trains a model with a secret input that differs for "normal" accounts and for "abnormal" accounts. $S$ initializes this model, but requires aid in training it. + +### Setup \& Key Management + +We assume there is an underlying PKI in place: all parties are aware of the public keys of the other parties and can use those to encrypt messages that can be decrypted only by said parties. $S$ uses the public keys of banks to encrypt account identifiers for them; the banks and $S$ use the public key of $S$ to encrypt other information. + +### Training + +$S$ initializes and manages the main training procedure which operates in batches. For each batch, $S$ randomly samples a batch $K$ of $k$ transactions ($k$ being a hyperparameter). For each transaction $s$, $S$ inputs $s$ through its model $M$ twice: once with the secret input set to normal and once to abnormal. Note that $S$ does not know if the receiving account flag is normal or abnormal, and thus compute two possible updates $u_0(s)$ and $u_1(s)$. + + + +### Inference Time + +During inference time, when $S$ receives a transaction $s$ and wishes to predict the label, $S$ runs it through its model twice, once guessing that the account is normal and once guessing that the account is abnormal. + + +### Algorithms & Protocols Utilized + +In principle, $S$ can use any model structure that can be trained by incremental and aggregatable updates, e.g. stochastic gradient descent. + + +Our approach uses a combination of oblivious transfer and secret sharing for protecting privacy. + + +## Discussion of Privacy + +As privacy in this setting implies protecting against private data leakage from the respective data holders, we discuss each case separately below. + +### Privacy Against Aggregator. + +The aggregator aids $S$ and the banks in training the federated model. In general we assume that either the aggregator or $S$ is honest, and for this section we assume that $S$ is honest. Privacy against the aggregator then reduces to the security of the underlying oblivious transfer protocol and the properties of secret sharing. + + + +To protect privacy, the aggregator samples and adds noise to aggregated updates. We assume that the aggregator will not observe the model or its outputs. + + +### Privacy Against Bank(s). + +During training, a Bank only learns the account queried and the secret share $s_1$, which leaks no information. + +### Privacy Against $S$. + +During training, $S$ receives a model update from the aggregator. Having computed the individual updates (two per transaction), $S$ could potentially attempt to find the update selected for each transaction to infer associated account flag. However, our approach applies noise of sufficient magnitude to the aggregator's update for each batch to prevent $S$ from inferring flags. + +Our goal is to hide flag-information at account level and not merely pertaining to individual transactions from $S$. Hence, we consider the expected number of transactions from the same account in a batch. This aspect is exaggerated by up-scaling abnormal transactions in the dataset. + +During inference, $S$ learns the final classification of a transaction, which does not provide information about the Bank's flag other than the inherent privacy leakage. + +## Empirical Evaluation of Privacy + +We empirically evaluated the privacy-accuracy trade-off against the membership inference attack during training time. + +### Threat Model + +$S$ attempts to learn accounts' bit flags from the Banks. In our learning protocol, $S$ already knows the model architecture, the training parameters and all features of a transaction except the receiver bank account flag. In addition, we also assume $S$ already knows the status flags of a fraction (denoted by $\alpha$) of the bank accounts. With this prior knowledge, $S$ can then build an attack model that predicts the receiving account bit flag based on the features and the fincrime detection model's prediction of a transaction. The attacker's strength is characterized by the fraction $\alpha$ of account flags already known to $S$. We consider both a strong ($\alpha=0.2$) and a weak ($\alpha=0.05$) attacker. + +### Attack Algorithm. + +We empirically evaluate the privacy leakage during training time leveraging aspects of the classic membership inference attack (MIA) framework. +1. (Shadow model generation.) + In MIA, the attacker learns a set of shadow models using its own data to investigate the relation between training data and resulting model. Let $D_{known}$ denote the transactions where $S$ knows the receiver's bank account flag, and let $D_{unknown}$ denote the remaining transactions. $S$ first trains $m=5$ shadow models over different train/test splits of $D_{known}$. The shadow model training is identical to training the target anomaly detector. + +2. (MIA model generation.) During the test time of the shadow model, $S$ collects the model prediction over two copies, one with bank status flag 0 and another with flag 1, for each transaction. This prediction, together with the transaction's feature, becomes training data for the MIA model. The label of the data is 1 if the bank flag is correct and 0 otherwise. $S$ then trains an MIA model over the generated dataset. In our evaluation, the model is a MLP with three hidden layers of 128, 64 and 64 nodes. + +3. (Account flag inference.) For each transaction in $D_{unknown}$, $S$ collects two copies of predictions from the target anomaly detection model --- one with bank flag 0 and another with bank flag 1 --- and concatenates each prediction with the transaction features. Then, $S$ use the MIA model generated in Step 2 to calculate which bank flag is more likely to be true. + + +### Key Baselines + +**(Privacy.)** Since non-anomalous transactions always have a 'normal' receiver account flag 0, we only consider $S$ in the attacker role for deriving the bank flag bit associated with anomalous transactions. Furthermore, we observe that 82\% of accounts associated with an anomalous transaction have account flag 0 in the training dataset. $S$ can already achieve a success rate of 0.82 by guessing 0 all the time. Attack success rates below 0.82, achieved by adding noise, indicate that privacy is preserved. + +**(Accuracy.)** The sample XGBoost solution with AUPRC=0.6 without bank account flags is considered a baseline. An AUPRC lower than that will defeat the purpose of sharing information through federated learning. + +## Evaluation + +The table below shows the training time privacy-accuracy trade-off of our learning protocol. Even in the strong attack scheme, our noise injection mechanism can effectively enhance privacy in federated learning. When adding Gaussian noise ($\sigma=0.2$) and Laplace noise ($\lambda=0.1$), our pipeline can limit the success rate of MIA close to the naive 0.82 baseline (of guessing all 0). Meanwhile, the AUPRC of both methods are higher than a centralized solution *without* bank flags. The results show that a well-designed privacy enhancing federated learning pipeline can help achieve higher utility against financial crimes with little additional privacy leakage. + + +**Table: Privacy-accuracy tradeoff.** A larger AUPRC indicates better anomaly detection model performance. A smaller MIA success rate corresponds to better privacy-protection of bank account flags during training. Gaussian noise with $\sigma=0.2$ and Laplace noise with $\lambda=0.1$ achieve a good trade-off. The baseline for attacker success (always guess 00) given account flag distribution in training set is 0.82. MIA success rate below 0.82 is a good measure of privacy-protection. + +| Attack
Strength | Noise | MIA
Success Rate | AUPRC | +|:------:|:-----:|:-----:|:------:| +| Strong
$$\alpha=0.2$$ | No noise
Gaussian, $\sigma=0.1$
Laplace, $\lambda=0.1$
Gaussian, $\sigma=0.2$
Laplace, $\lambda=1$ | 0.93
0.92
0.86
0.80
0.57 | 0.79
0.72
0.70
0.65
0.13 | +| Weak
$$\alpha=0.05$$ | No noise
Gaussian, $\sigma=0.1$
Laplace, $\lambda=0.1$
Gaussian, $\sigma=0.2$
Laplace, $\lambda=1$ | 0.89
0.89
0.84
0.79
0.56 | 0.79
0.76
0.70
0.65
0.13 | + From 0e0722a7f88ef9e568f597705d196e684f9e15c0 Mon Sep 17 00:00:00 2001 From: Jayati Date: Thu, 28 Mar 2024 15:18:26 -0400 Subject: [PATCH 48/58] Create README.md --- tools/risk-assessment/Comcast-MAP/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tools/risk-assessment/Comcast-MAP/README.md diff --git a/tools/risk-assessment/Comcast-MAP/README.md b/tools/risk-assessment/Comcast-MAP/README.md new file mode 100644 index 0000000..fa23e9a --- /dev/null +++ b/tools/risk-assessment/Comcast-MAP/README.md @@ -0,0 +1,20 @@ +# Tool Template + +**Name of Tool:** + +**Primary Focus Area (select one):** Disassociability or Privacy Risk Assessment + +**Disassociability Keywords (select any relevant):** Differential Privacy, K-Anonymity, Anonymization, Access Control, Information Leakage, Algorithmic Fairness, Verification of Algorithms, Machine Learning, Database Queries, Synthetic Data Generation, Location Data, Adversarial Examples, Other/Propose New Keyword + +**Brief Description:** + +**Additional Notes:** [can include any additional links or resources, describe particular ways in which people can engage with the project, if relevant, etc.] + +**GitHub User Serving as POC (or Email Address):** @[POC] + +**Affiliation/Organization(s) Contributing (if relevant):** + +## For a Linked Tool +*Add the link here and remove the below section, "For A Hosted Tool".* + +**Tool Link:** From 32afe3d70cfc6eda1eebc6abb72eb444d118c7be Mon Sep 17 00:00:00 2001 From: Jayati Date: Thu, 28 Mar 2024 15:20:36 -0400 Subject: [PATCH 49/58] Update README.md --- tools/risk-assessment/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/risk-assessment/README.md b/tools/risk-assessment/README.md index 3d65839..2fc1279 100644 --- a/tools/risk-assessment/README.md +++ b/tools/risk-assessment/README.md @@ -5,6 +5,10 @@ Contributions are listed in alphabetical order. **[Link to tool](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/risk-assessment/FAIR-Privacy)** +## Models of Applied Privacy (MAP) + +**[Link to tool](https://github.com/Comcast/MAP)** + ## NIST Privacy Risk Assessment Methodology (PRAM) **[Link to tool](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/risk-assessment/NIST-Privacy-Risk-Assessment-Methodology-PRAM)** From 98986cceac97e3493a3d64e57b1f1e3b5af34c2a Mon Sep 17 00:00:00 2001 From: Jayati Date: Thu, 28 Mar 2024 19:27:03 -0400 Subject: [PATCH 50/58] Update README.md --- tools/risk-assessment/Comcast-MAP/README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tools/risk-assessment/Comcast-MAP/README.md b/tools/risk-assessment/Comcast-MAP/README.md index fa23e9a..8b7c3cc 100644 --- a/tools/risk-assessment/Comcast-MAP/README.md +++ b/tools/risk-assessment/Comcast-MAP/README.md @@ -1,20 +1,18 @@ -# Tool Template +# Models of Applied Privacy (MAP) -**Name of Tool:** +**Name of Tool:** MAP (Personas) and xCOMPASS (Questionnaire) -**Primary Focus Area (select one):** Disassociability or Privacy Risk Assessment +**Primary Focus Area:** Privacy Threat Modeling -**Disassociability Keywords (select any relevant):** Differential Privacy, K-Anonymity, Anonymization, Access Control, Information Leakage, Algorithmic Fairness, Verification of Algorithms, Machine Learning, Database Queries, Synthetic Data Generation, Location Data, Adversarial Examples, Other/Propose New Keyword - -**Brief Description:** +**Brief Description:** Models of Applied Privacy (MAP) is a privacy threat modeling framework, built on top of LINDDUN and NIST Privacy Risk Assessment Methodology. It uses personas to contextualize threats, by considering potential privacy threats as a combination of threat actor (both malicious and benign), mechanism of attack, and probable impact. To better frame these threats, MAP is accompanied by xCOMPASS, which is an assessment based on these various privacy threat personas. A user may choose to either play the card version, by creating different persona combinations and developing their own threats. They can also use xCOMPASS directly as an assessment to model different privacy threats to their application. For more information on xCOMPASS, please use the link below. **Additional Notes:** [can include any additional links or resources, describe particular ways in which people can engage with the project, if relevant, etc.] -**GitHub User Serving as POC (or Email Address):** @[POC] +**GitHub User Serving as POC (or Email Address):** spider@comcast.com -**Affiliation/Organization(s) Contributing (if relevant):** +**Affiliation/Organization(s) Contributing (if relevant):** Comcast -## For a Linked Tool +## More information *Add the link here and remove the below section, "For A Hosted Tool".* -**Tool Link:** +**Tool Link:** https://github.com/Comcast/MAP From c376475d1e42d7aa3e6116d3cbdb97029075144b Mon Sep 17 00:00:00 2001 From: Jayati Date: Fri, 29 Mar 2024 09:07:55 -0400 Subject: [PATCH 51/58] Update README.md --- tools/risk-assessment/Comcast-MAP/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/risk-assessment/Comcast-MAP/README.md b/tools/risk-assessment/Comcast-MAP/README.md index 8b7c3cc..6cd99a0 100644 --- a/tools/risk-assessment/Comcast-MAP/README.md +++ b/tools/risk-assessment/Comcast-MAP/README.md @@ -6,13 +6,11 @@ **Brief Description:** Models of Applied Privacy (MAP) is a privacy threat modeling framework, built on top of LINDDUN and NIST Privacy Risk Assessment Methodology. It uses personas to contextualize threats, by considering potential privacy threats as a combination of threat actor (both malicious and benign), mechanism of attack, and probable impact. To better frame these threats, MAP is accompanied by xCOMPASS, which is an assessment based on these various privacy threat personas. A user may choose to either play the card version, by creating different persona combinations and developing their own threats. They can also use xCOMPASS directly as an assessment to model different privacy threats to their application. For more information on xCOMPASS, please use the link below. -**Additional Notes:** [can include any additional links or resources, describe particular ways in which people can engage with the project, if relevant, etc.] **GitHub User Serving as POC (or Email Address):** spider@comcast.com **Affiliation/Organization(s) Contributing (if relevant):** Comcast ## More information -*Add the link here and remove the below section, "For A Hosted Tool".* **Tool Link:** https://github.com/Comcast/MAP From 4404780b899f714cd9e1d718630867e6726c1598 Mon Sep 17 00:00:00 2001 From: Jayati Date: Fri, 29 Mar 2024 09:10:30 -0400 Subject: [PATCH 52/58] Update README.md --- tools/risk-assessment/Comcast-MAP/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/risk-assessment/Comcast-MAP/README.md b/tools/risk-assessment/Comcast-MAP/README.md index 6cd99a0..9e8e421 100644 --- a/tools/risk-assessment/Comcast-MAP/README.md +++ b/tools/risk-assessment/Comcast-MAP/README.md @@ -7,7 +7,7 @@ **Brief Description:** Models of Applied Privacy (MAP) is a privacy threat modeling framework, built on top of LINDDUN and NIST Privacy Risk Assessment Methodology. It uses personas to contextualize threats, by considering potential privacy threats as a combination of threat actor (both malicious and benign), mechanism of attack, and probable impact. To better frame these threats, MAP is accompanied by xCOMPASS, which is an assessment based on these various privacy threat personas. A user may choose to either play the card version, by creating different persona combinations and developing their own threats. They can also use xCOMPASS directly as an assessment to model different privacy threats to their application. For more information on xCOMPASS, please use the link below. -**GitHub User Serving as POC (or Email Address):** spider@comcast.com +**GitHub User Serving as POC (or Email Address):** @[0spider](https://github.com/0spider), @[devjayati](https://github.com/devjayati/) **Affiliation/Organization(s) Contributing (if relevant):** Comcast From 7e67b15e3b73ae98988bcb79bcec849a84954d99 Mon Sep 17 00:00:00 2001 From: Jayati Date: Wed, 8 May 2024 11:15:45 -0400 Subject: [PATCH 53/58] Update README.md Map ---> xcompass --- tools/risk-assessment/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/risk-assessment/README.md b/tools/risk-assessment/README.md index 2fc1279..cf53999 100644 --- a/tools/risk-assessment/README.md +++ b/tools/risk-assessment/README.md @@ -5,9 +5,9 @@ Contributions are listed in alphabetical order. **[Link to tool](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/risk-assessment/FAIR-Privacy)** -## Models of Applied Privacy (MAP) +## Comcast xCompass -**[Link to tool](https://github.com/Comcast/MAP)** +**[Link to tool](https://github.com/Comcast/xCompass)** ## NIST Privacy Risk Assessment Methodology (PRAM) From 175a9224bf401b81728d63a512da5cf4607e6abc Mon Sep 17 00:00:00 2001 From: Jayati Date: Wed, 8 May 2024 11:29:23 -0400 Subject: [PATCH 54/58] Create README.md --- .../risk-assessment/Comcast-xCompass/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tools/risk-assessment/Comcast-xCompass/README.md diff --git a/tools/risk-assessment/Comcast-xCompass/README.md b/tools/risk-assessment/Comcast-xCompass/README.md new file mode 100644 index 0000000..f77b6ea --- /dev/null +++ b/tools/risk-assessment/Comcast-xCompass/README.md @@ -0,0 +1,17 @@ +# Comcast xCompass + +**Name of Tool:** xCOMPASS + +**Primary Focus Area:** Privacy Threat Modeling + +**Brief Description:** xCOMPASS is a questionnaire developed from Models of Applied Privacy (MAP) personas so that threat modelers can ask specific and targeted questions covering a range of privacy threats. Each question is linked to a persona, built on top of LINDDUN and NIST Privacy Risk Assessment Methodology. xCompass contextualizes threats, by considering potential privacy threats as a combination of threat actor (both malicious and benign), mechanism of attack, and probable impact. Teams can use xCOMPASS directly as an assessment to model different privacy threats to their application. For more information on xCOMPASS, please use the link below. + +**GitHub User Serving as POC (or Email Address):** @[rtrimana](https://github.com/rtrimana), @[0spider](https://github.com/0spider), @[devjayati](https://github.com/devjayati/) + +**Affiliation/Organization(s) Contributing (if relevant):** Comcast + +## More information + +**Tool Link:** [https://github.com/Comcast/xCompass](https://github.com/Comcast/xCompass) + + From bb7f39018e5903ddaf0288a1797a591db9fed325 Mon Sep 17 00:00:00 2001 From: Jayati Date: Wed, 8 May 2024 11:29:55 -0400 Subject: [PATCH 55/58] Delete tools/risk-assessment/Comcast-MAP directory --- tools/risk-assessment/Comcast-MAP/README.md | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 tools/risk-assessment/Comcast-MAP/README.md diff --git a/tools/risk-assessment/Comcast-MAP/README.md b/tools/risk-assessment/Comcast-MAP/README.md deleted file mode 100644 index 9e8e421..0000000 --- a/tools/risk-assessment/Comcast-MAP/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Models of Applied Privacy (MAP) - -**Name of Tool:** MAP (Personas) and xCOMPASS (Questionnaire) - -**Primary Focus Area:** Privacy Threat Modeling - -**Brief Description:** Models of Applied Privacy (MAP) is a privacy threat modeling framework, built on top of LINDDUN and NIST Privacy Risk Assessment Methodology. It uses personas to contextualize threats, by considering potential privacy threats as a combination of threat actor (both malicious and benign), mechanism of attack, and probable impact. To better frame these threats, MAP is accompanied by xCOMPASS, which is an assessment based on these various privacy threat personas. A user may choose to either play the card version, by creating different persona combinations and developing their own threats. They can also use xCOMPASS directly as an assessment to model different privacy threats to their application. For more information on xCOMPASS, please use the link below. - - -**GitHub User Serving as POC (or Email Address):** @[0spider](https://github.com/0spider), @[devjayati](https://github.com/devjayati/) - -**Affiliation/Organization(s) Contributing (if relevant):** Comcast - -## More information - -**Tool Link:** https://github.com/Comcast/MAP From e891ea93ca09cdbd379bcaa5bf96150653e2fc54 Mon Sep 17 00:00:00 2001 From: Rahmadi Trimananda Date: Mon, 9 Jun 2025 11:46:02 -0400 Subject: [PATCH 56/58] Add CARAF as a risk-assessment tool. --- tools/risk-assessment/CARAF/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tools/risk-assessment/CARAF/README.md diff --git a/tools/risk-assessment/CARAF/README.md b/tools/risk-assessment/CARAF/README.md new file mode 100644 index 0000000..936f09a --- /dev/null +++ b/tools/risk-assessment/CARAF/README.md @@ -0,0 +1,15 @@ +# Comcast CARAF + +**Name of Tool:** CARAF (Crypto Agility Risk Assessment Framework) + +**Primary Focus Area:** Post-Quantum Cryptography (PQC) Risk Assessment + +**Brief Description:** Crypto Agility Risk Assessment Framework (CARAF) is a framework tailored for organizations seeking to evaluate the risk of PQC to their assets, understand where to start with migration, prioritize their assets for PQC migration, and receive technical guidance or resources to enable the PQC migration. Crypto agility refers to the ability to replace existing crypto primitives, algorithms, or protocols quickly, inexpensively, and with minimal risk of exposure and business overhead. Transitioning from one crypto solution to another can be time-consuming and expose organizations to unnecessary security risks. Therefore, CARAF can help organizations perform risk-based assessment and determine appropriate mitigation strategies tailored to their risk tolerance. + +**GitHub User Serving as POC (or Email Address):** @[rtrimana](https://github.com/rtrimana), @[bahmanrashidi](https://github.com/bahmanrashidi) + +**Affiliation/Organization(s) Contributing (if relevant):** Comcast + +## More information + +**Tool Link:** [https://github.com/Comcast/CARAF](https://github.com/Comcast/CARAF) From 1dfe818e3fe81c9f03ce061f492d0a15f7694c67 Mon Sep 17 00:00:00 2001 From: Rahmadi Trimananda Date: Mon, 9 Jun 2025 11:47:22 -0400 Subject: [PATCH 57/58] Rename CARAF to Comcast-CARAF. --- tools/risk-assessment/{CARAF => Comcast-CARAF}/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/risk-assessment/{CARAF => Comcast-CARAF}/README.md (100%) diff --git a/tools/risk-assessment/CARAF/README.md b/tools/risk-assessment/Comcast-CARAF/README.md similarity index 100% rename from tools/risk-assessment/CARAF/README.md rename to tools/risk-assessment/Comcast-CARAF/README.md From 9dcb2aa3421fb22be610c317767abc385d07173d Mon Sep 17 00:00:00 2001 From: Rahmadi Trimananda Date: Mon, 9 Jun 2025 11:50:20 -0400 Subject: [PATCH 58/58] Update README.md Add link to Comcast CARAF. --- tools/risk-assessment/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/risk-assessment/README.md b/tools/risk-assessment/README.md index cf53999..2073411 100644 --- a/tools/risk-assessment/README.md +++ b/tools/risk-assessment/README.md @@ -5,6 +5,10 @@ Contributions are listed in alphabetical order. **[Link to tool](https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/risk-assessment/FAIR-Privacy)** +## Comcast CARAF + +**[Link to tool](https://github.com/Comcast/CARAF)** + ## Comcast xCompass **[Link to tool](https://github.com/Comcast/xCompass)**

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 z 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 literal 0 HcmV?d00001 diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_writeup_and_privacy_proof.odt b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_writeup_and_privacy_proof.odt new file mode 100644 index 0000000000000000000000000000000000000000..01a8061c6fb3000d6292411e8e73f86d961121b0 GIT binary patch literal 25079 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 aT1@_u>aVC05#z*Yv44-TnM=eBclLkH#m$lc literal 0 HcmV?d00001 diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_writeup_and_privacy_proof.pdf b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/gardn999_NistDp3_writeup_and_privacy_proof.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8f212de10886f0db463874d3c25ebb7b73c6ac14 GIT binary patch literal 63251 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 literal 0 HcmV?d00001 diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/run_docker.sh b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/run_docker.sh new file mode 100644 index 0000000..aa4fa15 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/run_docker.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# arguments: +# $1 +# $2 +# $3 +# $4 +# $5 +# $6 +# $7 [delta limit] +docker build -t gardn999_nistdp3 ./solution + +if [ $# = 6 ] || [ $# = 7 ] +then +docker run -v $1:/input -v $2:/output gardn999_nistdp3 /input/$3 /input/$4 /output/$5 $6 +elif [ $# = 4 ] +then +docker run -v $1:/input -v $2:/output gardn999_nistdp3 /input/$3 /input/$4 /output/8_0.csv 8.0 +docker run -v $1:/input -v $2:/output gardn999_nistdp3 /input/$3 /input/$4 /output/1_0.csv 1.0 +docker run -v $1:/input -v $2:/output gardn999_nistdp3 /input/$3 /input/$4 /output/0_3.csv 0.3 +else +docker run -v $1:/input -v $2:/output gardn999_nistdp3 +fi \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/Dockerfile b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/Dockerfile new file mode 100644 index 0000000..cc54a6a --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/Dockerfile @@ -0,0 +1,25 @@ + +FROM ubuntu:16.04 + +## Install General Requirements +RUN apt-get update && \ + apt-get install -y openjdk-8-jdk && \ + apt-get install -y --no-install-recommends \ + apt-utils \ + build-essential \ + cmake \ + git \ + wget \ + nano \ + software-properties-common + +WORKDIR /x + +# copy entire directory where docker file is into docker container at /work +COPY . /x/ + +RUN chmod 777 run.sh +RUN chmod 777 compile.sh +RUN sh compile.sh + +ENTRYPOINT [ "/x/run.sh" ] diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/codebook.cbk b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/codebook.cbk new file mode 100644 index 0000000..682369f --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/codebook.cbk @@ -0,0 +1,8629 @@ +Description: 1940 Full Extract +Samples selected: + 1940 100% database + Note: Preliminary data release. + Density of the full data file: 100.0% + Density of your extract: 100.0% + +File Type: hierarchical +Case Selection: No + Variable Columns Len 1940 + RECTYPE H 1 1 X + YEAR H 2-5 4 X + DATANUM H 6-7 2 X + SERIAL H 8-15 8 X + NUMPREC H 16-17 2 X + SUBSAMP H 18-19 2 X + HHWT H 20-29 10 X + NUMPERHH H 30-33 4 X + HHTYPE H 34 1 X + DWELLING H 35-42 8 X + SLPERNUM H 43-44 2 X + CPI99 H 45-49 5 X + REGION H 50-51 2 X + STATEICP H 52-53 2 X + STATEFIP H 54-55 2 X + COUNTY H 56-59 4 X + URBAN H 60 1 X + METRO H 61 1 X + METAREA H 62-64 3 X + METAREAD H 65-68 4 X + CITY H 69-72 4 X + CITYPOP H 73-77 5 X + SIZEPL H 78-79 2 X + URBPOP H 80-84 5 X + SEA H 85-87 3 X + WARD H 88-90 3 X + CNTRY H 91-93 3 X + GQ H 94 1 X + GQTYPE H 95 1 X + GQTYPED H 96-98 3 X + GQFUNDS H 99-100 2 X + FARM H 101 1 X + OWNERSHP H 102 1 X + OWNERSHPD H 103-104 2 X + RENT H 105-108 4 X + VALUEH H 109-115 7 X + NFAMS H 116-117 2 X + NSUBFAM H 118 1 X + NCOUPLES H 119 1 X + NMOTHERS H 120 1 X + NFATHERS H 121 1 X + MULTGEN H 122 1 X + MULTGEND H 123-124 2 X + ENUMDIST H 125-128 4 X + SUPDIST H 129-131 3 X + RESPOND H 132 1 X + SPLIT H 133 1 X + SPLITHID H 134-141 8 X + SPLITNUM H 142-145 4 X + SPLIT40 H 146 1 X + SERIAL40 H 147-154 8 X + NUMPREC40 H 155-158 4 X + EDMISS H 159 1 X + RECTYPEP P 1 1 X + YEARP P 2-5 4 X + DATANUMP P 6-7 2 X + SERIALP P 8-15 8 X + PERNUM P 16-19 4 X + PERWT P 20-29 10 X + SLWT P 30-39 10 X + SLREC P 40 1 X + RESPONDT P 41 1 X + FAMUNIT P 42-43 2 X + FAMSIZE P 44-45 2 X + SUBFAM P 46 1 X + SFTYPE P 47 1 X + SFRELATE P 48 1 X + MOMLOC P 49-50 2 X + STEPMOM P 51 1 X + MOMRULE_HIST P 52 1 X + POPLOC P 53-54 2 X + STEPPOP P 55 1 X + POPRULE_HIST P 56 1 X + SPLOC P 57-58 2 X + SPRULE_HIST P 59 1 X + NCHILD P 60 1 X + NCHLT5 P 61 1 X + NSIBS P 62 1 X + ELDCH P 63-64 2 X + YNGCH P 65-66 2 X + RELATE P 67-68 2 X + RELATED P 69-72 4 X + SEX P 73 1 X + AGE P 74-76 3 X + AGEMONTH P 77-78 2 X + MARST P 79 1 X + MARRNO P 80 1 X + AGEMARR P 81-82 2 X + CHBORN P 83-84 2 X + RACE P 85 1 X + RACED P 86-88 3 X + HISPAN P 89 1 X + HISPAND P 90-92 3 X + BPL P 93-95 3 X + BPLD P 96-100 5 X + MBPL P 101-103 3 X + MBPLD P 104-108 5 X + FBPL P 109-111 3 X + FBPLD P 112-116 5 X + NATIVITY P 117 1 X + CITIZEN P 118 1 X + MTONGUE P 119-120 2 X + MTONGUED P 121-124 4 X + SPANNAME P 125 1 X + HISPRULE P 126 1 X + SCHOOL P 127 1 X + HIGRADE P 128-129 2 X + HIGRADED P 130-132 3 X + EDUC P 133-134 2 X + EDUCD P 135-137 3 X + EMPSTAT P 138 1 X + EMPSTATD P 139-140 2 X + LABFORCE P 141 1 X + OCC P 142-145 4 X + OCC1950 P 146-148 3 X + IND P 149-152 4 X + IND1950 P 153-155 3 X + CLASSWKR P 156 1 X + CLASSWKRD P 157-158 2 X + WKSWORK1 P 159-160 2 X + WKSWORK2 P 161 1 X + HRSWORK1 P 162-163 2 X + HRSWORK2 P 164 1 X + DURUNEMP P 165-167 3 X + UOCC P 168-170 3 X + UOCC95 P 171-173 3 X + UIND P 174-176 3 X + UCLASSWK P 177 1 X + INCWAGE P 178-183 6 X + INCNONWG P 184 1 X + OCCSCORE P 185-186 2 X + SEI P 187-188 2 X + PRESGL P 189-191 3 X + ERSCOR50 P 192-195 4 X + EDSCOR50 P 196-199 4 X + NPBOSS50 P 200-203 4 X + MIGRATE5 P 204 1 X + MIGRATE5D P 205-206 2 X + MIGPLAC5 P 207-209 3 X + MIGMET5 P 210-213 4 X + MIGTYPE5 P 214 1 X + MIGCITY5 P 215-218 4 X + MIGSEA5 P 219-221 3 X + SAMEPLAC P 222 1 X + SAMESEA5 P 223 1 X + MIGCOUNTY P 224-227 4 X + VETSTAT P 228 1 X + VETSTATD P 229-230 2 X + VET1940 P 231 1 X + VETWWI P 232 1 X + VETPER P 233 1 X + VETCHILD P 234 1 X + HISTID P 235-270 36 X + SURSIM P 271-272 2 X + SSENROLL P 273 1 X + +Variable Availability Key: +All Years X - available in this sample +All Years . - not available in this sample + + NUMPREC Number of person records following +00 Vacant household +01 1 person record +02 2 +03 3 +04 4 +05 5 +06 6 +07 7 +08 8 +09 9 +10 10 +11 11 +12 12 +13 13 +14 14 +15 15 +16 16 +17 17 +18 18 +19 19 +20 20 +21 21 +22 22 +23 23 +24 24 +25 25 +26 26 +27 27 +28 28 +29 29 +30 30 + + SUBSAMP Subsample number +00 First 1% subsample +01 2nd 1% subsample +02 2 +03 3 +04 4 +05 5 +06 6 +07 7 +08 8 +09 9 +10 10 +11 11 +12 12 +13 13 +14 14 +15 15 +16 16 +17 17 +18 18 +19 19 +20 20 +21 21 +22 22 +23 23 +24 24 +25 25 +26 26 +27 27 +28 28 +29 29 +30 30 +31 31 +32 32 +33 33 +34 34 +35 35 +36 36 +37 37 +38 38 +39 39 +40 40 +41 41 +42 42 +43 43 +44 44 +45 45 +46 46 +47 47 +48 48 +49 49 +50 50 +51 51 +52 52 +53 53 +54 54 +55 55 +56 56 +57 57 +58 58 +59 59 +60 60 +61 61 +62 62 +63 63 +64 64 +65 65 +66 66 +67 67 +68 68 +69 69 +70 70 +71 71 +72 72 +73 73 +74 74 +75 75 +76 76 +77 77 +78 78 +79 79 +80 80 +81 81 +82 82 +83 83 +84 84 +85 85 +86 86 +87 87 +88 88 +89 89 +90 90 +91 91 +92 92 +93 93 +94 94 +95 95 +96 96 +97 97 +98 98 +99 99 + + HHTYPE Household Type +0 N/A + Family Households: +1 Married-couple family household +2 Male householder, no wife present +3 Female householder, no husband present + Nonfamily Households: +4 Male householder, living alone +5 Male householder, not living alone +6 Female householder, living alone +7 Female householder, not living alone +9 HHTYPE could not be determined + + REGION Census region and division + NORTHEAST REGION +11 New England Division +12 Middle Atlantic Division +13 Mixed Northeast Divisions (1970 Metro) + MIDWEST REGION +21 East North Central Div. +22 West North Central Div. +23 Mixed Midwest Divisions (1970 Metro) + SOUTH REGION +31 South Atlantic Division +32 East South Central Div. +33 West South Central Div. +34 Mixed Southern Divisions (1970 Metro) + WEST REGION +41 Mountain Division +42 Pacific Division +43 Mixed Western Divisions (1970 Metro) + STATE UNKNOWN +91 Military/Military reservations +92 PUMA boundaries cross state lines-1% sample +97 State not identified +99 Not identified + + STATEICP State (ICPSR code) +01 Connecticut +02 Maine +03 Massachusetts +04 New Hampshire +05 Rhode Island +06 Vermont +11 Delaware +12 New Jersey +13 New York +14 Pennsylvania +21 Illinois +22 Indiana +23 Michigan +24 Ohio +25 Wisconsin +31 Iowa +32 Kansas +33 Minnesota +34 Missouri +35 Nebraska +36 North Dakota +37 South Dakota +40 Virginia +41 Alabama +42 Arkansas +43 Florida +44 Georgia +45 Louisiana +46 Mississippi +47 North Carolina +48 South Carolina +49 Texas +51 Kentucky +52 Maryland +53 Oklahoma +54 Tennessee +56 West Virginia +61 Arizona +62 Colorado +63 Idaho +64 Montana +65 Nevada +66 New Mexico +67 Utah +68 Wyoming +71 California +72 Oregon +73 Washington +81 Alaska +82 Hawaii +83 Puerto Rico +96 State groupings (1980 Urban/rural sample) +97 Military/Mil. Reservations +98 District of Columbia +99 State not identified + + STATEFIP State (FIPS code) +01 Alabama +02 Alaska +04 Arizona +05 Arkansas +06 California +08 Colorado +09 Connecticut +10 Delaware +11 District of Columbia +12 Florida +13 Georgia +15 Hawaii +16 Idaho +17 Illinois +18 Indiana +19 Iowa +20 Kansas +21 Kentucky +22 Louisiana +23 Maine +24 Maryland +25 Massachusetts +26 Michigan +27 Minnesota +28 Mississippi +29 Missouri +30 Montana +31 Nebraska +32 Nevada +33 New Hampshire +34 New Jersey +35 New Mexico +36 New York +37 North Carolina +38 North Dakota +39 Ohio +40 Oklahoma +41 Oregon +42 Pennsylvania +44 Rhode Island +45 South Carolina +46 South Dakota +47 Tennessee +48 Texas +49 Utah +50 Vermont +51 Virginia +53 Washington +54 West Virginia +55 Wisconsin +56 Wyoming + State groups (1980 Urban/rural sample): +61 Maine-New Hampshire-Vermont +62 Massachusetts-Rhode Island +63 Minnesota-Iowa-Missouri-Kansas-Nebraska-S.Dakota-N.Dakota +64 Maryland-Delaware +65 Montana-Idaho-Wyoming +66 Utah-Nevada +67 Arizona-New Mexico +68 Alaska-Hawaii +72 Puerto Rico +97 Military/Mil. Reservation +99 State not identified + + URBAN Urban/rural status +0 N/A +1 Rural +2 Urban + + METRO Metropolitan status +0 Not identifiable +1 Not in metro area + In metro area: +2 In metro area, central / principal city +3 In metro area, outside central / principal city +4 Central / Principal city status unknown + + METAREA Metropolitan area [general version] +000 Not identifiable or not in an MSA +004 Abilene, TX +006 Aguadilla, PR +008 Akron, OH +012 Albany, GA +016 Albany-Schenectady-Troy, NY +020 Albuquerque, NM +022 Alexandria, LA +024 Allentown-Bethlehem-Easton, PA/NJ +028 Altoona, PA +032 Amarillo, TX +038 Anchorage, AK +040 Anderson, IN +044 Ann Arbor, MI +045 Anniston, AL +046 Appleton-Oshkosh-Neenah, WI +047 Arecibo, PR +048 Asheville, NC +050 Athens, GA +052 Atlanta, GA +056 Atlantic City, NJ +058 Auburn-Opekika, AL +060 Augusta-Aiken, GA/SC +064 Austin, TX +068 Bakersfield, CA +072 Baltimore, MD +073 Bangor, ME +074 Barnstable-Yarmouth, MA +076 Baton Rouge, LA +078 Battle Creek, MI +084 Beaumont-Port Arthur-Orange, TX +086 Bellingham, WA +087 Benton Harbor, MI +088 Billings, MT +092 Biloxi-Gulfport, MS +096 Binghamton, NY +100 Birmingham, AL +102 Bloomington, IN +104 Bloomington-Normal, IL +108 Boise City, ID +112 Boston, MA/NH +114 Bradenton, FL +115 Bremerton, WA +116 Bridgeport, CT +120 Brockton, MA +124 Brownsville-Harlingen-San Benito, TX +126 Bryan-College Station, TX +128 Buffalo-Niagara Falls, NY +130 Burlington, NC +131 Burlington, VT +132 Canton, OH +133 Caguas, PR +135 Casper, WY +136 Cedar Rapids, IA +140 Champaign-Urbana-Rantoul, IL +144 Charleston-N. Charleston, SC +148 Charleston, WV +152 Charlotte-Gastonia-Rock Hill, NC/SC +154 Charlottesville, VA +156 Chattanooga, TN/GA +158 Cheyenne, WY +160 Chicago, IL +162 Chico, CA +164 Cincinnati-Hamilton, OH/KY/IN +166 Clarksville- Hopkinsville, TN/KY +168 Cleveland, OH +172 Colorado Springs, CO +174 Columbia, MO +176 Columbia, SC +180 Columbus, GA/AL +184 Columbus, OH +188 Corpus Christi, TX +190 Cumberland, MD/WV +192 Dallas-Fort Worth, TX +193 Danbury, CT +195 Danville, VA +196 Davenport, IA - Rock Island-Moline, IL +200 Dayton-Springfield, OH +202 Daytona Beach, FL +203 Decatur, AL +204 Decatur, IL +208 Denver-Boulder, CO +212 Des Moines, IA +216 Detroit, MI +218 Dothan, AL +219 Dover, DE +220 Dubuque, IA +224 Duluth-Superior, MN/WI +228 Dutchess Co., NY +229 Eau Claire, WI +231 El Paso, TX +232 Elkhart-Goshen, IN +233 Elmira, NY +234 Enid, OK +236 Erie, PA +240 Eugene-Springfield, OR +244 Evansville, IN/KY +252 Fargo-Morehead, ND/MN +256 Fayetteville, NC +258 Fayetteville-Springdale, AR +260 Fitchburg-Leominster, MA +262 Flagstaff, AZ/UT +264 Flint, MI +265 Florence, AL +266 Florence, SC +267 Fort Collins-Loveland, CO +268 Fort Lauderdale-Hollywood-Pompano Beach, FL +270 Fort Myers-Cape Coral, FL +271 Fort Pierce, FL +272 Fort Smith, AR/OK +275 Fort Walton Beach, FL +276 Fort Wayne, IN +284 Fresno, CA +288 Gadsden, AL +290 Gainesville, FL +292 Galveston-Texas City, TX +297 Glens Falls, NY +298 Goldsboro, NC +299 Grand Forks, ND +300 Grand Rapids, MI +301 Grand Junction, CO +304 Great Falls, MT +306 Greeley, CO +308 Green Bay, WI +312 Greensboro-Winston Salem-High Point, NC +315 Greenville, NC +316 Greenville-Spartenburg-Anderson, SC +318 Hagerstown, MD +320 Hamilton-Middleton, OH +324 Harrisburg-Lebanon--Carlisle, PA +328 Hartford-Bristol-Middleton- New Britain, CT +329 Hickory-Morganton, NC +330 Hattiesburg, MS +332 Honolulu, HI +335 Houma-Thibodoux, LA +336 Houston-Brazoria, TX +340 Huntington-Ashland, WV/KY/OH +344 Huntsville, AL +348 Indianapolis, IN +350 Iowa City, IA +352 Jackson, MI +356 Jackson, MS +358 Jackson, TN +359 Jacksonville, FL +360 Jacksonville, NC +361 Jamestown-Dunkirk, NY +362 Janesville-Beloit, WI +366 Johnson City-Kingsport--Bristol, TN/VA +368 Johnstown, PA +371 Joplin, MO +372 Kalamazoo-Portage, MI +374 Kankakee, IL +376 Kansas City, MO/KS +380 Kenosha, WI +381 Kileen-Temple, TX +384 Knoxville, TN +385 Kokomo, IN +387 LaCrosse, WI +388 Lafayette, LA +392 Lafayette-W. Lafayette, IN +396 Lake Charles, LA +398 Lakeland-Winterhaven, FL +400 Lancaster, PA +404 Lansing-E. Lansing, MI +408 Laredo, TX +410 Las Cruces, NM +412 Las Vegas, NV +415 Lawrence, KS +420 Lawton, OK +424 Lewiston-Auburn, ME +428 Lexington-Fayette, KY +432 Lima, OH +436 Lincoln, NE +440 Little Rock-N. Little Rock, AR +441 Long Branch-Asbury Park, NJ +442 Longview-Marshall, TX +444 Lorain-Elyria, OH +448 Los Angeles-Long Beach, CA +452 Louisville, KY/IN +460 Lubbock, TX +464 Lynchburg, VA +468 Macon-Warner Robins, GA +472 Madison, WI +476 Manchester, NH +480 Mansfield, OH +484 Mayaguez, PR +488 McAllen-Edinburg-Pharr-Mission, TX +489 Medford, OR +490 Melbourne-Titusville-Cocoa-Palm Bay, FL +492 Memphis, TN/AR/MS +494 Merced, CA +500 Miami-Hialeah, FL +504 Midland, TX +508 Milwaukee, WI +512 Minneapolis-St. Paul, MN +514 Missoula, MT +516 Mobile, AL +517 Modesto, CA +519 Monmouth-Ocean, NJ +520 Monroe, LA +524 Montgomery, AL +528 Muncie, IN +532 Muskegon-Norton Shores-Muskegon Heights, MI +533 Myrtle Beach, SC +534 Naples, FL +535 Nashua, NH +536 Nashville, TN +540 New Bedford, MA +546 New Brunswick-Perth Amboy-Sayreville, NJ +548 New Haven-Meriden, CT +552 New London-Norwich, CT/RI +556 New Orleans, LA +560 New York, NY-Northeastern NJ +564 Newark, OH +566 Newburgh-Middletown, NY +572 Norfolk-VA Beach--Newport News, VA +576 Norwalk, CT +579 Ocala, FL +580 Odessa, TX +588 Oklahoma City, OK +591 Olympia, WA +592 Omaha, NE/IA +595 Orange, NY +596 Orlando, FL +599 Owensboro, KY +601 Panama City, FL +602 Parkersburg-Marietta,WV/OH +603 Pascagoula-Moss Point, MS +608 Pensacola, FL +612 Peoria, IL +616 Philadelphia, PA/NJ +620 Phoenix, AZ +628 Pittsburgh, PA +632 Pittsfield, MA +636 Ponce, PR +640 Portland, ME +644 Portland, OR/WA +645 Portsmouth-Dover--Rochester, NH/ME +646 Poughkeepsie, NY +648 Providence-Fall River-Pawtucket, MA/RI +652 Provo-Orem, UT +656 Pueblo, CO +658 Punta Gorda, FL +660 Racine, WI +664 Raleigh-Durham, NC +666 Rapid City, SD +668 Reading, PA +669 Redding, CA +672 Reno, NV +674 Richland-Kennewick-Pasco, WA +676 Richmond-Petersburg, VA +678 Riverside-San Bernardino, CA +680 Roanoke, VA +682 Rochester, MN +684 Rochester, NY +688 Rockford, IL +689 Rocky Mount, NC +692 Sacramento, CA +696 Saginaw-Bay City-Midland, MI +698 St. Cloud, MN +700 St. Joseph, MO +704 St. Louis, MO/IL +708 Salem, OR +712 Salinas-Sea Side-Monterey, CA +714 Salisbury-Concord, NC +716 Salt Lake City-Ogden, UT +720 San Angelo, TX +724 San Antonio, TX +732 San Diego, CA +736 San Francisco-Oakland-Vallejo, CA +740 San Jose, CA +744 San Juan-Bayamon, PR +746 San Luis Obispo-Atascad-P Robles, CA +747 Santa Barbara-Santa Maria-Lompoc, CA +748 Santa Cruz, CA +749 Santa Fe, NM +750 Santa Rosa-Petaluma, CA +751 Sarasota, FL +752 Savannah, GA +756 Scranton-Wilkes-Barre, PA +760 Seattle-Everett, WA +761 Sharon, PA +762 Sheboygan, WI +764 Sherman-Davidson, TX +768 Shreveport, LA +772 Sioux City, IA/NE +776 Sioux Falls, SD +780 South Bend-Mishawaka, IN +784 Spokane, WA +788 Springfield, IL +792 Springfield, MO +800 Springfield-Holyoke-Chicopee, MA +804 Stamford, CT +805 State College, PA +808 Steubenville-Weirton,OH/WV +812 Stockton, CA +814 Sumter, SC +816 Syracuse, NY +820 Tacoma, WA +824 Tallahassee, FL +828 Tampa-St. Petersburg-Clearwater, FL +832 Terre Haute, IN +836 Texarkana, TX/AR +840 Toledo, OH/MI +844 Topeka, KS +848 Trenton, NJ +852 Tucson, AZ +856 Tulsa, OK +860 Tuscaloosa, AL +864 Tyler, TX +868 Utica-Rome, NY +873 Ventura-Oxnard-Simi Valley, CA +875 Victoria, TX +876 Vineland-Milville-Bridgetown, NJ +878 Visalia-Tulare-Porterville, CA +880 Waco, TX +884 Washington, DC/MD/VA +888 Waterbury, CT +892 Waterloo-Cedar Falls, IA +894 Wausau, WI +896 West Palm Beach-Boca Raton-Delray Beach, FL +900 Wheeling, WV/OH +904 Wichita, KS +908 Wichita Falls, TX +914 Williamsport, PA +916 Wilmington, DE/NJ/MD +920 Wilmington, NC +924 Worcester, MA +926 Yakima, WA +927 Yolo, CA +928 York, PA +932 Youngstown-Warren, OH/PA +934 Yuba City, CA +936 Yuma, AZ + + METAREAD Metropolitan area [detailed version] +0000 Not identifiable or not in an MSA +0040 Abilene, TX +0060 Aguadilla, PR +0080 Akron, OH +0120 Albany, GA +0160 Albany-Schenectady-Troy, NY +0200 Albuquerque, NM +0220 Alexandria, LA +0240 Allentown-Bethlehem-Easton, PA/NJ +0280 Altoona, PA +0320 Amarillo, TX + Anaheim-Santa Ana, CA (See Los Angeles) +0380 Anchorage, AK +0400 Anderson, IN + Anderson, SC (See Greenville) +0440 Ann Arbor, MI +0450 Anniston, AL +0460 Appleton-Oshkosh-Neenah, WI +0470 Arecibo, PR +0480 Asheville, NC +0500 Athens, GA +0520 Atlanta, GA +0560 Atlantic City, NJ +0580 Auburn-Opelika, AL +0600 Augusta-Aiken, GA/SC + Aurora-Elgin, IL (See Chicago) +0640 Austin, TX +0680 Bakersfield, CA +0720 Baltimore, MD +0730 Bangor, ME +0740 Barnstable-Yarmouth, MA +0760 Baton Rouge, LA +0780 Battle Creek, MI + Bay City, MI (See Saginaw) +0840 Beaumont-Port Arthur-Orange, TX + Beaver County, PA (See Pittsburgh) +0860 Bellingham, WA +0870 Benton Harbor, MI + Bergen-Passaic, NY (See New York) +0880 Billings, MT +0920 Biloxi-Gulfport, MS +0960 Binghamton, NY +1000 Birmingham, AL +1010 Bismarck, ND +1020 Bloomington, IN +1040 Bloomington-Normal, IL +1080 Boise City, ID +1120 Boston, MA +1121 Lawrence-Haverhill, MA/NH +1122 Lowell, MA/NH +1123 Salem-Gloucester, MA + Boulder, CO (See Denver) +1140 Bradenton, FL + Brazoria, TX (See Houston) +1150 Bremerton, WA +1160 Bridgeport, CT + Bristol, CT (See Hartford) +1200 Brockton, MA +1240 Brownsville-Harlingen-San Benito, TX +1260 Bryan-College Station, TX +1280 Buffalo-Niagara Falls, NY +1281 Niagara Falls, NY +1300 Burlington, NC +1310 Burlington, VT +1320 Canton, OH +1330 Caguas, PR +1350 Casper, WY +1360 Cedar Rapids, IA +1400 Champaign-Urbana-Rantoul, IL +1440 Charleston-N. Charleston, SC +1480 Charleston, WV +1520 Charlotte-Gastonia-Rock Hill, SC +1521 Rock Hill, SC +1540 Charlottesville, VA +1560 Chattanooga, TN/GA +1580 Cheyenne, WY +1600 Chicago-Gary-Lake, IL +1601 Aurora-Elgin, IL +1602 Gary-Hammond-East Chicago, IN +1603 Joliet, IL +1604 Lake County, IL +1620 Chico, CA +1640 Cincinnati, OH/KY/IN +1660 Clarksville-Hopkinsville, TN/KY +1680 Cleveland, OH +1720 Colorado Springs, CO +1740 Columbia, MO +1760 Columbia, SC +1800 Columbus, GA/AL +1840 Columbus, OH +1880 Corpus Christi, TX +1900 Cumberland, MD/WV +1920 Dallas-Fort Worth, TX +1921 Fort Worth-Arlington, TX +1930 Danbury, CT +1950 Danville, VA +1960 Davenport, IA - Rock Island-Moline, IL +2000 Dayton-Springfield, OH +2001 Springfield, OH +2020 Daytona Beach, FL +2030 Decatur, AL +2040 Decatur, IL +2080 Denver-Boulder-Longmont, CO +2081 Boulder-Longmont, CO +2120 Des Moines, IA +2121 Polk, IA +2160 Detroit, MI +2180 Dothan, AL +2190 Dover, DE +2200 Dubuque, IA +2240 Duluth-Superior, MN/WI +2281 Dutchess Co., NY +2290 Eau Claire, WI +2310 El Paso, TX +2320 Elkhart-Goshen, IN +2330 Elmira, NY +2340 Enid, OK +2360 Erie, PA +2400 Eugene-Springfield, OR +2440 Evansville, IN/KY + Fall River, MA (See Providence) +2520 Fargo-Morehead, ND/MN +2560 Fayetteville, NC +2580 Fayetteville-Springdale, AR +2600 Fitchburg-Leominster, MA +2620 Flagstaff, AZ/UT +2640 Flint, MI +2650 Florence, AL +2660 Florence, SC +2670 Fort Collins-Loveland, CO +2680 Fort Lauderdale-Hollywood-Pompano Beach, FL +2700 Fort Myers-Cape Coral, FL +2710 Fort Pierce, FL +2720 Fort Smith, AR/OK +2750 Fort Walton Beach, FL +2760 Fort Wayne, IN + Fort Worth, TX (See Dallas) +2840 Fresno, CA +2880 Gadsden, AL +2900 Gainesville, FL +2920 Galveston-Texas City, TX + Gary, IN (See Chicago) +2970 Glens Falls, NY +2980 Goldsboro, NC +2990 Grand Forks, ND/MN +3000 Grand Rapids, MI +3010 Grand Junction, CO +3040 Great Falls, MT +3060 Greeley, CO +3080 Green Bay, WI +3120 Greensboro-Winston Salem-High Point, NC +3121 Winston-Salem, NC +3150 Greenville, NC +3160 Greenville-Spartenburg-Anderson, SC +3161 Anderson, SC +3180 Hagerstown, MD +3200 Hamilton-Middleton, OH +3240 Harrisburg-Lebanon-Carlisle, PA +3280 Hartford-Bristol-Middleton-New Britain, CT +3281 Bristol, CT +3282 Middletown, CT +3283 New Britain, CT +3290 Hickory-Morganton, NC +3300 Hattiesburg, MS +3320 Honolulu, HI +3350 Houma-Thibodoux, LA +3360 Houston-Brazoria, TX +3361 Brazoria, TX +3400 Huntington-Ashland, WV/KY/OH +3440 Huntsville, AL +3480 Indianapolis, IN +3500 Iowa City, IA +3520 Jackson, MI +3560 Jackson, MS +3580 Jackson, TN +3590 Jacksonville, FL +3600 Jacksonville, NC +3610 Jamestown-Dunkirk, NY +3620 Janesville-Beloit, WI + Jersey City, NJ (See New York) +3660 Johnson City-Kingsport-Bristol, TN/VA +3680 Johnstown, PA + Joliet, IL (See Chicago) +3710 Joplin, MO +3720 Kalamazoo-Portage, MI +3740 Kankakee, IL +3760 Kansas City, MO/KS +3800 Kenosha, WI +3810 Kileen-Temple, TX +3840 Knoxville, TN +3850 Kokomo, IN +3870 LaCrosse, WI +3880 Lafayette, LA +3920 Lafayette-W. Lafayette, IN +3960 Lake Charles, LA + Lake County, IL (See Chicago) +3980 Lakeland-Winterhaven, FL +4000 Lancaster, PA +4040 Lansing-E. Lansing, MI +4080 Laredo, TX +4100 Las Cruces, NM +4120 Las Vegas, NV +4150 Lawrence, KS + Lawrence-Haverhill, MA (See Boston) +4200 Lawton, OK +4240 Lewiston-Auburn, ME +4280 Lexington-Fayette, KY +4320 Lima, OH +4360 Lincoln, NE +4400 Little Rock-N. Little Rock, AR +4410 Long Branch-Asbury Park, NJ +4420 Longview-Marshall, TX +4440 Lorain-Elyria, OH +4480 Los Angeles-Long Beach, CA +4481 Anaheim-Santa Ana-Garden Grove, CA +4482 Orange County, CA +4520 Louisville, KY/IN + Lowell, MA (See Boston) +4600 Lubbock, TX +4640 Lynchburg, VA +4680 Macon-Warner Robins, GA +4720 Madison, WI +4760 Manchester, NH +4800 Mansfield, OH +4840 Mayaguez, PR +4880 McAllen-Edinburg-Pharr-Mission, TX +4890 Medford, OR +4900 Melbourne-Titusville-Cocoa-Palm Bay, FL +4920 Memphis, TN/AR/MS +4940 Merced, CA +5000 Miami-Hialeah, FL + Middlesex-Somerset-Hunterdon (See New York) + Middleton, CT (See Hartford) +5040 Midland, TX +5080 Milwaukee, WI +5120 Minneapolis-St. Paul, MN +5140 Missoula, MT +5160 Mobile, AL +5170 Modesto, CA +5190 Monmouth-Ocean, NJ +5200 Monroe, LA +5240 Montgomery, AL +5280 Muncie, IN +5320 Muskegon-Norton Shores-Muskegon Heights, MI +5330 Myrtle Beach, SC +5340 Naples, FL +5350 Nashua, NH +5360 Nashville, TN + Nassau-Suffolk, NY (See New York) +5400 New Bedford, MA + New Britian, CT (See Hartford) +5460 New Brunswick-Perth Amboy-Sayreville, NJ +5480 New Haven-Meriden, CT +5481 Meriden +5482 New Haven, CT +5520 New London-Norwich, CT/RI +5560 New Orleans, LA +5600 New York, NY-Northeastern NJ +5601 Nassau Co, NY +5602 Bergen-Passaic, NJ +5603 Jersey City, NJ +5604 Middlesex-Somerset-Hunterdon, NJ +5605 Newark, NJ +5640 Newark, OH +5660 Newburgh-Middletown, NY + Niagara Falls, NY (See Buffalo) +5720 Norfolk-VA Beach-Newport News, VA +5721 Newport News-Hampton +5722 Norfolk- VA Beach-Portsmouth +5760 Norwalk, CT + Oakland, CA (See San Francisco) +5790 Ocala, FL +5800 Odessa, TX + Ogden, UT (See Salt Lake) +5880 Oklahoma City, OK +5910 Olympia, WA +5920 Omaha, NE/IA +5950 Orange, NY +5960 Orlando, FL +5990 Owensboro, KY +6010 Panama City, FL +6020 Parkersburg-Marietta,WV/OH +6030 Pascagoula-Moss Point, MS + Paterson-Clifton, NJ (See New York) + Pawtuckett, RI (See Providence) +6080 Pensacola, FL +6120 Peoria, IL + Petersburg-Col.Heights, VA (See Richmond) +6160 Philadelphia, PA/NJ +6200 Phoenix, AZ +6240 Pine Bluff, AR +6280 Pittsburgh-Beaver Valley, PA +6281 Beaver County, PA +6320 Pittsfield, MA +6360 Ponce, PR +6400 Portland, ME +6440 Portland-Vancouver, OR +6441 Vancouver, WA +6450 Portsmouth-Dover-Rochester, NH/ME +6460 Poughkeepsie, NY +6480 Providence-Fall River-Pawtucket, MA/RI +6481 Fall River, MA/RI +6482 Pawtuckett-Woonsocket-Attleboro, RI/MA +6520 Provo-Orem, UT +6560 Pueblo, CO +6580 Punta Gorda, FL +6600 Racine, WI +6640 Raleigh-Durham, NC +6641 Durham, NC +6660 Rapid City, SD +6680 Reading, PA +6690 Redding, CA +6720 Reno, NV +6740 Richland-Kennewick-Pasco, WA +6760 Richmond-Petersburg, VA +6761 Petersburg-Colonial Heights, VA +6780 Riverside-San Bernardino, CA +6781 San Bernardino, CA +6800 Roanoke, VA +6820 Rochester, MN +6840 Rochester, NY + Rock Hill, SC (See Charlotte) +6880 Rockford, IL +6895 Rocky Mount, NC +6920 Sacramento, CA +6960 Saginaw-Bay City-Midland, MI +6961 Bay City, MI + Salem-Gloucester, MA (See Boston) + San Bernadino, CA (see Riverside) + Springfield, OH (See Dayton) +6980 St. Cloud, MN +7000 St. Joseph, MO +7040 St. Louis, MO/IL +7080 Salem, OR +7120 Salinas-Sea Side-Monterey, CA +7140 Salisbury-Concord, NC +7160 Salt Lake City-Ogden, UT +7161 Ogden +7200 San Angelo, TX +7240 San Antonio, TX +7320 San Diego, CA +7360 San Francisco-Oakland-Vallejo, CA +7361 Oakland, CA +7362 Vallejo-Fairfield-Napa, CA +7400 San Jose, CA +7440 San Juan-Bayamon, PR +7460 San Luis Obispo-Atascad-P Robles, CA +7470 Santa Barbara-Santa Maria-Lompoc, CA +7480 Santa Cruz, CA +7490 Santa Fe, NM +7500 Santa Rosa-Petaluma, CA +7510 Sarasota, FL +7520 Savannah, GA +7560 Scranton-Wilkes-Barre, PA +7561 Wilkes-Barre-Hazelton, PA +7600 Seattle-Everett, WA +7610 Sharon, PA +7620 Sheboygan, WI +7640 Sherman-Denison, TX +7680 Shreveport, LA +7720 Sioux City, IA/NE +7760 Sioux Falls, SD +7800 South Bend-Mishawaka, IN +7840 Spokane, WA +7880 Springfield, IL + Springfield, OH (See Dayton) +7920 Springfield, MO +8000 Springfield-Holyoke-Chicopee, MA +8040 Stamford, CT +8050 State College, PA +8080 Steubenville-Weirton,OH/WV +8120 Stockton, CA +8140 Sumter, SC +8160 Syracuse, NY +8200 Tacoma, WA +8240 Tallahassee, FL +8280 Tampa-St. Petersburg-Clearwater, FL +8320 Terre Haute, IN +8360 Texarkana, TX/AR +8400 Toledo, OH/MI +8440 Topeka, KS +8480 Trenton, NJ +8520 Tucson, AZ +8560 Tulsa, OK +8600 Tuscaloosa, AL +8640 Tyler, TX +8680 Utica-Rome, NY + Vallejo-Fairfield-Napa, CA (See San Francisco) + Vancouver, WA (see Portland) +8730 Ventura-Oxnard-Simi Valley, CA +8750 Victoria, TX +8760 Vineland-Milville-Bridgetown, NJ +8780 Visalia-Tulare-Porterville, CA +8800 Waco, TX +8840 Washington, DC/MD/VA +8880 Waterbury, CT +8920 Waterloo-Cedar Falls, IA +8940 Wausau, WI +8960 West Palm Beach-Boca Raton-Delray Beach, FL +9000 Wheeling, WV/OH +9040 Wichita, KS +9080 Wichita Falls, TX + Wilkes-Barre, PA (See Scranton) +9140 Williamsport, PA +9160 Wilmington, DE/NJ/MD +9200 Wilmington, NC + Winston-Salem, NC (see Greensboro) +9240 Worcester, MA +9260 Yakima, WA +9270 Yolo, CA +9280 York, PA +9320 Youngstown-Warren, OH/PA +9340 Yuba City, CA +9360 Yuma, AZ + + CITY City +0000 Not in identifiable city (or size group) +0001 Aberdeen, SD +0002 Aberdeen, WA +0003 Abilene, TX +0004 Ada, OK +0005 Adams, MA +0006 Adrian, MI +0007 Abington, PA +0010 Akron, OH +0030 Alameda, CA +0050 Albany, NY +0051 Albany, GA +0052 Albert Lea, MN +0070 Albuquerque, NM +0090 Alexandria, VA +0091 Alexandria, LA +0100 Alhambra, CA +0110 Allegheny, PA +0120 Aliquippa, PA +0130 Allentown, PA +0131 Alliance, OH +0132 Alpena, MI +0140 Alton, IL +0150 Altoona, PA +0160 Amarillo, TX +0161 Ambridge, PA +0162 Ames, IA +0163 Amesbury, MA +0170 Amsterdam, NY +0171 Anaconda, MT +0190 Anaheim, CA +0210 Anchorage, AK +0230 Anderson, IN +0231 Anderson, SC +0250 Andover, MA +0270 Ann Arbor, MI +0271 Annapolis, MD +0272 Anniston, AL +0273 Ansonia, CT +0275 Antioch, CA +0280 Appleton, WI +0281 Ardmore, OK +0282 Argenta, AR +0283 Arkansas, KS +0284 Arden-Arcade, CA +0290 Arlington, TX +0310 Arlington, VA +0311 Arlington, MA +0312 Arnold, PA +0313 Asbury Park, NJ +0330 Asheville, NC +0331 Ashland, OH +0340 Ashland, KY +0341 Ashland, WI +0342 Ashtabula, OH +0343 Astoria, OR +0344 Atchison, KS +0345 Athens, GA +0346 Athol, MA +0347 Athens-Clarke County, GA +0350 Atlanta, GA +0370 Atlantic City, NJ +0371 Attleboro, MA +0390 Auburn, NY +0391 Auburn, ME +0410 Augusta, GA +0411 Augusta-Richmond County, GA +0430 Augusta, ME +0450 Aurora, CO +0470 Aurora, IL +0490 Austin, TX +0491 Austin, MN +0510 Bakersfield, CA +0530 Baltimore, MD +0550 Bangor, ME +0551 Barberton, OH +0552 Barre, VT +0553 Bartlesville, OK +0554 Batavia, NY +0570 Bath, ME +0590 Baton Rouge, LA +0610 Battle Creek, MI +0630 Bay City, MI +0640 Bayamon, PR +0650 Bayonne, NJ +0651 Beacon, NY +0652 Beatrice, NE +0660 Belleville, IL +0670 Beaumont, TX +0671 Beaver Falls, PA +0672 Bedford, IN +0673 Bellaire, OH +0680 Bellevue, WA +0690 Bellingham, WA +0695 Belvedere, CA +0700 Belleville, NJ +0701 Bellevue, PA +0702 Belmont, OH +0703 Belmont, MA +0704 Beloit, WI +0705 Bennington, VT +0706 Benton Harbor, MI +0710 Berkeley, CA +0711 Berlin, NH +0712 Berwick, PA +0720 Berwyn, IL +0721 Bessemer, AL +0730 Bethlehem, PA +0740 Biddeford, ME +0741 Big Spring, TX +0742 Billings, MT +0743 Biloxi, MS +0750 Binghamton, NY +0760 Beverly, MA +0761 Beverly Hills, CA +0770 Birmingham, AL +0771 Birmingham, CT +0772 Bismarck, ND +0780 Bloomfield, NJ +0790 Bloomington, IL +0791 Bloomington, IN +0792 Blue Island, IL +0793 Bluefield, WV +0794 Blytheville, AR +0795 Bogalusa, LA +0800 Boise, ID +0801 Boone, IA +0810 Boston, MA +0811 Boulder, CO +0812 Bowling Green, KY +0813 Braddock, PA +0814 Braden, WA +0815 Bradford, PA +0816 Brainerd, MN +0817 Braintree, MA +0818 Brawley, CA +0819 Bremerton, WA +0830 Bridgeport, CT +0831 Bridgeton, NJ +0832 Bristol, CT +0833 Bristol, PA +0834 Bristol, VA +0835 Bristol, TN +0837 Bristol, RI +0850 Brockton, MA +0851 Brookfield, IL +0870 Brookline, MA + Brooklyn, NY (See NYC) +0880 Brownsville, TX +0881 Brownwood, TX +0882 Brunswick, GA +0883 Bucyrus, OH +0890 Buffalo, NY +0900 Burlington, IA +0905 Burlington, VT +0906 Burlington, NJ +0907 Bushkill, PA +0910 Butte, MT +0911 Butler, PA +0920 Burbank, CA +0921 Burlingame, CA +0926 Cairo, IL +0927 Calumet City, IL +0930 Cambridge, MA +0931 Cambridge, OH +0950 Camden, NJ +0951 Campbell, OH +0952 Canonsburg, PA +0970 Camden, NY +0990 Canton, OH +0991 Canton, IL +0992 Cape Girardeau, MO +0993 Carbondale, PA +0994 Carlisle, PA +0995 Carnegie, PA +0996 Carrick, PA +0997 Carteret, NJ +0998 Carthage, MO +0999 Casper, WY +1000 Cape Coral, FL +1010 Cedar Rapids, IA +1020 Central Falls, RI +1021 Centralia, IL +1023 Chambersburg, PA +1024 Champaign, IL +1025 Chanute, KS +1026 Charleroi, PA +1027 Chandler, AZ +1030 Charlestown, MA +1050 Charleston, SC +1060 Carolina, PR +1070 Charleston, WV +1090 Charlotte, NC +1091 Charlottesville, VA +1110 Chattanooga, TN +1130 Chelsea, MA +1140 Cheltenham, PA +1150 Chesapeake, VA +1170 Chester, PA +1171 Cheyenne, WY +1190 Chicago, IL +1191 Chicago Heights, IL +1192 Chickasha, OK +1210 Chicopee, MA +1230 Chillicothe, OH +1250 Chula Vista, CA +1270 Cicero, IL +1290 Cincinnati, OH +1291 Clairton, PA +1292 Claremont, NH +1310 Clarksburg, WV +1311 Clarksdale, MS +1312 Cleburne, TX +1330 Cleveland, OH +1340 Cleveland Heights, OH +1341 Cliffside Park, NJ +1350 Clifton, NJ +1351 Clinton, IN +1370 Clinton, IA +1371 Clinton, MA +1372 Coatesville, PA +1373 Coffeyville, KS +1374 Cohoes, NY +1375 Collingswood, NJ +1390 Colorado Springs, CO +1400 Cohoes, NY +1410 Columbia, SC +1411 Columbia, PA +1412 Columbia, MO +1420 Columbia City, IN +1430 Columbus, GA +1450 Columbus, OH +1451 Columbus, MS +1452 Compton, CA +1470 Concord, CA +1490 Concord, NH +1491 Concord, NC +1492 Connellsville, PA +1493 Connersville, IN +1494 Conshohocken, PA +1495 Coraopolis, PA +1496 Corning, NY +1500 Corona, CA +1510 Council Bluffs, IA +1520 Corpus Christi, TX +1521 Corsicana, TX +1522 Cortland, NY +1523 Coshocton, OH +1530 Covington, KY +1540 Costa Mesa, CA +1545 Cranford, NJ +1550 Cranston, RI +1551 Crawfordsville, IN +1552 Cripple Creek, CO +1553 Cudahy, WI +1570 Cumberland, MD +1571 Cumberland, RI +1572 Cuyahoga Falls, OH +1590 Dallas, TX +1591 Danbury, CT +1592 Daly City, CA +1610 Danvers, MA +1630 Danville, IL +1631 Danville, VA +1650 Davenport, IA +1670 Dayton, OH +1671 Daytona Beach, FL +1680 Dearborn, MI +1690 Decatur, IL +1691 Decatur, AL +1692 Decatur, GA +1693 Dedham, MA +1694 Del Rio, TX +1695 Denison, TX +1710 Denver, CO +1711 Derby, CT +1713 Derry, PA +1730 Des Moines, IA +1750 Detroit, MI +1751 Dickson City, PA +1752 Dodge, KS +1753 Donora, PA +1754 Dormont, PA +1755 Dothan, AL +1770 Dorchester, MA +1790 Dover, NH +1791 Dover, NJ +1792 Du Bois, PA +1800 Downey, CA +1810 Dubuque, IA +1830 Duluth, MN +1831 Dunkirk, NY +1832 Dunmore, PA +1833 Duquesne, PA +1834 Dundalk, MD +1850 Durham, NC +1860 +1870 East Chicago, IN +1890 East Cleveland, OH +1891 East Hartford, CT +1892 East Liverpool, OH +1893 East Moline, IL +1910 East Los Angeles, CA +1930 East Orange, NJ +1931 East Providence, RI +1940 East Saginaw, MI +1950 East St. Louis, IL +1951 East Youngstown, OH +1952 Easthampton, MA +1970 Easton, PA +1971 Eau Claire, WI +1972 Ecorse, MI +1973 El Dorado, KS +1974 El Dorado, AR +1990 El Monte, CA +2010 El Paso, TX +2030 Elgin, IL +2040 Elyria, OH +2050 Elizabeth, NJ +2051 Elizabeth City, NC +2055 Elk Grove, CA +2060 Elkhart, IN +2061 Ellwood City, PA +2062 Elmhurst, IL +2070 Elmira, NY +2071 Elmwood Park, IL +2072 Elwood, IN +2073 Emporia, KS +2074 Endicott, NY +2075 Enfield, CT +2076 Englewood, NJ +2080 Enid, OK +2090 Erie, PA +2091 Escanaba, MI +2092 Euclid, OH +2110 Escondido, CA +2130 Eugene, OR +2131 Eureka, CA +2150 Evanston, IL +2170 Evansville, IN +2190 Everett, MA +2210 Everett, WA +2211 Fairfield, AL +2212 Fairfield, CT +2213 Fairhaven, MA +2214 Fairmont, WV +2220 Fargo, ND +2221 Faribault, MN +2222 Farrell, PA +2230 Fall River, MA +2240 Fayetteville, NC +2241 Ferndale, MI +2242 Findlay, OH +2250 Fitchburg, MA +2260 Fontana, CA +2270 Flint, MI +2271 Floral Park, NY +2273 Florence, AL +2274 Florence, SC +2275 Flushing, NY +2280 Fond du Lac, WI +2281 Forest Park, IL +2290 Fort Lauderdale, FL +2300 Fort Collins, CO +2301 Fort Dodge, IA +2302 Fort Madison, IA +2303 Fort Scott, KS +2310 Fort Smith, AR +2311 Fort Thomas, KY +2330 Fort Wayne, IN +2350 Fort Worth, TX +2351 Fostoria, OH +2352 Framingham, MA +2353 Frankfort, IN +2354 Frankfort, KY +2355 Franklin, PA +2356 Frederick, MD +2357 Freeport, NY +2358 Freeport, IL +2359 Fremont, OH +2360 Fremont, NE +2370 Fresno, CA +2390 Fullerton, CA +2391 Fulton, NY +2392 Gadsden, AL +2393 Galena, KS +2394 Gainseville, FL +2400 Galesburg, IL +2410 Galveston, TX +2411 Gardner, MA +2430 Garden Grove, CA +2435 Gardena, CA +2440 Garfield, NJ +2441 Garfield Heights, OH +2450 Garland, TX +2470 Gary, IN +2471 Gastonia, NC +2472 Geneva, NY + Georgetown, DC (See Washington, DC) +2473 Glen Cove, NY +2489 Glendale, AZ +2490 Glendale, CA +2491 Glens Falls, NY +2510 Gloucester, MA +2511 Gloucester, NJ +2512 Gloversville, NY +2513 Goldsboro, NC +2514 Goshen, IN +2515 Grand Forks, ND +2516 Grand Island, NE +2517 Grand Junction, CO +2520 Granite City, IL +2530 Grand Rapids, MI +2531 Grandville, MI +2540 Great Falls, MT +2541 Greeley, CO +2550 Green Bay, WI +2551 Greenfield, MA +2570 Greensboro, NC +2571 Greensburg, PA +2572 Greenville, MS +2573 Greenville, SC +2574 Greenville, TX +2575 Greenwich, CT +2576 Greenwood, MS +2577 Greenwood, SC +2578 Griffin, GA +2579 Grosse Pointe Park, MI +2580 Guynabo, PR +2581 Groton, CT +2582 Gulfport, MS +2583 Guthrie, OK +2584 Hackensack, NJ +2590 Hagerstown, MD +2591 Hamden, CT +2610 Hamilton, OH +2630 Hammond, IN +2650 Hampton, VA +2670 Hamtramck Village, MI +2680 Hannibal, MO +2681 Hanover, PA +2682 Harlingen, TX +2683 Hanover township, Luzerne county, PA +2690 Harrisburg, PA +2691 Harrisburg, IL +2692 Harrison, NJ +2693 Harrison, PA +2710 Hartford, CT +2711 Harvey, IL +2712 Hastings, NE +2713 Hattiesburg, MS +2725 Haverford, PA +2730 Haverhill, MA +2731 Hawthorne, NJ +2740 Hayward, CA +2750 Hazleton, PA +2751 Helena, MT +2752 Hempstead, NY +2753 Henderson, KY +2754 Herkimer, NY +2755 Herrin, IL +2756 Hibbing, MN +2757 Henderson, NV +2770 Hialeah, FL +2780 High Point, NC +2781 Highland Park, IL +2790 Highland Park, MI +2791 Hilo, HI +2792 Hillside, NJ +2810 Hoboken, NJ +2811 Holland, MI +2830 Hollywood, FL +2850 Holyoke, MA +2851 Homestead, PA +2870 Honolulu, HI +2871 Hopewell, VA +2872 Hopkinsville, KY +2873 Hoquiam, WA +2874 Hornell, NY +2875 Hot Springs, AR +2890 Houston, TX +2891 Hudson, NY +2892 Huntington, IN +2910 Huntington, WV +2930 Huntington Beach, CA +2950 Huntsville, AL +2951 Huron, SD +2960 Hutchinson, KS +2961 Hyde Park, MA +2962 Ilion, NY +2963 Independence, KS +2970 Independence, MO +2990 Indianapolis, IN +3010 Inglewood, CA +3011 Iowa City, IA +3012 Iron Mountain, MI +3013 Ironton, OH +3014 Ironwood, MI +3015 Irondequoit, NY +3020 Irvine, CA +3030 Irving, TX +3050 Irvington, NJ +3051 Ishpeming, MI +3052 Ithaca, NY +3070 Jackson, MI +3071 Jackson, MN +3090 Jackson, MS +3091 Jackson, TN +3110 Jacksonville, FL +3111 Jacksonville, IL +3130 Jamestown , NY +3131 Janesville, WI +3132 Jeannette, PA +3133 Jefferson City, MO +3134 Jeffersonville, IN +3150 Jersey City, NJ +3151 Johnson City, NY +3160 Johnson City, TN +3161 Johnstown, NY +3170 Johnstown, PA +3190 Joliet, IL +3191 Jonesboro, AR +3210 Joplin, MO +3230 Kalamazoo, MI +3231 Kankakee, IL +3250 Kansas City, KS +3260 Kansas City, MO +3270 Kearny, NJ +3271 Keene, NH +3272 Kenmore, NY +3273 Kenmore, OH +3290 Kenosha, WI +3291 Keokuk, IA +3292 Kewanee, IL +3293 Key West, FL +3294 Kingsport, TN +3310 Kingston, NY +3311 Kingston, PA +3312 Kinston, NC +3313 Klamath Falls, OR +3330 Knoxville, TN +3350 Kokomo, IN +3370 La Crosse, WI +3380 Lafayette, IN +3390 Lafayette, LA +3391 La Grange, IL +3392 La Grange, GA +3393 La Porte, IN +3394 La Salle, IL +3395 Lackawanna, NY +3396 Laconia, NH +3400 Lake Charles, LA +3405 Lakeland, FL +3410 Lakewood, CO +3430 Lakewood, OH +3440 Lancaster, CA +3450 Lancaster, PA +3451 Lancaster, OH +3470 Lansing, MI +3471 Lansingburgh, NY +3480 Laredo, TX +3481 Latrobe, PA +3482 Laurel, MS +3490 Las Vegas, NV +3510 Lawrence, MA +3511 Lawrence, KS +3512 Lawton, OK +3513 Leadville, CO +3520 Leavenworth, KS +3521 Lebanon, PA +3522 Leominster, MA +3530 Lehigh, PA +3540 Lebanon, PA +3550 Lewiston, ME +3551 Lewistown, PA +3560 Lewisville, TX +3570 Lexington, KY +3590 Lexington-Fayette, KY +3610 Lima, OH +3630 Lincoln, NE +3631 Lincoln, IL +3632 Lincoln Park, MI +3633 Lincoln, RI +3634 Linden, NJ +3635 Little Falls, NY +3638 Lodi, NJ +3639 Logansport, IN +3650 Little Rock, AR +3670 Livonia, MI +3680 Lockport, NY +3690 Long Beach, CA +3691 Long Branch, NJ +3692 Long Island City, NY +3693 Longview, WA +3710 Lorain, OH +3730 Los Angeles, CA +3750 Louisville, KY +3765 Lower Merion, PA +3770 Lowell, MA +3771 Lubbock, TX +3772 Lynbrook, NY +3790 Lynchburg, VA +3800 Lyndhurst, NJ +3810 Lynn, MA +3830 Macon, GA +3850 Madison, IN +3870 Madison, WI +3871 Mahanoy City, PA +3890 Malden, MA +3891 Mamaroneck, NY +3910 Manchester, NH +3911 Manchester, CT +3912 Manhattan, KS +3913 Manistee, MI +3914 Manitowoc, WI +3915 Mankato, MN +3929 Maplewood, NJ +3930 Mansfield, OH +3931 Maplewood, MO +3932 Marietta, OH +3933 Marinette, WI +3934 Marion, IN +3940 Maywood, IL +3950 Marion, OH +3951 Marlborough, MA +3952 Marquette, MI +3953 Marshall, TX +3954 Marshalltown, IA +3955 Martins Ferry, OH +3956 Martinsburg, WV +3957 Mason City, IA +3958 Massena, NY +3959 Massillon, OH +3960 McAllen, TX +3961 Mattoon, IL +3962 Mcalester, OK +3963 Mccomb, MS +3964 Mckees Rocks, PA +3970 McKeesport, PA +3971 Meadville, PA +3990 Medford, MA +3991 Medford, OR +3992 Melrose, MA +3993 Melrose Park, IL +4010 Memphis, TN +4011 Menominee, MI +4030 Meriden, CT +4040 Meridian, MS +4041 Methuen, MA +4050 Mesa, AZ +4070 Mesquite, TX +4090 Metairie, LA +4110 Miami, FL +4120 Michigan City, IN +4121 Middlesborough, KY +4122 Middletown, CT +4123 Middletown, NY +4124 Middletown, OH +4125 Milford, CT +4126 Milford, MA +4127 Millville, NJ +4128 Milton, MA +4130 Milwaukee, WI +4150 Minneapolis, MN +4151 Minot, ND +4160 Mishawaka, IN +4161 Missoula, MT +4162 Mitchell, SD +4163 Moberly, MO +4170 Mobile, AL +4190 Modesto, CA +4210 Moline, IL +4211 Monessen, PA +4212 Monroe, MI +4213 Monroe, LA +4214 Monrovia, CA +4230 Montclair, NJ +4250 Montgomery, AL +4251 Morgantown, WV +4252 Morristown, NJ +4253 Moundsville, WV +4254 Mount Arlington, NJ +4255 Mount Carmel, PA +4256 Mount Clemens, MI +4260 Mount Lebanon, PA +4270 Moreno Valley, CA +4290 Mount Vernon, NY +4291 Mount Vernon, IL +4310 Muncie, IN +4311 Munhall, PA +4312 Murphysboro, IL +4313 Muscatine, IA +4330 Muskegon, MI +4331 Muskegon Heights, MI +4350 Muskogee, OK +4351 Nanticoke, PA +4370 Nantucket, MA +4390 Nashua, NH +4410 Nashville-Davidson, TN +4411 Nashville, TN +4413 Natchez, MS +4414 Natick, MA +4415 Naugatuck, CT +4416 Needham, MA +4420 Neptune, NJ +4430 New Albany, IN +4450 New Bedford, MA +4451 New Bern, NC +4452 New Brighton, NY +4470 New Britain, CT +4490 New Brunswick, NJ +4510 New Castle, PA +4511 New Castle, IN +4530 New Haven, CT +4550 New London, CT +4570 New Orleans, LA +4571 New Philadelphia, OH +4590 New Rochelle, NY +4610 New York, NY +4611 Brooklyn (only in census years before 1900) +4630 Newark, NJ +4650 Newark, OH +4670 Newburgh, NY +4690 Newburyport, MA +4710 Newport, KY +4730 Newport, RI +4750 Newport News, VA +4770 Newton, MA +4771 Newton, IA +4772 Newton, KS +4790 Niagara Falls, NY +4791 Niles, MI +4792 Niles, OH +4810 Norfolk, VA +4811 Norfolk, NE +4820 North Las Vegas, NV + Northern Liberties, PA (See Philadelphia) +4830 Norristown Borough, PA +4831 North Adams, MA +4832 North Attleborough, MA +4833 North Bennington, VT +4834 North Braddock, PA +4835 North Branford, CT +4836 North Haven, CT +4837 North Little Rock, AR +4838 North Platte, NE +4839 North Providence, RI +4840 Northampton, MA +4841 North Tonawanda, NY +4842 North Yakima, WA +4843 Northbridge, MA +4845 North Bergen, NJ +4850 North Providence, RI +4860 Norwalk, CA +4870 Norwalk, CT +4890 Norwich, CT +4900 Norwood, OH +4901 Norwood, MA +4902 Nutley, NJ +4905 Oak Park, IL +4910 Oak Park Village +4930 Oakland, CA +4950 Oceanside, CA +4970 Ogden, UT +4971 Ogdensburg, NY +4972 Oil City, PA +4990 Oklahoma City, OK +4991 Okmulgee, OK +4992 Old Bennington, VT +4993 Old Forge, PA +4994 Olean, NY +4995 Olympia, WA +4996 Olyphant, PA +5010 Omaha, NE +5011 Oneida, NY +5012 Oneonta, NY +5030 Ontario, CA +5040 Orange, CA +5050 Orange, NJ +5051 Orange, CT +5070 Orlando, FL +5090 Oshkosh, WI +5091 Oskaloosa, IA +5092 Ossining, NY +5110 Oswego, NY +5111 Ottawa, IL +5112 Ottumwa, IA +5113 Owensboro, KY +5114 Owosso, MI +5116 Painesville, OH +5117 Palestine, TX +5118 Palo Alto, CA +5119 Pampa, TX +5121 Paris, TX +5122 Park Ridge, IL +5123 Parkersburg, WV +5124 Parma, OH +5125 Parsons, KS +5130 Oxnard, CA +5140 Palmdale, CA +5150 Pasadena, CA +5170 Pasadena, TX +5180 Paducah, KY +5190 Passaic, NJ +5210 Paterson, NJ +5230 Pawtucket, RI +5231 Peabody, MA +5232 Peekskill, NY +5233 Pekin, IL +5240 Pembroke Pines, FL +5250 Pensacola, FL +5255 Pensauken, NJ +5269 Peoria, AZ +5270 Peoria, IL +5271 Peoria Heights, IL +5290 Perth Amboy, NJ +5291 Peru, IN +5310 Petersburg, VA +5311 Phenix City, AL +5330 Philadelphia, PA +5331 Kensington +5332 Mayamensing +5333 Northern Liberties +5334 Southwark +5335 Spring Garden +5341 Phillipsburg, NJ +5350 Phoenix, AZ +5351 Phoenixville, PA +5352 Pine Bluff, AR +5353 Piqua, OH +5354 Pittsburg, KS +5370 Pittsburgh, PA +5390 Pittsfield, MA +5391 Pittston, PA +5409 Plains, PA +5410 Plainfield, NJ +5411 Plattsburg, NY +5412 Pleasantville, NJ +5413 Plymouth, PA +5414 Plymouth, MA +5415 Pocatello, ID +5430 Plano, TX +5450 Pomona, CA +5451 Ponca City, OK +5460 Ponce, PR +5470 Pontiac, MI +5471 Port Angeles, WA +5480 Port Arthur, TX +5481 Port Chester, NY +5490 Port Huron, MI +5491 Port Jervis, NY +5500 Port St. Lucie, FL +5510 Portland, ME +5511 Portland, IL +5530 Portland, OR +5550 Portsmouth, NH +5570 Portsmouth, OH +5590 Portsmouth, VA +5591 Pottstown, PA +5610 Pottsville, PA +5630 Poughkeepsie, NY +5650 Providence, RI +5660 Provo, UT +5670 Pueblo, CO +5671 Punxsutawney, PA +5690 Quincy, IL +5710 Quincy, MA +5730 Racine, WI +5731 Rahway, NJ +5750 Raleigh, NC +5751 Ranger, TX +5752 Rapid City, SD +5770 Rancho Cucamonga, CA +5790 Reading, PA +5791 Red Bank, NJ +5792 Redlands, CA +5810 Reno, NV +5811 Rensselaer, NY +5830 Revere, MA +5850 Richmond, IN +5870 Richmond, VA +5871 Richmond, CA +5872 Ridgefield Park, NJ +5873 Ridgewood, NJ +5874 River Rouge, MI +5890 Riverside, CA +5910 Roanoke, VA +5930 Rochester, NY +5931 Rochester, NH +5932 Rochester, MN +5933 Rock Hill, SC +5950 Rock Island, IL +5970 Rockford, IL +5971 Rockland, ME +5972 Rockton, IL +5973 Rockville Centre, NY +5974 Rocky Mount, NC +5990 Rome, NY +5991 Rome, GA +5992 Roosevelt, NJ +5993 Roselle, NJ +5994 Roswell, NM +5995 Roseville, CA +6010 Roxbury, MA +6011 Royal Oak, MI +6012 Rumford Falls, ME +6013 Rutherford, NJ +6014 Rutland, VT +6030 Sacramento, CA +6050 Saginaw, MI +6070 Saint Joseph, MO +6090 Saint Louis, MO +6110 Saint Paul, MN +6130 Saint Petersburg, FL +6150 Salem, MA +6170 Salem, OR +6171 Salem, OH +6172 Salina, KS +6190 Salinas, CA +6191 Salisbury, NC +6192 Salisbury, MD +6210 Salt Lake City, UT +6211 San Angelo, TX +6220 San Angelo, TX +6230 San Antonio, TX +6231 San Benito, TX +6250 San Bernardino, CA +6260 San Buenaventura (Ventura), CA +6270 San Diego, CA +6280 Sandusky, OH +6281 Sanford, FL +6282 Sanford, ME +6290 San Francisco, CA +6300 San Juan, PR +6310 San Jose, CA +6311 San Leandro, CA +6312 San Mateo, CA +6320 Santa Barbara, CA +6321 Santa Cruz, CA +6322 Santa Fe, NM +6330 Santa Ana, CA +6335 Santa Clara, CA +6340 Santa Clarita, CA +6350 Santa Rosa, CA +6351 Sapulpa, OK +6352 Saratoga Springs, NY +6353 Saugus, MA +6354 Sault Ste. Marie, MI +6360 Santa Monica, CA +6370 Savannah, GA +6390 Schenectedy, NY +6410 Scranton, PA +6430 Seattle, WA +6431 Sedalia, MO +6432 Selma, AL +6433 Seminole, OK +6434 Shaker Heights, OH +6435 Shamokin, PA +6437 Sharpsville, PA +6438 Shawnee, OK +6440 Sharon, PA +6450 Sheboygan, WI +6451 Shelby, NC +6452 Shelbyville, IN +6453 Shelton, CT +6470 Shenandoah Borough, PA +6471 Sherman, TX +6472 Shorewood, WI +6490 Shreveport, LA +6500 Simi Valley, CA +6510 Sioux City, IA +6530 Sioux Falls, SD +6550 Smithfield, RI (1850) +6570 Somerville, MA +6590 South Bend, IN +6591 South Bethlehem, PA +6592 South Boise, ID +6593 South Gate, CA +6594 South Milwaukee, WI +6595 South Norwalk, CT +6610 South Omaha, NE +6611 South Orange, NJ +6612 South Pasadena, CA +6613 South Pittsburgh, PA +6614 South Portland, ME +6615 South River, NJ +6616 South St. Paul, MN +6617 Southbridge, MA + Southwark, PA (See Philadelphia) +6620 Spartanburg, SC +6630 Spokane, WA + Spring Garden, PA (See Philadelphia) +6640 Spring Valley, NV +6650 Springfield, IL +6670 Springfield, MA +6690 Springfield, MO +6691 St. Augustine, FL +6692 St. Charles, MO +6693 St. Cloud, MN +6710 Springfield, OH +6730 Stamford, CT +6731 Statesville, NC +6732 Staunton, VA +6733 Steelton, PA +6734 Sterling, IL +6750 Sterling Heights, MI +6770 Steubenville, OH +6771 Stevens Point, WI +6772 Stillwater, MN +6789 Stowe, PA +6790 Stockton, CA +6791 Stoneham, MA +6792 Stonington, CT +6793 Stratford, CT +6794 Streator, IL +6795 Struthers, OH +6796 Suffolk, VA +6797 Summit, NJ +6798 Sumter, SC +6799 Sunbury, PA +6810 Sunnyvale, CA +6830 Superior, WI +6831 Swampscott, MA +6832 Sweetwater, TX +6833 Swissvale, PA +6850 Syracuse, NY +6870 Tacoma, WA +6871 Tallahassee, FL +6872 Tamaqua, PA +6890 Tampa, FL +6910 Taunton, MA +6911 Taylor, PA +6912 Temple, TX +6913 Teaneck, NJ +6930 Tempe, AZ +6950 Terre Haute, IN +6951 Texarkana, TX +6952 Thomasville, GA +6953 Thomasville, NC +6954 Tiffin, OH +6960 Thousand Oaks, CA +6970 Toledo, OH +6971 Tonawanda, NY +6990 Topeka, KS +6991 Torrington, CT +6992 Traverse City, MI +7000 Torrance, CA +7010 Trenton, NJ +7011 Trinidad, CO +7030 Troy, NY +7050 Tucson, AZ +7070 Tulsa, OK +7071 Turtle Creek, PA +7072 Tuscaloosa, AL +7073 Two Rivers, WI +7074 Tyler, TX +7079 Union, NJ +7080 Union City, NJ +7081 Uniontown, PA +7082 University City, MO +7083 Urbana, IL +7084 Upper Darby, PA +7090 Utica, NY +7091 Valdosta, GA +7092 Vallejo, CA +7093 Valley Stream, NY +7100 Vancouver, WA +7110 Vallejo, CA +7111 Vandergrift, PA +7112 Venice, CA +7120 Vicksburg, MS +7121 Vincennes, IN +7122 Virginia, MN +7123 Virginia City, NV +7130 Virginia Beach, VA +7140 Visalia, CA +7150 Waco, TX +7151 Wakefield, MA +7152 Walla Walla, WA +7153 Wallingford, CT +7170 Waltham, MA +7180 Warren, MI +7190 Warren, OH +7191 Warren, PA +7210 Warwick Town, RI +7230 Washington, DC +7231 Georgetown, DC +7241 Washington, PA +7242 Washington, VA +7250 Waterbury, CT +7270 Waterloo, IA +7290 Waterloo, NY +7310 Watertown, NY +7311 Watertown, WI +7312 Watertown, SD +7313 Watertown, MA +7314 Waterville, ME +7315 Watervliet, NY +7316 Waukegan, IL +7317 Waukesha, WI +7318 Wausau, WI +7319 Wauwatosa, WI +7320 West Covina, CA +7321 Waycross, GA +7322 Waynesboro, PA +7323 Webb City, MO +7324 Webster Groves, MO +7325 Webster, MA +7326 Wellesley, MA +7327 Wenatchee, WA +7328 Weehawken, NJ +7329 West Bay City, MI +7330 West Hoboken, NJ +7331 West Bethlehem, PA +7332 West Chester, PA +7333 West Frankfort, IL +7334 West Hartford, CT +7335 West Haven, CT +7340 West Allis, WI +7350 West New York, NJ +7351 West Orange, NJ +7352 West Palm Beach, FL +7353 West Springfield, MA +7370 West Troy, NY +7371 West Warwick, RI +7372 Westbrook, ME +7373 Westerly, RI +7374 Westfield, MA +7375 Westfield, NJ +7376 Wewoka, OK +7377 Weymouth, MA +7390 Wheeling, WV +7400 White Plains, NY +7401 Whiting, IN +7402 Whittier, CA +7410 Wichita, KS +7430 Wichita Falls, TX +7450 Wilkes-Barre, PA +7451 Wilkinsburg, PA +7460 Wilkinsburg, PA +7470 Williamsport, PA +7471 Willimantic, CT +7472 Wilmette, IL +7490 Wilmington, DE +7510 Wilmington, NC +7511 Wilson, NC +7512 Winchester, VA +7513 Winchester, MA +7514 Windham, CT +7515 Winnetka, IL +7516 Winona, MN +7530 Winston-Salem, NC +7531 Winthrop, MA +7532 Woburn, MA +7533 Woodlawn, PA +7534 Woodmont, CT +7535 Woodbridge, NJ +7550 Woonsocket, RI +7551 Wooster, OH +7570 Worcester, MA +7571 Wyandotte, MI +7572 Xenia, OH +7573 Yakima, WA +7590 Yonkers, NY +7610 York, PA +7630 Youngstown, OH +7631 Ypsilanti, MI +7650 Zanesville, OH + Spring Garden, PA (See Philadelphia) + + SIZEPL Size of place +00 Not identifiable +01 Under 1,000, or unincorporated +02 1,000 - 2,499 +03 2,500 - 3,999 +04 4,000 - 4,999 +05 5,000 - 9,999 +06 10,000 - 24,999 +07 25,000 - 49,999 +08 50,000 - 74,999 +09 75,000 - 99,999 +10 100,000 - 199,999 +20 200,000 - 299,999 +30 300,000 - 399,999 +40 400,000 - 499,999 +50 500,000 - 599,999 +60 600,000 - 749,999 +70 750,000 - 999,999 +80 1,000,000 - 1,999,999 +90 2,000,000+ + + CNTRY Country +630 Puerto Rico +840 United States + + GQ Group quarters status +0 Vacant unit + Households: +1 Households under 1970 definition +2 Additional households under 1990 definition + Group Quarters: +3 Group quarters--Institutions +4 Other group quarters +5 Additional households under 2000 definition +6 Fragment + + GQTYPE Group quarters type [general version] +0 NA (non-group quarters households) + Institutions +1 Institution (1990, 2000, ACS/PRCS) +2 Correctional institutions +3 Mental institutions +4 Institutions for the elderly, handicapped, and poor + Non-institutional group quarters +5 Non-institutional GQ +6 Military +7 College dormitory +8 Rooming house +9 Other non-institutional GQ and unknown + + GQTYPED Group quarters type [detailed version] +000 NA (non-group quarters households) + GQ in IPUMS, sampled as a larger unit (10 or more unrelated persons): +010 Family group, someone related to head +020 Unrelated individuals, no one related to head + Institutions +100 Institution (1990, 2000, ACS/PRCS) +200 Correctional institution +210 Federal/state correctional +211 Prison +212 Penitentiary +213 Military prison +220 Local correctional +221 Jail +230 School juvenile delinquents +240 Reformatory +250 Camp or chain gang +260 House of correction +300 Mental institutions +400 Institutions for the elderly, handicapped, and poor +410 Homes for elderly +411 Aged, dependent home +412 Nursing/convalescent home +413 Old soldiers' home +420 Other Instits (Not Aged) +421 Other Institution nec +430 Homes neglected/depend children +431 Orphan school +432 Orphans' home, asylum +440 Other instits for children +441 Children's home, asylum +450 Homes physically handicapped +451 Deaf, blind school +452 Deaf, blind, epilepsy +460 Mentally handicapped home +461 School for feeblemind +470 TB and chronic disease hospital +471 Chronic hospitals +472 Sanatoria +480 Poor houses and farms +481 Poor house, almshouse +482 Poor farm, workhouse +491 Maternity homes for unmarried mothers +492 Homes for widows, single, fallen women +493 Detention homes +494 Misc asylums +495 Home, other dependent +496 Institution combination or unknown + Non-Institutional group quarters +500 Non-institutional group quarters +501 Family formerly in institutional group quarters +502 Unrelated individual residing with family formerly in institutional group quarters +600 Military +601 U.S. army installation +602 Navy, marine installation +603 Navy ships +604 Air service +700 College dormitory +701 Military service academies +800 Rooming house +801 Hotel +802 House, lodging apartments +803 YMCA, YWCA +804 Club +900 Other Non-Instit GQ +901 Other Non-Instit GQ +910 Schools +911 Boarding schools +912 Academy, institute +913 Industrial training +914 Indian school +920 Hospitals +921 Hospital, charity +922 Infirmary +923 Maternity hospital +924 Children's hospital + Religious Institutions +931 Church, Abbey +932 Convent +933 Monastery +934 Mission +935 Seminary +936 Religious commune +937 Other religious +940 Work sites +941 Construction, except rr +942 Lumber +943 Mining +944 Railroad +945 Farms, ranches +946 Ships, boats +947 Other industrial +948 Other worksites +950 Nurses home, dorm +955 Passenger ships +960 Other group quarters +999 Fragment (boarders and lodgers, 1900) + + GQFUNDS Group quarters funding +00 N/A + Public Funds +11 Federal support +12 Federal and State +13 State support +14 Local support +15 State and Local +16 Government, not specified + Private Funds +21 Private, Nonprofit +22 Private, Commercial +23 Religious +24 Ethnic, fraternal +25 Private, unknown +99 Fragment or Unknown + + FARM Farm status +0 N/A +1 Non-Farm +2 Farm + + OWNERSHP Ownership of dwelling (tenure) [general version] +0 N/A +1 Owned or being bought (loan) +2 Rented + + OWNERSHPD Ownership of dwelling (tenure) [detailed version] +00 N/A +10 Owned or being bought +11 Check mark (owns?) +12 Owned free and clear +13 Owned with mortgage or loan +20 Rented +21 No cash rent +22 With cash rent + + NFAMS Number of families in household +00 0 families (vacant unit) +01 1 family or N/A +02 2 families +03 3 +04 4 +05 5 +06 6 +07 7 +08 8 +09 9 +10 10 +11 11 +12 12 +13 13 +14 14 +15 15 +16 16 +17 17 +18 18 +19 19 +20 20 +21 21 +22 22 +23 23 +24 24 +25 25 +26 26 +27 27 +28 28 +29 29 +30 30 + + NSUBFAM Number of subfamilies in household +0 No subfamilies or N/A (GQ/vacant unit) +1 1 subfamily +2 2 subfamilies +3 3 +4 4 +5 5 +6 6 +7 7 +8 8 +9 9 + + NCOUPLES Number of couples in household +0 0 couples or N/A +1 1 +2 2 +3 3 +4 4 +5 5 +6 6 +7 7 +8 8 +9 9 + + NMOTHERS Number of mothers in household +0 0 mothers or N/A +1 1 +2 2 +3 3 +4 4 +5 5 +6 6 +7 7 +8 8 + + NFATHERS Number of fathers in household +0 0 fathers or N/A +1 1 +2 2 +3 3 +4 4 +5 5 +6 6 + + MULTGEN Multigenerational household [general version] +0 N/A +1 1 generation +2 2 generations +3 3+ generations + + MULTGEND Multigenerational household [detailed version] +00 N/A +10 1 generation +20 1-2 generations (Census 2008 definition) +21 2 adjacent generations, adult-children +22 2 adjacent generations, adult-adult +23 2 nonadjacent generations +31 3+ generations (Census 2008 definition) +32 3+ generations (Additional IPUMS definition) + + RESPOND Respondent's relationship to household head +0 N/A (Group quarters) +1 Household head +2 Wife of household head +3 Related adult other than spouse +4 Related child (under 14 years old) +5 Nonrelated adult +6 Nonrelated child (under 14 years old) +7 No respondent indicated + + SPLIT Large group quarters that was split up (100% datasets) +0 Person was not in a large group quarters that was split apart +1 Person was in a large group quarters that was split apart + + SPLIT40 Large group quarters that was split up, 1940 100% +0 Person was not in a large group quarters that was split apart +1 Person was in a large group quarters that was split apart + + EDMISS Identifies households in missing data enumeration districts +0 Household not in missing data enumeration district +1 Household in missing data enumeration district + + YEARP Census year +1850 1850 +1860 1860 +1870 1870 +1880 1880 +1900 1900 +1910 1910 +1920 1920 +1930 1930 +1940 1940 +1950 1950 +1960 1960 +1970 1970 +1980 1980 +1990 1990 +2000 2000 +2001 2001 +2002 2002 +2003 2003 +2004 2004 +2005 2005 +2006 2006 +2007 2007 +2008 2008 + + SLREC Sample-line person identifier +1 Not a sample-line person +2 Sample-line person + + RESPONDT Respondent indicator +0 Respondent is not a member of the household +1 Person is not respondent for household or GQ +2 Person is respondent + + FAMUNIT Family unit membership +01 1st family in household or group quarters +02 2nd family in household or group quarters +03 3rd +04 4th +05 5th +06 6th +07 7th +08 8th +09 9th +10 10th +11 11th +12 12th +13 13th +14 14th +15 15th +16 16th +17 17th +18 18th +19 19th +20 20th +21 21th +22 22th +23 23th +24 24th +25 25th +26 26th +27 27th +28 28th +29 29th +30 30th + + FAMSIZE Number of own family members in household +01 1 family member present +02 2 family members present +03 3 +04 4 +05 5 +06 6 +07 7 +08 8 +09 9 +10 10 +11 11 +12 12 +13 13 +14 14 +15 15 +16 16 +17 17 +18 18 +19 19 +20 20 +21 21 +22 22 +23 23 +24 24 +25 25 +26 26 +27 27 +28 28 +29 29 + + SUBFAM Subfamily membership +0 Group quarters or not in subfamily +1 1st subfamily in household +2 2nd subfamily in household +3 3rd +4 4th +5 5th +6 6th +7 7th +8 8th +9 9th + + SFTYPE Subfamily type +0 Group quarters or not in subfamily + RELATED SUBFAMILIES +1 Married-couple related subfamily with children +2 Married-couple related subfamily without children +3 Father-child related subfamily +4 Mother-child related subfamily + UNRELATED SUBFAMILIES +5 Married-couple unrelated subfamily with children +6 Married-couple unrelated subfamily without children +7 Father-child unrelated subfamily +8 Mother-child unrelated subfamily + + SFRELATE Relationship within subfamily +0 Group quarters or not in subfamily +1 Reference person +2 Spouse (married-couple subfamily only) +3 Child + + STEPMOM Probable step/adopted mother +0 No stepmother present +1 Improbable age difference +2 Spouse of father +3 Identified stepmother +4 No surviving children +5 Identified as adopted +6 Birthplace/marriage duration mismatch +7 Number of children born/children surviving check + + MOMRULE_HIST Rule for linking mother +0 No mother link +1 Unambiguous mother link +2 Daughter/grandchild link +3 Preceding female (no intervening person) +4 Preceding female (surname similarity) +5 Daughter/grandchild (child surviving status) +6 Preceding female (child surviving status) +7 Spouse of father becomes stepmother + + STEPPOP Probable step/adopted father +0 No stepfather present +1 Improbable age difference +2 Spouse of mother +3 Identified stepfather +5 Identified as adopted +6 Birthplace/marriage duration mismatch +7 Surname difference -- male child or never-married female + + POPRULE_HIST Rule for linking father +0 No father link +1 Unambiguous father link +2 Son/granchild link +3 Preceding male (no intervening person) +4 Preceding male (surname similarity) +7 Husband of mother becomes stepfather + + SPRULE_HIST Rule for linking spouse +0 No spouse link + Unambiguous link: +1 Wife follows husband +2 Wife precedes husband +3 Non-adjacent links -- consistent relationship to head/age differences +4 Adjacent links (wife follows husband -- no age, other relative conflicts) +5 Adjacent links (wife precedes husband -- no age, other relative conflicts) +6 Non-adjacent links -- no age, other relative conflicts +7 Previously allocated marital status -- no age, other relative conflicts + + NCHILD Number of own children in the household +0 0 children present +1 1 child present +2 2 +3 3 +4 4 +5 5 +6 6 +7 7 +8 8 +9 9+ + + NCHLT5 Number of own children under age 5 in household +0 No children under age 5 +1 1 child under age 5 +2 2 +3 3 +4 4 +5 5 +6 6 +7 7 +8 8 +9 9+ + + NSIBS Number of own siblings in household +0 0 siblings +1 1 sibling +2 2 siblings +3 3 siblings +4 4 siblings +5 5 siblings +6 6 siblings +7 7 siblings +8 8 siblings +9 9 or more siblings + + ELDCH Age of eldest own child in household +00 Less than 1 year old +01 1 +02 2 +03 3 +04 4 +05 5 +06 6 +07 7 +08 8 +09 9 +10 10 +11 11 +12 12 +13 13 +14 14 +15 15 +16 16 +17 17 +18 18 +19 19 +20 20 +21 21 +22 22 +23 23 +24 24 +25 25 +26 26 +27 27 +28 28 +29 29 +30 30 +31 31 +32 32 +33 33 +34 34 +35 35 +36 36 +37 37 +38 38 +39 39 +40 40 +41 41 +42 42 +43 43 +44 44 +45 45 +46 46 +47 47 +48 48 +49 49 +50 50 +51 51 +52 52 +53 53 +54 54 +55 55 +56 56 +57 57 +58 58 +59 59 +60 60 +61 61 +62 62 +63 63 +64 64 +65 65 +66 66 +67 67 +68 68 +69 69 +70 70 +71 71 +72 72 +73 73 +74 74 +75 75 +76 76 +77 77 +78 78 +79 79 +80 80 +81 81 +82 82 +83 83 +84 84 +85 85 +86 86 +87 87 +88 88 +89 89 +90 90 +91 91 +92 92 +93 93 +94 94 +95 95 +96 96 +97 97 +98 98 +99 N/A + + YNGCH Age of youngest own child in household +00 Less than 1 year old +01 1 +02 2 +03 3 +04 4 +05 5 +06 6 +07 7 +08 8 +09 9 +10 10 +11 11 +12 12 +13 13 +14 14 +15 15 +16 16 +17 17 +18 18 +19 19 +20 20 +21 21 +22 22 +23 23 +24 24 +25 25 +26 26 +27 27 +28 28 +29 29 +30 30 +31 31 +32 32 +33 33 +34 34 +35 35 +36 36 +37 37 +38 38 +39 39 +40 40 +41 41 +42 42 +43 43 +44 44 +45 45 +46 46 +47 47 +48 48 +49 49 +50 50 +51 51 +52 52 +53 53 +54 54 +55 55 +56 56 +57 57 +58 58 +59 59 +60 60 +61 61 +62 62 +63 63 +64 64 +65 65 +66 66 +67 67 +68 68 +69 69 +70 70 +71 71 +72 72 +73 73 +74 74 +75 75 +76 76 +77 77 +78 78 +79 79 +80 80 +81 81 +82 82 +83 83 +84 84 +85 85 +86 86 +87 87 +88 88 +89 89 +90 90 +91 91 +92 92 +93 93 +94 94 +95 95 +96 96 +97 97 +98 98 +99 N/A + + RELATE Relationship to household head [general version] + RELATIVES +01 Head/Householder +02 Spouse +03 Child +04 Child-in-law +05 Parent +06 Parent-in-Law +07 Sibling +08 Sibling-in-Law +09 Grandchild +10 Other relatives + NON-RELATIVES +11 Partner, friend, visitor +12 Other non-relatives +13 Institutional inmates + + RELATED Relationship to household head [detailed version] + RELATIVES +0101 Head/Householder +0201 Spouse +0202 2nd/3rd Wife (Polygamous) +0301 Child +0302 Adopted Child +0303 Stepchild +0304 Adopted, n.s. +0401 Child-in-law +0402 Step Child-in-law +0501 Parent +0502 Stepparent +0601 Parent-in-Law +0602 Stepparent-in-law +0701 Sibling +0702 Step/Half/Adopted Sibling +0801 Sibling-in-Law +0802 Step/Half Sibling-in-law +0901 Grandchild +0902 Adopted Grandchild +0903 Step Grandchild +0904 Grandchild-in-law +1000 Other Relatives: +1001 Other Relatives +1011 Grandparent +1012 Step Grandparent +1013 Grandparent-in-law +1021 Aunt or Uncle +1022 Aunt,Uncle-in-law +1031 Nephew, Niece +1032 Neph/Niece-in-law +1033 Step/Adopted Nephew/Niece +1034 Grand Niece/Nephew +1041 Cousin +1042 Cousin-in-law +1051 Great Grandchild +1061 Other relatives, nec + NON-RELATIVES +1100 Partner, Friend, Visitor +1110 Partner/friend +1111 Friend +1112 Partner +1113 Partner/roommate +1114 Unmarried Partner +1115 Housemate/Roomate +1120 Relative of partner +1130 Concubine/Mistress +1131 Visitor +1132 Companion and family of companion +1139 Allocated partner/friend/visitor + Pre-1940 system: +1200 Other non-relatives +1201 Roomers/boarders/lodgers +1202 Boarders +1203 Lodgers +1204 Roomer +1205 Tenant +1206 Foster child +1210 Employees: +1211 Servant +1212 Housekeeper +1213 Maid +1214 Cook +1215 Nurse +1216 Other probable domestic employee +1217 Other employee +1219 Relative of employee +1221 Military +1222 Students +1223 Members of religious orders +1230 Other non-relatives +1239 Allocated other non-relative + 1940-2000 System: + Household members: +1240 Roomers/boarders/lodgers and foster children +1241 Roomers/boarders/lodgers +1242 Foster children +1250 Employees +1251 Domestic employees +1252 Non-domestic employees +1253 Relative of employee +1260 Other non-relatives (1990 includes employees) + Group quarters members: +1270 Non-inmate 1990 +1281 Head of group quarters +1282 Employees of group quarters +1283 Relative of head, staff, or employee group quarters +1284 Other non-inmate 1940-1959 +1291 Military +1292 College dormitories +1293 Residents of rooming houses +1294 Other non-inmate 1980 (includes employees and non-inmates in +1295 Other non-inmates 1960-1970 (includes employees) +1296 Non-inmates in institutions +1301 Institutional inmates +9996 Unclassifiable +9997 Unknown +9998 Illegible +9999 Missing + + SEX Sex +1 Male +2 Female + + AGE Age +000 Less than 1 year old +001 1 +002 2 +003 3 +004 4 +005 5 +006 6 +007 7 +008 8 +009 9 +010 10 +011 11 +012 12 +013 13 +014 14 +015 15 +016 16 +017 17 +018 18 +019 19 +020 20 +021 21 +022 22 +023 23 +024 24 +025 25 +026 26 +027 27 +028 28 +029 29 +030 30 +031 31 +032 32 +033 33 +034 34 +035 35 +036 36 +037 37 +038 38 +039 39 +040 40 +041 41 +042 42 +043 43 +044 44 +045 45 +046 46 +047 47 +048 48 +049 49 +050 50 +051 51 +052 52 +053 53 +054 54 +055 55 +056 56 +057 57 +058 58 +059 59 +060 60 +061 61 +062 62 +063 63 +064 64 +065 65 +066 66 +067 67 +068 68 +069 69 +070 70 +071 71 +072 72 +073 73 +074 74 +075 75 +076 76 +077 77 +078 78 +079 79 +080 80 +081 81 +082 82 +083 83 +084 84 +085 85 +086 86 +087 87 +088 88 +089 89 +090 90 (90+ in 1980 and 1990) +091 91 +092 92 +093 93 +094 94 +095 95 +096 96 +097 97 +098 98 +099 99 +100 100 (100+ in 1960-1970) +101 101 +102 102 +103 103 +104 104 +105 105 +106 106 +107 107 +108 108 +109 109 +110 110 +111 111 +112 112 (112+ in the 1980 internal data) +113 113 +114 114 +115 115 (115+ in the 1990 internal data) +116 116 +117 117 +118 118 +119 119 +120 120 +121 121 +122 122 +123 123 +124 124 +125 125 +126 126 +129 129 +130 130 +135 135 + + AGEMONTH Age in months +00 0 months old +01 1 month old +02 2 +03 3 +04 4 +05 5 +06 6 +07 7 +08 8 +09 9 +10 10 +11 11 +12 12 +98 Unknown/illegible +99 N/A or blank + + MARST Marital status +1 Married, spouse present +2 Married, spouse absent +3 Separated +4 Divorced +5 Widowed +6 Never married/single + + MARRNO Times married +0 Not Applicable +1 Married once +2 Married twice (or more) +3 Married thrice (or more) +4 Four times +5 Five times +6 Six times +7 Unknown +8 Illegible +9 Missing + + AGEMARR Age at first marriage +00 N/A and missing +12 12 years old +13 13 +14 14 +15 15 +16 16 +17 17 +18 18 +19 19 +20 20 +21 21 +22 22 +23 23 +24 24 +25 25 +26 26 +27 27 +28 28 +29 29 +30 30 +31 31 +32 32 +33 33 +34 34 +35 35 +36 36 +37 37 +38 38 +39 39 +40 40 +41 41 +42 42 +43 43 +44 44 +45 45 +46 46 +47 47 +48 48 +49 49 +50 50 +51 51 +52 52 +53 53 +54 54 +55 55 +56 56 +57 57 +58 58 +59 59 +60 60 +61 61 +62 62 +63 63 +64 64 +65 65 +66 66 +67 67 +68 68 +69 69 +70 70 +71 71 +72 72 +73 73 +74 74 +75 75 +76 76 +77 77 +78 78 +79 79 +80 80 +81 81 +82 82 +83 83 +84 84 +85 85 +86 86 +87 87 +88 88 +89 89 +90 90 +91 91 +92 92 +93 93 +94 94 +95 95 +96 96 +97 97 +98 98 +99 99+ + + CHBORN Children ever born +00 N/A +01 No children +02 1 child +03 2 children +04 3 +05 4 +06 5 +07 6 +08 7 +09 8 +10 9 +11 10 +12 11 +13 12 (12+ 1960-1990) +14 13 +15 14 +16 15 +17 16 +18 17 +19 18 +20 19 +21 20 +22 21 +23 22 +24 23 +25 24 +26 25 (25+ 1950) +27 26 +28 27 +29 28 +30 29 +31 30 +32 31 +33 32 +34 33 +35 34 +36 35 +37 36 +38 37 +39 38 +40 39 +41 40 +42 41 +43 42 +44 43 +45 44 +46 45 +47 46 +48 47 +49 48 +50 49 +51 50 +52 51 +53 52 +54 53 +55 54 +56 55 +57 56 +58 57 +61 60 +87 87 + + RACE Race [general version] +1 White +2 Black/African American/Negro +3 American Indian or Alaska Native +4 Chinese +5 Japanese +6 Other Asian or Pacific Islander +7 Other race, nec +8 Two major races +9 Three or more major races + + RACED Race [detailed version] + ONE MAJOR RACE GROUP +100 White +110 Spanish write_in +120 Blank (white) (1850) +130 Portuguese +140 Mexican (1930) +150 Puerto Rican (1910 Hawaii) +200 Black/African American/Negro +210 Mulatto +300 American Indian/Alaska Native + American Indian: +302 Apache +303 Blackfoot +304 Cherokee +305 Cheyenne +306 Chickasaw +307 Chippewa +308 Choctaw +309 Comanche +310 Creek +311 Crow +312 Iroquois +313 Kiowa +314 Lumbee +315 Navajo +316 Osage +317 Paiute +318 Pima +319 Potawatomi +320 Pueblo +321 Seminole +322 Shoshone +323 Sioux +324 Tlingit (Tlingit_Haida, 2000/ACS) +325 Tohono O Odham +326 All other tribes (1990) +328 Hopi +329 Central American Indian +330 Spanish American Indian +350 Delaware +351 Latin American Indian +352 Puget Sound Salish +353 Yakama +354 Yaqui +355 Colville +356 Houma +357 Menominee +358 Yuman +359 South American Indian +360 Mexican American Indian +361 Other Amer. Indian tribe (2000,ACS) +362 2+ Amer. Indian tribes (2000,ACS) + Alaskan Native: +370 Alaskan Athabaskan +371 Aleut +372 Eskimo +373 Alaskan mixed +374 Inupiat +375 Yup'ik +379 Other Alaska Native tribe(s) (2000,ACS) +398 Both Am. Ind. and Alaska Native (2000,ACS) +399 Tribe not specified + Asian or Pacific Islander: +400 Chinese +410 Taiwanese +420 Chinese and Taiwanese +500 Japanese +600 Filipino +610 Asian Indian (Hindu 1920_1940) +620 Korean +630 Hawaiian +631 Hawaiian and Asian (1900,1920) +632 Hawaiian and European (1900,1920) +634 Hawaiian mixed +640 Vietnamese +641 Bhutanese +642 Mongolian +643 Nepalese +650 Other Asian or Pacific Islander (1920,1980) +651 Asian only (CPS) +652 Pacific Islander only (CPS) +653 Asian or Pacific Islander, n.s. (1990 Internal Census files) +660 Cambodian +661 Hmong +662 Laotian +663 Thai +664 Bangladeshi +665 Burmese +666 Indonesian +667 Malaysian +668 Okinawan +669 Pakistani +670 Sri Lankan +671 Other Asian, n.e.c. +672 Asian, not specified + Two or more Asian races +673 Chinese and Japanese +674 Chinese and Filipino +675 Chinese and Vietnamese +676 Chinese and Asian write_in +677 Japanese and Filipino +678 Asian Indian and Asian write_in +679 Other Asian race combinations + Polynesian region (also includes Hawaii): +680 Samoan +681 Tahitian +682 Tongan +683 Other Polynesian (1990) +684 1+ other Polynesian races (2000,ACS) + Micronesian region: +685 Guamanian/Chamorro +686 Northern Mariana Islander +687 Palauan +688 Other Micronesian (1990) +689 1+ other Micronesian races (2000,ACS) + Melanesian region: +690 Fijian +691 Other Melanesian (1990) +692 1+ other Melanesian races (2000,ACS) + Pacific Islanders from two or more PI regions: +698 2+ PI races from 2+ PI regions +699 Pacific Islander, n.s. +700 Other race, n.e.c. + Two major race groups: +801 White and Black +802 White and AIAN +810 White and Asian +811 White and Chinese +812 White and Japanese +813 White and Filipino +814 White and Asian Indian +815 White and Korean +816 White and Vietnamese +817 White and Asian write_in +818 White and other Asian race(s) +819 White and two or more Asian groups +820 White and PI +821 White and Native Hawaiian +822 White and Samoan +823 White and Guamanian +824 White and PI write_in +825 White and other PI race(s) +826 White and other race write_in +827 White and other race, n.e.c. +830 Black and AIAN +831 Black and Asian +832 Black and Chinese +833 Black and Japanese +834 Black and Filipino +835 Black and Asian Indian +836 Black and Korean +837 Black and Asian write_in +838 Black and other Asian race(s) +840 Black and PI +841 Black and PI write_in +842 Black and other PI race(s) +845 Black and other race write_in +850 AIAN and Asian +851 AIAN and Filipino (2000 1%) +852 AIAN and Asian Indian +853 AIAN and Asian write_in (2000 1%) +854 AIAN and other Asian race(s) +855 AIAN and PI +856 AIAN and other race write_in +860 Asian and PI +861 Chinese and Hawaiian +862 Chinese, Filipino, Hawaiian (2000 1%) +863 Japanese and Hawaiian (2000 1%) +864 Filipino and Hawaiian +865 Filipino and PI write_in +866 Asian Indian and PI write_in (2000 1%) +867 Asian write_in and PI write_in +868 Other Asian race(s) and PI race(s) +869 Japanese and Korean (ACS) +880 Asian and other race write_in +881 Chinese and other race write_in +882 Japanese and other race write_in +883 Filipino and other race write_in +884 Asian Indian and other race write_in +885 Asian write_in and other race write_in +886 Other Asian race(s) and other race write_in +887 Chinese and Korean +890 PI and other race write_in: +891 PI write_in and other race write_in +892 Other PI race(s) and other race write_in +893 Native Hawaiian or PI other race(s) +899 API and other race write_in + Three major race groups: +901 White, Black, AIAN +902 White, Black, Asian +903 White, Black, PI +904 White, Black, other race write_in +905 White, AIAN, Asian +906 White, AIAN, PI +907 White, AIAN, other race write_in +910 White, Asian, PI +911 White, Chinese, Hawaiian +912 White, Chinese, Filipino, Hawaiian (2000 1%) +913 White, Japanese, Hawaiian (2000 1%) +914 White, Filipino, Hawaiian +915 Other White, Asian race(s), PI race(s) +916 White, AIAN and Filipino +917 White, Black, and Filipino +920 White, Asian, other race write_in +921 White, Filipino, other race write_in (2000 1%) +922 White, Asian write_in, other race write_in (2000 1%) +923 Other White, Asian race(s), other race write_in (2000 1%) +925 White, PI, other race write_in +930 Black, AIAN, Asian +931 Black, AIAN, PI +932 Black, AIAN, other race write_in +933 Black, Asian, PI +934 Black, Asian, other race write_in +935 Black, PI, other race write_in +940 AIAN, Asian, PI +941 AIAN, Asian, other race write_in +942 AIAN, PI, other race write_in +943 Asian, PI, other race write_in +944 Asian (Chinese, Japanese, Korean, Vietnamese); and Native Hawaiian or PI; and Other +949 2 or 3 races (CPS) + Four major race groups: +950 White, Black, AIAN, Asian +951 White, Black, AIAN, PI +952 White, Black, AIAN, other race write_in +953 White, Black, Asian, PI +954 White, Black, Asian, other race write_in +955 White, Black, PI, other race write_in +960 White, AIAN, Asian, PI +961 White, AIAN, Asian, other race write_in +962 White, AIAN, PI, other race write_in +963 White, Asian, PI, other race write_in +964 White, Chinese, Japanese, Native Hawaiian +970 Black, AIAN, Asian, PI +971 Black, AIAN, Asian, other race write_in +972 Black, AIAN, PI, other race write_in +973 Black, Asian, PI, other race write_in +974 AIAN, Asian, PI, other race write_in +975 AIAN, Asian, PI, Hawaiian other race write_in +976 Two specified Asian (Chinese and other Asian, Chinese and Japanese, Japanese and other Asian, Korean and other Asian); Native Hawaiian/PI; and Other Race + Five major race groups: +980 White, Black, AIAN, Asian, PI +981 White, Black, AIAN, Asian, other race write_in +982 White, Black, AIAN, PI, other race write_in +983 White, Black, Asian, PI, other race write_in +984 White, AIAN, Asian, PI, other race write_in +985 Black, AIAN, Asian, PI, other race write_in +986 Black, AIAN, Asian, PI, Hawaiian, other race write_in +989 4 or 5 races (CPS) + Six major race groups: +990 White, Black, AIAN, Asian, PI, other race write_in +991 White race; Some other race; Black or African American race and/or American Indian and Alaska Native race and/or Asian groups and/or Native Hawaiian and Other Pacific Islander groups +996 2+ races, n.e.c. (CPS) + + HISPAN Hispanic origin [general version] +0 Not Hispanic +1 Mexican +2 Puerto Rican +3 Cuban +4 Other +9 Not Reported + + HISPAND Hispanic origin [detailed version] +000 Not Hispanic +100 Mexican +102 Mexican American +103 Mexicano/Mexicana +104 Chicano/Chicana +105 La Raza +106 Mexican American Indian +107 Mexico +200 Puerto Rican +300 Cuban + Other: + Central American +401 Central American Indian +402 Canal Zone +411 Costa Rican +412 Guatemalan +413 Honduran +414 Nicaraguan +415 Panamanian +416 Salvadoran +417 Central American, n.e.c. + South American +420 Argentinean +421 Bolivian +422 Chilean +423 Colombian +424 Ecuadorian +425 Paraguayan +426 Peruvian +427 Uruguayan +428 Venezuelan +429 South American Indian +430 Criollo +431 South American, n.e.c. + Spaniard +450 Spaniard +451 Andalusian +452 Asturian +453 Castillian +454 Catalonian +455 Balearic Islander +456 Gallego +457 Valencian +458 Canarian +459 Spanish Basque +460 Dominican +465 Latin American +470 Hispanic +480 Spanish +490 Californio +491 Tejano +492 Nuevo Mexicano +493 Spanish American +494 Spanish American Indian +495 Meso American Indian +496 Mestizo +498 Other, n.s. +499 Other, n.e.c. +900 Not Reported + + BPL Birthplace [general version] + UNITED STATES +001 Alabama +002 Alaska +004 Arizona +005 Arkansas +006 California +008 Colorado +009 Connecticut +010 Delaware +011 District of Columbia +012 Florida +013 Georgia +015 Hawaii +016 Idaho +017 Illinois +018 Indiana +019 Iowa +020 Kansas +021 Kentucky +022 Louisiana +023 Maine +024 Maryland +025 Massachusetts +026 Michigan +027 Minnesota +028 Mississippi +029 Missouri +030 Montana +031 Nebraska +032 Nevada +033 New Hampshire +034 New Jersey +035 New Mexico +036 New York +037 North Carolina +038 North Dakota +039 Ohio +040 Oklahoma +041 Oregon +042 Pennsylvania +044 Rhode Island +045 South Carolina +046 South Dakota +047 Tennessee +048 Texas +049 Utah +050 Vermont +051 Virginia +053 Washington +054 West Virginia +055 Wisconsin +056 Wyoming +090 Native American +099 United States, ns + US OUTLYING AREAS/TERRITORIES +100 American Samoa +105 Guam +110 Puerto Rico +115 U.S. Virgin Islands +120 Other US Possessions + OTHER NORTH AMERICA +150 Canada +155 St. Pierre and Miquelon +160 Atlantic Islands +199 North America, ns + CENTRAL AMERICA AND CARIBBEAN +200 Mexico +210 Central America + Caribbean: +250 Cuba +260 West Indies +299 Americas, n.s. +300 SOUTH AMERICA + EUROPE + Northern Europe: +400 Denmark +401 Finland +402 Iceland +403 Lapland, n.s. +404 Norway +405 Sweden + United Kingdom and Ireland: +410 England +411 Scotland +412 Wales +413 United Kingdom, ns +414 Ireland +419 Northern Europe, ns + Western Europe: +420 Belgium +421 France +422 Liechtenstein +423 Luxembourg +424 Monaco +425 Netherlands +426 Switzerland +429 Western Europe, ns + Southern Europe: +430 Albania +431 Andorra +432 Gibraltar +433 Greece +434 Italy +435 Malta +436 Portugal +437 San Marino +438 Spain +439 Vatican City +440 Southern Europe, ns + Central/Eastern Europe: +450 Austria +451 Bulgaria +452 Czechoslovakia +453 Germany +454 Hungary +455 Poland +456 Romania +457 Yugoslavia +458 Central Europe, ns +459 Eastern Europe, ns + Russian Empire: + Baltic States: +460 Estonia +461 Latvia +462 Lithuania +463 Baltic States, ns +465 Other USSR/Russia +499 Europe, ns + ASIA + East Asia: +500 China +501 Japan +502 Korea +509 East Asia, ns + Southeast Asia: +510 Brunei +511 Cambodia (Kampuchea) +512 Indonesia +513 Laos +514 Malaysia +515 Philippines +516 Singapore +517 Thailand +518 Vietnam +519 Southeast Asia, ns + India/Southwest Asia: +520 Afghanistan +521 India +522 Iran +523 Maldives +524 Nepal + Middle East/Asia Minor: +530 Bahrain +531 Cyprus +532 Iraq +533 Iraq/Saudi Arabia +534 Israel/Palestine +535 Jordan +536 Kuwait +537 Lebanon +538 Oman +539 Qatar +540 Saudi Arabia +541 Syria +542 Turkey +543 United Arab Emirates +544 Yemen Arab Republic (North) +545 Yemen, PDR (South) +546 Persian Gulf States, n.s. +547 Middle East, ns +548 Southwest Asia, nec/ns +549 Asia Minor, ns +550 South Asia, nec +599 Asia, nec/ns + AFRICA +600 AFRICA + Northern Africa: + West Africa: + East Africa: + Central Africa: + Southern Africa: + OCEANIA +700 Australia and New Zealand +710 Pacific Islands +800 Antarctica, ns/nec +900 Abroad (unknown) or at sea +950 Other n.e.c. +999 Missing/blank + + BPLD Birthplace [detailed version] + UNITED STATES +00100 Alabama +00200 Alaska +00400 Arizona +00500 Arkansas +00600 California +00800 Colorado +00900 Connecticut +01000 Delaware +01100 District of Columbia +01200 Florida +01300 Georgia +01500 Hawaii +01600 Idaho +01610 Idaho Territory +01700 Illinois +01800 Indiana +01900 Iowa +02000 Kansas +02100 Kentucky +02200 Louisiana +02300 Maine +02400 Maryland +02500 Massachusetts +02600 Michigan +02700 Minnesota +02800 Mississippi +02900 Missouri +03000 Montana +03100 Nebraska +03200 Nevada +03300 New Hampshire +03400 New Jersey +03500 New Mexico +03510 New Mexico Territory +03600 New York +03700 North Carolina +03800 North Dakota +03900 Ohio +04000 Oklahoma +04010 Indian Territory +04100 Oregon +04200 Pennsylvania +04400 Rhode Island +04500 South Carolina +04600 South Dakota +04610 Dakota Territory +04700 Tennessee +04800 Texas +04900 Utah +04910 Utah Territory +05000 Vermont +05100 Virginia +05300 Washington +05400 West Virginia +05500 Wisconsin +05600 Wyoming +05610 Wyoming Territory +09000 Native American +09900 United States, ns + US OUTLYING AREAS/TERRITORIES +10000 American Samoa +10010 Samoa, 1940-1950 +10500 Guam +11000 Puerto Rico +11500 U.S. Virgin Islands +11510 St. Croix +11520 St. John +11530 St. Thomas +12000 Other US Possessions: +12010 Johnston Atoll +12020 Midway Islands +12030 Wake Island +12040 Other US Caribbean Islands +12041 Navassa Island +12050 Other US Pacific Islands +12051 Baker Island +12052 Howland Island +12053 Jarvis Island +12054 Kingman Reef +12055 Palmyra Atoll +12056 Canton and Enderbury Island +12090 US outlying areas, ns +12091 US possessions, ns +12092 US territory, ns + OTHER NORTH AMERICA +15000 Canada +15010 English Canada +15011 British Columbia +15013 Alberta +15015 Saskatchewan +15017 Northwest +15019 Ruperts Land +15020 Manitoba +15021 Red River +15030 Ontario/Upper Canada +15031 Upper Canada +15032 Canada West +15040 New Brunswick +15050 Nova Scotia +15051 Cape Breton +15052 Halifax +15060 Prince Edward Island +15070 Newfoundland +15080 French Canada +15081 Quebec +15082 Lower Canada +15083 Canada East +15500 St. Pierre and Miquelon +16000 Atlantic Islands +16010 Bermuda +16020 Cape Verde +16030 Falkland Islands +16040 Greenland +16050 St. Helena and Ascension +16060 Canary Islands +19900 North America, ns + CENTRAL AMERICA AND CARIBBEAN +20000 Mexico +21000 Central America +21010 Belize/British Honduras +21020 Costa Rica +21030 El Salvador +21040 Guatemala +21050 Honduras +21060 Nicaragua +21070 Panama +21071 Canal Zone +21090 Central America, ns + Caribbean: +25000 Cuba +26000 West Indies +26010 Dominican Republic +26020 Haiti +26030 Jamaica +26040 British West Indies +26041 Anguilla +26042 Antigua-Barbuda +26043 Bahamas +26044 Barbados +26045 British Virgin Islands +26046 Anegada +26047 Cooper +26048 Jost Van Dyke +26049 Peter +26050 Tortola +26051 Virgin Gorda +26052 Br. Virgin Islands, ns +26053 Cayman Islands +26054 Dominica +26055 Grenada +26056 Montserrat +26057 St. Kitts-Nevis +26058 St. Lucia +26059 St. Vincent +26060 Trinidad and Tobago +26061 Turks and Caicos +26069 Br. Virgin Islands, ns +26070 Other West Indies + Dutch West Indies: +26071 Aruba +26072 Netherlands Antilles +26073 Bonaire +26074 Curacao +26075 Dutch St. Maarten +26076 Saba +26077 St. Eustatius +26079 Dutch Caribbean, ns + French West Indies: +26080 French St. Maarten +26081 Guadeloupe +26082 Martinique +26083 St. Barthelemy +26089 French Caribbean, ns +26090 Antilles, ns +26091 Caribbean, ns +26092 Latin America, ns +26093 Leeward Islands, ns +26094 West Indies, ns +26095 Windward Islands, ns +29900 Americas, ns +30000 South America +30005 Argentina +30010 Bolivia +30015 Brazil +30020 Chile +30025 Colombia +30030 Ecuador +30035 French Guiana +30040 Guyana/British Guiana +30045 Paraguay +30050 Peru +30055 Suriname +30060 Uruguay +30065 Venezuela +30090 South America, ns +30091 South and Central America, n.s. + EUROPE + Northern Europe: +40000 Denmark +40010 Faeroe Islands +40100 Finland +40200 Iceland +40300 Lapland, ns +40400 Norway +40410 Svalbard and Jan Meyen +40411 Svalbard +40412 Jan Meyen +40500 Sweden + United Kingdom and Ireland: +41000 England +41010 Channel Islands +41011 Guernsey +41012 Jersey +41020 Isle of Man +41100 Scotland +41200 Wales +41300 United Kingdom, ns +41400 Ireland +41410 Northern Ireland +41900 Northern Europe, ns + Western Europe: +42000 Belgium +42100 France +42110 Alsace-Lorraine +42111 Alsace +42112 Lorraine +42200 Liechtenstein +42300 Luxembourg +42400 Monaco +42500 Netherlands +42600 Switzerland +42900 Western Europe, ns + Southern Europe: +43000 Albania +43100 Andorra +43200 Gibraltar +43300 Greece +43310 Dodecanese Islands +43320 Turkey Greece +43330 Macedonia +43400 Italy +43500 Malta +43600 Portugal +43610 Azores +43620 Madeira Islands +43630 Cape Verde Islands +43640 St. Miguel +43700 San Marino +43800 Spain +43900 Vatican City +44000 Southern Europe, ns + Central/Eastern Europe: +45000 Austria +45010 Austria-Hungary +45020 Austria-Graz +45030 Austria-Linz +45040 Austria-Salzburg +45050 Austria-Tyrol +45060 Austria-Vienna +45070 Austria-Kaernsten +45080 Austria-Neustadt +45100 Bulgaria +45200 Czechoslovakia +45210 Bohemia +45211 Bohemia-Moravia +45212 Slovakia +45213 Czech Republic +45300 Germany +45301 Berlin +45302 West Berlin +45303 East Berlin + West Germany +45310 West Germany +45311 Baden +45312 Bavaria +45313 Braunschweig +45314 Bremen +45315 Hamburg +45316 Hanover +45317 Hessen +45318 Hesse-Nassau +45319 Lippe +45320 Lubeck +45321 Oldenburg +45322 Rheinland +45323 Schaumburg-Lippe +45324 Schleswig +45325 Sigmaringen +45326 Schwarzburg +45327 Westphalia +45328 Wurttemberg +45329 Waldeck +45330 Wittenberg +45331 Frankfurt +45332 Saarland +45333 Nordrhein-Westfalen + East Germany +45340 East Germany +45341 Anhalt +45342 Brandenburg +45344 Kingdom of Saxony +45345 Mecklenburg +45346 Saxony +45347 Thuringian States +45348 Sachsen-Meiningen +45349 Sachsen-Weimar-Eisenach +45350 Probable Saxony +45351 Schwerin +45352 Strelitz +45353 Probably Thuringian States +45360 Prussia, nec +45361 Hohenzollern +45362 Niedersachsen +45400 Hungary +45500 Poland +45510 Austrian Poland +45511 Galicia +45520 German Poland +45521 East Prussia +45522 Pomerania +45523 Posen +45524 Prussian Poland +45525 Silesia +45526 West Prussia +45530 Russian Poland +45600 Romania +45610 Transylvania +45700 Yugoslavia +45710 Croatia +45720 Montenegro +45730 Serbia +45740 Bosnia +45750 Dalmatia +45760 Slovonia +45770 Carniola +45780 Slovenia +45790 Kosovo +45800 Central Europe, ns +45900 Eastern Europe, ns + Russian Empire: + Baltic States: +46000 Estonia +46100 Latvia +46200 Lithuania +46300 Baltic States, ns +46500 Other USSR/Russia +46510 Byelorussia +46520 Moldavia +46521 Bessarabia +46530 Ukraine +46540 Armenia +46541 Azerbaijan +46542 Republic of Georgia +46543 Kazakhstan +46544 Kirghizia +46545 Tadzhik +46546 Turkmenistan +46547 Uzbekistan +46548 Siberia +46590 USSR, ns +49900 Europe, ns. + ASIA + East Asia: +50000 China +50010 Hong Kong +50020 Macau +50030 Mongolia +50040 Taiwan +50100 Japan +50200 Korea +50210 North Korea +50220 South Korea +50900 East Asia, ns + Southeast Asia: +51000 Brunei +51100 Cambodia (Kampuchea) +51200 Indonesia +51210 East Indies +51220 East Timor +51300 Laos +51400 Malaysia +51500 Philippines +51600 Singapore +51700 Thailand +51800 Vietnam +51900 Southeast Asia, ns +51910 Indochina, ns + India/Southwest Asia: +52000 Afghanistan +52100 India +52110 Bangladesh +52120 Bhutan +52130 Burma (Myanmar) +52140 Pakistan +52150 Sri Lanka (Ceylon) +52200 Iran +52300 Maldives +52400 Nepal + Middle East/Asia Minor: +53000 Bahrain +53100 Cyprus +53200 Iraq +53210 Mesopotamia +53300 Iraq/Saudi Arabia + Neutral Zone +53400 Israel/Palestine +53410 Gaza Strip +53420 Palestine +53430 West Bank +53440 Israel +53500 Jordan +53600 Kuwait +53700 Lebanon +53800 Oman +53900 Qatar +54000 Saudi Arabia +54100 Syria +54200 Turkey +54210 European Turkey +54220 Asian Turkey +54300 United Arab Emirates +54400 Yemen Arab Republic (North) +54500 Yemen, PDR (South) +54600 Persian Gulf States, ns +54700 Middle East, ns +54800 Southwest Asia, nec/ns +54900 Asia Minor, ns +55000 South Asia, nec +59900 Asia, nec/ns + AFRICA +60000 Africa + Northern Africa: +60010 Northern Africa +60011 Algeria +60012 Egypt/United Arab Rep. +60013 Libya +60014 Morocco +60015 Sudan +60016 Tunisia +60017 Western Sahara +60019 North Africa, ns + West Africa: +60020 Benin +60021 Burkina Faso +60022 Gambia +60023 Ghana +60024 Guinea +60025 Guinea-Bissau +60026 Ivory Coast +60027 Liberia +60028 Mali +60029 Mauritania +60030 Niger +60031 Nigeria +60032 Senegal +60033 Sierra Leone +60034 Togo +60038 Western Africa, ns +60039 French West Africa, ns + East Africa: +60040 British Indian Ocean Territory +60041 Burundi +60042 Comoros +60043 Djibouti +60044 Ethiopia +60045 Kenya +60046 Madagascar +60047 Malawi +60048 Mauritius +60049 Mozambique +60050 Reunion +60051 Rwanda +60052 Seychelles +60053 Somalia +60054 Tanzania +60055 Uganda +60056 Zambia +60057 Zimbabwe +60058 Bassas de India +60059 Europa +60060 Gloriosos +60061 Juan de Nova +60062 Mayotte +60063 Tromelin +60064 Eastern Africa, nec/ns +60065 Eritrea + Central Africa: +60070 Central Africa +60071 Angola +60072 Cameroon +60073 Central African Republic +60074 Chad +60075 Congo +60076 Equatorial Guinea +60077 Gabon +60078 Sao Tome and Principe +60079 Zaire +60080 Central Africa, ns +60081 Equatorial Africa, ns +60082 French Equatorial Africa, ns + Southern Africa: +60090 Southern Africa +60091 Botswana +60092 Lesotho +60093 Namibia +60094 South Africa (Union of) +60095 Swaziland +60096 Southern Africa, ns +60099 Africa, ns/nec + OCEANIA +70000 Australia and New Zealand +70010 Australia +70011 Ashmore and Cartier Islands +70012 Coral Sea Islands Territory +70013 Christmas Island +70014 Cocos Islands +70020 New Zealand +71000 Pacific Islands + Melanesia: +71010 New Caledonia +71012 Papua New Guinea +71013 Solomon Islands +71014 Vanuatu (New Hebrides) +71015 Fiji +71016 Melanesia, ns + Polynesia: +71017 Norfolk Islands +71018 Niue +71020 Cook Islands +71022 French Polynesia +71023 Tonga +71024 Wallis and Futuna Islands +71025 Western Samoa +71026 Pitcairn Island +71027 Tokelau +71028 Tuvalu +71029 Polynesia, ns + Micronesia: +71032 Kiribati +71033 Canton and Enderbury +71034 Nauru +71039 Micronesia, ns + US Pacific Trust Territories: +71040 US Pacific Trust Territories +71041 Marshall Islands +71042 Micronesia +71043 Kosrae +71044 Pohnpei +71045 Truk +71046 Yap +71047 Northern Mariana Islands +71048 Palau +71049 Pacific Trust Terr, ns +71050 Clipperton Island +71090 Oceania, ns/nec +80000 Antarctica, ns/nec +80010 Bouvet Islands +80020 British Antarctic Terr. +80030 Dronning Maud Land +80040 French Southern and Antarctic Lands +80050 Heard and McDonald Islands +90000 Abroad (unknown) or at sea +90010 Abroad, ns +90011 Abroad (US citizen) +90020 At sea +90021 At sea (US citizen) +90022 At sea or abroad (U.S. citizen) + Other unknowns: +95000 Other n.e.c. +99900 Missing/blank + + MBPL Mother's birthplace [general version] +000 Not Applicable + UNITED STATES +001 Alabama +002 Alaska +004 Arizona +005 Arkansas +006 California +008 Colorado +009 Connecticut +010 Delaware +011 District of Columbia +012 Florida +013 Georgia +015 Hawaii +016 Idaho +017 Illinois +018 Indiana +019 Iowa +020 Kansas +021 Kentucky +022 Louisiana +023 Maine +024 Maryland +025 Massachusetts +026 Michigan +027 Minnesota +028 Mississippi +029 Missouri +030 Montana +031 Nebraska +032 Nevada +033 New Hampshire +034 New Jersey +035 New Mexico +036 New York +037 North Carolina +038 North Dakota +039 Ohio +040 Oklahoma +041 Oregon +042 Pennsylvania +044 Rhode Island +045 South Carolina +046 South Dakota +047 Tennessee +048 Texas +049 Utah +050 Vermont +051 Virginia +053 Washington +054 West Virginia +055 Wisconsin +056 Wyoming +090 Native American +099 United States, ns + US OUTLYING AREAS / TERRITORIES +100 American Samoa +105 Guam +110 Puerto Rico +115 U.S. Virgin Islands +120 Other US Possessions + OTHER NORTH AMERICA +150 Canada +155 St. Pierre and Miquelon +160 Atlantic Islands +199 North America, n.s. + CENTRAL AMERICA AND CARIBBEAN +200 Mexico +210 Central America + Caribbean: +250 Cuba +260 West Indies +299 Americas, n.s. +300 SOUTH AMERICA + EUROPE + Northern Europe: +400 Denmark +401 Finland +402 Iceland +403 Lapland, n.s. +404 Norway +405 Sweden + United Kingdom and Ireland: +410 England +411 Scotland +412 Wales +413 United Kingdom, ns +414 Ireland +419 Northern Europe, ns + Western Europe: +420 Belgium +421 France +422 Liechtenstein +423 Luxembourg +424 Monaco +425 Netherlands +426 Switzerland +429 Western Europe, ns + Southern Europe: +430 Albania +431 Andorra +432 Gibraltar +433 Greece +434 Italy +435 Malta +436 Portugal +437 San Marino +438 Spain +439 Vatican City +440 Southern Europe, n.s. + Central/Eastern Europe: +450 Austria +451 Bulgaria +452 Czechoslovakia +453 Germany +454 Hungary +455 Poland +456 Romania +457 Yugoslavia +458 Central Europe, ns +459 Eastern Europe, n.s. + Russian Empire: + Baltic States: +460 Estonia +461 Latvia +462 Lithuania +463 Baltic States, ns +465 Other USSR/Russia +499 Europe, nec/ns + ASIA + East Asia: +500 China +501 Japan +502 Korea +509 East Asia, n.s. + Southeast Asia: +510 Brunei +511 Cambodia (Kampuchea) +512 Indonesia +513 Laos +514 Malaysia +515 Philippines +516 Singapore +517 Thailand +518 Vietnam +519 Southeast Asia, ns + India/Southwest Asia: +520 Afghanistan +521 India +522 Iran +523 Maldives +524 Nepal + Middle East/Asia Minor: +530 Bahrain +531 Cyprus +532 Iraq +533 Iraq/Saudi Arabia +534 Israel/Palestine +535 Jordan +536 Kuwait +537 Lebanon +538 Oman +539 Qatar +540 Saudi Arabia +541 Syria +542 Turkey +543 United Arab Emirates +544 Yemen Arab Republic (North) +545 Yemen, PDR (South) +546 Persian Gulf States, n.s. +547 Middle East, n.s. +548 Southwest Asia, nec/ns +549 Asia Minor, n.s. +550 South Asia, n.e.c. +599 Asia, nec/ns + AFRICA +600 AFRICA + Northern Africa: + West Africa: + East Africa: + Central Africa: + Southern Africa: + OCEANIA +700 Australia and New Zealand +710 Pacific Islands +900 Abroad (unknown) or at sea +950 Other n.e.c. +997 Unknown +999 Missing/blank + + MBPLD Mother's birthplace [detailed version] +00000 Not Applicable + UNITED STATES +00100 Alabama +00200 Alaska +00400 Arizona +00500 Arkansas +00600 California +00800 Colorado +00900 Connecticut +01000 Delaware +01100 District of Columbia +01200 Florida +01300 Georgia +01500 Hawaii +01600 Idaho +01610 Idaho Territory +01700 Illinois +01800 Indiana +01900 Iowa +02000 Kansas +02100 Kentucky +02200 Louisiana +02300 Maine +02400 Maryland +02500 Massachusetts +02600 Michigan +02700 Minnesota +02800 Mississippi +02900 Missouri +03000 Montana +03100 Nebraska +03200 Nevada +03300 New Hampshire +03400 New Jersey +03500 New Mexico +03510 New Mexico Territory +03600 New York +03700 North Carolina +03800 North Dakota +03900 Ohio +04000 Oklahoma +04010 Indian Territory +04100 Oregon +04200 Pennsylvania +04400 Rhode Island +04500 South Carolina +04600 South Dakota +04610 Dakota Territory +04700 Tennessee +04800 Texas +04900 Utah +04910 Utah Territory +05000 Vermont +05100 Virginia +05300 Washington +05400 West Virginia +05500 Wisconsin +05600 Wyoming +05610 Wyoming Territory +09000 Native American +09900 United States, n.s. + US OUTLYING AREAS / TERRITORIES +10000 American Samoa +10010 Samoa, 1940-1950 +10500 Guam +11000 Puerto Rico +11500 U.S. Virgin Islands +11510 St. Croix +11520 St. John +11530 St. Thomas +12000 Other US Possessions +12010 Johnston Atoll +12020 Midway Islands +12030 Wake Island +12040 Other US Caribbean Islands +12041 Navassa Island +12050 Other US Pacific Is. +12051 Baker Island +12052 Howland Island +12053 Jarvis Island +12054 Kingman Reef +12055 Palmyra Atoll +12056 Canton and Enderbury Island +12090 US outlying areas, ns +12091 US Possessions, n.s. +12092 US territory, ns + OTHER NORTH AMERICA +15000 Canada +15010 English Canada +15011 British Columbia +15013 Alberta +15015 Saskatchewan +15017 Northwest +15019 Ruperts Land +15020 Manitoba +15021 Red River +15030 Ontario/Upper Canada +15031 Upper Canada +15032 Canada West +15040 New Brunswick +15050 Nova Scotia +15051 Cape Breton +15052 Halifax +15060 Prince Edward Island +15070 Newfoundland +15080 French Canada +15081 Quebec +15082 Lower Canada +15083 Canada East +15500 St. Pierre and Miquelon +16000 Atlantic Islands +16010 Bermuda +16020 Cape Verde +16030 Falkland Islands +16040 Greenland +16050 St. Helena and Ascension +16060 Canary Islands +19900 North America, n.s. + CENTRAL AMERICA AND CARIBBEAN +20000 Mexico +21000 Central America +21010 Belize/British Honduras +21020 Costa Rica +21030 El Salvador +21040 Guatemala +21050 Honduras +21060 Nicaragua +21070 Panama +21071 Canal Zone +21090 Central America, ns + Caribbean: +25000 Cuba +26000 West Indies +26010 Dominican Republic +26020 Haiti +26030 Jamaica +26040 British West Indies +26041 Anguilla +26042 Antigua-Barbuda +26043 Bahamas +26044 Barbados +26045 British Virgin Islands +26046 Anegada +26047 Cooper +26048 Jost Van Dyke +26049 Peter +26050 Tortola +26051 Virgin Gorda +26052 Br. Virgin Islands, ns +26053 Cayman Isles +26054 Dominica +26055 Grenada +26056 Montserrat +26057 St. Kitts-Nevis +26058 St. Lucia +26059 St. Vincent +26060 Trinidad and Tobago +26061 Turks and Caicos +26069 British West Indies, ns +26070 Other West Indies + Dutch West Indies: +26071 Aruba +26072 Netherlands Antilles +26073 Bonaire +26074 Curacao +26075 Dutch St. Maarten +26076 Saba +26077 St. Eustatius +26079 Dutch Caribbean, ns + French West Indies: +26080 French St. Maarten +26081 Guadeloupe +26082 Martinique +26083 St. Barthelemy +26089 French Caribbean, ns +26090 Antilles, n.s. +26091 Caribbean, n.s. / n.e.c. +26092 Latin America, ns +26093 Leeward Islands, n.s. +26094 West Indies, ns +26095 Winward Islands +29900 Americas, ns +30000 SOUTH AMERICA +30005 Argentina +30010 Bolivia +30015 Brazil +30020 Chile +30025 Colombia +30030 Ecuador +30035 French Guiana +30040 Guyana/British Guiana +30045 Paraguay +30050 Peru +30055 Suriname +30060 Uruguay +30065 Venezuela +30090 South America, n.s. +30091 South and Central America, n.s. + EUROPE + Northern Europe: +40000 Denmark +40010 Faroe Islands +40100 Finland +40200 Iceland +40300 Lapland, ns +40400 Norway +40410 Svalbard and Jan Meyen +40411 Svalbard +40412 Jan Meyen +40500 Sweden + United Kingdom and Ireland: +41000 England +41010 Channel Islands +41011 Guernsey +41012 Jersey +41020 Isle of Man +41100 Scotland +41200 Wales +41300 United Kingdom, n.s. +41400 Ireland +41410 Northern Ireland +41900 Northern Europe, ns + Western Europe: +42000 Belgium +42100 France +42110 Alsace-Lorraine +42111 Alsace +42112 Lorraine +42200 Liechtenstein +42300 Luxembourg +42400 Monaco +42500 Netherlands +42600 Switzerland +42900 Western Euproe, ns + Southern Europe: +43000 Albania +43100 Andorra +43200 Gibraltar +43300 Greece +43310 Dodecanese Islands +43320 Turkey Greece +43330 Macedonia +43400 Italy +43500 Malta +43600 Portugal +43610 Azores +43620 Madeira Islands +43630 Cape Verde Islands +43640 St. Miguel +43700 San Marino +43800 Spain +43900 Vatican City +44000 Southern Europe, ns + Central/Eastern Europe: +45000 Austria +45010 Austria-Hungary +45020 Austria-Graz +45030 Austria-Linz +45040 Austria-Salzburg +45050 Austria-Tyrol +45060 Austria-Vienna +45070 Austria-Kaernten +45080 Austria-Neustadt +45100 Bulgaria +45200 Czechoslovakia +45210 Bohemia +45211 Bohemia-Moravia +45212 Slovakia +45213 Czech Republic +45300 Germany +45301 Berlin + West Germany +45310 West Germany +45311 Baden +45312 Bavaria +45313 Bremen +45314 Braunschweig +45315 Hamburg +45316 Hanover +45317 Hessen +45318 Hesse-Nassau +45319 Holstein +45320 Lippe +45321 Lubeck +45322 Oldenburg +45323 Rheinland +45324 Schleswig +45325 Schleswig-Holstein +45326 Schwarzburg +45327 Waldeck +45328 West Berlin +45329 Westphalia +45330 Wurttemberg +45331 Frankfurt +45332 Saarland +45333 Nordrhein-Westfalen + East Germany +45340 East Germany +45341 Anhalt +45342 Brandenburg +45343 East Berlin +45344 Mecklenburg +45345 Sachsen-Altenburg +45346 Sachsen-Coburg +45347 Sachsen-Gotha +45348 Sachsen-Meiningen +45349 Sachsen-Weimar-Eisenach +45350 Saxony +45351 Schwerin +45352 Strelitz +45353 Thuringian States +45360 Prussia, n.e.c. +45361 Hohenzollern +45362 Niedersachsen +45400 Hungary +45500 Poland +45510 Austrian Poland +45511 Galicia +45520 German Poland +45521 East Prussia +45522 Pomerania +45523 Posen +45524 Prussian Poland +45525 Silesia +45526 West Prussia +45530 Russian Poland +45600 Romania +45610 Transylvania +45700 Yugoslavia +45710 Croatia +45720 Montenegro +45730 Serbia +45740 Bosnia +45750 Dalmatia +45760 Slovonia +45770 Carniola +45780 Slovenia +45790 Kosovo +45800 Central Europe, n.s. +45900 Eastern Europe, n.s. + Russian Empire: + Baltic States: +46000 Estonia +46100 Latvia +46200 Lithuania +46300 Baltic States, ns +46500 Other USSR/Russia +46510 Byelorussia +46520 Moldavia +46521 Bessarabia +46530 Ukraine +46540 Armenia +46541 Azerbaijan +46542 Republic of Georgia +46543 Kazakhstan +46544 Kirghizia +46545 Tadzhik +46546 Turkmenistan +46547 Uzbekistan +46548 Siberia +46590 USSR, ns +49900 Europe, n.e.c./n.s. + ASIA + East Asia: +50000 China +50010 Hong Kong +50020 Macau +50030 Mongolia +50040 Taiwan +50100 Japan +50200 Korea +50210 North Korea +50220 South Korea +50900 East Asia, n.s. + Southeast Asia: +51000 Brunei +51100 Cambodia (Kampuchea) +51200 Indonesia +51210 East Indies +51220 East Timor +51300 Laos +51400 Malaysia +51500 Philippines +51600 Singapore +51700 Thailand +51800 Vietnam +51900 Southeast Asia, ns +51910 Indochina, ns + India/Southwest Asia: +52000 Afghanistan +52100 India +52110 Bangladesh +52120 Bhutan +52130 Burma (Myanmar) +52140 Pakistan +52150 Sri Lanka (Ceylon) +52200 Iran +52300 Maldives +52400 Nepal + Middle East/Asia Minor: +53000 Bahrain +53100 Cyprus +53200 Iraq +53210 Mesopotamia +53300 Iraq/Saudi Arabia + Neutral Zone +53400 Israel/Palestine +53420 Palestine +53430 West Bank +53440 Israel +53410 Gaza Strip +53500 Jordan +53600 Kuwait +53700 Lebanon +53800 Oman +53900 Qatar +54000 Saudi Arabia +54100 Syria +54200 Turkey +54210 European Turkey +54220 Asian Turkey +54300 United Arab Emirates +54400 Yemen Arab Republic (North) +54500 Yemen, PDR (South) +54600 Persian Gulf States, ns +54700 Middle East, n.s. +54800 Southwest Asia, nec/ns +54900 Asia Minor, n.s. +55000 South Asia, n.e.c. +59900 Asia, nec/ns + AFRICA +60000 AFRICA + Northern Africa: +60010 Northern Africa +60011 Algeria +60012 Egypt/United Arab Rep. +60013 Libya +60014 Morocco +60015 Sudan +60016 Tunisia +60017 Western Sahara +60019 North Africa, ns + West Africa: +60020 Benin +60021 Burkina Faso +60022 Gambia +60023 Ghana +60024 Guinea +60025 Guinea-Bissau +60026 Ivory Coast +60027 Liberia +60028 Mali +60029 Mauritania +60030 Niger +60031 Nigeria +60032 Senegal +60033 Sierra Leone +60034 Togo +60038 Western Africa, n.s. +60039 French West Africa, ns + East Africa: +60040 British Indian Ocean Territory +60041 Burundi +60042 Comoros +60043 Djibouti +60044 Ethiopia +60045 Kenya +60046 Madagascar +60047 Malawi +60048 Mauritius +60049 Mozambique +60050 Reunion +60051 Rwanda +60052 Seychelles +60053 Somalia +60054 Tanzania +60055 Uganda +60056 Zambia +60057 Zimbabwe +60058 Bassas de India +60059 Europa +60060 Gloriosos +60061 Juan de Nova +60062 Mayotte +60063 Tromelin +60064 Eastern Africa, nec/ns +60065 Eritrea + Central Africa: +60070 Central Africa +60071 Angola +60072 Cameroon +60073 Central African Republic +60074 Chad +60075 Congo +60076 Equatorial Guinea +60077 Gabon +60078 Sao Tome and Principe +60079 Zaire +60080 Central Africa, ns +60081 Equatorial Africa, ns +60082 French Equatorial Africa, ns + Southern Africa: +60090 Southern Africa +60091 Botswana +60092 Lesotho +60093 Namibia +60094 South Africa (Union of) +60095 Swaziland +60096 Southern Africa, n.s. +60099 Africa, ns/nec + OCEANIA +70000 Australia and New Zealand +70010 Australia +70011 Ashmore and Cartier Islands +70012 Coral Sea Islands Territory +70013 Christmas Island +70014 Cocos Islands +70020 New Zealand +71000 Pacific Islands + Melanesia: +71010 New Caledonia +71012 Papua New Guinea +71013 Solomon Islands +71014 Vanuatu (New Hebrides) +71016 Melanesia, ns + Polynesia: +71017 Norfolk Islands +71018 Niue +71020 Cook Islands +71021 Fiji +71022 French Polynesia +71023 Tonga +71024 Wallis and Futuna Islands +71025 Western Samoa +71026 Pitcairn Island +71027 Tokelau +71028 Tuvalu +71029 Polynesia, n.s. + Micronesia: +71032 Kiribati +71033 Canton and Enderbury +71034 Nauru +71039 Micronesia, ns + US Pacific Trust Territories: +71040 US Pacific Trust Territories +71041 Marshall Islands +71042 Micronesia +71043 Kosrae +71044 Pohnpei +71045 Truk +71046 Yap +71047 Northern Mariana Islands +71048 Palau +71049 Pacific Trust Terr, ns +71050 Clipperton Island +71090 Oceania, ns/nec +80000 Antarctica, ns/nec +80010 Bouvet Islands +80020 British Antarctic Terr. +80030 Dronning Maud Land +80040 French Southern and Antarctic Lands +80050 Heard and McDonald Islands +90000 Abroad (unknown) or at sea +90010 Abroad, ns +90011 Abroad (US citizen) +90020 At sea +90021 At sea (US citizen) +90022 At sea or abroad (U.S. citizen) + Other unknowns: +95000 Other n.e.c. +99700 Unknown +99900 Missing/blank + + FBPL Father's birthplace [general version] +000 Not Applicable + UNITED STATES +001 Alabama +002 Alaska +004 Arizona +005 Arkansas +006 California +008 Colorado +009 Connecticut +010 Delaware +011 District of Columbia +012 Florida +013 Georgia +015 Hawaii +016 Idaho +017 Illinois +018 Indiana +019 Iowa +020 Kansas +021 Kentucky +022 Louisiana +023 Maine +024 Maryland +025 Massachusetts +026 Michigan +027 Minnesota +028 Mississippi +029 Missouri +030 Montana +031 Nebraska +032 Nevada +033 New Hampshire +034 New Jersey +035 New Mexico +036 New York +037 North Carolina +038 North Dakota +039 Ohio +040 Oklahoma +041 Oregon +042 Pennsylvania +044 Rhode Island +045 South Carolina +046 South Dakota +047 Tennessee +048 Texas +049 Utah +050 Vermont +051 Virginia +053 Washington +054 West Virginia +055 Wisconsin +056 Wyoming +090 Native American +099 United States, ns + US OUTLYING AREAS/TERRITORIES +100 American Samoa +105 Guam +110 Puerto Rico +115 US Virgin Islands +120 Other US Possessions + OTHER NORTH AMERICA +150 Canada +155 St Pierre and Miquelon +160 Atlantic Islands +199 North America, n.s. + CENTRAL AMERICA AND CARIBBEAN +200 Mexico +210 Central America + Caribbean: +250 Cuba +260 West Indies +299 Americas, n.s. +300 SOUTH AMERICA + EUROPE + Northern Europe: +400 Denmark +401 Finland +402 Iceland +403 Lapland, n.s. +404 Norway +405 Sweden +406 Svalbard + United Kingdom and Ireland: +410 England +411 Scotland +412 Wales +413 United Kingdom, ns +414 Ireland +419 Northern Europe, ns + Western Europe: +420 Belgium +421 France +422 Liechtenstein +423 Luxembourg +424 Monaco +425 Netherlands +426 Switzerland +429 Western Europe, ns + Southern Europe: +430 Albania +431 Andorra +432 Gibraltar +433 Greece +434 Italy +435 Malta +436 Portugal +437 San Marino +438 Spain +439 Vatican City +440 Southern Europe, n.s. + Central/Eastern Europe: +450 Austria +451 Bulgaria +452 Czechsolovakia +453 Germany +454 Hungary +455 Poland +456 Romania +457 Yugoslavia +458 Central Europe, ns +459 Eastern Europe, ns + Russian Empire: + Baltic States: +460 Estonia +461 Latvia +462 Lithuania +463 Baltic States, ns +465 Other USSR/Russia +499 Europe, nec/ns + ASIA + East Asia: +500 China +501 Japan +502 Korea + Southeast Asia: +510 Brunei +511 Cambodia (Kampuchea) +512 Indonesia +513 Laos +514 Malaysia +515 Philippines +516 Singapore +517 Thailand +518 Vietnam +519 Southeast Asia, ns + India/Southwest Asia: +520 Afghanistan +521 India +522 Iran +523 Maldives +524 Nepal + Middle East/Asia Minor: +530 Bahrain +531 Cyprus +532 Iraq +533 Iraq/Saudi Arabia +534 Israel/Palestine +535 Jordan +536 Kuwait +537 Lebanon +538 Oman +539 Qatar +540 Saudi Arabia +541 Syria +542 Turkey +543 United Arab Emirates +544 Yemen Arab Republic (North) +545 Yemen, PDR (South) +546 Persian Gulf States, n.s. +547 Middle East, ns +548 Southwest Asia, nec/ns +549 Asia Minor, n.s. +550 South Asia, n.e.c. +599 Asia, nec/ns + AFRICA +600 AFRICA + Northern Africa: + West Africa: + East Africa: + Central Africa: + Southern Africa: + OCEANIA +700 Australia and New Zealand +710 Pacific Islands +900 Abroad (unknown) or at sea +950 Other n.e.c. +997 Unknown +998 Illegible +999 Missing/blank + + FBPLD Father's birthplace [detailed version] +00000 Not Applicable + UNITED STATES +00100 Alabama +00200 Alaska +00400 Arizona +00500 Arkansas +00600 California +00800 Colorado +00900 Connecticut +01000 Delaware +01100 District of Columbia +01200 Florida +01300 Georgia +01500 Hawaii +01600 Idaho +01610 Idaho Territory +01700 Illinois +01800 Indiana +01900 Iowa +02000 Kansas +02100 Kentucky +02200 Louisiana +02300 Maine +02400 Maryland +02500 Massachusetts +02600 Michigan +02700 Minnesota +02800 Mississippi +02900 Missouri +03000 Montana +03100 Nebraska +03200 Nevada +03300 New Hampshire +03400 New Jersey +03500 New Mexico +03510 New Mexico Territory +03600 New York +03700 North Carolina +03800 North Dakota +03900 Ohio +04000 Oklahoma +04010 Indian Territory +04100 Oregon +04200 Pennsylvania +04400 Rhode Island +04500 South Carolina +04600 South Dakota +04610 Dakota Territory +04700 Tennessee +04800 Texas +04900 Utah +04910 Utah Territory +05000 Vermont +05100 Virginia +05300 Washington +05400 West Virginia +05500 Wisconsin +05600 Wyoming +05610 Wyoming Territory +09000 Native American +09900 United States, ns + US OUTLYING AREAS/TERRITORIES +10000 American Samoa +10010 Samoa, 1940-1950 +10500 Guam +11000 Puerto Rico +11500 US Virgin Islands +11510 St Croix +11520 St. John +11530 St Thomas +12000 Other US Possessions +12010 Johnston Atoll +12020 Midway Islands +12030 Wake Island +12040 Other US Caribbean Islands +12041 Navassa Island +12050 Other US Pacific Is. +12051 Baker Island +12052 Howland Island +12053 Jarvis Island +12054 Kingman Reef +12055 Palmyra Atoll +12056 Canton and Enderbury Island +12090 US outlying areas, ns +12091 US Possessions, ns +12092 US territory, ns + OTHER NORTH AMERICA +15000 Canada +15010 English Canada +15011 British Columbia +15013 Alberta +15015 Saskatchewan +15017 Northwest +15019 Ruperts Land +15020 Manitoba +15021 Red River +15030 Ontario/Upper Canada +15031 Upper Canada +15032 Canada West +15040 New Brunswick +15042 Canada West +15050 Nova Scotia +15051 Cape Breton +15052 Halifax +15060 Prince Edward Island +15070 Newfoundland +15080 French Canada +15081 Quebec +15082 Lower Canada +15083 Canada East +15500 St Pierre and Miquelon +16000 Atlantic Islands +16010 Bermuda +16020 Cape Verde +16030 Falkland Islands +16040 Greenland +16050 St Helena and Ascension +16060 Canary Islands +19900 North America, n.s. + CENTRAL AMERICA AND CARIBBEAN +20000 Mexico +21000 Central America +21010 Belize/British Honduras +21020 Costa Rica +21030 El Salvador +21040 Guatemala +21050 Honduras +21060 Nicaragua +21070 Panama +21071 Canal Zone +21090 Central America, ns + Caribbean: +25000 Cuba +26000 West Indies +26010 Dominican Republic +26020 Haiti +26030 Jamaica +26040 British West Indies +26041 Anguilla +26042 Antigua-Barbuda +26043 Bahamas +26044 Barbados +26045 British Virgin Islands +26046 Anegada +26047 Cooper +26048 Jost Van Dyke +26049 Peter +26050 Tortola +26051 Virgin Gorda +26052 Br. Virgin Islands, ns +26053 Cayman Islands +26054 Dominica +26055 Grenada +26056 Montserrat +26057 St Kitts-Nevis +26058 St Lucia +26059 St Vincent +26060 Trinidad and Tobago +26061 Turks and Caicos +26069 British West Indies, ns +26070 Other West Indies + Dutch West Indies: +26071 Aruba +26072 Netherlands Antilles +26073 Bonaire +26074 Curacao +26075 Dutch St. Maarten +26076 Saba +26077 St. Eustatius +26079 Dutch Caribbean, ns + French West Indies: +26080 French St Maarten +26081 Guadeloupe +26082 Martinique +26083 St. Barthelemy +26089 French Caribbean, ns +26090 Antilles, n.s. +26091 Caribbean, n.s. / n.e.c. +26092 Latin America, ns +26093 Leeward Islands, ns +26094 West Indies, ns +26095 Winward Islands +29900 Americas, ns +30000 South America +30005 Argentina +30010 Bolivia +30015 Brazil +30020 Chile +30025 Colombia +30030 Ecuador +30035 French Guiana +30040 Guyana/British Guiana +30045 Paraguay +30050 Peru +30055 Suriname +30060 Uruguay +30065 Venezuela +30090 South America, ns +30091 South and Central America, n.s. + EUROPE + Northern Europe: +40000 Denmark +40010 Faroe Islands +40100 Finland +40200 Iceland +40300 Lapland, ns +40400 Norway +40410 Svalbard and Jan Meyen +40412 Jan Meyen +40500 Sweden +40600 Svalbard + United Kingdom and Ireland: +41000 England +41010 Channel Islands +41011 Guernsey +41012 Jersey +41020 Isle of Man +41100 Scotland +41200 Wales +41300 United Kingdom, ns +41400 Ireland +41410 Northern Ireland +41900 Northern Europe, ns + Western Europe: +42000 Belgium +42100 France +42110 Alsace-Lorraine +42111 Alsace +42112 Lorraine +42200 Liechtenstein +42300 Luxembourg +42400 Monaco +42500 Netherlands +42600 Switzerland +42900 Western Europe, ns + Southern Europe: +43000 Albania +43100 Andorra +43200 Gibraltar +43300 Greece +43310 Dodecanese Islands +43320 Turkey Greece +43330 Macedonia +43400 Italy +43500 Malta +43600 Portugal +43610 Azores +43620 Madeira Islands +43630 Cape Verde Islands +43640 St Miguel +43700 San Marino +43800 Spain +43900 Vatican City +44000 Southern Europe, ns + Central/Eastern Europe: +45000 Austria +45010 Austria-Hungary +45020 Austria-Graz +45030 Austria-Linz +45040 Austria-Salzburg +45050 Austria-Tyrol +45060 Austria-Vienna +45070 Austria-Kaernsten +45080 Austria-Neustadt +45100 Bulgaria +45200 Czechsolovakia +45210 Bohemia +45211 Bohemia-Moravia +45212 Slovakia +45213 Czech Republic +45300 Germany +45301 Berlin + West Germany +45310 West Germany +45311 Baden +45312 Bavaria +45313 Bremen +45314 Braunschweig +45315 Hamburg +45316 Hanover +45317 Hessen +45318 Hesse-Nassau +45319 Holstein +45320 Lippe +45321 Lubeck +45322 Oldenburg +45323 Rheinland +45324 Schleswig +45325 Schleswig-Holstein +45326 Schwarzburg +45327 Waldeck +45328 West Berlin +45329 Westphalia +45330 Wurttemberg +45331 Frankfurt +45332 Saarland +45333 Nordrhein-Westfalen + East Germany +45340 East Germany +45341 Anhalt +45342 Brandenburg +45343 East Berlin +45344 Mecklenburg +45345 Sachsen-Altenburg +45346 Sachsen-Coburg +45347 Sachsen-Gotha +45348 Sachsen-Meiningen +45349 Sachsen-Weimar-Eisenach +45350 Saxony +45351 Schwerin +45352 Strelitz +45353 Thuringian States +45360 Prussia, nec +45361 Hohenzollern +45362 Niedersachsen +45400 Hungary +45500 Poland +45510 Austrian Poland +45511 Galicia +45520 German Poland +45521 East Prussia +45522 Pomerania +45523 Posen +45524 Prussian Poland +45525 Silesia +45526 West Prussia +45530 Russian Poland +45600 Romania +45610 Transylvania +45700 Yugoslavia +45710 Croatia +45720 Montenegro +45730 Serbia +45740 Bosnia +45750 Dalmatia +45760 Slovonia +45770 Carniola +45780 Slovenia +45790 Kosovo +45800 Central Europe, ns +45900 Eastern Europe, ns + Russian Empire: + Baltic States: +46000 Estonia +46100 Latvia +46200 Lithuania +46300 Baltic States, ns +46500 Other USSR/Russia +46510 Byelorussia +46520 Moldavia +46521 Bessarabia +46530 Ukraine +46540 Armenia +46541 Azerbaijan +46542 Republic of Georgia +46543 Kazakhstan +46544 Kirghizia +46545 Tadzhik +46546 Turkmenistan +46547 Uzbekistan +46548 Siberia +46590 USSR, ns +49900 Europe, nec/ns + ASIA + East Asia: +50000 China +50010 Hong Kong +50020 Macau +50030 Mongolia +50040 Taiwan +50100 Japan +50200 Korea +50210 North Korea +50220 South Korea +50900 East Asia, n.s. + Southeast Asia: +51000 Brunei +51100 Cambodia (Kampuchea) +51200 Indonesia +51210 East Indies +51220 East Timor +51300 Laos +51400 Malaysia +51500 Philippines +51600 Singapore +51700 Thailand +51800 Vietnam +51900 Southeast Asia, ns +51910 Indochina, ns + India/Southwest Asia: +52000 Afghanistan +52100 India +52110 Bangladesh +52120 Bhutan +52130 Burma (Myanmar) +52140 Pakistan +52150 Sri Lanka (Ceylon) +52200 Iran +52300 Maldives +52400 Nepal + Middle East/Asia Minor: +53000 Bahrain +53100 Cyprus +53200 Iraq +53210 Mesopotamia +53300 Iraq/Saudi Arabia + Neutral Zone +53400 Israel/Palestine +53410 Gaza Strip +53420 Palestine +53430 West Bank +53440 Israel +53500 Jordan +53600 Kuwait +53700 Lebanon +53800 Oman +53900 Qatar +54000 Saudi Arabia +54100 Syria +54200 Turkey +54210 European Turkey +54220 Asian Turkey +54300 United Arab Emirates +54400 Yemen Arab Republic (North) +54500 Yemen, PDR (South) +54600 Persian Gulf States, ns +54700 Middle East, ns +54800 Southwest Asia, nec/ns +54900 Asia Minor, ns +55000 South Asia, n.e.c. +59900 Asia, nec/ns + AFRICA +60000 Africa + Northern Africa: +60010 Northern Africa +60011 Algeria +60012 Egypt/United Arab Rep +60013 Libya +60014 Morocco +60015 Sudan +60016 Tunisia +60017 Western Sahara +60019 North Africa, ns + West Africa: +60020 Benin +60021 Burkina Faso +60022 Gambia +60023 Ghana +60024 Guinea +60025 Guinea-Bissau +60026 Ivory Coast +60027 Liberia +60028 Mali +60029 Mauritania +60030 Niger +60031 Nigeria +60032 Senegal +60033 Sierra Leone +60034 Togo +60038 Western Africa, n.s. +60039 French West Africa, ns + East Africa: +60040 British Indian Ocean Territory +60041 Burundi +60042 Comoros +60043 Djibouti +60044 Ethiopia +60045 Kenya +60046 Madagascar +60047 Malawi +60048 Mauritius +60049 Mozambique +60050 Reunion +60051 Rwanda +60052 Seychelles +60053 Somalia +60054 Tanzania +60055 Uganda +60056 Zambia +60057 Zimbabwe +60058 Bassas de India +60059 Europa +60060 Gloriosos +60061 Juan de Nova +60062 Mayotte +60063 Tromelin +60064 Eastern Africa, nec/ns +60065 Eritrea + Central Africa: +60070 Central Africa +60071 Angola +60072 Cameroon +60073 Central African Republic +60074 Chad +60075 Congo +60076 Equatorial Guinea +60077 Gabon +60078 Sao Tome and Principe +60079 Zaire +60080 Central Africa, ns +60081 Equatorial Africa, ns +60082 French Equatorial Africa, ns + Southern Africa: +60090 Southern Africa +60091 Botswana +60092 Lesotho +60093 Namibia +60094 South Africa (Union of) +60095 Swaziland +60096 Southern Africa, n.s. +60099 Africa, ns/nec + OCEANIA +70000 Australia and New Zealand +70010 Australia +70011 Ashmore and Cartier Islands +70012 Coral Sea Islands Territory +70013 Christmas Island +70014 Cocos Islands +70020 New Zealand +71000 Pacific Islands + Melanesia: +71010 New Caledonia +71012 Papua New Guinea +71013 Solomon Islands +71014 Vanuatu (New Hebrides) +71016 Melanesia, ns + Polynesia: +71017 Norfolk Islands +71018 Niue +71020 Cook Islands +71021 Fiji +71022 French Polynesia +71023 Tonga +71024 Wallis and Futuna Islands +71025 Western Samoa +71026 Pitcairn Island +71027 Tokelau +71028 Tuvalu +71029 Polynesia, n.s. + Micronesia: +71032 Kiribati +71033 Canton and Enderbury +71034 Nauru +71039 Micronesia, ns + US Pacific Trust Territories: +71040 US Pacific Trust Territories +71041 Marshall Islands +71042 Micronesia +71043 Kosrae +71044 Pohnpei +71045 Truk +71046 Yap +71047 Northern Mariana Islands +71048 Palau +71049 Pacific Trust Terr, ns +71050 Clipperton Island +71090 Oceania, ns/nec +80000 Antarctica, ns/nec +80010 Bouvet Islands +80020 British Antarctic Terr. +80030 Dronning Maud Land +80040 French Southern and Antarctic Lands +80050 Heard and McDonald Islands +90000 Abroad (unknown) or at sea +90010 Abroad, ns +90011 Abroad (US citizen) +90020 At sea +90021 At sea (US citizen) +90022 At sea or abroad (U.S. citizen) + Other unknowns: +95000 Other n.e.c. +99700 Unknown +99800 Illegible +99900 Missing/blank + + NATIVITY Foreign birthplace or parentage +0 N/A + Native-Born +1 Native born, and both parents native born +2 Native born, and father foreign, mother native +3 Native born, and mother foreign, father native +4 Native born, and both parents foreign +5 Foreign born + + CITIZEN Citizenship status +0 N/A +1 Born abroad of American parents +2 Naturalized citizen +3 Not a citizen +4 Not a citizen, but has received first papers +5 Foreign born, citizenship status not reported + + MTONGUE Mother tongue [general version] +00 N/A or blank +01 English +02 German +03 Yiddish, Jewish +04 Dutch +05 Swedish +06 Danish +07 Norwegian +08 Icelandic +09 Scandinavian +10 Italian +11 French +12 Spanish +13 Portuguese +14 Rumanian +15 Celtic +16 Greek +17 Albanian +18 Russian +19 Ukrainian +20 Czech +21 Polish +22 Slovak +23 Serbo-Croatian, Yugoslavian +24 Slovene +25 Lithuanian +26 Other Balto-Slavic +27 Slavic unknown +28 Armenian +29 Persian, Iranian, Farsi +30 Other Persian dialects +31 Hindi and related +32 Romany, Gypsy +33 Finnish +34 Magyar, Hungarian +35 Uralic +36 Turkish +37 Other Altaic +38 Caucasian, Gerogian, Avar +39 Basque +40 Dravidian +41 Kurukh +42 Burushaski +43 Chinese +44 Tibetan +45 Burmese, Lisu, Lolo +46 Kachin +47 Thai, Siamese, Lao +48 Japanese +49 Korean +50 Vietnamese +51 Other East/Southeast Asian +52 Indonesian +53 Other Malayan +54 Filipino, Tagalog +55 Micronesian, Polynesian +56 Hawaiian +57 Arabic +58 Near East Arabic dialect +59 Hebrew, Israeli +60 Amharic, Ethiopian +61 Hamitic +63 Nilotic +64 African, ns +70 American Indian (all) +71 Aleut, Eskimo +72 Algonquian +73 Salish, Flathead +74 Athapascan +75 Navajo +76 Penutian-Sahaptin +77 Mountain Maidu, Maidu +78 Zuni +79 Yuman +80 Achumawi +81 Siouan languages +82 Muskogean +83 Keres +84 Iroquoian +85 Caddoan +86 Shoshonean/Hopi +87 Pima/Papago +88 Yaqui +89 Aztecan, Nahuatl, Uto-Aztecan +90 Tanoan languages +91 Wiyot +92 Mayan languages +93 American Indian, n.s. +94 Native +96 Other or not reported +97 Unknown +99 Not reported, blank + + MTONGUED Mother tongue [detailed version] +0000 N/A or blank +0100 English +0110 Jamaican Creole +0120 Krio, Pidgin Krio +0130 Hawaiian Pidgin +0140 Pidgin +0150 Gullah, Geechee +0160 Saramacca +0200 German +0210 Austrian +0220 Swiss +0230 Luxembourgian +0240 Pennsylvania Dutch +0300 Yiddish, Jewish +0310 Jewish +0320 Yiddish +0400 Dutch +0410 Dutch, Flemish, Belgian +0420 Afrikaans +0430 Frisian +0440 Dutch, Afrikaans, Frisian +0450 Belgian, Flemish +0460 Belgian +0470 Flemish +0500 Swedish +0600 Danish +0700 Norwegian +0800 Icelandic +0810 Faroese +0900 Scandinavian +1000 Italian +1010 Rhaeto-Romanic, Ladin +1020 Friulian +1030 Romansh +1100 French +1110 French, Walloon +1120 Provencal +1130 Patois +1140 French or Haitian Creole +1150 Cajun +1200 Spanish +1210 Catalonian, Valencian +1220 Ladino, Sefaradit, Spanol +1230 Pachuco +1240 Papia Mentae +1250 Mexican +1300 Portuguese +1400 Rumanian +1500 Celtic +1510 Welsh, Breton, Cornish +1520 Welsh +1530 Breton +1540 Irish Gaelic, Gaelic +1550 Gaelic +1560 Irish +1570 Scottish Gaelic +1580 Scotch +1590 Manx, Manx Gaelic +1600 Greek +1700 Albanian +1800 Russian +1810 Russian, Great Russian +1811 Great Russian +1820 Bielo-, White Russian +1900 Ukrainian +1910 Ruthenian +1920 Little Russian +1930 Ukrainian +2000 Czech +2010 Bohemian +2020 Moravian +2100 Polish +2110 Kashubian, Slovincian +2200 Slovak +2300 Serbo-Croatian, Yugoslavian, Slavonian +2310 Croatian +2320 Serbian +2330 Dalmatian, Montenegrin +2331 Dalmatian +2332 Montenegrin +2400 Slovene +2500 Lithuanian +2510 Lettish +2600 Other Balto-Slavic +2610 Bulgarian +2620 Lusatian, Sorbian, Wendish +2621 Wendish +2630 Macedonian +2700 Slavic unknown +2800 Armenian +2900 Persian, Iranian, Farsi +2910 Persian + Other Persian dialects: +3000 Other Persian dialects +3010 Pashto, Afghan +3020 Kurdish +3030 Balochi +3040 Tadzhik +3050 Ossete +3100 Hindi and related +3101 Hindi, Hindustani, Indic, Jaipuri, Pali, Urdu +3102 Hindi, Hindustani, Urdu +3103 Hindu + Other Indo-Aryan: +3110 Other Indo-Aryan +3111 Sanskrit +3112 Bengali +3113 Panjabi +3114 Marathi +3115 Gujarathi +3116 Bihari +3117 Rajasthani +3118 Oriya +3119 Assamese +3120 Kashmiri +3121 Sindhi +3122 Maldivian +3123 Sinhalese +3130 Kannada +3140 India nec +3150 Pakistan nec +3190 Georgian +3200 Romany, Gypsy +3210 Gypsy +3300 Finnish +3400 Magyar, Hungarian +3401 Magyar +3402 Hungarian +3500 Uralic +3510 Estonian, Ingrian, Livonian, Vepsian, Votic +3511 Estonian +3520 Lapp, Inari, Kola, Lule, Pite, Ruija, Skolt, Ume +3521 Lappish +3530 Other Uralic +3600 Turkish +3700 Other Altaic +3701 Chuvash +3702 Karakalpak +3703 Kazakh +3704 Kirghiz +3705 Karachay, Tatar, Balkar, Bashkir, Kumyk +3706 Uzbek, Uighur-40 +3707 Azerbaijani +3708 Turkmen +3709 Yakut +3710 Mongolian +3711 Tungus +3800 Caucasian, Georgian, Avar +3810 Georgian +3900 Basque +4000 Dravidian +4001 Brahui +4002 Gondi +4003 Telugu +4004 Malayalam +4005 Tamil +4010 Bhili +4011 Nepali +4100 Kurukh +4110 Munda +4200 Burushaski +4300 Chinese +4301 Chinese, Cantonese, Min, Yueh +4302 Cantonese, Yueh +4303 Mandarin + Other Chinese: +4310 Other Chinese +4311 Hakka, Fukien, Kechia +4312 Kan, Nan Chang +4313 Hsiang, Chansa, Hunan, Iyan +4314 Fuchow, Min Pei +4315 Wu +4400 Tibetan +4410 Miao-Yao +4420 Miao, Hmong +4500 Burmese, Lisu, Lolo +4510 Karen +4600 Kachin +4700 Thai, Siamese, Lao +4710 Thai +4720 Laotian +4800 Japanese +4900 Korean +5000 Vietnamese + Other East/Southeast Asian: +5100 Other East/Southeast Asian +5110 Ainu +5120 Mon-Khmer, Cambodian +5130 Siberian, n.e.c. +5140 Yukagir +5150 Muong +5200 Indonesian +5210 Buginese +5220 Moluccan +5230 Achinese +5240 Balinese +5250 Cham +5260 Madurese +5270 Malay +5280 Minangkabau +5290 Other Asian languages + Other Malayan: +5300 Other Malayan +5310 Formosan, Taiwanese +5320 Javanese +5330 Malagasy +5340 Sundanese +5400 Filipino, Tagalog +5410 Bisayan +5420 Sebuano +5430 Pangasinan +5440 Ilocano +5450 Bikol +5460 Pampangan +5470 Gorontalo +5480 Palau + Micronesian, Polynesian: +5500 Micronesian, Polynesian +5501 Micronesian +5502 Carolinian +5503 Chamorro, Guamanian +5504 Gilbertese +5505 Kusaiean +5506 Marshallese +5507 Mokilese +5508 Mortlockese +5509 Nauruan +5510 Ponapean +5511 Trukese +5512 Ulithean, Fais +5513 Woleai-Ulithi +5514 Yapese +5520 Melanesian +5521 Polynesian +5522 Samoan +5523 Tongan +5524 Niuean +5525 Tokelauan +5526 Fijian +5527 Marquesan +5528 Rarotongan +5529 Maori +5530 Nukuoro, Kapingarangan +5590 Other Pacific Island languages +5600 Hawaiian +5700 Arabic +5710 Algerian, Moroccan, Tunisian +5720 Egyptian +5730 Iraqi, Chaldean-70 +5740 Libyan +5750 Maltese +5800 Near East Arabic dialect +5810 Syriac, Aramaic, Chaldean-40 +5820 Syrian +5900 Hebrew, Israeli +6000 Amharic, Ethiopian, etc. + Hamitic +6100 Hamitic +6110 Berber +6120 Chadic, Hamitic, Hausa +6130 Cushite, Beja, Somali + Sub-Saharan Africa: +6300 Nilotic +6301 Nilo-Hamitic +6302 Nubian +6303 Saharan +6304 Nilo-Saharan, Fur, Songhai +6305 Khoisan +6306 Sudanic +6307 Bantu (many subheads) +6308 Swahili +6309 Mande +6310 Fulani +6311 Gur +6312 Kru +6313 Efik, Ibibio, Tiv +6314 Mbum, Gbaya, Sango, Zande + Sub-Saharan Africa (1960, 1970 only): +6320 Eastern Sudanic and Khoisan +6321 Niger-Congo regions (many subheads) +6322 Congo, Kongo, Luba, Ruanda, Rundi, Santali, Swahili +6390 Other specified African languages +6400 African, n.s. +7000 American Indian (all) +7100 Aleut, Eskimo +7110 Aleut +7120 Pacific Gulf Yupik +7130 Eskimo +7140 Inupik, Innuit +7150 St. Lawrence Isl. Yupik +7160 Yupik +7200 Algonquian +7201 Arapaho +7202 Atsina, Gros Ventre +7203 Blackfoot +7204 Cheyenne +7205 Cree +7206 Delaware, Lenni-Lenape +7207 Fox, Sac +7208 Kickapoo +7209 Menomini +7210 Metis, French Cree +7211 Miami +7212 Micmac +7213 Ojibwa, Chippewa +7214 Ottawa +7215 Passamaquoddy, Malecite +7216 Penobscot +7217 Abnaki +7218 Potawatomi +7219 Shawnee +7300 Salish, Flathead +7301 Lower Chehalis +7302 Upper Chehalis, Chelalis, Satsop +7303 Clallam +7304 Coeur dAlene, Skitsamish +7305 Columbia, Chelan, Wenatchee +7306 Cowlitz +7307 Nootsack +7308 Okanogan +7309 Puget Sound Salish +7310 Quinault, Queets +7311 Tillamook +7312 Twana +7313 Kalispel +7314 Spokane +7400 Athapascan +7401 Ahtena +7402 Han +7403 Ingalit +7404 Koyukon +7405 Kuchin +7406 Upper Kuskokwim +7407 Tanaina +7408 Tanana, Minto +7409 Tanacross +7410 Upper Tanana, Nabesena, Tetlin +7411 Tutchone +7412 Chasta Costa, Chetco, Coquille, Smith River Athapascan +7413 Hupa +7420 Apache +7421 Jicarilla, Lipan +7422 Chiricahua, Mescalero +7423 San Carlos, Cibecue, White Mountain +7424 Kiowa-Apache +7430 Kiowa +7440 Eyak +7450 Other Athapascan-Eyak, Cahto, Mattole, Wailaki +7490 Other Algonquin languages +7500 Navajo + Penutian-Sahaptin +7600 Penutian-Sahaptin +7610 Klamath, Modoc +7620 Nez Perce +7630 Sahaptian, Celilo, Klikitat, Palouse, Tenino, Umatilla, Warm Springs, Yakima + Other Penutian: +7700 Mountain Maidu, Maidu +7701 Northwest Maidu, Concow +7702 Southern Maidu, Nisenan +7703 Coast Miwok, Bodega, Marin +7704 Plains Miwok +7705 Sierra Miwok, Miwok +7706 Nomlaki, Tehama +7707 Patwin, Colouse, Suisun +7708 Wintun +7709 Foothill North Yokuts +7710 Tachi +7711 Santiam, Calapooya, Wapatu +7712 Siuslaw, Coos, Lower Umpqua +7713 Tsimshian +7714 Upper Chinook, Clackamas, Multnomah, Wasco, Wishram +7715 Chinook Jargon +7800 Zuni + Hokan languages: +7900 Yuman +7910 Upriver Yuman +7920 Cocomaricopa +7930 Mohave +7940 Diegueno +7950 Delta River Yuman +7960 Upland Yuman +7970 Havasupai +7980 Walapai +7990 Yavapai +8000 Achumawi +8010 Atsugewi +8020 Karok +8030 Pomo +8040 Shastan +8050 Washo +8060 Chumash + Siouan languages: +8100 Siouan languages: +8101 Crow, Absaroke +8102 Hidatsa +8103 Mandan +8104 Dakota, Lakota, Nakota, Sioux +8105 Chiwere +8106 Winnebago +8107 Kansa, Kaw +8108 Omaha +8109 Osage +8110 Ponca +8111 Quapaw, Arkansas +8120 Iowa +8200 Muskogean +8210 Alabama +8220 Choctaw, Chickasaw +8230 Mikasuki +8240 Hichita, Apalachicola +8250 Koasati +8260 Muskogee, Creek, Seminole +8300 Keres +8400 Iroquoian +8410 Mohawk +8420 Oneida +8430 Onondaga +8440 Cayuga +8450 Seneca +8460 Tuscarora +8470 Wyandot, Huron +8480 Cherokee +8500 Caddoan +8510 Arikara +8520 Pawnee +8530 Wichita + Uto-Aztecan languages: + Shoshonean/Hopi: +8600 Shoshonean/Hopi: +8601 Comanche +8602 Mono, Owens Valley Paiute +8603 Paiute +8604 Northern Paiute, Bannock, Num, Snake +8605 Southern Paiute +8606 Chemehuevi +8607 Kawaiisu +8608 Ute +8609 Shoshoni +8610 Panamint +8620 Hopi +8630 Cahuilla +8631 Cupeno +8632 Luiseno +8633 Serrano +8640 Tubatulabal + Sonoran languages: +8700 Pima, Papago +8800 Yaqui +8810 Sonoran n.e.c., Cahita, Guassave, Huichole, Nayit, Tarahumar + Sonoran, n.e.c.: +8820 Tarahumara + Other Uto-Aztecan: +8900 Aztecan, Nahuatl, Uto-Aztecan +8910 Aztecan, Mexicano, Nahua + Tanoan languages: +9000 Tanoan languages +9010 Picuris, Northern Tiwa, Taos +9020 Tiwa, Isleta +9030 Sandia +9040 Tewa, Hano, Hopi-Tewa, San Ildefonso, San Juan, Santa Clara +9050 Towa + Other Indian languages: +9100 Wiyot +9101 Yurok +9110 Kwakiutl +9111 Nootka +9112 Makah +9120 Kutenai +9130 Haida +9131 Tlingit, Chilkat, Sitka, Tongass, Yakutat +9140 Tonkawa +9150 Yuchi +9160 Chetemacha +9170 Yuki +9171 Wappo +9200 Mayan languages +9210 Misumalpan +9211 Cakchiquel +9212 Mam +9213 Maya +9214 Quekchi +9215 Quiche +9220 Tarascan + Mapuche +9230 Mapuche +9231 Araucanian + Oto-Manguen +9240 Oto-Manguen +9241 Mixtec +9242 Zapotec +9250 Quechua +9260 Aymara + Arawakian +9270 Arawakian +9271 Island Caribs + Chibchan +9280 Chibchan +9281 Cuna +9282 Guaymi + Tupi-Guarani +9290 Tupi-Guarani +9291 Tupi +9292 Guarani +9300 American Indian, n.s. +9400 Native +9410 Other specified American Indian languages +9420 South/Central American Indian +9500 No language +9600 Other or not reported +9601 Other n.e.c. +9602 Other n.s. +9700 Unknown +9900 Not reported, blank +9999 + + SPANNAME Spanish surname +0 N/A +1 No, not Spanish surname +2 Yes, Spanish surname +9 Not reported + + HISPRULE Hispanic origin rule +0 Not assigned as Hispanic +1 Birthplace is Hispanic +2 Parental birthplace is Hispanic +3 Grandparental birthplace is Hispanic +4 Spouse is Hispanic +5 Related HH head is Hispanic +6 Spanish surname +7 Spouse has Spanish surname +8 Related HH head has Spanish surname + + SCHOOL School attendance +0 N/A +1 No, not in school +2 Yes, in school +9 Missing + + HIGRADE Highest grade of schooling [general version] +00 N/A (or None, 1980) +01 None +02 Nursery school +03 Kindergarten + Elementary school: +04 1st grade +05 2nd grade +06 3rd grade +07 4th grade +08 5th grade +09 6th grade +10 7th grade +11 8th grade + High school: +12 9th grade +13 10th grade +14 11th grade +15 12th grade + College: +16 1st year +17 2nd year +18 3rd year +19 4th year +20 5th year or more (40-50) +21 6th year or more (60,70) +22 7th year +23 8th year or more + + HIGRADED Highest grade of schooling [detailed version] +000 N/A +010 None +011 Did not finish nurs sch +012 Attending nurs sch +020 Nursery school +021 Did not finish kindergart +022 Attending kindergarten +030 Kindergarten +031 Did not finish 1st grade +032 Attending 1st grade + Elementary school: +040 1st grade +041 Did not finish 2nd grade +042 Attending 2nd grade +050 2nd grade +051 Did not finish 3rd grade +052 Attending 3rd grade +060 3rd grade +061 Did not finish 4th grade +062 Attending 4th grade +070 4th grade +071 Did not finish 5th grade +072 Attending 5th grade +080 5th grade +081 Did not finish 6th grade +082 Attending 6th grade +090 6th grade +091 Did not finish 7th grade +092 Attending 7th grade +100 7th grade +101 Did not finish 8th grade +102 Attending 8th grade +110 8th grade +111 Did not finish 9th grade +112 Attending 9th grade + High school: +120 9th grade +121 Did not finish 10th grade +122 Attending 10th grade +130 10th grade +131 Did not finish 11th grade +132 Attending 11th grade +140 11th grade +141 Did not finish 12th grade +142 Attending 12th grade +150 12th grade +151 Did not finish 1st year college +152 Attending 1st yesr college + College: +160 1st year of college +161 Did not finish 2nd year of college +162 Attending 2nd year of college +170 2nd year of college +171 Did not finish 3rd year of college +172 Attending 3rd year of college +180 3rd year of college +181 Did not finish 4th year of college +182 Attending 4th year of college +190 4th year of college +191 Did not finish 5th year of college +192 Attending 5th year of college +200 5th year or more of college (1940, 50) +201 Did not finish 6th year of college +202 Attending 6th year of college +210 6th year or more of college (1960,70) +211 Did not finish 7th year of college +212 Attending 7th year of college +220 7th year of college +221 Did not finish 8th year of college +222 Attending 8th year of college +230 8th year or more of college +999 Missing + + EDUC Educational attainment [general version] +00 N/A or no schooling +01 Nursery school to grade 4 +02 Grade 5, 6, 7, or 8 +03 Grade 9 +04 Grade 10 +05 Grade 11 +06 Grade 12 +07 1 year of college +08 2 years of college +09 3 years of college +10 4 years of college +11 5+ years of college + + EDUCD Educational attainment [detailed version] +000 N/A or no schooling +001 N/A +002 No schooling completed +010 Nursery school to grade 4 +011 Nursery school, preschool +012 Kindergarten +013 Grade 1, 2, 3, or 4 +014 Grade 1 +015 Grade 2 +016 Grade 3 +017 Grade 4 +020 Grade 5, 6, 7, or 8 +021 Grade 5 or 6 +022 Grade 5 +023 Grade 6 +024 Grade 7 or 8 +025 Grade 7 +026 Grade 8 +030 Grade 9 +040 Grade 10 +050 Grade 11 +060 Grade 12 +061 12th grade, no diploma +062 High school graduate or GED +063 Regular high school diploma +064 GED or alternative credential +065 Some college, but less than 1 year +070 1 year of college +071 1 or more years of college credit, no degree +080 2 years of college +081 Associate's degree, type not specified +082 Associate's degree, occupational program +083 Associate's degree, academic program +090 3 years of college +100 4 years of college +101 Bachelor's degree +110 5+ years of college +111 6 years of college (6+ in 1960-1970) +112 7 years of college +113 8+ years of college +114 Master's degree +115 Professional degree beyond a bachelor's degree +116 Doctoral degree +999 Missing + + EMPSTAT Employment status [general version] +0 N/A +1 Employed +2 Unemployed +3 Not in labor force + + EMPSTATD Employment status [detailed version] +00 N/A + In Labor Force + Employed +10 At work +11 At work, public emerg +12 Has job, not working +13 Armed forces +14 Armed forces--at work +15 Armed forces--not at work but with job +20 Unemployed +21 Unemp, exper worker +22 Unemp, new worker +30 Not in Labor Force +31 NILF, housework +32 NILF, unable to work +33 NILF, school +34 NILF, other + + LABFORCE Labor force status +0 N/A +1 No, not in the labor force +2 Yes, in the labor force + + OCC1950 Occupation, 1950 basis + Professional, Technical: +000 Accountants and auditors +001 Actors and actresses +002 Airplane pilots and navigators +003 Architects +004 Artists and art teachers +005 Athletes +006 Authors +007 Chemists +008 Chiropractors +009 Clergymen +010 College presidents and deans + Professors and instructors: +012 Agricultural sciences-Professors and instructors +013 Biological sciences-Professors and instructors +014 Chemistry-Professors and instructors +015 Economics-Professors and instructors +016 Engineering-Professors and instructors +017 Geology and geophysics-Professors and instructors +018 Mathematics-Professors and instructors +019 Medical Sciences-Professors and instructors +023 Physics-Professors and instructors +024 Psychology-Professors and instructors +025 Statistics-Professors and instructors +026 Natural science (nec)-Professors and instructors +027 Social sciences (nec)-Professors and instructors +028 Non-scientific subjects-Professors and instructors +029 Subject not specified-Professors and instructors +031 Dancers and dancing teachers +032 Dentists +033 Designers +034 Dietitians and nutritionists +035 Draftsmen +036 Editors and reporters +041 Aeronautical-Engineers +042 Chemical-Engineers +043 Civil-Engineers +044 Electrical-Engineers +045 Industrial-Engineers +046 Mechanical-Engineers +047 Metallurgical, metallurgists-Engineers +048 Mining-Engineers +049 Engineers (nec) +051 Entertainers (nec) +052 Farm and home management advisors +053 Foresters and conservationists +054 Funeral directors and embalmers +055 Lawyers and judges +056 Librarians +057 Musicians and music teachers +058 Nurses, professional +059 Nurses, student professional +061 Agricultural scientists +062 Biological scientists +063 Geologists and geophysicists +067 Mathematicians +068 Physicists +069 Misc. natural scientists +070 Optometrists +071 Osteopaths +072 Personnel and labor relations workers +073 Pharmacists +074 Photographers +075 Physicians and surgeons +076 Radio operators +077 Recreation and group workers +078 Religious workers +079 Social and welfare workers, except group +081 Economists +082 Psychologists +083 Statisticians and actuaries +084 Misc social scientists +091 Sports instructors and officials +092 Surveyors +093 Teachers (n.e.c.) +094 Medical and dental-technicians +095 Testing-technicians +096 Technicians (nec) +097 Therapists and healers (nec) +098 Veterinarians +099 Professional, technical and kindred workers (nec) + Farmers: +100 Farmers (owners and tenants) +123 Farm managers + Managers, Officials, and Proprietors: +200 Buyers and dept heads, store +201 Buyers and shippers, farm products +203 Conductors, railroad +204 Credit men +205 Floormen and floor managers, store +210 Inspectors, public administration +230 Managers and superintendants, building +240 Officers, pilots, pursers and engineers, ship +250 Officials and administratators (nec), public administration +260 Officials, lodge, society, union, etc. +270 Postmasters +280 Purchasing agents and buyers (nec) +290 Managers, officials, and proprietors (nec) + Clerical and Kindred +300 Agents (nec) +301 Attendants and assistants, library +302 Attendants, physicians and dentists office +304 Baggagemen, transportation +305 Bank tellers +310 Bookkeepers +320 Cashiers +321 Collectors, bill and account +322 Dispatchers and starters, vehicle +325 Express messengers and railway mail clerks +335 Mail carriers +340 Messengers and office boys +341 Office machine operators +342 Shipping and receiving clerks +350 Stenographers, typists, and secretaries +360 Telegraph messengers +365 Telegraph operators +370 Telephone operators +380 Ticket, station, and express agents +390 Clerical and kindred workers (n.e.c.) + Sales workers: +400 Advertising agents and salesmen +410 Auctioneers +420 Demonstrators +430 Hucksters and peddlers +450 Insurance agents and brokers +460 Newsboys +470 Real estate agents and brokers +480 Stock and bond salesmen +490 Salesmen and sales clerks (nec) + Craftsmen: +500 Bakers +501 Blacksmiths +502 Bookbinders +503 Boilermakers +504 Brickmasons,stonemasons, and tile setters +505 Cabinetmakers +510 Carpenters +511 Cement and concrete finishers +512 Compositors and typesetters +513 Cranemen,derrickmen, and hoistmen +514 Decorators and window dressers +515 Electricians +520 Electrotypers and stereotypers +521 Engravers, except photoengravers +522 Excavating, grading, and road machinery operators +523 Foremen (nec) +524 Forgemen and hammermen +525 Furriers +530 Glaziers +531 Heat treaters, annealers, temperers +532 Inspectors, scalers, and graders log and lumber +533 Inspectors (nec) +534 Jewelers, watchmakers, goldsmiths, and silversmiths +535 Job setters, metal +540 Linemen and servicemen, telegraph, telephone, and power +541 Locomotive engineers +542 Locomotive firemen +543 Loom fixers +544 Machinists +545 Airplane-mechanics and repairmen +550 Automobile-mechanics and repairmen +551 Office machine-mechanics and repairmen +552 Radio and television-mechanics and repairmen +553 Railroad and car shop-mechanics and repairmen +554 Mechanics and repairmen (nec) +555 Millers, grain, flour, feed, etc +560 Millwrights +561 Molders, metal +562 Motion picture projectionists +563 Opticians and lens grinders and polishers +564 Painters, construction and maintenance +565 Paperhangers +570 Pattern and model makers, except paper +571 Photoengravers and lithographers +572 Piano and organ tuners and repairmen +573 Plasterers +574 Plumbers and pipe fitters +575 Pressmen and plate printers, printing +580 Rollers and roll hands, metal +581 Roofers and slaters +582 Shoemakers and repairers, except factory +583 Stationary engineers +584 Stone cutters and stone carvers +585 Structural metal workers +590 Tailors and tailoresses +591 Tinsmiths, coppersmiths, and sheet metal workers +592 Tool makers, and die makers and setters +593 Upholsterers +594 Craftsmen and kindred workers (nec) +595 Members of the armed services + Operatives: +600 Auto mechanics apprentice +601 Bricklayers and masons apprentice +602 Carpenters apprentice +603 Electricians apprentice +604 Machinists and toolmakers apprentice +605 Mechanics, except auto apprentice +610 Plumbers and pipe fitters apprentice +611 Apprentices, building trades (nec) +612 Apprentices, metalworking trades (nec) +613 Apprentices, printing trades +614 Apprentices, other specified trades +615 Apprentices, trade not specified +620 Asbestos and insulation workers +621 Attendants, auto service and parking +622 Blasters and powdermen +623 Boatmen, canalmen, and lock keepers +624 Brakemen, railroad +625 Bus drivers +630 Chainmen, rodmen, and axmen, surveying +631 Conductors, bus and street railway +632 Deliverymen and routemen +633 Dressmakers and seamstresses, except factory +634 Dyers +635 Filers, grinders, and polishers, metal +640 Fruit, nut, and vegetable graders, and packers, except facto +641 Furnacemen, smeltermen and pourers +642 Heaters, metal +643 Laundry and dry cleaning Operatives +644 Meat cutters, except slaughter and packing house +645 Milliners +650 Mine operatives and laborers +660 Motormen, mine, factory, logging camp, etc +661 Motormen, street, subway, and elevated railway +662 Oilers and greaser, except auto +670 Painters, except construction or maintenance +671 Photographic process workers +672 Power station operators +673 Sailors and deck hands +674 Sawyers +675 Spinners, textile +680 Stationary firemen +681 Switchmen, railroad +682 Taxicab drivers and chauffeurs +683 Truck and tractor drivers +684 Weavers, textile +685 Welders and flame cutters +690 Operative and kindred workers (nec) + Service Workers (private household): +700 Housekeepers, private household +710 Laundresses, private household +720 Private household workers (nec) + Service Workers (not household): +730 Attendants, hospital and other institution +731 Attendants, professional and personal service (nec) +732 Attendants, recreation and amusement +740 Barbers, beauticians, and manicurists +750 Bartenders +751 Bootblacks +752 Boarding and lodging house keepers +753 Charwomen and cleaners +754 Cooks, except private household +760 Counter and fountain workers +761 Elevator operators +762 Firemen, fire protection +763 Guards, watchmen, and doorkeepers +764 Housekeepers and stewards, except private household +770 Janitors and sextons +771 Marshals and constables +772 Midwives +773 Policemen and detectives +780 Porters +781 Practical nurses +782 Sheriffs and bailiffs +783 Ushers, recreation and amusement +784 Waiters and waitresses +785 Watchmen (crossing) and bridge tenders +790 Service workers, except private household (nec) + Farm Laborers: +810 Farm foremen +820 Farm laborers, wage workers +830 Farm laborers, unpaid family workers +840 Farm service laborers, self-employed + Laborers: +910 Fishermen and oystermen +920 Garage laborers and car washers and greasers +930 Gardeners, except farm and groundskeepers +940 Longshoremen and stevedores +950 Lumbermen, raftsmen, and woodchoppers +960 Teamsters +970 Laborers (nec) +979 Not yet classified + Non-occupational response: +980 Keeps house/housekeeping at home/housewife +981 Imputed keeping house (1850-1900) +982 Helping at home/helps parents/housework +983 At school/student +984 Retired +985 Unemployed/without occupation +986 Invalid/disabled w/ no occupation reported +987 Inmate +990 New Worker +991 Gentleman/lady/at leisure +995 Other non-occupation +997 Occupation missing/unknown +999 N/A (blank) + + IND1950 Industry, 1950 basis +000 N/A or none reported + Agriculture, Forestry, and Fishing: +105 Agriculture +116 Forestry +126 Fisheries + Mining: +206 Metal mining +216 Coal mining +226 Crude petroleum and natural gas extraction +236 Nonmettalic mining and quarrying, except fuel +239 Mining, not specified + Construction: +246 Construction + Manufacturing: + Durable Goods: +306 Logging +307 Sawmills, planing mills, and mill work +308 Misc wood products +309 Furniture and fixtures +316 Glass and glass products +317 Cement, concrete, gypsum and plaster products +318 Structural clay products +319 Pottery and related prods +326 Misc nonmetallic mineral and stone products +336 Blast furnaces, steel works, and rolling mills +337 Other primary iron and steel industries +338 Primary nonferrous industries +346 Fabricated steel products +347 Fabricated nonferrous metal products +348 Not specified metal industries +356 Agricultural machinery and tractors +357 Office and store machines +358 Misc machinery +367 Electrical machinery, equipment and supplies +376 Motor vehicles and motor vehicle equipment +377 Aircraft and parts +378 Ship and boat building and repairing +379 Railroad and misc transportation equipment +386 Professional equipment +387 Photographic equipment and supplies +388 Watches, clocks, and clockwork-operated devices +399 Misc manufacturing industries + Nondurable Goods: +406 Meat products +407 Dairy products +408 Canning and preserving fruits, vegetables, and seafoods +409 Grain-mill products +416 Bakery products +417 Confectionery and related products +418 Beverage industries +419 Misc food preparations and kindred products +426 Not specified food industries +429 Tobacco manufactures +436 Knitting mills +437 Dyeing and finishing textiles, except knit goods +438 Carpets, rugs, and other floor coverings +439 Yarn, thread, and fabric +446 Misc textile mill products +448 Apparel and accessories +449 Misc fabricated textile products +456 Pulp, paper, and paper-board mills +457 Paperboard containers and boxes +458 Misc paper and pulp products +459 Printing, publishing, and allied industries +466 Synthetic fibers +467 Drugs and medicines +468 Paints, varnishes, and related products +469 Misc chemicals and allied products +476 Petroleum refining +477 Misc petroleum and coal products +478 Rubber products +487 Leather: tanned, curried, and finished +488 Footwear, except rubber +489 Leather products, except footwear +499 Not specified manufacturing industries + Transportation, Communication, and Other Utilities: + Transportation: +506 Railroads and railway +516 Street railways and bus lines +526 Trucking service +527 Warehousing and storage +536 Taxicab service +546 Water transportation +556 Air transportation +567 Petroleum and gasoline pipe lines +568 Services incidental to transportation + Telecommunications: +578 Telephone +579 Telegraph + Utilities and Sanitary Services: +586 Electric light and power +587 Gas and steam supply systems +588 Electric-gas utilities +596 Water supply +597 Sanitary services +598 Other and not specified utilities + Wholesale and Retail Trade: + Wholesale Trade: +606 Motor vehicles and equipment +607 Drugs, chemicals, and allied products +608 Dry goods apparel +609 Food and related products +616 Electrical goods, hardware, and plumbing equipment +617 Machinery, equipment, and supplies +618 Petroleum products +619 Farm prods--raw materials +626 Misc wholesale trade +627 Not specified wholesale trade + Retail Trade: +636 Food stores, except dairy +637 Dairy prods stores and milk retailing +646 General merchandise +647 Five and ten cent stores +656 Apparel and accessories stores, except shoe +657 Shoe stores +658 Furniture and house furnishings stores +659 Household appliance and radio stores +667 Motor vehicles and accessories retailing +668 Gasoline service stations +669 Drug stores +679 Eating and drinking places +686 Hardware and farm implement stores +687 Lumber and building material retailing +688 Liquor stores +689 Retail florists +696 Jewelry stores +697 Fuel and ice retailing +698 Misc retail stores +699 Not specified retail trade + Finance, Insurance, and Real Estate: +716 Banking and credit +726 Security and commodity brokerage and invest companies +736 Insurance +746 Real estate +756 Real estate-insurance-law offices + Business and Repair Services: +806 Advertising +807 Accounting, auditing, and bookkeeping services +808 Misc business services +816 Auto repair services and garages +817 Misc repair services + Personal services: +826 Private households +836 Hotels and lodging places +846 Laundering, cleaning, and dyeing +847 Dressmaking shops +848 Shoe repair shops +849 Misc personal services + Entertainment and Recreation Services: +856 Radio broadcasting and television +857 Theaters and motion pictures +858 Bowling alleys, and billiard and pool parlors +859 Misc entertainment and recreation services + Professional and Related Services: +868 Medical and other health services, except hospitals +869 Hospitals +879 Legal services +888 Educational services +896 Welfare and religious services +897 Nonprofit membership organizs. +898 Engineering and architectural services +899 Misc professional and related + Public Administration: +906 Postal service +916 Federal public administration +926 State public administration +936 Local public administration +946 Public Administration, level not specified +976 Common or general laborer +979 Not yet specified +980 Unpaid domestic work +982 Housework at home +983 School response (students, etc.) +984 Retired +986 Sick/disabled +987 Institution response + Other: +991 Lady/Man of leisure +995 Non-industrial response +997 Nonclassifiable +998 Industry not reported +999 Blank or blank equivalent + + CLASSWKR Class of worker [general version] +0 N/A +1 Self-employed +2 Works for wages + + CLASSWKRD Class of worker [detailed version] +00 N/A +10 Self-employed +11 Employer +12 Working on own account +13 Self-employed, not incorporated +14 Self-employed, incorporated +20 Works for wages +21 Works on salary (1920) +22 Wage/salary, private +23 Wage/salary at non-profit +24 Wage/salary, government +25 Federal govt employee +26 Armed forces +27 State govt employee +28 Local govt employee +29 Unpaid family worker + + WKSWORK2 Weeks worked last year, intervalled +0 N/A +1 1-13 weeks +2 14-26 weeks +3 27-39 weeks +4 40-47 weeks +5 48-49 weeks +6 50-52 weeks + + HRSWORK2 Hours worked last week, intervalled +0 N/A +1 1-14 hours +2 15-29 hours +3 30-34 hours +4 35-39 hours +5 40 hours +6 41-48 hours +7 49-59 hours +8 60+ hours + + UOCC Usual occupation + PROFESSIONAL AND SEMIPROFESSIONAL WORKERS + Professional Workers +001 Artists and art teachers +002 Authors +003 Editors and reporters +004 Chemists, assayers, and metallurgists +005 Clergymen +006 College presidents, professors, and instructors +007 Dentists +008 Chemical engineers +009 Civil engineers +010 Electrical engineers +011 Industrial engineers +012 Mechanical engineers +013 Mining and metallurgical engineers +014 Lawyers and judges +015 Musicians and music teachers +016 Pharmacists +017 Physicians and surgeons +018 Teachers, n.e.c., excluding college teachers and teachers of art, dancing, miscellaneous, and athletics +019 Trained nurses and student nurses +020 Actors and actresses +021 Architects +022 County agents and farm demonstrators +023 Librarians +024 Osteopaths +025 Social and welfare workers +026 Veterinarians +027 Professional workers, n.e.c. + Semiprofessional Workers +028 Designers +029 Draftsmen +030 Funeral directors and embalmers +031 Photographers +032 Religious workers +033 Technicians and assistants, laboratory +034 Technicians, except laboratory +035 Athletes +036 Aviators +037 Chiropractors +038 Dancers, dancing teachers, and chorus girls +039 Healers and medical service workers, n.e.c +040 Optometrists +041 Radio and wireless operators +042 Showmen +043 Sports instructors and officials +044 Surveyors +045 Semiprofessional workers, n.e.c. + FARMERS AND FARM MANAGERS +098 Farmers owners and tenants +099 Farm managers + PROPRIETORS, MANAGERS, AND OFFICIALS, EXCEPT FARM +100 Advertising agents +102 Conductors--railroad + Inspectors--Government +104 Inspectors--United States +106 Inspectors--state +108 Inspectors--city +110 Inspectors--county and local + Public Officials, n.e.c. +112 Officials--United States +114 Officials--state +116 Officials--city +118 Officials--county and local + Miscellaneous Proprietors, Managers, and Officials +120 Buyers and department heads--store +122 Country buyers and shippers of livestock and other farm products +124 Credit men +126 Floormen and floor managers--store +128 Managers and superintendents--building +130 Officers, pilots, pursers, and engineers--ship +132 Officials--lodge, society, union, etc. +134 Postmasters +136 Purchasing agents and buyers, n.e.c. +156 Proprietors, managers, and officials, n.e.c. + Clerical and Kindred Workers +200 Agents, n.e.c. +210 Bookkeepers, accountants, and cashiers +220 "Clerks" in stores +222 Mail carriers +224 Messengers, errand, and office boys and girls +226 Shipping and receiving clerks +236 Stenographers, typists, and secretaries +240 Telegraph operators +242 Telephone operators +244 Ticket, station, and express agents +246 Attendants and assistants--library +248 Attendants--physicians' and dentists' offices +250 Baggagemen--transportation +252 Collectors--bill and account +254 Express messengers and railway mail clerks +256 Office machine operators +258 Telegraph messengers +266 Clerical and kindred workers, n.e.c. + Salesmen and Saleswomen +270 Canvassers and solicitors +272 Hucksters and peddlars +274 Insurance agents and brokers +276 Real estate agents and brokers +278 Traveling salesmen and sales agents +280 Auctioneers +282 Demonstrators +284 Newsboys +286 Salesmen, finance, brokerage, and commission firms +298 Salesmen and saleswomen, n.e.c. + CRAFTSMEN, FOREMEN, AND KINDRED WORKERS +300 Bakers +302 Blacksmiths, forgemen, and hammermen +304 Boilermakers +306 Brickmasons, stonemasons, and tile setters +308 Carpenters +310 Compositors and typesetters +312 Decorators and window dressers +314 Electricians +316 Foremen, n.e.c. +318 Inspectors, n.e.c. +320 Jewelers, watchmakers, goldsmiths, and silversmiths +322 Locomotive engineers +324 Locomotive firemen +326 Machinists +327 Millwrights +328 Tool makers, and die makers and setters + Mechanics and Repairmen +330 Mechanics and repairmen--airplane +332 Mechanics and repairmen--automobile +334 Mechanics and repairmen--railroad and car shop +336 Mechanics and repairmen, n.e.c. +338 Molders--metal +340 Painters--construction and maintenance +342 Paperhangers +344 Pattern and model makers, except paper +346 Plasterers +348 Plumbers and gas and steam fitters +350 Roofers and slaters +352 Sawyers +354 Shoemakers and repairers--not in factory + Stationary Engineers, Cranemen, Hoistmen, etc. +356 Stationary engineers +358 Cranemen, hoistmen, and construction machinery operators +260 Tailors and tailoresses +362 Tinsmiths, coppersmiths, and sheet metal workers +364 Upholsterers + Other Craftsmen and Kindred Workers +366 Cabinetmakers +368 Cement and concrete finishers +370 Electrotypers and stereotypers +372 Engravers, except photoengravers +374 Furriers +376 Glaziers +378 Heat treaters, annealers, and temperers +380 Inspectors, scalers, and graders--log and lumber +382 Loom fixers +384 Millers--grain, flour, feed, etc. +386 Opticians and lens grinders and polishers +388 Photoengravers and lithographers +390 Piano and organ tuners +392 Pressmen and plate printers--printing +394 Rollers and roll hands--metal +396 Stonecutters and stone carvers +398 Structural and ornamental metal workers + OPERATIVES AND KINDRED WORKERS +400 Carpenters' apprentices +402 Electricians' apprentices +404 Machinists' apprentices +406 Plumbers' apprentices +408 Building and hand trade apprentices, n.e.c. +410 Apprentices--printing trades +412 Apprentices--specified trades, n.e.c. +414 Apprentices--trades n.s. +416 Attendants--filling station, parking lot, garage, and airport +418 Brakemen--railroad +420 Chauffeurs and drivers, bus, taxi, truck, and tractor drivers +430 Conductors--bus and street railway +432 Deliverymen +434 Dressmakers and seamstresses--not in factory +436 Buffers and polishers +438 Filers +440 Grinders +442 Firemen, except locomotive and fire department +444 Furnacemen, smeltermen, and pourers +446 Heaters-metal +448 Laundry operatives and laundresses --except private family +450 Linemen and servicemen--telegraph, telephone, and power +452 Meat cutters--except slaughter and packing house +454 Mine operatives and laborers, including laborers who extract minerals +456 Motormen--street, subway, and elevated railway +458 Painters--except construction and maintenance +460 Sailors and deck hands--except United States Navy +462 Switchmen--railroad +464 Welders and flame-cutters + Miscellaneous Operatives and Kindred Workers +466 Asbestos and insulation workers +468 Blasters and powdermen +470 Boatmen, canalmen, and lock keepers +472 Chainmen, rodmen, and axmen--surveying +474 Dyers +476 Fruit and vegetable graders and packers--except in cannery +478 Milliners--not in factory +480 Motion picture projectionists +482 Motormen, vehicle--mine, factory, logging camp, etc. +484 Oilers, machinery +486 Photographic process workers +488 Power station operators +496 Operatives and kindred workers, n.e.c. + DOMESTIC SERVICE WORKERS +500 Housekeepers--private family +510 Laundresses--private family +520 Servants--private family + PROTECTIVE SERVICE WORKERS +600 Firemen, fire department +602 Guards, watchmen, and doorkeepers +604 Policemen and detectives--government +606 Policemen and detectives--except government +608 Soldiers, sailors, marines, and coast guards +610 Marshals and constables +612 Sheriffs and bailiffs +614 Watchmen, crossing and bridge tenders + SERVICE WORKERS, EXCEPT DOMESTIC AND PROTECTIVE +700 Barbers, beauticians, and manicurists +710 Bartenders +712 Boarding house and lodging house keepers +714 Charwomen and cleaners +720 Cooks--except private family +730 Elevator operators +732 Housekeepers, stewards, and hostesses--except private family +740 Janitors and sextons +750 Porters +760 Practical nurses and midwives +770 Servants--except private family +780 Waiters and waitresses--except private family + Miscellaneous Service Workers--Except Domestic and Protective +790 Attendants--hospital and other institution +792 Attendants--professional and personal service, n.e.c. +794 Attendants--recreation and amusement +796 Bootblacks +798 Ushers--amusement place or assembly + FARM LABORERS AND FOREMEN +844 Farm foremen +866 Farm laborers--wage workers +888 Farm laborers--unpaid family workers + LABORERS, EXCEPT FARM +900 Fishermen and oystermen +902 Garage laborers and car washers and greasers +904 Gardeners--except farm and groundskeepers +906 Longshoremen and stevedores +908 Lumbermen, raftsmen, and woodchoppers +910 Teamsters +988 Laborers, n.e.c. + NONCLASSIFIABLE +995 None, etc. +996 Non-Occupation Response +997 Blank +998 Nonclassifiable occupation +999 N/A + + UOCC95 Usual occupation, 1950 classification + Professional, Technical: +000 Accountants and auditors +001 Actors and actresses +002 Airplane pilots and navigators +003 Architects +004 Artists and art teachers +005 Athletes +006 Authors +007 Chemists +008 Chiropractors +009 Clergymen +010 College presidents and deans + Professors and instructors: +012 Agricultural sciences +013 Biological sciences +014 Chemistry +015 Economics +016 Engineering +017 Geology and geophysics +018 Mathematics +019 Medical sciences +023 Physics +024 Psychology +025 Statistics +026 Natural science (n.e.c.) +027 Social sciences (n.e.c.) +028 Nonscientific subjects +029 Professors and instructors, subject not specified +031 Dancers and dancing teachers +032 Dentists +033 Designers +034 Dieticians and nutritionists +035 Draftsmen +036 Editors and reporters +041 Engineers, aeronautical +042 Engineers, chemical +043 Engineers, civil +044 Engineers, electrical +045 Engineers, industrial +046 Engineers, mechanical +047 Engineers, metallurgical, metallurgists +048 Engineers, mining +049 Engineers (n.e.c.) +051 Entertainers (n.e.c.) +052 Farm and home management advisors +053 Foresters and conservationists +054 Funeral directors and embalmers +055 Lawyers and judges +056 Librarians +057 Musicians and music teachers +058 Nurses, professional +059 Nurses, student professional +061 Agricultural scientists +062 Biological scientists +063 Geologists and geophysicists +067 Mathematicians +068 Physicists +069 Miscellaneous natural scientists +070 Optometrists +071 Osteopaths +072 Personnel and labor relations workers +073 Pharmacists +074 Photographers +075 Physicians and surgeons +076 Radio operators +077 Recreation and group workers +078 Religious workers +079 Social and welfare workers, except group +081 Economists +082 Psychologists +083 Statisticians and actuaries +084 Miscellaneous social scientists +091 Sports instructors and officials +092 Surveyors +093 Teachers (n.e.c.) +094 Technicians, medical and dental +095 Technicians, testing +096 Technicians (n.e.c.) +097 Therapists and healers (n.e.c.) +098 Veterinarians +099 Professional, technical and kindred workers (n.e.c.) + Farmers: +100 Farmers (owners and tenants) +123 Farm managers + Managers, Officials, and Proprietors: +200 Buyers and department heads, store +201 Buyers and shippers, farm products +203 Conductors, railroad +204 Credit men +205 Floormen and floor managers, store +210 Inspectors, public administration +230 Managers and superintendents, building +240 Officers, pilots, pursers and engineers, ship +250 Officials and administrators (n.e.c.), public administration +260 Officials, lodge, society, union, etc. +270 Postmasters +280 Purchasing agents and buyers (n.e.c.) +290 Managers, officials, and proprietors (n.e.c.) + Clerical and Kindred +300 Agents (n.e.c.) +301 Attendants and assistants, library +302 Attendants, physician's and dentist's office +304 Baggagemen, transportation +305 Bank tellers +310 Bookkeepers +320 Cashiers +321 Collectors, bill and account +322 Dispatchers and starters, vehicle +325 Express messengers and railway mail clerks +335 Mail carriers +340 Messengers and office boys +341 Office machine operators +342 Shipping and receiving clerks +350 Stenographers, typists, and secretaries +360 Telegraph messengers +365 Telegraph operators +370 Telephone operators +380 Ticket, station, and express agents +390 Clerical and kindred workers (n.e.c.) + Sales workers: +400 Advertising agents and salesmen +410 Auctioneers +420 Demonstrators +430 Hucksters and peddlers +450 Insurance agents and brokers +460 Newsboys +470 Real estate agents and brokers +480 Stock and bond salesmen +490 Salesmen and sales clerks (n.e.c.) + Craftsmen: +500 Bakers +501 Blacksmiths +502 Bookbinders +503 Boilermakers +504 Brickmasons, stonemasons, and tile setters +505 Cabinetmakers +510 Carpenters +511 Cement and concrete finishers +512 Compositors and typesetters +513 Cranemen, derrickmen, and hoistmen +514 Decorators and window dressers +515 Electricians +520 Electrotypers and stereotypers +521 Engravers, except photoengravers +522 Excavating, grading, and road machinery operators +523 Foremen (n.e.c.) +524 Forgemen and hammermen +525 Furriers +530 Glaziers +531 Heat treaters, annealers, temperers +532 Inspectors, scalers, and graders, log and lumber +533 Inspectors (n.e.c.) +534 Jewelers, watchmakers, goldsmiths, and silversmiths +535 Job setters, metal +540 Linemen and servicemen, telegraph, telephone, and power +541 Locomotive engineers +542 Locomotive firemen +543 Loom fixers +544 Machinists +545 Mechanics and repairmen, airplane +550 Mechanics and repairmen, automobile +551 Mechanics and repairmen, office machine +552 Mechanics and repairmen, radio and television +553 Mechanics and repairmen, railroad and car shop +554 Mechanics and repairmen (n.e.c.) +555 Millers, grain, flour, feed, etc. +560 Millwrights +561 Molders, metal +562 Motion picture projectionists +563 Opticians and lens grinders and polishers +564 Painters, construction and maintenance +565 Paperhangers +570 Pattern and model makers, except paper +571 Photoengravers and lithographers +572 Piano and organ tuners and repairmen +573 Plasterers +574 Plumbers and pipe fitters +575 Pressmen and plate printers, printing +580 Rollers and roll hands, metal +581 Roofers and slaters +582 Shoemakers and repairers, except factory +583 Stationary engineers +584 Stone cutters and stone carvers +585 Structural metal workers +590 Tailors and tailoresses +591 Tinsmiths, coppersmiths, and sheet metal workers +592 Tool makers, and die makers and setters +593 Upholsterers +594 Craftsmen and kindred workers (n.e.c.) +595 Members of the armed services + Operatives: +600 Apprentice auto mechanics +601 Apprentice bricklayers and masons +602 Apprentice carpenters +603 Apprentice electricians +604 Apprentice machinists and toolmakers +605 Apprentice mechanics, except auto +610 Apprentice plumbers and pipe fitters +611 Apprentices, building trades (n.e.c.) +612 Apprentices, metalworking trades (n.e.c.) +613 Apprentices, printing trades +614 Apprentices, other specified trades +615 Apprentices, trade not specified +620 Asbestos and insulation workers +621 Attendants, auto service and parking +622 Blasters and powdermen +623 Boatmen, canalmen, and lock keepers +624 Brakemen, railroad +625 Bus drivers +630 Chainmen, rodmen, and axmen, surveying +631 Conductors, bus and street railway +632 Deliverymen and routemen +633 Dressmakers and seamstresses, except factory +634 Dyers +635 Filers, grinders, and polishers, metal +640 Fruit, nut, and vegetable graders, and packers, except factory +641 Furnacemen, smeltermen and pourers +642 Heaters, metal +643 Laundry and dry cleaning operatives +644 Meat cutters, except slaughter and packing house +645 Milliners +650 Mine operatives and laborers +660 Motormen, mine, factory, logging camp, etc. +661 Motormen, street, subway, and elevated railway +662 Oilers and greaser, except auto +670 Painters, except construction or maintenance +671 Photographic process workers +672 Power station operators +673 Sailors and deck hands +674 Sawyers +675 Spinners, textile +680 Stationary firemen +681 Switchmen, railroad +682 Taxicab drivers and chauffers +683 Truck and tractor drivers +684 Weavers, textile +685 Welders and flame cutters +690 Operative and kindred workers (n.e.c.) + Service Workers (private household): +700 Housekeepers, private household +710 Laundressses, private household +720 Private household workers (n.e.c.) + Service Workers (not household): +730 Attendants, hospital and other institution +731 Attendants, professional and personal service (n.e.c.) +732 Attendants, recreation and amusement +740 Barbers, beauticians, and manicurists +750 Bartenders +751 Bootblacks +752 Boarding and lodging house keepers +753 Charwomen and cleaners +754 Cooks, except private household +760 Counter and fountain workers +761 Elevator operators +762 Firemen, fire protection +763 Guards, watchmen, and doorkeepers +764 Housekeepers and stewards, except private household +770 Janitors and sextons +771 Marshals and constables +772 Midwives +773 Policemen and detectives +780 Porters +781 Practical nurses +782 Sheriffs and bailiffs +783 Ushers, recreation and amusement +784 Waiters and waitresses +785 Watchmen (crossing) and bridge tenders +790 Service workers, except private household (n.e.c.) + Farm Laborers: +810 Farm foremen +820 Farm laborers, wage workers +830 Farm laborers, unpaid family workers +840 Farm service laborers, self-employed + Laborers: +910 Fishermen and oystermen +920 Garage laborers and car washers and greasers +930 Gardeners, except farm, and groundskeepers +940 Longshoremen and stevedores +950 Lumbermen, raftsmen, and woodchoppers +960 Teamsters +970 Laborers (n.e.c.) +975 Employed, unclassifiable + Non-occupational response: +980 Keeps house/housekeeping at home/housewife +982 Helping at home/helps parents/housework +983 At school/student +984 Retired +986 Invalid/disabled w/ no occupation reported +987 Inmate +995 Other non-occupational response +997 Occupation missing/unknown +999 N/A (blank) + + UIND Usual industry + AGRICULTURE, FORESTRY, AND FISHERY  +001 Agriculture +002 Forestry except logging +003 Fishery + MINING +004 Coal mining +005 Metal mining +006 Crude petroleum and natural gas production, including natural gasoline production +007 Sand and gravel production +008 Stone quarrying +009 Miscellaneous nonmetallic mining +010 Mining, n.s. + CONSTRUCTION +011 All construction + MANUFACTURING  + Food and Kindred Products: +012 Bakery products +013 Beverage industries +014 Canning and preserving fruits, vegetables, and sea food +015 Confectionery +016 Dairy products +017 Grain-mill products +018 Meat products +019 Miscellaneous food industries +020 Tobacco manufactures + Textile-mill Products: +021 Cotton manufactures +022 Silk and rayon manufactures +023 Woolen and worsted manufactures +024 Knit goods +025 Dyeing and finishing textiles +026 Carpets, rugs, and other floor coverings +027 Hats except cloth and millinery +028 Miscellaneous textile goods +029 Textile mills, n.s. + Apparel, and Other Fabricated Textile Products: +030 Apparel and accessories +031 Miscellaneous fabricated textile products + Lumber, Furniture, and Lumber Products: +032 Logging +033 Sawmills and planing mills +034 Furniture and store fixtures +035 Miscellaneous wooden goods + Paper and Allied Products: +036 Pulp, paper, and paperboard mills +037 Paperboard containers and boxes +038 Miscellaneous paper and pulp products +039 Printing, publishing, and allied industries + Chemicals and Allied Products: +040 Paints, varnishes, and colors +041 Rayon and allied products +042 Miscellaneous chemical industries + Petroleum and Coal Products: +043 Petroleum refining +044 Miscellaneous petroleum and coal products +045 Rubber products + Leather and Leather Products: +046 Leather (tanned, curried, and finished) +047 Footwear industries except rubber +048 Leather products except footwear + Stone, Clay, and Glass Products: +049 Cement, and concrete, gypsum, and plaster products +050 Cut-stone and stone products +051 Glass and glass products +052 Pottery and related products +053 Structural clay products +054 Miscellaneous nonmetallic mineral products + Iron and Steel and Their Products: +055 Blast furnaces, steel works, and rolling mills +056 Tin cans and other tinware +057 Miscellaneous iron and steel industries + Nonferrous Metals and Their Products: +058 Nonferrous metal primary products +059 Clocks, watches, jewelry, and silverware, including metal engraving except for printing purposes, plating, and polishing +060 Miscellaneous nonferrous metal products + Machinery: +061 Agricultural machinery and tractors +062 Electrical machinery and equipment +063 Office and store machines, equipment, and supplies +064 Miscellaneous machinery + Transportation Equipment: +065 Aircraft and parts +066 Automobiles and automobile equipment +067 Ship and boat building and repairing +068 Railroad and miscellaneous transportation equipment +069 Metal industries, n.s. + Miscellaneous Manufacturing Industries: +070 Scientific and photographic equipment and supplies +071 Miscellaneous manufacturing industries n.e.c. +072 Manufacturing industries, n.s. + TRANSPORTATION, COMMUNICATION, AND OTHER PUBLIC UTILITIES  + Transportation: +073 Air transportation +074 Petroleum and gasoline pipe lines +075 Railroads, including railroad repair shops +076 Railway express service +077 Street railways and bus lines, including suburban and interurban railways +078 Taxicab service +079 Trucking service +080 Water transportation +081 Warehousing and storage +082 Services incidental to transportation +083 Transportation, n.s. + Communication: +084 Telephone -- wire and radio +085 Telegraph -- wire and radio +086 Radio broadcasting and television + Utilities: +087 Electric light and power +088 Gas works and steam plants +089 Water and sanitary services + WHOLESALE AND RETAIL TRADE  +090 Wholesale trade + Retail Trade: +091 Food stores, except dairy products +092 Dairy products stores and milk retailing +093 General merchandise stores +094 Limited price variety stores +095 Apparel and accessories stores, except shoes +096 Shoe stores +097 Furniture and housefurnishings stores +098 Household appliance and radio stores +099 Motor vehicles and accessories retailing +100 Filling stations +101 Drug stores +102 Eating and drinking places +103 Hardware and farm implement stores +104 Lumber and building material retailing +105 Liquor stores +106 Retail florists +107 Jewelry stores +108 Fuel and ice retailing +109 Miscellaneous retail stores +110 Retail trade, n.s. (may include some returns not specified as to whether the workers were in wholesale or retail trade) + FINANCE, INSURANCE, AND REAL ESTATE  +111 Banking and other finance +112 Insurance +113 Real estate + BUSINESS AND REPAIR SERVICES  +114 Advertising +115 Business services, except advertising +116 Automobile storage, rental, and repair services +117 Miscellaneous repair services and hand trades + PERSONAL SERVICES  +118 Domestic service +119 Hotels and lodging places +120 Laundering, cleaning, and dyeing services +121 Miscellaneous personal services + AMUSEMENT, RECREATION, AND RELATED SERVICES  +122 Theaters and motion pictures +123 Miscellaneous amusement and recreation + PROFESSIONAL AND RELATED SERVICES  +124 Educational services +125 Medical and other health services +126 Legal, engineering, and miscellaneous professional services +127 Charitable, religious, and membership organizations + GOVERNMENT  +128 Postal service +129 National defense +130 Federal government n.e.c. +131 State and local government + NONCLASSIFIABLE  +995 None, etc. +996 Non-Industry Response +997 Blank +998 Nonclassifiable +999 N/A: under 14 years old, not in labor force, and institutional inmates in IND and IND50; under 14 years old in UINDUS) + + UCLASSWK Usual class of worker +0 N/A +1 Self-employed +2 Employer +3 Wage/salary, private work +4 Wage/salary, gov't work +6 Unpaid family worker +7 No usual occupation +8 Illegible + + INCNONWG Had non-wage/salary income over $50 +0 N/A +1 Less than $50 nonwage, nonsalary income +2 $50+ nonwage, nonsalary income +9 Missing + + MIGRATE5 Migration status, 5 years [general version] +0 N/A +1 Same house +2 Moved within state +3 Moved between states +4 Abroad five years ago +8 Moved (place not reported) +9 Unknown + + MIGRATE5D Migration status, 5 years [detailed version] +00 N/A +10 Same house + Different house: +20 Same state (migration status within state unknown) +21 Different house, moved within county +22 Different house, moved within state, between counties +23 Different house, moved within state, within PUMA +24 Different house, moved within state, between PUMAs +25 Different house, unknown within state +30 Different state (general) +31 Moved between contiguous states +32 Moved between non-contiguous states +33 Unknown between states +40 Abroad five years ago +80 Moved, but place was not reported +90 Unknown + + MIGPLAC5 State or country of residence 5 years ago +000 N/A +001 Alabama +002 Alaska +004 Arizona +005 Arkansas +006 California +008 Colorado +009 Connecticut +010 Delaware +011 District of Columbia +012 Florida +013 Georgia +015 Hawaii +016 Idaho +017 Illinois +018 Indiana +019 Iowa +020 Kansas +021 Kentucky +022 Louisiana +023 Maine +024 Maryland +025 Massachusetts +026 Michigan +027 Minnesota +028 Mississippi +029 Missouri +030 Montana +031 Nebraska +032 Nevada +033 New Hampshire +034 New Jersey +035 New Mexico +036 New York +037 North Carolina +038 North Dakota +039 Ohio +040 Oklahoma +041 Oregon +042 Pennsylvania +044 Rhode Island +045 South Carolina +046 South Dakota +047 Tennessee +048 Texas +049 Utah +050 Vermont +051 Virginia +053 Washington +054 West Virginia +055 Wisconsin +056 Wyoming + State group codes (1980 UR sample) +061 Maine-New Hampshire-Vermont +062 Massachussetts-Rhode Island +063 Minnesota-Iowa-Missouri-Kansas-Nebraska-Dakotas +064 Maryland-Delaware +065 Montana-Idaho-Wyoming +066 Utah-Nevada +067 Arizona-New Mexico +068 Alaska-Hawaii +099 United States, not specified or state confidential +100 Samoa +105 Guam +110 Puerto Rico +115 Virgin Islands +119 US outlying area (1980) +120 Other US Possessions +150 Canada +151 English Canada +152 French Canada +155 St Pierre and Miquelon +160 Atlantic Islands +199 North America +200 Mexico +211 Belize/British Honduras +212 Costa Rica +213 El Salvador +214 Guatemala +215 Honduras +216 Nicaragua +217 Panama +218 Canal Zone +219 Central America, nec +250 Cuba +260 West Indies +261 Dominican Republic +262 Haita +263 Jamaica +264 British West Indies +266 Trinidad and Tobago +267 Other West Indies +305 Argentina +310 Bolivia +315 Brazil +320 Chile +325 Colombia +330 Ecuador +345 Paraguay +350 Peru +360 Uruguay +365 Venezuela +370 North or Central America, n.s. (2000 5%) +390 South America, nec +400 Denmark +401 Finland +402 Iceland +404 Norway +405 Sweden +410 England +411 Scotland +412 Wales +413 United Kingdom +414 Ireland +415 Northern Ireland +420 Belgium +421 France +422 Liechtenstein +423 Luxembourg +424 Monaco +425 Netherlands +426 Switzerland +430 Albania +431 Andorra +432 Gibraltar +433 Greece +434 Dodecanese Islands +435 Italy +436 Portugal +437 Azores +438 Spain +439 Vatican City +440 Malta +450 Austria +451 Bulgaria +452 Czechoslovakia +453 Germany +454 Hungary +455 Poland +456 Romania +457 Yugoslavia +460 Estonia +461 Latvia +462 Lithuania +465 USSR +496 Byelorussia +498 Ukraine +499 Europe, n.s. +500 China +501 Japan +502 Korea +510 Brunei +511 Cambodia +512 Indonesia +513 Laos +514 Malaysia +515 Philippines +516 Singapore +517 Thailand +518 Vietnam +520 Afghanistan +521 India +525 Pakistan +522 Iran +523 Maldives +524 Nepal +530 Bahrain +531 Cyprus +532 Iraq +534 Israel +535 Jordan +536 Kuwait +537 Lebanon +538 Oman +539 Qatar +540 Saudi Arabia +541 Syria +542 Turkey +543 United Arab Emirates +544 Yemen +548 Southwest Asia, nec/ns +599 Asia, nec/ns +600 Africa +610 Northern Africa +612 Egypt/United Arab Rep. +670 Central Africa +690 Southern Africa +694 South Africa (Union of) +699 Africa, nec +700 Coral Sea Islands +701 Australia +702 New Zealand +710 Pacific Islands +715 US Pacific Trust Territories +800 Heard and McDonald Islands +900 Abroad (unknown) or at sea +911 Abroad, ns +912 At sea +990 Same house +999 Missing/unknown + + MIGTYPE5 Metropolitan status 5 years ago +0 N/A +1 Not in metro area +2 In a metro area, central city status unknown +3 Central city +4 Not central city +9 Unknown + + SAMEPLAC Lived same incorporated place 5 years ago +0 N/A +1 No, different community +2 Yes, same community +9 Not ascertained + + SAMESEA5 Lived same SEA 5 years ago +0 N/A +1 Same SEA in reference year +2 Different SEA in reference year +3 In U.S. in reference year, location unknown +4 Abroad during the reference year +9 Not ascertained + + VETSTAT Veteran status [general version] +0 N/A +1 Not a veteran +2 Veteran +9 Unknown + + VETSTATD Veteran status [detailed version] +00 N/A +10 Not a veteran +11 No military service +12 Currently on active duty +13 Training for Reserves or National Guard only +20 Veteran +21 Veteran, on active duty prior to past year +22 Veteran, on active duty in past year +23 Veteran, on active duty in Reserves or National Guard only +99 Unknown + + VET1940 Veteran status, 1940 +0 N/A (not sample-line person) +1 Not veteran, spouse, or (under 18 years old) child of vetera +2 Veteran, spouse, or child of veteran +8 Not ascertained + + VETWWI Veteran, served during WWI era +0 N/A (all years) or No (1940, 1950, 1980) +1 No (1950, 1960, 1970) +2 Yes, served this period + + VETPER Veteran period of service, 1940 +0 N.A. +1 World War I +2 Spanish-Amer, Philippine Insurrection or Boxer Rebellion +3 Spanish-American and WW I +4 Regular establishment (peace-time service only) +5 Other war or expedition +7 Period of service not ascertained +8 Not ascertained + + VETCHILD Mortality status of child's veteran father +0 N/A +1 Child of dead veteran +2 Child of living veteran +8 Child not identified as having veteran father +9 Veteran status unknown + + SURSIM Surname similarity +00 N/A (sampled at the individual level) +01 1st surname in household +02 2 +03 3 +04 4 +05 5 +06 6 +07 7 +08 8 +09 9 +10 10 +11 11 +12 12 +13 13 +14 14 +15 15 +16 16 +17 17 +18 18 +19 19 +20 20 +21 21 +22 22 +23 23 +24 24 +25 25 +26 26 +27 27 +28 28 +29 29 +30 30 +99 Unknown + + SSENROLL Social Security enrollment +0 N/A +1 No Social Security number +2 Yes, has Soc. Sec. number \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/compile.sh b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/compile.sh new file mode 100644 index 0000000..cb72203 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/compile.sh @@ -0,0 +1,8 @@ +mkdir -p build +javac src/*.java -d build +cd build + +jar cfe ../NistDp3.jar Main *.class + +cd .. +echo "Jar file successfully created." diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/run.sh b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/run.sh new file mode 100644 index 0000000..41ee6ca --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/run.sh @@ -0,0 +1,3 @@ +#!/bin/sh +java -Xmx1G -jar NistDp3.jar $1 $2 $3 $4 + diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Field.java b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Field.java new file mode 100644 index 0000000..590aa7a --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Field.java @@ -0,0 +1,45 @@ + +import java.util.*; + +public class Field{ + String name, type; + int maxval; + ArrayList values; + HashMap valueToBin; + + void reset(){ + values = new ArrayList<>(); + valueToBin = new HashMap<>(); + } + + void addValue(int value){ + if (values == null) reset(); + values.add(value); + valueToBin.put(value, values.size() - 1); + } + + // get histogram bin for value + int getBin(int value){ + if (valueToBin == null) return value; + Integer bin = valueToBin.get(value); + // if value not in codebook use first bin + return bin == null ? 0 : bin; + } + + // get value corresponding to bin value + int getValue(int bin){ + return values == null ? bin : (int)values.get(bin); + } + + // set value ranges for undefined fields + // params are value ranges: first0, last0, step0, first1, last1, step1, .... + void setBins(int ... ranges){ + reset(); + + for (int i = 0; i < ranges.length; i+= 3){ + int first = ranges[i], last = ranges[i+1], step = ranges[i+2]; + for (int j = first; j <= last; j+= step) + addValue(j); + } + } +} diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Main.java b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Main.java new file mode 100644 index 0000000..12d2e1d --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Main.java @@ -0,0 +1,126 @@ + +import java.io.*; +import java.util.*; + +public class Main{ + static final String codebookPath = "codebook.cbk"; + + // Laplace distribution: f(x) = 1/(2*scale)*exp(-|x|/scale) + public static double laplace(SplittableRandom rnd, double scale){ + return scale*Math.log(rnd.nextDouble())*(rnd.nextInt(2) == 0 ? -1 : 1); + } + + // read data specs from the .json file to a map of fieldname -> Field object + public static HashMap + readSpecInfo(String specs, String codebook){ + HashMap info = new HashMap<>(); + + try (BufferedReader br = new BufferedReader(new FileReader(specs))){ + + // get map of fields from the .json specs file + Field currentField = null; + for (String line; (line = br.readLine()) != null; ){ + String[] s = line.split(":"); + if (s.length != 2) continue; + for (int i = 0; i < s.length; i++){ + s[i] = s[i].trim(); + if (s[i].charAt(s[i].length()-1) == ',') + s[i] = s[i].substring(0, s[i].length()-1); + } + if (s[1].equals("{")){ + String label = s[0].substring(1, s[0].length()-1); + currentField = new Field(); + currentField.name = label; + info.put(label, currentField); + }else if (currentField != null){ + String name = s[0].substring(1, s[0].length()-1); + switch(name){ + case "type": + currentField.type = s[1].substring(1, s[1].length()-1); + break; + case "maxval": + currentField.maxval = Integer.parseInt(s[1]); + break; + default: + System.err.println("INVALID FIELD!"); + break; + } + } + } + }catch (Exception e){ System.err.println(e); } + + // get field bins from the codebook + String currentLabel = null; + boolean labelUsed = false; + try (BufferedReader br = new BufferedReader(new FileReader(codebook))){ + for (String line; (line = br.readLine()) != null; ){ + String[] s = line.split("\t"); + if (s[0].length() >= 2 && s[0].charAt(0) == ' ' && + s[0].charAt(1) != ' '){ + currentLabel = s[0].trim(); + labelUsed = info.containsKey(currentLabel); + }else if (labelUsed && s[0].length() > 0 && + Character.isDigit(s[0].charAt(0))){ + info.get(currentLabel).addValue(Integer.parseInt(s[0])); + } + } + }catch (Exception e){ System.err.println(e); } + + // make sure maxval is a bin + for (String label : info.keySet()){ + Field field = info.get(label); + if (field.valueToBin != null && + !field.valueToBin.containsKey(field.maxval)) + field.addValue(field.maxval); + } + + return info; + } + + static void runCommandLine(String[] args){ + // create synthetic datasets for epsilon = 8.0, 1.0 and 0.3 + if (args.length == 2){ + String inputFile = args[0], specsFile = args[1]; + HashMap specInfo = readSpecInfo(specsFile, codebookPath); + + Solution solution = new Solution(); + + solution.run(inputFile, "8_0.csv", specInfo, 8.0); + System.out.println("Synthetic data saved to 8_0.csv\n"); + + solution.run(inputFile, "1_0.csv", specInfo, 1.0); + System.out.println("Synthetic data saved to 1_0.csv\n"); + + solution.run(inputFile, "0_3.csv", specInfo, 0.3); + System.out.println("Synthetic data saved to 0_3.csv\n"); + + // save synthetic dataset to args[2] using epsilon = args[3] + }else if (args.length == 4){ + String inputFile = args[0], + specsFile = args[1], + outputFile = args[2]; + double epsilon = Double.parseDouble(args[3]); + Solution solution = new Solution(); + solution.run(inputFile, outputFile, + readSpecInfo(specsFile, codebookPath), epsilon); + System.out.println("Synthetic data saved to " + outputFile); + System.out.println(); + + // display instructions if an invalid number of arguments are used + }else{ + System.out.println("NistDp3 - synthetic data creation"); + System.out.println("syntax:"); + System.out.println(" java -jar NistDp3.jar " + + " [ ]"); + System.out.println(); + System.out.println(" If and are omitted, " + + "outputs using epsilon = 8.0, 1.0 and 0.3"); + System.out.println(" will be saved to 8_0.csv, 1_0.csv and 0_3.csv"); + } + } + + public static void main(String[] args){ + runCommandLine(args); + } + +} diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Solution.java b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Solution.java new file mode 100644 index 0000000..586bd54 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPFieldGroups/solution/src/Solution.java @@ -0,0 +1,323 @@ + +import java.io.*; +import java.util.*; + +public class Solution{ + static final int OUTPUT_ROWS = 800_000, + MAX_FILE_SIZE = 349_000_000; // make sure < 350 MB + + // set to true to allow accessing ground truth data in order to determine + // distinct values for state-dependent columns + static final boolean findDistinctStateValues = true; + + SplittableRandom rnd; + + int nColumns, nGroups; + Field[] fields = null; + int[] nBins = null; + + HashMap fieldNameToColumn = new HashMap<>(); + ArrayList fieldGroups; + HashSet usedFields; + + void run(String inDataFilename, String outDataFilename, + HashMap specs, + double epsilon){ + + // pre-processing ////////////////////////////////////////////////////////// + + System.out.println(String.format("Using epsilon = %3.2f", epsilon)); + + // use a different random sequence every run + rnd = new SplittableRandom(); + + String dataHeaderLine; + String[] dataHeader; + fieldGroups = new ArrayList<>(); + usedFields = new HashSet<>(); + + // read data file + ArrayList data = new ArrayList<>(); + try (BufferedReader br = new BufferedReader( + new FileReader(inDataFilename))){ + dataHeaderLine = br.readLine(); + dataHeader = dataHeaderLine.split(",", -1); + for (int col = 0; col < dataHeader.length; col++) + fieldNameToColumn.put(dataHeader[col], col); + + // define bins and column arrays + nColumns = dataHeader.length; + nBins = new int[nColumns]; + fields = new Field[nColumns]; + + for (String line; (line = br.readLine()) != null; ) data.add(line); + }catch (Exception e){ + System.err.println(e); + System.err.println("Error reading data."); + return; + } + + // determine distinct values for state-dependent columns + if (findDistinctStateValues){ + String[] names = { "SEA", "METAREA", "COUNTY", "CITY", "METAREAD" }; + int n = names.length; + + // find columns fields are in + int[] columns = { -1, -1, -1, -1, -1 }; + for (int col = 0; col < nColumns; col++) + for (int i = 0; i < n; i++) + if (dataHeader[col].equals(names[i])){ + columns[i] = col; + specs.get(names[i]).reset(); + } + + // assign distinct values to fields + for (String line : data){ + String[] row = line.split(",", -1); + for (int i = 0; i < names.length; i++) + if (columns[i] >= 0){ + int value = Integer.parseInt(row[columns[i]]); + if (!specs.get(names[i]).valueToBin.containsKey(value)) + specs.get(names[i]).addValue(value); + } + } + } + + for (int col = 0; col < nColumns; col++){ + Field field = specs.get(dataHeader[col]); + + // define some bins for fields not in codebook + switch(field.name){ + case "SUPDIST": + field.setBins(10, 630, 10); + break; + case "MIGSEA5": + field.setBins(1, 502, 1, 990, 991, 1, 997, 997, 1); + break; + case "INCWAGE": + field.setBins(0, 5000, 1, 999998, 999998, 1); + break; + case "VALUEH": + field.setBins(0, 30000, 1, 9999998, 9999999, 1); + break; + case "EDSCOR50": + field.setBins(0, 1000, 1, 9999, 9999, 1); + break; + case "ERSCOR50": + field.setBins(0, 1000, 1, 9999, 9999, 1); + break; + case "MIGMET5": + field.setBins(0, 9600, 20, 9999, 9999, 1); + break; + case "MIGCOUNTY": + field.setBins(0, 8000, 10, 9997, 9999, 2); + break; + case "NPBOSS50": + field.setBins(0, 1000, 1, 9999, 9999, 1); + break; + case "IND": + field.setBins(0, 231, 1, 995, 995, 1, 998, 999, 1); + break; + default: + break; + } + + // determine number of bins and field type for column + if (field.type.equals("enum")){ + if (field.valueToBin == null) + // if value bins not defined set to all values from 0 to maxval + nBins[col] = field.maxval + 1; + else + nBins[col] = field.valueToBin.size(); + + fields[col] = field; + }else{ + System.out.println("UNEXPECTED TYPE!"); + } + } + + // combine correlated fields into groups + addGroup("SPLIT", "OWNERSHP", "GQ", "GQTYPE", "OWNERSHPD", + "GQFUNDS", "GQTYPED"); + addGroup("VETWWI", "SLREC", "SSENROLL", "NATIVITY", "VETPER", + "VET1940", "UCLASSWK", "VETCHILD", "VETSTAT", "VETSTATD"); + addGroup("RESPONDT", "CITIZEN", "MARST", "NCHLT5", "FAMSIZE"); + addGroup("URBAN", "FARM", "WARD", "CITYPOP"); + addGroup("LABFORCE", "SCHOOL", "SEX", "EMPSTAT", "CLASSWKR", + "INCNONWG", "EMPSTATD", "CLASSWKRD"); + addGroup("SPANNAME", "HISPAN", "HISPRULE", "HISPAND"); + addGroup("METRO", "METAREA", "METAREAD"); + addGroup("RACE", "RACED"); + addGroup("WKSWORK2", "WKSWORK1"); + addGroup("HRSWORK2", "HRSWORK1"); + addGroup("SAMESEA5", "SAMEPLAC", "MIGTYPE5", "MIGRATE5", "MIGRATE5D"); + addGroup("MARRNO", "CHBORN"); + addGroup("SIZEPL", "URBPOP"); + addGroup("SEA", "CITY"); + addGroup("SUPDIST", "COUNTY"); + addGroup("OCCSCORE", "EDSCOR50"); + addGroup("MTONGUE", "MTONGUED"); + addGroup("SEI", "PRESGL"); + addGroup("HIGRADE", "EDUC", "HIGRADED", "EDUCD"); + addGroup("MBPL", "MBPLD"); + addGroup("FBPL", "FBPLD"); + addGroup("BPL", "BPLD"); + addGroup("IND1950", "IND"); + addGroup("MIGSEA5", "MIGPLAC5"); + addGroup("OCC", "OCC1950"); + addGroup("UOCC95", "UIND", "UOCC"); + + // add any remaining fields into single sized groups + for (int i = 0; i < nColumns; i++) + if (!usedFields.contains(i)) addGroup(i); + + nGroups = fieldGroups.size(); + System.out.println("Total field groups: " + nGroups); + + // fill bins + for (String line : data){ + String[] row = line.split(",", -1); + for (FieldGroup group : fieldGroups) group.add(row); + } + + // privatization /////////////////////////////////////////////////////////// + + // add noise + // divide total epsilon equally for each FieldGroup's histogram of counts + // --> epsilon per group = (total epsilon)/(number of groups) + // --> scale = 1/(epsilon per group) = (number of groups)/(total epsilon) + double scale = nGroups/epsilon; + + // add Laplacian noise with scale = nGroups/epsilon to every counts bin + for (FieldGroup group : fieldGroups) group.privatize(scale); + + // post-processing ///////////////////////////////////////////////////////// + + // create data + int fileSize = 0; + try (PrintWriter pw = new PrintWriter(outDataFilename)){ + fileSize+= dataHeaderLine.length() + 1; + + // write header + pw.write(dataHeaderLine + "\n"); + + for (int row = 0; row < OUTPUT_ROWS; row++){ + + // get noisy count weighted random values for each column + int[] colValues = new int[nColumns]; + for (FieldGroup group : fieldGroups){ + int[] values = group.getRandomValues(); + for (int i = 0; i < values.length; i++) + colValues[group.columns[i]] = values[i]; + } + + // add values for the row to a string + String s = ""; + for (int col = 0; col < nColumns; col++) + s+= (col > 0 ? "," : "") + colValues[col]; + + s+= "\n"; + + // stop if output file would be too large + fileSize+= s.length(); + if (fileSize > MAX_FILE_SIZE) break; + + // write row + pw.write(s); + } + }catch (Exception e){ System.err.println(e); } + } + + // add another FieldGroup using names to choose fields + void addGroup(String...names){ + // if field name is present in data header, add to group + ArrayList colList = new ArrayList<>(); + for (String name : names) + if(fieldNameToColumn.containsKey(name)) + colList.add(fieldNameToColumn.get(name)); + + // if no fields are present then do nothing + if (colList.isEmpty()) return; + + int[] columns = new int[colList.size()]; + for (int i = 0; i < columns.length; i++) + columns[i] = colList.get(i); + + addGroup(columns); + } + + // add another FieldGroup using columns to choose fields + void addGroup(int...columns){ + fieldGroups.add(new FieldGroup(columns)); + for (int i : columns){ + if (usedFields.contains(i)) + System.err.println("Field column " + i + " already used!"); + else + usedFields.add(i); + } + } + + // Field Group: fields grouped together share a single counts histogram + public class FieldGroup{ + int[] columns; + long[] counts; + + FieldGroup(int...columns){ + this.columns = columns; + int totalBins = 1; + for (int col : columns) totalBins*= nBins[col]; + counts = new long[totalBins]; + } + + // add a count to the correct bin + void add(String[] row){ + int bin = 0, totalBins = 1; + for (int col : columns){ + bin+= totalBins * fields[col].getBin(Integer.parseInt(row[col])); + totalBins*= nBins[col]; + } + counts[bin]++; + } + + // add Laplacian noise to all counts bins + // add previous bins counts to greatly improve speed of getRandomValues() + // by enabling use of a binary search + void privatize(double scale){ + double threshold = 1.9*scale*Math.log10(counts.length); + for (int bin = 0; bin < counts.length; bin++){ + double noise = Main.laplace(rnd, scale), + dpbin = counts[bin] + noise; + counts[bin] = dpbin < threshold ? 0 : (int)Math.round(dpbin); + if (bin > 0) counts[bin]+= counts[bin-1]; + } + } + + // get random values for each column in the group + // values in returned array correspond to columns in columns[] array + int[] getRandomValues(){ + int[] values = new int[columns.length]; + if (counts[counts.length-1] > 0){ + + // use binary search to get noisy counts weighted random bin + long r = rnd.nextLong(counts[counts.length-1]); + int bin = Arrays.binarySearch(counts, r); + if (bin < 0){ + bin = -bin-1; + }else{ + bin++; + while (bin < counts.length && counts[bin]==counts[bin-1]) bin++; + } + + // get values for each column corresponding to the random bin + int totalBins = 1; + for (int iCol = 0; iCol < columns.length; iCol++){ + int col = columns[iCol]; + values[iCol] = fields[col].getValue((bin/totalBins) % nBins[col]); + totalBins*= nBins[col]; + } + } + return values; + } + } + +} diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/LICENSE b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/LICENSE new file mode 100644 index 0000000..1ccb1dc --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Zhangzhk0819 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/README.md b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/README.md new file mode 100644 index 0000000..4912e05 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/README.md @@ -0,0 +1,19 @@ +# DPSyn + +## Team Members & Affiliation(s): +Ninghui Li (Purdue University) +Zhikun Zhang (Zhejiang University) +Tianhao Wang (Purdue University) + +## GitHub User(s) Serving as POC: +@Zhangzhk0819 + +## How to Cite: +- Author: Ninghui Li, Zhikun Zhang, Tianhao Wang +- Date: May 30, 2019 +- Title: DPSyn +- Type: source code +- Web address: https://github.com/usnistgov/PrivacyEngCollabSpace/tree/master/tools/de-identification/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn + +## Brief Description: +We present DPSyn, an algorithm for synthesizing microdata while satisfying differential privacy, and its instantiation to the dataset used in the competition, namely Public Use Microdata Sample (PUMS) of the 1940 USA Census Data. diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/config.py b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/config.py new file mode 100644 index 0000000..1bc5a56 --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/config.py @@ -0,0 +1,3 @@ +# path related constant +MAPPING_PATH = "temp_data/mapping/" + diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/document/README.md b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/document/README.md new file mode 100644 index 0000000..138c87e --- /dev/null +++ b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/document/README.md @@ -0,0 +1,52 @@ +# Overview +The program starts from main.py and calls "experiment/experiment_C5_1-C5_2.py" for different epsilon input. "experiment/experiment_C5_1-C5_2.py" contains the main procedures to generate the synthetic dataset, they will invoke functions in other modules to implement corresponding tasks. Specifically, "configuration_C5_1-C5_2()" functions is responsible for constructing the noisy marginals, which is the only part that touches the private dataset. The consist_views(), synthesize_records() and post_processing() functions are for post-processing step. + +# Run the code +Our code receives 4 positional arguments and 5 optional arguments as listed in the main.py file. Here is an example: + +``` +python main.py colorado.csv out.csv colorado-specs.json 1.0 +``` + +Note that the data file colorado.csv and specs file colorado-specs.json are not included in the repository, one can download them from the topcoder platform for the third challenge. + +# Functionality of each file +## General code +* main.py: the main file, program starts from here +* experiment/experiment_C5_1-C5_2.py: contains the main procedures to generate the synthetic dataset, they will invoke functions in other modules +* experiment/experiment_C5.py: contains the functions share by experiment_C5_1.py and experiment_C5_2.py +* config.py: define some global constants +* lib_composition/advanced_composition.py: determine privacy budget allocation using Renyi-DP +* lib_view/view.py: some basic methods to process marginals/views +* query.py, scoring.py, utility.py, evaluation/marginal.py, evaluation/range_query.py, evaluation/income_property.py: designed for local evaluation +* experiment/experiment_dpsyn.py: contains functions to construct marginals, and functions for local evaluation. + +## Pre-processing code +* load_csv.py: load data from dataset, and pre-process the data into numpy format. Most of the operations in our code are based on numpy operation +* experiment/experiment.py: call the functions in load_csv.py to load dataset +* lib_attributes/attributes_preprocess.py: preprocess the original dataset for the algorithms, such as extract distinct values for attributes CITY and COUNTY. + +## Privatization code +* experiment/experiment_C5_1-C5_2.py: "configuration_C5_1-C5_2()" functions in these modules are responsible for constructing the noisy marginals, which is the only part that touches the private dataset. The following module would be invoked if necessary. +* lib_attributes/attributes_recode.py: recode and compress attributes using certain amount of privacy budget. + +## Post-processing code +* lib_view/consistent.py: make the noisy marginals consistent +* lib_dpsyn/records_update.py: use the noisy marginals to update a randomly generated synthetic dataset +* lib_attributes/attributes_postprocess: all the functions are designed for post-processing the synthetic dataset +* generate_submission.py: transform the synthetic dataset from numpy format to the original dataset format + +# Running time +* When epsilon <= 0.2, the program takes about 40 minutes to generate the synthetic dataset for the Colorado dataset +* When epsilon > 0.2, the program takes about 50 minutes to generate the synthetic dataset for the Colorado dataset + + + + + + + + + + + diff --git a/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/document/algorithm description.pdf b/tools/disassociability/Differential-Privacy-Synthetic-Data-Challenge-Algorithms/DPSyn/document/algorithm description.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ddd8a6c88e35e29fad9d85d977926ce8462ddca7 GIT binary patch literal 234972 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 z z5WUOgBGLAVwM}yUCiDQjIIhpMT}xZJ*_bx`%2SHuh#n%E059a{i=~+VZvJSN{SKgJ z7EZ1V?t*W_;+PY6{KC@O*o&yau>5PKk3uDX#a13IF*r>Jko+)J5YWSpy9Z#gC5J;5{wx4dbG|q| zduX5faI~=q=3xltjA(>^&)1GuQQxw{7Z?1J=(}Ed-hmbccs zUcOA-n@hT_r!oV(*?^{t=7USw7%?N_PfcWrk*qVEYl#%4~63D_` zQ;cg;-w$w1(tB&mVfLO6d`ZyjS32w66CMcEWJ_xx>^zzDV-4d8(#$VqFb<6Q?x)kR zAug~%W8Lb19()?4Jf?9HV>?DTe|Ste?oir!D|$)+XL9t2Mc1|8=|Yfh8{ssxK%CeI zyI&u8J0_w08u!8DYk>6xJWaSh;N>iaGrU;a2p`&Jf|fkDV!V9W0rIoL0EE02J-IEr{Tsb z`zN#PHWaMY+;ji(s3AZ98M5l-C%s0uUV3(${oy5y&j*p&T9sG6_kYnkG3WdxzHEv8 zn`Kz-{EudV5LBMh<`zO-+)yfrxi+l@PR&>XaKI~~}RtA>Vgw=C!l9)hIzw2+OJ)M5qKbQ<3kk^qss37g8%i~Rn2 z>9B&Ju5a+18C+H{#=<;o$k{8{Qt6r4jPW59vnobGj?vkgMPy`{Yqf$nqO3ZVAxT>< z{wfrZ_a^x`C8X8Wc56mS&{&&}nk6yLMu#S!pdN6tj8X@~hRz`2MI!?$0S#u|5#C>m z!+`?n&EP1hWK|p0RlO+jKJr65_1byNvS-S#D!;Qe#XwgpH^j_S zM&{EY!|)asj$@Quv5&DJ3r}pK{VD+Kiv3hAI#<=B5(Sm5R3efnQHw5^a;AYFEIe42 zI5~X&|Grt`c`=MrFe7;s=n&K!Tl}H-(hA?fCbY2a!6dL7=*#C~<7f<8<)DaP@-w~d zAA@hnT3KY-Q|DDn4OS=@S1T!zy{I*6!W4Giwm^8NfunMSu&pyDVjh8l*Al^8bb=0^ z7?y+PJQOc38sUWobnocz{8erVc~A_?jMu0_-S^?(Li9eMTrUoOG;-{ORxY6HbMG~u zW<ⅅ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