diff --git a/.gitignore b/.gitignore index dd5dd11..c9ca503 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ build/* .cproject .project .vs -CMakeSettings.json \ No newline at end of file +CMakeSettings.json + diff --git a/CMakeLists.txt b/CMakeLists.txt index ae5cbef..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() @@ -290,3 +290,77 @@ define_test(test_grammar bparser) 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_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}/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_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}/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/cases/basic_expr_def.cc b/cases/basic_expr_def.cc new file mode 100644 index 0000000..7f4eaa1 --- /dev/null +++ b/cases/basic_expr_def.cc @@ -0,0 +1,61 @@ +/* +* Prepare an environment for both DAG generation and then subsequent running +*/ + + +#ifndef NITPICK_IDE_IGNORE +#include "nitpick_include.hh" +#endif //NITPICK_IDE_IGNORE + + + +#ifndef NITPICK_IDE_IGNORE +int main() { +#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; + + + + //Memory allocation + + 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; + } + 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 + // 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 + Parser p(max_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}, 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); + // Set the result variable (the last line of the expression) + P_SET_VARIABLE(_result_, { 3 }, vres); + +#ifndef NITPICK_IDE_IGNORE +} +#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/array.hh b/include/array.hh index b590b27..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); @@ -1216,6 +1216,163 @@ 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) is not yet possible" << "\n"; + /*Shape s; //empty Shape for scalar + Array r(s); + + 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"; + } + } + + 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 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 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/dag_printer.hh b/include/dag_printer.hh new file mode 100644 index 0000000..e931fcc --- /dev/null +++ b/include/dag_printer.hh @@ -0,0 +1,390 @@ +/* + * dag_printer.hh + * + * Implements tostring/print methods for ExpressionDAG in various formats: + * DOT (Graphviz) + * C++ + * + * Created on: Jan 05, 2026 + * Author: LV + */ + + +#ifndef DAG_PRINTER_HH +#define DAG_PRINTER_HH + +#include "expression_dag.hh" + +namespace bparser { +namespace details { + + class DagPrinter { + private: + // Backward topologicaly sorted nodes; results first, inputs last + bparser::details::ExpressionDAG::NodeVec sorted; + + // Holds the names of every node + std::map node_names; + + public: + + DagPrinter(ExpressionDAG& dag) + :sorted(dag.sort_nodes()) + { + name_nodes(); + } + + DagPrinter(const ExpressionDAG::NodeVec& vec) + : sorted(vec) + { + name_nodes(); + } + + typedef std::pair InvDotNameAndIndices; + typedef std::map InvDotMap; + typedef std::unordered_map CXXVarMap; + + + + private: + void name_nodes() { + for (uint i = 0; i < sorted.size(); ++i) { + ScalarNodePtr node = sorted[i]; + node_names[node] = node->op_name_ + "_" + std::to_string(i) + "__" + std::to_string(node->result_storage); + } + } + + public: + /* + * Print ScalarExpression graph in the common dot format. + * Useful for understanding the DAG. + */ + void print_in_dot2() const { + 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) const { + 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) const { + + 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], 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 << " "; + std::cout << _get_dot_node_id(sorted[i]); + std::cout << "\n -> "; + std::cout << _get_dot_node_id(sorted[i]->inputs_[in]); + std::cout << "\n\n"; + } + } + std::cout << "}" << "\n"; + std::cout << "----- end cut here -----" << "\n"; + std::cout.flush(); + } + + + std::string print_in_cxx(const CXXVarMap& map) { + std::ostringstream result; + ExpressionDAG::NodeVec result_nodes; + + result << "//AUTOGENERATED This file has been autogenerated by bparser::details::DagPrinter::print_in_cxx" << "\n"; + result << "// " << __DATE__ << " " << __TIME__ << "\n"; + result << "\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_IDE_IGNORE" << "\n"; + result << "\n"; + + //Print nodes + for (uint i = sorted.size(); i-- > 0U; ) { // N-1,N-2,... 0 + + result << _get_cxx_node_definition(sorted[i], map); + //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], map); + } + //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_IDE_IGNORE" << "\n"; + result << "} //main" << "\n"; + result << "#endif //NITPICK_IDE_IGNORE" << "\n"; + //std::cout << result.str(); + return result.str(); + + } + + //Create a map of ScalarNodePtr -> (variable name, indices) + InvDotMap create_inverse_map(const std::map& symbols) const { + InvDotMap inv_map; + if (symbols.empty()) return inv_map; + for (const auto& s : symbols) + { + 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; + } + + protected: + std::string _get_node_id(const ScalarNodePtr& node) const { + return node_names.at(node); + } + + //Print the vertice identifier for dot + std::string _get_dot_node_id(const ScalarNodePtr& node) const { + return _get_node_id(node); + } + + //Print how the vertice should look in dot + void _print_dot_node_definition(const ScalarNodePtr& node, const InvDotMap& invmap) const { + std::cout << _get_dot_node_id(node); + std::cout << ' '; + + 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; + } + + 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; + } + + case ResultStorage::expr_result: { //Result + std::cout << "[shape=box,label=\"" << node->op_name_ << " [" << node->result_idx_ << "]" << "\"]" << std::endl; + break; + } + + 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 << '"'; + } + catch (const std::out_of_range&) { + std::cout << "label=<var>"; + } + + std::cout << "]" << std::endl; + break; + } + + 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 << '"'; + } + catch (const std::out_of_range&) { + std::cout << "label=<var_cp>"; + } + std::cout << "]" << std::endl; + break; + } + + default: { //Temporary & other + std::cout << "[label=\"" << node->op_name_ << "\"]" << std::endl; + break; + } + } //switch + } + + + std::string _get_cxx_node_id(const ScalarNodePtr& node) const { + return _get_node_id(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]); + if (in < node->n_inputs_ - 1) { + result += ", "; + } + } + return result; + } + + 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::constant: { + 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 { + result << "ScalarNode::create_const(" << *node->values_ << ");\n"; + } + break; + } + case ResultStorage::constant_bool: { + 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_value(node_map[\"" << map.at(node->values_) << "\"]);\n"; + break; + } + case ResultStorage::value_copy: { + 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"; + 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, const CXXVarMap& map) const { + return "ScalarNode::create_result(" + _get_cxx_node_id(node) + ", node_map[\"" + map.at(node->values_) + "\"]);\n"; + } + + }; //DagPrinter +} //details +} //bparser + +#endif //DAG_PRINTER_HH \ No newline at end of file diff --git a/include/expression_dag.hh b/include/expression_dag.hh index da08bb5..88d7db7 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 { @@ -102,6 +103,7 @@ public: /** * Print ScalarExpression graph in the dot format. + * Useful for debugging */ void print_in_dot() { std::map i_node; @@ -131,8 +133,8 @@ public: std::cout << "Node: " << node->op_name_ << "_" << node->result_idx_ << " " << node->result_storage << std::endl; } - private: + void _print_i_node(uint i) { std::cout << sorted[i]->op_name_ << "_" << i << "_"<< sorted[i]->result_idx_; } diff --git a/include/grammar.impl.hh b/include/grammar.impl.hh index 8a5ad5c..c03be4a 100644 --- a/include/grammar.impl.hh +++ b/include/grammar.impl.hh @@ -179,8 +179,16 @@ 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) + FN("cross" , &Array::cross) ; unary_op.add diff --git a/include/parser.hh b/include/parser.hh index ca8bde1..3013d5b 100644 --- a/include/parser.hh +++ b/include/parser.hh @@ -19,6 +19,7 @@ #include "processor.hh" #include "grammar.hh" #include "create_processor.hh" +#include "dag_printer.hh" namespace bparser { @@ -190,6 +191,8 @@ public: details::ExpressionDAG se(result_array_.elements()); //se.print_in_dot(); + //DagPrinter(se).print_in_dot2(); + //DagPrinter(se).print_in_dot2(symbols_); processor = ProcessorBase::create_processor(se, max_vec_size, simd_size, arena); } diff --git a/include/scalar_wrapper.hh b/include/scalar_wrapper.hh index 54dda38..7aa050f 100644 --- a/include/scalar_wrapper.hh +++ b/include/scalar_wrapper.hh @@ -12,6 +12,8 @@ #include "scalar_node.hh" #include +#include +//#include //impossible namespace bparser { namespace details { @@ -23,6 +25,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,24 +42,66 @@ 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); } 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; } + /* 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 @@ -78,6 +126,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 +147,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 +179,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/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/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 new file mode 100644 index 0000000..8fee154 --- /dev/null +++ b/nitpick/nitpick_generate.cc @@ -0,0 +1,23 @@ +#include "nitpick_include.hh" +#include +#include + +int main() { + +#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_GEN_FILE); + file << DagPrinter(dag).print_in_cxx(inv_map); + file.close(); + 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 new file mode 100644 index 0000000..a08e4db --- /dev/null +++ b/nitpick/nitpick_include.hh @@ -0,0 +1,83 @@ +#ifndef NITPICK_INCLUDE_HH +#define NITPICK_INCLUDE_HH + +#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__ + +#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_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_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 + +// 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 << ','; + } + } + 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 new file mode 100644 index 0000000..370d860 --- /dev/null +++ b/nitpick/nitpick_run.cc @@ -0,0 +1,27 @@ +#include "nitpick_include.hh" + +int main() { +// only get us the variables +#define NITPICK_IDE_IGNORE +#include "nitpick_common.cc" + + //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 //TODO: move or expand + + processor->set_subset(subset); + processor->run(); + + //TODO: + std::cout << "Result: \n"; + + // Result in the 'vres' value space. + std::cout << print_vec(vres, vres_size); +} \ No newline at end of file diff --git a/test/test_parser.cc b/test/test_parser.cc index 28b3c20..4083d47 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 }, {})); //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 }, {})); + 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}, {})); @@ -296,6 +304,22 @@ 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 })); + + 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: