From 090c867eb81c00e5fd039bca3e5e0d24f922ef1b Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Wed, 15 Oct 2025 18:59:22 +0200 Subject: [PATCH 01/14] Alternative dot print which helps understand the DAG using any dot graph creator --- include/expression_dag.hh | 53 +++++++++++++++++++++++++++++++++++++++ include/parser.hh | 1 + 2 files changed, 54 insertions(+) diff --git a/include/expression_dag.hh b/include/expression_dag.hh index da08bb5..2b44a47 100644 --- a/include/expression_dag.hh +++ b/include/expression_dag.hh @@ -102,6 +102,7 @@ public: /** * Print ScalarExpression graph in the dot format. + * Useful for debugging */ void print_in_dot() { std::map i_node; @@ -131,6 +132,58 @@ public: std::cout << "Node: " << node->op_name_ << "_" << node->result_idx_ << " " << node->result_storage << std::endl; } + /** + * Print ScalarExpression graph in the common dot format. + * Useful for understanding the DAG + */ + void print_in_dot2() { + sort_nodes(); + + std::cout << "\n" << "----- begin cut here -----" << "\n"; + std::cout << "digraph Expr {" << "\n"; + + std::cout << "/* definitions */" << "\n"; + std::cout << "edge [dir=back]" << "\n"; + for (uint i = 0; i < sorted.size(); ++i) { + _print_dot_node_definition(sorted[i]); + } + std::cout << "/* end of definitions */" << "\n"; + + for (uint i = 0; i < sorted.size(); ++i) { + for (uint in = 0; in < sorted[i]->n_inputs_; ++in) { + std::cout << " "; + _print_dot_node(sorted[i]); + std::cout << "\n -> "; + _print_dot_node(sorted[i]->inputs_[in]); + std::cout << "\n\n"; + } + } + std::cout << "}" << "\n"; + std::cout << "----- end cut here -----" << "\n"; + std::cout.flush(); + } + void _print_dot_node(ScalarNodePtr node) { + std::cout << node->op_name_ << "_" << (int)node.get() << "__" << node->result_storage;// << std::endl; + } + + void _print_dot_node_definition(ScalarNodePtr node) { + _print_dot_node(node); + std::cout << ' '; + + if (node->result_storage == ResultStorage::constant) { + std::cout << "[shape=circle,label=\"const " << *node->values_ << "\"]" << std::endl; + } + else if (node->result_storage == ResultStorage::constant_bool) { + std::cout << "[shape=circle,label=\"const " << *node->values_ << "\"]" << std::endl; + } + else if (node->result_storage == ResultStorage::expr_result) { + std::cout << "[shape=box,label=\"" << node->op_name_ << " " << node->result_idx_ << "\"]" << std::endl; + } + else { + std::cout << "[label=\"" << node->op_name_ << "\"]" << std::endl; + } + } + private: void _print_i_node(uint i) { diff --git a/include/parser.hh b/include/parser.hh index ca8bde1..ade2953 100644 --- a/include/parser.hh +++ b/include/parser.hh @@ -190,6 +190,7 @@ public: details::ExpressionDAG se(result_array_.elements()); //se.print_in_dot(); + //se.print_in_dot2(); processor = ProcessorBase::create_processor(se, max_vec_size, simd_size, arena); } From cb9414e2a97cdce2772d349c661d44a661c3e6e9 Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Sun, 19 Oct 2025 16:58:29 +0200 Subject: [PATCH 02/14] Better cast --- include/expression_dag.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/expression_dag.hh b/include/expression_dag.hh index 2b44a47..64f95e3 100644 --- a/include/expression_dag.hh +++ b/include/expression_dag.hh @@ -163,7 +163,7 @@ public: std::cout.flush(); } void _print_dot_node(ScalarNodePtr node) { - std::cout << node->op_name_ << "_" << (int)node.get() << "__" << node->result_storage;// << std::endl; + std::cout << node->op_name_ << "_" << (uintptr_t)node.get() << "__" << node->result_storage;// << std::endl; } void _print_dot_node_definition(ScalarNodePtr node) { From 95d91c38f64a1659c60b1ad6162c432d9a410c90 Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Mon, 20 Oct 2025 19:03:11 +0200 Subject: [PATCH 03/14] Improved print_in_dot2. If the symbols_ map is supplied, it will also print the variable and constant names in dot --- include/expression_dag.hh | 130 +++++++++++++++++++++++++++++++++----- include/parser.hh | 1 + 2 files changed, 115 insertions(+), 16 deletions(-) diff --git a/include/expression_dag.hh b/include/expression_dag.hh index 64f95e3..b57cfc5 100644 --- a/include/expression_dag.hh +++ b/include/expression_dag.hh @@ -15,6 +15,7 @@ #include "config.hh" #include "scalar_node.hh" #include "assert.hh" +#include "array.hh" namespace bparser { @@ -40,6 +41,8 @@ private: /// Result nodes, given as input. NodeVec results; + typedef std::pair InvDotNameAndScalar; + typedef std::map InvDotMap; /** * Used in the setup_result_storage to note number of unclosed nodes @@ -134,27 +137,45 @@ public: /** * Print ScalarExpression graph in the common dot format. - * Useful for understanding the DAG + * Useful for understanding the DAG. */ void print_in_dot2() { + print_in_dot2(InvDotMap()); + } + + /** + * Print ScalarExpression graph in the common dot format. + * Useful for understanding the DAG. Using the parser's map of var. Name -> Array find the inverse ScalarNodePtr -> var. Name + */ + void print_in_dot2(const std::map& symbols) { + print_in_dot2(create_inverse_map(symbols)); + } + + /** + * Print ScalarExpression graph in the common dot format. + * Useful for understanding the DAG. Using the map of ScalarNodePtr -> variableName + */ + void print_in_dot2(const InvDotMap& names) { + sort_nodes(); std::cout << "\n" << "----- begin cut here -----" << "\n"; std::cout << "digraph Expr {" << "\n"; std::cout << "/* definitions */" << "\n"; + std::cout << "edge [dir=back]" << "\n"; for (uint i = 0; i < sorted.size(); ++i) { - _print_dot_node_definition(sorted[i]); + _print_dot_node_definition(sorted[i],names); } std::cout << "/* end of definitions */" << "\n"; for (uint i = 0; i < sorted.size(); ++i) { for (uint in = 0; in < sorted[i]->n_inputs_; ++in) { std::cout << " "; - _print_dot_node(sorted[i]); + _print_dot_node_id(sorted[i]); std::cout << "\n -> "; - _print_dot_node(sorted[i]->inputs_[in]); + _print_dot_node_id(sorted[i]->inputs_[in]); std::cout << "\n\n"; } } @@ -162,30 +183,107 @@ public: std::cout << "----- end cut here -----" << "\n"; std::cout.flush(); } - void _print_dot_node(ScalarNodePtr node) { + + //Create a map of ScalarNodePtr -> (variable name, is_scalar) + InvDotMap create_inverse_map(const std::map& symbols) const { + InvDotMap inv_map; + if (symbols.empty()) return inv_map; + for (const auto& s : symbols) + { + for (const auto& n : s.second.elements()) { + inv_map[n] = std::pair(s.first, s.second.shape().empty()); + } + } + return inv_map; + } + + +private: + //Print the vertice identifier for dot + void _print_dot_node_id(const ScalarNodePtr& node) const { std::cout << node->op_name_ << "_" << (uintptr_t)node.get() << "__" << node->result_storage;// << std::endl; } - void _print_dot_node_definition(ScalarNodePtr node) { - _print_dot_node(node); + //Print how the vertice should look in dot + void _print_dot_node_definition(const ScalarNodePtr& node, const InvDotMap& invmap) const { + _print_dot_node_id(node); std::cout << ' '; - if (node->result_storage == ResultStorage::constant) { - std::cout << "[shape=circle,label=\"const " << *node->values_ << "\"]" << std::endl; + if (node->result_storage == ResultStorage::constant) { // Constant + std::cout << "[shape=circle,"; + + try { //If the constant has a name + std::string name(invmap.at(node).first); + std::cout << "label=\"" << name << ": " << *node->values_ << "\",group=\"" << name << '"'; + } + catch (std::out_of_range) { //No name + std::cout << "label=\"" << "const " << *node->values_ << '"'; + } + std::cout << "]" << std::endl; + } + + else if (node->result_storage == ResultStorage::constant_bool) { //Constant bool + std::cout << "[shape=circle,"; + + try { //If the constant has a name + std::string name(invmap.at(node).first); + std::cout << "label=\"" << name << ": " << *node->values_ << "\",group=\"" << name << '"'; + } + catch (std::out_of_range) { //No name + std::cout << "label=\"" << "const " << *node->values_ << '"'; + } + std::cout << "]" << std::endl; } - else if (node->result_storage == ResultStorage::constant_bool) { - std::cout << "[shape=circle,label=\"const " << *node->values_ << "\"]" << std::endl; + + else if (node->result_storage == ResultStorage::expr_result) { //Result + std::cout << "[shape=box,label=\"" << node->op_name_ << " [" << node->result_idx_ << "]" << "\"]" << std::endl; } - else if (node->result_storage == ResultStorage::expr_result) { - std::cout << "[shape=box,label=\"" << node->op_name_ << " " << node->result_idx_ << "\"]" << std::endl; + + else if (node->result_storage == ResultStorage::value) { // Value + + std::cout << "[shape=circle,"; + try { + std::string name(invmap.at(node).first); + bool scalar(invmap.at(node).second); + if (scalar) { + std::cout << "label=\"" << name << '"'; + } + else { + std::cout << "label=<" << name << "i" << '>'; + } + std::cout << ",group=\"" << name << '"'; + } + catch (std::out_of_range) { + std::cout << "label=<var>"; + } + + std::cout << "]" << std::endl; + } + + else if (node->result_storage == ResultStorage::value_copy) { //Value copy + std::cout << "[shape=circle,"; + try { + std::string name(invmap.at(node).first); + bool scalar(invmap.at(node).second); + if (scalar) { + std::cout << "label=\"" << name << '"'; + } + else { + std::cout << "label=<" << name << "i" << '>'; + } + std::cout << ",group=\"" << name << '"'; + } + catch (std::out_of_range) { + std::cout << "label=<var_cp>"; + } + std::cout << "]" << std::endl; } - else { + + else {//Temporary & other //Temporary & other std::cout << "[label=\"" << node->op_name_ << "\"]" << std::endl; } } - -private: void _print_i_node(uint i) { std::cout << sorted[i]->op_name_ << "_" << i << "_"<< sorted[i]->result_idx_; } diff --git a/include/parser.hh b/include/parser.hh index ade2953..781dbc8 100644 --- a/include/parser.hh +++ b/include/parser.hh @@ -191,6 +191,7 @@ public: //se.print_in_dot(); //se.print_in_dot2(); + //se.print_in_dot2(symbols_); processor = ProcessorBase::create_processor(se, max_vec_size, simd_size, arena); } From 12b37e909f8d269efbbdb8630c3715eab8b2774e Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Mon, 20 Oct 2025 19:08:09 +0200 Subject: [PATCH 04/14] More appropriate catch blocks --- include/expression_dag.hh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/expression_dag.hh b/include/expression_dag.hh index b57cfc5..ab32ccb 100644 --- a/include/expression_dag.hh +++ b/include/expression_dag.hh @@ -216,7 +216,7 @@ private: std::string name(invmap.at(node).first); std::cout << "label=\"" << name << ": " << *node->values_ << "\",group=\"" << name << '"'; } - catch (std::out_of_range) { //No name + catch (const std::out_of_range&) { //No name std::cout << "label=\"" << "const " << *node->values_ << '"'; } std::cout << "]" << std::endl; @@ -229,7 +229,7 @@ private: std::string name(invmap.at(node).first); std::cout << "label=\"" << name << ": " << *node->values_ << "\",group=\"" << name << '"'; } - catch (std::out_of_range) { //No name + catch (const std::out_of_range&) { //No name std::cout << "label=\"" << "const " << *node->values_ << '"'; } std::cout << "]" << std::endl; @@ -253,7 +253,7 @@ private: } std::cout << ",group=\"" << name << '"'; } - catch (std::out_of_range) { + catch (const std::out_of_range&) { std::cout << "label=<var>"; } @@ -273,7 +273,7 @@ private: } std::cout << ",group=\"" << name << '"'; } - catch (std::out_of_range) { + catch (const std::out_of_range&) { std::cout << "label=<var_cp>"; } std::cout << "]" << std::endl; From b44c9d7afdf64972293df751b071aae683e626b3 Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Sat, 1 Nov 2025 17:23:38 +0100 Subject: [PATCH 05/14] Matrix and vector norms --- include/array.hh | 87 +++++++++++++++++++++++++++++++++++++++ include/grammar.impl.hh | 4 ++ include/scalar_wrapper.hh | 37 +++++++++++++---- test/test_parser.cc | 8 ++++ 4 files changed, 129 insertions(+), 7 deletions(-) diff --git a/include/array.hh b/include/array.hh index b590b27..4f0c174 100644 --- a/include/array.hh +++ b/include/array.hh @@ -1216,6 +1216,93 @@ public: //return full_({}, *wrap_array(a).trace()); } + static Array norm1(const Array& a) { + switch (a.shape().size()) { + case 0: //scalar + Throw() << "Norms are not for scalar values" << "\n"; + break; + case 1: //vector + { + + Shape s; //empty Shape for scalar + Array r(s); + r.elements_[0U] = *wrap_array(a).lpNorm<1>(); + return r; + } + case 2: //matrix + { + Shape s; //empty Shape for scalar + Array r(s); + r.elements_[0U] = *wrap_array(a).colwise().lpNorm<1>().maxCoeff(); + return r; + } + default: + Throw() << "Norms are not avaiable for ND tensors" << "\n"; + } + } + + static Array norm2(const Array& a) { + switch (a.shape().size()) { + case 0: //scalar + Throw() << "Norms are not for scalar values" << "\n"; + break; + case 1: //vector + { + //Euclidean norm + Shape s; //empty Shape for scalar + Array r(s); + r.elements_[0U] = *wrap_array(a).norm(); + return r; + } + case 2: //matrix + { + //Spectral norm + Throw() << "norm2(matrix) not yet implemented" << "\n"; + Shape s; //empty Shape for scalar + Array r(s); + //r.elements_[0U] = *wrap_array(a); + return r; + } + default: + Throw() << "Norms are not avaiable for ND tensors" << "\n"; + } + } + + static Array normfro(const Array& a) { + if (a.shape().size() != 2) { + Throw() << "Frobenius norm is only defined for matrices" << "\n"; + } + + Shape s; + Array r(s); + r.elements_[0U] = *wrap_array(a).norm(); + return r; + } + + static Array norminf(const Array& a) { + switch (a.shape().size()) { + case 0: //scalar + Throw() << "Norms are not for scalar values" << "\n"; + break; + case 1: //vector + { + Shape s; //empty Shape for scalar + Array r(s); + r.elements_[0U] = *wrap_array(a).lpNorm(); + return r; + } + case 2: //matrix + { + Shape s; //empty Shape for scalar + Array r(s); + r.elements_[0U] = *wrap_array(a).rowwise().lpNorm<1>().maxCoeff(); + return r; + } + default: + Throw() << "Norms are not avaiable for ND tensors" << "\n"; + } + } + static Array flatten(const Array &tensor) { uint n_elements = shape_size(tensor.shape()); Shape res_shape(1, n_elements); diff --git a/include/grammar.impl.hh b/include/grammar.impl.hh index 8a5ad5c..082592f 100644 --- a/include/grammar.impl.hh +++ b/include/grammar.impl.hh @@ -181,6 +181,10 @@ struct grammar : qi::grammar { FN("maximum", binary_array<_max_>()) FN("diag" , &Array::diag) FN("tr" , &Array::trace) + FN("norm1" , &Array::norm1) + FN("norm2" , &Array::norm2) + FN("normfro", &Array::normfro) + FN("norminf", &Array::norminf) ; unary_op.add diff --git a/include/scalar_wrapper.hh b/include/scalar_wrapper.hh index 54dda38..5273e6d 100644 --- a/include/scalar_wrapper.hh +++ b/include/scalar_wrapper.hh @@ -49,8 +49,7 @@ namespace bparser { } inline bool operator==(const ScalarWrapper& b) const { - if (((***this).result_storage == constant && (**b).result_storage == constant ) || - ((***this).result_storage == constant_bool && (**b).result_storage == constant_bool) ) + if ((*this).is_constant() && (*this).have_same_result_storage(b)) return *(***this).values_ == *(**b).values_; return false; } @@ -78,6 +77,14 @@ namespace bparser { protected: ScalarNodePtr node; + inline bool is_constant() const { + return (***this).result_storage == constant || + (***this).result_storage == constant_bool; + } + + inline bool have_same_result_storage(const ScalarWrapper& b)const { + return (***this).result_storage == (**b).result_storage; + } }; //ScalarWrapper @@ -91,18 +98,25 @@ namespace bparser { } \ using std::OP; - /* +#define BIN_OP(OP) \ + inline ScalarWrapper OP(const ScalarWrapper& a,const ScalarWrapper& b) { \ + return ScalarWrapper::bin_op<_##OP##_>(a,b); \ + } \ + using std::OP; + + UN_OP(abs) //https://eigen.tuxfamily.org/dox/namespaceEigen.html#a54cc34b64b4935307efc06d56cd531df inline ScalarWrapper abs2(const ScalarWrapper& s) { return s*s; - }; - */ + } + - //UN_OP(sqrt) + UN_OP(sqrt) //UN_OP(exp) //UN_OP(log) + //UN_OP(log2) //UN_OP(log10) //UN_OP(sin) //UN_OP(sinh) @@ -116,9 +130,18 @@ namespace bparser { //UN_OP(ceil) //UN_OP(floor) + BIN_OP(max) + inline ScalarWrapper maxi(const ScalarWrapper& a, const ScalarWrapper& b) { + return ScalarWrapper::bin_op<_max_>(a, b); + } + BIN_OP(min) + inline ScalarWrapper mini(const ScalarWrapper& a, const ScalarWrapper& b) { + return ScalarWrapper::bin_op<_min_>(a, b); + } - + //BIN_OP(atan2) + //BIN_OP(pow) } //details } //bparser diff --git a/test/test_parser.cc b/test/test_parser.cc index 28b3c20..df23f82 100644 --- a/test/test_parser.cc +++ b/test/test_parser.cc @@ -261,6 +261,14 @@ void test_expression() { BP_ASSERT(test_expr("tr([[1,9,9],[9,1,9],[9,9,1]])", { 3 }, {})); + BP_ASSERT(test_expr("norm1([-4,-3,-2,-1,0,1,2,3,4])", {20}, {})); + BP_ASSERT(test_expr("norm1([[-4,-3,-2],[-1,0,1],[2,3,4]])", { 7 }, {})); + BP_ASSERT(test_expr("norm2([-4,-3,-2,-1,0,1,2,3,4])", { 7.745966692414834 }, {})); + //BP_ASSERT(test_expr("norm2([[-4,-3,-2],[-1,0,1],[2,3,4]])", { 7.3484692283495345 }, {})); + BP_ASSERT(test_expr("normfro([[-4,-3,-2],[-1,0,1],[2,3,4]])", { 7.745966692414834 }, {})); + BP_ASSERT(test_expr("norminf([-4,-3,-2,-1,0,1,2,3,4])", { 4 }, {})); + BP_ASSERT(test_expr("norminf([[-4,-3,-2],[-1,0,1],[2,3,4]])", { 9 }, {})); + BP_ASSERT(test_expr("abs(-1)+abs(0)+abs(1)", {2})); BP_ASSERT(test_expr("floor(-3.5)", {-4}, {})); BP_ASSERT(test_expr("ceil(-3.5)", {-3}, {})); From 5bb3f9ac2146beabed02ed407985677471cd6394 Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Sun, 9 Nov 2025 17:37:58 +0100 Subject: [PATCH 06/14] InverseMap now holds the indices of the node which is then printed to dot --- include/expression_dag.hh | 190 +++++++++++++++++++++++++------------- 1 file changed, 126 insertions(+), 64 deletions(-) diff --git a/include/expression_dag.hh b/include/expression_dag.hh index ab32ccb..43a194f 100644 --- a/include/expression_dag.hh +++ b/include/expression_dag.hh @@ -41,8 +41,8 @@ private: /// Result nodes, given as input. NodeVec results; - typedef std::pair InvDotNameAndScalar; - typedef std::map InvDotMap; + typedef std::pair InvDotNameAndIndices; + typedef std::map InvDotMap; /** * Used in the setup_result_storage to note number of unclosed nodes @@ -190,9 +190,12 @@ public: if (symbols.empty()) return inv_map; for (const auto& s : symbols) { - for (const auto& n : s.second.elements()) { - inv_map[n] = std::pair(s.first, s.second.shape().empty()); + for (MultiIdx idx(s.second.range()); idx.valid();idx.inc_src()) { + inv_map[s.second[idx]] = InvDotNameAndIndices(s.first, idx.indices()); } + /*for (const auto& n : s.second.elements()) { + inv_map[n] = InvDotNameAndIndices(s.first, s.second.shape().empty()); + }*/ } return inv_map; } @@ -209,79 +212,138 @@ private: _print_dot_node_id(node); std::cout << ' '; - if (node->result_storage == ResultStorage::constant) { // Constant - std::cout << "[shape=circle,"; - - try { //If the constant has a name - std::string name(invmap.at(node).first); - std::cout << "label=\"" << name << ": " << *node->values_ << "\",group=\"" << name << '"'; - } - catch (const std::out_of_range&) { //No name - std::cout << "label=\"" << "const " << *node->values_ << '"'; + switch (node->result_storage) { + case ResultStorage::constant: { // Constant + std::cout << "[shape=circle,"; + + try { //If the constant has a name + std::string name(invmap.at(node).first); + const MultiIdx::VecUint indices(invmap.at(node).second); + bool scalar(indices.empty()); + if (scalar) { + std::cout << "label=\"" << name << '=' << *node->values_ << '\"'; + } + else { + MultiIdx::VecUint::size_type size(indices.size()); + std::cout << "label=\"" << name << "["; + for (MultiIdx::VecUint::size_type i = 0; i < size; i++) { + std::cout << indices.at(i); + if (i != size - 1) { + std::cout << ','; + } + } + std::cout << "]"; + std::cout << '=' << *node->values_ << '\"'; + } + std::cout << ", group = \"" << name << '\"'; + } + catch (const std::out_of_range&) { //No name + std::cout << "label=\"" << "const " << *node->values_ << '"'; + } + std::cout << "]" << std::endl; + break; } - std::cout << "]" << std::endl; - } - else if (node->result_storage == ResultStorage::constant_bool) { //Constant bool - std::cout << "[shape=circle,"; - - try { //If the constant has a name - std::string name(invmap.at(node).first); - std::cout << "label=\"" << name << ": " << *node->values_ << "\",group=\"" << name << '"'; - } - catch (const std::out_of_range&) { //No name - std::cout << "label=\"" << "const " << *node->values_ << '"'; + case ResultStorage::constant_bool: { //Constant bool + std::cout << "[shape=circle,"; + + try { //If the constant has a name + std::string name(invmap.at(node).first); + const MultiIdx::VecUint indices(invmap.at(node).second); + bool scalar(indices.empty()); + if (scalar) { + std::cout << "label=\"" << name << '=' << *node->values_ << '\"'; + } + else { + MultiIdx::VecUint::size_type size(indices.size()); + std::cout << "label=\"" << name << "["; + for (MultiIdx::VecUint::size_type i = 0; i < size; i++) { + std::cout << indices.at(i); + if (i != size - 1) { + std::cout << ','; + } + } + std::cout << "]"; + std::cout << '=' << *node->values_ << '\"'; + } + std::cout << ", group = \"" << name << '\"'; + } + catch (const std::out_of_range&) { //No name + std::cout << "label=\"" << "const " << *node->values_ << '"'; + } + std::cout << "]" << std::endl; + break; } - std::cout << "]" << std::endl; - } - else if (node->result_storage == ResultStorage::expr_result) { //Result - std::cout << "[shape=box,label=\"" << node->op_name_ << " [" << node->result_idx_ << "]" << "\"]" << std::endl; - } - - else if (node->result_storage == ResultStorage::value) { // Value + case ResultStorage::expr_result: { //Result + std::cout << "[shape=box,label=\"" << node->op_name_ << " [" << node->result_idx_ << "]" << "\"]" << std::endl; + break; + } - std::cout << "[shape=circle,"; - try { - std::string name(invmap.at(node).first); - bool scalar(invmap.at(node).second); - if (scalar) { - std::cout << "label=\"" << name << '"'; + case ResultStorage::value: { // Value + std::cout << "[shape=circle,"; + try { + std::string name(invmap.at(node).first); + const MultiIdx::VecUint indices(invmap.at(node).second); + bool scalar(indices.empty()); + if (scalar) { + std::cout << "label=\"" << name << '"'; + } + else { + MultiIdx::VecUint::size_type size(indices.size()); + std::cout << "label=\"" << name << "["; + for (MultiIdx::VecUint::size_type i = 0; i < size; i++) { + std::cout << indices.at(i); + if (i != size - 1) { + std::cout << ','; + } + } + std::cout << "]\""; + } + std::cout << ",group=\"" << name << '"'; } - else { - std::cout << "label=<" << name << "i" << '>'; + catch (const std::out_of_range&) { + std::cout << "label=<var>"; } - std::cout << ",group=\"" << name << '"'; - } - catch (const std::out_of_range&) { - std::cout << "label=<var>"; + + std::cout << "]" << std::endl; + break; } - - std::cout << "]" << std::endl; - } - else if (node->result_storage == ResultStorage::value_copy) { //Value copy - std::cout << "[shape=circle,"; - try { - std::string name(invmap.at(node).first); - bool scalar(invmap.at(node).second); - if (scalar) { - std::cout << "label=\"" << name << '"'; + case ResultStorage::value_copy: { //Value copy + std::cout << "[shape=circle,"; + try { + std::string name(invmap.at(node).first); + MultiIdx::VecUint indices(invmap.at(node).second); + bool scalar(indices.empty()); + if (scalar) { + std::cout << "label=\"" << name << '"'; + } + else { + MultiIdx::VecUint::size_type size(indices.size()); + std::cout << "label=\"" << name << "["; + for (MultiIdx::VecUint::size_type i = 0; i < size; i++) { + std::cout << indices.at(i); + if (i != size - 1) { + std::cout << ','; + } + } + std::cout << "]\""; + } + std::cout << ",group=\"" << name << '"'; } - else { - std::cout << "label=<" << name << "i" << '>'; + catch (const std::out_of_range&) { + std::cout << "label=<var_cp>"; } - std::cout << ",group=\"" << name << '"'; - } - catch (const std::out_of_range&) { - std::cout << "label=<var_cp>"; + std::cout << "]" << std::endl; + break; } - std::cout << "]" << std::endl; - } - else {//Temporary & other //Temporary & other - std::cout << "[label=\"" << node->op_name_ << "\"]" << std::endl; - } + default: { //Temporary & other + std::cout << "[label=\"" << node->op_name_ << "\"]" << std::endl; + break; + } + } //switch } void _print_i_node(uint i) { From 69be136a871486f1369004034cac09e7b60ffa40 Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Sun, 9 Nov 2025 19:26:46 +0100 Subject: [PATCH 07/14] Attempt at computing spectral norm using eigenvalues. Requires comparison operators and static cast to double, not possible for Bparser --- include/array.hh | 14 +++++++---- include/scalar_wrapper.hh | 50 ++++++++++++++++++++++++++++++++++++++- test/test_parser.cc | 2 +- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/include/array.hh b/include/array.hh index 4f0c174..cbd88fd 100644 --- a/include/array.hh +++ b/include/array.hh @@ -1257,11 +1257,17 @@ public: case 2: //matrix { //Spectral norm - Throw() << "norm2(matrix) not yet implemented" << "\n"; - Shape s; //empty Shape for scalar + Throw() << "norm2(matrix) is not yet possible" << "\n"; + /*Shape s; //empty Shape for scalar Array r(s); - //r.elements_[0U] = *wrap_array(a); - return r; + + Eigen::MatrixX m( wrap_array(a) ); + + r.elements_[0U] = *details::sqrt((m.adjoint()*m).eigenvalues().real().maxCoeff()); + //computing eigenvalues would require static cast to double and comparison operators (<,<=,>,>=,!=,==) + //something which we cannot support + return r;*/ + break; } default: Throw() << "Norms are not avaiable for ND tensors" << "\n"; diff --git a/include/scalar_wrapper.hh b/include/scalar_wrapper.hh index 5273e6d..8a07d46 100644 --- a/include/scalar_wrapper.hh +++ b/include/scalar_wrapper.hh @@ -12,6 +12,7 @@ #include "scalar_node.hh" #include +//#include //impossible namespace bparser { namespace details { @@ -23,6 +24,10 @@ namespace bparser { ScalarWrapper(double d) : node(ScalarNode::create_const(d)) { ; } ScalarWrapper(ScalarNodePtr existing_ptr) : node(existing_ptr) { ; } + inline ScalarWrapper operator+() const { + return ScalarWrapper(*this); + } + inline ScalarWrapper operator-() const { return un_op<_minus_>(*this); } @@ -36,14 +41,29 @@ namespace bparser { return bin_op<_add_>(*this, b); } + inline ScalarWrapper& operator-=(const ScalarWrapper& b) { + node = bin_op<_sub_>(*this, b).get(); + return *this; + } + inline ScalarWrapper operator-(const ScalarWrapper& b) const { return bin_op<_sub_>(*this, b); } + inline ScalarWrapper& operator*=(const ScalarWrapper& b) { + node = bin_op<_mul_>(*this, b).get(); + return *this; + } + inline ScalarWrapper operator*(const ScalarWrapper& b) const { return bin_op<_mul_>(*this, b); } + inline ScalarWrapper& operator/=(const ScalarWrapper& b) { + node = bin_op<_div_>(*this, b).get(); + return *this; + } + inline ScalarWrapper operator/(const ScalarWrapper& b) const { return bin_op<_div_>(*this, b); } @@ -53,6 +73,34 @@ namespace bparser { return *(***this).values_ == *(**b).values_; return false; } + /* These do not make any sense with what we are trying to achieve + inline bool operator!=(const ScalarWrapper& b) const { + return !((*this) == b); + } + + inline bool operator<(const ScalarWrapper& b) const { + if ((*this).is_constant() && (*this).have_same_result_storage(b)) + return *(***this).values_ < *(**b).values_; + return false; + } + + inline bool operator<=(const ScalarWrapper& b) const { + if ((*this).is_constant() && (*this).have_same_result_storage(b)) + return *(***this).values_ <= *(**b).values_; + return false; + } + + inline bool operator>=(const ScalarWrapper& b) const { + if ((*this).is_constant() && (*this).have_same_result_storage(b)) + return *(***this).values_ >= *(**b).values_; + return false; + } + + inline bool operator>(const ScalarWrapper& b) const { + if ((*this).is_constant() && (*this).have_same_result_storage(b)) + return *(***this).values_ > *(**b).values_; + return false; + }*/ inline ScalarNodePtr operator*() const { //dereference @@ -111,7 +159,7 @@ namespace bparser { inline ScalarWrapper abs2(const ScalarWrapper& s) { return s*s; } - + UN_OP(sqrt) //UN_OP(exp) diff --git a/test/test_parser.cc b/test/test_parser.cc index df23f82..e226692 100644 --- a/test/test_parser.cc +++ b/test/test_parser.cc @@ -264,7 +264,7 @@ void test_expression() { BP_ASSERT(test_expr("norm1([-4,-3,-2,-1,0,1,2,3,4])", {20}, {})); BP_ASSERT(test_expr("norm1([[-4,-3,-2],[-1,0,1],[2,3,4]])", { 7 }, {})); BP_ASSERT(test_expr("norm2([-4,-3,-2,-1,0,1,2,3,4])", { 7.745966692414834 }, {})); - //BP_ASSERT(test_expr("norm2([[-4,-3,-2],[-1,0,1],[2,3,4]])", { 7.3484692283495345 }, {})); + //BP_ASSERT(test_expr("norm2([[-4,-3,-2],[-1,0,1],[2,3,4]])", { 7.3484692283495345 }, {})); //Spectral norm uses eigenvalues/singular values. Eigen uses comparison operators in the algorithm. Bparser does not like that BP_ASSERT(test_expr("normfro([[-4,-3,-2],[-1,0,1],[2,3,4]])", { 7.745966692414834 }, {})); BP_ASSERT(test_expr("norminf([-4,-3,-2,-1,0,1,2,3,4])", { 4 }, {})); BP_ASSERT(test_expr("norminf([[-4,-3,-2],[-1,0,1],[2,3,4]])", { 9 }, {})); From 8b5e0eb4c73eb7e251cbd7e74b0bf137c885a6f4 Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Sun, 23 Nov 2025 12:36:56 +0100 Subject: [PATCH 08/14] min, max, sum of all coefficients --- include/array.hh | 21 +++++++++++++++++++++ include/grammar.impl.hh | 3 +++ test/test_parser.cc | 9 +++++++++ 3 files changed, 33 insertions(+) diff --git a/include/array.hh b/include/array.hh index cbd88fd..57df92f 100644 --- a/include/array.hh +++ b/include/array.hh @@ -1309,6 +1309,27 @@ public: } } + static Array max(const Array& a) { + Shape s; + Array r(s); + r.elements_[0U] = *wrap_array(flatten(a)).maxCoeff(); + return r; + } + + static Array min(const Array& a) { + Shape s; + Array r(s); + r.elements_[0U] = *wrap_array(flatten(a)).minCoeff(); + return r; + } + + static Array sum(const Array& a) { + Shape s; + Array r(s); + r.elements_[0U] = *wrap_array(flatten(a)).sum(); + return r; + } + static Array flatten(const Array &tensor) { uint n_elements = shape_size(tensor.shape()); Shape res_shape(1, n_elements); diff --git a/include/grammar.impl.hh b/include/grammar.impl.hh index 082592f..410055b 100644 --- a/include/grammar.impl.hh +++ b/include/grammar.impl.hh @@ -179,12 +179,15 @@ struct grammar : qi::grammar { FN("power" , binary_array<_pow_>()) FN("minimum", binary_array<_min_>()) FN("maximum", binary_array<_max_>()) + FN("min" , &Array::min) + FN("max" , &Array::max) FN("diag" , &Array::diag) FN("tr" , &Array::trace) FN("norm1" , &Array::norm1) FN("norm2" , &Array::norm2) FN("normfro", &Array::normfro) FN("norminf", &Array::norminf) + FN("sum" , &Array::sum) ; unary_op.add diff --git a/test/test_parser.cc b/test/test_parser.cc index e226692..457f1a6 100644 --- a/test/test_parser.cc +++ b/test/test_parser.cc @@ -304,6 +304,15 @@ void test_expression() { // BP_ASSERT(test_expr("norm([2, 3])", {5})); BP_ASSERT(test_expr("minimum([1,2,3], [0,4,3])", {0,2,3})); BP_ASSERT(test_expr("maximum([1,2,3], [0,4,3])", {1,4,3})); + BP_ASSERT(test_expr("min([1,2,3])", {1})); + BP_ASSERT(test_expr("min([-3,-2,-1,0,1,2,3])", { -3 })); + BP_ASSERT(test_expr("min([[[8,7],[6,5]],[[4,3],[2,1]]])", { 1 })); + BP_ASSERT(test_expr("max([1,2,3])", { 3 })); + BP_ASSERT(test_expr("max([-3,-2,-1,0,1,2,3])", { 3 })); + BP_ASSERT(test_expr("max([[[8,7],[6,5]],[[4,3],[2,1]]])", { 8 })); + BP_ASSERT(test_expr("sum([1,2,3])", { 6 })); + BP_ASSERT(test_expr("sum([-3,-2,-1,0,1,2,3])", { 0 })); + BP_ASSERT(test_expr("sum([[[8,7],[6,5]],[[4,3],[2,1]]])", { 1+2+3+4+5+6+7+8 })); /** * All bool tests have defined: From c061f775255963732f5e51e5cc4edd0efa05ec9d Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Sun, 23 Nov 2025 16:50:00 +0100 Subject: [PATCH 09/14] cross product for vectors --- include/array.hh | 57 ++++++++++++++++++++++++++++++++++----- include/grammar.impl.hh | 1 + include/scalar_wrapper.hh | 1 + test/test_parser.cc | 7 +++++ 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/include/array.hh b/include/array.hh index 57df92f..b6378de 100644 --- a/include/array.hh +++ b/include/array.hh @@ -859,12 +859,12 @@ public: return result; } - + typedef Eigen::MatrixX WrappedArray; //Wraps the ScalarNodes of an Array into an Eigen Matrix of ScalarWrappers. //Vectors will be column vectors. Eigen does not support vectors without orientation. //Cannot wrap scalars. To wrap scalars, use the bparser::details::ScalarWrapper constructor - static Eigen::MatrixX wrap_array(const bparser::Array& a) { + static WrappedArray wrap_array(const bparser::Array& a) { MultiIdx idx(a.range()); return wrap_array(a, idx); } @@ -872,7 +872,7 @@ public: //Wraps the ScalarNodes of an Array accessed via MultiIdx.idx_trg() created from supplied MultiIdxRange into an Eigen Matrix of ScalarWrapper //Vectors will be column vectors. Eigen does not support vectors without orientation. //Cannot wrap scalars. To wrap scalars, use the bparser::details::ScalarWrapper constructor - static Eigen::MatrixX wrap_array(const bparser::Array& a, MultiIdxRange& range) { + static WrappedArray wrap_array(const bparser::Array& a, MultiIdxRange& range) { MultiIdx idx (range); return wrap_array(a, idx); } @@ -880,7 +880,7 @@ public: //Wraps the ScalarNodes of an Array accessed via MultiIdx.idx_trg() into an Eigen Matrix of ScalarWrapper //Vectors will be column vectors. Eigen does not support vectors without orientation. //Cannot wrap scalars. To wrap scalars, use the bparser::details::ScalarWrapper constructor - static Eigen::MatrixX wrap_array(const bparser::Array& a, MultiIdx& index) { + static WrappedArray wrap_array(const bparser::Array& a, MultiIdx& index) { using namespace details; Shape trg_shape = index.range_.target_shape(); @@ -1127,15 +1127,15 @@ public: //std::cout << print_shape(result_shape) << std::endl; Array result(result_shape); - bool should_transpose = a.shape().size() == 1; + //bool should_transpose = a.shape().size() == 1; for (MultiIdx result_idx(result.range()), a_idx(a_range), b_idx(b_range); result_idx.valid(); ) { - Eigen::MatrixX m_a = wrap_array(a, a_idx); - Eigen::MatrixX m_b = wrap_array(b, b_idx); + WrappedArray m_a = wrap_array(a, a_idx); + WrappedArray m_b = wrap_array(b, b_idx); Array matmult = unwrap_array(m_a * m_b); @@ -1330,6 +1330,49 @@ public: return r; } + static Array cross(const Array& a, const Array& b) { + Shape a_shape(a.shape()); + Shape b_shape(b.shape()); + if (a_shape.size() != 1 && a_shape.size() != 2) + Throw() << "Array a of cross product has wrong dimensions"; + if (b_shape.size() != 1 && b_shape.size() != 2) + Throw() << "Array b of cross product has wrong dimensions"; + if (a_shape.back() != 2 && a_shape.back() != 3) + Throw() << "Array a of cross product doesn't have the right amount of elements"; + if (b_shape.back() != 2 && b_shape.back() != 3) + Throw() << "Array b of cross product doesn't have the right amount of elements"; + + //for (MultiIdx) //TODO: Support multiple vector cross-products + //{ + WrappedArray m_a = wrap_array(a); + WrappedArray m_b = wrap_array(b); + + if (m_a.cols() == 1) m_a.transposeInPlace(); //col -> row + if (m_b.cols() == 1) m_b.transposeInPlace(); //col -> row + + if (m_a.cols() == 2 && m_b.cols() == 3) { + m_a.conservativeResize(Eigen::NoChange, 3); + m_a(0, 2) = details::ScalarWrapper(details::ScalarNode::create_zero()); + } + else if (m_b.cols() == 2 && m_a.cols() == 3) { + m_b.conservativeResize(Eigen::NoChange, 3); + m_b(0, 2) = details::ScalarWrapper(details::ScalarNode::create_zero()); + } + + WrappedArray cross; + if (m_a.cols() == 2 && m_b.cols() == 2) { + //cross = Eigen::Ref>(m_a).cross(Eigen::Ref>(m_b)); // Only in Eigen 5.0.0+ + cross = WrappedArray(1, 1); + cross(0, 0) = (m_a(0, 0) * m_b(0, 1) - m_b(0, 0) * m_a(0, 1)); + } + else { + cross = Eigen::Ref>(m_a).cross(Eigen::Ref>(m_b)); + } + Array arr = unwrap_array(cross, true); + //} + return Array(arr); + } + static Array flatten(const Array &tensor) { uint n_elements = shape_size(tensor.shape()); Shape res_shape(1, n_elements); diff --git a/include/grammar.impl.hh b/include/grammar.impl.hh index 410055b..c03be4a 100644 --- a/include/grammar.impl.hh +++ b/include/grammar.impl.hh @@ -188,6 +188,7 @@ struct grammar : qi::grammar { FN("normfro", &Array::normfro) FN("norminf", &Array::norminf) FN("sum" , &Array::sum) + FN("cross" , &Array::cross) ; unary_op.add diff --git a/include/scalar_wrapper.hh b/include/scalar_wrapper.hh index 8a07d46..7aa050f 100644 --- a/include/scalar_wrapper.hh +++ b/include/scalar_wrapper.hh @@ -12,6 +12,7 @@ #include "scalar_node.hh" #include +#include //#include //impossible namespace bparser { diff --git a/test/test_parser.cc b/test/test_parser.cc index 457f1a6..4083d47 100644 --- a/test/test_parser.cc +++ b/test/test_parser.cc @@ -314,6 +314,13 @@ void test_expression() { BP_ASSERT(test_expr("sum([-3,-2,-1,0,1,2,3])", { 0 })); BP_ASSERT(test_expr("sum([[[8,7],[6,5]],[[4,3],[2,1]]])", { 1+2+3+4+5+6+7+8 })); + BP_ASSERT(test_expr("cross([1,2,3],[4,5,6])", {-3, 6, -3}, {3})); + BP_ASSERT(test_expr("cross([1,2],[4,5,6])", { 12, -6, -3 }, { 3 })); + BP_ASSERT(test_expr("cross([1,2,0],[4,5,6])", { 12, -6, -3 }, { 3 })); + BP_ASSERT(test_expr("cross([1,2],[4,5])", { -3 }, {})); + //BP_ASSERT(test_expr("cross([[1,2,3],[4,5,6]," + + // "[[4,5,6],[1,2,3]] )")); + /** * All bool tests have defined: * v1 - scalar array == [88..134] From 591eb620692d80b19d33466ddf66c5210630329a Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Sun, 7 Dec 2025 19:17:54 +0100 Subject: [PATCH 10/14] Created two more Make targets, to generete and run an expanded DAG from bparsed string --- .gitignore | 5 +- CMakeLists.txt | 28 +++++++++ include/expression_dag.hh | 115 ++++++++++++++++++++++++++++++++++-- nitpick/nitpick_def.cc | 54 +++++++++++++++++ nitpick/nitpick_generate.cc | 16 +++++ nitpick/nitpick_include.hh | 10 ++++ nitpick/nitpick_run.cc | 19 ++++++ 7 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 nitpick/nitpick_def.cc create mode 100644 nitpick/nitpick_generate.cc create mode 100644 nitpick/nitpick_include.hh create mode 100644 nitpick/nitpick_run.cc diff --git a/.gitignore b/.gitignore index dd5dd11..3485587 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,7 @@ build/* .cproject .project .vs -CMakeSettings.json \ No newline at end of file +CMakeSettings.json + +# Autogenerated files +/nitpick/autogenerated.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index ae5cbef..f029243 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -290,3 +290,31 @@ define_test(test_grammar bparser) define_test(test_processor bparser) #is it broken? -LV define_test(test_speed bparser) define_test(test_simd) + +set(src_name "nitpick_generate") +set(nit_source "${CMAKE_CURRENT_SOURCE_DIR}/nitpick/${src_name}.cc") +set(nit_binary "${src_name}_bin") +set(nit_name "${src_name}") + +add_executable(${nit_binary} EXCLUDE_FROM_ALL ${nit_source} ) +add_dependencies(${nit_binary} bparser) +target_link_libraries(${nit_binary} bparser) + +add_custom_target(${nit_name} +COMMAND "$" +DEPENDS ${nit_binary} +WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/nitpick") + +set(src_name "nitpick_run") +set(nit_source "${CMAKE_CURRENT_SOURCE_DIR}/nitpick/${src_name}.cc") +set(nit_binary "${src_name}_bin") +set(nit_name "${src_name}") + +add_executable(${nit_binary} EXCLUDE_FROM_ALL ${nit_source} ) +add_dependencies(${nit_binary} bparser) +target_link_libraries(${nit_binary} bparser) + +add_custom_target(${nit_name} +COMMAND "$" +DEPENDS ${nit_binary} +WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/nitpick") diff --git a/include/expression_dag.hh b/include/expression_dag.hh index ab32ccb..c69e279 100644 --- a/include/expression_dag.hh +++ b/include/expression_dag.hh @@ -43,6 +43,7 @@ private: typedef std::pair InvDotNameAndScalar; typedef std::map InvDotMap; + typedef std::unordered_map CXXVarMap; /** * Used in the setup_result_storage to note number of unclosed nodes @@ -173,9 +174,9 @@ public: for (uint i = 0; i < sorted.size(); ++i) { for (uint in = 0; in < sorted[i]->n_inputs_; ++in) { std::cout << " "; - _print_dot_node_id(sorted[i]); + std::cout << _get_dot_node_id(sorted[i]); std::cout << "\n -> "; - _print_dot_node_id(sorted[i]->inputs_[in]); + std::cout << _get_dot_node_id(sorted[i]->inputs_[in]); std::cout << "\n\n"; } } @@ -183,6 +184,53 @@ public: std::cout << "----- end cut here -----" << "\n"; std::cout.flush(); } + + std::string print_in_cxx(/*const CXXVarMap& map*/) { + std::ostringstream result; + NodeVec result_nodes; + + result << "//AUTOGENERATED This file has been autogenerated by bparser::ExpressionDAG::print_in_cxx" << "\n"; + result << "\n"; + result << "#ifndef NITPICK_INCLUDE_ONLY" << "\n"; + result << "#include \"parser.hh\"" << "\n"; + result << "using namespace bparser;" << "\n"; + result << "using namespace bparser::details;" << "\n"; + result << "int main(){ //This is here only to stop any compiler warnings, do not run this file as is" << "\n"; + result << "#endif //NITPICK_INCLUDE_ONLY" << "\n"; + result << "\n"; + + //Print nodes + for (uint i = sorted.size(); i-- > 0U; ) { // N-1,N-2,... 0 + + result << _get_cxx_node_definition(sorted[i]); + //result << "\n"; + if (sorted[i]->result_storage == expr_result) { + result_nodes.push_back(sorted[i]); + } + } + result << "\n\n"; + //Print results + for (uint i = 0U; i < result_nodes.size(); ++i) { + result << "ScalarNodePtr " << "r" << i << " = " << _get_cxx_result(result_nodes[i]); + } + //Print final dag + result << "ExpressionDAG se({"; + for (uint i = 0U; i < result_nodes.size(); ++i) { + result << "r" << i; + if (i < result_nodes.size() - 1) { + result << ", "; + } + } + result << "}); //Do not rename this DAG, it is used later in the nitpick_run.cc file"; + + result << "\n"; + result << "#ifndef NITPICK_INCLUDE_ONLY" << "\n"; + result << "} //main" << "\n"; + result << "#endif //NITPICK_INCLUDE_ONLY" << "\n"; + //std::cout << result.str(); + return result.str(); + + } //Create a map of ScalarNodePtr -> (variable name, is_scalar) InvDotMap create_inverse_map(const std::map& symbols) const { @@ -200,13 +248,13 @@ public: private: //Print the vertice identifier for dot - void _print_dot_node_id(const ScalarNodePtr& node) const { - std::cout << node->op_name_ << "_" << (uintptr_t)node.get() << "__" << node->result_storage;// << std::endl; + std::string _get_dot_node_id(const ScalarNodePtr& node) const { + return (std::ostringstream() << node->op_name_ << "_" << (uintptr_t)node.get() << "__" << node->result_storage).str();// << std::endl; } //Print how the vertice should look in dot void _print_dot_node_definition(const ScalarNodePtr& node, const InvDotMap& invmap) const { - _print_dot_node_id(node); + std::cout << _get_dot_node_id(node); std::cout << ' '; if (node->result_storage == ResultStorage::constant) { // Constant @@ -284,6 +332,63 @@ private: } } + std::string _get_cxx_node_id(const ScalarNodePtr& node) { + return _get_dot_node_id(node); + } + + std::string _get_cxx_input_ids(const ScalarNodePtr& node) { + std::string result; + for (uint in = 0; in < node->n_inputs_; ++in) { + result += _get_cxx_node_id(node->inputs_[in]); + if (in < node->n_inputs_ - 1) { + result += ", "; + } + } + return result; + } + + std::string _get_cxx_node_definition(const ScalarNodePtr& node) { + std::ostringstream result; + + result << "ScalarNodePtr " << _get_cxx_node_id(node) << " = "; + switch (node->result_storage) + { + case ResultStorage::value: + case ResultStorage::constant:{ + result << "ScalarNode::create_const(" << *node->values_ << ");\n"; + break; + } + case ResultStorage::constant_bool: { + result << "ScalarNode::create_const_bool(" << *node->values_ << ");\n"; + break; + } + /*case ResultStorage::value: { + result << "ScalarNode::create_const(" << *node->values_ << ");\n"; + break; + } + case ResultStorage::value_copy: { + result << "ScalarNode::create_const(" << *node->values_ << ");\n"; + break; + }*/ + case ResultStorage::expr_result: + case ResultStorage::temporary: { + result << "ScalarNode::create<_" << node->op_name_ << "_>(" << _get_cxx_input_ids(node) << ");\n"; + break; + } + /*case ResultStorage::expr_result: { + result << "ScalarNode::create_result(" << _get_cxx_node_id(node->inputs_[0]) << ", " << "???" << ");\n"; + break; + }*/ + default: + break; + } + return result.str(); + } + + std::string _get_cxx_result(const ScalarNodePtr& node/*, std::string idx*/) { + return "ScalarNode::create_result(" + _get_cxx_node_id(node) + ", " + "???" + ");\n"; + } + void _print_i_node(uint i) { std::cout << sorted[i]->op_name_ << "_" << i << "_"<< sorted[i]->result_idx_; } diff --git a/nitpick/nitpick_def.cc b/nitpick/nitpick_def.cc new file mode 100644 index 0000000..ca16fb6 --- /dev/null +++ b/nitpick/nitpick_def.cc @@ -0,0 +1,54 @@ +/* + * nitpick_def.c + * + * Created on: Dec 7, 2025 + * Author: LV + */ + +/* +* Prepare an environment for both DAG generation and then subsequent running +*/ + +#ifndef NITPICK_INCLUDE_ONLY +#include "nitpick_include.hh" +#endif //NITPICK_INCLUDE_ONLY + +using namespace bparser; + +#ifndef NITPICK_INCLUDE_ONLY +int main() { +#endif //NITPICK_INCLUDE_ONLY + + // Define own value vectors, preferably aligned. + constexpr uint vec_size = 8; + + double v1[vec_size * 3]; + for (uint i = 0; i < vec_size * 3; ++i) { + v1[i] = i; + } + double v2[vec_size * 3]; + for (uint i = 0; i < vec_size * 3; ++i) { + v2[i] = 2; + } + double vres[vec_size * 3]; + + // Create parser, give the size of the value spaces. + // That is maximal allocated space. Actual values and + // active subset can be changed between evaluations of the expression. + Parser p(vec_size); + // parse an expression. + p.parse("1 * v1 + cs1 * v2"); + + // "cs1" constant with shape {}, i.e. scalar and values {2}. + p.set_constant("cs1", {}, { 2 }); + // "cv1" vector constant with shape {3} + p.set_constant("cv1", { 3 }, { 1, 2, 3 }); + // "v1" variable with shape {3}; v1 is pointer to the value space + p.set_variable("v1", { 3 }, v1); + p.set_variable("v2", { 3 }, v2); + // Set the result variable (the last line of the expression) + p.set_variable("_result_", { 3 }, vres); + +#ifndef NITPICK_INCLUDE_ONLY +} +#endif //NITPICK_INCLUDE_ONLY \ No newline at end of file diff --git a/nitpick/nitpick_generate.cc b/nitpick/nitpick_generate.cc new file mode 100644 index 0000000..35fafc8 --- /dev/null +++ b/nitpick/nitpick_generate.cc @@ -0,0 +1,16 @@ +#include "nitpick_include.hh" +#include + +int main() { +#define NITPICK_INCLUDE_ONLY +#include "nitpick_def.cc" + // Compile the expression into internal processor. + p.compile(); + + ExpressionDAG dag(p.result_array().elements()); + + std::ofstream file(NITPICK_AUTOGENERATED_FILE_NAME); + file << dag.print_in_cxx(); + file.close(); + std::cout << "File " << std::filesystem::current_path() << " " << NITPICK_AUTOGENERATED_FILE_NAME << " created"; +} \ No newline at end of file diff --git a/nitpick/nitpick_include.hh b/nitpick/nitpick_include.hh new file mode 100644 index 0000000..d457b2e --- /dev/null +++ b/nitpick/nitpick_include.hh @@ -0,0 +1,10 @@ +#ifndef NITPICK_INCLUDE_HH +#define NITPICK_INCLUDE_HH + +#define NITPICK_AUTOGENERATED_FILE_NAME "autogenerated.cc" //Keep the same in .gitignore + +#include "parser.hh" +#include +#include + +#endif //NITPICK_INCLUDE_HH \ No newline at end of file diff --git a/nitpick/nitpick_run.cc b/nitpick/nitpick_run.cc new file mode 100644 index 0000000..6c78e89 --- /dev/null +++ b/nitpick/nitpick_run.cc @@ -0,0 +1,19 @@ +#include "nitpick_include.hh" + +int main() { +// only get us the variables +#define NITPICK_INCLUDE_ONLY +#include "nitpick_def.cc" //include all the variables used to generate the file + +#include NITPICK_AUTOGENERATED_FILE_NAME + + ProcessorBase* processor = ProcessorBase::create_processor(se, vec_size, bparser::get_simd_size(), nullptr); + + std::vector subset = { 0, 1 }; + + processor->set_subset(subset); + processor->run(); + + // Result in the 'vres' value space. + //std::cout << print_vec(vres, 3 * vec_size); +} \ No newline at end of file From 717f3c9aea7ae28b732e896bd76ab316bee511c3 Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Mon, 29 Dec 2025 15:41:52 +0100 Subject: [PATCH 11/14] Nitpick working on basic example --- include/expression_dag.hh | 47 ++++++++++++++++++++++--------------- include/test_tools.hh | 2 +- nitpick/nitpick_def.cc | 29 ++++++++++++++++------- nitpick/nitpick_generate.cc | 4 +++- nitpick/nitpick_include.hh | 34 +++++++++++++++++++++++++++ nitpick/nitpick_run.cc | 6 +++-- 6 files changed, 91 insertions(+), 31 deletions(-) diff --git a/include/expression_dag.hh b/include/expression_dag.hh index c69e279..5b51776 100644 --- a/include/expression_dag.hh +++ b/include/expression_dag.hh @@ -43,7 +43,7 @@ private: typedef std::pair InvDotNameAndScalar; typedef std::map InvDotMap; - typedef std::unordered_map CXXVarMap; + typedef std::unordered_map CXXVarMap; /** * Used in the setup_result_storage to note number of unclosed nodes @@ -185,7 +185,7 @@ public: std::cout.flush(); } - std::string print_in_cxx(/*const CXXVarMap& map*/) { + std::string print_in_cxx(const CXXVarMap& map) { std::ostringstream result; NodeVec result_nodes; @@ -195,14 +195,14 @@ public: result << "#include \"parser.hh\"" << "\n"; result << "using namespace bparser;" << "\n"; result << "using namespace bparser::details;" << "\n"; - result << "int main(){ //This is here only to stop any compiler warnings, do not run this file as is" << "\n"; + result << "int main(){ //This is here only to stop any IDE warnings, do not run this file as is" << "\n"; result << "#endif //NITPICK_INCLUDE_ONLY" << "\n"; result << "\n"; //Print nodes for (uint i = sorted.size(); i-- > 0U; ) { // N-1,N-2,... 0 - result << _get_cxx_node_definition(sorted[i]); + result << _get_cxx_node_definition(sorted[i],map); //result << "\n"; if (sorted[i]->result_storage == expr_result) { result_nodes.push_back(sorted[i]); @@ -211,7 +211,7 @@ public: result << "\n\n"; //Print results for (uint i = 0U; i < result_nodes.size(); ++i) { - result << "ScalarNodePtr " << "r" << i << " = " << _get_cxx_result(result_nodes[i]); + result << "ScalarNodePtr " << "r" << i << " = " << _get_cxx_result(result_nodes[i],map); } //Print final dag result << "ExpressionDAG se({"; @@ -332,11 +332,11 @@ private: } } - std::string _get_cxx_node_id(const ScalarNodePtr& node) { + std::string _get_cxx_node_id(const ScalarNodePtr& node) const { return _get_dot_node_id(node); } - std::string _get_cxx_input_ids(const ScalarNodePtr& node) { + std::string _get_cxx_input_ids(const ScalarNodePtr& node) const { std::string result; for (uint in = 0; in < node->n_inputs_; ++in) { result += _get_cxx_node_id(node->inputs_[in]); @@ -347,29 +347,38 @@ private: return result; } - std::string _get_cxx_node_definition(const ScalarNodePtr& node) { + std::string _get_cxx_node_definition(const ScalarNodePtr& node, const CXXVarMap& map) const { std::ostringstream result; result << "ScalarNodePtr " << _get_cxx_node_id(node) << " = "; switch (node->result_storage) { - case ResultStorage::value: - case ResultStorage::constant:{ - result << "ScalarNode::create_const(" << *node->values_ << ");\n"; - break; + case ResultStorage::constant: { + if (map.count(node->values_) == 1){ + result << "ScalarNode::create_const(node_map[\"" << map.at(node->values_) << "\"]);\n"; + } + else { + result << "ScalarNode::create_const(" << *node->values_ << ");\n"; } + break; + } case ResultStorage::constant_bool: { - result << "ScalarNode::create_const_bool(" << *node->values_ << ");\n"; + if (map.count(node->values_) == 1) { + result << "ScalarNode::create_const_bool(node_map[\"" << map.at(node->values_) << "\"]);\n"; + } + else { + result << "ScalarNode::create_const_bool(" << *node->values_ << ");\n"; + } break; } - /*case ResultStorage::value: { - result << "ScalarNode::create_const(" << *node->values_ << ");\n"; + case ResultStorage::value: { + result << "ScalarNode::create_value(node_map[\"" << map.at(node->values_) << "\"]);\n"; break; } case ResultStorage::value_copy: { - result << "ScalarNode::create_const(" << *node->values_ << ");\n"; + result << "ScalarNode::create_val_copy(node_map[\"" << map.at(node->values_) << "\"]);\n"; break; - }*/ + } case ResultStorage::expr_result: case ResultStorage::temporary: { result << "ScalarNode::create<_" << node->op_name_ << "_>(" << _get_cxx_input_ids(node) << ");\n"; @@ -385,8 +394,8 @@ private: return result.str(); } - std::string _get_cxx_result(const ScalarNodePtr& node/*, std::string idx*/) { - return "ScalarNode::create_result(" + _get_cxx_node_id(node) + ", " + "???" + ");\n"; + std::string _get_cxx_result(const ScalarNodePtr& node, const CXXVarMap& map) const { + return "ScalarNode::create_result(" + _get_cxx_node_id(node) + ", node_map[\"" + map.at(node->values_) + "\"]);\n"; } void _print_i_node(uint i) { diff --git a/include/test_tools.hh b/include/test_tools.hh index 498812d..6e53dce 100644 --- a/include/test_tools.hh +++ b/include/test_tools.hh @@ -8,7 +8,7 @@ #ifndef INCLUDE_TEST_TOOLS_HH_ #define INCLUDE_TEST_TOOLS_HH_ - +#include "config.hh" #include "assert.hh" #include diff --git a/nitpick/nitpick_def.cc b/nitpick/nitpick_def.cc index ca16fb6..a8e6286 100644 --- a/nitpick/nitpick_def.cc +++ b/nitpick/nitpick_def.cc @@ -20,7 +20,15 @@ int main() { #endif //NITPICK_INCLUDE_ONLY // Define own value vectors, preferably aligned. + // These variable names are used in macros and/or autogenerated files. Renaming them will cause issues constexpr uint vec_size = 8; + constexpr uint max_vec_size = vec_size; + + // These variable names are used in macros and/or autogenerated files. Renaming them will cause issues + std::unordered_map inv_map; //given to the print_in_cxx function to generate the file + std::unordered_map node_map; //is used by the generated file to use the same double pointers as the parser + + //Memory allocation double v1[vec_size * 3]; for (uint i = 0; i < vec_size * 3; ++i) { @@ -30,24 +38,29 @@ int main() { for (uint i = 0; i < vec_size * 3; ++i) { v2[i] = 2; } - double vres[vec_size * 3]; + constexpr int vres_size = vec_size * 3; + double vres[vres_size]; - // Create parser, give the size of the value spaces. + // Create parser, give the size of the value spaces. // That is maximal allocated space. Actual values and // active subset can be changed between evaluations of the expression. - Parser p(vec_size); + // This variable name is used in macros and/or autogenerated files. Renaming it will cause issues + Parser p(max_vec_size); + + // parse an expression. p.parse("1 * v1 + cs1 * v2"); + //TODO: Create the SET_CONSTANT macro // "cs1" constant with shape {}, i.e. scalar and values {2}. - p.set_constant("cs1", {}, { 2 }); + p.set_constant("cs1", {}, {2}); // "cv1" vector constant with shape {3} - p.set_constant("cv1", { 3 }, { 1, 2, 3 }); + p.set_constant("cv1", {3}, {1, 2, 3}); // "v1" variable with shape {3}; v1 is pointer to the value space - p.set_variable("v1", { 3 }, v1); - p.set_variable("v2", { 3 }, v2); + P_SET_VARIABLE(v1, { 3 }, v1); + P_SET_VARIABLE(v2, { 3 }, v2); // Set the result variable (the last line of the expression) - p.set_variable("_result_", { 3 }, vres); + P_SET_VARIABLE(_result_, { 3 }, vres); #ifndef NITPICK_INCLUDE_ONLY } diff --git a/nitpick/nitpick_generate.cc b/nitpick/nitpick_generate.cc index 35fafc8..bf7c418 100644 --- a/nitpick/nitpick_generate.cc +++ b/nitpick/nitpick_generate.cc @@ -2,15 +2,17 @@ #include int main() { + #define NITPICK_INCLUDE_ONLY #include "nitpick_def.cc" + // Compile the expression into internal processor. p.compile(); ExpressionDAG dag(p.result_array().elements()); std::ofstream file(NITPICK_AUTOGENERATED_FILE_NAME); - file << dag.print_in_cxx(); + file << dag.print_in_cxx(inv_map); file.close(); std::cout << "File " << std::filesystem::current_path() << " " << NITPICK_AUTOGENERATED_FILE_NAME << " created"; } \ No newline at end of file diff --git a/nitpick/nitpick_include.hh b/nitpick/nitpick_include.hh index d457b2e..f8d7a7f 100644 --- a/nitpick/nitpick_include.hh +++ b/nitpick/nitpick_include.hh @@ -3,8 +3,42 @@ #define NITPICK_AUTOGENERATED_FILE_NAME "autogenerated.cc" //Keep the same in .gitignore +#define P_SET_(TYPE, NAME, SHAPE, POINTER) \ + Array NAME##_array = Array::value(POINTER, max_vec_size, SHAPE); \ + \ + for (MultiIdx idx(NAME##_array.range()); idx.valid(); idx.inc_src()) { \ + std::string var_name = get_var_name(#NAME, idx.indices()); \ + inv_map[NAME##_array[idx]->values_] = var_name; \ + node_map[var_name] = NAME##_array[idx]->values_; \ + } \ + p.set_##TYPE(#NAME, SHAPE, POINTER); + +#define P_SET_VARIABLE(NAME, SHAPE, POINTER) P_SET_(variable, NAME, SHAPE, POINTER) +//#define P_SET_CONSTANT(NAME, SHAPE, POINTER) P_SET_(constant, NAME, SHAPE, POINTER) +#define P_SET_VAR_COPY(NAME, SHAPE, POINTER) P_SET_(var_copy, NAME, SHAPE, POINTER) + +#include "test_tools.hh" #include "parser.hh" #include #include +std::string get_var_name(const std::string & name, const bparser::MultiIdx::VecUint & indices); + +// v1, (1,2,3) => "v1__1_2_3" +std::string get_var_name(const std::string& name, const bparser::MultiIdx::VecUint& indices) { + bparser::MultiIdx::VecUint::size_type size(indices.size()); + + std::ostringstream result; + result << name; + result << "__"; + for (bparser::MultiIdx::VecUint::size_type i = 0; i < size; i++) { + result << indices.at(i); + if (i != size - 1) { + result << '_'; + } + } + return result.str(); +} + + #endif //NITPICK_INCLUDE_HH \ No newline at end of file diff --git a/nitpick/nitpick_run.cc b/nitpick/nitpick_run.cc index 6c78e89..aa5d6b0 100644 --- a/nitpick/nitpick_run.cc +++ b/nitpick/nitpick_run.cc @@ -7,13 +7,15 @@ int main() { #include NITPICK_AUTOGENERATED_FILE_NAME - ProcessorBase* processor = ProcessorBase::create_processor(se, vec_size, bparser::get_simd_size(), nullptr); + ProcessorBase* processor = ProcessorBase::create_processor(se, max_vec_size, bparser::get_simd_size(), nullptr); std::vector subset = { 0, 1 }; processor->set_subset(subset); processor->run(); + std::cout << "Result: \n"; + // Result in the 'vres' value space. - //std::cout << print_vec(vres, 3 * vec_size); + std::cout << print_vec(vres, vres_size); } \ No newline at end of file From 955e006cb70e843161b6549be6d76a4af0882d2e Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Sun, 4 Jan 2026 19:00:22 +0100 Subject: [PATCH 12/14] included generation time and date for autogenerated.cc --- include/expression_dag.hh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/expression_dag.hh b/include/expression_dag.hh index 5b51776..b70770e 100644 --- a/include/expression_dag.hh +++ b/include/expression_dag.hh @@ -190,6 +190,7 @@ public: NodeVec result_nodes; result << "//AUTOGENERATED This file has been autogenerated by bparser::ExpressionDAG::print_in_cxx" << "\n"; + result << "// " << __DATE__ << " " << __TIME__ << "\n"; result << "\n"; result << "#ifndef NITPICK_INCLUDE_ONLY" << "\n"; result << "#include \"parser.hh\"" << "\n"; @@ -354,7 +355,7 @@ private: switch (node->result_storage) { case ResultStorage::constant: { - if (map.count(node->values_) == 1){ + if (map.count(node->values_) == 1){ //this will not work, each Array::const has its own pointers result << "ScalarNode::create_const(node_map[\"" << map.at(node->values_) << "\"]);\n"; } else { From c6ba90e39e7a798e366d552771fb4df6d1aa8866 Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Sun, 4 Jan 2026 19:01:04 +0100 Subject: [PATCH 13/14] P_SET_CONSTANT macro added, does not fully work --- nitpick/nitpick_def.cc | 8 +++++--- nitpick/nitpick_generate.cc | 1 + nitpick/nitpick_include.hh | 33 +++++++++++++++++++++++---------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/nitpick/nitpick_def.cc b/nitpick/nitpick_def.cc index a8e6286..ba486ed 100644 --- a/nitpick/nitpick_def.cc +++ b/nitpick/nitpick_def.cc @@ -40,6 +40,9 @@ int main() { } constexpr int vres_size = vec_size * 3; double vres[vres_size]; + for (uint i = 0; i < vres_size; ++i) { + vres[i] = NAN; + } // Create parser, give the size of the value spaces. // That is maximal allocated space. Actual values and @@ -51,11 +54,10 @@ int main() { // parse an expression. p.parse("1 * v1 + cs1 * v2"); - //TODO: Create the SET_CONSTANT macro // "cs1" constant with shape {}, i.e. scalar and values {2}. - p.set_constant("cs1", {}, {2}); + P_SET_CONSTANT(cs1, {}, {2}); // "cv1" vector constant with shape {3} - p.set_constant("cv1", {3}, {1, 2, 3}); + P_SET_CONSTANT(cv1, {3}, ARG({1, 2, 3})); // "v1" variable with shape {3}; v1 is pointer to the value space P_SET_VARIABLE(v1, { 3 }, v1); P_SET_VARIABLE(v2, { 3 }, v2); diff --git a/nitpick/nitpick_generate.cc b/nitpick/nitpick_generate.cc index bf7c418..46e424d 100644 --- a/nitpick/nitpick_generate.cc +++ b/nitpick/nitpick_generate.cc @@ -10,6 +10,7 @@ int main() { p.compile(); ExpressionDAG dag(p.result_array().elements()); + //dag.print_in_dot2(); std::ofstream file(NITPICK_AUTOGENERATED_FILE_NAME); file << dag.print_in_cxx(inv_map); diff --git a/nitpick/nitpick_include.hh b/nitpick/nitpick_include.hh index f8d7a7f..eab3ba5 100644 --- a/nitpick/nitpick_include.hh +++ b/nitpick/nitpick_include.hh @@ -3,40 +3,53 @@ #define NITPICK_AUTOGENERATED_FILE_NAME "autogenerated.cc" //Keep the same in .gitignore +#define ARG(...) __VA_ARGS__ + #define P_SET_(TYPE, NAME, SHAPE, POINTER) \ - Array NAME##_array = Array::value(POINTER, max_vec_size, SHAPE); \ + Array NAME##_array = Array::value(POINTER, max_vec_size, SHAPE); \ \ for (MultiIdx idx(NAME##_array.range()); idx.valid(); idx.inc_src()) { \ std::string var_name = get_var_name(#NAME, idx.indices()); \ - inv_map[NAME##_array[idx]->values_] = var_name; \ + inv_map[NAME##_array[idx]->values_] = var_name; \ node_map[var_name] = NAME##_array[idx]->values_; \ } \ p.set_##TYPE(#NAME, SHAPE, POINTER); -#define P_SET_VARIABLE(NAME, SHAPE, POINTER) P_SET_(variable, NAME, SHAPE, POINTER) -//#define P_SET_CONSTANT(NAME, SHAPE, POINTER) P_SET_(constant, NAME, SHAPE, POINTER) -#define P_SET_VAR_COPY(NAME, SHAPE, POINTER) P_SET_(var_copy, NAME, SHAPE, POINTER) +#define P_SET_C(TYPE, NAME, SHAPE, VALUES) \ + Array NAME##_array = Array::constant(VALUES, SHAPE); \ + \ + for (MultiIdx idx(NAME##_array.range()); idx.valid(); idx.inc_src()) { \ + std::string var_name = get_var_name(#NAME, idx.indices()); \ + inv_map[NAME##_array[idx]->values_] = var_name; \ + node_map[var_name] = NAME##_array[idx]->values_; \ + } \ + p.set_##TYPE(#NAME, SHAPE, VALUES); + +//this has different pointers than the parser's Array::constant, so it does not work + +#define P_SET_VARIABLE(NAME, SHAPE, POINTER) P_SET_(variable, NAME, ARG(SHAPE), POINTER) +#define P_SET_CONSTANT(NAME, SHAPE, VALUES) P_SET_C(constant, NAME, ARG(SHAPE), ARG(VALUES)) +#define P_SET_VAR_COPY(NAME, SHAPE, POINTER) P_SET_(var_copy, NAME, ARG(SHAPE), POINTER) #include "test_tools.hh" #include "parser.hh" #include #include -std::string get_var_name(const std::string & name, const bparser::MultiIdx::VecUint & indices); - -// v1, (1,2,3) => "v1__1_2_3" +// v1, (1,2,3) => "v1[1,2,3]" std::string get_var_name(const std::string& name, const bparser::MultiIdx::VecUint& indices) { bparser::MultiIdx::VecUint::size_type size(indices.size()); std::ostringstream result; result << name; - result << "__"; + result << "["; for (bparser::MultiIdx::VecUint::size_type i = 0; i < size; i++) { result << indices.at(i); if (i != size - 1) { - result << '_'; + result << ','; } } + result << "]"; return result.str(); } From e2c6d3214e824c80e3fc1988873f19e5227f3fc2 Mon Sep 17 00:00:00 2001 From: lukas-valenta-tul Date: Wed, 7 Jan 2026 15:26:20 +0100 Subject: [PATCH 14/14] Generation and running of generated files via preprocessor variables DEF_FILE and GEN_FILE Renamed NITPICK_INLCLUDE_ONLY to NITPICK_IDE_IGNROE --- .gitignore | 2 - CMakeLists.txt | 60 ++++++++++++++++--- .../nitpick_def.cc => cases/basic_expr_def.cc | 28 ++++----- cases/norm2_def.cc | 20 +++++++ include/dag_printer.hh | 8 +-- nitpick/example_def.cc | 22 +++++++ nitpick/nitpick_common.cc | 17 ++++++ nitpick/nitpick_generate.cc | 12 ++-- nitpick/nitpick_include.hh | 32 +++++++++- nitpick/nitpick_run.cc | 14 +++-- 10 files changed, 173 insertions(+), 42 deletions(-) rename nitpick/nitpick_def.cc => cases/basic_expr_def.cc (67%) create mode 100644 cases/norm2_def.cc create mode 100644 nitpick/example_def.cc create mode 100644 nitpick/nitpick_common.cc diff --git a/.gitignore b/.gitignore index 3485587..c9ca503 100644 --- a/.gitignore +++ b/.gitignore @@ -41,5 +41,3 @@ build/* .vs CMakeSettings.json -# Autogenerated files -/nitpick/autogenerated.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index f029243..168a52d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -199,7 +199,7 @@ message(STATUS "=======================================================\n\n") message(STATUS "VCL2_INCLUDE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}/third_party/VCL_v2") -set(BPARSER_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include ${Boost_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/third_party/VCL_v2 ${EIGEN3_INCLUDE_DIR}) +set(BPARSER_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/cases ${Boost_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/third_party/VCL_v2 ${EIGEN3_INCLUDE_DIR}) if(NOT PROJECT_IS_TOP_LEVEL) set(BPARSER_INCLUDES ${BPARSER_INCLUDES} PARENT_SCOPE) endif() @@ -291,30 +291,76 @@ define_test(test_processor bparser) #is it broken? -LV define_test(test_speed bparser) define_test(test_simd) +macro(define_nit_gen make_name def_file gen_file) set(src_name "nitpick_generate") set(nit_source "${CMAKE_CURRENT_SOURCE_DIR}/nitpick/${src_name}.cc") -set(nit_binary "${src_name}_bin") -set(nit_name "${src_name}") +set(nit_name "${src_name}_${make_name}") +set(nit_binary "${nit_name}_bin") add_executable(${nit_binary} EXCLUDE_FROM_ALL ${nit_source} ) add_dependencies(${nit_binary} bparser) target_link_libraries(${nit_binary} bparser) +#set_property(TARGET ${nit_binary} PROPERTY COMPILE_DEFINITIONS "DEF_FILE=$;GEN_FILE=$") +set_property(TARGET ${nit_binary} PROPERTY COMPILE_DEFINITIONS "DEF_FILE=\"${def_file}\";GEN_FILE=\"${gen_file}\"") add_custom_target(${nit_name} COMMAND "$" DEPENDS ${nit_binary} -WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/nitpick") +WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/cases") +endmacro() +macro(define_nit_run make_name def_file gen_file) set(src_name "nitpick_run") set(nit_source "${CMAKE_CURRENT_SOURCE_DIR}/nitpick/${src_name}.cc") -set(nit_binary "${src_name}_bin") -set(nit_name "${src_name}") +set(nit_name "${src_name}_${make_name}") +set(nit_binary "${nit_name}_bin") add_executable(${nit_binary} EXCLUDE_FROM_ALL ${nit_source} ) add_dependencies(${nit_binary} bparser) target_link_libraries(${nit_binary} bparser) +#set_target_properties(${nit_binary} PROPERTIES COMPILE_DEFINITIONS "DEF_FILE=$;GEN_FILE=$") +set_property(TARGET ${nit_binary} PROPERTY COMPILE_DEFINITIONS "DEF_FILE=\"${def_file}\";GEN_FILE=\"${gen_file}\"") add_custom_target(${nit_name} COMMAND "$" DEPENDS ${nit_binary} -WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/nitpick") +WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/cases") +endmacro() + +macro(define_nit make_name def_file gen_file) +define_nit_gen(${make_name} ${def_file} ${gen_file}) + +define_nit_run(${make_name} ${def_file} ${gen_file}) +endmacro() + +define_nit(basic_expr basic_expr_def.cc basic_expr_gen.cc) +define_nit(norm2 norm2_def.cc norm2_gen.cc) + + +#set(src_name "nitpick_generate") +#set(nit_source "${CMAKE_CURRENT_SOURCE_DIR}/nitpick/${src_name}.cc") +#set(nit_binary "${src_name}_bin") +#set(nit_name "${src_name}") + +#add_executable(${nit_binary} EXCLUDE_FROM_ALL ${nit_source} ) +#add_dependencies(${nit_binary} bparser) +#target_link_libraries(${nit_binary} bparser) + +#add_custom_target(${nit_name} +#COMMAND "$" +#DEPENDS ${nit_binary} +#WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/nitpick") + +#set(src_name "nitpick_run") +#set(nit_source "${CMAKE_CURRENT_SOURCE_DIR}/nitpick/${src_name}.cc") +#set(nit_binary "${src_name}_bin") +#set(nit_name "${src_name}") + +#add_executable(${nit_binary} EXCLUDE_FROM_ALL ${nit_source} ) +#add_dependencies(${nit_binary} bparser) +#target_link_libraries(${nit_binary} bparser) + +#add_custom_target(${nit_name} +#COMMAND "$" +#DEPENDS ${nit_binary} +#WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/nitpick") diff --git a/nitpick/nitpick_def.cc b/cases/basic_expr_def.cc similarity index 67% rename from nitpick/nitpick_def.cc rename to cases/basic_expr_def.cc index ba486ed..7f4eaa1 100644 --- a/nitpick/nitpick_def.cc +++ b/cases/basic_expr_def.cc @@ -1,32 +1,24 @@ -/* - * nitpick_def.c - * - * Created on: Dec 7, 2025 - * Author: LV - */ - /* * Prepare an environment for both DAG generation and then subsequent running */ -#ifndef NITPICK_INCLUDE_ONLY + +#ifndef NITPICK_IDE_IGNORE #include "nitpick_include.hh" -#endif //NITPICK_INCLUDE_ONLY +#endif //NITPICK_IDE_IGNORE -using namespace bparser; -#ifndef NITPICK_INCLUDE_ONLY + +#ifndef NITPICK_IDE_IGNORE int main() { -#endif //NITPICK_INCLUDE_ONLY +#endif //NITPICK_IDE_IGNORE // Define own value vectors, preferably aligned. // These variable names are used in macros and/or autogenerated files. Renaming them will cause issues constexpr uint vec_size = 8; constexpr uint max_vec_size = vec_size; - // These variable names are used in macros and/or autogenerated files. Renaming them will cause issues - std::unordered_map inv_map; //given to the print_in_cxx function to generate the file - std::unordered_map node_map; //is used by the generated file to use the same double pointers as the parser + //Memory allocation @@ -44,7 +36,7 @@ int main() { vres[i] = NAN; } - // Create parser, give the size of the value spaces. + // Create parser, give the size of the value spaces. // That is maximal allocated space. Actual values and // active subset can be changed between evaluations of the expression. // This variable name is used in macros and/or autogenerated files. Renaming it will cause issues @@ -64,6 +56,6 @@ int main() { // Set the result variable (the last line of the expression) P_SET_VARIABLE(_result_, { 3 }, vres); -#ifndef NITPICK_INCLUDE_ONLY +#ifndef NITPICK_IDE_IGNORE } -#endif //NITPICK_INCLUDE_ONLY \ No newline at end of file +#endif //NITPICK_IDE_IGNORE \ No newline at end of file diff --git a/cases/norm2_def.cc b/cases/norm2_def.cc new file mode 100644 index 0000000..3559b86 --- /dev/null +++ b/cases/norm2_def.cc @@ -0,0 +1,20 @@ +#ifndef NITPICK_IDE_IGNORE +# include "nitpick_include.hh" +#endif //This block will not be compiled during generation/run. But it helps the IDE understand the code + +constexpr uint vec_size = 8; +constexpr uint max_vec_size = vec_size; + +//MEM_ALLOC(name, 3*vec_size, 2) //2,2,2,2,2,2... +//MEM_ALLOC_INDEX(namei, 3 * vec_size) // 0,1,2,3,4,5... +MEM_ALLOC_LINEAR(v1, 3 * vec_size) // 1,2,3,4,5,6... +constexpr uint vres_size = 3 * vec_size; +MEM_ALLOC(vres, vres_size, NAN) + +Parser p(max_vec_size); + +p.parse("norm2(v1)"); + +P_SET_VARIABLE(v1, ARG({3}), v1) +P_SET_VARIABLE(_result_, { }, vres); + diff --git a/include/dag_printer.hh b/include/dag_printer.hh index ff39f4b..e931fcc 100644 --- a/include/dag_printer.hh +++ b/include/dag_printer.hh @@ -110,12 +110,12 @@ namespace details { result << "//AUTOGENERATED This file has been autogenerated by bparser::details::DagPrinter::print_in_cxx" << "\n"; result << "// " << __DATE__ << " " << __TIME__ << "\n"; result << "\n"; - result << "#ifndef NITPICK_INCLUDE_ONLY" << "\n"; + result << "#ifndef NITPICK_IDE_IGNORE" << "\n"; result << "#include \"parser.hh\"" << "\n"; result << "using namespace bparser;" << "\n"; result << "using namespace bparser::details;" << "\n"; result << "int main(){ //This is here only to stop any IDE warnings, do not run this file as is" << "\n"; - result << "#endif //NITPICK_INCLUDE_ONLY" << "\n"; + result << "#endif //NITPICK_IDE_IGNORE" << "\n"; result << "\n"; //Print nodes @@ -143,9 +143,9 @@ namespace details { result << "}); //Do not rename this DAG, it is used later in the nitpick_run.cc file"; result << "\n"; - result << "#ifndef NITPICK_INCLUDE_ONLY" << "\n"; + result << "#ifndef NITPICK_IDE_IGNORE" << "\n"; result << "} //main" << "\n"; - result << "#endif //NITPICK_INCLUDE_ONLY" << "\n"; + result << "#endif //NITPICK_IDE_IGNORE" << "\n"; //std::cout << result.str(); return result.str(); diff --git a/nitpick/example_def.cc b/nitpick/example_def.cc new file mode 100644 index 0000000..829aab1 --- /dev/null +++ b/nitpick/example_def.cc @@ -0,0 +1,22 @@ +// vec size and p names must stay the same! + +#ifndef NITPICK_IDE_IGNORE +# include "nitpick_include.hh" +#endif //This block will not be compiled during generation/run. But it helps the IDE understand the code + +constexpr uint vec_size = 8; +constexpr uint max_vec_size = vec_size; + +//MEM_ALLOC(name, 3*vec_size, 2) //2,2,2,2,2,2... +//MEM_ALLOC_INDEX(namei, 3 * vec_size) // 0,1,2,3,4,5... +//MEM_ALLOC_LINEAR(namel, 3 * vec_size) // 1,2,3,4,5,6... +constexpr uint vres_size = 3 * vec_size; +MEM_ALLOC(vres, vres_size, NAN) + +Parser p(max_vec_size); + +//P_SET_CONSTANT(name, ARG({2,2}), ARG({1,2,3,4})) //2x2 matrix +//P_SET_VARIABLE(v1, ARG({3}), namei) //vector from namei double* +P_SET_VARIABLE(_result_, { }, vres); + +p.parse("1+1"); \ No newline at end of file diff --git a/nitpick/nitpick_common.cc b/nitpick/nitpick_common.cc new file mode 100644 index 0000000..5747e84 --- /dev/null +++ b/nitpick/nitpick_common.cc @@ -0,0 +1,17 @@ +/* + * nitpick_common.cc + * Included in the nitpick_run.cc and nitpick_generate.cc files, do not compile + * + * Created on: Jan 7, 2026 + * Author: LV + */ + +#ifndef NITPICK_IDE_IGNORE +#include "parser.h" //this is here to help the IDE, header file includes are in nitpick_include.hh +#endif //NITPICK_IDE_IGNORE + +using namespace bparser; + +// These variable names are used in macros and/or autogenerated files. Renaming them will cause issues +std::unordered_map inv_map; //given to the print_in_cxx function to generate the file +std::unordered_map node_map; //is used by the generated file to use the same double pointers as the parser \ No newline at end of file diff --git a/nitpick/nitpick_generate.cc b/nitpick/nitpick_generate.cc index 8ea0790..8fee154 100644 --- a/nitpick/nitpick_generate.cc +++ b/nitpick/nitpick_generate.cc @@ -1,19 +1,23 @@ #include "nitpick_include.hh" +#include #include int main() { -#define NITPICK_INCLUDE_ONLY -#include "nitpick_def.cc" +#define NITPICK_IDE_IGNORE +#include "nitpick_common.cc" +#include NITPICK_DEF_FILE + + //p is in NITPICK_DEF_FILE // Compile the expression into internal processor. p.compile(); ExpressionDAG dag(p.result_array().elements()); //dag.print_in_dot2(); - std::ofstream file(NITPICK_AUTOGENERATED_FILE_NAME); + std::ofstream file(NITPICK_GEN_FILE); file << DagPrinter(dag).print_in_cxx(inv_map); file.close(); - std::cout << "File " << std::filesystem::current_path() << " " << NITPICK_AUTOGENERATED_FILE_NAME << " created"; + std::cout << "File " << std::filesystem::current_path() << " " << NITPICK_GEN_FILE << " created from " << NITPICK_DEF_FILE; } \ No newline at end of file diff --git a/nitpick/nitpick_include.hh b/nitpick/nitpick_include.hh index eab3ba5..a08e4db 100644 --- a/nitpick/nitpick_include.hh +++ b/nitpick/nitpick_include.hh @@ -1,7 +1,24 @@ #ifndef NITPICK_INCLUDE_HH #define NITPICK_INCLUDE_HH -#define NITPICK_AUTOGENERATED_FILE_NAME "autogenerated.cc" //Keep the same in .gitignore +#define NITPICK_FALLBACK_DEF_FILE_NAME "example_def.cc" +#define NITPICK_AUTOGENERATED_FILE_NAME "autogenerated.cc" + +#ifndef NITPICK_DEF_FILE +# ifdef DEF_FILE +# define NITPICK_DEF_FILE DEF_FILE +# else +# define NITPICK_DEF_FILE NITPICK_FALLBACK_DEF_FILE_NAME +# endif +#endif + +#ifndef NITPICK_GEN_FILE +# ifdef GEN_FILE +# define NITPICK_GEN_FILE GEN_FILE +# else +# define NITPICK_GEN_FILE NITPICK_AUTOGENERATED_FILE_NAME +# endif +#endif #define ARG(...) __VA_ARGS__ @@ -27,14 +44,23 @@ //this has different pointers than the parser's Array::constant, so it does not work -#define P_SET_VARIABLE(NAME, SHAPE, POINTER) P_SET_(variable, NAME, ARG(SHAPE), POINTER) #define P_SET_CONSTANT(NAME, SHAPE, VALUES) P_SET_C(constant, NAME, ARG(SHAPE), ARG(VALUES)) +#define P_SET_VARIABLE(NAME, SHAPE, POINTER) P_SET_(variable, NAME, ARG(SHAPE), POINTER) #define P_SET_VAR_COPY(NAME, SHAPE, POINTER) P_SET_(var_copy, NAME, ARG(SHAPE), POINTER) + +#define MEM_ALLOC(NAME, SIZE, VALUE) \ + double NAME[(SIZE)]; \ + for (uint i = 0; i < (SIZE); ++i) { \ + NAME[i] = (VALUE); \ + } + +#define MEM_ALLOC_INDEX(NAME, SIZE) MEM_ALLOC(NAME, SIZE, i) +#define MEM_ALLOC_LINEAR(NAME, SIZE) MEM_ALLOC(NAME, SIZE, i+1) + #include "test_tools.hh" #include "parser.hh" #include -#include // v1, (1,2,3) => "v1[1,2,3]" std::string get_var_name(const std::string& name, const bparser::MultiIdx::VecUint& indices) { diff --git a/nitpick/nitpick_run.cc b/nitpick/nitpick_run.cc index e8fe33f..370d860 100644 --- a/nitpick/nitpick_run.cc +++ b/nitpick/nitpick_run.cc @@ -2,18 +2,24 @@ int main() { // only get us the variables -#define NITPICK_INCLUDE_ONLY -#include "nitpick_def.cc" //include all the variables used to generate the file +#define NITPICK_IDE_IGNORE +#include "nitpick_common.cc" -#include NITPICK_AUTOGENERATED_FILE_NAME + //include all the variables used to generate the file +#include NITPICK_DEF_FILE + //include the generated file +#include NITPICK_GEN_FILE + //se is in the NITPICK_GEN_FILE + //max_vec_size is in NITPICK_DEF_FILE ProcessorBase* processor = ProcessorBase::create_processor(se, max_vec_size, bparser::get_simd_size(), nullptr); - std::vector subset = { 0, 1 }; //ctverice doubluu + std::vector subset = { 0, 1 }; //ctverice doubluu //TODO: move or expand processor->set_subset(subset); processor->run(); + //TODO: std::cout << "Result: \n"; // Result in the 'vres' value space.