diff --git a/.gitignore b/.gitignore index dd5dd11..370ad6c 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 +test/cases/*_gen.hh \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ae5cbef..95b7771 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,61 @@ 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_target make_name def_file gen_file src_name) + set(nit_source "${CMAKE_CURRENT_SOURCE_DIR}/test/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=\"${def_file}\";GEN_FILE=\"${gen_file}\"") + + add_custom_target(${nit_name} + COMMAND "$" + DEPENDS ${nit_binary} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/cases") +endmacro() + +macro(define_nit make_name) + set(nit_def_file "${make_name}_def.hh") + set(nit_gen_file "${make_name}_gen") + + define_nit_target(${make_name} ${nit_def_file} "${nit_gen_file}.hh" "nitpick_generate") #generate the _gen.hh file + + define_nit_target(${make_name} ${nit_def_file} "${nit_gen_file}_ref.hh" "nitpick_run") #run the _gen_ref.hh file + define_nit_target("${make_name}_edit" ${nit_def_file} "${nit_gen_file}_edit.hh" "nitpick_run") #run the _gen_edit.hh file +endmacro() + +define_nit(basic_expr) +define_nit(norm2) + + +#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/array.hh b/include/array.hh index b590b27..5ea5093 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,234 @@ 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); + } + + // [[ 1, 2 ], -> [[ 1, 3 ], + // [ 3, 4 ]] [ 2, 4 ]] + static Array transpose(const Array& a) { + switch (a.shape().size()) { + case 0: //scalar + Throw() << "Cannot transpose a scalar" << "\n"; + break; + case 1: //vector + { + Throw() << "Cannot transpose vector. BParser vectors do not have an orientation" << "\n"; + } + case 2: //matrix + { + return unwrap_array(wrap_array(a).transpose()); + } + default: + Throw() << "Cannot transpose ND tensors" << "\n"; + } + } + + static Array sym(const Array& a) { + switch (a.shape().size()) { + case 0: //scalar + Throw() << "Cannot sym a scalar" << "\n"; + break; + case 1: //vector + { + Throw() << "Cannot sym a vector" << "\n"; + } + case 2: //matrix + { + if (a.shape()[0] != a.shape()[1]) { + Throw() << "Cannot sym non-square matrix" << "\n"; + } + + WrappedArray m_a(wrap_array(a)); + using namespace details; + ScalarWrapper two(ScalarNode::create_const(2)); + return unwrap_array( (m_a + m_a.transpose())/two ); + } + default: + Throw() << "Cannot sym ND tensors" << "\n"; + } + } + + static Array dev(const Array& a) { + switch (a.shape().size()) { + case 0: //scalar + Throw() << "Cannot dev a scalar" << "\n"; + break; + case 1: //vector + { + Throw() << "Cannot dev a vector" << "\n"; + } + case 2: //matrix + { + if (a.shape()[0] != a.shape()[1]) { + Throw() << "Cannot dev non-square matrix" << "\n"; + } + WrappedArray m_a(wrap_array(a)); + using namespace details; + ScalarWrapper D((int)m_a.rows()); + WrappedArray I(WrappedArray::Identity(m_a.rows(), m_a.cols())); + return unwrap_array( m_a - ( (m_a.trace()/D ) * I ) ); + } + default: + Throw() << "Cannot dev 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/dag_printer.hh b/include/dag_printer.hh new file mode 100644 index 0000000..bb2e2ae --- /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; + //TODO: Make a function returning se and getting node_map + 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 \"nitpick_include.hh\"" << "\n"; + result << "using namespace bparser;" << "\n"; + result << "using namespace bparser::details;" << "\n"; + result << "#endif //NITPICK_IDE_IGNORE //This is here only to stop any IDE warnings" << "\n"; + result << "ExpressionDAG gen(const NodeMap& node_map){ //Do not rename this function, it is used later in the nitpick_run.cc file" << "\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 << "});" << "\n"; + result << "\n"; + result << "return se;" << "\n"; + + result << "} //gen" << "\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.at(\"" << 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.at(\"" << map.at(node->values_) << "\"));\n"; + } + else { + result << "ScalarNode::create_const_bool(" << *node->values_ << ");\n"; + } + break; + } + case ResultStorage::value: { + result << "ScalarNode::create_value(node_map.at(\"" << map.at(node->values_) << "\"));\n"; + break; + } + case ResultStorage::value_copy: { + result << "ScalarNode::create_val_copy(node_map.at(\"" << 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.at(\"" + 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..0260960 100644 --- a/include/grammar.impl.hh +++ b/include/grammar.impl.hh @@ -98,6 +98,7 @@ struct grammar : qi::grammar { multiplicative_expr, signed_optional, signed_expr, + post_expr, power, array_constr, array_constr_list_opt, @@ -129,6 +130,7 @@ struct grammar : qi::grammar { reserved, func, unary_op, + unary_op_post, not_op, additive_op, multiplicative_op, @@ -179,8 +181,18 @@ 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) + FN("sym" , &Array::sym) + FN("dev" , &Array::dev) ; unary_op.add @@ -188,6 +200,10 @@ struct grammar : qi::grammar { FN("-", unary_array<_minus_>()) ; + unary_op_post.add + FN(".T", &Array::transpose) + ; + additive_op.add FN("+", binary_array<_add_>()) FN("-", binary_array<_sub_>()) @@ -307,6 +323,8 @@ struct grammar : qi::grammar { RULE(power) = primary[qi::_val = qi::_1] >> -(power_op > signed_optional)[qi::_val = ast::make_binary(qi::_1, qi::_val, qi::_2)]; + RULE(post_expr) = (subscriptable >> unary_op_post)[qi::_val = ast::make_unary(qi::_2, qi::_1)]; //TODO: Do properly, this does not work -LV + RULE(primary) = literal_number | const_lit | subscription; RULE(subscriptable) = array_constr | enclosure | call | identifier; 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/processor.hh b/include/processor.hh index b16e018..3754cfc 100644 --- a/include/processor.hh +++ b/include/processor.hh @@ -431,7 +431,7 @@ struct Processor : public ProcessorBase { CODE(_copy_); CODE(_ifelse_); CODE(_log2_); -// CODE(__); + CODE(_muladd_); // CODE(__); // CODE(__); // CODE(__); diff --git a/include/scalar_node.hh b/include/scalar_node.hh index 44faedb..1547f55 100644 --- a/include/scalar_node.hh +++ b/include/scalar_node.hh @@ -86,6 +86,9 @@ struct ScalarNode { template static ScalarNodePtr create(ScalarNodePtr a, ScalarNodePtr b); + template + static ScalarNodePtr create(ScalarNodePtr a, ScalarNodePtr b, ScalarNodePtr c); + ScalarNode() @@ -728,6 +731,20 @@ inline void _ifelse_::eval(double &res, double a, double b, double c) { } UNARY_FN(_log2_, 52, log2); +inline double mul_add(double a, double b, double c) { + return std::fma(a, b, c); +} +using ::mul_add; //+ VCL mul_add + +struct _muladd_ : public ScalarNode { + static const char op_code = 53; + static const char n_eval_args = 4; + template + inline static void eval(VecType& res, VecType a, VecType b, VecType c) { + res = mul_add(a, b, c); // a * b + c + } +}; + /*********************** * Construction Nodes. @@ -808,6 +825,26 @@ ScalarNodePtr ScalarNode::create(ScalarNodePtr a, ScalarNodePtr b) { return node_ptr; } +template +ScalarNodePtr ScalarNode::create(ScalarNodePtr a, ScalarNodePtr b, ScalarNodePtr c) { + std::shared_ptr node_ptr = std::make_shared(); + node_ptr->op_code_ = T::op_code; + node_ptr->set_name(typeid(T).name()); + node_ptr->add_input(a); + node_ptr->add_input(b); + node_ptr->add_input(c); + if (T::n_eval_args == 3) { + // Note: in place operations are not supported + node_ptr->result_storage = none; + } + else { + BP_ASSERT(T::n_eval_args == 4); + node_ptr->result_storage = temporary; + } + + return node_ptr; +} + inline ScalarNodePtr ScalarNode::create_ifelse(ScalarNodePtr a, ScalarNodePtr b, ScalarNodePtr c) { std::shared_ptr<_ifelse_> node_ptr = std::make_shared<_ifelse_>(); node_ptr->op_code_ = _ifelse_::op_code; 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/test/cases/README.md b/test/cases/README.md new file mode 100644 index 0000000..f1ccd99 --- /dev/null +++ b/test/cases/README.md @@ -0,0 +1,41 @@ + +# Nitpick test cases +The nitpick test cases allows developers to time and compare different Directed Acyclic Graphs (DAG) of ScalarNodes for various bparser expressions. + +## File structure + +There are 4 different files for each case: +* `_def.hh` - Defines what the parser expression is, what are the variable shapes and names and the result shape +* `_gen.hh` - Autogenerated file containing a reconstruction of the parser's DAG. This file is in .gitignore +* `_gen_ref.hh` - The autogenerated file before being edited. +Most likely identical to `_gen.hh` but the autogenerated file can look different now than how it would generate before. +* `_gen_edit.hh` - Former file, but edited. The DAG may be constructed more optimally or is using different ScalarNode operations + +## Adding a case + +1. Create a `_def.hh` file. + * This file must define the function `void def(ExprCase&)` + * You must call the `ExprCase::parse` method to set the bparser's expression. + * You must specify the result shape using the `ExprCase::set_result_shape` method. + * You may use the `ExprCase::set_variable` and `ExprCase::set_var_copy` methods. + They are similar to `Parser::set_variable` and `Parser::set_var_copy`, except you don't need a double pointer. + * You may use `#ifndef NITPICK_IDE_IGNORE #endif` to trick the IDE into thinking something exists and/or is included. This block will not actually be compiled. +2. Declare the case in CMakeLists.txt + * Add `define_nit()` near similar definitions +3. Generate the file + * Make the `nitpick_generate_` target +4. Copy the autogenerated file + * First copy should have `_ref` at the end of the filename + * Second copy should have `_edit` at the end of the filename +5. Run and compare + * Use the `nitpick_run_` and `nitpick_run__edit` Make targets to run and time the original or edited file + + +## Timing +The nitpick_run.cc file will repeatedly: +1. Allocate \ × vec_size for each variable set in the ExprCase object where vec_size changes every iteration +2. Run and time the autogenerated DAG for N ÷ vec_size times, where N is a very high constant integer + * This is to spend more time computing small vec_sizes +3. Deallocate memory from step 1. + +This is done for every vec_size which is an array of integers set in the nitpick_run.cc file \ No newline at end of file diff --git a/test/cases/basic_expr_def.hh b/test/cases/basic_expr_def.hh new file mode 100644 index 0000000..c227bf0 --- /dev/null +++ b/test/cases/basic_expr_def.hh @@ -0,0 +1,25 @@ +/* +* Prepare an environment for both DAG generation and then subsequent running +*/ + +#ifndef NITPICK_IDE_IGNORE +#include "nitpick_include.hh" +#endif //NITPICK_IDE_IGNORE + +void def(ExprCase& c) { + + // parse an expression. + c.parse("1 * v1 + cs1 * v2"); + + // "cs1" constant with shape {}, i.e. scalar and values {2}. + c.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 + c.set_variable("v1", {3}); + c.set_variable("v2", {3}); + // Set the result variable (the last line of the expression) + c.set_result_shape({ 3 }); + + +} diff --git a/test/cases/basic_expr_gen_ref.hh b/test/cases/basic_expr_gen_ref.hh new file mode 100644 index 0000000..fc596b2 --- /dev/null +++ b/test/cases/basic_expr_gen_ref.hh @@ -0,0 +1,36 @@ +//AUTOGENERATED This file has been autogenerated by bparser::details::DagPrinter::print_in_cxx +// Jan 20 2026 17:52:25 + +#ifndef NITPICK_IDE_IGNORE +#include "nitpick_include.hh" +using namespace bparser; +using namespace bparser::details; +#endif //NITPICK_IDE_IGNORE //This is here only to stop any IDE warnings + +ExpressionDAG gen(const NodeMap& node_map){ //Do not rename this function, it is used later in the nitpick_run.cc file + ScalarNodePtr Const_16__1 = ScalarNode::create_const(1); + ScalarNodePtr Value_15__2 = ScalarNode::create_value(node_map.at("v1[0]")); + ScalarNodePtr mul_14__3 = ScalarNode::create<_mul_>(Const_16__1, Value_15__2); + ScalarNodePtr Const_13__1 = ScalarNode::create_const(2); + ScalarNodePtr Value_12__2 = ScalarNode::create_value(node_map.at("v2[0]")); + ScalarNodePtr mul_11__3 = ScalarNode::create<_mul_>(Const_13__1, Value_12__2); + ScalarNodePtr add_10__4 = ScalarNode::create<_add_>(mul_14__3, mul_11__3); + ScalarNodePtr Value_9__2 = ScalarNode::create_value(node_map.at("v1[1]")); + ScalarNodePtr mul_8__3 = ScalarNode::create<_mul_>(Const_16__1, Value_9__2); + ScalarNodePtr Value_7__2 = ScalarNode::create_value(node_map.at("v2[1]")); + ScalarNodePtr mul_6__3 = ScalarNode::create<_mul_>(Const_13__1, Value_7__2); + ScalarNodePtr add_5__4 = ScalarNode::create<_add_>(mul_8__3, mul_6__3); + ScalarNodePtr Value_4__2 = ScalarNode::create_value(node_map.at("v1[2]")); + ScalarNodePtr mul_3__3 = ScalarNode::create<_mul_>(Const_16__1, Value_4__2); + ScalarNodePtr Value_2__2 = ScalarNode::create_value(node_map.at("v2[2]")); + ScalarNodePtr mul_1__3 = ScalarNode::create<_mul_>(Const_13__1, Value_2__2); + ScalarNodePtr add_0__4 = ScalarNode::create<_add_>(mul_3__3, mul_1__3); + + + ScalarNodePtr r0 = ScalarNode::create_result(add_10__4, node_map.at("_result_[0]")); + ScalarNodePtr r1 = ScalarNode::create_result(add_5__4, node_map.at("_result_[1]")); + ScalarNodePtr r2 = ScalarNode::create_result(add_0__4, node_map.at("_result_[2]")); + ExpressionDAG se({r0, r1, r2}); + + return se; +} //gen diff --git a/test/cases/norm2_def.hh b/test/cases/norm2_def.hh new file mode 100644 index 0000000..f2dd8d9 --- /dev/null +++ b/test/cases/norm2_def.hh @@ -0,0 +1,13 @@ + +#ifndef NITPICK_IDE_IGNORE +# include "nitpick/nitpick_include.hh" +#endif // NITPICK_IDE_IGNORE + +void def(ExprCase& c) { + +c.parse("norm2(v1)"); + +c.set_variable("v1", { 3 }); +c.set_result_shape({ }); +} + diff --git a/test/cases/norm2_gen_edit.hh b/test/cases/norm2_gen_edit.hh new file mode 100644 index 0000000..59cb024 --- /dev/null +++ b/test/cases/norm2_gen_edit.hh @@ -0,0 +1,27 @@ +//AUTOGENERATED This file has been autogenerated by bparser::details::DagPrinter::print_in_cxx +// Jan 20 2026 18:42:12 +// And edited on Jan 22 to use multiply-accumulate (fma) + +#ifndef NITPICK_IDE_IGNORE +#include "nitpick_include.hh" +using namespace bparser; +using namespace bparser::details; +#endif //NITPICK_IDE_IGNORE //This is here only to stop any IDE warnings +ExpressionDAG gen(const NodeMap& node_map){ //Do not rename this function, it is used later in the nitpick_run.cc file + +ScalarNodePtr Value_8__2 = ScalarNode::create_value(node_map.at("v1[0]")); +ScalarNodePtr Value_6__2 = ScalarNode::create_value(node_map.at("v1[1]")); +ScalarNodePtr Value_3__2 = ScalarNode::create_value(node_map.at("v1[2]")); +ScalarNodePtr mul_7__3 = ScalarNode::create<_mul_>(Value_8__2, Value_8__2); +ScalarNodePtr muladd_4__3 = ScalarNode::create<_muladd_>(Value_6__2, Value_6__2, mul_7__3); +//ScalarNodePtr add_4__3 = ScalarNode::create<_add_>(mul_7__3, mul_5__3); +ScalarNodePtr muladd_2__3 = ScalarNode::create<_muladd_>(Value_3__2, Value_3__2, muladd_4__3); +//ScalarNodePtr add_1__3 = ScalarNode::create<_add_>(add_5__3, mul_2__3); +ScalarNodePtr sqrt_0__4 = ScalarNode::create<_sqrt_>(muladd_2__3); + + +ScalarNodePtr r0 = ScalarNode::create_result(sqrt_0__4, node_map.at("_result_[]")); +ExpressionDAG se({r0}); + +return se; +} //gen diff --git a/test/cases/norm2_gen_ref.hh b/test/cases/norm2_gen_ref.hh new file mode 100644 index 0000000..74f2ef5 --- /dev/null +++ b/test/cases/norm2_gen_ref.hh @@ -0,0 +1,26 @@ +//AUTOGENERATED This file has been autogenerated by bparser::details::DagPrinter::print_in_cxx +// Jan 20 2026 18:42:12 + +#ifndef NITPICK_IDE_IGNORE +#include "nitpick_include.hh" +using namespace bparser; +using namespace bparser::details; +#endif //NITPICK_IDE_IGNORE //This is here only to stop any IDE warnings +ExpressionDAG gen(const NodeMap& node_map){ //Do not rename this function, it is used later in the nitpick_run.cc file + +ScalarNodePtr Value_8__2 = ScalarNode::create_value(node_map.at("v1[0]")); +ScalarNodePtr mul_7__3 = ScalarNode::create<_mul_>(Value_8__2, Value_8__2); +ScalarNodePtr Value_6__2 = ScalarNode::create_value(node_map.at("v1[1]")); +ScalarNodePtr mul_5__3 = ScalarNode::create<_mul_>(Value_6__2, Value_6__2); +ScalarNodePtr add_4__3 = ScalarNode::create<_add_>(mul_7__3, mul_5__3); +ScalarNodePtr Value_3__2 = ScalarNode::create_value(node_map.at("v1[2]")); +ScalarNodePtr mul_2__3 = ScalarNode::create<_mul_>(Value_3__2, Value_3__2); +ScalarNodePtr add_1__3 = ScalarNode::create<_add_>(add_4__3, mul_2__3); +ScalarNodePtr sqrt_0__4 = ScalarNode::create<_sqrt_>(add_1__3); + + +ScalarNodePtr r0 = ScalarNode::create_result(sqrt_0__4, node_map.at("_result_[]")); +ExpressionDAG se({r0}); + +return se; +} //gen diff --git a/test/nitpick/README.md b/test/nitpick/README.md new file mode 100644 index 0000000..2e1c33b --- /dev/null +++ b/test/nitpick/README.md @@ -0,0 +1,8 @@ +# Nitpick + +## Files + +* `exprcase.hh` - Defines the ExprCase class used in the test cases. It is referenced in every test/case/*_def.hh file +* `nitpick_include.hh` - Common includes and macros, shared by both .cc files. Throws preprocessor error if DEF_FILE or GEN_FILE is not defined +* `nitpick_generate.cc` - Takes the provided DEF_FILE, uses DagPrinter to generate the output and writes to GEN_FILE +* `nitpick_run.cc` - Takes the provided DEF_FILE and GEN_FILE and runs and times the expression for different vec_sizes \ No newline at end of file diff --git a/test/nitpick/example_def.hh b/test/nitpick/example_def.hh new file mode 100644 index 0000000..5fafc77 --- /dev/null +++ b/test/nitpick/example_def.hh @@ -0,0 +1,12 @@ + +#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 + +void def(ExprCase& c) { + +c.parse("1+1"); + +//c.set_variable("v1" {3}); +c.set_result_shape({}); +} \ No newline at end of file diff --git a/test/nitpick/exprcase.hh b/test/nitpick/exprcase.hh new file mode 100644 index 0000000..d5f9e99 --- /dev/null +++ b/test/nitpick/exprcase.hh @@ -0,0 +1,188 @@ +/* + * exprcase.hh + * Holds the memory and other config for generating and running DAGs + * + * Created on: Jan 20, 2026 + * Author: LV + */ + + +#ifndef EXPRCASE_HH +#define EXPRCASE_HH + +#include "arena_resource.hh" +#include "parser.hh" + +using namespace bparser; + +namespace bparser { + + typedef std::unordered_map NodeMap; + + class ExprCase { + public: + enum VarType { + Variable, + VarCopy, + Const + }; + + using string = std::string; + using Shape = bparser::Shape; + typedef std::pair VarInfo; + + + + public: //Constructors + ExprCase(uint max_vec_size, void* buffer, size_t buffer_size, size_t simd_size) : + max_vec_size(max_vec_size), + arena(std::make_shared(buffer, buffer_size, simd_size)), + parser(Parser(max_vec_size)) + { + ; + } + + ExprCase(uint max_vec_size, void* buffer, size_t buffer_size) : + ExprCase(max_vec_size, buffer, buffer_size, bparser::get_simd_size()) + { + ; + } + protected: + + uint max_vec_size; + + PatchArenaPtr arena; + Parser parser; + std::string parseExpr; + + std::unordered_map variables; + std::unordered_map> variable_consts; + + std::unordered_map inv_map; //given to the print_in_cxx function to generate the file + NodeMap node_map; //is used by the generated file to use the same double pointers as the parser + + + public: //Methods for the *_def.hh files + void set_variable(string name, Shape shape){ + variables[name] = VarInfo(Variable, shape); + } + void set_var_copy(string name, Shape shape){ + variables[name] = VarInfo(VarCopy, shape); + } + void set_constant(string name, Shape shape, std::vector values){ + variables[name] = VarInfo(Const, shape); + variable_consts[name] = values; + } + void set_result_shape(Shape shape) { + variables["_result_"] = VarInfo(Variable, shape); + } + void parse(string expression) { + parseExpr = expression; + + parser.parse(expression); + } + + public: //Methods for the gen/run files + + void deallocate() { + arena->reset(); + } + + void allocate(double vec_size) { + for (const auto& [varName, varInfo] : variables) { + VarType t = varInfo.first; + Shape shape = varInfo.second; + double n_values = numel(shape); + double* ptr = arena->allocate_simd(n_values * vec_size); + + if (t == Const) { + map_const(varName, variable_consts[varName], shape); + } + else { + map_variable(varName, ptr, shape); + } + + + if (t == Variable) { + parser.set_variable(varName, shape, ptr); + }else if (t == VarCopy) { + parser.set_var_copy(varName, shape, ptr); + } + else if (t == Const) { + parser.set_constant(varName, shape, variable_consts[varName]); + } + + } + } + + PatchArenaPtr get_patch_arena() const { + return arena; + } + string get_expression() const { + return parseExpr; + } + + const std::unordered_map& get_inv_map() const{ + return inv_map; + } + const NodeMap& get_node_map() const{ + return node_map; + } + + Parser& get_parser() { + return parser; + } + + protected: //Helper methods + + //Put the variable names and ptrs in the maps for gen/run use + void map_variable(string name,double* pointer, const Shape& shape) { + Array array = Array::value(pointer, max_vec_size, shape); + + for (MultiIdx idx(array.range()); idx.valid(); idx.inc_src()) { + + std::string var_name = get_var_name(name, idx.indices()); + inv_map[array[idx]->values_] = var_name; + node_map[var_name] = array[idx]->values_; + } + } + + void map_const(string name, const std::vector& values, const Shape& shape) { + Array array = Array::constant(values, shape); //useless + + for (MultiIdx idx(array.range()); idx.valid(); idx.inc_src()) { + + std::string var_name = get_var_name(name, idx.indices()); + //inv_map[array[idx]->values_] = var_name; + //node_map[var_name] = array[idx]->values_; + } + } + + inline double numel(const Shape& s) { + double res = 1; + for (const uint& n : s) { + res *= n; + } + return res; + } + + // 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 //EXPRCASE_HH \ No newline at end of file diff --git a/test/nitpick/nitpick_generate.cc b/test/nitpick/nitpick_generate.cc new file mode 100644 index 0000000..1d76ac6 --- /dev/null +++ b/test/nitpick/nitpick_generate.cc @@ -0,0 +1,45 @@ +#include "nitpick_include.hh" +#include +#include +#define NITPICK_IDE_IGNORE + +#include NITPICK_DEF_FILE + +using namespace bparser; + +int main() { + + const uint max_vec_size = 20000; + const size_t buffer_size = sizeof(double) * max_vec_size; + + void* buffer = ::operator new(buffer_size); + + + //double buffer[buffer_size]; + ExprCase exprcase(max_vec_size,(void*)buffer, buffer_size); + + //def is in NITPICK_DEF_FILE + def(exprcase); + + if (exprcase.get_expression().empty()) { + Throw() << "No expression was set!"; + } + + exprcase.allocate(1); + Parser& p = exprcase.get_parser(); + + // Compile the expression into internal processor. + p.compile(); + //p.compile(exprcase.get_patch_arena()); //Add arena from ExprCase + + ExpressionDAG dag(p.result_array().elements()); + //dag.print_in_dot2(); + + std::ofstream file(NITPICK_GEN_FILE); + file << DagPrinter(dag).print_in_cxx(exprcase.get_inv_map()); + file.close(); + std::cout << "File " << std::filesystem::current_path() << " " << NITPICK_GEN_FILE << " created from " << NITPICK_DEF_FILE; + + ::operator delete(buffer); + +} //main \ No newline at end of file diff --git a/test/nitpick/nitpick_include.hh b/test/nitpick/nitpick_include.hh new file mode 100644 index 0000000..a22067f --- /dev/null +++ b/test/nitpick/nitpick_include.hh @@ -0,0 +1,29 @@ +#ifndef NITPICK_INCLUDE_HH +#define NITPICK_INCLUDE_HH + +#ifndef NITPICK_DEF_FILE +# ifdef DEF_FILE +# define NITPICK_DEF_FILE DEF_FILE +# else +# error [Nitpick] DEF_FILE not defined +# endif +#endif + +#ifndef NITPICK_GEN_FILE +# ifdef GEN_FILE +# define NITPICK_GEN_FILE GEN_FILE +# else +# error [Nitpick] GEN_FILE not defined +# endif +#endif + +#include "test_tools.hh" +#include "parser.hh" +#include +#include "exprcase.hh" + +//void def(ExprCase c); +//ExpressionDAG gen(const NodeMap& node_map); + + +#endif //NITPICK_INCLUDE_HH \ No newline at end of file diff --git a/test/nitpick/nitpick_run.cc b/test/nitpick/nitpick_run.cc new file mode 100644 index 0000000..50f8aba --- /dev/null +++ b/test/nitpick/nitpick_run.cc @@ -0,0 +1,66 @@ +#include "nitpick_include.hh" +#include +// only get us the variables +#define NITPICK_IDE_IGNORE + +using namespace bparser; + //include all the variables used to generate the file +#include NITPICK_DEF_FILE + //include the generated file +#include NITPICK_GEN_FILE + +int main() { + + const uint max_vec_size = 2000; + const size_t buffer_size = sizeof(double) * max_vec_size + 500000; + + const uint N = 1073741824U; //2^30 + + void* buffer = ::operator new(buffer_size); + ExprCase exprcase(max_vec_size, (void*)buffer, buffer_size); + + def(exprcase); + + if (exprcase.get_expression().empty()) { + Throw() << "No expression was set!"; + } + + std::vector subset = { 0, 1, 2, 3, 4,5,6,7,8,9,10,11,12 }; //ctverice doubluu //TODO: autocreate from vec_size + std::vector vec_sizes({ 16U, 64U, 256U, 1024U }); + + PatchArenaPtr arena = exprcase.get_patch_arena(); + for (uint vec_size : vec_sizes) { + + uint n_repeats = N / vec_size; + std::cout << "Running \"" << exprcase.get_query() << "\" " << n_repeats << "x for vec_size: " << vec_size << std::endl; + exprcase.allocate(vec_size); + + //Get autogenerated dag; + ExpressionDAG se = gen(exprcase.get_node_map()); + + ProcessorBase* processor = ProcessorBase::create_processor(se, max_vec_size, bparser::get_simd_size(), arena); //get arena from ExprCase + processor->set_subset(subset); + + auto start_time = std::chrono::high_resolution_clock::now(); + for (uint i_rep = 0; i_rep < n_repeats; i_rep++) { + processor->run(); + } + auto end_time = std::chrono::high_resolution_clock::now(); + + double time = std::chrono::duration_cast>(end_time - start_time).count(); + std::cout << "Finished in " << time << std::endl; + + + exprcase.deallocate(); //processor is allocated in arena + } //for + + + //TODO: have result + //std::cout << "Result: \n"; + + // Result in the 'vres' value space. + //std::cout << print_vec(vres, vres_size); + + ::operator delete(buffer); + +} //main \ No newline at end of file diff --git a/test/test_parser.cc b/test/test_parser.cc index 28b3c20..836455b 100644 --- a/test/test_parser.cc +++ b/test/test_parser.cc @@ -261,6 +261,19 @@ 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("[[1,2],[3,4]].T", {1, 3, 2, 4}, { 2,2 })); //Fix .T before uncommenting + //BP_ASSERT(test_expr("a = sym([[1,2],[3,4]]); a.T == a", { 1,1,1,1 }, { 2,2 })); + BP_ASSERT(test_expr("dev([[1,2],[3,4]])", { 1-2.5, 2, 3, 4-2.5 }, {2,2})); + BP_ASSERT(test_expr("tr(dev([[1,2],[3,4]]))", { 0 }, {})); + 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 +309,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: