From daf7a9955cff8db84d346bc9bec421fd28a58510 Mon Sep 17 00:00:00 2001 From: Martun Karapetyan Date: Tue, 5 Nov 2024 15:22:29 +0400 Subject: [PATCH 1/4] Add a class for lookup input constraints. --- .../include/nil/blueprint/bbf/bbf_wrapper.hpp | 4 +- .../include/nil/blueprint/bbf/generic.hpp | 829 +++++++++--------- crypto3/libs/blueprint/test/CMakeLists.txt | 1 + .../plonk/lookup_constraint.hpp | 60 +- .../plonk/lookup_constraint.hpp | 60 +- 5 files changed, 537 insertions(+), 417 deletions(-) diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp index 996ae4e31f..bbbfd32800 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp @@ -277,7 +277,7 @@ namespace nil { dynamic_lookup_tables = ct.get_dynamic_lookup_tables(); // compatibility layer: lookup constraint list - std::unordered_map, std::vector>>> + std::unordered_map, std::vector> lookup_constraints = ct.get_lookup_constraints(); std::set lookup_tables; for(const auto& [row_list, lookup_list] : lookup_constraints) { @@ -293,7 +293,7 @@ namespace nil { lookup_tables.insert(table_name); } std::size_t table_index = bp.get_reserved_indices().at(table_name); - lookup_gate.push_back({table_index,single_lookup_constraint.second}); + lookup_gate.push_back({table_index, single_lookup_constraint.second}); } auto iter = selector_to_index_map.find(row_list); diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp index ce6a38b8ff..5b1a35b0cc 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp @@ -175,96 +175,96 @@ namespace nil { template class context : public basic_context { // assignment-specific definition + public: + using TYPE = typename FieldType::value_type; + using assignment_type = assignment>; using assignment_description_type = nil::crypto3::zk::snark::plonk_table_description; using plonk_copy_constraint = crypto3::zk::snark::plonk_copy_constraint; - using lookup_constraint_type = std::pair>; + using lookup_input_constraints_type = std::vector; + using lookup_constraint_type = std::pair; using dynamic_lookup_table_container_type = std::map, row_selector<>>>; using basic_context::col_map; using basic_context::add_rows_to_description; - public: - using TYPE = typename FieldType::value_type; - using basic_context::get_col; - using basic_context::get_row; - using basic_context::is_allocated; - using basic_context::mark_allocated; - - public: - - context(assignment_type &assignment_table, std::size_t max_rows) - : basic_context(add_rows_to_description(assignment_table.get_description(), max_rows), max_rows) - , at(assignment_table) - { }; - - context(assignment_type &assignment_table, std::size_t max_rows, std::size_t row_shift) - : basic_context(add_rows_to_description(assignment_table.get_description(), max_rows), max_rows, row_shift) - , at(assignment_table) - { }; - - void allocate(TYPE &C, size_t col, size_t row, column_type t) { - if (is_allocated(col, row, t)) { - std::stringstream ss; - ss << "RE-allocation of " << t << " cell at col = " << col << ", row = " << row << ".\n"; - throw std::logic_error(ss.str()); - } - switch (t) { - // NB: we use get_col/get_row here because active area might differ - // from the entire assignment table, while col and row are intended - // to be _relative_ to the active area - case column_type::witness: at.witness(get_col(col,t), get_row(row)) = C; break; - case column_type::public_input: at.public_input(get_col(col,t), get_row(row)) = C; break; - case column_type::constant: - // constants should already be assigned at this point - if (C != at.constant(get_col(col,t), get_row(row))) { - BOOST_LOG_TRIVIAL(error) << "Constant " << C << "doesn't match previous assignment " - << at.constant(get_col(col,t), get_row(row)) << "\n"; - } - BOOST_ASSERT(C == at.constant(get_col(col,t), get_row(row))); - break; - } - mark_allocated(col, row, t); - } - - void copy_constrain(TYPE &A, TYPE &B) { - if (A != B) { - // NB: This might be an error, but we don't stop execution, - // because we want to be able to run tests-to-fail. - BOOST_LOG_TRIVIAL(warning) << "Assignment violates copy constraint (" << A << " != " << B << ")"; - } - } - void constrain(TYPE C, std::string constraint_name) { - if (C != 0) { - // NB: This might be an error, but we don't stop execution, - // because we want to be able to run tests-to-fail. - BOOST_LOG_TRIVIAL(warning) << "Assignment violates polynomial constraint " - << constraint_name << " (" << C << " != 0)"; - } - } - void lookup(std::vector &C, std::string table_name) { - // TODO: actually check membership of C in table? - } - void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { - // most probably do nothing - } - context subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { - context res = *this; - std::vector new_W = {}; - for(std::size_t i = 0; i < W.size(); i++) { - new_W.push_back(col_map[column_type::witness][W[i]]); - } - res.col_map[column_type::witness] = new_W; - res.row_shift += new_row_shift; - res.max_rows = new_max_rows; - res.current_row[column_type::witness] = 0; // reset to 0, because in the new column set everything is different - return res; - } - - context fresh_subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { - context res = subcontext(W, new_row_shift, new_max_rows); - // TODO: Maybe we should create a fresh assignment table here? - return res; - } + using basic_context::get_col; + using basic_context::get_row; + using basic_context::is_allocated; + using basic_context::mark_allocated; + + context(assignment_type &assignment_table, std::size_t max_rows) + : basic_context(add_rows_to_description(assignment_table.get_description(), max_rows), max_rows) + , at(assignment_table) + { }; + + context(assignment_type &assignment_table, std::size_t max_rows, std::size_t row_shift) + : basic_context(add_rows_to_description(assignment_table.get_description(), max_rows), max_rows, row_shift) + , at(assignment_table) + { }; + + void allocate(TYPE &C, size_t col, size_t row, column_type t) { + if (is_allocated(col, row, t)) { + std::stringstream ss; + ss << "RE-allocation of " << t << " cell at col = " << col << ", row = " << row << ".\n"; + throw std::logic_error(ss.str()); + } + switch (t) { + // NB: we use get_col/get_row here because active area might differ + // from the entire assignment table, while col and row are intended + // to be _relative_ to the active area + case column_type::witness: at.witness(get_col(col,t), get_row(row)) = C; break; + case column_type::public_input: at.public_input(get_col(col,t), get_row(row)) = C; break; + case column_type::constant: + // constants should already be assigned at this point + if (C != at.constant(get_col(col,t), get_row(row))) { + BOOST_LOG_TRIVIAL(error) << "Constant " << C << "doesn't match previous assignment " + << at.constant(get_col(col,t), get_row(row)) << "\n"; + } + BOOST_ASSERT(C == at.constant(get_col(col,t), get_row(row))); + break; + } + mark_allocated(col, row, t); + } + + void copy_constrain(TYPE &A, TYPE &B) { + if (A != B) { + // NB: This might be an error, but we don't stop execution, + // because we want to be able to run tests-to-fail. + BOOST_LOG_TRIVIAL(warning) << "Assignment violates copy constraint (" << A << " != " << B << ")"; + } + } + void constrain(TYPE C, std::string constraint_name) { + if (C != 0) { + // NB: This might be an error, but we don't stop execution, + // because we want to be able to run tests-to-fail. + BOOST_LOG_TRIVIAL(warning) << "Assignment violates polynomial constraint " + << constraint_name << " (" << C << " != 0)"; + } + } + void lookup(std::vector &C, std::string table_name) { + // TODO: actually check membership of C in table? + } + void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { + // most probably do nothing + } + context subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { + context res = *this; + std::vector new_W = {}; + for(std::size_t i = 0; i < W.size(); i++) { + new_W.push_back(col_map[column_type::witness][W[i]]); + } + res.col_map[column_type::witness] = new_W; + res.row_shift += new_row_shift; + res.max_rows = new_max_rows; + res.current_row[column_type::witness] = 0; // reset to 0, because in the new column set everything is different + return res; + } + + context fresh_subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { + context res = subcontext(W, new_row_shift, new_max_rows); + // TODO: Maybe we should create a fresh assignment table here? + return res; + } private: // reference to the actual assignment table @@ -273,6 +273,7 @@ namespace nil { template class context : public basic_context { // circuit-specific definition + public: using constraint_id_type = gate_id; using value_type = typename FieldType::value_type; using var = crypto3::zk::snark::plonk_variable; @@ -280,10 +281,12 @@ namespace nil { using plonk_copy_constraint = crypto3::zk::snark::plonk_copy_constraint; using constraints_container_type = std::map>>; using copy_constraints_container_type = std::vector; // TODO: maybe it's a set, not a vec? + using lookup_input_constraints_type = crypto3::zk::snark::lookup_input_constraints; using lookup_constraints_container_type = std::map, // - std::pair,row_selector<>>>; + std::pair>>; // ^^^ expressions, rows - using lookup_constraint_type = std::pair>; // NB: NOT exactly as plonk!!! + // NB: NOT exactly as plonk!!! + using lookup_constraint_type = std::pair; using dynamic_lookup_table_container_type = std::map, row_selector<>>>; // ^^^ name -> (columns, rows) using basic_context::col_map; @@ -294,390 +297,394 @@ namespace nil { using basic_context::row_shift; - public: - using TYPE = constraint_type; - using basic_context::get_col; - using basic_context::get_row; - using basic_context::is_allocated; - using basic_context::mark_allocated; - - public: - - /** - * \param[in] desc - this assignment table description actually shows the used part of the table, - * and 'max_rows' rows will be added to it. - */ - context(const assignment_description_type& desc, std::size_t max_rows) - : desc(add_rows_to_description(desc, max_rows)) - , basic_context(add_rows_to_description(desc, max_rows), max_rows) { - reset_storage(); - } - - /** - * \param[in] desc - this assignment table description actually shows the used part of the table, - * and 'max_rows' rows will be added to it. - */ - context(const assignment_description_type& desc, std::size_t max_rows, std::size_t row_shift) - : desc(add_rows_to_description(desc, max_rows)) - , basic_context(add_rows_to_description(desc, max_rows), max_rows, row_shift) { - reset_storage(); - } - - void allocate(TYPE &C, size_t col, size_t row, column_type t) { - if (is_allocated(col, row, t)) { - BOOST_LOG_TRIVIAL(warning) << "RE-allocation of " << t << " cell at col = " << col << ", row = " << row << ".\n"; - } - if (t == column_type::constant) { - auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(C); - if (has_vars) { - std::stringstream error; - error << "Trying to assign constraint " << C << " to constant cell!"; - throw std::invalid_argument(error.str()); - } - value_type C_val = C.evaluate(0, *constants_storage); - constants_storage->constant(get_col(col,t), get_row(row)) = C_val; // store the constant - } - var res = var(get_col(col,t), get_row(row), // get_col/get_row are active-area-aware - false, // false = use absolute cell address - static_cast(t)); - if ((C != TYPE()) && (t == column_type::witness)) { // TODO: TYPE() - is this ok? NB: we only constrain witnesses! - constrain(res - C,""); // TODO: maybe add a name for this constraint? + using TYPE = constraint_type; + using basic_context::get_col; + using basic_context::get_row; + using basic_context::is_allocated; + using basic_context::mark_allocated; + + /** + * \param[in] desc - this assignment table description actually shows the used part of the table, + * and 'max_rows' rows will be added to it. + */ + context(const assignment_description_type& desc, std::size_t max_rows) + : desc(add_rows_to_description(desc, max_rows)) + , basic_context(add_rows_to_description(desc, max_rows), max_rows) { + reset_storage(); + } + + /** + * \param[in] desc - this assignment table description actually shows the used part of the table, + * and 'max_rows' rows will be added to it. + */ + context(const assignment_description_type& desc, std::size_t max_rows, std::size_t row_shift) + : desc(add_rows_to_description(desc, max_rows)) + , basic_context(add_rows_to_description(desc, max_rows), max_rows, row_shift) { + reset_storage(); + } + + void allocate(TYPE &C, size_t col, size_t row, column_type t) { + if (is_allocated(col, row, t)) { + BOOST_LOG_TRIVIAL(warning) << "RE-allocation of " << t << " cell at col = " << col << ", row = " << row << ".\n"; + } + if (t == column_type::constant) { + auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(C); + if (has_vars) { + std::stringstream error; + error << "Trying to assign constraint " << C << " to constant cell!"; + throw std::invalid_argument(error.str()); } - - C = res; - mark_allocated(col, row, t); + value_type C_val = C.evaluate(0, *constants_storage); + constants_storage->constant(get_col(col,t), get_row(row)) = C_val; // store the constant + } + var res = var(get_col(col,t), get_row(row), // get_col/get_row are active-area-aware + false, // false = use absolute cell address + static_cast(t)); + if ((C != TYPE()) && (t == column_type::witness)) { // TODO: TYPE() - is this ok? NB: we only constrain witnesses! + constrain(res - C,""); // TODO: maybe add a name for this constraint? } - void copy_constrain(TYPE &A, TYPE &B) { - auto is_var = expression_is_variable_visitor::is_var; + C = res; + mark_allocated(col, row, t); + } - if (!is_var(A) || !is_var(B)) { - BOOST_LOG_TRIVIAL(error) << "Copy constraint applied to non-variable: " << A << " = " << B << ".\n"; - } - BOOST_ASSERT(is_var(A) && is_var(B)); + void copy_constrain(TYPE &A, TYPE &B) { + auto is_var = expression_is_variable_visitor::is_var; + + if (!is_var(A) || !is_var(B)) { + BOOST_LOG_TRIVIAL(error) << "Copy constraint applied to non-variable: " << A << " = " << B << ".\n"; + } + BOOST_ASSERT(is_var(A) && is_var(B)); - var A_var = boost::get>(A.get_expr()).get_vars()[0]; - var B_var = boost::get>(B.get_expr()).get_vars()[0]; + var A_var = boost::get>(A.get_expr()).get_vars()[0]; + var B_var = boost::get>(B.get_expr()).get_vars()[0]; - if (A_var != B_var) { - copy_constraints->push_back({A_var,B_var}); - } + if (A_var != B_var) { + copy_constraints->push_back({A_var,B_var}); } + } - bool is_absolute(TYPE C) { - return expression_relativity_check_visitor::is_absolute(C); + bool is_absolute(TYPE C) { + return expression_relativity_check_visitor::is_absolute(C); + } + bool is_relative(TYPE C) { + return expression_relativity_check_visitor::is_relative(C); + } + TYPE relativize(TYPE C, int32_t shift) { + return expression_relativize_visitor::relativize(C, shift); + } + std::vector relativize(std::vector C, int32_t shift) { + std::vector res; + for(TYPE c_part : C) { + res.push_back(expression_relativize_visitor::relativize(c_part,shift)); } - bool is_relative(TYPE C) { - return expression_relativity_check_visitor::is_relative(C); + return res; + } + + void constrain(TYPE C, std::string constraint_name) { + if (!is_absolute(C)) { + std::stringstream ss; + ss << "Constraint " << C << " has relative variables, cannot constrain."; + throw std::logic_error(ss.str()); } - TYPE relativize(TYPE C, int32_t shift) { - return expression_relativize_visitor::relativize(C, shift); + + auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(C); + if (!has_vars) { + BOOST_LOG_TRIVIAL(error) << "Constraint " << C << " has no variables!\n"; } - std::vector relativize(std::vector C, int32_t shift) { - std::vector res; - for(TYPE c_part : C) { - res.push_back(expression_relativize_visitor::relativize(c_part,shift)); - } - return res; + BOOST_ASSERT(has_vars); + if (max_row - min_row > 2) { + BOOST_LOG_TRIVIAL(warning) << "Constraint " << C << " spans over 3 rows!\n"; } + std::size_t row = (min_row + max_row)/2; - void constrain(TYPE C, std::string constraint_name) { - if (!is_absolute(C)) { - std::stringstream ss; - ss << "Constraint " << C << " has relative variables, cannot constrain."; - throw std::logic_error(ss.str()); - } + TYPE C_rel = relativize(C, -row); - auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(C); - if (!has_vars) { - BOOST_LOG_TRIVIAL(error) << "Constraint " << C << " has no variables!\n"; - } - BOOST_ASSERT(has_vars); - if (max_row - min_row > 2) { - BOOST_LOG_TRIVIAL(warning) << "Constraint " << C << " spans over 3 rows!\n"; - } - std::size_t row = (min_row + max_row)/2; - - TYPE C_rel = relativize(C, -row); + add_constraint(C_rel, row); + } - add_constraint(C_rel, row); + // accesible only at GenerationStage::CONSTRAINTS ! + void relative_constrain(TYPE C_rel, std::size_t row) { + if (!is_relative(C_rel)) { + std::stringstream ss; + ss << "Constraint " << C_rel << " has absolute variables, cannot constrain."; + throw std::logic_error(ss.str()); } + add_constraint(C_rel, get_row(row)); + } - // accesible only at GenerationStage::CONSTRAINTS ! - void relative_constrain(TYPE C_rel, std::size_t row) { - if (!is_relative(C_rel)) { - std::stringstream ss; - ss << "Constraint " << C_rel << " has absolute variables, cannot constrain."; - throw std::logic_error(ss.str()); - } - add_constraint(C_rel, get_row(row)); - } - - void lookup(std::vector &C, std::string table_name) { - std::set base_rows = {}; - - // Choose the best row to relativize. Different expressions in a single lookup might accept - // up to 3 different rows for relativization. We take the intersection for all expressions in - // the constraint. - for(TYPE c_part : C) { - auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(c_part); - if (has_vars) { // NB: not having variables seems to be ok for a part of a lookup expression - if (max_row - min_row > 2) { - BOOST_LOG_TRIVIAL(warning) << "Expression " << c_part << " in lookup constraint spans over 3 rows!\n"; - } - std::size_t row = (min_row + max_row)/2; - std::set current_base_rows = {row}; - if (max_row - min_row <= 1) { - current_base_rows.insert(row+1); - } - if (max_row == min_row) { - current_base_rows.insert(row-1); - } - if (base_rows.empty()) { - base_rows = current_base_rows; - } else { - std::set new_base_rows; - std::set_intersection(base_rows.begin(), base_rows.end(), - current_base_rows.begin(), current_base_rows.end(), - std::inserter(new_base_rows, new_base_rows.end())); - base_rows = new_base_rows; - } - } - } - if (base_rows.empty()) { - BOOST_LOG_TRIVIAL(error) << "Lookup constraint expressions have no variables or have incompatible spans!\n"; - } - BOOST_ASSERT(!base_rows.empty()); - - std::size_t row = (base_rows.size() == 3) ? *(std::next(base_rows.begin())) : *(base_rows.begin()); - std::vector res; - for(TYPE c_part : C) { - TYPE c_part_rel = expression_relativize_visitor::relativize(c_part, -row); - res.push_back(c_part_rel); - } - add_lookup_constraint(table_name, res, row); - } + void lookup(std::vector &C, std::string table_name) { + std::set base_rows = {}; - // accesible only at GenerationStage::CONSTRAINTS ! - void relative_lookup(std::vector &C, std::string table_name, std::size_t row) { - for(const TYPE c_part : C) { - if (!is_relative(c_part)) { - std::stringstream ss; - ss << "Constraint " << c_part << " has absolute variables, cannot constrain."; - throw std::logic_error(ss.str()); + // Choose the best row to relativize. Different expressions in a single lookup might accept + // up to 3 different rows for relativization. We take the intersection for all expressions in + // the constraint. + for(TYPE c_part : C) { + auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(c_part); + if (has_vars) { // NB: not having variables seems to be ok for a part of a lookup expression + if (max_row - min_row > 2) { + BOOST_LOG_TRIVIAL(warning) << "Expression " << c_part << " in lookup constraint spans over 3 rows!\n"; + } + std::size_t row = (min_row + max_row)/2; + std::set current_base_rows = {row}; + if (max_row - min_row <= 1) { + current_base_rows.insert(row+1); + } + if (max_row == min_row) { + current_base_rows.insert(row-1); + } + if (base_rows.empty()) { + base_rows = current_base_rows; + } else { + std::set new_base_rows; + std::set_intersection(base_rows.begin(), base_rows.end(), + current_base_rows.begin(), current_base_rows.end(), + std::inserter(new_base_rows, new_base_rows.end())); + base_rows = new_base_rows; } } - add_lookup_constraint(table_name, C, row); } - - void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { - if (lookup_tables->find(name) != lookup_tables->end()) { - BOOST_LOG_TRIVIAL(error) << "Double declaration of dynamic lookup table '" << name << "'!\n"; - } - BOOST_ASSERT(lookup_tables->find(name) == lookup_tables->end()); - std::vector cols; - row_selector<> rows(desc.rows_amount); - - for(std::size_t i = 0; i < W.size(); i++) { - cols.push_back(col_map[column_type::witness][W[i]]); - } - for(std::size_t i = 0; i < num_rows; i++) { - rows.set_row(get_row(from_row + i)); // store absolute row numbers - } - - lookup_tables->insert({name,{cols,rows}}); + if (base_rows.empty()) { + BOOST_LOG_TRIVIAL(error) << "Lookup constraint expressions have no variables or have incompatible spans!\n"; } + BOOST_ASSERT(!base_rows.empty()); - void optimize_gates() { - // NB: std::map>> constraints; - // intended to - // shift some of the constraints so that we have less selectors - /* - for(const auto& [id, data] : *constraints) { - std::cout << "Constraint: " << data.first << "\n"; - for(std::size_t row : data.second) { - std::cout << row << " "; - } - std::cout << "\n"; - } - */ + std::size_t row = (base_rows.size() == 3) ? *(std::next(base_rows.begin())) : *(base_rows.begin()); + std::vector res; + for(TYPE c_part : C) { + TYPE c_part_rel = expression_relativize_visitor::relativize(c_part, -row); + res.push_back(c_part_rel); } + add_lookup_constraint(table_name, res, row); + } - std::unordered_map, std::vector> get_constraints() { - // joins constraints with identic selectors into a single gate - - // drop the constraint_id from the stored id->(constraint,row_list) map and - // join constrains into single element if they have the same row list: - std::unordered_map, std::vector> res; - for(const auto& [id, data] : *constraints) { - auto it = res.find(data.second); - if (it == res.end()) { - res[data.second] = {data.first}; - } else { - it->second.push_back(data.first); - } + // accesible only at GenerationStage::CONSTRAINTS ! + void relative_lookup(std::vector &C, std::string table_name, std::size_t row) { + for(const TYPE c_part : C) { + if (!is_relative(c_part)) { + std::stringstream ss; + ss << "Constraint " << c_part << " has absolute variables, cannot constrain."; + throw std::logic_error(ss.str()); } - return res; } + add_lookup_constraint(table_name, C, row); + } - std::vector& get_copy_constraints() { - return *copy_constraints; + void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { + if (lookup_tables->find(name) != lookup_tables->end()) { + BOOST_LOG_TRIVIAL(error) << "Double declaration of dynamic lookup table '" << name << "'!\n"; } + BOOST_ASSERT(lookup_tables->find(name) == lookup_tables->end()); + std::vector cols; + row_selector<> rows(desc.rows_amount); - dynamic_lookup_table_container_type& get_dynamic_lookup_tables() { - return *lookup_tables; + for(std::size_t i = 0; i < W.size(); i++) { + cols.push_back(col_map[column_type::witness][W[i]]); } - - std::unordered_map, std::vector> get_lookup_constraints() { - // std::map, // - // std::pair,row_selector<>>> // expressions, rows - - std::unordered_map, std::vector> res; - for(const auto& [id, data] : *lookup_constraints) { - auto it = res.find(data.second); - if (it == res.end()) { - res[data.second] = {{id.first, data.first}}; - } else { - it->second.push_back({id.first, data.first}); - } - } - - /* - for(const auto& [lcs, rows] : res) { - for(const auto& [table, cs] : lcs) { - std::cout << "Table " << table << ": "; - for(const auto& c : cs) { std::cout << c << ", "; } - } - std::cout << "Rows: "; - for(const auto& r : rows) { std::cout << r << " "; } - std::cout << "\n"; - } - */ - return res; + for(std::size_t i = 0; i < num_rows; i++) { + rows.set_row(get_row(from_row + i)); // store absolute row numbers } - context subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { - context res = *this; - std::vector new_W = {}; - for(std::size_t i = 0; i < W.size(); i++) { - new_W.push_back(col_map[column_type::witness][W[i]]); + lookup_tables->insert({name,{cols,rows}}); + } + + void optimize_gates() { + // NB: std::map>> constraints; + // intended to + // shift some of the constraints so that we have less selectors + /* + for(const auto& [id, data] : *constraints) { + std::cout << "Constraint: " << data.first << "\n"; + for(std::size_t row : data.second) { + std::cout << row << " "; } - res.col_map[column_type::witness] = new_W; - res.row_shift += new_row_shift; - res.max_rows = new_max_rows; - res.current_row[column_type::witness] = 0; // reset to 0, because in the new column set everything is different - return res; + std::cout << "\n"; } + */ + } - void reset_storage() { - constraints = std::make_shared(); - copy_constraints = std::make_shared(); - lookup_constraints = std::make_shared(); - lookup_tables = std::make_shared(); - constants_storage = std::make_shared(0, 0, desc.constant_columns, 0); - is_fresh = false; - } + std::unordered_map, std::vector> get_constraints() { + // joins constraints with identic selectors into a single gate - auto get_constants() { - return constants_storage->constants(); + // drop the constraint_id from the stored id->(constraint,row_list) map and + // join constrains into single element if they have the same row list: + std::unordered_map, std::vector> res; + for(const auto& [id, data] : *constraints) { + auto it = res.find(data.second); + if (it == res.end()) { + res[data.second] = {data.first}; + } else { + it->second.push_back(data.first); + } } + return res; + } - // This one will create its own set of constraint storages. - context fresh_subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { - context res = subcontext(W, new_row_shift, new_max_rows); - res.reset_storage(); - is_fresh = true; - return res; - } + std::vector& get_copy_constraints() { + return *copy_constraints; + } + dynamic_lookup_table_container_type& get_dynamic_lookup_tables() { + return *lookup_tables; + } - private: + std::unordered_map, std::vector> get_lookup_constraints() { + // std::map, // + // std::pair,row_selector<>>> // expressions, rows - void add_constraint(TYPE &C_rel, std::size_t row) { - std::size_t stored_row = row - (is_fresh ? row_shift : 0); - constraint_id_type C_id = constraint_id_type(C_rel); - if (constraints->find(C_id) == constraints->end()) { - constraints->insert({C_id, {C_rel, row_selector<>(desc.rows_amount)}}); - } - constraints->at(C_id).second.set_row(stored_row); + std::unordered_map, std::vector> res; + for(const auto& [id, data] : *lookup_constraints) { + auto it = res.find(data.second); + if (it == res.end()) { + res[data.second] = {{id.first, data.first}}; + } else { + it->second.push_back({id.first, data.first}); + } } - void add_lookup_constraint(std::string table_name, std::vector &C_rel, std::size_t row) { - std::size_t stored_row = row - (is_fresh ? row_shift : 0); - constraint_id_type C_id = constraint_id_type(C_rel); - if (lookup_constraints->find({table_name,C_id}) == lookup_constraints->end()) { - lookup_constraints->insert({{table_name,C_id}, {C_rel, row_selector<>(desc.rows_amount)}}); + /* + for(const auto& [lcs, rows] : res) { + for(const auto& [table, cs] : lcs) { + std::cout << "Table " << table << ": "; + for(const auto& c : cs) { std::cout << c << ", "; } } - lookup_constraints->at({table_name,C_id}).second.set_row(stored_row); - } - - // Assignment description will be used when resetting the context. - assignment_description_type desc; - - // constraints (with unique id), and the rows they are applied to - std::shared_ptr constraints; - // copy constraints as in BP - std::shared_ptr copy_constraints; - // lookup constraints with table name, unique id and row list - std::shared_ptr lookup_constraints; - // dynamic lookup tables - std::shared_ptr lookup_tables; - // constants - std::shared_ptr constants_storage; - // are we in a fresh context or not - bool is_fresh; + std::cout << "Rows: "; + for(const auto& r : rows) { std::cout << r << " "; } + std::cout << "\n"; + } + */ + return res; + } + + context subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { + context res = *this; + std::vector new_W = {}; + for(std::size_t i = 0; i < W.size(); i++) { + new_W.push_back(col_map[column_type::witness][W[i]]); + } + res.col_map[column_type::witness] = new_W; + res.row_shift += new_row_shift; + res.max_rows = new_max_rows; + res.current_row[column_type::witness] = 0; // reset to 0, because in the new column set everything is different + return res; + } + + void reset_storage() { + constraints = std::make_shared(); + copy_constraints = std::make_shared(); + lookup_constraints = std::make_shared(); + lookup_tables = std::make_shared(); + constants_storage = std::make_shared(0, 0, desc.constant_columns, 0); + is_fresh = false; + } + + auto get_constants() { + return constants_storage->constants(); + } + + // This one will create its own set of constraint storages. + context fresh_subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { + context res = subcontext(W, new_row_shift, new_max_rows); + res.reset_storage(); + is_fresh = true; + return res; + } + + private: + void add_constraint(TYPE &C_rel, std::size_t row) { + std::size_t stored_row = row - (is_fresh ? row_shift : 0); + constraint_id_type C_id = constraint_id_type(C_rel); + if (constraints->find(C_id) == constraints->end()) { + constraints->insert({C_id, {C_rel, row_selector<>(desc.rows_amount)}}); + } + constraints->at(C_id).second.set_row(stored_row); + } + + void add_lookup_constraint( + std::string table_name, const lookup_input_constraints_type &C_rel, std::size_t row) { + std::size_t stored_row = row - (is_fresh ? row_shift : 0); + constraint_id_type C_id = constraint_id_type(C_rel); + std::pair key = {table_name, C_id}; + if (lookup_constraints->find(key) == lookup_constraints->end()) { + lookup_constraints->insert({ + key, + {C_rel, row_selector<>(desc.rows_amount)} + }); + } + lookup_constraints->at(key).second.set_row(stored_row); + } + + void add_lookup_constraint(std::string table_name, std::vector &C_rel, std::size_t row) { + add_lookup_constraint(table_name, lookup_input_constraints_type(C_rel), row); + } + + // Assignment description will be used when resetting the context. + assignment_description_type desc; + + // constraints (with unique id), and the rows they are applied to + std::shared_ptr constraints; + // copy constraints as in BP + std::shared_ptr copy_constraints; + // lookup constraints with table name, unique id and row list + std::shared_ptr lookup_constraints; + // dynamic lookup tables + std::shared_ptr lookup_tables; + // constants + std::shared_ptr constants_storage; + // are we in a fresh context or not + bool is_fresh; }; template class generic_component { - public: - using TYPE = typename std::conditional(stage), - crypto3::zk::snark::plonk_constraint, - typename FieldType::value_type>::type; - using context_type = context; - using plonk_copy_constraint = crypto3::zk::snark::plonk_copy_constraint; + public: + using TYPE = typename std::conditional(stage), + crypto3::zk::snark::plonk_constraint, + typename FieldType::value_type>::type; + using context_type = context; + using plonk_copy_constraint = crypto3::zk::snark::plonk_copy_constraint; - private: - context_type &ct; + private: + context_type &ct; - public: - void allocate(TYPE &C, column_type t = column_type::witness) { - auto [col, row] = ct.next_free_cell(t); - ct.allocate(C, col, row, t); - } + public: + void allocate(TYPE &C, column_type t = column_type::witness) { + auto [col, row] = ct.next_free_cell(t); + ct.allocate(C, col, row, t); + } - void allocate(TYPE &C, size_t col, size_t row, column_type t = column_type::witness) { - ct.allocate(C,col,row,t); - } + void allocate(TYPE &C, size_t col, size_t row, column_type t = column_type::witness) { + ct.allocate(C,col,row,t); + } - void copy_constrain(TYPE &A, TYPE &B) { - ct.copy_constrain(A,B); - } + void copy_constrain(TYPE &A, TYPE &B) { + ct.copy_constrain(A,B); + } - void constrain(TYPE C, std::string constraint_name = "") { - ct.constrain(C, constraint_name); - } + void constrain(TYPE C, std::string constraint_name = "") { + ct.constrain(C, constraint_name); + } - void lookup(std::vector C, std::string table_name) { - ct.lookup(C,table_name); - } + void lookup(std::vector C, std::string table_name) { + ct.lookup(C,table_name); + } - void lookup(TYPE C, std::string table_name) { - std::vector input = {C}; - ct.lookup(input,table_name); - } + void lookup(TYPE C, std::string table_name) { + std::vector input = {C}; + ct.lookup(input,table_name); + } - void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { - ct.lookup_table(name,W,from_row,num_rows); - } + void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { + ct.lookup_table(name,W,from_row,num_rows); + } - generic_component(context_type &context_object, // context object, created outside - bool crlf = true // do we assure a component starts on a new row? Default is "yes" - ) : ct(context_object) { - if (crlf) { // TODO: Implement crlf parameter consequences - ct.new_line(column_type::witness); - } - }; + generic_component(context_type &context_object, // context object, created outside + bool crlf = true // do we assure a component starts on a new row? Default is "yes" + ) : ct(context_object) { + if (crlf) { // TODO: Implement crlf parameter consequences + ct.new_line(column_type::witness); + } + }; }; } // namespace bbf diff --git a/crypto3/libs/blueprint/test/CMakeLists.txt b/crypto3/libs/blueprint/test/CMakeLists.txt index 32ba69d964..c6e55059cd 100644 --- a/crypto3/libs/blueprint/test/CMakeLists.txt +++ b/crypto3/libs/blueprint/test/CMakeLists.txt @@ -54,6 +54,7 @@ macro(define_blueprint_test test) crypto3::algebra crypto3::zk crypto3::random + crypto3::block ) set_target_properties(${full_test_name} PROPERTIES CXX_STANDARD 17) diff --git a/crypto3/libs/zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp b/crypto3/libs/zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp index 12584aaf30..41f9b9943c 100644 --- a/crypto3/libs/zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp +++ b/crypto3/libs/zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp @@ -28,6 +28,7 @@ #ifndef CRYPTO3_ZK_PLONK_LOOKUP_CONSTRAINT_HPP #define CRYPTO3_ZK_PLONK_LOOKUP_CONSTRAINT_HPP +#include #include #include @@ -36,6 +37,61 @@ namespace nil { namespace zk { namespace snark { + template + class lookup_input_constraints : public std::vector> { + public: + using constraint_type = crypto3::zk::snark::plonk_constraint; + using base_type = std::vector; + using expression_type = typename constraint_type::base_type; + + // Using the base class's constructors + using std::vector::vector; // Inherit constructors + + // Constructor to initialize from std::vector + lookup_input_constraints(const base_type& other) + : base_type(other) {} + + // Multiply each element with an expression. + lookup_input_constraints& operator*=(const expression_type& other) { + for (auto& element : *this) { + element *= other; + } + return *this; + } + + lookup_input_constraints operator*(const expression_type& other) { + lookup_input_constraints result = *this; + result *= other; + return result; + } + + // Allow multiplication with any container of the same type. + template + lookup_input_constraints& operator*=(const typename std::enable_if_t< + nil::crypto3::detail::is_range::value && std::is_same::value, + Container>& other) { + if (this->size() < other.size()) + this->resize(other.size()); + + auto it1 = this->begin(); + auto it2 = other.begin(); + for (; it2 != other.end(); ++it1, ++it2) { + *it1 *= *it2; + } + return *this; + } + + template + lookup_input_constraints& operator*(const typename std::enable_if_t< + nil::crypto3::detail::is_range::value && std::is_same::value, + Container>& other) { + lookup_input_constraints result = *this; + result *= other; + return result; + } + }; + + template> class plonk_lookup_constraint { public: @@ -45,9 +101,9 @@ namespace nil { using constraint_type = plonk_constraint; std::size_t table_id; - std::vector lookup_input; + lookup_input_constraints lookup_input; - bool operator== (const plonk_lookup_constraint &other) const { + bool operator==(const plonk_lookup_constraint &other) const { return table_id == other.table_id && lookup_input == other.lookup_input; } }; diff --git a/parallel-crypto3/libs/parallel-zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp b/parallel-crypto3/libs/parallel-zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp index 12584aaf30..41f9b9943c 100644 --- a/parallel-crypto3/libs/parallel-zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp +++ b/parallel-crypto3/libs/parallel-zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp @@ -28,6 +28,7 @@ #ifndef CRYPTO3_ZK_PLONK_LOOKUP_CONSTRAINT_HPP #define CRYPTO3_ZK_PLONK_LOOKUP_CONSTRAINT_HPP +#include #include #include @@ -36,6 +37,61 @@ namespace nil { namespace zk { namespace snark { + template + class lookup_input_constraints : public std::vector> { + public: + using constraint_type = crypto3::zk::snark::plonk_constraint; + using base_type = std::vector; + using expression_type = typename constraint_type::base_type; + + // Using the base class's constructors + using std::vector::vector; // Inherit constructors + + // Constructor to initialize from std::vector + lookup_input_constraints(const base_type& other) + : base_type(other) {} + + // Multiply each element with an expression. + lookup_input_constraints& operator*=(const expression_type& other) { + for (auto& element : *this) { + element *= other; + } + return *this; + } + + lookup_input_constraints operator*(const expression_type& other) { + lookup_input_constraints result = *this; + result *= other; + return result; + } + + // Allow multiplication with any container of the same type. + template + lookup_input_constraints& operator*=(const typename std::enable_if_t< + nil::crypto3::detail::is_range::value && std::is_same::value, + Container>& other) { + if (this->size() < other.size()) + this->resize(other.size()); + + auto it1 = this->begin(); + auto it2 = other.begin(); + for (; it2 != other.end(); ++it1, ++it2) { + *it1 *= *it2; + } + return *this; + } + + template + lookup_input_constraints& operator*(const typename std::enable_if_t< + nil::crypto3::detail::is_range::value && std::is_same::value, + Container>& other) { + lookup_input_constraints result = *this; + result *= other; + return result; + } + }; + + template> class plonk_lookup_constraint { public: @@ -45,9 +101,9 @@ namespace nil { using constraint_type = plonk_constraint; std::size_t table_id; - std::vector lookup_input; + lookup_input_constraints lookup_input; - bool operator== (const plonk_lookup_constraint &other) const { + bool operator==(const plonk_lookup_constraint &other) const { return table_id == other.table_id && lookup_input == other.lookup_input; } }; From d843da8bd402733e771499570a2a266379a31e9c Mon Sep 17 00:00:00 2001 From: Martun Karapetyan Date: Mon, 11 Nov 2024 12:35:34 +0400 Subject: [PATCH 2/4] Non-compiling code for gate optimizer. --- .../include/nil/blueprint/bbf/bbf_wrapper.hpp | 8 +- .../nil/blueprint/bbf/gate_optimizer.hpp | 148 ++++++++++++++++++ .../include/nil/blueprint/bbf/generic.hpp | 15 -- .../nil/blueprint/bbf/row_selector.hpp | 10 ++ 4 files changed, 164 insertions(+), 17 deletions(-) create mode 100644 crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp index bbbfd32800..d87d33b1ee 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp @@ -239,7 +239,9 @@ namespace nil { context_type ct4 = ct.subcontext(ct4_area,0,4); Useless c4 = Useless(ct4); - ct.optimize_gates(); + // Don't use 'ct' below this line!!! + gates_optimizer optimizer(std::move(ct)); + optimized_gates gates = optimizer.optimize_gates(); // compatibility layer: constraint list => gates & selectors std::unordered_map, std::vector> constraint_list = @@ -248,7 +250,7 @@ namespace nil { // We will store selectors to re-use for lookup gates. std::unordered_map, std::size_t> selector_to_index_map; - for(const auto& [row_list, constraints] : constraint_list) { + for (const auto& [row_list, constraints] : constraint_list) { /* std::cout << "GATE:\n"; for(const auto& c : constraints) { @@ -339,6 +341,8 @@ namespace nil { // TODO: does this make sense?! dynamic_lookup_cols.push_back( var(c, 0, false, var::column_type::witness)); + if (c > 3) + throw "Problem here!" } table_specs.lookup_options = {dynamic_lookup_cols}; bp.define_dynamic_table(name,table_specs); diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp new file mode 100644 index 0000000000..ca1690174b --- /dev/null +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp @@ -0,0 +1,148 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2024 Alexey Yashunsky +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// +// @file Declaration of interfaces for PLONK BBF context & generic component classes +//---------------------------------------------------------------------------// + +#ifndef CRYPTO3_BLUEPRINT_BBF_GATE_OPTIMIZER_HPP +#define CRYPTO3_BLUEPRINT_BBF_GATE_OPTIMIZER_HPP + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace nil { + namespace blueprint { + namespace bbf { + + template + class optimized_gates { + public: + using constraint_type = crypto3::zk::snark::plonk_constraint; + using plonk_copy_constraint = crypto3::zk::snark::plonk_copy_constraint; + using lookup_input_constraints_type = crypto3::zk::snark::lookup_input_constraints; + using lookup_constraint_type = std::pair; + using row_selector_ptr = std::shared_ptr>; + + + std::vector> constraint_list; + std::vector copy_constraints; + std::map, row_selector_ptr>> dynamic_lookup_tables; + std::vector, row_selector_ptr> lookup_constraints; + }; + + template + class gates_optimizer { + public: + using constraint_type = crypto3::zk::snark::plonk_constraint; + using context_type = context; + + // We expect you to move context into this object, and stop using it. + gates_optimizer(context_type&& c) + : context_(std::make_unique(std::move(c))) { + } + + optimized_gates optimize_gates() { + optimized_gates result; + + // Take everything out of context, and erase the context to free its memory. + std::unordered_map, std::vector> constraint_list = context_.get_constraints(); + std::map, row_selector<>>> + dynamic_lookup_tables = ct.get_dynamic_lookup_tables(); + std::vector copy_constraints = ct.get_copy_constraints(); + std::unordered_map, std::vector> + lookup_constraints = ct.get_lookup_constraints(); + context_.reset(nullptr); + + // Push all the selectos into the 'selectors_' map, this will eliminate duplicates. + for (auto& [selector, constraints]: constraint_list) { + if (has_selector(selector)) { + continue; + } + + if (!selector[0]) { + // Consider shifting left, if that would help. + row_selector<> shifted_left = selector; + shifted_left <<= 1; + if (has_selector(shifted_left)) { + // try to shift left the constraints, if all can be shifted, we better re-use the selector. + std::optional> shifted_constraints = shift_constraints(constraints, -1); + if (shifted_constraints) { + // If we were able to rotate constraints to the left, keep the rotated version. + } + continue; + } + } + row_selector<> shifted_right = selector; + shifted_right <<= 1; + if (has_selector(shifted_right)) { + // try to shift left the constraints, if all can be shifted, we better re-use the selector. + continue; + } + add_selector(selector); + } + + for (const auto& [name, area] : dynamic_lookup_tables) { + const auto& selector = area.second; + add_selector(selector); + } + + for (const auto& [row_list, lookup_list] : lookup_constraints) { + add_selector(row_list); + } + + return result; + } + + size_t add_selector(const row_selector<>& selector) { + auto iter = selectors_.find(selector); + if (iter == selectors_.end()) { + selectors_.insert({selector, next_selector_id_}); + return next_selector_id_++; + } + return itee->second; + } + + private: + std::unique_ptr> context_; + + // We will map each selector to the corresponding number. + std::unordered_map, size_t> selectors_; + size_t next_selector_id_ = 0; + }; + + } // namespace bbf + } // namespace blueprint +} // namespace nil + +#endif // CRYPTO3_BLUEPRINT_BBF_GATE_OPTIMIZER_HPP diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp index 5b1a35b0cc..26031f6bae 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp @@ -488,21 +488,6 @@ namespace nil { lookup_tables->insert({name,{cols,rows}}); } - void optimize_gates() { - // NB: std::map>> constraints; - // intended to - // shift some of the constraints so that we have less selectors - /* - for(const auto& [id, data] : *constraints) { - std::cout << "Constraint: " << data.first << "\n"; - for(std::size_t row : data.second) { - std::cout << row << " "; - } - std::cout << "\n"; - } - */ - } - std::unordered_map, std::vector> get_constraints() { // joins constraints with identic selectors into a single gate diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/row_selector.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/row_selector.hpp index 0a3e2b50e5..1f8551b71a 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/bbf/row_selector.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/row_selector.hpp @@ -136,6 +136,16 @@ namespace nil { return *this; }*/ + row_selector& operator<<=(size_t bitcount) { + used_rows_ <<= bitcount; + return *this; + } + + row_selector& operator>>=(size_t bitcount) { + used_rows_ >>= bitcount; + return *this; + } + template friend std::size_t hash_value(const row_selector& a); From b1323ed3b5aca281012b46fea088e96b53c0ee4a Mon Sep 17 00:00:00 2001 From: Martun Karapetyan Date: Wed, 13 Nov 2024 13:17:22 +0400 Subject: [PATCH 3/4] Saving unworking code. --- .../nil/blueprint/bbf/gate_optimizer.hpp | 142 +++++++++++++----- .../include/nil/blueprint/bbf/generic.hpp | 3 +- 2 files changed, 107 insertions(+), 38 deletions(-) diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp index ca1690174b..87d4db945a 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp @@ -1,5 +1,5 @@ //---------------------------------------------------------------------------// -// Copyright (c) 2024 Alexey Yashunsky +// Copyright (c) 2024 Martun Karapetyan // // MIT License // @@ -52,13 +52,15 @@ namespace nil { using plonk_copy_constraint = crypto3::zk::snark::plonk_copy_constraint; using lookup_input_constraints_type = crypto3::zk::snark::lookup_input_constraints; using lookup_constraint_type = std::pair; - using row_selector_ptr = std::shared_ptr>; - - std::vector> constraint_list; + // Here size_t is the index of the selector from 'selectors_'. + std::vector> constraint_list; std::vector copy_constraints; - std::map, row_selector_ptr>> dynamic_lookup_tables; - std::vector, row_selector_ptr> lookup_constraints; + std::map, size_t>> dynamic_lookup_tables; + std::vector, size_t> lookup_constraints; + + // We will map each selector to the corresponding number. + std::vector> selectors_; }; template @@ -71,12 +73,65 @@ namespace nil { gates_optimizer(context_type&& c) : context_(std::make_unique(std::move(c))) { } - + + /** Tries to shift the constraints to left or right. + * \param[in] shift - Must be +-1, we cannot shift more than by 1. + */ + std::optional, std::vector>> try_shift_constraints( + const std::vector constraints, const row_selector<>& selector, size_t shift) { + if (shift != -1 && shift != 1) + return nullptr; + if (shift == -1 && selector[0]) + return nullptr; + if (shift == 1 && selector[selector.size() - 1]) + return nullptr; + + row_selector<> shifted_selector = selector; + if (shift == 1) + shifted_selector >>= 1; + else + shifted_selector <<= 1; + + // try to shift the constraints. + std::optional> shifted_constraints = shift_constraints(constraints, shift); + if (!shifted_constraints) + return nullptr; + return {shifted_selector, *shifted_constraints}; + } + + /** Tries to shift the lookup constraints to left or right. + * \param[in] shift - Must be +-1, we cannot shift more than by 1. + */ + std::optional, std::vector>> + try_shift_lookup_constraints( + const std::vector& lookup_list, + const row_selector<>& selector, size_t shift) { + if (shift != -1 && shift != 1) + return nullptr; + if (shift == -1 && selector[0]) + return nullptr; + if (shift == 1 && selector[selector.size() - 1]) + return nullptr; + + row_selector<> shifted_selector = selector; + if (shift == 1) + shifted_selector >>= 1; + else + shifted_selector <<= 1; + + // try to shift the constraints. + std::optional> shifted_constraints = + shift_lookup_constraints(lookup_list, shift); + if (!shifted_constraints) + return nullptr; + return {shifted_selector, *shifted_constraints}; + } + optimized_gates optimize_gates() { optimized_gates result; // Take everything out of context, and erase the context to free its memory. - std::unordered_map, std::vector> constraint_list = context_.get_constraints(); + std::unordered_map, std::vector> constraint_list = context_.get_constraints(); std::map, row_selector<>>> dynamic_lookup_tables = ct.get_dynamic_lookup_tables(); std::vector copy_constraints = ct.get_copy_constraints(); @@ -84,33 +139,7 @@ namespace nil { lookup_constraints = ct.get_lookup_constraints(); context_.reset(nullptr); - // Push all the selectos into the 'selectors_' map, this will eliminate duplicates. - for (auto& [selector, constraints]: constraint_list) { - if (has_selector(selector)) { - continue; - } - - if (!selector[0]) { - // Consider shifting left, if that would help. - row_selector<> shifted_left = selector; - shifted_left <<= 1; - if (has_selector(shifted_left)) { - // try to shift left the constraints, if all can be shifted, we better re-use the selector. - std::optional> shifted_constraints = shift_constraints(constraints, -1); - if (shifted_constraints) { - // If we were able to rotate constraints to the left, keep the rotated version. - } - continue; - } - } - row_selector<> shifted_right = selector; - shifted_right <<= 1; - if (has_selector(shifted_right)) { - // try to shift left the constraints, if all can be shifted, we better re-use the selector. - continue; - } - add_selector(selector); - } + optimize_selectors_by_shifting(constraint_list, lookup_constraints); for (const auto& [name, area] : dynamic_lookup_tables) { const auto& selector = area.second; @@ -122,7 +151,7 @@ namespace nil { } return result; - } + } size_t add_selector(const row_selector<>& selector) { auto iter = selectors_.find(selector); @@ -130,10 +159,49 @@ namespace nil { selectors_.insert({selector, next_selector_id_}); return next_selector_id_++; } - return itee->second; + return iter->second; } private: + + /** This function tries to reduce the number of selectors required by rotating the constraints by +-1. + * \param[in, out] constraint_list - Gate constraints. + * \param[in, out] lookup_constraints - Lookup constraints. + */ + void optimize_selectors_by_shifting( + std::unordered_map, std::vector>& constraint_list, + std::unordered_map, std::vector>& lookup_constraints) { + auto shift_optimize = [&constraint_list](size_t shift) { + // Check if some gate constraint can be shifted to match the selector of another one. + for (auto& [selector, constraints]: constraint_list) { + if (constraints.empty()) + continue; + + // Consider shifting left, if that would help. + std::optional, std::vector>> shifted = try_shift_constraints( + constraints, selector, shift); + if (shifted) { + auto iter = constraint_list.find(shifted->first); + if (iter != constraint_list.end()) { + iter->second.insert(iter->second.end(), shifted->second.begin(), shifted->second.end()); + // We don't want to erase a key in 'constraint_list' while iterating over it, so just + // drop the constraints for now. + constraints.resize(0); + continue; + } + } + } + // Check if constraints in the lookup constraints can be shifted to match another one, or some + // selector in the gate constraints. + for (const auto& [row_list, lookup_list] : lookup_constraints) { + + } + }; + + shift_optimize(-1); + shift_optimize(+1); + } + std::unique_ptr> context_; // We will map each selector to the corresponding number. diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp index 26031f6bae..13d2249e3a 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp @@ -271,8 +271,9 @@ namespace nil { assignment_type &at; }; + // circuit-specific definition template - class context : public basic_context { // circuit-specific definition + class context : public basic_context { public: using constraint_id_type = gate_id; using value_type = typename FieldType::value_type; From 05882d20ed45c351ab2accd0a2f6084c4c52bdec Mon Sep 17 00:00:00 2001 From: Martun Karapetyan Date: Wed, 13 Nov 2024 14:17:22 +0400 Subject: [PATCH 4/4] Saving. --- .../nil/blueprint/bbf/gate_optimizer.hpp | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp index 87d4db945a..9ebcb55f22 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/gate_optimizer.hpp @@ -194,12 +194,28 @@ namespace nil { // Check if constraints in the lookup constraints can be shifted to match another one, or some // selector in the gate constraints. for (const auto& [row_list, lookup_list] : lookup_constraints) { - + if (constraints.empty()) + continue; + + // Consider shifting left, if that would help. + std::optional, std::vector>> shifted = try_shift_constraints( + constraints, selector, shift); + if (shifted) { + auto iter = constraint_list.find(shifted->first); + if (iter != constraint_list.end()) { + iter->second.insert(iter->second.end(), shifted->second.begin(), shifted->second.end()); + // We don't want to erase a key in 'constraint_list' while iterating over it, so just + // drop the constraints for now. + constraints.resize(0); + continue; + } + } } }; - shift_optimize(-1); + // Always shift down first, because if we have 3 selectors, we want to move all 3 to the middle one. shift_optimize(+1); + shift_optimize(-1); } std::unique_ptr> context_;