Repository: zama-ai/concrete (language: python)

Issue:
Add ability to configure custom parameter restrictions
We require the ability to configure a more restricted set of parameters from the python front end, specifically around the polynomial size. I've created this PR which is a very naive approach just to achieve the desired functionality, it would be great if someone could let me know how best to fit this functionality into the current API design and I'll get it tidied up.


Discussion / hints:

Fix patch:
diff --git a/compilers/concrete-compiler/compiler/include/concretelang/Support/V0Parameters.h b/compilers/concrete-compiler/compiler/include/concretelang/Support/V0Parameters.h
index 5a4c355008..3ef9b19e9b 100644
--- a/compilers/concrete-compiler/compiler/include/concretelang/Support/V0Parameters.h
+++ b/compilers/concrete-compiler/compiler/include/concretelang/Support/V0Parameters.h
@@ -124,6 +124,7 @@ struct Config {
   bool cache_on_disk;
   uint32_t ciphertext_modulus_log;
   uint32_t fft_precision;
+  concrete_optimizer::ParameterRestrictions parameter_restrictions;
   std::vector<CompositionRule> composition_rules;
   bool composable;
 };
@@ -141,6 +142,7 @@ const Config DEFAULT_CONFIG = {UNSPECIFIED_P_ERROR,
                                DEFAULT_CACHE_ON_DISK,
                                DEFAULT_CIPHERTEXT_MODULUS_LOG,
                                DEFAULT_FFT_PRECISION,
+                               concrete_optimizer::ParameterRestrictions{},
                                DEFAULT_COMPOSITION_RULES,
                                DEFAULT_COMPOSABLE};
 
diff --git a/compilers/concrete-compiler/compiler/lib/Bindings/Python/CompilerAPIModule.cpp b/compilers/concrete-compiler/compiler/lib/Bindings/Python/CompilerAPIModule.cpp
index bbd12d4067..5c5b264a59 100644
--- a/compilers/concrete-compiler/compiler/lib/Bindings/Python/CompilerAPIModule.cpp
+++ b/compilers/concrete-compiler/compiler/lib/Bindings/Python/CompilerAPIModule.cpp
@@ -444,6 +444,118 @@ void mlir::concretelang::python::populateCompilerAPISubmodule(
             options.optimizerConfig.security = security_level;
           },
           "Set security level.", arg("security_level"))
+      .def("set_glwe_pbs_restrictions",
+           [](CompilationOptions &options,
+              std::optional<uint64_t> log2_polynomial_size_min,
+              std::optional<uint64_t> log2_polynomial_size_max,
+              std::optional<uint64_t> glwe_dimension_min,
+              std::optional<uint64_t> glwe_dimension_max) {
+             if (log2_polynomial_size_min) {
+               options.optimizerConfig.parameter_restrictions.glwe_pbs
+                   .log2_polynomial_size_min =
+                   std::make_shared<uint64_t>(*log2_polynomial_size_min);
+             }
+             if (log2_polynomial_size_max) {
+               options.optimizerConfig.parameter_restrictions.glwe_pbs
+                   .log2_polynomial_size_max =
+                   std::make_shared<uint64_t>(*log2_polynomial_size_max);
+             }
+             if (glwe_dimension_min) {
+               options.optimizerConfig.parameter_restrictions.glwe_pbs
+                   .glwe_dimension_min =
+                   std::make_shared<uint64_t>(*glwe_dimension_min);
+             }
+             if (glwe_dimension_max) {
+               options.optimizerConfig.parameter_restrictions.glwe_pbs
+                   .glwe_dimension_max =
+                   std::make_shared<uint64_t>(*glwe_dimension_max);
+             }
+           })
+      .def("set_free_glwe_restrictions",
+           [](CompilationOptions &options,
+              std::optional<uint64_t> log2_polynomial_size_min,
+              std::optional<uint64_t> log2_polynomial_size_max,
+              std::optional<uint64_t> glwe_dimension_min,
+              std::optional<uint64_t> glwe_dimension_max) {
+             if (log2_polynomial_size_min) {
+               options.optimizerConfig.parameter_restrictions.free_glwe
+                   .log2_polynomial_size_min =
+                   std::make_shared<uint64_t>(*log2_polynomial_size_min);
+             }
+             if (log2_polynomial_size_max) {
+               options.optimizerConfig.parameter_restrictions.free_glwe
+                   .log2_polynomial_size_max =
+                   std::make_shared<uint64_t>(*log2_polynomial_size_max);
+             }
+             if (glwe_dimension_min) {
+               options.optimizerConfig.parameter_restrictions.free_glwe
+                   .glwe_dimension_min =
+                   std::make_shared<uint64_t>(*glwe_dimension_min);
+             }
+             if (glwe_dimension_max) {
+               options.optimizerConfig.parameter_restrictions.free_glwe
+                   .glwe_dimension_max =
+                   std::make_shared<uint64_t>(*glwe_dimension_max);
+             }
+           })
+      .def("set_br_decomposition_restrictions",
+           [](CompilationOptions &options,
+              std::optional<uint64_t> log2_base_min,
+              std::optional<uint64_t> log2_base_max,
+              std::optional<uint64_t> level_min,
+              std::optional<uint64_t> level_max) {
+             if (log2_base_min) {
+               options.optimizerConfig.parameter_restrictions.br_decomposition
+                   .log2_base_min = std::make_shared<uint64_t>(*log2_base_min);
+             }
+             if (log2_base_max) {
+               options.optimizerConfig.parameter_restrictions.br_decomposition
+                   .log2_base_max = std::make_shared<uint64_t>(*log2_base_max);
+             }
+             if (level_min) {
+               options.optimizerConfig.parameter_restrictions.br_decomposition
+                   .level_min = std::make_shared<uint64_t>(*level_min);
+             }
+             if (level_max) {
+               options.optimizerConfig.parameter_restrictions.br_decomposition
+                   .level_max = std::make_shared<uint64_t>(*level_max);
+             }
+           })
+      .def("set_ks_decomposition_restrictions",
+           [](CompilationOptions &options,
+              std::optional<uint64_t> log2_base_min,
+              std::optional<uint64_t> log2_base_max,
+              std::optional<uint64_t> level_min,
+              std::optional<uint64_t> level_max) {
+             if (log2_base_min) {
+               options.optimizerConfig.parameter_restrictions.ks_decomposition
+                   .log2_base_min = std::make_shared<uint64_t>(*log2_base_min);
+             }
+             if (log2_base_max) {
+               options.optimizerConfig.parameter_restrictions.ks_decomposition
+                   .log2_base_max = std::make_shared<uint64_t>(*log2_base_max);
+             }
+             if (level_min) {
+               options.optimizerConfig.parameter_restrictions.ks_decomposition
+                   .level_min = std::make_shared<uint64_t>(*level_min);
+             }
+             if (level_max) {
+               options.optimizerConfig.parameter_restrictions.ks_decomposition
+                   .level_max = std::make_shared<uint64_t>(*level_max);
+             }
+           })
+      .def("set_free_lwe_restrictions",
+           [](CompilationOptions &options, std::optional<uint64_t> free_lwe_min,
+              std::optional<uint64_t> free_lwe_max) {
+             if (free_lwe_min) {
+               options.optimizerConfig.parameter_restrictions.free_lwe_min =
+                   std::make_shared<uint64_t>(*free_lwe_min);
+             }
+             if (free_lwe_max) {
+               options.optimizerConfig.parameter_restrictions.free_lwe_max =
+                   std::make_shared<uint64_t>(*free_lwe_max);
+             }
+           })
       .def(
           "set_v0_parameter",
           [](CompilationOptions &options, size_t glweDimension,
diff --git a/compilers/concrete-compiler/compiler/lib/Support/V0Parameters.cpp b/compilers/concrete-compiler/compiler/lib/Support/V0Parameters.cpp
index 3921c15111..6476440763 100644
--- a/compilers/concrete-compiler/compiler/lib/Support/V0Parameters.cpp
+++ b/compilers/concrete-compiler/compiler/lib/Support/V0Parameters.cpp
@@ -35,7 +35,8 @@ concrete_optimizer::Options options_from_config(optimizer::Config config) {
       /* .encoding = */ config.encoding,
       /* .cache_on_disk = */ config.cache_on_disk,
       /* .ciphertext_modulus_log = */ config.ciphertext_modulus_log,
-      /* .fft_precision = */ config.fft_precision};
+      /* .fft_precision = */ config.fft_precision,
+      /* .parameter_restrictions = */ config.parameter_restrictions};
   return options;
 }
 
diff --git a/compilers/concrete-optimizer/charts/src/bin/norm2_complexity.rs b/compilers/concrete-optimizer/charts/src/bin/norm2_complexity.rs
index 83cc4144bc..75e2b12fc9 100644
--- a/compilers/concrete-optimizer/charts/src/bin/norm2_complexity.rs
+++ b/compilers/concrete-optimizer/charts/src/bin/norm2_complexity.rs
@@ -1,21 +1,25 @@
 use charts::{draw, Serie};
 use concrete_optimizer::computing_cost::cpu::CpuComplexity;
 use concrete_optimizer::config;
-use concrete_optimizer::global_parameters::DEFAUT_DOMAINS;
+use concrete_optimizer::global_parameters::DEFAULT_DOMAINS;
 use concrete_optimizer::optimization::atomic_pattern::{self as optimize_atomic_pattern};
 use concrete_optimizer::optimization::config::{Config, SearchSpace};
 use concrete_optimizer::optimization::decomposition;
 use concrete_optimizer::optimization::wop_atomic_pattern::optimize as optimize_wop_atomic_pattern;
 
 pub const _4_SIGMA: f64 = 1.0 - 0.999_936_657_516;
-const MIN_LOG_POLY_SIZE: u64 = DEFAUT_DOMAINS
-    .glwe_pbs_constrained
+const MIN_LOG_POLY_SIZE: u64 = DEFAULT_DOMAINS
+    .glwe_pbs_constrained_cpu
     .log2_polynomial_size
     .start;
-const MAX_LOG_POLY_SIZE: u64 = DEFAUT_DOMAINS.glwe_pbs_constrained.log2_polynomial_size.end - 1;
-pub const MAX_GLWE_DIM: u64 = DEFAUT_DOMAINS.glwe_pbs_constrained.glwe_dimension.end - 1;
-pub const MIN_LWE_DIM: u64 = DEFAUT_DOMAINS.free_glwe.glwe_dimension.start;
-pub const MAX_LWE_DIM: u64 = DEFAUT_DOMAINS.free_glwe.glwe_dimension.end - 1;
+const MAX_LOG_POLY_SIZE: u64 = DEFAULT_DOMAINS
+    .glwe_pbs_constrained_cpu
+    .log2_polynomial_size
+    .end
+    - 1;
+pub const MAX_GLWE_DIM: u64 = DEFAULT_DOMAINS.glwe_pbs_constrained_cpu.glwe_dimension.end - 1;
+pub const MIN_LWE_DIM: u64 = DEFAULT_DOMAINS.free_glwe.glwe_dimension.start;
+pub const MAX_LWE_DIM: u64 = DEFAULT_DOMAINS.free_glwe.glwe_dimension.end - 1;
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     let sum_size = 4096;
@@ -31,7 +35,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         glwe_log_polynomial_sizes,
         glwe_dimensions,
         internal_lwe_dimensions,
-        levelled_only_lwe_dimensions: DEFAUT_DOMAINS.free_lwe,
+        levelled_only_lwe_dimensions: DEFAULT_DOMAINS.free_lwe,
     };
 
     let precision = 8;
diff --git a/compilers/concrete-optimizer/charts/src/bin/precision_complexity.rs b/compilers/concrete-optimizer/charts/src/bin/precision_complexity.rs
index 068cf6efbe..8de3837986 100644
--- a/compilers/concrete-optimizer/charts/src/bin/precision_complexity.rs
+++ b/compilers/concrete-optimizer/charts/src/bin/precision_complexity.rs
@@ -1,21 +1,25 @@
 use charts::{draw, Serie};
 use concrete_optimizer::computing_cost::cpu::CpuComplexity;
 use concrete_optimizer::config;
-use concrete_optimizer::global_parameters::DEFAUT_DOMAINS;
+use concrete_optimizer::global_parameters::DEFAULT_DOMAINS;
 use concrete_optimizer::optimization::atomic_pattern::{self as optimize_atomic_pattern};
 use concrete_optimizer::optimization::config::{Config, SearchSpace};
 use concrete_optimizer::optimization::decomposition;
 use concrete_optimizer::optimization::wop_atomic_pattern::optimize as optimize_wop_atomic_pattern;
 
 pub const _4_SIGMA: f64 = 1.0 - 0.999_936_657_516;
-const MIN_LOG_POLY_SIZE: u64 = DEFAUT_DOMAINS
-    .glwe_pbs_constrained
+const MIN_LOG_POLY_SIZE: u64 = DEFAULT_DOMAINS
+    .glwe_pbs_constrained_cpu
     .log2_polynomial_size
     .start;
-const MAX_LOG_POLY_SIZE: u64 = DEFAUT_DOMAINS.glwe_pbs_constrained.log2_polynomial_size.end - 1;
-pub const MAX_GLWE_DIM: u64 = DEFAUT_DOMAINS.glwe_pbs_constrained.glwe_dimension.end - 1;
-pub const MIN_LWE_DIM: u64 = DEFAUT_DOMAINS.free_glwe.glwe_dimension.start;
-pub const MAX_LWE_DIM: u64 = DEFAUT_DOMAINS.free_glwe.glwe_dimension.end - 1;
+const MAX_LOG_POLY_SIZE: u64 = DEFAULT_DOMAINS
+    .glwe_pbs_constrained_cpu
+    .log2_polynomial_size
+    .end
+    - 1;
+pub const MAX_GLWE_DIM: u64 = DEFAULT_DOMAINS.glwe_pbs_constrained_cpu.glwe_dimension.end - 1;
+pub const MIN_LWE_DIM: u64 = DEFAULT_DOMAINS.free_glwe.glwe_dimension.start;
+pub const MAX_LWE_DIM: u64 = DEFAULT_DOMAINS.free_glwe.glwe_dimension.end - 1;
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     let processing_unit = config::ProcessingUnit::Cpu;
@@ -37,7 +41,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         glwe_log_polynomial_sizes,
         glwe_dimensions,
         internal_lwe_dimensions,
-        levelled_only_lwe_dimensions: DEFAUT_DOMAINS.free_lwe,
+        levelled_only_lwe_dimensions: DEFAULT_DOMAINS.free_lwe,
     };
 
     let config = Config {
diff --git a/compilers/concrete-optimizer/concrete-optimizer-cpp/Makefile b/compilers/concrete-optimizer/concrete-optimizer-cpp/Makefile
index da55d3e43c..d453c9951b 100644
--- a/compilers/concrete-optimizer/concrete-optimizer-cpp/Makefile
+++ b/compilers/concrete-optimizer/concrete-optimizer-cpp/Makefile
@@ -37,7 +37,7 @@ $(INTERFACE_LIB): $(INTERFACE_LIB_ORIG)
 TESTS_SOURCES = tests/src/main.cpp
 TEST_DEP_LIBS = -l pthread -ldl
 tests/tests_exe: $(INTERFACE_LIB) $(INTERFACE_HEADER) $(INTERFACE_CPP) $(TESTS_SOURCES)
-	g++ -Wall -Werror -Wextra -o $@ $(TESTS_SOURCES) $(INTERFACE_CPP) $(INTERFACE_LIB) -I $(shell dirname $(INTERFACE_HEADER)) $(TEST_DEP_LIBS)
+	g++ -Wall -Werror -Wextra -std=c++17 -o $@ $(TESTS_SOURCES) $(INTERFACE_CPP) $(INTERFACE_LIB) -I $(shell dirname $(INTERFACE_HEADER)) $(TEST_DEP_LIBS)
 	chmod +x $@
 
 test: tests/tests_exe
diff --git a/compilers/concrete-optimizer/concrete-optimizer-cpp/src/concrete-optimizer.rs b/compilers/concrete-optimizer/concrete-optimizer-cpp/src/concrete-optimizer.rs
index 25db907193..06f38c9eb9 100644
--- a/compilers/concrete-optimizer/concrete-optimizer-cpp/src/concrete-optimizer.rs
+++ b/compilers/concrete-optimizer/concrete-optimizer-cpp/src/concrete-optimizer.rs
@@ -10,6 +10,7 @@ use concrete_optimizer::dag::operator::{
     self, FunctionTable, LevelledComplexity, OperatorIndex, Precision, Shape,
 };
 use concrete_optimizer::dag::unparametrized;
+use concrete_optimizer::global_parameters::{ParameterDomains, DEFAULT_DOMAINS};
 use concrete_optimizer::optimization::config::{Config, SearchSpace};
 use concrete_optimizer::optimization::dag::multi_parameters::keys_spec;
 use concrete_optimizer::optimization::dag::multi_parameters::keys_spec::CircuitSolution;
@@ -41,7 +42,7 @@ fn no_dag_solution() -> ffi::DagSolution {
     }
 }
 
-fn caches_from(options: ffi::Options) -> decomposition::PersistDecompCaches {
+fn caches_from(options: &ffi::Options) -> decomposition::PersistDecompCaches {
     if !options.cache_on_disk {
         println!("optimizer: Using stateless cache.");
         let cache_dir = default_cache_dir();
@@ -58,6 +59,195 @@ fn caches_from(options: ffi::Options) -> decomposition::PersistDecompCaches {
     )
 }
 
+fn calculate_parameter_domain(options: &ffi::Options) -> ParameterDomains {
+    let mut domains = DEFAULT_DOMAINS;
+
+    if !options
+        .parameter_restrictions
+        .glwe_pbs
+        .log2_polynomial_size_min
+        .is_null()
+    {
+        domains.glwe_pbs_constrained_cpu.log2_polynomial_size.start = *options
+            .parameter_restrictions
+            .glwe_pbs
+            .log2_polynomial_size_min;
+        domains.glwe_pbs_constrained_gpu.log2_polynomial_size.start = *options
+            .parameter_restrictions
+            .glwe_pbs
+            .log2_polynomial_size_min;
+    }
+    if !options
+        .parameter_restrictions
+        .glwe_pbs
+        .log2_polynomial_size_max
+        .is_null()
+    {
+        domains.glwe_pbs_constrained_cpu.log2_polynomial_size.end = *options
+            .parameter_restrictions
+            .glwe_pbs
+            .log2_polynomial_size_max;
+        domains.glwe_pbs_constrained_gpu.log2_polynomial_size.end = *options
+            .parameter_restrictions
+            .glwe_pbs
+            .log2_polynomial_size_max;
+    }
+    if !options
+        .parameter_restrictions
+        .glwe_pbs
+        .glwe_dimension_min
+        .is_null()
+    {
+        domains.glwe_pbs_constrained_cpu.glwe_dimension.start =
+            *options.parameter_restrictions.glwe_pbs.glwe_dimension_min;
+        domains.glwe_pbs_constrained_gpu.glwe_dimension.start =
+            *options.parameter_restrictions.glwe_pbs.glwe_dimension_min;
+    }
+    if !options
+        .parameter_restrictions
+        .glwe_pbs
+        .glwe_dimension_max
+        .is_null()
+    {
+        domains.glwe_pbs_constrained_cpu.glwe_dimension.end =
+            *options.parameter_restrictions.glwe_pbs.glwe_dimension_max;
+        domains.glwe_pbs_constrained_gpu.glwe_dimension.end =
+            *options.parameter_restrictions.glwe_pbs.glwe_dimension_max;
+    }
+
+    if !options
+        .parameter_restrictions
+        .free_glwe
+        .log2_polynomial_size_min
+        .is_null()
+    {
+        domains.free_glwe.log2_polynomial_size.start = *options
+            .parameter_restrictions
+            .free_glwe
+            .log2_polynomial_size_min;
+    }
+    if !options
+        .parameter_restrictions
+        .free_glwe
+        .log2_polynomial_size_max
+        .is_null()
+    {
+        domains.free_glwe.log2_polynomial_size.end = *options
+            .parameter_restrictions
+            .free_glwe
+            .log2_polynomial_size_max;
+    }
+    if !options
+        .parameter_restrictions
+        .free_glwe
+        .glwe_dimension_min
+        .is_null()
+    {
+        domains.free_glwe.glwe_dimension.start =
+            *options.parameter_restrictions.free_glwe.glwe_dimension_min;
+    }
+    if !options
+        .parameter_restrictions
+        .free_glwe
+        .glwe_dimension_max
+        .is_null()
+    {
+        domains.free_glwe.glwe_dimension.end =
+            *options.parameter_restrictions.free_glwe.glwe_dimension_max;
+    }
+
+    if !options
+        .parameter_restrictions
+        .br_decomposition
+        .log2_base_min
+        .is_null()
+    {
+        domains.br_decomposition.log2_base.start = *options
+            .parameter_restrictions
+            .br_decomposition
+            .log2_base_min;
+    }
+    if !options
+        .parameter_restrictions
+        .br_decomposition
+        .log2_base_max
+        .is_null()
+    {
+        domains.br_decomposition.log2_base.end = *options
+            .parameter_restrictions
+            .br_decomposition
+            .log2_base_max;
+    }
+    if !options
+        .parameter_restrictions
+        .br_decomposition
+        .level_min
+        .is_null()
+    {
+        domains.br_decomposition.level.start =
+            *options.parameter_restrictions.br_decomposition.level_min;
+    }
+    if !options
+        .parameter_restrictions
+        .br_decomposition
+        .level_max
+        .is_null()
+    {
+        domains.br_decomposition.level.end =
+            *options.parameter_restrictions.br_decomposition.level_max;
+    }
+
+    if !options
+        .parameter_restrictions
+        .ks_decomposition
+        .log2_base_min
+        .is_null()
+    {
+        domains.ks_decomposition.log2_base.start = *options
+            .parameter_restrictions
+            .ks_decomposition
+            .log2_base_min;
+    }
+    if !options
+        .parameter_restrictions
+        .ks_decomposition
+        .log2_base_max
+        .is_null()
+    {
+        domains.ks_decomposition.log2_base.end = *options
+            .parameter_restrictions
+            .ks_decomposition
+            .log2_base_max;
+    }
+    if !options
+        .parameter_restrictions
+        .ks_decomposition
+        .level_min
+        .is_null()
+    {
+        domains.ks_decomposition.level.start =
+            *options.parameter_restrictions.ks_decomposition.level_min;
+    }
+    if !options
+        .parameter_restrictions
+        .ks_decomposition
+        .level_max
+        .is_null()
+    {
+        domains.ks_decomposition.level.end =
+            *options.parameter_restrictions.ks_decomposition.level_max;
+    }
+
+    if !options.parameter_restrictions.free_lwe_min.is_null() {
+        domains.free_lwe.start = *options.parameter_restrictions.free_lwe_min;
+    }
+    if !options.parameter_restrictions.free_lwe_max.is_null() {
+        domains.free_lwe.end = *options.parameter_restrictions.free_lwe_max;
+    }
+
+    domains
+}
+
 #[derive(Clone)]
 pub struct ExternalPartition(
     concrete_optimizer::optimization::dag::multi_parameters::partition_cut::ExternalPartition,
@@ -88,7 +278,7 @@ pub fn get_external_partition(
 }
 
 pub fn get_noise_br(
-    options: ffi::Options,
+    options: &ffi::Options,
     log2_polynomial_size: u64,
     glwe_dimension: u64,
     lwe_dim: u64,
@@ -112,7 +302,7 @@ pub fn get_noise_br(
     }
 }
 
-fn optimize_bootstrap(precision: u64, noise_factor: f64, options: ffi::Options) -> ffi::Solution {
+fn optimize_bootstrap(precision: u64, noise_factor: f64, options: &ffi::Options) -> ffi::Solution {
     // Support composable since there is no dag
     let processing_unit = processing_unit(options);
 
@@ -127,7 +317,8 @@ fn optimize_bootstrap(precision: u64, noise_factor: f64, options: ffi::Options)
 
     let sum_size = 1;
 
-    let search_space = SearchSpace::default(processing_unit);
+    let parameter_restrictions = calculate_parameter_domain(options);
+    let search_space = SearchSpace::default(processing_unit, parameter_restrictions);
 
     let result = concrete_optimizer::optimization::atomic_pattern::optimize_one(
         sum_size,
@@ -566,7 +757,7 @@ impl Dag {
             .collect()
     }
 
-    fn optimize(&self, options: ffi::Options) -> ffi::DagSolution {
+    fn optimize(&self, options: &ffi::Options) -> ffi::DagSolution {
         let processing_unit = processing_unit(options);
         let config = Config {
             security_level: options.security_level,
@@ -577,7 +768,8 @@ impl Dag {
             complexity_model: &CpuComplexity::default(),
         };
 
-        let search_space = SearchSpace::default(processing_unit);
+        let parameter_restrictions = calculate_parameter_domain(options);
+        let search_space = SearchSpace::default(processing_unit, parameter_restrictions);
 
         let encoding = options.encoding.into();
 
@@ -638,7 +830,7 @@ impl Dag {
         self.0.add_compositions(froms, tos);
     }
 
-    fn optimize_multi(&self, options: ffi::Options) -> ffi::CircuitSolution {
+    fn optimize_multi(&self, options: &ffi::Options) -> ffi::CircuitSolution {
         let processing_unit = processing_unit(options);
         let config = Config {
             security_level: options.security_level,
@@ -648,7 +840,8 @@ impl Dag {
             fft_precision: options.fft_precision,
             complexity_model: &CpuComplexity::default(),
         };
-        let search_space = SearchSpace::default(processing_unit);
+        let parameter_restrictions = calculate_parameter_domain(options);
+        let search_space = SearchSpace::default(processing_unit, parameter_restrictions);
 
         let encoding = options.encoding.into();
         #[allow(clippy::wildcard_in_or_patterns)]
@@ -917,7 +1110,7 @@ mod ffi {
     extern "Rust" {
 
         #[namespace = "concrete_optimizer::v0"]
-        fn optimize_bootstrap(precision: u64, noise_factor: f64, options: Options) -> Solution;
+        fn optimize_bootstrap(precision: u64, noise_factor: f64, options: &Options) -> Solution;
 
         #[namespace = "concrete_optimizer::utils"]
         fn convert_to_dag_solution(solution: &Solution) -> DagSolution;
@@ -951,7 +1144,7 @@ mod ffi {
 
         #[namespace = "concrete_optimizer::utils"]
         fn get_noise_br(
-            options: Options,
+            options: &Options,
             log2_polynomial_size: u64,
             glwe_dimension: u64,
             lwe_dim: u64,
@@ -1045,7 +1238,7 @@ mod ffi {
 
         unsafe fn tag_operator_as_output(self: &mut DagBuilder<'_>, op: OperatorIndex);
 
-        fn optimize(self: &Dag, options: Options) -> DagSolution;
+        fn optimize(self: &Dag, options: &Options) -> DagSolution;
 
         unsafe fn add_composition<'a>(
             self: &mut Dag,
@@ -1073,7 +1266,7 @@ mod ffi {
 
         fn get_circuit_count(self: &Dag) -> usize;
 
-        fn optimize_multi(self: &Dag, options: Options) -> CircuitSolution;
+        fn optimize_multi(self: &Dag, options: &Options) -> CircuitSolution;
 
         fn get_input_indices(self: &Dag) -> Vec<OperatorIndex>;
 
@@ -1143,7 +1336,36 @@ mod ffi {
     }
 
     #[namespace = "concrete_optimizer"]
-    #[derive(Debug, Clone, Copy)]
+    #[derive(Debug, Clone)]
+    pub struct GlweParameterRestrictions {
+        pub log2_polynomial_size_min: SharedPtr<u64>,
+        pub log2_polynomial_size_max: SharedPtr<u64>,
+        pub glwe_dimension_min: SharedPtr<u64>,
+        pub glwe_dimension_max: SharedPtr<u64>,
+    }
+
+    #[namespace = "concrete_optimizer"]
+    #[derive(Debug, Clone)]
+    pub struct DecompositionParameterRestrictions {
+        pub log2_base_min: SharedPtr<u64>,
+        pub log2_base_max: SharedPtr<u64>,
+        pub level_min: SharedPtr<u64>,
+        pub level_max: SharedPtr<u64>,
+    }
+
+    #[namespace = "concrete_optimizer"]
+    #[derive(Debug, Clone)]
+    pub struct ParameterRestrictions {
+        pub glwe_pbs: GlweParameterRestrictions,
+        pub free_glwe: GlweParameterRestrictions,
+        pub br_decomposition: DecompositionParameterRestrictions,
+        pub ks_decomposition: DecompositionParameterRestrictions,
+        pub free_lwe_min: SharedPtr<u64>,
+        pub free_lwe_max: SharedPtr<u64>,
+    }
+
+    #[namespace = "concrete_optimizer"]
+    #[derive(Debug, Clone)]
     pub struct Options {
         pub security_level: u64,
         pub maximum_acceptable_error_probability: f64,
@@ -1155,6 +1377,7 @@ mod ffi {
         pub cache_on_disk: bool,
         pub ciphertext_modulus_log: u32,
         pub fft_precision: u32,
+        pub parameter_restrictions: ParameterRestrictions,
     }
 
     #[namespace = "concrete_optimizer::dag"]
@@ -1266,7 +1489,7 @@ mod ffi {
     }
 }
 
-fn processing_unit(options: ffi::Options) -> ProcessingUnit {
+fn processing_unit(options: &ffi::Options) -> ProcessingUnit {
     if options.use_gpu_constraints {
         config::ProcessingUnit::Gpu {
             pbs_type: config::GpuPbsType::Amortized,
diff --git a/compilers/concrete-optimizer/concrete-optimizer-cpp/src/cpp/concrete-optimizer.cpp b/compilers/concrete-optimizer/concrete-optimizer-cpp/src/cpp/concrete-optimizer.cpp
index 4936973333..7851f883f0 100644
--- a/compilers/concrete-optimizer/concrete-optimizer-cpp/src/cpp/concrete-optimizer.cpp
+++ b/compilers/concrete-optimizer/concrete-optimizer-cpp/src/cpp/concrete-optimizer.cpp
@@ -5,6 +5,7 @@
 #include <cstdint>
 #include <initializer_list>
 #include <iterator>
+#include <memory>
 #include <new>
 #include <stdexcept>
 #include <string>
@@ -948,6 +949,9 @@ namespace concrete_optimizer {
   struct Weights;
   enum class Encoding : ::std::uint8_t;
   enum class MultiParamStrategy : ::std::uint8_t;
+  struct GlweParameterRestrictions;
+  struct DecompositionParameterRestrictions;
+  struct ParameterRestrictions;
   struct Options;
   namespace dag {
     struct OperatorIndex;
@@ -973,11 +977,11 @@ namespace concrete_optimizer {
 struct Dag final : public ::rust::Opaque {
   ::rust::Box<::concrete_optimizer::DagBuilder> builder(::rust::String circuit) noexcept;
   ::rust::String dump() const noexcept;
-  ::concrete_optimizer::dag::DagSolution optimize(::concrete_optimizer::Options options) const noexcept;
+  ::concrete_optimizer::dag::DagSolution optimize(::concrete_optimizer::Options const &options) const noexcept;
   void add_composition(::std::string const &from_func, ::std::size_t from_pos, ::std::string const &to_func, ::std::size_t to_pos) noexcept;
   void add_all_compositions() noexcept;
   ::std::size_t get_circuit_count() const noexcept;
-  ::concrete_optimizer::dag::CircuitSolution optimize_multi(::concrete_optimizer::Options options) const noexcept;
+  ::concrete_optimizer::dag::CircuitSolution optimize_multi(::concrete_optimizer::Options const &options) const noexcept;
   ::rust::Vec<::concrete_optimizer::dag::OperatorIndex> get_input_indices() const noexcept;
   ::rust::Vec<::concrete_optimizer::dag::OperatorIndex> get_output_indices() const noexcept;
   ~Dag() = delete;
@@ -1136,6 +1140,44 @@ enum class MultiParamStrategy : ::std::uint8_t {
 };
 #endif // CXXBRIDGE1_ENUM_concrete_optimizer$MultiParamStrategy
 
+#ifndef CXXBRIDGE1_STRUCT_concrete_optimizer$GlweParameterRestrictions
+#define CXXBRIDGE1_STRUCT_concrete_optimizer$GlweParameterRestrictions
+struct GlweParameterRestrictions final {
+  ::std::shared_ptr<::std::uint64_t> log2_polynomial_size_min;
+  ::std::shared_ptr<::std::uint64_t> log2_polynomial_size_max;
+  ::std::shared_ptr<::std::uint64_t> glwe_dimension_min;
+  ::std::shared_ptr<::std::uint64_t> glwe_dimension_max;
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_concrete_optimizer$GlweParameterRestrictions
+
+#ifndef CXXBRIDGE1_STRUCT_concrete_optimizer$DecompositionParameterRestrictions
+#define CXXBRIDGE1_STRUCT_concrete_optimizer$DecompositionParameterRestrictions
+struct DecompositionParameterRestrictions final {
+  ::std::shared_ptr<::std::uint64_t> log2_base_min;
+  ::std::shared_ptr<::std::uint64_t> log2_base_max;
+  ::std::shared_ptr<::std::uint64_t> level_min;
+  ::std::shared_ptr<::std::uint64_t> level_max;
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_concrete_optimizer$DecompositionParameterRestrictions
+
+#ifndef CXXBRIDGE1_STRUCT_concrete_optimizer$ParameterRestrictions
+#define CXXBRIDGE1_STRUCT_concrete_optimizer$ParameterRestrictions
+struct ParameterRestrictions final {
+  ::concrete_optimizer::GlweParameterRestrictions glwe_pbs;
+  ::concrete_optimizer::GlweParameterRestrictions free_glwe;
+  ::concrete_optimizer::DecompositionParameterRestrictions br_decomposition;
+  ::concrete_optimizer::DecompositionParameterRestrictions ks_decomposition;
+  ::std::shared_ptr<::std::uint64_t> free_lwe_min;
+  ::std::shared_ptr<::std::uint64_t> free_lwe_max;
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_concrete_optimizer$ParameterRestrictions
+
 #ifndef CXXBRIDGE1_STRUCT_concrete_optimizer$Options
 #define CXXBRIDGE1_STRUCT_concrete_optimizer$Options
 struct Options final {
@@ -1149,6 +1191,7 @@ struct Options final {
   bool cache_on_disk;
   ::std::uint32_t ciphertext_modulus_log;
   ::std::uint32_t fft_precision;
+  ::concrete_optimizer::ParameterRestrictions parameter_restrictions;
 
   using IsRelocatable = ::std::true_type;
 };
@@ -1305,7 +1348,7 @@ struct CircuitSolution final {
 
 namespace v0 {
 extern "C" {
-::concrete_optimizer::v0::Solution concrete_optimizer$v0$cxxbridge1$optimize_bootstrap(::std::uint64_t precision, double noise_factor, ::concrete_optimizer::Options options) noexcept;
+::concrete_optimizer::v0::Solution concrete_optimizer$v0$cxxbridge1$optimize_bootstrap(::std::uint64_t precision, double noise_factor, ::concrete_optimizer::Options const &options) noexcept;
 } // extern "C"
 } // namespace v0
 
@@ -1336,7 +1379,7 @@ ::concrete_optimizer::Location *concrete_optimizer$utils$cxxbridge1$location_fro
 
 ::concrete_optimizer::ExternalPartition *concrete_optimizer$utils$cxxbridge1$get_external_partition(::rust::String *name, ::std::uint64_t log2_polynomial_size, ::std::uint64_t glwe_dimension, ::std::uint64_t internal_dim, double max_variance, double variance) noexcept;
 
-double concrete_optimizer$utils$cxxbridge1$get_noise_br(::concrete_optimizer::Options options, ::std::uint64_t log2_polynomial_size, ::std::uint64_t glwe_dimension, ::std::uint64_t lwe_dim, ::std::uint64_t pbs_level, ::std::uint64_t pbs_log2_base) noexcept;
+double concrete_optimizer$utils$cxxbridge1$get_noise_br(::concrete_optimizer::Options const &options, ::std::uint64_t log2_polynomial_size, ::std::uint64_t glwe_dimension, ::std::uint64_t lwe_dim, ::std::uint64_t pbs_level, ::std::uint64_t pbs_log2_base) noexcept;
 } // extern "C"
 } // namespace utils
 
@@ -1375,7 +1418,7 @@ ::concrete_optimizer::dag::OperatorIndex concrete_optimizer$cxxbridge1$DagBuilde
 
 void concrete_optimizer$cxxbridge1$DagBuilder$tag_operator_as_output(::concrete_optimizer::DagBuilder &self, ::concrete_optimizer::dag::OperatorIndex op) noexcept;
 
-void concrete_optimizer$cxxbridge1$Dag$optimize(::concrete_optimizer::Dag const &self, ::concrete_optimizer::Options options, ::concrete_optimizer::dag::DagSolution *return$) noexcept;
+void concrete_optimizer$cxxbridge1$Dag$optimize(::concrete_optimizer::Dag const &self, ::concrete_optimizer::Options const &options, ::concrete_optimizer::dag::DagSolution *return$) noexcept;
 
 void concrete_optimizer$cxxbridge1$Dag$add_composition(::concrete_optimizer::Dag &self, ::std::string const &from_func, ::std::size_t from_pos, ::std::string const &to_func, ::std::size_t to_pos) noexcept;
 
@@ -1406,7 +1449,7 @@ ::concrete_optimizer::Weights *concrete_optimizer$weights$cxxbridge1$number(::st
 extern "C" {
 ::std::size_t concrete_optimizer$cxxbridge1$Dag$get_circuit_count(::concrete_optimizer::Dag const &self) noexcept;
 
-void concrete_optimizer$cxxbridge1$Dag$optimize_multi(::concrete_optimizer::Dag const &self, ::concrete_optimizer::Options options, ::concrete_optimizer::dag::CircuitSolution *return$) noexcept;
+void concrete_optimizer$cxxbridge1$Dag$optimize_multi(::concrete_optimizer::Dag const &self, ::concrete_optimizer::Options const &options, ::concrete_optimizer::dag::CircuitSolution *return$) noexcept;
 
 void concrete_optimizer$cxxbridge1$Dag$get_input_indices(::concrete_optimizer::Dag const &self, ::rust::Vec<::concrete_optimizer::dag::OperatorIndex> *return$) noexcept;
 
@@ -1416,7 +1459,7 @@ ::std::uint64_t concrete_optimizer$cxxbridge1$NO_KEY_ID() noexcept;
 } // extern "C"
 
 namespace v0 {
-::concrete_optimizer::v0::Solution optimize_bootstrap(::std::uint64_t precision, double noise_factor, ::concrete_optimizer::Options options) noexcept {
+::concrete_optimizer::v0::Solution optimize_bootstrap(::std::uint64_t precision, double noise_factor, ::concrete_optimizer::Options const &options) noexcept {
   return concrete_optimizer$v0$cxxbridge1$optimize_bootstrap(precision, noise_factor, options);
 }
 } // namespace v0
@@ -1480,7 +1523,7 @@ ::rust::Box<::concrete_optimizer::ExternalPartition> get_external_partition(::ru
   return ::rust::Box<::concrete_optimizer::ExternalPartition>::from_raw(concrete_optimizer$utils$cxxbridge1$get_external_partition(&name, log2_polynomial_size, glwe_dimension, internal_dim, max_variance, variance));
 }
 
-double get_noise_br(::concrete_optimizer::Options options, ::std::uint64_t log2_polynomial_size, ::std::uint64_t glwe_dimension, ::std::uint64_t lwe_dim, ::std::uint64_t pbs_level, ::std::uint64_t pbs_log2_base) noexcept {
+double get_noise_br(::concrete_optimizer::Options const &options, ::std::uint64_t log2_polynomial_size, ::std::uint64_t glwe_dimension, ::std::uint64_t lwe_dim, ::std::uint64_t pbs_level, ::std::uint64_t pbs_log2_base) noexcept {
   return concrete_optimizer$utils$cxxbridge1$get_noise_br(options, log2_polynomial_size, glwe_dimension, lwe_dim, pbs_level, pbs_log2_base);
 }
 } // namespace utils
@@ -1551,7 +1594,7 @@ void DagBuilder::tag_operator_as_output(::concrete_optimizer::dag::OperatorIndex
   concrete_optimizer$cxxbridge1$DagBuilder$tag_operator_as_output(*this, op);
 }
 
-::concrete_optimizer::dag::DagSolution Dag::optimize(::concrete_optimizer::Options options) const noexcept {
+::concrete_optimizer::dag::DagSolution Dag::optimize(::concrete_optimizer::Options const &options) const noexcept {
   ::rust::MaybeUninit<::concrete_optimizer::dag::DagSolution> return$;
   concrete_optimizer$cxxbridge1$Dag$optimize(*this, options, &return$.value);
   return ::std::move(return$.value);
@@ -1601,7 +1644,7 @@ ::std::size_t Dag::get_circuit_count() const noexcept {
   return concrete_optimizer$cxxbridge1$Dag$get_circuit_count(*this);
 }
 
-::concrete_optimizer::dag::CircuitSolution Dag::optimize_multi(::concrete_optimizer::Options options) const noexcept {
+::concrete_optimizer::dag::CircuitSolution Dag::optimize_multi(::concrete_optimizer::Options const &options) const noexcept {
   ::rust::MaybeUninit<::concrete_optimizer::dag::CircuitSolution> return$;
   concrete_optimizer$cxxbridge1$Dag$optimize_multi(*this, options, &return$.value);
   return ::std::move(return$.value);
diff --git a/compilers/concrete-optimizer/concrete-optimizer-cpp/src/cpp/concrete-optimizer.hpp b/compilers/concrete-optimizer/concrete-optimizer-cpp/src/cpp/concrete-optimizer.hpp
index 9f993b9f92..3f56c09391 100644
--- a/compilers/concrete-optimizer/concrete-optimizer-cpp/src/cpp/concrete-optimizer.hpp
+++ b/compilers/concrete-optimizer/concrete-optimizer-cpp/src/cpp/concrete-optimizer.hpp
@@ -6,6 +6,7 @@
 #include <cstdint>
 #include <initializer_list>
 #include <iterator>
+#include <memory>
 #include <new>
 #include <stdexcept>
 #include <string>
@@ -929,6 +930,9 @@ namespace concrete_optimizer {
   struct Weights;
   enum class Encoding : ::std::uint8_t;
   enum class MultiParamStrategy : ::std::uint8_t;
+  struct GlweParameterRestrictions;
+  struct DecompositionParameterRestrictions;
+  struct ParameterRestrictions;
   struct Options;
   namespace dag {
     struct OperatorIndex;
@@ -954,11 +958,11 @@ namespace concrete_optimizer {
 struct Dag final : public ::rust::Opaque {
   ::rust::Box<::concrete_optimizer::DagBuilder> builder(::rust::String circuit) noexcept;
   ::rust::String dump() const noexcept;
-  ::concrete_optimizer::dag::DagSolution optimize(::concrete_optimizer::Options options) const noexcept;
+  ::concrete_optimizer::dag::DagSolution optimize(::concrete_optimizer::Options const &options) const noexcept;
   void add_composition(::std::string const &from_func, ::std::size_t from_pos, ::std::string const &to_func, ::std::size_t to_pos) noexcept;
   void add_all_compositions() noexcept;
   ::std::size_t get_circuit_count() const noexcept;
-  ::concrete_optimizer::dag::CircuitSolution optimize_multi(::concrete_optimizer::Options options) const noexcept;
+  ::concrete_optimizer::dag::CircuitSolution optimize_multi(::concrete_optimizer::Options const &options) const noexcept;
   ::rust::Vec<::concrete_optimizer::dag::OperatorIndex> get_input_indices() const noexcept;
   ::rust::Vec<::concrete_optimizer::dag::OperatorIndex> get_output_indices() const noexcept;
   ~Dag() = delete;
@@ -1117,6 +1121,44 @@ enum class MultiParamStrategy : ::std::uint8_t {
 };
 #endif // CXXBRIDGE1_ENUM_concrete_optimizer$MultiParamStrategy
 
+#ifndef CXXBRIDGE1_STRUCT_concrete_optimizer$GlweParameterRestrictions
+#define CXXBRIDGE1_STRUCT_concrete_optimizer$GlweParameterRestrictions
+struct GlweParameterRestrictions final {
+  ::std::shared_ptr<::std::uint64_t> log2_polynomial_size_min;
+  ::std::shared_ptr<::std::uint64_t> log2_polynomial_size_max;
+  ::std::shared_ptr<::std::uint64_t> glwe_dimension_min;
+  ::std::shared_ptr<::std::uint64_t> glwe_dimension_max;
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_concrete_optimizer$GlweParameterRestrictions
+
+#ifndef CXXBRIDGE1_STRUCT_concrete_optimizer$DecompositionParameterRestrictions
+#define CXXBRIDGE1_STRUCT_concrete_optimizer$DecompositionParameterRestrictions
+struct DecompositionParameterRestrictions final {
+  ::std::shared_ptr<::std::uint64_t> log2_base_min;
+  ::std::shared_ptr<::std::uint64_t> log2_base_max;
+  ::std::shared_ptr<::std::uint64_t> level_min;
+  ::std::shared_ptr<::std::uint64_t> level_max;
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_concrete_optimizer$DecompositionParameterRestrictions
+
+#ifndef CXXBRIDGE1_STRUCT_concrete_optimizer$ParameterRestrictions
+#define CXXBRIDGE1_STRUCT_concrete_optimizer$ParameterRestrictions
+struct ParameterRestrictions final {
+  ::concrete_optimizer::GlweParameterRestrictions glwe_pbs;
+  ::concrete_optimizer::GlweParameterRestrictions free_glwe;
+  ::concrete_optimizer::DecompositionParameterRestrictions br_decomposition;
+  ::concrete_optimizer::DecompositionParameterRestrictions ks_decomposition;
+  ::std::shared_ptr<::std::uint64_t> free_lwe_min;
+  ::std::shared_ptr<::std::uint64_t> free_lwe_max;
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_concrete_optimizer$ParameterRestrictions
+
 #ifndef CXXBRIDGE1_STRUCT_concrete_optimizer$Options
 #define CXXBRIDGE1_STRUCT_concrete_optimizer$Options
 struct Options final {
@@ -1130,6 +1172,7 @@ struct Options final {
   bool cache_on_disk;
   ::std::uint32_t ciphertext_modulus_log;
   ::std::uint32_t fft_precision;
+  ::concrete_optimizer::ParameterRestrictions parameter_restrictions;
 
   using IsRelocatable = ::std::true_type;
 };
@@ -1285,7 +1328,7 @@ struct CircuitSolution final {
 } // namespace dag
 
 namespace v0 {
-::concrete_optimizer::v0::Solution optimize_bootstrap(::std::uint64_t precision, double noise_factor, ::concrete_optimizer::Options options) noexcept;
+::concrete_optimizer::v0::Solution optimize_bootstrap(::std::uint64_t precision, double noise_factor, ::concrete_optimizer::Options const &options) noexcept;
 } // namespace v0
 
 namespace utils {
@@ -1299,7 +1342,7 @@ ::rust::Box<::concrete_optimizer::Location> location_from_string(::rust::Str str
 
 ::rust::Box<::concrete_optimizer::ExternalPartition> get_external_partition(::rust::String name, ::std::uint64_t log2_polynomial_size, ::std::uint64_t glwe_dimension, ::std::uint64_t internal_dim, double max_variance, double variance) noexcept;
 
-double get_noise_br(::concrete_optimizer::Options options, ::std::uint64_t log2_polynomial_size, ::std::uint64_t glwe_dimension, ::std::uint64_t lwe_dim, ::std::uint64_t pbs_level, ::std::uint64_t pbs_log2_base) noexcept;
+double get_noise_br(::concrete_optimizer::Options const &options, ::std::uint64_t log2_polynomial_size, ::std::uint64_t glwe_dimension, ::std::uint64_t lwe_dim, ::std::uint64_t pbs_level, ::std::uint64_t pbs_log2_base) noexcept;
 } // namespace utils
 
 namespace dag {
diff --git a/compilers/concrete-optimizer/concrete-optimizer/src/global_parameters.rs b/compilers/concrete-optimizer/concrete-optimizer/src/global_parameters.rs
index c9e4d46969..910d5c35ae 100644
--- a/compilers/concrete-optimizer/concrete-optimizer/src/global_parameters.rs
+++ b/compilers/concrete-optimizer/concrete-optimizer/src/global_parameters.rs
@@ -14,18 +14,23 @@ struct ParameterCount {
 pub struct ParameterDomains {
     // move next comment to pareto ranges definition
     // TODO: verify if pareto optimal parameters depends on precisions
-    pub glwe_pbs_constrained: GlweParameterRanges,
+    pub glwe_pbs_constrained_cpu: GlweParameterRanges,
+    pub glwe_pbs_constrained_gpu: GlweParameterRanges,
     pub free_glwe: GlweParameterRanges,
     pub br_decomposition: BrDecompositionParameterRanges,
     pub ks_decomposition: KsDecompositionParameterRanges,
     pub free_lwe: Range,
 }
 
-pub const DEFAUT_DOMAINS: ParameterDomains = ParameterDomains {
-    glwe_pbs_constrained: GlweParameterRanges {
+pub const DEFAULT_DOMAINS: ParameterDomains = ParameterDomains {
+    glwe_pbs_constrained_cpu: GlweParameterRanges {
         log2_polynomial_size: Range { start: 8, end: 18 },
         glwe_dimension: Range { start: 1, end: 7 },
     },
+    glwe_pbs_constrained_gpu: GlweParameterRanges {
+        log2_polynomial_size: Range { start: 8, end: 14 },
+        glwe_dimension: Range { start: 1, end: 7 },
+    },
     free_glwe: GlweParameterRanges {
         log2_polynomial_size: Range { start: 0, end: 1 },
         glwe_dimension: Range {
diff --git a/compilers/concrete-optimizer/concrete-optimizer/src/optimization/config.rs b/compilers/concrete-optimizer/concrete-optimizer/src/optimization/config.rs
index d23c255b77..56122f6b29 100644
--- a/compilers/concrete-optimizer/concrete-optimizer/src/optimization/config.rs
+++ b/compilers/concrete-optimizer/concrete-optimizer/src/optimization/config.rs
@@ -1,7 +1,7 @@
 use crate::computing_cost::complexity_model::ComplexityModel;
 use crate::config;
 use crate::config::GpuPbsType;
-use crate::global_parameters::{Range, DEFAUT_DOMAINS};
+use crate::global_parameters::{ParameterDomains, Range};
 
 #[derive(Clone, Copy, Debug)]
 pub struct NoiseBoundConfig {
@@ -20,6 +20,14 @@ pub struct Config<'a> {
     pub complexity_model: &'a dyn ComplexityModel,
 }
 
+#[derive(Clone, Copy, Debug)]
+pub struct ParameterRestrictions {
+    pub log2_polynomial_size_min: u64,
+    pub log2_polynomial_size_max: u64,
+    pub glwe_dimension_min: u64,
+    pub glwe_dimension_max: u64,
+}
+
 #[derive(Clone, Debug)]
 pub struct SearchSpace {
     pub glwe_log_polynomial_sizes: Vec<u64>,
@@ -29,14 +37,17 @@ pub struct SearchSpace {
 }
 
 impl SearchSpace {
-    pub fn default_cpu() -> Self {
-        let glwe_log_polynomial_sizes: Vec<u64> = DEFAUT_DOMAINS
-            .glwe_pbs_constrained
+    pub fn default_cpu(parameter_domains: ParameterDomains) -> Self {
+        let glwe_log_polynomial_sizes: Vec<u64> = parameter_domains
+            .glwe_pbs_constrained_cpu
             .log2_polynomial_size
             .as_vec();
-        let glwe_dimensions: Vec<u64> = DEFAUT_DOMAINS.glwe_pbs_constrained.glwe_dimension.as_vec();
-        let internal_lwe_dimensions: Vec<u64> = DEFAUT_DOMAINS.free_glwe.glwe_dimension.as_vec();
-        let levelled_only_lwe_dimensions = DEFAUT_DOMAINS.free_lwe;
+        let glwe_dimensions: Vec<u64> = parameter_domains
+            .glwe_pbs_constrained_cpu
+            .glwe_dimension
+            .as_vec();
+        let internal_lwe_dimensions: Vec<u64> = parameter_domains.free_glwe.glwe_dimension.as_vec();
+        let levelled_only_lwe_dimensions = parameter_domains.free_lwe;
         Self {
             glwe_log_polynomial_sizes,
             glwe_dimensions,
@@ -45,14 +56,20 @@ impl SearchSpace {
         }
     }
 
-    pub fn default_gpu_lowlat() -> Self {
+    pub fn default_gpu_lowlat(parameter_domains: ParameterDomains) -> Self {
         // See backends/concrete_cuda/implementation/src/bootstrap_low_latency.cu
-        let glwe_log_polynomial_sizes: Vec<u64> = (8..=14).collect();
+        let glwe_log_polynomial_sizes: Vec<u64> = parameter_domains
+            .glwe_pbs_constrained_gpu
+            .log2_polynomial_size
+            .as_vec();
 
-        let glwe_dimensions: Vec<u64> = DEFAUT_DOMAINS.glwe_pbs_constrained.glwe_dimension.as_vec();
+        let glwe_dimensions: Vec<u64> = parameter_domains
+            .glwe_pbs_constrained_gpu
+            .glwe_dimension
+            .as_vec();
 
-        let internal_lwe_dimensions: Vec<u64> = DEFAUT_DOMAINS.free_glwe.glwe_dimension.as_vec();
-        let levelled_only_lwe_dimensions = DEFAUT_DOMAINS.free_lwe;
+        let internal_lwe_dimensions: Vec<u64> = parameter_domains.free_glwe.glwe_dimension.as_vec();
+        let levelled_only_lwe_dimensions = parameter_domains.free_lwe;
         Self {
             glwe_log_polynomial_sizes,
             glwe_dimensions,
@@ -61,14 +78,20 @@ impl SearchSpace {
         }
     }
 
-    pub fn default_gpu_amortized() -> Self {
+    pub fn default_gpu_amortized(parameter_domains: ParameterDomains) -> Self {
         // See backends/concrete_cuda/implementation/src/bootstrap_amortized.cu
-        let glwe_log_polynomial_sizes: Vec<u64> = (8..=14).collect();
+        let glwe_log_polynomial_sizes: Vec<u64> = parameter_domains
+            .glwe_pbs_constrained_gpu
+            .log2_polynomial_size
+            .as_vec();
 
-        let glwe_dimensions: Vec<u64> = DEFAUT_DOMAINS.glwe_pbs_constrained.glwe_dimension.as_vec();
+        let glwe_dimensions: Vec<u64> = parameter_domains
+            .glwe_pbs_constrained_gpu
+            .glwe_dimension
+            .as_vec();
 
-        let internal_lwe_dimensions: Vec<u64> = DEFAUT_DOMAINS.free_glwe.glwe_dimension.as_vec();
-        let levelled_only_lwe_dimensions = DEFAUT_DOMAINS.free_lwe;
+        let internal_lwe_dimensions: Vec<u64> = parameter_domains.free_glwe.glwe_dimension.as_vec();
+        let levelled_only_lwe_dimensions = parameter_domains.free_lwe;
         Self {
             glwe_log_polynomial_sizes,
             glwe_dimensions,
@@ -76,17 +99,20 @@ impl SearchSpace {
             levelled_only_lwe_dimensions,
         }
     }
-    pub fn default(processing_unit: config::ProcessingUnit) -> Self {
+    pub fn default(
+        processing_unit: config::ProcessingUnit,
+        parameter_domains: ParameterDomains,
+    ) -> Self {
         match processing_unit {
-            config::ProcessingUnit::Cpu => Self::default_cpu(),
+            config::ProcessingUnit::Cpu => Self::default_cpu(parameter_domains),
             config::ProcessingUnit::Gpu {
                 pbs_type: GpuPbsType::Amortized,
                 ..
-            } => Self::default_gpu_amortized(),
+            } => Self::default_gpu_amortized(parameter_domains),
             config::ProcessingUnit::Gpu {
                 pbs_type: GpuPbsType::Lowlat,
                 ..
-            } => Self::default_gpu_lowlat(),
+            } => Self::default_gpu_lowlat(parameter_domains),
         }
     }
 }
diff --git a/compilers/concrete-optimizer/concrete-optimizer/src/optimization/dag/solo_key/optimize.rs b/compilers/concrete-optimizer/concrete-optimizer/src/optimization/dag/solo_key/optimize.rs
index 265c45b688..2a74924758 100644
--- a/compilers/concrete-optimizer/concrete-optimizer/src/optimization/dag/solo_key/optimize.rs
+++ b/compilers/concrete-optimizer/concrete-optimizer/src/optimization/dag/solo_key/optimize.rs
@@ -428,6 +428,7 @@ pub(crate) mod tests {
     use crate::computing_cost::cpu::CpuComplexity;
     use crate::config;
     use crate::dag::operator::{FunctionTable, Precision, Shape, Weights};
+    use crate::global_parameters::DEFAULT_DOMAINS;
     use crate::noise_estimator::p_error::repeat_p_error;
     use crate::optimization::config::SearchSpace;
     use crate::optimization::{atomic_pattern, decomposition};
@@ -479,7 +480,7 @@ pub(crate) mod tests {
             complexity_model: &CpuComplexity::default(),
         };
 
-        let search_space = SearchSpace::default_cpu();
+        let search_space = SearchSpace::default_cpu(DEFAULT_DOMAINS);
 
         super::optimize(dag, config, &search_space, &SHARED_CACHES)
     }
@@ -511,7 +512,7 @@ pub(crate) mod tests {
     fn v0_parameter_ref(precision: u64, weight: u64, times: &mut Times) {
         let processing_unit = config::ProcessingUnit::Cpu;
 
-        let search_space = SearchSpace::default(processing_unit);
+        let search_space = SearchSpace::default(processing_unit, DEFAULT_DOMAINS);
 
         let sum_size = 1;
 
@@ -609,7 +610,7 @@ pub(crate) mod tests {
             assert_f64_eq(square(weight) as f64, constraint.pareto_in_lut[0].lut_coeff);
         }
 
-        let search_space = SearchSpace::default(processing_unit);
+        let search_space = SearchSpace::default(processing_unit, DEFAULT_DOMAINS);
 
         let config = Config {
             security_level,
diff --git a/compilers/concrete-optimizer/v0-parameters/src/lib.rs b/compilers/concrete-optimizer/v0-parameters/src/lib.rs
index e3321efd53..1480909347 100644
--- a/compilers/concrete-optimizer/v0-parameters/src/lib.rs
+++ b/compilers/concrete-optimizer/v0-parameters/src/lib.rs
@@ -11,7 +11,7 @@
 use clap::Parser;
 use concrete_optimizer::computing_cost::cpu::CpuComplexity;
 use concrete_optimizer::config;
-use concrete_optimizer::global_parameters::DEFAUT_DOMAINS;
+use concrete_optimizer::global_parameters::DEFAULT_DOMAINS;
 use concrete_optimizer::optimization::config::{Config, SearchSpace};
 use concrete_optimizer::optimization::dag::solo_key::optimize::{self as optimize_dag};
 use concrete_optimizer::optimization::dag::solo_key::optimize_generic::Solution;
@@ -24,14 +24,18 @@ use rayon_cond::CondIterator;
 use std::io::Write;
 
 pub const _4_SIGMA: f64 = 1.0 - 0.999_936_657_516;
-const MIN_LOG_POLY_SIZE: u64 = DEFAUT_DOMAINS
-    .glwe_pbs_constrained
+const MIN_LOG_POLY_SIZE: u64 = DEFAULT_DOMAINS
+    .glwe_pbs_constrained_cpu
     .log2_polynomial_size
     .start;
-const MAX_LOG_POLY_SIZE: u64 = DEFAUT_DOMAINS.glwe_pbs_constrained.log2_polynomial_size.end - 1;
-pub const MAX_GLWE_DIM: u64 = DEFAUT_DOMAINS.glwe_pbs_constrained.glwe_dimension.end - 1;
-pub const MIN_LWE_DIM: u64 = DEFAUT_DOMAINS.free_glwe.glwe_dimension.start;
-pub const MAX_LWE_DIM: u64 = DEFAUT_DOMAINS.free_glwe.glwe_dimension.end - 1;
+const MAX_LOG_POLY_SIZE: u64 = DEFAULT_DOMAINS
+    .glwe_pbs_constrained_cpu
+    .log2_polynomial_size
+    .end
+    - 1;
+pub const MAX_GLWE_DIM: u64 = DEFAULT_DOMAINS.glwe_pbs_constrained_cpu.glwe_dimension.end - 1;
+pub const MIN_LWE_DIM: u64 = DEFAULT_DOMAINS.free_glwe.glwe_dimension.start;
+pub const MAX_LWE_DIM: u64 = DEFAULT_DOMAINS.free_glwe.glwe_dimension.end - 1;
 
 /// Find parameters for classical PBS and new WoP-PBS
 #[derive(Parser, Debug)]
@@ -105,7 +109,7 @@ pub fn all_results(args: &Args) -> Vec<Vec<Option<Solution>>> {
         glwe_log_polynomial_sizes: (args.min_log_poly_size..=args.max_log_poly_size).collect(),
         glwe_dimensions: (args.min_glwe_dim..=args.max_glwe_dim).collect(),
         internal_lwe_dimensions: (args.min_intern_lwe_dim..=args.max_intern_lwe_dim).collect(),
-        levelled_only_lwe_dimensions: DEFAUT_DOMAINS.free_lwe,
+        levelled_only_lwe_dimensions: DEFAULT_DOMAINS.free_lwe,
     };
 
     let precisions = args.min_precision..=args.max_precision;


Test patch:
diff --git a/compilers/concrete-optimizer/concrete-optimizer-cpp/tests/src/main.cpp b/compilers/concrete-optimizer/concrete-optimizer-cpp/tests/src/main.cpp
index 3269504aff..cde2ee8e10 100644
--- a/compilers/concrete-optimizer/concrete-optimizer-cpp/tests/src/main.cpp
+++ b/compilers/concrete-optimizer/concrete-optimizer-cpp/tests/src/main.cpp
@@ -30,6 +30,35 @@ concrete_optimizer::Options default_options() {
       .cache_on_disk = true,
       .ciphertext_modulus_log = CIPHERTEXT_MODULUS_LOG,
       .fft_precision = 53,
+      .parameter_restrictions = concrete_optimizer::ParameterRestrictions{
+          .glwe_pbs = concrete_optimizer::GlweParameterRestrictions{
+              .log2_polynomial_size_min = {},
+              .log2_polynomial_size_max = {},
+              .glwe_dimension_min = {},
+              .glwe_dimension_max = {},
+          },
+          .free_glwe = concrete_optimizer::GlweParameterRestrictions{
+                        .log2_polynomial_size_min = {},
+                        .log2_polynomial_size_max = {},
+                        .glwe_dimension_min = {},
+                        .glwe_dimension_max = {},
+        },
+
+          .br_decomposition = concrete_optimizer::DecompositionParameterRestrictions{
+              .log2_base_min = {},
+              .log2_base_max = {},
+              .level_min = {},
+              .level_max = {},
+          },
+          .ks_decomposition = concrete_optimizer::DecompositionParameterRestrictions{
+                        .log2_base_min = {},
+                        .log2_base_max = {},
+                        .level_min = {},
+                        .level_max = {},
+                    },
+         .free_lwe_min = {},
+         .free_lwe_max = {}
+      }
   };
 }
 
diff --git a/compilers/concrete-optimizer/concrete-optimizer/src/optimization/dag/multi_parameters/optimize/tests.rs b/compilers/concrete-optimizer/concrete-optimizer/src/optimization/dag/multi_parameters/optimize/tests.rs
index 2d357affb8..be91c3e252 100644
--- a/compilers/concrete-optimizer/concrete-optimizer/src/optimization/dag/multi_parameters/optimize/tests.rs
+++ b/compilers/concrete-optimizer/concrete-optimizer/src/optimization/dag/multi_parameters/optimize/tests.rs
@@ -7,6 +7,7 @@ use super::*;
 use crate::computing_cost::cpu::CpuComplexity;
 use crate::dag::operator::{FunctionTable, LevelledComplexity, Shape};
 use crate::dag::unparametrized;
+use crate::global_parameters::DEFAULT_DOMAINS;
 use crate::optimization::dag::multi_parameters::partitionning::tests::{
     get_tfhers_noise_br, SHARED_CACHES, TFHERS_MACRO_PARAMS,
 };
@@ -37,7 +38,7 @@ fn optimize(
     default_partition: PartitionIndex,
 ) -> Option<Parameters> {
     let config = default_config();
-    let search_space = SearchSpace::default_cpu();
+    let search_space = SearchSpace::default_cpu(DEFAULT_DOMAINS);
     super::optimize(
         dag,
         config,
@@ -614,7 +615,7 @@ fn test_levelled_only() {
     let mut dag = unparametrized::Dag::new();
     let _ = dag.add_input(22, Shape::number());
     let config = default_config();
-    let search_space = SearchSpace::default_cpu();
+    let search_space = SearchSpace::default_cpu(DEFAULT_DOMAINS);
     let sol =
         super::optimize_to_circuit_solution(&dag, config, &search_space, &SHARED_CACHES, &None);
     let sol_mono = solo_key::optimize::tests::optimize(&dag)
@@ -645,7 +646,7 @@ fn test_big_secret_key_sharing() {
         key_sharing: false,
         ..config_sharing
     };
-    let mut search_space = SearchSpace::default_cpu();
+    let mut search_space = SearchSpace::default_cpu(DEFAULT_DOMAINS);
     // eprintln!("{:?}", search_space);
     search_space.glwe_dimensions = vec![1]; // forcing big key sharing
     let sol_sharing = super::optimize_to_circuit_solution(
@@ -694,7 +695,7 @@ fn test_big_and_small_secret_key() {
         key_sharing: false,
         ..config_sharing
     };
-    let mut search_space = SearchSpace::default_cpu();
+    let mut search_space = SearchSpace::default_cpu(DEFAULT_DOMAINS);
     search_space.glwe_dimensions = vec![1]; // forcing big key sharing
     search_space.internal_lwe_dimensions = vec![768]; // forcing small key sharing
     let sol_sharing = super::optimize_to_circuit_solution(
@@ -731,7 +732,7 @@ fn test_composition_2_partitions() {
     let lut3 = dag.add_lut(lut1, FunctionTable::UNKWOWN, 3);
     let input2 = dag.add_dot([input1, lut3], [1, 1]);
     let out = dag.add_lut(input2, FunctionTable::UNKWOWN, 3);
-    let search_space = SearchSpace::default_cpu();
+    let search_space = SearchSpace::default_cpu(DEFAULT_DOMAINS);
     let normal_sol = super::optimize(
         &dag,
         default_config(),
@@ -766,7 +767,7 @@ fn test_composition_1_partition_not_composable() {
     let oup = dag.add_dot([lut1], [1 << 16]);
     let normal_config = default_config();
     let composed_config = normal_config;
-    let search_space = SearchSpace::default_cpu();
+    let search_space = SearchSpace::default_cpu(DEFAULT_DOMAINS);
     let normal_sol = super::optimize(
         &dag,
         normal_config,
@@ -791,7 +792,7 @@ fn test_composition_1_partition_not_composable() {
 #[test]
 fn test_maximal_multi() {
     let config = default_config();
-    let search_space = SearchSpace::default_cpu();
+    let search_space = SearchSpace::default_cpu(DEFAULT_DOMAINS);
     let mut dag = unparametrized::Dag::new();
     let input = dag.add_input(8, Shape::number());
     let lut1 = dag.add_lut(input, FunctionTable::UNKWOWN, 8u8);