From b7a7264257cfc40386a9749cae5a8a49db5d2fff Mon Sep 17 00:00:00 2001 From: rudy Date: Thu, 3 Mar 2022 16:20:59 +0100 Subject: [PATCH] feat: atomic_pattern optimizer, with v0_parameters table generation Resolve #11 --- .github/workflows/rust.yml | 4 +- Cargo.toml | 10 +- examples/v0_parameters.ref-02-03-2022 | 240 +++++++ examples/v0_parameters.rs | 154 +++++ src/lib.rs | 2 + src/noise_estimator/error.rs | 40 ++ src/noise_estimator/mod.rs | 2 +- .../operators/atomic_pattern.rs | 26 +- src/optimisation/atomic_pattern.rs | 586 ++++++++++++++++++ src/optimisation/mod.rs | 1 + .../security.rs => security/glwe.rs} | 42 +- src/security/mod.rs | 1 + 12 files changed, 1055 insertions(+), 53 deletions(-) create mode 100644 examples/v0_parameters.ref-02-03-2022 create mode 100644 examples/v0_parameters.rs create mode 100644 src/noise_estimator/error.rs create mode 100644 src/optimisation/atomic_pattern.rs create mode 100644 src/optimisation/mod.rs rename src/{noise_estimator/security.rs => security/glwe.rs} (62%) create mode 100644 src/security/mod.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 26d1e792b..c4b5259b1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -44,7 +44,7 @@ jobs: RUSTFLAGS: -D warnings with: command: build - args: ${{ env.CARGO_ARGS }} + args: ${{ env.CARGO_ARGS }} --lib --examples --tests - name: cargo clippy uses: actions-rs/cargo@v1 @@ -56,4 +56,4 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: ${{ env.CARGO_ARGS }} --no-fail-fast + args: ${{ env.CARGO_ARGS }} --no-fail-fast --all-targets diff --git a/Cargo.toml b/Cargo.toml index d5208f2c2..414d485b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,15 @@ git-fetch-with-cli = true [dependencies] concrete-commons = { git = "ssh://git@github.com/zama-ai/concrete_internal.git", branch = "fix/optimizer_compat" } concrete-npe = { git = "ssh://git@github.com/zama-ai/concrete_internal.git", branch = "fix/optimizer_compat" } +statrs = "0.15.0" [dev-dependencies] approx = "0.5.1" -pretty_assertions = "1" +clap = { version = "3.1.2", features = ["derive"] } +rayon-cond = "0.2" # to avoid rayon code coloring +# pprof = { version = "0.4", features = ["flamegraph"] } +rayon = "1.5.1" +text-diff = "0.4.0" + +[[example]] +name = "v0_parameters" diff --git a/examples/v0_parameters.ref-02-03-2022 b/examples/v0_parameters.ref-02-03-2022 new file mode 100644 index 000000000..86547b72c --- /dev/null +++ b/examples/v0_parameters.ref-02-03-2022 @@ -0,0 +1,240 @@ +{ /* 6.3e-5 errors */ +{ /* precision 1 */ + /* 0 */ V0Parameter( 1, 10, 538, 2, 8, 3, 3), // 46 mops, 5.9e-5 errors + /* 1 */ V0Parameter( 1, 10, 514, 2, 8, 5, 2), // 46 mops, 5.4e-5 errors + /* 2 */ V0Parameter( 1, 10, 519, 2, 8, 5, 2), // 46 mops, 6.0e-5 errors + /* 3 */ V0Parameter( 1, 10, 515, 3, 6, 5, 2), // 59 mops, 4.9e-5 errors + /* 4 */ V0Parameter( 1, 10, 523, 3, 6, 5, 2), // 60 mops, 5.4e-5 errors + /* 5 */ V0Parameter( 1, 11, 563, 1, 23, 3, 3), // 71 mops, 5.4e-5 errors + /* 6 */ V0Parameter( 1, 11, 563, 1, 23, 3, 3), // 71 mops, 5.5e-5 errors + /* 7 */ V0Parameter( 1, 11, 563, 1, 23, 3, 3), // 71 mops, 5.8e-5 errors + /* 8 */ V0Parameter( 1, 11, 564, 1, 23, 3, 3), // 71 mops, 5.8e-5 errors + /* 9 */ V0Parameter( 1, 11, 569, 1, 23, 3, 3), // 72 mops, 5.5e-5 errors + /* 10 */ V0Parameter( 1, 11, 555, 1, 23, 5, 2), // 75 mops, 5.7e-5 errors + /* 11 */ V0Parameter( 1, 11, 532, 2, 16, 5, 2), // 101 mops, 6.2e-5 errors + /* 12 */ V0Parameter( 1, 11, 532, 2, 16, 5, 2), // 101 mops, 6.2e-5 errors + /* 13 */ V0Parameter( 1, 11, 532, 2, 16, 5, 2), // 101 mops, 6.3e-5 errors + /* 14 */ V0Parameter( 1, 11, 533, 2, 16, 5, 2), // 101 mops, 4.9e-5 errors + /* 15 */ V0Parameter( 1, 11, 533, 2, 16, 5, 2), // 101 mops, 5.5e-5 errors + /* 16 */ V0Parameter( 1, 11, 535, 2, 16, 5, 2), // 102 mops, 5.3e-5 errors + /* 17 */ V0Parameter( 1, 11, 544, 2, 16, 5, 2), // 103 mops, 5.6e-5 errors + /* 18 */ V0Parameter( 1, 11, 533, 3, 12, 5, 2), // 130 mops, 5.0e-5 errors + /* 19 */ V0Parameter( 1, 11, 533, 3, 12, 5, 2), // 130 mops, 6.0e-5 errors + /* 20 */ V0Parameter( 1, 11, 536, 3, 12, 5, 2), // 131 mops, 5.6e-5 errors + /* 21 */ V0Parameter( 1, 11, 552, 3, 12, 5, 2), // 135 mops, 5.7e-5 errors + /* 22 */ V0Parameter( 1, 11, 536, 4, 9, 5, 2), // 160 mops, 6.1e-5 errors + /* 23 */ V0Parameter( 1, 11, 555, 4, 9, 5, 2), // 166 mops, 5.7e-5 errors + /* 24 */ V0Parameter( 1, 11, 539, 5, 8, 5, 2), // 191 mops, 5.2e-5 errors + /* 25 */ V0Parameter( 1, 11, 593, 5, 8, 5, 2), // 209 mops, 6.2e-5 errors + /* 26 */ V0Parameter( 1, 11, 541, 7, 6, 5, 2), // 251 mops, 6.3e-5 errors + /* 27 */ V0Parameter( 1, 11, 569, 8, 5, 5, 2), // 294 mops, 6.1e-5 errors + /* 28 */ V0Parameter( 1, 11, 549, 11, 4, 5, 2), // 374 mops, 5.5e-5 errors + /* 29 */ V0Parameter( 1, 11, 559, 15, 3, 5, 2), // 503 mops, 5.8e-5 errors + /* 30 */ V0Parameter( 1, 11, 542, 44, 1, 10, 1), // 1358 mops, 6.0e-5 errors + /* 31 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + }, +{ /* precision 2 */ + /* 0 */ V0Parameter( 1, 10, 557, 2, 8, 5, 2), // 49 mops, 5.2e-5 errors + /* 1 */ V0Parameter( 1, 10, 564, 2, 8, 5, 2), // 50 mops, 5.7e-5 errors + /* 2 */ V0Parameter( 1, 10, 558, 3, 6, 5, 2), // 63 mops, 5.1e-5 errors + /* 3 */ V0Parameter( 1, 10, 569, 3, 6, 5, 2), // 65 mops, 5.7e-5 errors + /* 4 */ V0Parameter( 1, 11, 579, 1, 23, 5, 2), // 78 mops, 6.0e-5 errors + /* 5 */ V0Parameter( 1, 11, 579, 1, 23, 5, 2), // 78 mops, 6.1e-5 errors + /* 6 */ V0Parameter( 1, 11, 580, 1, 23, 5, 2), // 78 mops, 5.3e-5 errors + /* 7 */ V0Parameter( 1, 11, 581, 1, 23, 5, 2), // 78 mops, 5.4e-5 errors + /* 8 */ V0Parameter( 1, 11, 585, 1, 23, 5, 2), // 78 mops, 6.3e-5 errors + /* 9 */ V0Parameter( 1, 11, 639, 1, 23, 5, 2), // 85 mops, 6.3e-5 errors + /* 10 */ V0Parameter( 1, 11, 579, 2, 16, 5, 2), // 109 mops, 6.0e-5 errors + /* 11 */ V0Parameter( 1, 11, 579, 2, 16, 5, 2), // 109 mops, 6.0e-5 errors + /* 12 */ V0Parameter( 1, 11, 579, 2, 16, 5, 2), // 109 mops, 6.1e-5 errors + /* 13 */ V0Parameter( 1, 11, 579, 2, 16, 5, 2), // 109 mops, 6.3e-5 errors + /* 14 */ V0Parameter( 1, 11, 580, 2, 16, 5, 2), // 109 mops, 5.9e-5 errors + /* 15 */ V0Parameter( 1, 11, 583, 2, 16, 5, 2), // 110 mops, 5.7e-5 errors + /* 16 */ V0Parameter( 1, 11, 599, 2, 16, 5, 2), // 113 mops, 6.3e-5 errors + /* 17 */ V0Parameter( 1, 11, 580, 3, 12, 5, 2), // 141 mops, 5.2e-5 errors + /* 18 */ V0Parameter( 1, 11, 581, 3, 12, 5, 2), // 141 mops, 5.3e-5 errors + /* 19 */ V0Parameter( 1, 11, 585, 3, 12, 5, 2), // 142 mops, 5.7e-5 errors + /* 20 */ V0Parameter( 1, 11, 622, 3, 12, 5, 2), // 151 mops, 6.3e-5 errors + /* 21 */ V0Parameter( 1, 11, 585, 4, 9, 5, 2), // 174 mops, 6.3e-5 errors + /* 22 */ V0Parameter( 1, 11, 639, 4, 9, 5, 2), // 190 mops, 6.3e-5 errors + /* 23 */ V0Parameter( 1, 11, 589, 5, 8, 5, 2), // 208 mops, 6.1e-5 errors + /* 24 */ V0Parameter( 1, 11, 591, 6, 7, 5, 2), // 241 mops, 6.3e-5 errors + /* 25 */ V0Parameter( 1, 11, 595, 7, 6, 5, 2), // 275 mops, 5.7e-5 errors + /* 26 */ V0Parameter( 1, 11, 627, 8, 5, 11, 2), // 338 mops, 6.3e-5 errors + /* 27 */ V0Parameter( 1, 11, 611, 11, 4, 5, 2), // 416 mops, 6.2e-5 errors + /* 28 */ V0Parameter( 1, 11, 617, 15, 3, 11, 2), // 569 mops, 5.8e-5 errors + /* 29 */ V0Parameter( 1, 11, 600, 44, 1, 22, 1), // 1531 mops, 5.8e-5 errors + /* 30 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 31 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + }, +{ /* precision 3 */ + /* 0 */ V0Parameter( 1, 10, 673, 2, 8, 5, 4), // 59 mops, 5.8e-5 errors + /* 1 */ V0Parameter( 1, 10, 641, 3, 6, 5, 2), // 72 mops, 6.3e-5 errors + /* 2 */ V0Parameter( 1, 10, 649, 3, 6, 7, 3), // 76 mops, 6.2e-5 errors + /* 3 */ V0Parameter( 1, 11, 679, 1, 23, 5, 4), // 90 mops, 6.2e-5 errors + /* 4 */ V0Parameter( 1, 11, 679, 1, 23, 5, 4), // 90 mops, 6.3e-5 errors + /* 5 */ V0Parameter( 1, 11, 680, 1, 23, 5, 4), // 90 mops, 5.0e-5 errors + /* 6 */ V0Parameter( 1, 11, 681, 1, 23, 5, 4), // 90 mops, 4.8e-5 errors + /* 7 */ V0Parameter( 1, 11, 684, 1, 23, 5, 4), // 90 mops, 5.9e-5 errors + /* 8 */ V0Parameter( 1, 11, 710, 1, 23, 5, 4), // 94 mops, 6.0e-5 errors + /* 9 */ V0Parameter( 1, 11, 679, 2, 16, 5, 4), // 127 mops, 6.2e-5 errors + /* 10 */ V0Parameter( 1, 11, 679, 2, 16, 5, 4), // 127 mops, 6.2e-5 errors + /* 11 */ V0Parameter( 1, 11, 679, 2, 16, 5, 4), // 127 mops, 6.3e-5 errors + /* 12 */ V0Parameter( 1, 11, 680, 2, 16, 5, 4), // 127 mops, 4.8e-5 errors + /* 13 */ V0Parameter( 1, 11, 680, 2, 16, 5, 4), // 127 mops, 5.7e-5 errors + /* 14 */ V0Parameter( 1, 11, 682, 2, 16, 5, 4), // 127 mops, 6.0e-5 errors + /* 15 */ V0Parameter( 1, 11, 694, 2, 16, 5, 4), // 130 mops, 5.7e-5 errors + /* 16 */ V0Parameter( 1, 11, 652, 3, 12, 7, 3), // 163 mops, 4.8e-5 errors + /* 17 */ V0Parameter( 1, 11, 652, 3, 12, 7, 3), // 163 mops, 6.0e-5 errors + /* 18 */ V0Parameter( 1, 11, 655, 3, 12, 7, 3), // 164 mops, 6.3e-5 errors + /* 19 */ V0Parameter( 1, 11, 675, 3, 12, 7, 3), // 168 mops, 6.2e-5 errors + /* 20 */ V0Parameter( 1, 11, 656, 4, 9, 7, 3), // 200 mops, 5.4e-5 errors + /* 21 */ V0Parameter( 1, 11, 679, 4, 9, 7, 3), // 206 mops, 6.3e-5 errors + /* 22 */ V0Parameter( 1, 11, 659, 5, 8, 7, 3), // 237 mops, 5.1e-5 errors + /* 23 */ V0Parameter( 1, 11, 660, 6, 7, 7, 3), // 273 mops, 5.7e-5 errors + /* 24 */ V0Parameter( 1, 11, 638, 7, 6, 11, 2), // 309 mops, 6.2e-5 errors + /* 25 */ V0Parameter( 1, 11, 675, 8, 5, 11, 2), // 364 mops, 6.3e-5 errors + /* 26 */ V0Parameter( 1, 11, 647, 11, 4, 11, 2), // 455 mops, 5.7e-5 errors + /* 27 */ V0Parameter( 1, 11, 660, 15, 3, 11, 2), // 608 mops, 5.9e-5 errors + /* 28 */ V0Parameter( 1, 11, 641, 44, 1, 22, 1), // 1635 mops, 6.3e-5 errors + /* 29 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 30 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 31 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + }, +{ /* precision 4 */ + /* 0 */ V0Parameter( 1, 10, 691, 3, 6, 7, 3), // 80 mops, 6.2e-5 errors + /* 1 */ V0Parameter( 1, 10, 731, 3, 6, 7, 3), // 85 mops, 6.3e-5 errors + /* 2 */ V0Parameter( 1, 11, 720, 1, 23, 5, 4), // 95 mops, 5.5e-5 errors + /* 3 */ V0Parameter( 1, 11, 720, 1, 23, 5, 4), // 95 mops, 5.6e-5 errors + /* 4 */ V0Parameter( 1, 11, 720, 1, 23, 5, 4), // 95 mops, 6.0e-5 errors + /* 5 */ V0Parameter( 1, 11, 721, 1, 23, 5, 4), // 95 mops, 6.0e-5 errors + /* 6 */ V0Parameter( 1, 11, 726, 1, 23, 5, 4), // 95 mops, 5.4e-5 errors + /* 7 */ V0Parameter( 1, 11, 764, 1, 23, 5, 4), // 100 mops, 6.3e-5 errors + /* 8 */ V0Parameter( 1, 11, 720, 2, 16, 5, 4), // 134 mops, 5.5e-5 errors + /* 9 */ V0Parameter( 1, 11, 720, 2, 16, 5, 4), // 134 mops, 5.5e-5 errors + /* 10 */ V0Parameter( 1, 11, 720, 2, 16, 5, 4), // 134 mops, 5.6e-5 errors + /* 11 */ V0Parameter( 1, 11, 720, 2, 16, 5, 4), // 134 mops, 5.8e-5 errors + /* 12 */ V0Parameter( 1, 11, 721, 2, 16, 5, 4), // 134 mops, 5.3e-5 errors + /* 13 */ V0Parameter( 1, 11, 723, 2, 16, 5, 4), // 135 mops, 6.2e-5 errors + /* 14 */ V0Parameter( 1, 11, 738, 2, 16, 5, 4), // 137 mops, 5.9e-5 errors + /* 15 */ V0Parameter( 1, 11, 692, 3, 12, 7, 3), // 172 mops, 5.6e-5 errors + /* 16 */ V0Parameter( 1, 11, 693, 3, 12, 7, 3), // 173 mops, 5.4e-5 errors + /* 17 */ V0Parameter( 1, 11, 697, 3, 12, 7, 3), // 174 mops, 5.3e-5 errors + /* 18 */ V0Parameter( 1, 11, 724, 3, 12, 7, 3), // 180 mops, 6.1e-5 errors + /* 19 */ V0Parameter( 1, 11, 697, 4, 9, 7, 3), // 212 mops, 6.0e-5 errors + /* 20 */ V0Parameter( 1, 11, 731, 4, 9, 7, 3), // 222 mops, 6.2e-5 errors + /* 21 */ V0Parameter( 1, 11, 700, 5, 8, 7, 3), // 251 mops, 6.3e-5 errors + /* 22 */ V0Parameter( 1, 11, 702, 6, 7, 7, 3), // 290 mops, 6.1e-5 errors + /* 23 */ V0Parameter( 1, 11, 681, 7, 6, 11, 2), // 329 mops, 5.8e-5 errors + /* 24 */ V0Parameter( 1, 11, 686, 9, 5, 11, 2), // 407 mops, 5.6e-5 errors + /* 25 */ V0Parameter( 1, 11, 692, 11, 4, 11, 2), // 486 mops, 6.2e-5 errors + /* 26 */ V0Parameter( 1, 11, 715, 15, 3, 11, 2), // 658 mops, 6.3e-5 errors + /* 27 */ V0Parameter( 1, 11, 690, 44, 1, 22, 1), // 1760 mops, 6.2e-5 errors + /* 28 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 29 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 30 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 31 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + }, +{ /* precision 5 */ + /* 0 */ V0Parameter( 1, 11, 774, 1, 23, 5, 4), // 101 mops, 5.6e-5 errors + /* 1 */ V0Parameter( 1, 11, 774, 1, 23, 5, 4), // 101 mops, 5.7e-5 errors + /* 2 */ V0Parameter( 1, 11, 774, 1, 23, 5, 4), // 101 mops, 5.8e-5 errors + /* 3 */ V0Parameter( 1, 11, 774, 1, 23, 5, 4), // 101 mops, 6.2e-5 errors + /* 4 */ V0Parameter( 1, 11, 776, 1, 23, 5, 4), // 101 mops, 6.2e-5 errors + /* 5 */ V0Parameter( 1, 11, 787, 1, 23, 5, 4), // 103 mops, 6.0e-5 errors + /* 6 */ V0Parameter( 1, 11, 774, 2, 16, 5, 4), // 144 mops, 5.6e-5 errors + /* 7 */ V0Parameter( 1, 11, 774, 2, 16, 5, 4), // 144 mops, 5.6e-5 errors + /* 8 */ V0Parameter( 1, 11, 774, 2, 16, 5, 4), // 144 mops, 5.7e-5 errors + /* 9 */ V0Parameter( 1, 11, 774, 2, 16, 5, 4), // 144 mops, 5.7e-5 errors + /* 10 */ V0Parameter( 1, 11, 774, 2, 16, 5, 4), // 144 mops, 6.0e-5 errors + /* 11 */ V0Parameter( 1, 11, 775, 2, 16, 5, 4), // 144 mops, 6.2e-5 errors + /* 12 */ V0Parameter( 1, 11, 781, 2, 16, 5, 4), // 145 mops, 6.0e-5 errors + /* 13 */ V0Parameter( 1, 11, 745, 3, 12, 7, 3), // 185 mops, 5.6e-5 errors + /* 14 */ V0Parameter( 1, 11, 745, 3, 12, 7, 3), // 185 mops, 6.0e-5 errors + /* 15 */ V0Parameter( 1, 11, 747, 3, 12, 7, 3), // 186 mops, 5.8e-5 errors + /* 16 */ V0Parameter( 1, 11, 755, 3, 12, 7, 3), // 187 mops, 6.2e-5 errors + /* 17 */ V0Parameter( 1, 11, 747, 4, 9, 7, 3), // 226 mops, 6.0e-5 errors + /* 18 */ V0Parameter( 1, 11, 756, 4, 9, 7, 3), // 229 mops, 6.3e-5 errors + /* 19 */ V0Parameter( 1, 11, 748, 5, 8, 7, 3), // 268 mops, 6.2e-5 errors + /* 20 */ V0Parameter( 1, 11, 766, 5, 8, 7, 3), // 274 mops, 6.0e-5 errors + /* 21 */ V0Parameter( 1, 11, 773, 6, 7, 7, 3), // 319 mops, 6.0e-5 errors + /* 22 */ V0Parameter( 1, 11, 756, 7, 6, 11, 2), // 365 mops, 6.2e-5 errors + /* 23 */ V0Parameter( 1, 11, 729, 11, 4, 11, 2), // 512 mops, 6.0e-5 errors + /* 24 */ V0Parameter( 1, 11, 745, 14, 3, 11, 2), // 645 mops, 6.1e-5 errors + /* 25 */ V0Parameter( 1, 11, 746, 22, 2, 11, 2), // 972 mops, 6.2e-5 errors + /* 26 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 27 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 28 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 29 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 30 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 31 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + }, +{ /* precision 6 */ + /* 0 */ V0Parameter( 1, 12, 833, 1, 23, 5, 4), // 230 mops, 5.9e-5 errors + /* 1 */ V0Parameter( 1, 12, 833, 1, 23, 5, 4), // 230 mops, 6.2e-5 errors + /* 2 */ V0Parameter( 1, 12, 835, 1, 23, 5, 4), // 231 mops, 5.9e-5 errors + /* 3 */ V0Parameter( 1, 12, 843, 1, 23, 5, 4), // 233 mops, 5.9e-5 errors + /* 4 */ V0Parameter( 1, 12, 833, 2, 15, 5, 4), // 328 mops, 5.8e-5 errors + /* 5 */ V0Parameter( 1, 12, 833, 2, 15, 5, 4), // 328 mops, 5.8e-5 errors + /* 6 */ V0Parameter( 1, 12, 833, 2, 15, 5, 4), // 328 mops, 5.8e-5 errors + /* 7 */ V0Parameter( 1, 12, 833, 2, 15, 5, 4), // 328 mops, 5.8e-5 errors + /* 8 */ V0Parameter( 1, 12, 833, 2, 15, 5, 4), // 328 mops, 6.1e-5 errors + /* 9 */ V0Parameter( 1, 12, 834, 2, 15, 5, 4), // 328 mops, 6.2e-5 errors + /* 10 */ V0Parameter( 1, 12, 840, 2, 15, 5, 4), // 331 mops, 6.0e-5 errors + /* 11 */ V0Parameter( 1, 12, 843, 2, 15, 11, 2), // 371 mops, 6.3e-5 errors + /* 12 */ V0Parameter( 1, 12, 804, 3, 12, 7, 3), // 424 mops, 6.2e-5 errors + /* 13 */ V0Parameter( 1, 12, 807, 3, 12, 7, 3), // 425 mops, 5.6e-5 errors + /* 14 */ V0Parameter( 1, 12, 818, 3, 12, 7, 3), // 431 mops, 6.3e-5 errors + /* 15 */ V0Parameter( 1, 12, 806, 4, 9, 7, 3), // 519 mops, 5.6e-5 errors + /* 16 */ V0Parameter( 1, 12, 813, 4, 9, 7, 3), // 524 mops, 5.8e-5 errors + /* 17 */ V0Parameter( 1, 12, 809, 5, 8, 7, 3), // 616 mops, 5.8e-5 errors + /* 18 */ V0Parameter( 1, 12, 839, 5, 8, 7, 3), // 638 mops, 6.2e-5 errors + /* 19 */ V0Parameter( 1, 12, 828, 6, 7, 11, 2), // 753 mops, 6.3e-5 errors + /* 20 */ V0Parameter( 1, 12, 793, 8, 5, 11, 2), // 908 mops, 6.2e-5 errors + /* 21 */ V0Parameter( 1, 12, 791, 11, 4, 11, 2), // 1184 mops, 6.3e-5 errors + /* 22 */ V0Parameter( 1, 12, 805, 14, 3, 11, 2), // 1487 mops, 6.0e-5 errors + /* 23 */ V0Parameter( 1, 12, 821, 22, 2, 11, 2), // 2286 mops, 6.3e-5 errors + /* 24 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 25 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 26 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 27 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 28 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 29 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 30 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 31 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + }, +{ /* precision 7 */ + /* 0 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 1 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 2 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 3 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 4 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 5 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 6 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 7 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 8 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 9 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 10 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 11 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 12 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 13 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 14 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 15 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 16 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 17 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 18 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 19 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 20 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 21 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 22 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 23 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 24 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 25 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 26 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 27 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 28 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 29 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 30 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + /* 31 : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0), + }, +} diff --git a/examples/v0_parameters.rs b/examples/v0_parameters.rs new file mode 100644 index 000000000..eb51ddd2b --- /dev/null +++ b/examples/v0_parameters.rs @@ -0,0 +1,154 @@ +use clap::Parser; +use rayon_cond::CondIterator; + +use concrete_optimizer::optimisation::atomic_pattern as optimize_atomic_pattern; + +const _4_SIGMA: f64 = 1.0 - 0.999_936_657_516; + +/// Simple program to greet a person +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + #[clap(long, default_value_t = 1, help = "1..16")] + min_precision: u64, + + #[clap(long, default_value_t = 7, help = "1..16")] + max_precision: u64, + + #[clap(long, default_value_t = _4_SIGMA)] + p_error: f64, + + #[clap(long, default_value_t = 128, help = "Only 128 is supported")] + security_level: u64, + + #[clap(long, default_value_t = 10, help = "8..16")] + min_log_poly_size: u64, + + #[clap(long, default_value_t = 12, help = "8..16")] + max_log_poly_size: u64, + + #[clap(long, default_value_t = 1, help = "EXPERIMENTAL")] + min_glwe_dim: u64, + + #[clap(long, default_value_t = 1, help = "EXPERIMENTAL")] + // only usefull for very low precision, some parts are not correcte if used with k > 1 + max_glwe_dim: u64, + + #[clap(long, default_value_t = 512)] + min_intern_lwe_dim: u64, + + #[clap(long, default_value_t = 1024)] + max_intern_lwe_dim: u64, // 16bits needs around 1300 + + #[clap(long, default_value_t = 4096)] + sum_size: u64, + + #[clap(long)] + no_parallelize: bool, +} + +fn main() { + let args = Args::parse(); + let sum_size = args.sum_size; + let p_error = args.p_error; + let security_level = args.security_level; + if security_level != 128 { + panic!("Only 128bits of security is supported") + } + + let glwe_log_polynomial_sizes: Vec<_> = + (args.min_log_poly_size..=args.max_log_poly_size).collect(); + let glwe_dimensions: Vec<_> = (args.min_glwe_dim..=args.max_glwe_dim).collect(); + let internal_lwe_dimensions: Vec<_> = + (args.min_intern_lwe_dim..=args.max_intern_lwe_dim).collect(); + + let precisions = args.min_precision..=args.max_precision; + let manps = 0..=31; + + // let guard = pprof::ProfilerGuard::new(100).unwrap(); + + let precisions_iter = CondIterator::new(precisions.clone(), !args.no_parallelize); + + #[rustfmt::skip] + let all_results = precisions_iter.map(|precision| { + let mut last_solution = None; + manps.clone().map(|manp| { + let noise_scale = 2_f64.powi(manp); + let result = optimize_atomic_pattern::optimise_one::( + sum_size, + precision, + security_level, + noise_scale, + p_error, + &glwe_log_polynomial_sizes, + &glwe_dimensions, + &internal_lwe_dimensions, + last_solution, // 33% gains + ); + last_solution = result.best_solution; + result + }) + .collect::>() + }) + .collect::>(); + + /* + if let Ok(report) = guard.report().build() { + let file = std::fs::File::create("flamegraph.svg").unwrap(); + let mut options = pprof::flamegraph::Options::default(); + options.image_width = Some(32000); + report.flamegraph_with_options(file, &mut options).unwrap(); + }; + */ + + println!("{{ /* {:1.1e} errors */", p_error); + for (precision_i, precision) in precisions.enumerate() { + println!("{{ /* precision {:2} */", precision); + for (manp_i, manp) in manps.clone().enumerate() { + let solution = all_results[precision_i][manp_i].best_solution; + if let Some(solution) = solution { + println!(" /* {:2} */ V0Parameter({:2}, {:2}, {:4}, {:2}, {:2}, {:2}, {:2}), \t\t // {:4} mops, {:1.1e} errors", + manp, solution.glwe_dimension, (solution.glwe_polynomial_size as f64).log2() as u64, + solution.internal_ks_output_lwe_dimension, + solution.br_decomposition_level_count, solution.br_decomposition_base_log, + solution.ks_decomposition_level_count, solution.ks_decomposition_base_log, + (solution.complexity / (1024.0 * 1024.0)) as u64, + solution.p_error + ) + } else { + println!( + " /* {:2} : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0),", + manp + ); + } + } + println!(" }},"); + } + println!("}}"); +} + +#[cfg(test)] +mod tests { + + #[test] + fn test_reference_output() { + const REF_FILE: &str = "examples/v0_parameters.ref-02-03-2022"; + const V0_PARAMETERS_EXE: &str = "target/debug/examples/v0_parameters"; + const CMP_LINES: &str = "\n"; + const EXACT_EQUALITY: i32 = 0; + let _ = std::process::Command::new("cargo") + .args(["build", "-q", "--example", "v0_parameters"]) + .status() + .expect("Can't build"); + assert!(std::path::Path::new(V0_PARAMETERS_EXE).exists()); + + let actual_output = std::process::Command::new(V0_PARAMETERS_EXE) + .output() + .expect("failed to execute process"); + let actual_output = std::str::from_utf8(&actual_output.stdout).expect("Bad content"); + + let expected_output = std::fs::read_to_string(REF_FILE).expect("Can't read reference file"); + + text_diff::assert_diff(&expected_output, &actual_output, CMP_LINES, EXACT_EQUALITY); + } +} diff --git a/src/lib.rs b/src/lib.rs index f5adc687f..c7b4153fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,5 +18,7 @@ pub mod computing_cost; pub mod global_parameters; pub mod graph; pub mod noise_estimator; +pub mod optimisation; pub mod parameters; +pub mod security; pub mod weight; diff --git a/src/noise_estimator/error.rs b/src/noise_estimator/error.rs new file mode 100644 index 000000000..59276c7f6 --- /dev/null +++ b/src/noise_estimator/error.rs @@ -0,0 +1,40 @@ +pub fn sigma_scale_of_error_probability(p_error: f64) -> f64 { + // https://en.wikipedia.org/wiki/Error_function#Applications + let p_in = 1.0 - p_error; + statrs::function::erf::erf_inv(p_in) * 2_f64.sqrt() +} + +pub fn error_probability_of_sigma_scale(sigma_scale: f64) -> f64 { + // https://en.wikipedia.org/wiki/Error_function#Applications + 1.0 - statrs::function::erf::erf(sigma_scale / 2_f64.sqrt()) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_sigmas() { + // https://en.wikipedia.org/wiki/Normal_distribution#Standard_deviation_and_coverage + let reference = &[ + 0.682_689_492_137, // +- 1 sigma + 0.954_499_736_104, // 2 + 0.997_300_203_937, // ... + 0.999_936_657_516, + 0.999_999_426_697, + ]; + for (i, &p_in) in reference.iter().enumerate() { + let p_out = 1.0 - p_in; + let expected_scale = (i + 1) as f64; + approx::assert_relative_eq!( + expected_scale, + sigma_scale_of_error_probability(p_out), + max_relative = 1e-8 + ); + approx::assert_relative_eq!( + p_out, + error_probability_of_sigma_scale(sigma_scale_of_error_probability(p_out)), + max_relative = 1e-8 + ); + } + } +} diff --git a/src/noise_estimator/mod.rs b/src/noise_estimator/mod.rs index 46666d91e..4ba5d7afc 100644 --- a/src/noise_estimator/mod.rs +++ b/src/noise_estimator/mod.rs @@ -1,3 +1,3 @@ +pub mod error; pub mod operators; -pub mod security; pub mod utils; diff --git a/src/noise_estimator/operators/atomic_pattern.rs b/src/noise_estimator/operators/atomic_pattern.rs index b28177691..592490a34 100644 --- a/src/noise_estimator/operators/atomic_pattern.rs +++ b/src/noise_estimator/operators/atomic_pattern.rs @@ -5,7 +5,7 @@ use concrete_commons::parameters::{ DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize, }; -use super::super::security; +use crate::security; /// Additional noise generated by the keyswitch step. pub fn variance_keyswitch( @@ -30,17 +30,18 @@ pub fn variance_keyswitch( ) } -/// Compute the variance paramater for `variance_keyswitch` +/// Compute the variance parameter of the keyswitch key. pub fn variance_ksk( internal_ks_output_lwe_dimension: u64, ciphertext_modulus_log: u64, security_level: u64, ) -> Variance { - let glwe_poly_size = 1; - let glwe_dim = internal_ks_output_lwe_dimension; - security::variance_ksk( - glwe_poly_size, - glwe_dim, + let glwe_polynomial_size = 1; + let glwe_dimension = internal_ks_output_lwe_dimension; + // https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/keyswitch.py#L13 + security::glwe::minimal_variance( + glwe_polynomial_size, + glwe_dimension, ciphertext_modulus_log, security_level, ) @@ -51,8 +52,8 @@ pub fn fft_noise( internal_ks_output_lwe_dimension: u64, //n_small glwe_polynomial_size: u64, //N _glwe_dimension: u64, //k, unused - br_decomposition_level_count: u64, //l(KS) - br_decomposition_base_log: u64, //b(ks) + br_decomposition_level_count: u64, + br_decomposition_base_log: u64, ) -> Variance { // https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/bootstrap.py#L25 let n = internal_ks_output_lwe_dimension as f64; @@ -206,7 +207,8 @@ where D: DispersionParameter, W: UnsignedInteger, { - let variance_bsk = security::variance_bsk( + // https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/bootstrap.py#L66 + let variance_bsk = security::glwe::minimal_variance( glwe_polynomial_size, glwe_dimension, ciphertext_modulus_log, @@ -288,7 +290,7 @@ mod tests { let br_decomposition_base_log = 24; let ciphertext_modulus_log = 64; let security = 128; - let variance_bsk = security::variance_bsk( + let variance_bsk = security::glwe::minimal_variance( glwe_polynomial_size, glwe_dimension, ciphertext_modulus_log, @@ -318,7 +320,7 @@ mod tests { let br_decomposition_base_log = 5; let ciphertext_modulus_log = 128; let security = 128; - let variance_bsk = security::variance_bsk( + let variance_bsk = security::glwe::minimal_variance( glwe_polynomial_size, glwe_dimension, ciphertext_modulus_log, diff --git a/src/optimisation/atomic_pattern.rs b/src/optimisation/atomic_pattern.rs new file mode 100644 index 000000000..9a1b5d33f --- /dev/null +++ b/src/optimisation/atomic_pattern.rs @@ -0,0 +1,586 @@ +use concrete_commons::dispersion::{DispersionParameter, Variance}; +use concrete_commons::numeric::UnsignedInteger; + +use crate::computing_cost::operators::atomic_pattern as complexity_atomic_pattern; +use complexity_atomic_pattern::AtomicPatternComplexity; + +use crate::computing_cost::operators::keyswitch_lwe::KeySwitchLWEComplexity; +use crate::computing_cost::operators::pbs::PbsComplexity; + +use crate::noise_estimator::error::{ + error_probability_of_sigma_scale, sigma_scale_of_error_probability, +}; +use crate::noise_estimator::operators::atomic_pattern as noise_atomic_pattern; +use crate::security; + +#[rustfmt::skip] +const BR_BL: &[(u64, u64); 35] = &[ + (12, 1), (23, 1), (8, 2), (15, 2), (16, 2), (3, 3), (6, 3), (12, 3), (2, 4), + (5, 4), (9, 4), (4, 5), (8, 5), (7, 6), (3, 7), (6, 7), (1, 8), (5, 8), (1, 9), + (5, 9), (2, 10), (4, 10), (2, 11), (4, 11), (3, 14), (3, 15), (1, 21), (2, 21), (1, 22), + (2, 22), (2, 23), (1, 43), (1, 44), (1, 45), (1, 46) +]; + +#[rustfmt::skip] +const KS_BL: &[(u64, u64); 46] = &[ + (5, 1), (12, 1), (26, 1), (31, 1), (4, 2), (8, 2), (17, 2), (21, 2), (3, 3), + (6, 3), (13, 3), (15, 3), (2, 4), (5, 4), (10, 4), (12, 4), (2, 5), (4, 5), + (9, 5), (10, 5), (4, 6), (8, 6), (3, 7), (7, 7), (3, 8), (6, 8), (1, 9), (5, 9), (1, 10), + (5, 10), (2, 11), (2, 12), (4, 12), (4, 13), (3, 16), (3, 17), (1, 22), (1, 23), (2, 24), + (2, 25), (2, 26), (1, 48), (1, 49), (1, 50), (1, 51), (1, 52) +]; + +fn square(v: f64) -> f64 { + v * v +} + +/* enable to debug */ +const CHECKS: bool = false; +/* disable to debug */ +// Ref time for v0 table 1 thread: 950ms +const CUTS: bool = true; // 80ms +const PARETO_CUTS: bool = true; // 75ms +const CROSS_PARETO_CUTS: bool = PARETO_CUTS && true; // 70ms + +#[derive(Debug, Clone, Copy)] +pub struct Solution { + pub input_lwe_dimension: u64, //n_big + pub internal_ks_output_lwe_dimension: u64, //n_small + pub ks_decomposition_level_count: u64, //l(KS) + pub ks_decomposition_base_log: u64, //b(KS) + pub glwe_polynomial_size: u64, //N + pub glwe_dimension: u64, //k + pub br_decomposition_level_count: u64, //l(BR) + pub br_decomposition_base_log: u64, //b(BR) + pub complexity: f64, + pub noise_max: f64, + pub p_error: f64, // error probability +} + +// Constants during optimisation of decompositions +struct OptimizationDecompositionsConsts { + kappa: f64, + sum_size: u64, + security_level: u64, + noise_factor: f64, + ciphertext_modulus_log: u64, + keyswitch_decompositions: Vec<(u64, u64)>, + blind_rotate_decompositions: Vec<(u64, u64)>, + variance_max: f64, +} + +#[derive(Clone, Copy)] +struct ComplexityNoise { + index: usize, + complexity: f64, + noise: f64, +} + +impl ComplexityNoise { + const ZERO: Self = Self { + index: 0, + complexity: 0.0, + noise: 0.0, + }; +} + +fn blind_rotate_quantities( + consts: &OptimizationDecompositionsConsts, + internal_dim: u64, + glwe_poly_size: u64, + glwe_dim: u64, + cut_complexity: f64, + cut_noise: f64, +) -> Vec { + let br_decomp_len = consts.blind_rotate_decompositions.len(); + let mut quantities = vec![ComplexityNoise::ZERO; br_decomp_len]; + + let ciphertext_modulus_log = consts.ciphertext_modulus_log; + let security_level = consts.security_level; + let variance_bsk = security::glwe::minimal_variance( + glwe_poly_size, + glwe_dim, + ciphertext_modulus_log, + security_level, + ); + let mut increasing_complexity = 0.0; + let mut decreasing_variance = f64::INFINITY; + let mut size = 0; + for (i_br, &(br_b, br_l)) in consts.blind_rotate_decompositions.iter().enumerate() { + let complexity_pbs = complexity_atomic_pattern::DEFAULT.pbs.complexity( + internal_dim, + glwe_poly_size, + glwe_dim, + br_l, + br_b, + consts.ciphertext_modulus_log, + ); + if cut_complexity < complexity_pbs && CUTS { + break; // complexity is increasing + } + let base_noise = noise_atomic_pattern::variance_bootstrap::( + internal_dim, + glwe_poly_size, + glwe_dim, + br_l, + br_b, + consts.ciphertext_modulus_log, + variance_bsk, + ); + let noise_in = base_noise.get_variance() * square(consts.noise_factor); + if cut_noise < noise_in && CUTS { + continue; // noise is decreasing + } + if decreasing_variance < noise_in && PARETO_CUTS { + // the current case is dominated + continue; + } + let delta_complexity = complexity_pbs - increasing_complexity; + size -= if delta_complexity == 0.0 && PARETO_CUTS { + 1 // the previous case is dominated + } else { + 0 + }; + quantities[size] = ComplexityNoise { + index: i_br, + complexity: complexity_pbs, + noise: noise_in, + }; + assert!( + 0.0 <= delta_complexity, + "blind_rotate_decompositions should be by increasing complexity" + ); + increasing_complexity = complexity_pbs; + decreasing_variance = noise_in; + size += 1; + } + assert!(!PARETO_CUTS || size < 64); + quantities.truncate(size); + quantities +} + +fn keyswitch_quantities( + consts: &OptimizationDecompositionsConsts, + in_dim: u64, + internal_dim: u64, + cut_complexity: f64, + cut_noise: f64, +) -> Vec { + let ks_decomp_len = consts.keyswitch_decompositions.len(); + let mut quantities = vec![ComplexityNoise::ZERO; ks_decomp_len]; + + let ciphertext_modulus_log = consts.ciphertext_modulus_log; + let security_level = consts.security_level; + let variance_ksk = + noise_atomic_pattern::variance_ksk(internal_dim, ciphertext_modulus_log, security_level); + let mut increasing_complexity = 0.0; + let mut decreasing_variance = f64::INFINITY; + let mut size = 0; + for (i_ks, &(ks_b, ks_l)) in consts.keyswitch_decompositions.iter().enumerate() { + let complexity_keyswitch = complexity_atomic_pattern::DEFAULT.ks_lwe.complexity( + in_dim, + internal_dim, + ks_l, + ks_b, + ciphertext_modulus_log, + ); + if cut_complexity < complexity_keyswitch && CUTS { + break; + } + let noise_keyswitch = noise_atomic_pattern::variance_keyswitch::( + in_dim, + ks_l, + ks_b, + ciphertext_modulus_log, + variance_ksk, + ) + .get_variance(); + if cut_noise < noise_keyswitch && CUTS { + continue; // noise is decreasing + } + if decreasing_variance < noise_keyswitch && PARETO_CUTS { + // the current case is dominated + continue; + } + let delta_complexity = complexity_keyswitch - increasing_complexity; + size -= if delta_complexity == 0.0 && PARETO_CUTS { + 1 + } else { + 0 + }; + quantities[size] = ComplexityNoise { + index: i_ks, + complexity: complexity_keyswitch, + noise: noise_keyswitch, + }; + assert!( + 0.0 <= delta_complexity, + "keyswitch_decompositions should be by increasing complexity" + ); + increasing_complexity = complexity_keyswitch; + decreasing_variance = noise_keyswitch; + size += 1; + } + assert!(!PARETO_CUTS || size < 64); + quantities.truncate(size); + quantities +} + +pub struct OptimizationState { + pub best_solution: Option, + pub count_domain: usize, +} + +#[allow(clippy::too_many_lines)] +fn update_state_with_best_decompositions( + state: &mut OptimizationState, + consts: &OptimizationDecompositionsConsts, + internal_dim: u64, + glwe_poly_size: u64, + glwe_dim: u64, +) { + let input_lwe_dimension = glwe_dim * glwe_poly_size; + let noise_modulus_switching = + noise_atomic_pattern::estimate_modulus_switching_noise_with_binary_key::( + internal_dim, + glwe_poly_size, + ) + .get_variance(); + let variance_max = consts.variance_max; + if CUTS && noise_modulus_switching > variance_max { + return; + } + + let mut best_complexity = state.best_solution.map_or(f64::INFINITY, |s| s.complexity); + let mut best_variance = state.best_solution.map_or(f64::INFINITY, |s| s.noise_max); + + let complexity_multisum = (consts.sum_size * input_lwe_dimension) as f64; + let mut cut_complexity = best_complexity - complexity_multisum; + let mut cut_noise = variance_max - noise_modulus_switching; + let br_quantities = blind_rotate_quantities::( + consts, + internal_dim, + glwe_poly_size, + glwe_dim, + cut_complexity, + cut_noise, + ); + if br_quantities.is_empty() { + return; + } + if PARETO_CUTS { + cut_noise -= br_quantities[br_quantities.len() - 1].noise; + cut_complexity -= br_quantities[0].complexity; + } + let ks_quantities = keyswitch_quantities::( + consts, + input_lwe_dimension, + internal_dim, + cut_complexity, + cut_noise, + ); + if ks_quantities.is_empty() { + return; + } + + let i_max_ks = ks_quantities.len() - 1; + let mut i_current_max_ks = i_max_ks; + for br_quantity in br_quantities { + // increasing complexity, decreasing variance + let noise_in = br_quantity.noise; + let noise_max = noise_in + noise_modulus_switching; + if noise_max > variance_max && CUTS { + continue; + } + let complexity_pbs = br_quantity.complexity; + let complexity = complexity_multisum + complexity_pbs; + if complexity > best_complexity { + // As best can evolves it is complementary to blind_rotate_quantities cuts. + if PARETO_CUTS { + break; + } else if CUTS { + continue; + } + } + for i_ks_pareto in (0..=i_current_max_ks).rev() { + // increasing variance, decreasing complexity + let ks_quantity = ks_quantities[i_ks_pareto]; + let noise_keyswitch = ks_quantity.noise; + let noise_max = noise_in + noise_keyswitch + noise_modulus_switching; + let complexity_keyswitch = ks_quantity.complexity; + let complexity = complexity_multisum + complexity_keyswitch + complexity_pbs; + + if CHECKS { + assert_checks::( + consts, + internal_dim, + glwe_poly_size, + glwe_dim, + input_lwe_dimension, + ks_quantity, + br_quantity, + noise_max, + complexity_multisum, + complexity, + ); + } + + if noise_max > variance_max { + if CROSS_PARETO_CUTS { + // the pareto of 2 added pareto is scanned linearly + // but with all cuts, pre-computing => no gain + i_current_max_ks = usize::min(i_ks_pareto + 1, i_max_ks); + break; + // it's compatible with next i_br but with the worst complexity + } else if PARETO_CUTS { + // increasing variance => we can skip all remaining + break; + } + continue; + } else if complexity > best_complexity { + continue; + } + + // feasible and at least as good complexity + if complexity < best_complexity || noise_max < best_variance { + let sigma = Variance(variance_max).get_standard_dev() * consts.kappa; + let sigma_scale = sigma / Variance(noise_max).get_standard_dev(); + let p_error = error_probability_of_sigma_scale(sigma_scale); + + let i_br = br_quantity.index; + let i_ks = ks_quantity.index; + let (br_b, br_l) = consts.blind_rotate_decompositions[i_br]; + let (ks_b, ks_l) = consts.keyswitch_decompositions[i_ks]; + + best_complexity = complexity; + best_variance = noise_max; + state.best_solution = Some(Solution { + input_lwe_dimension, + internal_ks_output_lwe_dimension: internal_dim, + ks_decomposition_level_count: ks_l, + ks_decomposition_base_log: ks_b, + glwe_polynomial_size: glwe_poly_size, + glwe_dimension: glwe_dim, + br_decomposition_level_count: br_l, + br_decomposition_base_log: br_b, + noise_max, + complexity, + p_error, + }); + } + } + } // br ks +} + +// This function provides reference values with unoptimised code, until we have non regeression tests +#[allow(clippy::float_cmp)] +fn assert_checks( + consts: &OptimizationDecompositionsConsts, + internal_dim: u64, + glwe_poly_size: u64, + glwe_dim: u64, + input_lwe_dimension: u64, + ks_c_n: ComplexityNoise, + br_c_n: ComplexityNoise, + noise_max: f64, + complexity_multisum: f64, + complexity: f64, +) { + let i_ks = ks_c_n.index; + let i_br = br_c_n.index; + let noise_in = br_c_n.noise; + let noise_keyswitch = ks_c_n.noise; + let complexity_keyswitch = ks_c_n.complexity; + let complexity_pbs = br_c_n.complexity; + let ciphertext_modulus_log = consts.ciphertext_modulus_log; + let security_level = consts.security_level; + let (br_b, br_l) = consts.blind_rotate_decompositions[i_br]; + let (ks_b, ks_l) = consts.keyswitch_decompositions[i_ks]; + let variance_bsk = security::glwe::minimal_variance( + glwe_poly_size, + glwe_dim, + ciphertext_modulus_log, + security_level, + ); + let base_noise_ = noise_atomic_pattern::variance_bootstrap::( + internal_dim, + glwe_poly_size, + glwe_dim, + br_l, + br_b, + ciphertext_modulus_log, + variance_bsk, + ); + let noise_in_ = base_noise_.get_variance() * square(consts.noise_factor); + let complexity_pbs_ = complexity_atomic_pattern::DEFAULT.pbs.complexity( + internal_dim, + glwe_poly_size, + glwe_dim, + br_l, + br_b, + ciphertext_modulus_log, + ); + assert!(complexity_pbs == complexity_pbs_); + assert!(noise_in == noise_in_); + let variance_ksk = + noise_atomic_pattern::variance_ksk(internal_dim, ciphertext_modulus_log, security_level); + let noise_keyswitch_ = noise_atomic_pattern::variance_keyswitch::( + input_lwe_dimension, + ks_l, + ks_b, + ciphertext_modulus_log, + variance_ksk, + ) + .get_variance(); + let complexity_keyswitch_ = complexity_atomic_pattern::DEFAULT.ks_lwe.complexity( + input_lwe_dimension, + internal_dim, + ks_l, + ks_b, + ciphertext_modulus_log, + ); + assert!(complexity_keyswitch == complexity_keyswitch_); + assert!(noise_keyswitch == noise_keyswitch_); + + let check_max_noise = noise_atomic_pattern::maximal_noise::( + Variance(noise_in), + input_lwe_dimension, + internal_dim, + ks_l, + ks_b, + glwe_poly_size, + ciphertext_modulus_log, + security_level, + ) + .get_variance(); + assert!(f64::abs(noise_max - check_max_noise) / check_max_noise < 0.00000000001); + let check_complexity = complexity_atomic_pattern::DEFAULT.complexity( + consts.sum_size, + input_lwe_dimension, + internal_dim, + ks_l, + ks_b, + glwe_poly_size, + glwe_dim, + br_l, + br_b, + ciphertext_modulus_log, + ); + + let diff_complexity = f64::abs(complexity - check_complexity) / check_complexity; + if diff_complexity > 0.0001 { + println!( + "{} + {} + {} != {}", + complexity_multisum, complexity_keyswitch, complexity_pbs, check_complexity, + ); + } + assert!(diff_complexity < 0.0001); +} + +const BITS_CARRY: u64 = 1; +const BITS_PADDING_WITHOUT_NOISE: u64 = 1; + +#[allow(clippy::too_many_lines)] +pub fn optimise_one( + sum_size: u64, + precision: u64, + security_level: u64, + noise_factor: f64, + maximum_acceptable_error_probability: f64, + glwe_log_polynomial_sizes: &[u64], + glwe_dimensions: &[u64], + internal_lwe_dimensions: &[u64], + restart_at: Option, +) -> OptimizationState { + assert!(0 < precision && precision <= 16); + assert!(security_level == 128); + assert!(1.0 <= noise_factor); + assert!(0.0 < maximum_acceptable_error_probability); + assert!(maximum_acceptable_error_probability < 1.0); + + // this assumed the noise level is equal at input/output + // the security of the noise level of ouput is controlled by + // the blind rotate decomposition + + let ciphertext_modulus_log = W::BITS as u64; + + let no_noise_bits = BITS_CARRY + precision + BITS_PADDING_WITHOUT_NOISE; + let noise_bits = ciphertext_modulus_log - no_noise_bits; + let fatal_noise_limit = (1_u64 << noise_bits) as f64; + + // Now we search for P(x not in [-+fatal_noise_limit] | σ = safe_sigma) = p_error + // P(x not in [-+kappa] | σ = 1) = p_error + let kappa: f64 = sigma_scale_of_error_probability(maximum_acceptable_error_probability); + let safe_sigma = fatal_noise_limit / kappa; + let variance_max = Variance::from_modular_variance::(square(safe_sigma)); + + let consts = OptimizationDecompositionsConsts { + kappa, + sum_size, + security_level, + noise_factor, + ciphertext_modulus_log, + keyswitch_decompositions: KS_BL.to_vec(), + blind_rotate_decompositions: BR_BL.to_vec(), + variance_max: variance_max.get_variance(), + }; + + let mut state = OptimizationState { + best_solution: None, + count_domain: glwe_dimensions.len() + * glwe_log_polynomial_sizes.len() + * internal_lwe_dimensions.len() + * KS_BL.len() + * BR_BL.len(), + }; + + // cut only on glwe_poly_size based of modulus switching noise + // assume this noise is increasing with lwe_intern_dim + let min_internal_lwe_dimensions = internal_lwe_dimensions[0]; + let lower_bound_cut = |glwe_poly_size| { + // TODO: cut if min complexity is higher than current best + CUTS && noise_atomic_pattern::estimate_modulus_switching_noise_with_binary_key::( + min_internal_lwe_dimensions, + glwe_poly_size, + ) + .get_variance() + > consts.variance_max + }; + + let skip = |glwe_dim, glwe_poly_size| match restart_at { + Some(solution) => { + (glwe_dim, glwe_poly_size) < (solution.glwe_dimension, solution.glwe_polynomial_size) + } + None => false, + }; + + for &glwe_dim in glwe_dimensions { + assert!(1 <= glwe_dim); + assert!(glwe_dim < 4); + + for &glwe_log_poly_size in glwe_log_polynomial_sizes { + assert!(8 < glwe_log_poly_size); + assert!(glwe_log_poly_size < 18); + let glwe_poly_size = 1 << glwe_log_poly_size; + if lower_bound_cut(glwe_poly_size) { + continue; + } + if skip(glwe_dim, glwe_poly_size) { + continue; + } + + for &internal_dim in internal_lwe_dimensions { + assert!(256 < internal_dim); + update_state_with_best_decompositions::( + &mut state, + &consts, + internal_dim, + glwe_poly_size, + glwe_dim, + ); + } + } + } + + state +} diff --git a/src/optimisation/mod.rs b/src/optimisation/mod.rs new file mode 100644 index 000000000..cb66c1737 --- /dev/null +++ b/src/optimisation/mod.rs @@ -0,0 +1 @@ +pub mod atomic_pattern; diff --git a/src/noise_estimator/security.rs b/src/security/glwe.rs similarity index 62% rename from src/noise_estimator/security.rs rename to src/security/glwe.rs index 743d92245..83ecaddd1 100644 --- a/src/noise_estimator/security.rs +++ b/src/security/glwe.rs @@ -2,7 +2,7 @@ use concrete_commons::dispersion::Variance; /// Noise ensuring security // It was 128 bits of security on the 30th August 2021 with https://bitbucket.org/malb/lwe-estimator/commits/fb7deba98e599df10b665eeb6a26332e43fb5004 -pub fn variance_glwe( +pub fn minimal_variance( glwe_polynomial_size: u64, glwe_dimension: u64, ciphertext_modulus_log: u64, @@ -23,52 +23,20 @@ pub fn variance_glwe( Variance(f64::exp2(log2_var)) } -/// Noise ensuring ksk security -pub fn variance_ksk( - glwe_polynomial_size: u64, - glwe_dimension: u64, - ciphertext_modulus_log: u64, - security_level: u64, -) -> Variance { - // https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/keyswitch.py#L13 - variance_glwe( - glwe_polynomial_size, - glwe_dimension, - ciphertext_modulus_log, - security_level, - ) -} - -/// Noise ensuring bsk security -pub fn variance_bsk( - glwe_polynomial_size: u64, - glwe_dimension: u64, - ciphertext_modulus_log: u64, - security_level: u64, -) -> Variance { - // https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/bootstrap.py#L66 - variance_glwe( - glwe_polynomial_size, - glwe_dimension, - ciphertext_modulus_log, - security_level, - ) -} - #[cfg(test)] mod tests { use super::*; use concrete_commons::dispersion::DispersionParameter; #[test] - fn golden_python_prototype_security_variance_glwe_low() { + fn golden_python_prototype_security_security_glwe_variance_low() { // python securityFunc(10,14,64)= 0.3120089883926036 let log_poly_size = 14; let glwe_dimension = 10; let integer_size = 64; let golden_std_dev = 0.312_008_988_392_6036; let security_level = 128; - let actual = variance_glwe(log_poly_size, glwe_dimension, integer_size, security_level); + let actual = minimal_variance(log_poly_size, glwe_dimension, integer_size, security_level); approx::assert_relative_eq!( golden_std_dev, actual.get_standard_dev(), @@ -77,14 +45,14 @@ mod tests { } #[test] - fn golden_python_prototype_security_variance_glwe_high() { + fn golden_python_prototype_security_security_glwe_variance_high() { // python securityFunc(3,8,32)= 2.6011445832514504 let log_poly_size = 8; let glwe_dimension = 3; let integer_size = 32; let golden_std_dev = 2.6011445832514504; let security_level = 128; - let actual = variance_glwe(log_poly_size, glwe_dimension, integer_size, security_level); + let actual = minimal_variance(log_poly_size, glwe_dimension, integer_size, security_level); approx::assert_relative_eq!( golden_std_dev, actual.get_standard_dev(), diff --git a/src/security/mod.rs b/src/security/mod.rs new file mode 100644 index 000000000..b2a4742c1 --- /dev/null +++ b/src/security/mod.rs @@ -0,0 +1 @@ +pub mod glwe;