diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 97c3772c..7e4fd2be 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -43,3 +43,23 @@ jobs: run: | cd build make test + + coek-build-and-test-with-cpp14: + runs-on: ubuntu-latest + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v3 + - name: run cmake + run: | + mkdir build + cd build + cmake -DCMAKE_CXX_STANDARD=14 -Dwith_python=OFF -Dwith_tests=ON -Dwith_debug=ON .. + - name: build + run: | + cd build + make install_tpls + make + - name: test + run: | + cd build + make test diff --git a/.gitignore b/.gitignore index e18b4b6e..ac06aeb8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,6 @@ # ----------------------------------- # ignore top level build directory /build/ -# ignore Catch2 directory in top level third_party directory -/third_party/Catch2/ -# ignore CppAD directory in top level third_party directory -/third_party/CppAD/ _build /.vs diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cf05fcf..f225b932 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.13) project(CoekProject) set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}) @@ -39,11 +39,11 @@ add_subdirectory(tpl) add_custom_target(install_tpls DEPENDS _install_tpls - COMMAND ${CMAKE_COMMAND} -Dwith_pybind11=ON -Dwith_catch2=ON -Dwith_fmtlib=ON -Dwith_cppad=ON -Dwith_rapidjson=ON ${PROJECT_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} -Dwith_pybind11=ON -Dwith_catch2=ON -Dwith_fmtlib=ON -Dwith_cppad=ON -Dwith_rapidjson=ON -Dwith_asl=ON ${PROJECT_SOURCE_DIR} ) add_custom_target(install_tpl DEPENDS _install_tpls - COMMAND ${CMAKE_COMMAND} -Dwith_pybind11=ON -Dwith_catch2=ON -Dwith_fmtlib=ON -Dwith_cppad=ON -Dwith_rapidjson=ON ${PROJECT_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} -Dwith_pybind11=ON -Dwith_catch2=ON -Dwith_fmtlib=ON -Dwith_cppad=ON -Dwith_rapidjson=ON -Dwith_asl=ON ${PROJECT_SOURCE_DIR} ) ##################### Options ##################### @@ -61,24 +61,6 @@ option(with_tests "Build tests" OFF) option(with_docs "Build documentation" OFF) option(with_python "Build/Install Python libs" ${Python_FOUND}) -message("Initial Configuration Options") -# Options defined here -message("-- with_debug: ${with_debug}") -message("-- with_verbose: ${with_verbose}") -message("-- with_gcov: ${with_gcov}") -message("-- with_gprof: ${with_gprof}") -message("-- with_caliper: ${with_caliper}") -message("-- with_tests: ${with_tests}") -message("-- with_docs: ${with_docs}") -message("-- with_python: ${with_python}") -# Options defined in tpl -message("-- with_catch2: ${with_catch2}") -message("-- with_cppad: ${with_cppad}") -message("-- with_pybind11: ${with_pybind11}") -message("-- with_cppyy: ${with_cppyy}") -message("-- with_rapidjson: ${with_rapidjson}") -message("-- with_fmtlib: ${with_fmtlib}") - ##################### Checks for compiler ##################### message("Revised Configuration Options") @@ -118,6 +100,34 @@ else() MESSAGE("-- C++ version: C++${CMAKE_CXX_STANDARD}") endif() +if (CMAKE_CXX_STANDARD LESS 17) + message("DISABLING FMTLIB") + set(with_fmtlib OFF) +endif() + +##################### General Configuration Options ##################### + +message("Initial Configuration Options") +# Options defined here +message("-- with_debug: ${with_debug}") +message("-- with_verbose: ${with_verbose}") +message("-- with_gcov: ${with_gcov}") +message("-- with_gprof: ${with_gprof}") +message("-- with_caliper: ${with_caliper}") +message("-- with_tests: ${with_tests}") +message("-- with_docs: ${with_docs}") +message("-- with_python: ${with_python}") +# Options defined in tpl +message("-- with_asl: ${with_asl}") +message("-- with_catch2: ${with_catch2}") +message("-- with_cppad: ${with_cppad}") +message("-- with_cppyy: ${with_cppyy}") +message("-- with_fmtlib: ${with_fmtlib}") +message("-- with_pybind11: ${with_pybind11}") +message("-- with_rapidjson: ${with_rapidjson}") + +##################### Other Configuraton Options ##################### + if (with_gcov) if (CMAKE_CXX_COMPILER_ID MATCHES GNU) else() diff --git a/ChangeLog.md b/ChangeLog.md index e29add91..6a196c9a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,16 @@ Here we list changes of Coek. More detailed information about incremental changes can be found in the [commit history](https://github.com/sandialabs/coek/commits). +## 1.3 + +* Added ASL autograd functionality +* Various fixes in expression management logic. +* Improving support for C++14 +* Updates to support Python 3.11 +* Switch to use SHA256 hashes for third-party packages +* Adding expression simplifier logic +* Fixing many bugs with NL writer + ## 1.2 * Added the SubExpression object, which is used to denote diff --git a/app/demos/CMakeLists.txt b/app/demos/CMakeLists.txt index 90eecf25..b91e830a 100644 --- a/app/demos/CMakeLists.txt +++ b/app/demos/CMakeLists.txt @@ -4,7 +4,7 @@ PROJECT(demos) # # For now, let's just assume we have C++17 # -set(CMAKE_CXX_STANDARD 17) +#set(CMAKE_CXX_STANDARD 17) MESSAGE("-- With Gurobi Solver: ${with_gurobi}") if(with_gurobi) diff --git a/app/demos/demo1.cpp b/app/demos/demo1.cpp index dc7d9d1f..e4c0d50c 100644 --- a/app/demos/demo1.cpp +++ b/app/demos/demo1.cpp @@ -24,6 +24,7 @@ coek::Model knapsack(size_t N) auto model = coek::Model(); +#if __cpp_lib_variant auto x = coek::variable(N).bounds(0, 1).value(0); model.add(x); @@ -36,6 +37,7 @@ coek::Model knapsack(size_t N) auto con = coek::expression(); for (size_t n : coek::range(N)) con += w[n] * x(n); model.add(con <= W); +#endif return model; } diff --git a/lib/coek/coek/CMakeLists.txt b/lib/coek/coek/CMakeLists.txt index 5f37f25f..6f03a8f8 100644 --- a/lib/coek/coek/CMakeLists.txt +++ b/lib/coek/coek/CMakeLists.txt @@ -14,8 +14,9 @@ SET(sources ast/visitor_symdiff.cpp ast/visitor_mutable_values.cpp ast/visitor_variables.cpp + ast/visitor_simplify.cpp ast/visitor_eval.cpp - ast/varray.cpp + #ast/varray.cpp api/constants.cpp api/expression.cpp api/expression_visitor.cpp @@ -34,7 +35,7 @@ SET(sources autograd/autograd.cpp abstract/expr_rule.cpp ) -if (CMAKE_CXX_STANDARD GREATER_EQUAL 14) +if (CMAKE_CXX_STANDARD GREATER_EQUAL 17) list(APPEND sources ast/compact_terms.cpp api/parameter_assoc_array.cpp @@ -104,9 +105,7 @@ endif() # ADMODEL OPTIONS # -# # CppAD LIBRARY -# if(with_cppad) list(APPEND sources autograd/cppad_repn.cpp) @@ -115,11 +114,14 @@ if(with_cppad) endif() # ASL LIBRARY -# if(NOT with_cppad AND with_asl) -# MESSAGE("-- Building Source with AD: ASL") -# list(APPEND sources -# autograd/asl_model.cpp) -# endif() +if(with_asl) + list(APPEND sources + autograd/asl_repn.cpp) + list(APPEND coek_compile_options "-DWITH_ASL") + list(APPEND coek_include_directories ${CMAKE_INSTALL_PREFIX}/include/asl) + list(APPEND coek_link_directories ${CMAKE_INSTALL_PREFIX}/lib) + list(APPEND coek_link_libraries asl) +endif() # Default AD LIBRARY # if(NOT with_cppad AND NOT with_asl) @@ -197,12 +199,13 @@ target_include_directories(coek PUBLIC ${coek_include_directories}) target_link_libraries(coek PUBLIC ${coek_link_libraries}) +target_link_directories(coek PUBLIC ${coek_link_directories}) set_property(TARGET coek PROPERTY INTERFACE_LINK_LIBRARIES ${coek_link_libraries}) -if (WIN32) - target_link_libraries(coek ${coek_link_libraries}) - target_link_directories(coek PRIVATE ${coek_link_directories}) -endif() +#if (WIN32) +# target_link_libraries(coek ${coek_link_libraries}) +# target_link_directories(coek PRIVATE ${coek_link_directories}) +#endif() # # make install diff --git a/lib/coek/coek/api/expression.cpp b/lib/coek/coek/api/expression.cpp index cfff3aac..c28a65c9 100644 --- a/lib/coek/coek/api/expression.cpp +++ b/lib/coek/coek/api/expression.cpp @@ -299,18 +299,21 @@ Variable variable(const std::string& name) Expression::Expression() : repn(ZEROCONST) {} -Expression::Expression(const ParameterRepn& _repn) : repn(_repn) {} -Expression::Expression(const IndexParameterRepn& _repn) : repn(_repn) {} +// Expression::Expression(const ParameterRepn& _repn) : repn(_repn) {} + +// Expression::Expression(const IndexParameterRepn& _repn) : repn(_repn) {} + Expression::Expression(const VariableRepn& _repn) : repn(_repn) {} + Expression::Expression(const ExpressionRepn& _repn) : repn(_repn) {} Expression::Expression(ExpressionRepn&& _repn) : repn(_repn) {} -Expression::Expression(ParameterRepn&& _repn) : repn(_repn) {} +// Expression::Expression(ParameterRepn&& _repn) : repn(_repn) {} -Expression::Expression(IndexParameterRepn&& _repn) : repn(_repn) {} +// Expression::Expression(IndexParameterRepn&& _repn) : repn(_repn) {} -Expression::Expression(VariableRepn&& _repn) : repn(_repn) {} +Expression::Expression(VariableRepn&& _repn) : repn(_repn) {} // TODO - why isn't this covered? Expression::Expression(double value) { repn = CREATE_POINTER(ConstantTerm, value); } @@ -541,6 +544,7 @@ Expression expression(const Variable& arg) { return coek::Expression(arg); } SubExpression::SubExpression() { repn = CREATE_POINTER(SubExpressionTerm, ZEROCONST); } +/* SubExpression::SubExpression(double value) { repn = CREATE_POINTER(SubExpressionTerm, CREATE_POINTER(ConstantTerm, value)); @@ -550,6 +554,7 @@ SubExpression::SubExpression(int value) { repn = CREATE_POINTER(SubExpressionTerm, CREATE_POINTER(ConstantTerm, value)); } +*/ SubExpression::SubExpression(const Parameter& arg) { @@ -602,24 +607,6 @@ std::list SubExpression::to_list() const return tmp; } -Expression SubExpression::diff(const Variable& var) const -{ - std::map, expr_pointer_t> ans; - symbolic_diff_all(repn, ans); - Expression e; - if (ans.find(var.repn) != ans.end()) e = ans[var.repn]; - return e; -} - -Expression SubExpression::expand() -{ -#ifdef COEK_WITH_COMPACT_MODEL - return convert_expr_template(repn); -#else - return *this; -#endif -} - SubExpression& SubExpression::operator+=(int arg) { Expression e(arg); diff --git a/lib/coek/coek/api/expression.hpp b/lib/coek/coek/api/expression.hpp index 864c97f8..68ebc9be 100644 --- a/lib/coek/coek/api/expression.hpp +++ b/lib/coek/coek/api/expression.hpp @@ -306,12 +306,12 @@ class Expression { /** Implicit construction of an Expression from a SubExpression */ Expression(const SubExpression& arg); - Expression(const ParameterRepn& _repn); - Expression(const IndexParameterRepn& _repn); + // Expression(const ParameterRepn& _repn); + // Expression(const IndexParameterRepn& _repn); Expression(const VariableRepn& _repn); Expression(const ExpressionRepn& _repn); - Expression(ParameterRepn&& _repn); - Expression(IndexParameterRepn&& _repn); + // Expression(ParameterRepn&& _repn); + // Expression(IndexParameterRepn&& _repn); Expression(VariableRepn&& _repn); Expression(ExpressionRepn&& _repn); @@ -414,9 +414,9 @@ class SubExpression { /** Constructs a SubExpression without defining its value */ SubExpression(); /** Explict construction of a SubExpression from a double */ - explicit SubExpression(double value); + // explicit SubExpression(double value); /** Explict construction of a SubExpression from an integer */ - explicit SubExpression(int value); + // explicit SubExpression(int value); /** Implicit construction of a SubExpression from a Parameter */ SubExpression(const Parameter& arg); /** Implicit construction of a SubExpression from an IndexParameter */ @@ -449,13 +449,6 @@ class SubExpression { /** \returns a list representation of the expression */ std::list to_list() const; - /** - * Create an expression that computes the partial derivative relative to a specified variable. - * - * \param var - The variable that will be used to compute the partial derivative - * \returns an expression that computes the partial derivative - */ - Expression diff(const Variable& var) const; /** Add an integer to the expression */ SubExpression& operator+=(int arg); @@ -516,9 +509,6 @@ class SubExpression { SubExpression& operator/=(const Expression& arg); /** Divide the expression by a SubExpression */ SubExpression& operator/=(const SubExpression& arg); - - /** \returns an expanded Expression */ - Expression expand(); }; SubExpression subexpression(); diff --git a/lib/coek/coek/api/indexed_container.defs.hpp b/lib/coek/coek/api/indexed_container.defs.hpp index aae4292e..76479b7b 100644 --- a/lib/coek/coek/api/indexed_container.defs.hpp +++ b/lib/coek/coek/api/indexed_container.defs.hpp @@ -48,25 +48,27 @@ void IndexedComponentRepn_multiarray::generate_names() // then we do not try to generate names. The default/simple // names will be used. auto name = this->_name; - if (name == "") return; + if (name.size() == 0) return; - for (auto& con : this->value) { + for (auto& component : this->value) { name += "["; - if (shape.size() >= 1) name += std::to_string(con.first[0]); + auto shape_size = shape.size(); + if (shape_size >= 1) name += std::to_string(component.first[0]); - if (shape.size() > 1) { - for (size_t i = 1; i < shape.size(); ++i) name += "," + std::to_string(con.first[i]); + if (shape_size > 1) { + for (size_t i = 1; i < shape_size; ++i) { + name += ","; + name += std::to_string(component.first[i]); + } } name += "]"; - con.second.name(name); + component.second.name(name); } } -void zzz(); - #ifdef COEK_WITH_COMPACT_MODEL template class IndexedComponentRepn_setindex : public IndexedComponentRepn { @@ -88,7 +90,6 @@ class IndexedComponentRepn_setindex : public IndexedComponentRepn { return this->concrete_set.contains(args[0]); else { for (size_t i = 0; i < tmp.size(); i++) tmp[i] = args[i]; - // zzz(); return this->concrete_set.contains(tmp); } } @@ -175,13 +176,10 @@ IndexedComponent_Map::IndexedComponent_Map(ConcreteSet& arg) } #endif -void zzz(); - template TYPE& IndexedComponent_Map::index(const IndexVector& args) { assert(this->dim() == args.size()); - // zzz(); if (!(this->repn->valid_index(this->tmp))) { std::string err = "Unexpected index value: " + this->repn->_name + "("; diff --git a/lib/coek/coek/api/indexed_container.hpp b/lib/coek/coek/api/indexed_container.hpp index d1a43d17..b5d5db3c 100644 --- a/lib/coek/coek/api/indexed_container.hpp +++ b/lib/coek/coek/api/indexed_container.hpp @@ -55,8 +55,9 @@ class IndexedComponent { std::vector reftmp; public: - typename std::map::iterator begin() { return repn->value.begin(); } - typename std::map::iterator end() { return repn->value.end(); } + using iterator = typename std::map; + iterator begin() { return repn->value.begin(); } + iterator end() { return repn->value.end(); } size_t size() { return repn->size(); } size_t dim() { return repn->dim(); } diff --git a/lib/coek/coek/api/parameter_array.cpp b/lib/coek/coek/api/parameter_array.cpp index 5e99bcd4..ea57ac51 100644 --- a/lib/coek/coek/api/parameter_array.cpp +++ b/lib/coek/coek/api/parameter_array.cpp @@ -64,7 +64,7 @@ void ParameterArrayRepn::generate_names() // then we do not try to generate names. The default/simple // parameter names will be used. std::string name = parameter_template.name(); - if (name == "") return; + if (name.size() == 0) return; setup(); @@ -110,6 +110,8 @@ Parameter ParameterArray::index(const IndexVector& args) for (size_t i = 1; i < args.size(); i++) ndx = ndx * shape[i] + args[i]; if (ndx > size()) { + // TODO - Can't we do better than this check? Do we check if each index is in the correct + // range? std::string err = "Unknown index value: " + _repn->parameter_template.name() + "["; for (size_t i = 0; i < args.size(); i++) { if (i > 0) err += ","; diff --git a/lib/coek/coek/api/parameter_array.hpp b/lib/coek/coek/api/parameter_array.hpp index ce1eab9c..54488645 100644 --- a/lib/coek/coek/api/parameter_array.hpp +++ b/lib/coek/coek/api/parameter_array.hpp @@ -76,7 +76,7 @@ class ParameterArray : public ParameterAssocArray { void collect_args(size_t i, int arg) { - assert(arg >= 0); + assert(arg >= 0); // TODO - fix code coverage here tmp[i] = arg; } @@ -95,6 +95,7 @@ class ParameterArray : public ParameterAssocArray { collect_args(i + 1, args...); } +#ifdef COEK_WITH_COMPACT_MODEL template typename std::enable_if::value, Expression>::type operator()( const ARGTYPES&... args) @@ -104,6 +105,7 @@ class ParameterArray : public ParameterAssocArray { collect_refargs(static_cast(0), args...); return create_paramref(reftmp); } +#endif template typename std::enable_if::value, Parameter>::type operator()( diff --git a/lib/coek/coek/api/parameter_assoc_array.cpp b/lib/coek/coek/api/parameter_assoc_array.cpp index c0e32ef1..f21ea52d 100644 --- a/lib/coek/coek/api/parameter_assoc_array.cpp +++ b/lib/coek/coek/api/parameter_assoc_array.cpp @@ -7,9 +7,6 @@ namespace coek { -expr_pointer_t create_paramref(const std::vector& indices, const std::string& name, - void* var); - // // ParameterAssocArrayRepn // @@ -55,7 +52,13 @@ void ParameterAssocArrayRepn::name(const std::string& name) { parameter_template.name(name); if (values.size() > 0) { - for (auto& var : values) var.name(name); + // If the string is empty, then we reset the names of all variables + if (name.size() == 0) { + for (auto& var : values) var.name(name); + } + // Otherwise, we re-generate the names + else + generate_names(); } } @@ -71,15 +74,21 @@ std::vector::iterator ParameterAssocArray::begin() { return get_repn( std::vector::iterator ParameterAssocArray::end() { return get_repn()->values.end(); } +#ifdef COEK_WITH_COMPACT_MODEL +expr_pointer_t create_paramref(const std::vector& indices, const std::string& name, + void* var); + Expression ParameterAssocArray::create_paramref(const std::vector& args) { return coek::create_paramref(args, get_repn()->parameter_template.name(), this); } +#endif // // OTHER // +#ifdef COEK_WITH_COMPACT_MODEL expr_pointer_t get_concrete_param(ParameterRefTerm& paramref) { ParameterAssocArray* param = static_cast(paramref.param); @@ -104,5 +113,6 @@ expr_pointer_t get_concrete_param(ParameterRefTerm& paramref) Expression e = param->index(tmp); return e.repn; } +#endif } // namespace coek diff --git a/lib/coek/coek/api/parameter_assoc_array.hpp b/lib/coek/coek/api/parameter_assoc_array.hpp index 3aca488c..5735c3e6 100644 --- a/lib/coek/coek/api/parameter_assoc_array.hpp +++ b/lib/coek/coek/api/parameter_assoc_array.hpp @@ -18,7 +18,9 @@ class ParameterAssocArray { size_t dim(); virtual Parameter index(const IndexVector& args) = 0; +#ifdef COEK_WITH_COMPACT_MODEL Expression create_paramref(const std::vector& indices); +#endif }; } // namespace coek diff --git a/lib/coek/coek/api/parameter_assoc_array_repn.hpp b/lib/coek/coek/api/parameter_assoc_array_repn.hpp index 0c3155f1..74347a8c 100644 --- a/lib/coek/coek/api/parameter_assoc_array_repn.hpp +++ b/lib/coek/coek/api/parameter_assoc_array_repn.hpp @@ -34,12 +34,12 @@ class ParameterAssocArrayRepn { void resize_index_vectors(IndexVector& tmp, std::vector& reftmp); - /** Set the initial variable value. \returns the variable object. */ + /** Set the initial variable value. */ void value(double value); - /** Set the initial variable value. \returns the variable object. */ + /** Set the initial variable value. */ void value(const Expression& value); - /** Set the name of the variable. \returns the variable object */ + /** Set the name of the variable. */ void name(const std::string& name); }; diff --git a/lib/coek/coek/api/variable_array.cpp b/lib/coek/coek/api/variable_array.cpp index 21119c2e..8f9f3024 100644 --- a/lib/coek/coek/api/variable_array.cpp +++ b/lib/coek/coek/api/variable_array.cpp @@ -21,13 +21,15 @@ class VariableArrayRepn : public VariableAssocArrayRepn { cache.resize((size() + 1) * (dim() + 1)); } - VariableArrayRepn(const std::vector& _shape) : _size(1) - { - shape.resize(_shape.size()); - for (size_t i = 0; i < shape.size(); ++i) shape[i] = static_cast(_shape[i]); - for (auto n : shape) _size *= n; - cache.resize((size() + 1) * (dim() + 1)); - } + /* + VariableArrayRepn(const std::vector& _shape) : _size(1) + { + shape.resize(_shape.size()); + for (size_t i = 0; i < shape.size(); ++i) shape[i] = static_cast(_shape[i]); + for (auto n : shape) _size *= n; + cache.resize((size() + 1) * (dim() + 1)); + } + */ VariableArrayRepn(const std::initializer_list& _shape) : shape(_shape), _size(1) { @@ -97,11 +99,13 @@ VariableArray::VariableArray(const std::vector& shape) repn->resize_index_vectors(tmp, reftmp); } +/* VariableArray::VariableArray(const std::vector& shape) { repn = std::make_shared(shape); repn->resize_index_vectors(tmp, reftmp); } +*/ VariableArray::VariableArray(const std::initializer_list& shape) { @@ -148,13 +152,16 @@ void VariableArray::index_error(size_t i) throw std::runtime_error(err); } -std::vector::const_iterator VariableArray::begin() const { return repn->values.begin(); } +VariableArray::const_iterator VariableArray::cbegin() const noexcept +{ + return repn->values.begin(); +} -std::vector::const_iterator VariableArray::end() const { return repn->values.end(); } +VariableArray::const_iterator VariableArray::cend() const noexcept { return repn->values.end(); } -std::vector::iterator VariableArray::begin() { return repn->values.begin(); } +VariableArray::iterator VariableArray::begin() noexcept { return repn->values.begin(); } -std::vector::iterator VariableArray::end() { return repn->values.end(); } +VariableArray::iterator VariableArray::end() noexcept { return repn->values.end(); } VariableArray& VariableArray::generate_names() { diff --git a/lib/coek/coek/api/variable_array.hpp b/lib/coek/coek/api/variable_array.hpp index 6de78a82..52782416 100644 --- a/lib/coek/coek/api/variable_array.hpp +++ b/lib/coek/coek/api/variable_array.hpp @@ -20,10 +20,12 @@ class VariableArray : public VariableAssocArray { Variable index(const IndexVector& args); void index_error(size_t i); - std::vector::const_iterator begin() const; - std::vector::const_iterator end() const; - std::vector::iterator begin(); - std::vector::iterator end(); + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + iterator begin() noexcept; + iterator end() noexcept; VariableArray& generate_names(); @@ -82,7 +84,7 @@ class VariableArray : public VariableAssocArray { void collect_args(size_t i, int arg) { - assert(arg >= 0); + assert(arg >= 0); // TODO - Resolve coverage for this assertion tmp[i] = arg; } @@ -101,6 +103,7 @@ class VariableArray : public VariableAssocArray { collect_args(i + 1, args...); } +#ifdef COEK_WITH_COMPACT_MODEL template typename std::enable_if::value, Expression>::type operator()( const ARGTYPES&... args) @@ -110,6 +113,7 @@ class VariableArray : public VariableAssocArray { collect_refargs(static_cast(0), args...); return create_varref(reftmp); } +#endif template typename std::enable_if::value, Variable>::type operator()( @@ -144,7 +148,7 @@ class VariableArray : public VariableAssocArray { public: VariableArray(size_t n); VariableArray(const std::vector& shape); - VariableArray(const std::vector& shape); + // VariableArray(const std::vector& shape); VariableArray(const std::initializer_list& shape); ~VariableArray() {} diff --git a/lib/coek/coek/api/variable_assoc_array.cpp b/lib/coek/coek/api/variable_assoc_array.cpp index ba0dcfa1..bd128e4f 100644 --- a/lib/coek/coek/api/variable_assoc_array.cpp +++ b/lib/coek/coek/api/variable_assoc_array.cpp @@ -130,7 +130,19 @@ void VariableAssocArrayRepn::fixed(bool value) } } -void VariableAssocArrayRepn::name(const std::string& name) { variable_template.name(name); } +void VariableAssocArrayRepn::name(const std::string& name) +{ + variable_template.name(name); + if (values.size() > 0) { + // If the string is empty, then we reset the names of all variables + if (name.size() == 0) { + for (auto& var : values) var.name(name); + } + // Otherwise, we re-generate the names + else + generate_names(); + } +} void VariableAssocArrayRepn::within(VariableTypes vtype) { @@ -152,15 +164,18 @@ std::vector::iterator VariableAssocArray::begin() { return get_repn()- std::vector::iterator VariableAssocArray::end() { return get_repn()->values.end(); } +#ifdef COEK_WITH_COMPACT_MODEL Expression VariableAssocArray::create_varref(const std::vector& args) { return coek::create_varref(args, get_repn()->variable_template.name(), this); } +#endif // // OTHER // +#ifdef COEK_WITH_COMPACT_MODEL expr_pointer_t get_concrete_var(VariableRefTerm& varref) { VariableAssocArray* var = static_cast(varref.var); @@ -185,5 +200,6 @@ expr_pointer_t get_concrete_var(VariableRefTerm& varref) Expression e = var->index(tmp); return e.repn; } +#endif } // namespace coek diff --git a/lib/coek/coek/api/variable_assoc_array.hpp b/lib/coek/coek/api/variable_assoc_array.hpp index 6a256b58..1db86863 100644 --- a/lib/coek/coek/api/variable_assoc_array.hpp +++ b/lib/coek/coek/api/variable_assoc_array.hpp @@ -19,7 +19,9 @@ class VariableAssocArray { size_t dim() const; virtual Variable index(const IndexVector& args) = 0; +#ifdef COEK_WITH_COMPACT_MODEL Expression create_varref(const std::vector& indices); +#endif }; } // namespace coek diff --git a/lib/coek/coek/ast/base_terms.hpp b/lib/coek/coek/ast/base_terms.hpp index 7e5fabac..d2ddbd94 100644 --- a/lib/coek/coek/ast/base_terms.hpp +++ b/lib/coek/coek/ast/base_terms.hpp @@ -54,7 +54,6 @@ class ConstantTerm : public BaseExpressionTerm { double _eval() const { return value; } bool is_constant() const { return true; } - bool is_parameter() const { return false; } expr_pointer_t negate(const expr_pointer_t& repn); diff --git a/lib/coek/coek/ast/constraint_terms.hpp b/lib/coek/coek/ast/constraint_terms.hpp index c78e2ab5..5489c0e5 100644 --- a/lib/coek/coek/ast/constraint_terms.hpp +++ b/lib/coek/coek/ast/constraint_terms.hpp @@ -66,10 +66,6 @@ class ConstraintTerm : public BaseExpressionTerm { virtual bool is_inequality() const { return false; } virtual bool is_equality() const { return false; } virtual bool is_feasible() const = 0; - /* WEH - Not used (yet?) - virtual bool is_trivial() const - {return false;} - */ virtual std::string get_simple_name() { return "C[" + std::to_string(index) + "]"; } virtual std::string get_name() diff --git a/lib/coek/coek/ast/value_terms.cpp b/lib/coek/coek/ast/value_terms.cpp index febc8d7c..53034210 100644 --- a/lib/coek/coek/ast/value_terms.cpp +++ b/lib/coek/coek/ast/value_terms.cpp @@ -8,22 +8,25 @@ namespace coek { // // BaseExpressionTerm // +#if 0 double evaluate_expr(const BaseExpressionTerm* expr, std::map, double>& subexpr_value); +#endif double BaseExpressionTerm::eval() const { // -// The default behavior here is to use the new evaluate_expr() function. This should have minimal overhead -// for expressions without SubExpression terms. For expressions with SubExpression terms that are repeated, -// this has the potential to significantly minimize total cost of the evaluation. -// -// WEH - Testing indicates that this may have a small, but noticable performance degredation, even in cases -// where subexpressions aren't used. I think this relates to frequent calls to evaluate_expr(), where we -// setup temporary data structures. These aren't used in the _eval() logic. +// The default behavior here is to use the new evaluate_expr() function. This should have minimal +// overhead for expressions without SubExpression terms. For expressions with SubExpression terms +// that are repeated, this has the potential to significantly minimize total cost of the evaluation. +// +// WEH - Testing indicates that this may have a small, but noticable performance degredation, even +// in cases where subexpressions aren't used. I think this relates to frequent calls to +// evaluate_expr(), where we setup temporary data structures. These aren't used in the _eval() +// logic. // -// WEH - For now, I'm leaving the default behavior as not recognizing subexpressions. We can judiciously use the -// evaluate_expr() function in cases where we evaluate many expressions. +// WEH - For now, I'm leaving the default behavior as not recognizing subexpressions. We can +// judiciously use the evaluate_expr() function in cases where we evaluate many expressions. // #if 0 std::map, double> cache; diff --git a/lib/coek/coek/ast/visitor_eval.cpp b/lib/coek/coek/ast/visitor_eval.cpp index f96b8a3d..36022f2c 100644 --- a/lib/coek/coek/ast/visitor_eval.cpp +++ b/lib/coek/coek/ast/visitor_eval.cpp @@ -262,6 +262,17 @@ double evaluate_expr(const expr_pointer_t& expr, return visit_expression(expr, data); } +double evaluate_expr(const expr_pointer_t& expr) +{ + // GCOVR_EXCL_START + if (not expr) return 0.0; + // GCOVR_EXCL_STOP + + std::map, double> subexpr_value; + VariableData data(subexpr_value); + return visit_expression(expr, data); +} + double evaluate_expr(const BaseExpressionTerm* expr, std::map, double>& subexpr_value) { diff --git a/lib/coek/coek/ast/visitor_fns.hpp b/lib/coek/coek/ast/visitor_fns.hpp index 403a7625..d396cdf5 100644 --- a/lib/coek/coek/ast/visitor_fns.hpp +++ b/lib/coek/coek/ast/visitor_fns.hpp @@ -33,6 +33,7 @@ double evaluate_expr_debug(const expr_pointer_t& expr, #endif double evaluate_expr(const expr_pointer_t& expr, std::map, double>& subexpr_value); +double evaluate_expr(const expr_pointer_t& expr); double evaluate_expr(const BaseExpressionTerm* expr, std::map, double>& subexpr_value); @@ -62,4 +63,9 @@ void find_vars_and_params(const expr_pointer_t& expr, std::set>& visited_subexpressions); void find_variables(const expr_pointer_t& expr, std::unordered_set>& variables); + +expr_pointer_t simplify_expr( + const expr_pointer_t& expr, + std::map, expr_pointer_t>& subexpr_value); +expr_pointer_t simplify_expr(const expr_pointer_t& expr); } // namespace coek diff --git a/lib/coek/coek/ast/visitor_simplify.cpp b/lib/coek/coek/ast/visitor_simplify.cpp new file mode 100644 index 00000000..fd0b0443 --- /dev/null +++ b/lib/coek/coek/ast/visitor_simplify.cpp @@ -0,0 +1,434 @@ +#include +#include "base_terms.hpp" +#include "constraint_terms.hpp" +#include "expr_terms.hpp" +#include "value_terms.hpp" +#include "visitor.hpp" +#include "visitor_fns.hpp" +#include "../util/cast_utils.hpp" +/* +#if __cpp_lib_variant +# include "compact_terms.hpp" +#endif +*/ + +namespace coek { + +namespace { + +class VisitorData { + public: + std::map, expr_pointer_t>& subexpr_value; + + double last_value; + expr_pointer_t last_expr; + bool is_value; + + VisitorData(std::map, expr_pointer_t>& _subexpr_value) + : subexpr_value(_subexpr_value) + { + } +}; + +void visit_expression(const expr_pointer_t& expr, VisitorData& data); + +#define FROM_BODY_FN(TERM, FN) \ + void visit_##TERM(const expr_pointer_t& expr, VisitorData& data) \ + { \ + auto tmp = safe_pointer_cast(expr); \ + visit_expression(tmp->body, data); \ + if (data.is_value) \ + data.last_value = FN(data.last_value); \ + else { \ + data.last_expr = std::make_shared(data.last_expr); \ + } \ + } + +// ----------------------------------------------------------------------------------------- + +void visit_ConstantTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + data.last_value = tmp->value; + data.is_value = true; +} + +void visit_ParameterTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + data.last_value = tmp->eval(); + data.is_value = true; +} + +void visit_IndexParameterTerm(const expr_pointer_t& expr, VisitorData& data) +{ + data.last_expr = expr; + data.is_value = false; +} + +void visit_VariableTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + if (tmp->fixed) { + data.last_value = tmp->eval(); + data.is_value = true; + } + else { + data.last_expr = expr; + data.is_value = false; + } +} + +#ifdef COEK_WITH_COMPACT_MODEL +void visit_ParameterRefTerm(const expr_pointer_t& expr, VisitorData& data) +{ + data.last_expr = expr; + data.is_value = false; +} + +void visit_VariableRefTerm(const expr_pointer_t& expr, VisitorData& data) +{ + data.last_expr = expr; + data.is_value = false; +} +#endif + +void visit_MonomialTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + if (tmp->var->fixed) { + data.last_value = tmp->coef * tmp->var->eval(); + data.is_value = true; + } + else { + data.last_expr = expr; + data.is_value = false; + } +} + +void visit_ObjectiveTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + visit_expression(tmp->body, data); + if (data.is_value) + data.last_expr = std::make_shared( + std::make_shared(data.last_value), tmp->sense); + else + data.last_expr = std::make_shared(data.last_expr, tmp->sense); +} + +void visit_InequalityTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + visit_expression(tmp->body, data); + if (data.is_value) + // TODO - ignore constraints with a constant body + data.last_expr = std::make_shared( + tmp->lower, std::make_shared(data.last_value), tmp->upper); + else + data.last_expr = std::make_shared(tmp->lower, data.last_expr, tmp->upper); +} + +void visit_EqualityTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + visit_expression(tmp->body, data); + if (data.is_value) + // TODO - ignore constraints with a constant body + data.last_expr = std::make_shared( + std::make_shared(data.last_value), tmp->lower); + else + data.last_expr = std::make_shared(data.last_expr, tmp->lower); +} + +void visit_NegateTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + visit_expression(tmp->body, data); + if (data.is_value) + data.last_value *= -1; + else + data.last_expr = std::make_shared(data.last_expr); +} + +void visit_SubExpressionTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + auto it = data.subexpr_value.find(tmp); + if (it == data.subexpr_value.end()) { + visit_expression(tmp->body, data); + if (data.is_value) + data.subexpr_value[tmp] = std::make_shared(data.last_value); + else + data.subexpr_value[tmp] = data.last_expr; + } + else { + if (it->second->is_constant()) { + data.last_value = it->second->eval(); + data.is_value = true; + } + else { + data.last_expr = it->second; + data.is_value = false; + } + } +} + +void visit_PlusTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + auto& vec = *(tmp->data); + auto n = tmp->num_expressions(); + + double offset = 0.0; + bool first_term = true; + expr_pointer_t sum_of_terms; + + for (size_t i = 0; i < n; i++) { + visit_expression(vec[i], data); + if (data.is_value) + offset += data.last_value; + else if (first_term) { + first_term = false; + sum_of_terms = data.last_expr; + } + else + sum_of_terms = std::make_shared(sum_of_terms, data.last_expr); + } + + if (first_term) { + data.last_value = offset; + data.is_value = true; + } + else { + if (offset != 0.0) + data.last_expr + = std::make_shared(sum_of_terms, std::make_shared(offset)); + else + data.last_expr = sum_of_terms; + data.is_value = false; + } +} + +void visit_TimesTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + visit_expression(tmp->lhs, data); + if (data.is_value) { + if (data.last_value == 0.0) + return; + + else if (data.last_value == 1.0) { + visit_expression(tmp->rhs, data); + } + + else { + double val = data.last_value; + visit_expression(tmp->rhs, data); + if (data.is_value) + data.last_value *= val; + else + data.last_expr = std::make_shared(std::make_shared(val), + data.last_expr); + } + } + else { + expr_pointer_t val = data.last_expr; + visit_expression(tmp->rhs, data); + if (data.is_value) { + if (data.last_value == 0.0) + return; + else if (data.last_value == 1.0) { + data.last_expr = val; + data.is_value = false; + } + else { + data.last_expr = std::make_shared( + val, std::make_shared(data.last_value)); + data.is_value = false; + } + } + else + data.last_expr = std::make_shared(val, data.last_expr); + } +} + +void visit_DivideTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + visit_expression(tmp->lhs, data); + if (data.is_value) { + if (data.last_value == 0.0) + return; + + else { + double val = data.last_value; + visit_expression(tmp->rhs, data); + if (data.is_value) + data.last_value = val / data.last_value; // TODO - check for zero + else + data.last_expr = std::make_shared(std::make_shared(val), + data.last_expr); + } + } + else { + expr_pointer_t val = data.last_expr; + visit_expression(tmp->rhs, data); + if (data.is_value) { + if (data.last_value == 0.0) // TODO - check for zero + return; + else if (data.last_value == 1.0) { + data.last_expr = val; + data.is_value = false; + } + else { + data.last_expr = std::make_shared( + val, std::make_shared(data.last_value)); + data.is_value = false; + } + } + else + data.last_expr = std::make_shared(val, data.last_expr); + } +} + +// clang-format off +FROM_BODY_FN(AbsTerm, std::fabs) +FROM_BODY_FN(CeilTerm, std::ceil) +FROM_BODY_FN(FloorTerm, std::floor) +FROM_BODY_FN(ExpTerm, std::exp) +FROM_BODY_FN(LogTerm, std::log) +FROM_BODY_FN(Log10Term, std::log10) +FROM_BODY_FN(SqrtTerm, std::sqrt) +FROM_BODY_FN(SinTerm, std::sin) +FROM_BODY_FN(CosTerm, std::cos) +FROM_BODY_FN(TanTerm, std::tan) +FROM_BODY_FN(SinhTerm, std::sinh) +FROM_BODY_FN(CoshTerm, std::cosh) +FROM_BODY_FN(TanhTerm, std::tanh) +FROM_BODY_FN(ASinTerm, std::asin) +FROM_BODY_FN(ACosTerm, std::acos) +FROM_BODY_FN(ATanTerm, std::atan) +FROM_BODY_FN(ASinhTerm, std::asinh) +FROM_BODY_FN(ACoshTerm, std::acosh) +FROM_BODY_FN(ATanhTerm, std::atanh) +// clang-format on + +void visit_PowTerm(const expr_pointer_t& expr, VisitorData& data) +{ + auto tmp = safe_pointer_cast(expr); + visit_expression(tmp->lhs, data); + if (data.is_value) { + if ((data.last_value == 0.0) or (data.last_value == 1.0)) return; + + double val = data.last_value; + visit_expression(tmp->rhs, data); + if (data.is_value) + data.last_value = std::pow(val, data.last_value); + else + data.last_expr + = std::make_shared(std::make_shared(val), data.last_expr); + } + else { + expr_pointer_t val = data.last_expr; + visit_expression(tmp->rhs, data); + if (data.is_value) { + if (data.last_value == 0.0) + data.last_value = 1.0; + else if (data.last_value == 1.0) { + data.last_expr = val; + data.is_value = false; + } + else { + data.last_expr = std::make_shared( + val, std::make_shared(data.last_value)); + data.is_value = false; + } + } + else { + data.last_expr = std::make_shared(val, data.last_expr); + data.is_value = false; + } + } +} + +#define VISIT_CASE(TERM) \ + case TERM##_id: \ + return visit_##TERM(expr, data); + +void visit_expression(const expr_pointer_t& expr, VisitorData& data) +{ + switch (expr->id()) { + VISIT_CASE(ConstantTerm); + VISIT_CASE(ParameterTerm); + VISIT_CASE(IndexParameterTerm); + VISIT_CASE(VariableTerm); +#ifdef COEK_WITH_COMPACT_MODEL + VISIT_CASE(VariableRefTerm); + VISIT_CASE(ParameterRefTerm); +#endif + VISIT_CASE(MonomialTerm); + VISIT_CASE(InequalityTerm); + VISIT_CASE(EqualityTerm); + VISIT_CASE(ObjectiveTerm); + VISIT_CASE(SubExpressionTerm); + VISIT_CASE(NegateTerm); + VISIT_CASE(PlusTerm); + VISIT_CASE(TimesTerm); + VISIT_CASE(DivideTerm); + VISIT_CASE(AbsTerm); + VISIT_CASE(CeilTerm); + VISIT_CASE(FloorTerm); + VISIT_CASE(ExpTerm); + VISIT_CASE(LogTerm); + VISIT_CASE(Log10Term); + VISIT_CASE(SqrtTerm); + VISIT_CASE(SinTerm); + VISIT_CASE(CosTerm); + VISIT_CASE(TanTerm); + VISIT_CASE(SinhTerm); + VISIT_CASE(CoshTerm); + VISIT_CASE(TanhTerm); + VISIT_CASE(ASinTerm); + VISIT_CASE(ACosTerm); + VISIT_CASE(ATanTerm); + VISIT_CASE(ASinhTerm); + VISIT_CASE(ACoshTerm); + VISIT_CASE(ATanhTerm); + VISIT_CASE(PowTerm); + + // GCOVR_EXCL_START + default: + throw std::runtime_error( + "Error in simplify_expr visitor! Visiting unexpected expression term " + + std::to_string(expr->id())); + // GCOVR_EXCL_STOP + }; +} + +} // namespace + +expr_pointer_t simplify_expr( + const expr_pointer_t& expr, + std::map, expr_pointer_t>& subexpr_value) +{ + // GCOVR_EXCL_START + if (not expr) return expr; + // GCOVR_EXCL_STOP + + VisitorData data(subexpr_value); + visit_expression(expr, data); + + if (data.is_value) + return std::make_shared(data.last_value); + else + return data.last_expr; +} + +expr_pointer_t simplify_expr(const expr_pointer_t& expr) +{ + std::map, expr_pointer_t> simplified_subexpressions; + return simplify_expr(expr, simplified_subexpressions); +} + +} // namespace coek diff --git a/lib/coek/coek/ast/visitor_to_MutableNLPExpr.cpp b/lib/coek/coek/ast/visitor_to_MutableNLPExpr.cpp index 162a1891..02db3706 100644 --- a/lib/coek/coek/ast/visitor_to_MutableNLPExpr.cpp +++ b/lib/coek/coek/ast/visitor_to_MutableNLPExpr.cpp @@ -187,7 +187,11 @@ void visit(std::shared_ptr& expr, MutableNLPExpr& repn, double multip if (((lhs_mindegree + rhs_mindegree) > 2) or // Creating 3rd-degree polynomial (std::min(lhs_repn.linear_coefs.size(), rhs_repn.linear_coefs.size()) > 1)) { // Creating product of linear terms - repn.nonlinear = plus_(repn.nonlinear, expr); + if (multiplier == 1) + repn.nonlinear = plus_(repn.nonlinear, expr); + else + repn.nonlinear + = plus(repn.nonlinear, times(CREATE_POINTER(ConstantTerm, multiplier), expr)); std::unordered_set> exprvars; find_variables(expr, exprvars); repn.nonlinear_vars.insert(exprvars.begin(), exprvars.end()); @@ -196,7 +200,7 @@ void visit(std::shared_ptr& expr, MutableNLPExpr& repn, double multip // CONSTANT * CONSTANT if (not((lhs_repn.constval == ZEROCONST) or (rhs_repn.constval == ZEROCONST))) - repn.constval = times_(lhs_repn.constval, rhs_repn.constval); + repn.constval = plus_(repn.constval, times_(lhs_repn.constval, rhs_repn.constval)); if (not(lhs_repn.constval == ZEROCONST)) { // CONSTANT * LINEAR diff --git a/lib/coek/coek/autograd/asl_repn.cpp b/lib/coek/coek/autograd/asl_repn.cpp new file mode 100644 index 00000000..138c8068 --- /dev/null +++ b/lib/coek/coek/autograd/asl_repn.cpp @@ -0,0 +1,412 @@ +#include +#include +#include +#include + +#include "coek/util/sequence.hpp" +#include "coek/autograd/asl_repn.hpp" +#include "coek/ast/value_terms.hpp" + +// AMPL includes +#include "asl.h" +#include "asl_pfgh.h" +#include "getstub.h" +#ifdef range +# undef range +#endif + +namespace coek { + +// TODO - Put this declaration in a header +void write_nl_problem(Model& model, const std::string& fname, std::map& invvarmap, + std::map& invconmap); + +VariableRepn ASL_Repn::get_variable(size_t i) { return used_variables[varmap[i]]; } + +ASL_Repn::ASL_Repn(Model& model) : NLPModelRepn(model) +{ + nx = 0; + nf = 0; + nc = 0; + objval_called_with_current_x_ = false; + conval_called_with_current_x_ = false; + asl_ = 0; + nnz_jac_g = 0; + nnz_lag_h = 0; + + // First assume that we don't want to halt on error (default) + nerror_ = (void*)new fint; + *(fint*)nerror_ = 0; +} + +ASL_Repn::~ASL_Repn() +{ + free_asl(); + + delete (fint*)nerror_; + nerror_ = 0; +} + +size_t ASL_Repn::num_variables() const { return nx; } + +size_t ASL_Repn::num_objectives() const { return nf; } + +size_t ASL_Repn::num_constraints() const { return nc; } + +size_t ASL_Repn::num_nonzeros_Jacobian() const { return nnz_jac_g; } + +size_t ASL_Repn::num_nonzeros_Hessian_Lagrangian() const { return nnz_lag_h; } + +void ASL_Repn::set_variables(std::vector& x) { set_variables(&(x[0]), x.size()); } + +void ASL_Repn::set_variables(const double* x, size_t n) +{ + assert(n == currx.size()); + for (size_t i = 0; i < n; i++) currx[i] = x[i]; + + objval_called_with_current_x_ = false; + conval_called_with_current_x_ = false; +} + +void ASL_Repn::get_J_nonzeros(std::vector& jrow, std::vector& jcol) +{ + ASL_pfgh* asl = asl_; + + jrow.resize(nnz_jac_g); + jcol.resize(nnz_jac_g); + size_t curr_nz = 0; + for (size_t i : coek::range(nc)) { + for (cgrad* cg = Cgrad[i]; cg; cg = cg->next) { + jrow[cg->goff] = i; + jcol[cg->goff] = cg->varno; + curr_nz++; + } + } + + // The number of nonzeros should match what the value in the ASL data + assert(curr_nz == nnz_jac_g); +} + +void ASL_Repn::get_H_nonzeros(std::vector& hrow, std::vector& hcol) +{ + ASL_pfgh* asl = asl_; + + hrow.resize(nnz_lag_h); + hcol.resize(nnz_lag_h); + size_t curr_nz = 0; + for (size_t i : coek::range(nx)) { + for (size_t j = sputinfo->hcolstarts[i]; j < sputinfo->hcolstarts[i + 1]; j++) { + hrow[curr_nz] = i; + hcol[curr_nz] = sputinfo->hrownos[j]; + curr_nz++; + } + } + + // The number of nonzeros should match what the value in the ASL data + assert(curr_nz == nnz_lag_h); +} + +bool ASL_Repn::column_major_hessian() { return false; } + +void ASL_Repn::print_equations(std::ostream& ostr) const { NLPModelRepn::print_equations(ostr); } + +void ASL_Repn::print_values(std::ostream& ostr) const { NLPModelRepn::print_values(ostr); } + +// TODO - Should COEK capture errors and return an error flag? +// TODO - Generalize caching for multiple objectives +double ASL_Repn::compute_f(size_t i) +{ + assert(i == 0); + + if (nf == 0) { + objval_called_with_current_x_ = true; + return 0; + } + else if (not objval_called_with_current_x_) { + ASL_pfgh* asl = asl_; + f_cache = objval(static_cast(i), &(currx[0]), (fint*)nerror_); + nerror_ok = check_asl_status(nerror_); + if (nerror_ok) { + objval_called_with_current_x_ = true; + } + else { + objval_called_with_current_x_ = false; + f_cache = nan(""); + } + } + + return f_cache; +} + +void ASL_Repn::compute_df(double& f, std::vector& df, size_t i) +{ + assert(df.size() == nx); + + if (nf == 0) { + f = 0.0; + for (double& df_x : df) df_x = 0.0; + } + else { + f = compute_f(i); + + ASL_pfgh* asl = asl_; + objgrd(static_cast(i), &(currx[0]), &(df[0]), (fint*)nerror_); + nerror_ok = check_asl_status(nerror_); + } +} + +void ASL_Repn::compute_c(std::vector& c) +{ + assert(c.size() == nc); + + if (not conval_called_with_current_x_) { + ASL_pfgh* asl = asl_; + conval(&(currx[0]), &(c_cache[0]), (fint*)nerror_); + nerror_ok = check_asl_status(nerror_); + if (nerror_ok) + conval_called_with_current_x_ = true; + else { + conval_called_with_current_x_ = false; + for (size_t i : coek::range(nc)) c[i] = nan(""); + return; + } + } + for (size_t i : coek::range(nc)) c[i] = c_cache[i]; +} + +void ASL_Repn::compute_dc(std::vector& dc, size_t i) +{ + assert(dc.size() == nx); + + ASL_pfgh* asl = asl_; + congrd(i, &(currx[0]), &(dc[0]), (fint*)nerror_); + nerror_ok = check_asl_status(nerror_); + if (not nerror_ok) { + for (size_t j : coek::indices(dc)) dc[j] = nan(""); + } +} + +void ASL_Repn::compute_H(std::vector& w, std::vector& H) +{ + ASL_pfgh* asl = asl_; + + if (!objval_called_with_current_x_) { + f_cache = compute_f(0); // TODO - Extend API for multiple objectives + } + if (!conval_called_with_current_x_) { + compute_c(c_cache); + } + sphes(&(H[0]), -1, &(w[0]), &(w[nf])); +} + +void ASL_Repn::compute_J(std::vector& J) +{ + ASL_pfgh* asl = asl_; + + jacval(&(currx[0]), &(J[0]), (fint*)nerror_); + + nerror_ok = check_asl_status(nerror_); +} + +void ASL_Repn::initialize(bool /*_sparse_JH*/) +{ + // + // Initialize the NLPModelRepn data + // + find_used_variables(); + + // + // Create the ASL object + // + alloc_asl(); + ASL_pfgh* asl = asl_; + + // Must have at least one continuous variable. + assert(n_var > 0); + // API does not support discrete variables + assert((nbv == 0 && niv == 0 && nlvbi == 0 && nlvci == 0 && nlvoi == 0)); + // API does not support complementary constraints + assert(n_cc == 0); + // API does not support linear arc variables + assert(nwv == 0); + // API does not support nonlinear network constraints + assert(nlnc == 0); + // API does not support linear network constraints + assert(lnc == 0); + + // + // Set options in the asl structure + // + // allocate initial values for primal and dual if available + want_xpi0 = 1 | 2; + assert((want_xpi0 & 1) == 1 && (want_xpi0 & 2) == 2); + + call_hesset(); + + // + // Resize and initialize the ASL_Repn data + // + nx = static_cast(n_var); + nf = static_cast(n_obj); + nc = static_cast(n_con); + nnz_jac_g = static_cast(nzc); + + currx.resize(nx); + xlb.resize(nx); + xub.resize(nx); + c_cache.resize(nc); + + // + // Setup initial values + // + for (size_t i : coek::range(nx)) { + currx[i] = havex0[i] != 0 ? X0[i] : std::max(LUv[2 * i], std::min(LUv[2 * i + 1], 0.0)); + xlb[i] = LUv[2 * i]; + xub[i] = LUv[2 * i + 1]; + } + set_variables(currx); +} + +void ASL_Repn::reset(void) +{ + // + // We re-generate the NL file and parse it, using new parameter values and new + // fixed variables. + // + initialize(); +} + +bool ASL_Repn::check_asl_status(void* nerror) +{ + if (nerror == NULL || *((fint*)nerror) == 0) return true; + + std::cerr << "Error in an ASL evaluation." << std::endl; + std::cerr << "nerror = " << *((fint*)nerror) << std::endl; + return false; +} + +void ASL_Repn::alloc_asl() +{ + free_asl(); + // + // Create the ASL structure + // + ASL_pfgh* asl = reinterpret_cast(ASL_alloc(ASL_read_pfgh)); + asl_ = asl; + // + // Create a temporary filename + // + std::string fname = "/tmp/coek_XXXXXX"; + std::string tmp = mktemp(&fname[0]); + if (tmp.size() == 0) + throw std::runtime_error("Failure to create temporary file for ASL interface"); + fname += ".nl"; + // + // Write the NL file + // + { + std::map invvarmap; // ASL index -> Var ID + std::map invconmap; // Ignore + write_nl_problem(model, fname, invvarmap, invconmap); + std::map tmpvarmap; // Var ID -> Coek index + for (auto& it : used_variables) tmpvarmap[it.second->index] = it.first; + for (auto& it : invvarmap) + varmap[it.first] = tmpvarmap[it.second]; // ASL index -> Coek -> index + } + // + // Read the NL file with the ASL library + // + return_nofile = 1; // A hack to prevent the ASL from calling exit() + FILE* nlfile = jac0dim(&(fname[0]), 16); + if (!nlfile) { + throw std::runtime_error( + "ASL_Repn::alloc_asl - Cannot create ASL interface for model with no variables."); + } + // + // allocate space for initial values + // + X0 = new real[n_var]; + havex0 = new char[n_var]; + // + // Load model expressions + // + int retcode = pfgh_read(nlfile, ASL_return_read_err | ASL_findgroups); + // + // Close and remove the file + // + remove(fname.c_str()); + + // + // No errors, so return + // + if ((retcode == ASL_readerr_none) or (retcode == ASL_readerr_nonlin)) return; + + free_asl(); + if (retcode == ASL_readerr_nofile) + throw std::runtime_error("ASL_Repn::alloc_asl - Error opening file " + fname); + + else if (retcode == ASL_readerr_argerr) + throw std::runtime_error("ASL_Repn::alloc_asl - User-defined function with bad arguments"); + + else if (retcode == ASL_readerr_unavail) + throw std::runtime_error("ASL_Repn::alloc_asl - User-defined function not available"); + + else if (retcode == ASL_readerr_corrupt) + throw std::runtime_error("ASL_Repn::alloc_asl - Corrupt NL file"); + + else if (retcode == ASL_readerr_bug) + throw std::runtime_error("ASL_Repn::alloc_asl - Bug in ASL NL reader"); + + else if (retcode == ASL_readerr_CLP) + throw std::runtime_error( + "ASL_Repn::alloc_asl - NL file contains a constraint without \"=\", \">=\", or " + "\"<=\""); + + else + throw std::runtime_error("ASL_Repn::alloc_asl - Unknown error in ASL file reader"); +} + +void ASL_Repn::free_asl() +{ + ASL_pfgh* asl = asl_; + if (asl) { + if (X0) { + delete[] X0; + X0 = 0; + } + + if (havex0) { + delete[] havex0; + havex0 = 0; + } + + ASL* asl_to_free = (ASL*)asl_; + ASL_free(&asl_to_free); + asl_ = 0; + } +} + +void ASL_Repn::call_hesset() +{ + ASL_pfgh* asl = asl_; + + if (n_obj == 0) { + hesset(1, 0, 0, 0, nlc); + } + else { + // TODO - rethink how this is setup, since the ASL data structures + // are being optimized for a specific objective here. + obj_no = 0; + // see "changes" in solvers directory of ampl code... + hesset(1, obj_no, 1, 0, nlc); + } + + // find the nonzero structure for the hessian parameters to + // sphsetup: + int coeff_obj = 1; + int mult_supplied = 1; // multipliers will be supplied + int uptri = 1; // only need the upper triangular part + nnz_lag_h = static_cast(sphsetup(-1, coeff_obj, mult_supplied, uptri)); +} + +} // namespace coek diff --git a/lib/coek/coek/autograd/asl_repn.hpp b/lib/coek/coek/autograd/asl_repn.hpp new file mode 100644 index 00000000..28a5249d --- /dev/null +++ b/lib/coek/coek/autograd/asl_repn.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include +#include + +#include "autograd.hpp" + +extern "C" { +struct ASL_pfgh; +} + +namespace coek { + +class VariableTerm; + +// +// An extension model that uses the ASL library for autograd. +// +class ASL_Repn : public NLPModelRepn { + public: + // The main ASL structure + ASL_pfgh* asl_; + + // ---------------------------------------------------------------------- + // Problem information + // ---------------------------------------------------------------------- + /// Dimension of the range space for f(x). + size_t nf; + /// Dimension of the domain space + size_t nx; + /// Dimension of the range space for c(x) + size_t nc; + /// Number of nonzeros in the Jacobian + size_t nnz_jac_g; + /// Number of nonzeros in Hessian of the Lagrangian + size_t nnz_lag_h; + + /// initial value for x + std::vector xi; + /// lower limit for x + std::vector xlb; + /// upper limit for x + std::vector xub; + + bool objval_called_with_current_x_; + bool conval_called_with_current_x_; + double f_cache; + std::vector c_cache; + std::vector currx; + + public: + std::map varmap; // ASL index -> NLPModelRepn index + VariableRepn get_variable(size_t i); + + public: + ASL_Repn(Model& model); + ~ASL_Repn(); + + void initialize(bool sparse_JH = true); + + void reset(void); + + size_t num_variables() const; + + size_t num_objectives() const; + + size_t num_constraints() const; + + size_t num_nonzeros_Jacobian() const; + + size_t num_nonzeros_Hessian_Lagrangian() const; + + void set_variables(std::vector& x); + void set_variables(const double* x, size_t n); + + void get_J_nonzeros(std::vector& jrow, std::vector& jcol); + void get_H_nonzeros(std::vector& hrow, std::vector& hcol); + bool column_major_hessian(); + + void print_equations(std::ostream& ostr) const; + void print_values(std::ostream& ostr) const; + + public: + double compute_f(size_t i); + + void compute_df(double& f, std::vector& df, size_t i); + + void compute_c(std::vector& c); + + void compute_dc(std::vector& dc, size_t i); + + void compute_H(std::vector& w, std::vector& H); + + void compute_J(std::vector& J); + + protected: + void* nerror_; + + void free_asl(); + void alloc_asl(); + bool nerror_ok; + bool check_asl_status(void* nerror); + void call_hesset(); +}; + +} // namespace coek diff --git a/lib/coek/coek/autograd/autograd.cpp b/lib/coek/coek/autograd/autograd.cpp index fb796eae..e67a0f21 100644 --- a/lib/coek/coek/autograd/autograd.cpp +++ b/lib/coek/coek/autograd/autograd.cpp @@ -14,6 +14,9 @@ #ifdef WITH_CPPAD # include "cppad_repn.hpp" #endif +#ifdef WITH_ASL +# include "asl_repn.hpp" +#endif #include "unknownad_repn.hpp" namespace coek { @@ -26,6 +29,9 @@ NLPModelRepn* create_NLPModelRepn(Model& model, const std::string& name) #ifdef WITH_CPPAD if (name == "cppad") return new CppAD_Repn(model); #endif +#ifdef WITH_ASL + if (name == "asl") return new ASL_Repn(model); +#endif throw std::runtime_error("Unexpected NLP model type: " + name); } @@ -51,7 +57,11 @@ void NLPModelRepn::find_used_variables() used_variables.clear(); size_t i = 0; - for (auto& it : tmp) used_variables[i++] = it.second; + for (auto& it : tmp) { + // std::cout << "DEBUG USED VARS " << i << " " << it.first << " " << it.second->get_name() + // << std::endl; + used_variables[i++] = it.second; + } fixed_variables.clear(); parameters.clear(); diff --git a/lib/coek/coek/autograd/autograd.hpp b/lib/coek/coek/autograd/autograd.hpp index c52763a5..e2436d79 100644 --- a/lib/coek/coek/autograd/autograd.hpp +++ b/lib/coek/coek/autograd/autograd.hpp @@ -51,8 +51,9 @@ class NLPModelRepn { virtual void compute_J(std::vector& J) = 0; virtual void get_J_nonzeros(std::vector& jrow, std::vector& jcol) = 0; - // NOTE: Should we always assume column- or row-major order? virtual void get_H_nonzeros(std::vector& hrow, std::vector& hcol) = 0; + // Returns true if the Hessian representation is column-major order, and false otherwise + virtual bool column_major_hessian() = 0; public: void find_used_variables(); diff --git a/lib/coek/coek/autograd/cppad_repn.cpp b/lib/coek/coek/autograd/cppad_repn.cpp index 60eb7838..1be8af45 100644 --- a/lib/coek/coek/autograd/cppad_repn.cpp +++ b/lib/coek/coek/autograd/cppad_repn.cpp @@ -4,6 +4,7 @@ #include "../ast/constraint_terms.hpp" #include "../ast/expr_terms.hpp" #include "../ast/value_terms.hpp" +#include "../ast/visitor_fns.hpp" #include "coek/api/constraint.hpp" #include "coek/api/objective.hpp" #include "coek/model/model.hpp" @@ -71,6 +72,8 @@ void CppAD_Repn::get_H_nonzeros(std::vector& hrow, std::vector& } } +bool CppAD_Repn::column_major_hessian() { return false; } + void CppAD_Repn::print_equations(std::ostream& ostr) const { NLPModelRepn::print_equations(ostr); } void CppAD_Repn::print_values(std::ostream& ostr) const { NLPModelRepn::print_values(ostr); } @@ -224,19 +227,16 @@ void CppAD_Repn::compute_J(std::vector& J) } } -void CppAD_Repn::initialize(bool _sparse_JH) +void CppAD_Repn::create_CppAD_function() { - sparse_JH = _sparse_JH; - // - // Find all variables used in the NLP model - // - find_used_variables(); - nx = used_variables.size(); - nf = model.repn->objectives.size(); - nc = model.repn->constraints.size(); - - dynamic_params.resize(fixed_variables.size() + parameters.size()); - dynamic_param_vals.resize(fixed_variables.size() + parameters.size()); + if (simplify_expressions) { + dynamic_params.resize(0); + dynamic_param_vals.resize(0); + } + else { + dynamic_params.resize(fixed_variables.size() + parameters.size()); + dynamic_param_vals.resize(fixed_variables.size() + parameters.size()); + } // // Create the CppAD function @@ -252,16 +252,35 @@ void CppAD_Repn::initialize(bool _sparse_JH) CppAD::Independent(ADvars); try { - size_t nb = 0; - for (auto& it : model.repn->objectives) { - build_expression(it.repn, ADvars, ADrange[nb], _used_variables); - nb++; + if (simplify_expressions) { + std::map, expr_pointer_t> cache; + + size_t nb = 0; + for (auto& it : model.repn->objectives) { + build_expression(simplify_expr(it.repn, cache), ADvars, ADrange[nb], + _used_variables); + nb++; + } + + nb = 0; + for (auto& it : model.repn->constraints) { + build_expression(simplify_expr(it.repn, cache), ADvars, ADrange[nf + nb], + _used_variables); + nb++; + } } + else { + size_t nb = 0; + for (auto& it : model.repn->objectives) { + build_expression(it.repn, ADvars, ADrange[nb], _used_variables); + nb++; + } - nb = 0; - for (auto& it : model.repn->constraints) { - build_expression(it.repn, ADvars, ADrange[nf + nb], _used_variables); - nb++; + nb = 0; + for (auto& it : model.repn->constraints) { + build_expression(it.repn, ADvars, ADrange[nf + nb], _used_variables); + nb++; + } } } catch (std::runtime_error& err) { @@ -270,6 +289,20 @@ void CppAD_Repn::initialize(bool _sparse_JH) } ADfc.Dependent(ADvars, ADrange); ADfc.optimize(); +} + +void CppAD_Repn::initialize(bool _sparse_JH) +{ + sparse_JH = _sparse_JH; + // + // Find all variables used in the NLP model + // + find_used_variables(); + nx = used_variables.size(); + nf = model.repn->objectives.size(); + nc = model.repn->constraints.size(); + + create_CppAD_function(); // // Setup temporary arrays used during computations @@ -534,9 +567,15 @@ void CppAD_Repn::reset(void) // // Initialize the CppAD dynamic parameters // - for (auto& it : fixed_variables) dynamic_param_vals[it.second] = it.first->value->eval(); - for (auto& it : parameters) dynamic_param_vals[it.second] = it.first->value->eval(); - ADfc.new_dynamic(dynamic_param_vals); + if (not simplify_expressions) { + for (auto& it : fixed_variables) dynamic_param_vals[it.second] = it.first->value->eval(); + for (auto& it : parameters) dynamic_param_vals[it.second] = it.first->value->eval(); + ADfc.new_dynamic(dynamic_param_vals); + } + else { + // Regenerate CppAD data structures when simplifying the expressions + create_CppAD_function(); + } // // Setup initial value diff --git a/lib/coek/coek/autograd/cppad_repn.hpp b/lib/coek/coek/autograd/cppad_repn.hpp index ae7b970b..fe082c12 100644 --- a/lib/coek/coek/autograd/cppad_repn.hpp +++ b/lib/coek/coek/autograd/cppad_repn.hpp @@ -84,6 +84,8 @@ class CppAD_Repn : public NLPModelRepn { std::vector > dynamic_params; std::vector dynamic_param_vals; + bool simplify_expressions = true; + public: CppAD_Repn(Model& model); @@ -106,6 +108,7 @@ class CppAD_Repn : public NLPModelRepn { void get_J_nonzeros(std::vector& jrow, std::vector& jcol); void get_H_nonzeros(std::vector& hrow, std::vector& hcol); + bool column_major_hessian(); void print_equations(std::ostream& ostr) const; void print_values(std::ostream& ostr) const; @@ -124,6 +127,7 @@ class CppAD_Repn : public NLPModelRepn { void compute_J(std::vector& J); public: + void create_CppAD_function(); void build_expression(expr_pointer_t root, std::vector >& ADvars, CppAD::AD& range, std::unordered_map& _used_variables); diff --git a/lib/coek/coek/autograd/unknownad_repn.hpp b/lib/coek/coek/autograd/unknownad_repn.hpp index 9c3a965e..e209e8f5 100644 --- a/lib/coek/coek/autograd/unknownad_repn.hpp +++ b/lib/coek/coek/autograd/unknownad_repn.hpp @@ -63,6 +63,8 @@ class UnknownAD_Repn : public NLPModelRepn { throw std::runtime_error("Error accessing uninitialized NLPModel"); } + bool column_major_hessian() { return false; } + void print_equations(std::ostream&) const { throw std::runtime_error("Error accessing uninitialized NLPModel"); diff --git a/lib/coek/coek/compact/coek_exprterm.hpp b/lib/coek/coek/compact/coek_exprterm.hpp index 9a201790..616b67d2 100644 --- a/lib/coek/coek/compact/coek_exprterm.hpp +++ b/lib/coek/coek/compact/coek_exprterm.hpp @@ -12,7 +12,7 @@ class SumExpressionTerm : public BaseExpressionTerm { public: SumExpressionTerm(const ExpressionSequence& _seq) : seq(_seq) {} - double eval() const { throw std::runtime_error("Cannot evaluate a Sum() expression."); } + double _eval() const { throw std::runtime_error("Cannot evaluate a Sum() expression."); } bool is_expression() const { return false; } diff --git a/lib/coek/coek/compact/visitor_exprtemplate.cpp b/lib/coek/coek/compact/visitor_exprtemplate.cpp index 79b67b0e..bc995b87 100644 --- a/lib/coek/coek/compact/visitor_exprtemplate.cpp +++ b/lib/coek/coek/compact/visitor_exprtemplate.cpp @@ -6,8 +6,6 @@ #include "coek/api/expression.hpp" #include "coek_exprterm.hpp" -// void xxx() {} - namespace coek { expr_pointer_t get_concrete_var(VariableRefTerm& varref); @@ -20,7 +18,6 @@ expr_pointer_t visit(SumExpressionTerm&); expr_pointer_t visit(IndexParameterTerm& arg) { - // xxx(); // TODO - embed this logic in the IndexParameterTerm class if (arg.type == 1) return CREATE_POINTER(ConstantTerm, arg.double_value); diff --git a/lib/coek/coek/model/compact_model.cpp b/lib/coek/coek/model/compact_model.cpp index 9d38c2e7..86018934 100644 --- a/lib/coek/coek/model/compact_model.cpp +++ b/lib/coek/coek/model/compact_model.cpp @@ -12,12 +12,12 @@ namespace coek { #ifdef COEK_WITH_COMPACT_MODEL -void write_lp_problem(CompactModel& model, std::string& fname, std::map& varmap, - std::map& conmap); -void write_lp_problem_ostream(CompactModel& model, std::string& fname, +void write_lp_problem(CompactModel& model, const std::string& fname, + std::map& varmap, std::map& conmap); +void write_lp_problem_ostream(CompactModel& model, const std::string& fname, std::map& varmap, std::map& conmap); # ifdef WITH_FMTLIB -void write_lp_problem_fmtlib(CompactModel& model, std::string& fname, +void write_lp_problem_fmtlib(CompactModel& model, const std::string& fname, std::map& varmap, std::map& conmap); # endif @@ -248,14 +248,14 @@ Model CompactModel::expand() return model; } -void CompactModel::write(std::string fname) +void CompactModel::write(const std::string& fname) { std::map varmap; std::map conmap; write(fname, varmap, conmap); } -void CompactModel::write(std::string fname, std::map& varmap, +void CompactModel::write(const std::string& fname, std::map& varmap, std::map& conmap) { if (ends_with(fname, ".lp")) { diff --git a/lib/coek/coek/model/compact_model.hpp b/lib/coek/coek/model/compact_model.hpp index 01428ec0..48d00fca 100644 --- a/lib/coek/coek/model/compact_model.hpp +++ b/lib/coek/coek/model/compact_model.hpp @@ -135,8 +135,8 @@ class CompactModel { Model expand(); - void write(std::string filename); - void write(std::string filename, std::map& varmap, + void write(const std::string& filename); + void write(const std::string& filename, std::map& varmap, std::map& conmap); }; diff --git a/lib/coek/coek/model/model.cpp b/lib/coek/coek/model/model.cpp index 0d29d61c..11f6e0c6 100644 --- a/lib/coek/coek/model/model.cpp +++ b/lib/coek/coek/model/model.cpp @@ -285,13 +285,15 @@ std::map& Model::get_constraints_by_name() void Model::generate_names() { if (repn->name_generation_policy == Model::NameGeneration::lazy) { +#if __cpp_lib_variant for (auto& parray : repn->parameter_arrays) parray.generate_names(); for (auto& varray : repn->variable_arrays) varray.generate_names(); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL for (auto& pmap : repn->parameter_maps) pmap.generate_names(); for (auto& vmap : repn->variable_maps) vmap.generate_names(); -#endif +# endif for (auto& cmap : repn->constraint_maps) cmap.generate_names(); +#endif } repn->variables_by_name.clear(); @@ -351,38 +353,40 @@ std::set Model::constraint_suffix_names() const { return map_keys(r std::set Model::model_suffix_names() const { return map_keys(repn->msuffix); } -void write_lp_problem(Model& model, std::string& fname, std::map& varmap, +void write_lp_problem(Model& model, const std::string& fname, std::map& varmap, std::map& conmap); -void write_nl_problem(Model& model, std::string& fname, std::map& varmap, +void write_nl_problem(Model& model, const std::string& fname, std::map& varmap, std::map& conmap); -void write_lp_problem_ostream(Model& model, std::string& fname, std::map& varmap, - std::map& conmap); -void write_nl_problem_ostream(Model& model, std::string& fname, std::map& varmap, - std::map& conmap); +void write_lp_problem_ostream(Model& model, const std::string& fname, + std::map& varmap, std::map& conmap); +void write_nl_problem_ostream(Model& model, const std::string& fname, + std::map& varmap, std::map& conmap); #ifdef WITH_FMTLIB -void write_lp_problem_fmtlib(Model& model, std::string& fname, std::map& varmap, - std::map& conmap); -void write_nl_problem_fmtlib(Model& model, std::string& fname, std::map& varmap, - std::map& conmap); +void write_lp_problem_fmtlib(Model& model, const std::string& fname, + std::map& varmap, std::map& conmap); +void write_nl_problem_fmtlib(Model& model, const std::string& fname, + std::map& varmap, std::map& conmap); #endif -void Model::write(std::string fname) +void Model::write(const std::string& fname) { std::map varmap; std::map conmap; write(fname, varmap, conmap); } -void Model::write(std::string fname, std::map& varmap, +void Model::write(const std::string& fname, std::map& varmap, std::map& conmap) { if (repn->name_generation_policy == Model::NameGeneration::lazy) { +#if __cpp_lib_variant for (auto& varray : repn->variable_arrays) varray.generate_names(); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL for (auto& vmap : repn->variable_maps) vmap.generate_names(); -#endif +# endif for (auto& cmap : repn->constraint_maps) cmap.generate_names(); +#endif } if (ends_with(fname, ".lp")) { diff --git a/lib/coek/coek/model/model.hpp b/lib/coek/coek/model/model.hpp index 34c80607..865abe92 100644 --- a/lib/coek/coek/model/model.hpp +++ b/lib/coek/coek/model/model.hpp @@ -117,12 +117,12 @@ class Model { // VariableArray& add_variable(VariableArray&& var); VariableArray& add(VariableArray& var); VariableArray& add(VariableArray&& var); -#endif -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL VariableMap& add_variable(VariableMap& var); // VariableMap& add_variable(VariableMap&& var); VariableMap& add(VariableMap& var); VariableMap& add(VariableMap&& var); +# endif #endif /** \returns the variable with the specified id */ @@ -162,7 +162,7 @@ class Model { Objective& add(Objective& var); Objective& add(Objective&& var); - /** \returns the objective with the specified id */ + /** \returns the i-th objective that was added */ Objective get_objective(size_t i = 0); /** \returns the objective with the specified name */ Objective get_objective(const std::string& name); @@ -199,7 +199,7 @@ class Model { void add(ConstraintMap& expr); #endif - /** \returns the constraint with the specified id */ + /** \returns the i-th constraint that was added */ Constraint get_constraint(size_t i); /** \returns the constraint with the specified name */ Constraint get_constraint(const std::string& name); @@ -260,14 +260,14 @@ class Model { // /** Write the model to the specified file */ - void write(std::string filename); + void write(const std::string& filename); /** Write the model to the specified file * * \param filename the output file * \param varmap varmap[i] contains the id of the i-th variable in this model * \param conmap conmap[i] contains the id of the i-th constraint in this model */ - void write(std::string filename, std::map& varmap, + void write(const std::string& filename, std::map& varmap, std::map& conmap); /** Print the equations in the model to \c std::cout */ void print_equations() const; diff --git a/lib/coek/coek/model/model_repn.hpp b/lib/coek/coek/model/model_repn.hpp index ee943014..87e59670 100644 --- a/lib/coek/coek/model/model_repn.hpp +++ b/lib/coek/coek/model/model_repn.hpp @@ -7,9 +7,11 @@ #include "coek/api/expression.hpp" #include "coek/api/objective.hpp" #include "coek/api/constraint.hpp" -#include "coek/api/parameter_array.hpp" -#include "coek/api/variable_array.hpp" -#include "coek/api/constraint_map.hpp" +#if __cpp_lib_variant +# include "coek/api/parameter_array.hpp" +# include "coek/api/variable_array.hpp" +# include "coek/api/constraint_map.hpp" +#endif #include "coek/model/model.hpp" #ifdef COEK_WITH_COMPACT_MODEL # include "coek/compact/variable_sequence.hpp" @@ -31,9 +33,9 @@ class ModelRepn { std::vector constraints; std::vector variables; +#if __cpp_lib_variant std::vector parameter_arrays; std::vector variable_arrays; -#if __cpp_lib_variant # ifdef COEK_WITH_COMPACT_MODEL std::vector parameter_maps; std::vector variable_maps; diff --git a/lib/coek/coek/model/nlp_model.cpp b/lib/coek/coek/model/nlp_model.cpp index 6adc87d7..49d0f121 100644 --- a/lib/coek/coek/model/nlp_model.cpp +++ b/lib/coek/coek/model/nlp_model.cpp @@ -64,6 +64,8 @@ void NLPModel::get_H_nonzeros(std::vector& hrow, std::vector& hc repn->get_H_nonzeros(hrow, hcol); } +bool NLPModel::column_major_hessian() { return repn->column_major_hessian(); } + double NLPModel::compute_f(size_t i) { return repn->compute_f(i); } void NLPModel::compute_df(double& f, std::vector& df, size_t i) diff --git a/lib/coek/coek/model/nlp_model.hpp b/lib/coek/coek/model/nlp_model.hpp index 4b197eee..d65b420a 100644 --- a/lib/coek/coek/model/nlp_model.hpp +++ b/lib/coek/coek/model/nlp_model.hpp @@ -74,9 +74,13 @@ class NLPModel { * * \param hrow vector that is filled with row indices * \param hcol vector that is filled with column indices + * + * \returns a boolean that is true if the data is column-major ordered. */ void get_H_nonzeros(std::vector& hrow, std::vector& hcol); + bool column_major_hessian(); + /** Write the model to the specified file */ void write(std::string filename); /** Write the model to the specified file diff --git a/lib/coek/coek/model/writer_lp.cpp b/lib/coek/coek/model/writer_lp.cpp index a3eedcf7..76e9b974 100644 --- a/lib/coek/coek/model/writer_lp.cpp +++ b/lib/coek/coek/model/writer_lp.cpp @@ -651,7 +651,8 @@ void LPWriter::print_bounds(fmt::ostream& ostr) } // namespace -void write_lp_problem_ostream(Model& model, std::string& fname, std::map& invvarmap, +void write_lp_problem_ostream(Model& model, const std::string& fname, + std::map& invvarmap, std::map& invconmap) { std::ofstream ostr(fname); @@ -667,7 +668,7 @@ void write_lp_problem_ostream(Model& model, std::string& fname, std::map& invvarmap, std::map& invconmap) { @@ -685,7 +686,8 @@ void write_lp_problem_ostream(CompactModel& model, std::string& fname, #endif #ifdef WITH_FMTLIB -void write_lp_problem_fmtlib(Model& model, std::string& fname, std::map& invvarmap, +void write_lp_problem_fmtlib(Model& model, const std::string& fname, + std::map& invvarmap, std::map& invconmap) { auto ostr = fmt::output_file(fname, fmt::file::WRONLY | fmt::file::CREATE | FMT_POSIX(O_TRUNC)); @@ -700,14 +702,14 @@ void write_lp_problem_fmtlib(Model& model, std::string& fname, std::map& invvarmap, +void write_lp_problem(Model& model, const std::string& fname, std::map& invvarmap, std::map& invconmap) { write_lp_problem_fmtlib(model, fname, invvarmap, invconmap); } # ifdef COEK_WITH_COMPACT_MODEL -void write_lp_problem_fmtlib(CompactModel& model, std::string& fname, +void write_lp_problem_fmtlib(CompactModel& model, const std::string& fname, std::map& invvarmap, std::map& invconmap) { @@ -723,23 +725,23 @@ void write_lp_problem_fmtlib(CompactModel& model, std::string& fname, } } -void write_lp_problem(CompactModel& model, std::string& fname, std::map& varmap, - std::map& conmap) +void write_lp_problem(CompactModel& model, const std::string& fname, + std::map& varmap, std::map& conmap) { write_lp_problem_fmtlib(model, fname, varmap, conmap); } # endif #else -void write_lp_problem(Model& model, std::string& fname, std::map& invvarmap, +void write_lp_problem(Model& model, const std::string& fname, std::map& invvarmap, std::map& invconmap) { write_lp_problem_ostream(model, fname, invvarmap, invconmap); } # ifdef COEK_WITH_COMPACT_MODEL -void write_lp_problem(CompactModel& model, std::string& fname, std::map& varmap, - std::map& conmap) +void write_lp_problem(CompactModel& model, const std::string& fname, + std::map& varmap, std::map& conmap) { write_lp_problem_ostream(model, fname, varmap, conmap); } diff --git a/lib/coek/coek/model/writer_nl.cpp b/lib/coek/coek/model/writer_nl.cpp index 3fe6f0d8..d38abf30 100644 --- a/lib/coek/coek/model/writer_nl.cpp +++ b/lib/coek/coek/model/writer_nl.cpp @@ -25,6 +25,7 @@ #include "coek/api/expression_visitor.hpp" #include "coek/api/objective.hpp" #include "coek/model/model.hpp" +#include "coek/util/sequence.hpp" #include "model_repn.hpp" #define EPSILON 1e-12 @@ -32,6 +33,8 @@ namespace coek { +void to_MutableNLPExpr(const expr_pointer_t& expr, MutableNLPExpr& repn); + void check_that_expression_variables_are_declared(Model& model, const std::map& varobj); @@ -185,8 +188,15 @@ void PrintExpr::visit(SubExpressionTerm& arg) { arg.body->accept(*this); } void PrintExpr::visit(NegateTerm& arg) { - ostr << "o16\n"; - arg.body->accept(*this); + if (arg.body->is_constant()) { + ostr << "n"; + format(ostr, -arg.body->eval()); + ostr << "\n"; + } + else { + ostr << "o16\n"; + arg.body->accept(*this); + } } void PrintExpr::visit(PlusTerm& arg) @@ -384,8 +394,13 @@ void PrintExprFmtlib::visit(SubExpressionTerm& arg) { arg.body->accept(*this); } void PrintExprFmtlib::visit(NegateTerm& arg) { - ostr.print("o16\n"); - arg.body->accept(*this); + if (arg.body->is_constant()) { + ostr.print(fmt::format(_fmtstr_n, -arg.body->eval())); + } + else { + ostr.print("o16\n"); + arg.body->accept(*this); + } } void PrintExprFmtlib::visit(PlusTerm& arg) @@ -538,12 +553,6 @@ void print_expr(fmt::ostream& ostr, const MutableNLPExpr& repn, it->second += repn.quadratic_coefs[i]->eval(); else term[key] = repn.quadratic_coefs[i]->eval(); - /* - if (auto it{ term.find(key) }; it != term.end() ) - it->second += repn.quadratic_coefs[i].value(); - else - term[key] = repn.quadratic_coefs[i].value(); - */ } } @@ -592,22 +601,26 @@ class NLWriter { std::set vars; std::set nonlinear_vars_obj; std::set nonlinear_vars_con; - size_t num_inequalities; - size_t num_ranges; - size_t num_equalities; - size_t nonl_objectives; - size_t nonl_constraints; - size_t nonlinear_vars_both; - std::set linear_vars; - size_t num_linear_binary_vars; - size_t num_linear_integer_vars; - size_t num_nonlinear_obj_int_vars; - size_t num_nonlinear_con_int_vars; - size_t num_nonlinear_both_int_vars; - size_t nnz_Jacobian; - size_t nnz_gradient; + size_t num_inequalities = 0; + size_t num_ranges = 0; + size_t num_equalities = 0; + size_t nonl_objectives = 0; + size_t nonl_constraints = 0; + + size_t num_nonlinear_vars_con = 0; + size_t num_nonlinear_vars_obj = 0; + size_t num_nonlinear_vars_both = 0; + + size_t num_linear_binary_vars = 0; + size_t num_linear_integer_vars = 0; + size_t num_nonlinear_obj_int_vars = 0; + size_t num_nonlinear_con_int_vars = 0; + size_t num_nonlinear_both_int_vars = 0; + + size_t nnz_Jacobian = 0; + size_t nnz_gradient = 0; std::vector o_expr; std::vector c_expr; @@ -619,34 +632,16 @@ class NLWriter { std::vector> G; std::vector> J; - NLWriter() - { - num_inequalities = 0; - num_ranges = 0; - num_equalities = 0; - nonl_objectives = 0; - nonl_constraints = 0; - nonlinear_vars_both = 0; - - num_linear_binary_vars = 0; - num_linear_integer_vars = 0; - num_nonlinear_obj_int_vars = 0; - num_nonlinear_con_int_vars = 0; - num_nonlinear_both_int_vars = 0; - - nnz_Jacobian = 0; - nnz_gradient = 0; - } + NLWriter() {} void collect_nl_data(Model& model, std::map& invvarmap, std::map& invconmap); - void write_ostream(Model& model, std::string& fname); - void write_fmtlib(Model& model, std::string& fname); + void write_ostream(Model& model, const std::string& fname); + void write_fmtlib(Model& model, const std::string& fname); }; // TODO - Reorder constraints to have nonlinear before linear -// TODO - Reorder variables per the AMPL solver hookup logic void NLWriter::collect_nl_data(Model& model, std::map& invvarmap, std::map& invconmap) { @@ -655,61 +650,60 @@ void NLWriter::collect_nl_data(Model& model, std::map& invvarmap r.resize(model.repn->constraints.size()); rval.resize(2 * model.repn->constraints.size()); + std::map, expr_pointer_t> simplified_subexpressions; + CALI_CXX_MARK_FUNCTION; CALI_MARK_BEGIN("Prepare Objective Expressions"); - if (model.repn->objectives.size() == 0) { - throw std::runtime_error("Error writing NL file: No objectives specified!"); - } - if (model.repn->objectives.size() > 1) { - throw std::runtime_error("Error writing NL file: More than one objective defined!"); - } // Objectives try { { + nnz_gradient = 0; size_t ctr = 0; - for (auto it = model.repn->objectives.begin(); it != model.repn->objectives.end(); - ++it, ++ctr) { - o_expr[ctr].collect_terms(*it); + for (auto& obj : model.repn->objectives) { + to_MutableNLPExpr(simplify_expr(obj.repn, simplified_subexpressions), o_expr[ctr]); if ((o_expr[ctr].quadratic_coefs.size() > 0) or (not o_expr[ctr].nonlinear->is_constant())) ++nonl_objectives; - for (auto it = o_expr[ctr].linear_vars.begin(); it != o_expr[ctr].linear_vars.end(); - ++it) { - auto var = *it; - linear_vars.insert(var->index); - vars.insert(var->index); - varobj[var->index] = var; + + std::set curr_vars; + for (auto& var : o_expr[ctr].linear_vars) { + auto index = var->index; + linear_vars.insert(index); + vars.insert(index); + curr_vars.insert(index); + varobj[index] = var; } - for (auto it = o_expr[ctr].quadratic_lvars.begin(); - it != o_expr[ctr].quadratic_lvars.end(); ++it) { - auto var = *it; - nonlinear_vars_obj.insert(var->index); - vars.insert(var->index); - varobj[var->index] = var; + for (auto& var : o_expr[ctr].quadratic_lvars) { + auto index = var->index; + nonlinear_vars_obj.insert(index); + vars.insert(index); + curr_vars.insert(index); + varobj[index] = var; } - for (auto it = o_expr[ctr].quadratic_rvars.begin(); - it != o_expr[ctr].quadratic_rvars.end(); ++it) { - auto var = *it; - nonlinear_vars_obj.insert(var->index); - vars.insert(var->index); - varobj[var->index] = var; + for (auto& var : o_expr[ctr].quadratic_rvars) { + auto index = var->index; + nonlinear_vars_obj.insert(index); + vars.insert(index); + curr_vars.insert(index); + varobj[index] = var; } - for (auto it = o_expr[ctr].nonlinear_vars.begin(); - it != o_expr[ctr].nonlinear_vars.end(); ++it) { - auto var = *it; - nonlinear_vars_obj.insert(var->index); - vars.insert(var->index); - varobj[var->index] = var; + for (auto& var : o_expr[ctr].nonlinear_vars) { + auto index = var->index; + nonlinear_vars_obj.insert(index); + vars.insert(index); + curr_vars.insert(index); + varobj[index] = var; } + nnz_gradient += curr_vars.size(); + + ctr++; + break; // TODO - Fix this for multiobjective } } CALI_MARK_END("Prepare Objective Expressions"); - // Since we have just one objective, the # of variables is the # of nonzeros in gradients - nnz_gradient = vars.size(); - CALI_MARK_BEGIN("Prepare Constraint Expressions"); // Constraints { @@ -721,7 +715,9 @@ void NLWriter::collect_nl_data(Model& model, std::map& invvarmap invconmap[ctr] = Con.id(); - Expr.collect_terms(Con); + // std::cout << "OLD " << Con.body().to_list() << std::endl; + to_MutableNLPExpr(simplify_expr(Con.repn, simplified_subexpressions), Expr); + // std::cout << "NEW " << Expr.nonlinear->to_list() << std::endl; double bodyconst = Expr.constval->eval(); if (Con.is_inequality()) { @@ -767,34 +763,28 @@ void NLWriter::collect_nl_data(Model& model, std::map& invvarmap std::set curr_vars; - for (auto it = Expr.linear_vars.begin(); it != Expr.linear_vars.end(); ++it) { - auto var = *it; + for (auto& var : Expr.linear_vars) { auto index = var->index; linear_vars.insert(index); vars.insert(index); varobj[index] = var; curr_vars.insert(index); } - for (auto it = Expr.quadratic_lvars.begin(); it != Expr.quadratic_lvars.end(); - ++it) { - auto var = *it; + for (auto& var : Expr.quadratic_lvars) { auto index = var->index; nonlinear_vars_con.insert(index); vars.insert(index); varobj[index] = var; curr_vars.insert(index); } - for (auto it = Expr.quadratic_rvars.begin(); it != Expr.quadratic_rvars.end(); - ++it) { - auto var = *it; + for (auto& var : Expr.quadratic_rvars) { auto index = var->index; nonlinear_vars_con.insert(index); vars.insert(index); varobj[index] = var; curr_vars.insert(index); } - for (auto it = Expr.nonlinear_vars.begin(); it != Expr.nonlinear_vars.end(); ++it) { - auto var = *it; + for (auto& var : Expr.nonlinear_vars) { auto index = var->index; nonlinear_vars_con.insert(index); vars.insert(index); @@ -815,45 +805,123 @@ void NLWriter::collect_nl_data(Model& model, std::map& invvarmap } CALI_MARK_BEGIN("Misc NL"); - for (auto it = linear_vars.begin(); it != linear_vars.end(); ++it) { - auto& var = varobj[*it]; - if (var.is_binary()) - ++num_linear_binary_vars; - else if (var.is_integer()) - ++num_linear_integer_vars; + std::set nonlinear_vars_both_c; + std::set nonlinear_vars_both_i; + std::set nonlinear_vars_con_c; + std::set nonlinear_vars_con_i; + std::set nonlinear_vars_obj_c; + std::set nonlinear_vars_obj_i; + std::set linear_vars_c; + std::set linear_vars_b; + std::set linear_vars_i; + // + // Collect the variables that are in both objectives and constraints + // + for (auto& vid : nonlinear_vars_con) { + if (nonlinear_vars_obj.find(vid) == nonlinear_vars_obj.end()) { + auto& var = varobj[vid]; + // CON + if (var.is_binary() or var.is_integer()) + nonlinear_vars_con_i.insert(vid); + else + nonlinear_vars_con_c.insert(vid); + } + else { + auto& var = varobj[vid]; + // BOTH + if (var.is_binary() or var.is_integer()) + nonlinear_vars_both_i.insert(vid); + else + nonlinear_vars_both_c.insert(vid); + } } - - for (auto it = nonlinear_vars_obj.begin(); it != nonlinear_vars_obj.end(); ++it) { - auto& var = varobj[*it]; - bool flag = var.is_binary() or var.is_integer(); - if (flag) ++num_nonlinear_obj_int_vars; - if (nonlinear_vars_con.find(*it) != nonlinear_vars_con.end()) { - ++nonlinear_vars_both; - if (flag) ++num_nonlinear_both_int_vars; + for (auto& vid : nonlinear_vars_obj) { + if (nonlinear_vars_con.find(vid) == nonlinear_vars_con.end()) { + auto& var = varobj[vid]; + // OBJ + if (var.is_binary() or var.is_integer()) + nonlinear_vars_obj_i.insert(vid); + else + nonlinear_vars_obj_c.insert(vid); } } - for (auto it = nonlinear_vars_con.begin(); it != nonlinear_vars_con.end(); ++it) { - auto& var = varobj[*it]; - if (var.is_binary() or var.is_integer()) ++num_nonlinear_con_int_vars; + num_nonlinear_both_int_vars = nonlinear_vars_both_i.size(); + num_nonlinear_con_int_vars = num_nonlinear_con_int_vars + nonlinear_vars_con_i.size(); + num_nonlinear_obj_int_vars = num_nonlinear_con_int_vars + nonlinear_vars_obj_i.size(); + + num_nonlinear_vars_both = nonlinear_vars_both_i.size() + nonlinear_vars_both_c.size(); + num_nonlinear_vars_con + = num_nonlinear_vars_both + nonlinear_vars_con_i.size() + nonlinear_vars_con_c.size(); + num_nonlinear_vars_obj + = num_nonlinear_vars_con + nonlinear_vars_obj_i.size() + nonlinear_vars_obj_c.size(); + + num_linear_binary_vars = 0; + num_linear_integer_vars = 0; + for (auto& vid : linear_vars) { + auto& var = varobj[vid]; + if (var.is_binary()) + num_linear_binary_vars++; + else if (var.is_integer()) + num_linear_integer_vars++; + + if (nonlinear_vars_obj.find(vid) != nonlinear_vars_obj.end()) continue; + if (nonlinear_vars_con.find(vid) != nonlinear_vars_con.end()) continue; + + if (var.is_binary()) + linear_vars_b.insert(vid); + else if (var.is_integer()) + linear_vars_i.insert(vid); + else + linear_vars_c.insert(vid); } // Map Variable index to NL variable ID (0 ... n_vars-1) { size_t ctr = 0; - for (auto it = vars.begin(); it != vars.end(); ++it) { - invvarmap[ctr] = *it; - varmap[*it] = ctr; - ++ctr; + for (auto& vid : nonlinear_vars_both_c) { + invvarmap[ctr] = vid; + varmap[vid] = ctr++; + } + for (auto& vid : nonlinear_vars_both_i) { + invvarmap[ctr] = vid; + varmap[vid] = ctr++; + } + for (auto& vid : nonlinear_vars_con_c) { + invvarmap[ctr] = vid; + varmap[vid] = ctr++; + } + for (auto& vid : nonlinear_vars_con_i) { + invvarmap[ctr] = vid; + varmap[vid] = ctr++; + } + for (auto& vid : nonlinear_vars_obj_c) { + invvarmap[ctr] = vid; + varmap[vid] = ctr++; + } + for (auto& vid : nonlinear_vars_obj_i) { + invvarmap[ctr] = vid; + varmap[vid] = ctr++; + } + for (auto& vid : linear_vars_c) { + invvarmap[ctr] = vid; + varmap[vid] = ctr++; + } + for (auto& vid : linear_vars_b) { + invvarmap[ctr] = vid; + varmap[vid] = ctr++; + } + for (auto& vid : linear_vars_i) { + invvarmap[ctr] = vid; + varmap[vid] = ctr++; } - CALI_MARK_END("Misc NL"); - - // GCOVR_EXCL_START - if (vars.size() != varmap.size()) - throw std::runtime_error( - "Error writing NL file: Variables with duplicate index values detected!"); - // GCOVR_EXCL_STOP } + // GCOVR_EXCL_START + if (vars.size() != varmap.size()) + throw std::runtime_error( + "Error writing NL file: Variables with duplicate index values detected!"); + // GCOVR_EXCL_STOP + // Compute linear Jacobian and Gradient values CALI_MARK_BEGIN("Compute Jacobian/Gradient"); k_count.resize(vars.size()); @@ -863,50 +931,44 @@ void NLWriter::collect_nl_data(Model& model, std::map& invvarmap { size_t ctr = 0; for (auto it = o_expr.begin(); it != o_expr.end(); ++it, ++ctr) { - for (auto jt = it->quadratic_lvars.begin(); jt != it->quadratic_lvars.end(); ++jt) { - G[ctr][varmap[(*jt)->index]] = 0; + for (auto& var : it->quadratic_lvars) { + G[ctr][varmap[var->index]] = 0; } - for (auto jt = it->quadratic_rvars.begin(); jt != it->quadratic_rvars.end(); ++jt) { - G[ctr][varmap[(*jt)->index]] = 0; + for (auto& var : it->quadratic_rvars) { + G[ctr][varmap[var->index]] = 0; } - for (auto jt = it->nonlinear_vars.begin(); jt != it->nonlinear_vars.end(); ++jt) { - G[ctr][varmap[(*jt)->index]] = 0; + for (auto& var : it->nonlinear_vars) { + G[ctr][varmap[var->index]] = 0; } - for (size_t j = 0; j < it->linear_coefs.size(); ++j) { + for (size_t j : coek::indices(it->linear_coefs)) { auto index = varmap[it->linear_vars[j]->index]; auto jt = G[ctr].find(index); if (jt != G[ctr].end()) jt->second += it->linear_coefs[j]->eval(); else G[ctr][index] = it->linear_coefs[j]->eval(); - /* - if (auto jt{ G[ctr].find(index) }; jt != G[ctr].end() ) - jt->second += it->linear_coefs[j].value(); - else - G[ctr][index] = it->linear_coefs[j].value(); - */ } } } { size_t ctr = 0; for (auto it = c_expr.begin(); it != c_expr.end(); ++it, ++ctr) { - for (auto jt = it->quadratic_lvars.begin(); jt != it->quadratic_lvars.end(); ++jt) { - size_t index = varmap[(*jt)->index]; + for (auto& var : it->quadratic_lvars) { + size_t index = varmap[var->index]; k_count[index].insert(ctr); J[ctr][index] = 0; } - for (auto jt = it->quadratic_rvars.begin(); jt != it->quadratic_rvars.end(); ++jt) { - size_t index = varmap[(*jt)->index]; + for (auto& var : it->quadratic_rvars) { + size_t index = varmap[var->index]; k_count[index].insert(ctr); J[ctr][index] = 0; } - for (auto jt = it->nonlinear_vars.begin(); jt != it->nonlinear_vars.end(); ++jt) { - size_t index = varmap[(*jt)->index]; + for (auto& var : it->nonlinear_vars) { + size_t index = varmap[var->index]; k_count[index].insert(ctr); J[ctr][index] = 0; } - for (size_t j = 0; j < it->linear_coefs.size(); ++j) { + for (size_t j : coek::indices(it->linear_coefs)) { size_t index = varmap[it->linear_vars[j]->index]; auto jt = J[ctr].find(index); if (jt != J[ctr].end()) @@ -915,21 +977,13 @@ void NLWriter::collect_nl_data(Model& model, std::map& invvarmap k_count[index].insert(ctr); J[ctr][index] = it->linear_coefs[j]->eval(); } - /* - if (auto jt{ J[ctr].find(index) }; jt != J[ctr].end() ) - jt->second += it->linear_coefs[j].value(); - else { - k_count[ index ].insert(ctr); - J[ctr][index] = it->linear_coefs[j].value(); - } - */ } } } CALI_MARK_END("Compute Jacobian/Gradient"); } -void NLWriter::write_ostream(Model& model, std::string& fname) +void NLWriter::write_ostream(Model& model, const std::string& fname) { std::ofstream ostr(fname); @@ -942,14 +996,14 @@ void NLWriter::write_ostream(Model& model, std::string& fname) // writes a header that doesn't conform to it... // ostr << "g3 1 1 0 # unnamed problem generated by COEK\n"; - ostr << " " << vars.size() << " " << (num_inequalities + num_equalities) << " 1 " - << num_ranges << " " << num_equalities + ostr << " " << vars.size() << " " << (num_inequalities + num_equalities) << " " + << std::max((size_t)1, o_expr.size()) << " " << num_ranges << " " << num_equalities << " 0 # vars, constraints, objectives, ranges, eqns, lcons\n"; ostr << " " << nonl_constraints << " " << nonl_objectives << " # nonlinear constraints, objectives\n"; ostr << " 0 0 # network constraints: nonlinear, linear\n"; - ostr << " " << nonlinear_vars_con.size() << " " << nonlinear_vars_obj.size() << " " - << nonlinear_vars_both << " # nonlinear vars in constraints, objectives, both\n"; + ostr << " " << num_nonlinear_vars_con << " " << num_nonlinear_vars_obj << " " + << num_nonlinear_vars_both << " # nonlinear vars in constraints, objectives, both\n"; ostr << " 0 0 0 1 # linear network variables; functions; arith, flags\n"; ostr << " " << num_linear_binary_vars << " " << num_linear_integer_vars << " " << num_nonlinear_both_int_vars << " " << num_nonlinear_con_int_vars << " " @@ -978,20 +1032,25 @@ void NLWriter::write_ostream(Model& model, std::string& fname) // // "O" section - nonlinear objective segments // - ctr = 0; - for (auto it = o_expr.begin(); it != o_expr.end(); ++it, ++ctr) { - bool sense = model.repn->objectives[ctr].sense(); - if (sense == Model::minimize) - ostr << "O" << ctr << " 0\n"; - else - ostr << "O" << ctr << " 1\n"; - if ((not it->nonlinear->is_constant()) or (it->quadratic_coefs.size() > 0)) { - print_expr(ostr, *it, varmap, true); - } - else { - ostr << "n" << it->constval->eval() << '\n'; + if (o_expr.size() > 0) { + ctr = 0; + for (auto it = o_expr.begin(); it != o_expr.end(); ++it, ++ctr) { + bool sense = model.repn->objectives[ctr].sense(); + if (sense == Model::minimize) + ostr << "O" << ctr << " 0\n"; + else + ostr << "O" << ctr << " 1\n"; + if ((not it->nonlinear->is_constant()) or (it->quadratic_coefs.size() > 0)) { + print_expr(ostr, *it, varmap, true); + } + else { + ostr << "n" << it->constval->eval() << '\n'; + } } } + else { + ostr << "O0 0\nn0\n"; + } // // "x" section - primal initial values @@ -1003,8 +1062,8 @@ void NLWriter::write_ostream(Model& model, std::string& fname) auto tmp = varobj[*it].value(); if (not std::isnan(tmp)) values[ctr] = tmp; } + ostr << "x" << values.size() << '\n'; if (values.size() > 0) { - ostr << "x" << values.size() << '\n'; for (auto it = values.begin(); it != values.end(); ++it) ostr << it->first << " " << it->second << '\n'; } @@ -1091,11 +1150,17 @@ void NLWriter::write_ostream(Model& model, std::string& fname) // // "k" section - Jacobian column counts // - ostr << "k" << (k_count.size() - 1) << '\n'; - ctr = 0; - for (size_t i = 0; i < (k_count.size() - 1); ++i) { - ctr += k_count[i].size(); - ostr << ctr << '\n'; + if (J.size() > 0) { + if (k_count.size() > 1) { + ostr << "k" << (k_count.size() - 1) << '\n'; + ctr = 0; + for (size_t i = 0; i < (k_count.size() - 1); ++i) { + ctr += k_count[i].size(); + ostr << ctr << '\n'; + } + } + else + ostr << "k0\n"; } // @@ -1130,7 +1195,7 @@ void NLWriter::write_ostream(Model& model, std::string& fname) } #ifdef WITH_FMTLIB -void NLWriter::write_fmtlib(Model& model, std::string& fname) +void NLWriter::write_fmtlib(Model& model, const std::string& fname) { auto ostr = fmt::output_file(fname, fmt::file::WRONLY | fmt::file::CREATE | FMT_POSIX(O_TRUNC)); @@ -1141,12 +1206,13 @@ void NLWriter::write_fmtlib(Model& model, std::string& fname) // header that doesn't conform to it... // ostr.print("g3 1 1 0 # unnamed problem generated by COEK\n"); - ostr.print(" {} {} 1 {} {} 0 # vars, constraints, objectives, ranges, eqns, lcons\n", - vars.size(), (num_inequalities + num_equalities), num_ranges, num_equalities); + ostr.print(" {} {} {} {} {} 0 # vars, constraints, objectives, ranges, eqns, lcons\n", + vars.size(), (num_inequalities + num_equalities), std::max((size_t)1, o_expr.size()), + num_ranges, num_equalities); ostr.print(" {} {} # nonlinear constraints, objectives\n", nonl_constraints, nonl_objectives); ostr.print(" 0 0 # network constraints: nonlinear, linear\n"); ostr.print(" {} {} {} # nonlinear vars in constraints, objectives, both\n", - nonlinear_vars_con.size(), nonlinear_vars_obj.size(), nonlinear_vars_both); + num_nonlinear_vars_con, num_nonlinear_vars_obj, num_nonlinear_vars_both); ostr.print(" 0 0 0 1 # linear network variables; functions; arith, flags\n"); ostr.print(" {} {} {} {} {} # discrete variables: binary, integer, nonlinear (b,c,o)\n", num_linear_binary_vars, num_linear_integer_vars, num_nonlinear_both_int_vars, @@ -1180,22 +1246,27 @@ void NLWriter::write_fmtlib(Model& model, std::string& fname) // CALI_MARK_BEGIN("O"); { - constexpr auto _fmtstr_O_0 = FMT_COMPILE("O{} 0\n"); - constexpr auto _fmtstr_O_1 = FMT_COMPILE("O{} 1\n"); - size_t ctr = 0; - for (auto it = o_expr.begin(); it != o_expr.end(); ++it, ++ctr) { - bool sense = model.repn->objectives[ctr].sense(); - if (sense == Model::minimize) - ostr.print(fmt::format(_fmtstr_O_0, ctr)); - else - ostr.print(fmt::format(_fmtstr_O_1, ctr)); - if ((not it->nonlinear->is_constant()) or (it->quadratic_coefs.size() > 0)) { - print_expr(ostr, *it, varmap, true); - } - else { - ostr.print(fmt::format(_fmtstr_n, it->constval->eval())); + if (o_expr.size() > 0) { + constexpr auto _fmtstr_O_0 = FMT_COMPILE("O{} 0\n"); + constexpr auto _fmtstr_O_1 = FMT_COMPILE("O{} 1\n"); + size_t ctr = 0; + for (auto it = o_expr.begin(); it != o_expr.end(); ++it, ++ctr) { + bool sense = model.repn->objectives[ctr].sense(); + if (sense == Model::minimize) + ostr.print(fmt::format(_fmtstr_O_0, ctr)); + else + ostr.print(fmt::format(_fmtstr_O_1, ctr)); + if ((not it->nonlinear->is_constant()) or (it->quadratic_coefs.size() > 0)) { + print_expr(ostr, *it, varmap, true); + } + else { + ostr.print(fmt::format(_fmtstr_n, it->constval->eval())); + } } } + else { + ostr.print("O0 0\nn0\n"); + } } CALI_MARK_END("O"); @@ -1217,8 +1288,8 @@ void NLWriter::write_fmtlib(Model& model, std::string& fname) fmt::format_to(std::back_inserter(out), _fmtstr_x, ctr, tmp); } } + ostr.print("x{}\n", num); if (num) { - ostr.print("x{}\n", num); out.push_back(0); ostr.print("{}", out.data()); } @@ -1303,13 +1374,19 @@ void NLWriter::write_fmtlib(Model& model, std::string& fname) // "k" section - Jacobian column counts // CALI_MARK_BEGIN("k"); - ostr.print("k{}\n", k_count.size() - 1); // << "k" << (k_count.size()-1) << '\n'; - { - size_t ctr = 0; - for (size_t i = 0; i < (k_count.size() - 1); ++i) { - ctr += k_count[i].size(); - ostr.print(fmt::format(_fmtstr_value, ctr)); // << ctr << '\n'; + if (J.size() > 0) { + if (k_count.size() > 1) { + ostr.print("k{}\n", k_count.size() - 1); // << "k" << (k_count.size()-1) << '\n'; + { + size_t ctr = 0; + for (size_t i = 0; i < (k_count.size() - 1); ++i) { + ctr += k_count[i].size(); + ostr.print(fmt::format(_fmtstr_value, ctr)); // << ctr << '\n'; + } + } } + else + ostr.print("k0\n"); } CALI_MARK_END("k"); @@ -1352,7 +1429,8 @@ void NLWriter::write_fmtlib(Model& model, std::string& fname) } #endif -void write_nl_problem_ostream(Model& model, std::string& fname, std::map& invvarmap, +void write_nl_problem_ostream(Model& model, const std::string& fname, + std::map& invvarmap, std::map& invconmap) { NLWriter writer; @@ -1361,7 +1439,8 @@ void write_nl_problem_ostream(Model& model, std::string& fname, std::map& invvarmap, +void write_nl_problem_fmtlib(Model& model, const std::string& fname, + std::map& invvarmap, std::map& invconmap) { NLWriter writer; @@ -1369,14 +1448,14 @@ void write_nl_problem_fmtlib(Model& model, std::string& fname, std::map& invvarmap, +void write_nl_problem(Model& model, const std::string& fname, std::map& invvarmap, std::map& invconmap) { write_nl_problem_fmtlib(model, fname, invvarmap, invconmap); } #else -void write_nl_problem(Model& model, std::string& fname, std::map& invvarmap, +void write_nl_problem(Model& model, const std::string& fname, std::map& invvarmap, std::map& invconmap) { write_nl_problem_ostream(model, fname, invvarmap, invconmap); diff --git a/lib/coek/coek/solvers/ipopt/ipopt_capi.cpp b/lib/coek/coek/solvers/ipopt/ipopt_capi.cpp index 92e9f725..a791f8d8 100644 --- a/lib/coek/coek/solvers/ipopt/ipopt_capi.cpp +++ b/lib/coek/coek/solvers/ipopt/ipopt_capi.cpp @@ -59,8 +59,9 @@ int load_ipopt_library(const char* libname, std::string& error_message) class IpoptModel { public: IpoptProblem app; - NLPModel model; + NLPModel nlpmodel; + Number objsign; bool start_from_last_x; Number last_objval; Index last_iter_count; @@ -71,16 +72,17 @@ class IpoptModel { std::vector last_lambda; std::vector tmp_grad; - std::vector tmp_c; + std::vector tmp_g; std::vector tmp_j; std::vector tmp_h; std::vector tmp_hw; // default constructor - IpoptModel(NLPModel& _model) + IpoptModel(NLPModel& _nlpmodel) { app = 0; - model = _model; + nlpmodel = _nlpmodel; + objsign = 1.0; } // default destructor @@ -130,14 +132,6 @@ class IpoptModel { int perform_solve(); - /* - // This method is called when the algorithm is complete so the TNLP can store/write the - solution void finalize_solution(SolverReturn status, Index n, const Number* x, const Number* - z_L, const Number* z_U, Index m, const Number* g, const Number* lambda, Number obj_value, - const IpoptData* ip_data, - IpoptCalculatedQuantities* ip_cq); - */ - private: // This method should not be used. IpoptModel(const IpoptModel&) {} @@ -150,22 +144,23 @@ bool IpoptModel::get_nlp_info(Index& n, Index& m, Index& nnz_jac_g, Index& nnz_h int& index_style) { // The number of variables - n = static_cast(model.num_variables()); + n = static_cast(nlpmodel.num_variables()); // std::cout << "HERE z " << n << std::endl << std::flush; // The number of constraints - m = static_cast(model.num_constraints()); + m = static_cast(nlpmodel.num_constraints()); // The number of nonzeros in the jacobian - nnz_jac_g = static_cast(model.num_nonzeros_Jacobian()); + nnz_jac_g = static_cast(nlpmodel.num_nonzeros_Jacobian()); // The number of nonzeros in the hessian of the lagrangian - nnz_h_lag = static_cast(model.num_nonzeros_Hessian_Lagrangian()); + nnz_h_lag = static_cast(nlpmodel.num_nonzeros_Hessian_Lagrangian()); // std::cout << "HERE Z " << nnz_h_lag << std::endl << std::flush; // The index style for row/col entries - // index_style = FORTRAN_STYLE; - index_style = 0; /* C_STYLE */ + index_style = 1; // FORTRAN STYLE + + objsign = nlpmodel.get_objective(0).sense() ? 1.0 : -1.0; return true; } @@ -177,10 +172,10 @@ bool IpoptModel::get_bounds_info(Index /*n*/, Number* x_l, Number* x_u, Index /* // x_u[i] - the upper bound of variable i // std::cout << "GET BOUNDS " << std::endl << std::flush; - // std::cout << " num variables: " << model.num_variables() << std::endl; + // std::cout << " num variables: " << nlpmodel.num_variables() << std::endl; // Setting bounds to +/- infinity - for (size_t i = 0; i < model.num_variables(); i++) { - auto v = model.get_variable(i); + for (size_t i = 0; i < nlpmodel.num_variables(); i++) { + auto v = nlpmodel.get_variable(i); if (v.is_binary()) throw std::runtime_error("Cannot apply ipopt to problems with binary variables"); if (v.is_integer()) @@ -191,9 +186,9 @@ bool IpoptModel::get_bounds_info(Index /*n*/, Number* x_l, Number* x_u, Index /* // std::cout << "x " << i << " " << x_l[i] << " " << x_u[i] << std::endl << std::flush; } - // std::cout << " num constraints: " << model.num_constraints() << std::endl; - for (size_t j = 0; j < model.repn->model.repn->constraints.size(); j++) { - auto& con = model.repn->model.repn->constraints[j]; + // std::cout << " num constraints: " << nlpmodel.num_constraints() << std::endl; + for (size_t j = 0; j < nlpmodel.repn->model.repn->constraints.size(); j++) { + auto& con = nlpmodel.repn->model.repn->constraints[j]; if (con.is_inequality()) { if (con.has_lower()) g_l[j] = con.lower().value(); @@ -214,62 +209,23 @@ bool IpoptModel::get_bounds_info(Index /*n*/, Number* x_l, Number* x_u, Index /* return true; } -/* -bool IpoptModel::get_starting_point(Index n, bool init_x, Number* x, - bool init_z, Number* z_L, Number* z_U, - Index m, bool init_lambda, Number* lambda) -{ -//std::cout << "GET STARTING POINT " << init_x << " " << init_z << " " << init_lambda << std::endl -<< std::flush; - -// We only have starting values for x -if (init_x) { - size_t n_ = static_cast(n); - last_x.resize(n_); - // Initialize the x[i]; - for (size_t i=0; i(n); - last_zL.resize(n_); - last_zU.resize(n_); - for (size_t i=0; i(m); - last_lambda.resize(m_); - for (size_t i=0; i(n)); + nlpmodel.set_variable_view(x, static_cast(n)); } // std::cout << "Compute F - START" << std::endl << std::flush; // return the value of the objective function - obj_value = model.compute_f(0); + obj_value = objsign * nlpmodel.compute_f(0); + +#if 0 + for (size_t j=0; j(n)); + nlpmodel.set_variable_view(x, static_cast(n)); } +#if 0 + for (size_t j=0; j::iterator it = tmp_grad.begin(); it != tmp_grad.end(); it++) - grad_f[i++] = *it; + for (double val : tmp_grad) { + // std::cout << "DF " << i << " " << objsign << " " << val << std::endl; + grad_f[i++] = objsign * val; + } // std::cout << "EVAL DF - END" << std::endl << std::flush; return true; @@ -300,14 +263,14 @@ bool IpoptModel::eval_g(Index n, const Number* x, bool new_x, Index /*m*/, Numbe // std::cout << "EVAL G " << std::endl << std::flush; if (new_x) { // std::cout << "Set Vars " << std::endl << std::flush; - model.set_variable_view(x, static_cast(n)); + nlpmodel.set_variable_view(x, static_cast(n)); } // std::cout << "Compute G - START" << std::endl << std::flush; // return the value of the constraints: g(x) - model.compute_c(tmp_c); + nlpmodel.compute_c(tmp_g); size_t i = 0; - for (std::vector::iterator it = tmp_c.begin(); it != tmp_c.end(); it++) g[i++] = *it; + for (double val : tmp_g) g[i++] = val; // std::cout << "EVAL G - END" << std::endl << std::flush; return true; @@ -321,10 +284,10 @@ bool IpoptModel::eval_jac_g(Index n, const Number* x, bool new_x, Index /*m*/, I // Return the structure of the Jacobian of the constraints std::vector jrow; std::vector jcol; - model.get_J_nonzeros(jrow, jcol); + nlpmodel.get_J_nonzeros(jrow, jcol); for (size_t i = 0; i < jrow.size(); i++) { - jRow[i] = static_cast(jrow[i]); - jCol[i] = static_cast(jcol[i]); + jRow[i] = static_cast(jrow[i]) + 1; + jCol[i] = static_cast(jcol[i]) + 1; } // std::cout << "Get Structure " << std::endl << std::flush; // std::cout << jrow << std::endl; @@ -337,9 +300,9 @@ bool IpoptModel::eval_jac_g(Index n, const Number* x, bool new_x, Index /*m*/, I // std::cout << "nele_jac " << nele_jac << std::endl << std::flush; // Return the values of the Jacobian of the constraints if (new_x) { - model.set_variable_view(x, static_cast(n)); + nlpmodel.set_variable_view(x, static_cast(n)); } - model.compute_J(tmp_j); + nlpmodel.compute_J(tmp_j); for (size_t i = 0; i < tmp_j.size(); i++) values[i] = tmp_j[i]; // std::cout << "Do Eval - END" << std::endl << std::flush; } @@ -357,27 +320,31 @@ bool IpoptModel::eval_h(Index n, const Number* x, bool new_x, Number obj_factor, // triangle only. std::vector hrow; std::vector hcol; - model.get_H_nonzeros(hrow, hcol); - // std::cout << "HERE hrow_size " << hrow.size() << std::endl; + nlpmodel.get_H_nonzeros(hrow, hcol); for (size_t i = 0; i < hrow.size(); i++) { - hRow[i] = static_cast(hrow[i]); - hCol[i] = static_cast(hcol[i]); - // std::cout << hRow[i] << " " << hCol[i] << std::endl; + hRow[i] = static_cast(hrow[i]) + 1; + hCol[i] = static_cast(hcol[i]) + 1; + // std::cout << "H " << hRow[i] << " " << hCol[i] << std::endl; } } else { // Return the values of the Hessian if (new_x) { - model.set_variable_view(x, static_cast(n)); + nlpmodel.set_variable_view(x, static_cast(n)); } - size_t nf = model.num_objectives(); - size_t nc = model.num_constraints(); + size_t nf = nlpmodel.num_objectives(); + size_t nc = nlpmodel.num_constraints(); + // TODO - handle multiple objectives for (size_t i = 0; i < nf; i++) tmp_hw[i] = obj_factor; - // for (size_t i=nf; i(nnz_jac_g)); tmp_h.resize(static_cast(nnz_h_lag)); - tmp_hw.resize(model.num_objectives() + m_); + tmp_hw.resize(nlpmodel.num_objectives() + m_); std::vector x_L(n_); std::vector x_U(n_); @@ -565,7 +498,7 @@ class IpoptSolverRepn_CAPI : public IpoptSolverRepn { public: std::shared_ptr nlp; - IpoptSolverRepn_CAPI(NLPModel& model) { nlp = std::make_shared(model); } + IpoptSolverRepn_CAPI(NLPModel& nlpmodel) { nlp = std::make_shared(nlpmodel); } int perform_solve() { return nlp->perform_solve(); } @@ -612,11 +545,11 @@ void IpoptSolver::initialize() available_ = error_code == 0; } -void IpoptSolver::load(NLPModel& _model) +void IpoptSolver::load(NLPModel& _nlpmodel) { - auto repn_capi = std::make_shared(_model); + auto repn_capi = std::make_shared(_nlpmodel); repn_capi->nlp->build(); - model = &_model; + model = &_nlpmodel; repn = std::dynamic_pointer_cast(repn_capi); } diff --git a/lib/coek/examples/CMakeLists.txt b/lib/coek/examples/CMakeLists.txt index 7db0ca1b..9dc8ea5f 100644 --- a/lib/coek/examples/CMakeLists.txt +++ b/lib/coek/examples/CMakeLists.txt @@ -5,24 +5,35 @@ if (with_fmtlib) find_package(fmt REQUIRED) endif() -OPTION(STATIC_LINK "STATIC_LINK" OFF) +#OPTION(STATIC_LINK "STATIC_LINK" OFF) SET(sources simplelp1.cpp simplelp1_solve.cpp rosenbr.cpp srosenbr_vector.cpp - srosenbr_array.cpp invquad_vector.cpp - invquad_array.cpp - invquad_array_solve.cpp - invquad_array_resolve.cpp ) +if (CMAKE_CXX_STANDARD GREATER_EQUAL 17) + list(APPEND sources + srosenbr_array.cpp + invquad_array.cpp + invquad_array_solve.cpp + invquad_array_resolve.cpp + ) +endif() include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/..) # build library -ADD_LIBRARY(coekexamples ${sources}) -#TARGET_COMPILE_OPTIONS(coekexamples PRIVATE ${coek_compile_options}) +if (build_shared_libs) + add_library(coekexamples SHARED ${sources}) + message("-- Building shared libcoekexamples library") + set_target_properties(coekexamples PROPERTIES POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}) +else() + add_library(coekexamples STATIC ${sources}) + message("-- Building static libcoekexamples library") +endif() + target_compile_options(coekexamples PUBLIC ${coek_compile_options}) target_link_libraries(coekexamples PUBLIC coek) diff --git a/lib/coek/test/CMakeLists.txt b/lib/coek/test/CMakeLists.txt index e388e43c..a00fa2ad 100644 --- a/lib/coek/test/CMakeLists.txt +++ b/lib/coek/test/CMakeLists.txt @@ -11,7 +11,14 @@ endif() SET(sources runner.cpp test_model.cpp - test_expr_visitors.cpp + test_visitor_simplify.cpp + test_visitor_mutable.cpp + test_visitor_writer.cpp + test_visitor_quadexpr.cpp + test_visitor_nlpexpr.cpp + test_visitor_symdiff.cpp + test_visitor_findvarparam.cpp + test_visitor_eval.cpp test_testsolver.cpp test_examples.cpp test_writers.cpp @@ -35,6 +42,13 @@ if(with_cppad) test_autograd_cppad.cpp) endif() +# ASL LIBRARY +if(with_asl) + MESSAGE("-- Building Test with AD: ASL") + list(APPEND sources + test_autograd_asl.cpp) +endif() + include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/..) include_directories(BEFORE ${coek_include_directories}) LINK_DIRECTORIES(${coek_link_directories}) @@ -71,8 +85,9 @@ add_custom_target(test_coek_memcheck if (gcovr_FOUND) add_custom_target(gcovr_coek - COMMAND ${GCOVR_EXECUTABLE} -d -r ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/.. ) + COMMAND ${GCOVR_EXECUTABLE} -d -r ${coek_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/.. +) add_custom_target(gcovr_coek_verbose - COMMAND ${GCOVR_EXECUTABLE} -v -d -r ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/.. ) + COMMAND ${GCOVR_EXECUTABLE} -v -d -r ${coek_SOURCE_DIR} --filter "\.\." ${CMAKE_CURRENT_BINARY_DIR}/.. ) endif() diff --git a/lib/coek/test/baselines/small1.fmtnl b/lib/coek/test/baselines/small1.fmtnl index 7118ec26..395fc128 100644 --- a/lib/coek/test/baselines/small1.fmtnl +++ b/lib/coek/test/baselines/small1.fmtnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 1 0 # nonlinear vars in constraints, objectives, both + 1 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 1 1 # nonzeros in Jacobian, gradients @@ -10,11 +10,11 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o5 -v1 +v0 n2 O0 0 o5 -v0 +v1 n2 x2 0 1 @@ -25,8 +25,8 @@ b 3 3 k1 -0 +1 J0 1 -1 0 -G0 1 0 0 +G0 1 +1 0 diff --git a/lib/coek/test/baselines/small1.nl b/lib/coek/test/baselines/small1.nl index 7118ec26..395fc128 100644 --- a/lib/coek/test/baselines/small1.nl +++ b/lib/coek/test/baselines/small1.nl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 1 0 # nonlinear vars in constraints, objectives, both + 1 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 1 1 # nonzeros in Jacobian, gradients @@ -10,11 +10,11 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o5 -v1 +v0 n2 O0 0 o5 -v0 +v1 n2 x2 0 1 @@ -25,8 +25,8 @@ b 3 3 k1 -0 +1 J0 1 -1 0 -G0 1 0 0 +G0 1 +1 0 diff --git a/lib/coek/test/baselines/small1.ostrnl b/lib/coek/test/baselines/small1.ostrnl index 7118ec26..395fc128 100644 --- a/lib/coek/test/baselines/small1.ostrnl +++ b/lib/coek/test/baselines/small1.ostrnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 1 0 # nonlinear vars in constraints, objectives, both + 1 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 1 1 # nonzeros in Jacobian, gradients @@ -10,11 +10,11 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o5 -v1 +v0 n2 O0 0 o5 -v0 +v1 n2 x2 0 1 @@ -25,8 +25,8 @@ b 3 3 k1 -0 +1 J0 1 -1 0 -G0 1 0 0 +G0 1 +1 0 diff --git a/lib/coek/test/baselines/small13.fmtnl b/lib/coek/test/baselines/small13.fmtnl index 8f191e0e..da2d327c 100644 --- a/lib/coek/test/baselines/small13.fmtnl +++ b/lib/coek/test/baselines/small13.fmtnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 1 3 1 0 3 0 # vars, constraints, objectives, ranges, eqns, lcons 3 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 0 0 # nonlinear vars in constraints, objectives, both + 1 1 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 3 1 # nonzeros in Jacobian, gradients diff --git a/lib/coek/test/baselines/small13.nl b/lib/coek/test/baselines/small13.nl index 8f191e0e..da2d327c 100644 --- a/lib/coek/test/baselines/small13.nl +++ b/lib/coek/test/baselines/small13.nl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 1 3 1 0 3 0 # vars, constraints, objectives, ranges, eqns, lcons 3 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 0 0 # nonlinear vars in constraints, objectives, both + 1 1 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 3 1 # nonzeros in Jacobian, gradients diff --git a/lib/coek/test/baselines/small13.ostrnl b/lib/coek/test/baselines/small13.ostrnl index 8f191e0e..da2d327c 100644 --- a/lib/coek/test/baselines/small13.ostrnl +++ b/lib/coek/test/baselines/small13.ostrnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 1 3 1 0 3 0 # vars, constraints, objectives, ranges, eqns, lcons 3 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 0 0 # nonlinear vars in constraints, objectives, both + 1 1 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 3 1 # nonzeros in Jacobian, gradients diff --git a/lib/coek/test/baselines/small14.fmtnl b/lib/coek/test/baselines/small14.fmtnl index 686b2948..4a65ef18 100644 --- a/lib/coek/test/baselines/small14.fmtnl +++ b/lib/coek/test/baselines/small14.fmtnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 19 1 0 19 0 # vars, constraints, objectives, ranges, eqns, lcons 19 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 0 0 # nonlinear vars in constraints, objectives, both + 2 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 19 2 # nonzeros in Jacobian, gradients @@ -48,8 +48,8 @@ C12 o52 o3 o0 -n7.3890560989306495 v0 +n7.3890560989306495 n5.43656365691809 C13 o47 diff --git a/lib/coek/test/baselines/small14.nl b/lib/coek/test/baselines/small14.nl index 686b2948..4a65ef18 100644 --- a/lib/coek/test/baselines/small14.nl +++ b/lib/coek/test/baselines/small14.nl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 19 1 0 19 0 # vars, constraints, objectives, ranges, eqns, lcons 19 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 0 0 # nonlinear vars in constraints, objectives, both + 2 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 19 2 # nonzeros in Jacobian, gradients @@ -48,8 +48,8 @@ C12 o52 o3 o0 -n7.3890560989306495 v0 +n7.3890560989306495 n5.43656365691809 C13 o47 diff --git a/lib/coek/test/baselines/small14.ostrnl b/lib/coek/test/baselines/small14.ostrnl index 31a512be..2f3f2180 100644 --- a/lib/coek/test/baselines/small14.ostrnl +++ b/lib/coek/test/baselines/small14.ostrnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 19 1 0 19 0 # vars, constraints, objectives, ranges, eqns, lcons 19 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 0 0 # nonlinear vars in constraints, objectives, both + 2 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 19 2 # nonzeros in Jacobian, gradients @@ -48,8 +48,8 @@ C12 o52 o3 o0 -n7.38905609893065 v0 +n7.38905609893065 n5.43656365691809 C13 o47 diff --git a/lib/coek/test/baselines/small2.fmtnl b/lib/coek/test/baselines/small2.fmtnl index 1c7d5684..d55476dd 100644 --- a/lib/coek/test/baselines/small2.fmtnl +++ b/lib/coek/test/baselines/small2.fmtnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 0 0 # nonlinear vars in constraints, objectives, both + 1 1 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 1 1 # nonzeros in Jacobian, gradients @@ -10,7 +10,7 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o5 -v1 +v0 n2 O0 0 n0 @@ -23,8 +23,8 @@ b 3 3 k1 -0 +1 J0 1 -1 0 +0 0 G0 1 -0 1 +1 1 diff --git a/lib/coek/test/baselines/small2.nl b/lib/coek/test/baselines/small2.nl index 1c7d5684..d55476dd 100644 --- a/lib/coek/test/baselines/small2.nl +++ b/lib/coek/test/baselines/small2.nl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 0 0 # nonlinear vars in constraints, objectives, both + 1 1 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 1 1 # nonzeros in Jacobian, gradients @@ -10,7 +10,7 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o5 -v1 +v0 n2 O0 0 n0 @@ -23,8 +23,8 @@ b 3 3 k1 -0 +1 J0 1 -1 0 +0 0 G0 1 -0 1 +1 1 diff --git a/lib/coek/test/baselines/small2.ostrnl b/lib/coek/test/baselines/small2.ostrnl index 1c7d5684..d55476dd 100644 --- a/lib/coek/test/baselines/small2.ostrnl +++ b/lib/coek/test/baselines/small2.ostrnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 0 0 # nonlinear vars in constraints, objectives, both + 1 1 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 1 1 # nonzeros in Jacobian, gradients @@ -10,7 +10,7 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o5 -v1 +v0 n2 O0 0 n0 @@ -23,8 +23,8 @@ b 3 3 k1 -0 +1 J0 1 -1 0 +0 0 G0 1 -0 1 +1 1 diff --git a/lib/coek/test/baselines/small3.fmtnl b/lib/coek/test/baselines/small3.fmtnl index dfad5a4d..ef34e5d3 100644 --- a/lib/coek/test/baselines/small3.fmtnl +++ b/lib/coek/test/baselines/small3.fmtnl @@ -10,7 +10,7 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o5 -v1 +v0 n2 O0 0 o2 @@ -25,9 +25,9 @@ b 3 3 k1 -0 +1 J0 1 -1 0 +0 0 G0 2 0 0 1 0 diff --git a/lib/coek/test/baselines/small3.nl b/lib/coek/test/baselines/small3.nl index dfad5a4d..ef34e5d3 100644 --- a/lib/coek/test/baselines/small3.nl +++ b/lib/coek/test/baselines/small3.nl @@ -10,7 +10,7 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o5 -v1 +v0 n2 O0 0 o2 @@ -25,9 +25,9 @@ b 3 3 k1 -0 +1 J0 1 -1 0 +0 0 G0 2 0 0 1 0 diff --git a/lib/coek/test/baselines/small3.ostrnl b/lib/coek/test/baselines/small3.ostrnl index dfad5a4d..ef34e5d3 100644 --- a/lib/coek/test/baselines/small3.ostrnl +++ b/lib/coek/test/baselines/small3.ostrnl @@ -10,7 +10,7 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o5 -v1 +v0 n2 O0 0 o2 @@ -25,9 +25,9 @@ b 3 3 k1 -0 +1 J0 1 -1 0 +0 0 G0 2 0 0 1 0 diff --git a/lib/coek/test/baselines/small4.fmtnl b/lib/coek/test/baselines/small4.fmtnl index b74d1c64..6d2886dc 100644 --- a/lib/coek/test/baselines/small4.fmtnl +++ b/lib/coek/test/baselines/small4.fmtnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 1 1 # nonlinear vars in constraints, objectives, both + 2 2 1 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 2 1 # nonzeros in Jacobian, gradients @@ -14,7 +14,7 @@ v0 v1 O0 0 o5 -v1 +v0 n2 x2 0 1 @@ -30,4 +30,4 @@ J0 2 0 0 1 0 G0 1 -1 0 +0 0 diff --git a/lib/coek/test/baselines/small4.nl b/lib/coek/test/baselines/small4.nl index b74d1c64..6d2886dc 100644 --- a/lib/coek/test/baselines/small4.nl +++ b/lib/coek/test/baselines/small4.nl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 1 1 # nonlinear vars in constraints, objectives, both + 2 2 1 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 2 1 # nonzeros in Jacobian, gradients @@ -14,7 +14,7 @@ v0 v1 O0 0 o5 -v1 +v0 n2 x2 0 1 @@ -30,4 +30,4 @@ J0 2 0 0 1 0 G0 1 -1 0 +0 0 diff --git a/lib/coek/test/baselines/small4.ostrnl b/lib/coek/test/baselines/small4.ostrnl index b74d1c64..6d2886dc 100644 --- a/lib/coek/test/baselines/small4.ostrnl +++ b/lib/coek/test/baselines/small4.ostrnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 2 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 1 1 # nonlinear vars in constraints, objectives, both + 2 2 1 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 2 1 # nonzeros in Jacobian, gradients @@ -14,7 +14,7 @@ v0 v1 O0 0 o5 -v1 +v0 n2 x2 0 1 @@ -30,4 +30,4 @@ J0 2 0 0 1 0 G0 1 -1 0 +0 0 diff --git a/lib/coek/test/baselines/small5.fmtnl b/lib/coek/test/baselines/small5.fmtnl index f0a7b5d8..df3b8132 100644 --- a/lib/coek/test/baselines/small5.fmtnl +++ b/lib/coek/test/baselines/small5.fmtnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 12 1 0 12 0 # vars, constraints, objectives, ranges, eqns, lcons 12 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 3 1 1 # nonlinear vars in constraints, objectives, both + 3 3 1 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 36 1 # nonzeros in Jacobian, gradients @@ -155,11 +155,11 @@ n0.5 o5 v0 n2 -o3 +o2 +n0.5 o5 v0 n2 -n2 x3 0 1 1 2 diff --git a/lib/coek/test/baselines/small5.nl b/lib/coek/test/baselines/small5.nl index f0a7b5d8..df3b8132 100644 --- a/lib/coek/test/baselines/small5.nl +++ b/lib/coek/test/baselines/small5.nl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 12 1 0 12 0 # vars, constraints, objectives, ranges, eqns, lcons 12 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 3 1 1 # nonlinear vars in constraints, objectives, both + 3 3 1 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 36 1 # nonzeros in Jacobian, gradients @@ -155,11 +155,11 @@ n0.5 o5 v0 n2 -o3 +o2 +n0.5 o5 v0 n2 -n2 x3 0 1 1 2 diff --git a/lib/coek/test/baselines/small5.ostrnl b/lib/coek/test/baselines/small5.ostrnl index f0a7b5d8..df3b8132 100644 --- a/lib/coek/test/baselines/small5.ostrnl +++ b/lib/coek/test/baselines/small5.ostrnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 12 1 0 12 0 # vars, constraints, objectives, ranges, eqns, lcons 12 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 3 1 1 # nonlinear vars in constraints, objectives, both + 3 3 1 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 36 1 # nonzeros in Jacobian, gradients @@ -155,11 +155,11 @@ n0.5 o5 v0 n2 -o3 +o2 +n0.5 o5 v0 n2 -n2 x3 0 1 1 2 diff --git a/lib/coek/test/baselines/small6.fmtnl b/lib/coek/test/baselines/small6.fmtnl index fae10ee5..dd083350 100644 --- a/lib/coek/test/baselines/small6.fmtnl +++ b/lib/coek/test/baselines/small6.fmtnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 6 1 0 6 0 # vars, constraints, objectives, ranges, eqns, lcons 6 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 3 0 0 # nonlinear vars in constraints, objectives, both + 3 3 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 18 1 # nonzeros in Jacobian, gradients diff --git a/lib/coek/test/baselines/small6.nl b/lib/coek/test/baselines/small6.nl index fae10ee5..dd083350 100644 --- a/lib/coek/test/baselines/small6.nl +++ b/lib/coek/test/baselines/small6.nl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 6 1 0 6 0 # vars, constraints, objectives, ranges, eqns, lcons 6 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 3 0 0 # nonlinear vars in constraints, objectives, both + 3 3 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 18 1 # nonzeros in Jacobian, gradients diff --git a/lib/coek/test/baselines/small6.ostrnl b/lib/coek/test/baselines/small6.ostrnl index fae10ee5..dd083350 100644 --- a/lib/coek/test/baselines/small6.ostrnl +++ b/lib/coek/test/baselines/small6.ostrnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 6 1 0 6 0 # vars, constraints, objectives, ranges, eqns, lcons 6 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 3 0 0 # nonlinear vars in constraints, objectives, both + 3 3 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 18 1 # nonzeros in Jacobian, gradients diff --git a/lib/coek/test/baselines/small7.fmtnl b/lib/coek/test/baselines/small7.fmtnl index 79de48bd..dae9e63c 100644 --- a/lib/coek/test/baselines/small7.fmtnl +++ b/lib/coek/test/baselines/small7.fmtnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 24 1 0 24 0 # vars, constraints, objectives, ranges, eqns, lcons 24 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 3 0 0 # nonlinear vars in constraints, objectives, both + 3 3 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 72 1 # nonzeros in Jacobian, gradients @@ -318,7 +318,7 @@ r 4 2 4 2 4 2 -4 -16 +4 32 b 0 -1 1 0 -1 1 diff --git a/lib/coek/test/baselines/small7.nl b/lib/coek/test/baselines/small7.nl index 79de48bd..dae9e63c 100644 --- a/lib/coek/test/baselines/small7.nl +++ b/lib/coek/test/baselines/small7.nl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 24 1 0 24 0 # vars, constraints, objectives, ranges, eqns, lcons 24 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 3 0 0 # nonlinear vars in constraints, objectives, both + 3 3 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 72 1 # nonzeros in Jacobian, gradients @@ -318,7 +318,7 @@ r 4 2 4 2 4 2 -4 -16 +4 32 b 0 -1 1 0 -1 1 diff --git a/lib/coek/test/baselines/small7.ostrnl b/lib/coek/test/baselines/small7.ostrnl index 79de48bd..dae9e63c 100644 --- a/lib/coek/test/baselines/small7.ostrnl +++ b/lib/coek/test/baselines/small7.ostrnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 24 1 0 24 0 # vars, constraints, objectives, ranges, eqns, lcons 24 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 3 0 0 # nonlinear vars in constraints, objectives, both + 3 3 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 72 1 # nonzeros in Jacobian, gradients @@ -318,7 +318,7 @@ r 4 2 4 2 4 2 -4 -16 +4 32 b 0 -1 1 0 -1 1 diff --git a/lib/coek/test/baselines/small8.fmtnl b/lib/coek/test/baselines/small8.fmtnl index a26de0ae..a758751c 100644 --- a/lib/coek/test/baselines/small8.fmtnl +++ b/lib/coek/test/baselines/small8.fmtnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 3 1 0 0 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 1 0 # nonlinear vars in constraints, objectives, both + 1 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 5 3 # nonzeros in Jacobian, gradients @@ -10,16 +10,17 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o2 -v1 -v1 +v0 +v0 C1 n0 C2 n0 O0 0 o2 -v0 -v0 +v1 +v1 +x0 r 2 2 1 0 @@ -29,17 +30,17 @@ b 2 0 2 7 k2 -1 +3 4 J0 1 -1 0 +0 0 J1 2 -0 -0.5 -1 1 +0 1 +1 -0.5 J2 2 -1 -1 +0 -1 2 1 G0 3 -0 0 -1 1 +0 1 +1 0 2 1 diff --git a/lib/coek/test/baselines/small8.nl b/lib/coek/test/baselines/small8.nl index a26de0ae..a758751c 100644 --- a/lib/coek/test/baselines/small8.nl +++ b/lib/coek/test/baselines/small8.nl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 3 1 0 0 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 1 0 # nonlinear vars in constraints, objectives, both + 1 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 5 3 # nonzeros in Jacobian, gradients @@ -10,16 +10,17 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o2 -v1 -v1 +v0 +v0 C1 n0 C2 n0 O0 0 o2 -v0 -v0 +v1 +v1 +x0 r 2 2 1 0 @@ -29,17 +30,17 @@ b 2 0 2 7 k2 -1 +3 4 J0 1 -1 0 +0 0 J1 2 -0 -0.5 -1 1 +0 1 +1 -0.5 J2 2 -1 -1 +0 -1 2 1 G0 3 -0 0 -1 1 +0 1 +1 0 2 1 diff --git a/lib/coek/test/baselines/small8.ostrnl b/lib/coek/test/baselines/small8.ostrnl index a26de0ae..a758751c 100644 --- a/lib/coek/test/baselines/small8.ostrnl +++ b/lib/coek/test/baselines/small8.ostrnl @@ -2,7 +2,7 @@ g3 1 1 0 # unnamed problem generated by COEK 3 3 1 0 0 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 1 1 0 # nonlinear vars in constraints, objectives, both + 1 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) 5 3 # nonzeros in Jacobian, gradients @@ -10,16 +10,17 @@ g3 1 1 0 # unnamed problem generated by COEK 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o2 -v1 -v1 +v0 +v0 C1 n0 C2 n0 O0 0 o2 -v0 -v0 +v1 +v1 +x0 r 2 2 1 0 @@ -29,17 +30,17 @@ b 2 0 2 7 k2 -1 +3 4 J0 1 -1 0 +0 0 J1 2 -0 -0.5 -1 1 +0 1 +1 -0.5 J2 2 -1 -1 +0 -1 2 1 G0 3 -0 0 -1 1 +0 1 +1 0 2 1 diff --git a/lib/coek/test/baselines/small9.fmtnl b/lib/coek/test/baselines/small9.fmtnl index bfd418be..8c23eb99 100644 --- a/lib/coek/test/baselines/small9.fmtnl +++ b/lib/coek/test/baselines/small9.fmtnl @@ -1,11 +1,11 @@ g3 1 1 0 # unnamed problem generated by COEK 2 6 1 0 6 0 # vars, constraints, objectives, ranges, eqns, lcons - 4 0 # nonlinear constraints, objectives + 2 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 0 0 # nonlinear vars in constraints, objectives, both + 2 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) - 11 1 # nonzeros in Jacobian, gradients + 6 1 # nonzeros in Jacobian, gradients 0 0 # max name lengths: constraints, variables 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 @@ -13,11 +13,7 @@ o2 v0 v1 C1 -o2 n0 -o2 -v0 -v1 C2 n0 C3 @@ -25,15 +21,12 @@ o2 v0 v1 C4 -o2 n0 -o2 -v0 -v1 C5 n0 O0 0 n0 +x0 r 4 1 4 1 @@ -45,23 +38,16 @@ b 3 3 k1 -5 +4 J0 2 0 1 1 0 -J1 2 +J1 1 0 1 -1 0 -J2 2 +J2 1 0 1 -1 0 J3 2 0 0 1 0 -J4 2 -0 0 -1 0 -J5 1 -1 0 G0 1 0 1 diff --git a/lib/coek/test/baselines/small9.nl b/lib/coek/test/baselines/small9.nl index bfd418be..8c23eb99 100644 --- a/lib/coek/test/baselines/small9.nl +++ b/lib/coek/test/baselines/small9.nl @@ -1,11 +1,11 @@ g3 1 1 0 # unnamed problem generated by COEK 2 6 1 0 6 0 # vars, constraints, objectives, ranges, eqns, lcons - 4 0 # nonlinear constraints, objectives + 2 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 0 0 # nonlinear vars in constraints, objectives, both + 2 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) - 11 1 # nonzeros in Jacobian, gradients + 6 1 # nonzeros in Jacobian, gradients 0 0 # max name lengths: constraints, variables 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 @@ -13,11 +13,7 @@ o2 v0 v1 C1 -o2 n0 -o2 -v0 -v1 C2 n0 C3 @@ -25,15 +21,12 @@ o2 v0 v1 C4 -o2 n0 -o2 -v0 -v1 C5 n0 O0 0 n0 +x0 r 4 1 4 1 @@ -45,23 +38,16 @@ b 3 3 k1 -5 +4 J0 2 0 1 1 0 -J1 2 +J1 1 0 1 -1 0 -J2 2 +J2 1 0 1 -1 0 J3 2 0 0 1 0 -J4 2 -0 0 -1 0 -J5 1 -1 0 G0 1 0 1 diff --git a/lib/coek/test/baselines/small9.ostrnl b/lib/coek/test/baselines/small9.ostrnl index bfd418be..8c23eb99 100644 --- a/lib/coek/test/baselines/small9.ostrnl +++ b/lib/coek/test/baselines/small9.ostrnl @@ -1,11 +1,11 @@ g3 1 1 0 # unnamed problem generated by COEK 2 6 1 0 6 0 # vars, constraints, objectives, ranges, eqns, lcons - 4 0 # nonlinear constraints, objectives + 2 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 0 0 # nonlinear vars in constraints, objectives, both + 2 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) - 11 1 # nonzeros in Jacobian, gradients + 6 1 # nonzeros in Jacobian, gradients 0 0 # max name lengths: constraints, variables 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 @@ -13,11 +13,7 @@ o2 v0 v1 C1 -o2 n0 -o2 -v0 -v1 C2 n0 C3 @@ -25,15 +21,12 @@ o2 v0 v1 C4 -o2 n0 -o2 -v0 -v1 C5 n0 O0 0 n0 +x0 r 4 1 4 1 @@ -45,23 +38,16 @@ b 3 3 k1 -5 +4 J0 2 0 1 1 0 -J1 2 +J1 1 0 1 -1 0 -J2 2 +J2 1 0 1 -1 0 J3 2 0 0 1 0 -J4 2 -0 0 -1 0 -J5 1 -1 0 G0 1 0 1 diff --git a/lib/coek/test/baselines/testing1.fmtnl b/lib/coek/test/baselines/testing1.fmtnl index 3709b46d..878297f7 100644 --- a/lib/coek/test/baselines/testing1.fmtnl +++ b/lib/coek/test/baselines/testing1.fmtnl @@ -2,9 +2,9 @@ g3 1 1 0 # unnamed problem generated by COEK 4 8 1 1 5 0 # vars, constraints, objectives, ranges, eqns, lcons 3 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 0 0 # nonlinear vars in constraints, objectives, both + 2 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags - 1 1 0 2 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 1 1 0 2 2 # discrete variables: binary, integer, nonlinear (b,c,o) 14 1 # nonzeros in Jacobian, gradients 0 0 # max name lengths: constraints, variables 0 0 0 0 0 # common exprs: b,c,o,c1,o1 diff --git a/lib/coek/test/baselines/testing1.nl b/lib/coek/test/baselines/testing1.nl index 3709b46d..878297f7 100644 --- a/lib/coek/test/baselines/testing1.nl +++ b/lib/coek/test/baselines/testing1.nl @@ -2,9 +2,9 @@ g3 1 1 0 # unnamed problem generated by COEK 4 8 1 1 5 0 # vars, constraints, objectives, ranges, eqns, lcons 3 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 0 0 # nonlinear vars in constraints, objectives, both + 2 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags - 1 1 0 2 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 1 1 0 2 2 # discrete variables: binary, integer, nonlinear (b,c,o) 14 1 # nonzeros in Jacobian, gradients 0 0 # max name lengths: constraints, variables 0 0 0 0 0 # common exprs: b,c,o,c1,o1 diff --git a/lib/coek/test/baselines/testing1.ostrnl b/lib/coek/test/baselines/testing1.ostrnl index 3709b46d..878297f7 100644 --- a/lib/coek/test/baselines/testing1.ostrnl +++ b/lib/coek/test/baselines/testing1.ostrnl @@ -2,9 +2,9 @@ g3 1 1 0 # unnamed problem generated by COEK 4 8 1 1 5 0 # vars, constraints, objectives, ranges, eqns, lcons 3 0 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 0 0 # nonlinear vars in constraints, objectives, both + 2 2 0 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags - 1 1 0 2 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 1 1 0 2 2 # discrete variables: binary, integer, nonlinear (b,c,o) 14 1 # nonzeros in Jacobian, gradients 0 0 # max name lengths: constraints, variables 0 0 0 0 0 # common exprs: b,c,o,c1,o1 diff --git a/lib/coek/test/baselines/testing2.fmtnl b/lib/coek/test/baselines/testing2.fmtnl index 7328cc78..c2c0f464 100644 --- a/lib/coek/test/baselines/testing2.fmtnl +++ b/lib/coek/test/baselines/testing2.fmtnl @@ -18,17 +18,14 @@ v0 v0 v0 o54 -4 +3 o2 n-1 v0 -n2 o2 n3 v0 -o2 -n3 -n2 +n8 o41 o16 o46 @@ -38,6 +35,5 @@ x1 0 0 b 0 0 1 -k0 G0 1 0 3 diff --git a/lib/coek/test/baselines/testing2.nl b/lib/coek/test/baselines/testing2.nl index 7328cc78..c2c0f464 100644 --- a/lib/coek/test/baselines/testing2.nl +++ b/lib/coek/test/baselines/testing2.nl @@ -18,17 +18,14 @@ v0 v0 v0 o54 -4 +3 o2 n-1 v0 -n2 o2 n3 v0 -o2 -n3 -n2 +n8 o41 o16 o46 @@ -38,6 +35,5 @@ x1 0 0 b 0 0 1 -k0 G0 1 0 3 diff --git a/lib/coek/test/baselines/testing2.ostrnl b/lib/coek/test/baselines/testing2.ostrnl index 7328cc78..c2c0f464 100644 --- a/lib/coek/test/baselines/testing2.ostrnl +++ b/lib/coek/test/baselines/testing2.ostrnl @@ -18,17 +18,14 @@ v0 v0 v0 o54 -4 +3 o2 n-1 v0 -n2 o2 n3 v0 -o2 -n3 -n2 +n8 o41 o16 o46 @@ -38,6 +35,5 @@ x1 0 0 b 0 0 1 -k0 G0 1 0 3 diff --git a/lib/coek/test/baselines/testing4.fmtnl b/lib/coek/test/baselines/testing4.fmtnl index 5cf0e36a..3f802dd7 100644 --- a/lib/coek/test/baselines/testing4.fmtnl +++ b/lib/coek/test/baselines/testing4.fmtnl @@ -2,24 +2,24 @@ g3 1 1 0 # unnamed problem generated by COEK 5 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 2 1 # nonlinear vars in constraints, objectives, both + 2 3 1 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags - 1 1 1 2 2 # discrete variables: binary, integer, nonlinear (b,c,o) + 1 1 1 1 2 # discrete variables: binary, integer, nonlinear (b,c,o) 3 3 # nonzeros in Jacobian, gradients 0 0 # max name lengths: constraints, variables 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o0 o46 -v1 +v0 o46 -v2 +v1 O0 0 o0 o46 -v0 +v2 o46 -v1 +v0 x5 0 0 1 0 @@ -35,15 +35,15 @@ b 0 0 1 0 0 1 k4 -0 1 2 2 +3 J0 3 -1 0 -2 0 -4 1 -G0 3 0 0 1 0 3 1 +G0 3 +0 0 +2 0 +4 1 diff --git a/lib/coek/test/baselines/testing4.nl b/lib/coek/test/baselines/testing4.nl index 5cf0e36a..3f802dd7 100644 --- a/lib/coek/test/baselines/testing4.nl +++ b/lib/coek/test/baselines/testing4.nl @@ -2,24 +2,24 @@ g3 1 1 0 # unnamed problem generated by COEK 5 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 2 1 # nonlinear vars in constraints, objectives, both + 2 3 1 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags - 1 1 1 2 2 # discrete variables: binary, integer, nonlinear (b,c,o) + 1 1 1 1 2 # discrete variables: binary, integer, nonlinear (b,c,o) 3 3 # nonzeros in Jacobian, gradients 0 0 # max name lengths: constraints, variables 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o0 o46 -v1 +v0 o46 -v2 +v1 O0 0 o0 o46 -v0 +v2 o46 -v1 +v0 x5 0 0 1 0 @@ -35,15 +35,15 @@ b 0 0 1 0 0 1 k4 -0 1 2 2 +3 J0 3 -1 0 -2 0 -4 1 -G0 3 0 0 1 0 3 1 +G0 3 +0 0 +2 0 +4 1 diff --git a/lib/coek/test/baselines/testing4.ostrnl b/lib/coek/test/baselines/testing4.ostrnl index 5cf0e36a..3f802dd7 100644 --- a/lib/coek/test/baselines/testing4.ostrnl +++ b/lib/coek/test/baselines/testing4.ostrnl @@ -2,24 +2,24 @@ g3 1 1 0 # unnamed problem generated by COEK 5 1 1 0 1 0 # vars, constraints, objectives, ranges, eqns, lcons 1 1 # nonlinear constraints, objectives 0 0 # network constraints: nonlinear, linear - 2 2 1 # nonlinear vars in constraints, objectives, both + 2 3 1 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags - 1 1 1 2 2 # discrete variables: binary, integer, nonlinear (b,c,o) + 1 1 1 1 2 # discrete variables: binary, integer, nonlinear (b,c,o) 3 3 # nonzeros in Jacobian, gradients 0 0 # max name lengths: constraints, variables 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 o0 o46 -v1 +v0 o46 -v2 +v1 O0 0 o0 o46 -v0 +v2 o46 -v1 +v0 x5 0 0 1 0 @@ -35,15 +35,15 @@ b 0 0 1 0 0 1 k4 -0 1 2 2 +3 J0 3 -1 0 -2 0 -4 1 -G0 3 0 0 1 0 3 1 +G0 3 +0 0 +2 0 +4 1 diff --git a/lib/coek/test/baselines/testing5.fmtnl b/lib/coek/test/baselines/testing5.fmtnl index 0a2693f7..6386492e 100644 --- a/lib/coek/test/baselines/testing5.fmtnl +++ b/lib/coek/test/baselines/testing5.fmtnl @@ -14,6 +14,5 @@ x1 0 0 b 4 2 -k0 G0 1 0 1 diff --git a/lib/coek/test/baselines/testing5.nl b/lib/coek/test/baselines/testing5.nl index 0a2693f7..6386492e 100644 --- a/lib/coek/test/baselines/testing5.nl +++ b/lib/coek/test/baselines/testing5.nl @@ -14,6 +14,5 @@ x1 0 0 b 4 2 -k0 G0 1 0 1 diff --git a/lib/coek/test/baselines/testing5.ostrnl b/lib/coek/test/baselines/testing5.ostrnl index 0a2693f7..6386492e 100644 --- a/lib/coek/test/baselines/testing5.ostrnl +++ b/lib/coek/test/baselines/testing5.ostrnl @@ -14,6 +14,5 @@ x1 0 0 b 4 2 -k0 G0 1 0 1 diff --git a/lib/coek/test/baselines/testing6.fmtnl b/lib/coek/test/baselines/testing6.fmtnl new file mode 100644 index 00000000..4191d01f --- /dev/null +++ b/lib/coek/test/baselines/testing6.fmtnl @@ -0,0 +1,22 @@ +g3 1 1 0 # unnamed problem generated by COEK + 1 0 1 0 0 0 # vars, constraints, objectives, ranges, eqns, lcons + 0 1 # nonlinear constraints, objectives + 0 0 # network constraints: nonlinear, linear + 0 1 0 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 0 1 # nonzeros in Jacobian, gradients + 0 0 # max name lengths: constraints, variables + 0 0 0 0 0 # common exprs: b,c,o,c1,o1 +O0 0 +o2 +n-2 +o2 +v0 +v0 +x1 +0 0 +b +0 0 1 +G0 1 +0 0 diff --git a/lib/coek/test/baselines/testing6.nl b/lib/coek/test/baselines/testing6.nl new file mode 100644 index 00000000..4191d01f --- /dev/null +++ b/lib/coek/test/baselines/testing6.nl @@ -0,0 +1,22 @@ +g3 1 1 0 # unnamed problem generated by COEK + 1 0 1 0 0 0 # vars, constraints, objectives, ranges, eqns, lcons + 0 1 # nonlinear constraints, objectives + 0 0 # network constraints: nonlinear, linear + 0 1 0 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 0 1 # nonzeros in Jacobian, gradients + 0 0 # max name lengths: constraints, variables + 0 0 0 0 0 # common exprs: b,c,o,c1,o1 +O0 0 +o2 +n-2 +o2 +v0 +v0 +x1 +0 0 +b +0 0 1 +G0 1 +0 0 diff --git a/lib/coek/test/baselines/testing6.ostrnl b/lib/coek/test/baselines/testing6.ostrnl new file mode 100644 index 00000000..4191d01f --- /dev/null +++ b/lib/coek/test/baselines/testing6.ostrnl @@ -0,0 +1,22 @@ +g3 1 1 0 # unnamed problem generated by COEK + 1 0 1 0 0 0 # vars, constraints, objectives, ranges, eqns, lcons + 0 1 # nonlinear constraints, objectives + 0 0 # network constraints: nonlinear, linear + 0 1 0 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 0 1 # nonzeros in Jacobian, gradients + 0 0 # max name lengths: constraints, variables + 0 0 0 0 0 # common exprs: b,c,o,c1,o1 +O0 0 +o2 +n-2 +o2 +v0 +v0 +x1 +0 0 +b +0 0 1 +G0 1 +0 0 diff --git a/lib/coek/test/test_autograd_asl.cpp b/lib/coek/test/test_autograd_asl.cpp new file mode 100644 index 00000000..8f0286aa --- /dev/null +++ b/lib/coek/test/test_autograd_asl.cpp @@ -0,0 +1,1191 @@ +#include + +#include "catch2/catch.hpp" +#include "coek/ast/base_terms.hpp" +#include "coek/coek.hpp" + +const double PI = 3.141592653589793238463; +const double E = exp(1.0); + +#define ADNAME "asl" + +TEST_CASE("asl_add", "[smoke]") +{ + SECTION("error1") + { + auto v = coek::variable("v"); + coek::Model model; + model.add_objective(v); + + coek::NLPModel nlp; + REQUIRE_THROWS_WITH(nlp.initialize(model, ADNAME), + "Model expressions contain variable 'v' that is " + "not declared in the model."); + } + + SECTION("error2") + { + auto v = coek::variable("v"); + coek::Model model; + model.add_objective(2 * v); + + coek::NLPModel nlp; + REQUIRE_THROWS_WITH(nlp.initialize(model, ADNAME), + "Model expressions contain variable 'v' that is " + "not declared in the model."); + } + + SECTION("error3") + { + coek::Model model; + coek::NLPModel nlp; + REQUIRE_THROWS_WITH(nlp.initialize(model, "bad"), "Unexpected NLP model type: bad"); + } + + SECTION("Variables") + { + coek::Model model; + auto x = model.add_variable("x").lower(0).upper(1).value(0); + auto y = model.add_variable("y").lower(0).upper(1).value(0); + model.add_objective("o", x + y); + coek::NLPModel m; + m.initialize(model, ADNAME); + + REQUIRE(m.compute_f() == 0.0); + + std::vector tmp = {1.0, 2.0}; + m.set_variable_view(tmp); + + REQUIRE(m.compute_f() == 3.0); + } + + SECTION("Add Objective") + { + coek::Model model; + auto x = model.add_variable("x").lower(0).upper(1).value(0); + auto y = model.add_variable("y").lower(0).upper(1).value(0); + + model.add_objective("o", x + y); + + REQUIRE(model.num_variables() == 2); + + coek::NLPModel m; + + REQUIRE_THROWS_WITH(m.num_variables(), "Error accessing uninitialized NLPModel"); + REQUIRE_THROWS_WITH(m.num_objectives(), "Error accessing uninitialized NLPModel"); + REQUIRE_THROWS_WITH(m.num_constraints(), "Error accessing uninitialized NLPModel"); + + m.initialize(model, ADNAME); + REQUIRE(m.num_variables() == 2); + REQUIRE(m.num_objectives() == 1); + REQUIRE(m.num_constraints() == 0); + + auto o = m.get_objective(0); + REQUIRE(o.name() == "o"); + + REQUIRE_THROWS(m.get_objective(1), ""); + } + + SECTION("Add Inequality") + { + coek::Model model; + auto x = model.add_variable("x").lower(0).upper(1).value(0); + auto y = model.add_variable("y").lower(0).upper(1).value(0); + + auto e = x + y <= 0; + model.add_objective("o", x); + model.add_constraint("c", e); + + coek::NLPModel m; + m.initialize(model, ADNAME); + REQUIRE(m.num_variables() == 2); + REQUIRE(m.num_objectives() == 1); + REQUIRE(m.num_constraints() == 1); + + auto c = m.get_constraint(0); + REQUIRE(c.name() == "c"); + + REQUIRE_THROWS(m.get_constraint(1), ""); + } + + SECTION("Add Equality") + { + coek::Model model; + auto x = model.add_variable("x").lower(0).upper(1).value(0); + auto y = model.add_variable("y").lower(0).upper(1).value(0); + + auto e = x + y == 0; + model.add_objective("o", x); + model.add_constraint(e); + + coek::NLPModel m; + m.initialize(model, ADNAME); + REQUIRE(m.num_variables() == 2); + REQUIRE(m.num_objectives() == 1); + REQUIRE(m.num_constraints() == 1); + } +} + +TEST_CASE("asl_ad", "[smoke]") +{ +#if 0 + TODO - fix these tests with multiple objectives + + SECTION("f") + { + coek::Model model; + auto a = model.add_variable("a").lower(0).upper(1).value(0); + auto b = model.add_variable("b").lower(0).upper(1).value(0); + + model.add_objective(a + b); + model.add_objective(a * b); + + coek::NLPModel nlp(model, ADNAME); + + std::vector x{3, 5}; + REQUIRE(nlp.compute_f(x) == 8.0); + REQUIRE(nlp.compute_f(1) == 15.0); + REQUIRE(nlp.compute_f(x, 1) == 15.0); + + std::vector y{3, 6}; + REQUIRE(nlp.compute_f(y) == 9.0); + REQUIRE(nlp.compute_f(1) == 18.0); + REQUIRE(nlp.compute_f(y, 1) == 18.0); + } + + SECTION("df") + { + coek::Model model; + auto a = model.add_variable("a").lower(0).upper(1).value(0); + auto b = model.add_variable("b").lower(0).upper(1).value(0); + + model.add_objective(a + b); + model.add_objective(a * b); + + coek::NLPModel nlp(model, ADNAME); + + std::vector x{3, 5}; + std::vector df(2); + double f; + REQUIRE(nlp.compute_f(x) == 8.0); + + nlp.compute_df(x, df); + REQUIRE(df[0] == 1.0); + REQUIRE(df[1] == 1.0); + nlp.compute_df(df, 1); + REQUIRE(df[0] == 5.0); + REQUIRE(df[1] == 3.0); + nlp.compute_df(f, df, 1); + REQUIRE(f == 15.0); + + std::vector y{3, 6}; + REQUIRE(nlp.compute_f(y) == 9.0); + + nlp.compute_df(y, df); + REQUIRE(df[0] == 1.0); + REQUIRE(df[1] == 1.0); + nlp.compute_df(df, 1); + REQUIRE(df[0] == 6.0); + REQUIRE(df[1] == 3.0); + nlp.compute_df(f, df, 1); + REQUIRE(f == 18.0); + } +#endif + + SECTION("c") + { + coek::Model model; + auto a = model.add_variable("a"); + auto b = model.add_variable("b"); + + model.add_constraint(a + b <= 0); + model.add_constraint(a * b == 0); + + coek::NLPModel nlp(model, ADNAME); + + std::vector x{3, 5}; + std::vector c(2); + nlp.compute_c(x, c); + REQUIRE(c[0] == 8.0); + REQUIRE(c[1] == 15.0); + nlp.compute_c(c); + REQUIRE(c[0] == 8.0); + REQUIRE(c[1] == 15.0); + + std::vector y{3, 6}; + nlp.compute_c(y, c); + REQUIRE(c[0] == 9.0); + REQUIRE(c[1] == 18.0); + nlp.compute_c(c); + REQUIRE(c[0] == 9.0); + REQUIRE(c[1] == 18.0); + } + + SECTION("dc") + { + coek::Model model; + auto a = model.add_variable("a"); + auto b = model.add_variable("b"); + + model.add_constraint(a + b <= 0); + model.add_constraint(a * b == 0); + + coek::NLPModel nlp(model, ADNAME); + + std::vector x{3, 5}; + std::vector dc(2); + nlp.compute_dc(x, dc, 0); + REQUIRE(dc[0] == 1.0); + REQUIRE(dc[1] == 1.0); + nlp.compute_dc(dc, 1); + REQUIRE(dc[0] == 5.0); + REQUIRE(dc[1] == 3.0); + nlp.compute_dc(x, dc, 1); + REQUIRE(dc[0] == 5.0); + REQUIRE(dc[1] == 3.0); + + std::vector y{3, 6}; + nlp.compute_dc(y, dc, 0); + REQUIRE(dc[0] == 1.0); + REQUIRE(dc[1] == 1.0); + nlp.compute_dc(dc, 1); + REQUIRE(dc[0] == 6.0); + REQUIRE(dc[1] == 3.0); + nlp.compute_dc(y, dc, 1); + REQUIRE(dc[0] == 6.0); + REQUIRE(dc[1] == 3.0); + } + + SECTION("sparse_j") + { + WHEN("nx < nc") + { + coek::Model model; + auto a = model.add_variable("a"); + auto b = model.add_variable("b"); + + model.add_objective(a); + model.add_constraint(a <= 0); + model.add_constraint(a * b <= 0); + model.add_constraint(b <= 0); + + coek::NLPModel nlp(model, ADNAME); + REQUIRE(nlp.num_nonzeros_Jacobian() == 4); + + std::vector x{0, 1}; + std::vector j(nlp.num_nonzeros_Jacobian()); + nlp.compute_J(x, j); + REQUIRE(j[0] == 1); + REQUIRE(j[1] == 1); + REQUIRE(j[2] == 0); + REQUIRE(j[3] == 1); + } + + WHEN("nx > nc") + { + coek::Model model; + auto a = model.add_variable("a"); + auto b = model.add_variable("b"); + auto c = model.add_variable("c"); + auto d = model.add_variable("d"); + + model.add_objective(d); + model.add_constraint(a + a * b + b <= 0); + model.add_constraint(b + b * c + c <= 0); + + coek::NLPModel nlp(model, ADNAME); + REQUIRE(nlp.num_nonzeros_Jacobian() == 4); + + std::vector x{0, 1, 2, 3}; + std::vector j(nlp.num_nonzeros_Jacobian()); + nlp.compute_J(x, j); + REQUIRE(j[0] == 2); + REQUIRE(j[1] == 1); + REQUIRE(j[2] == 3); + REQUIRE(j[3] == 2); + } + } + + SECTION("sparse_h") + { + WHEN("nx < nc") + { + coek::Model model; + auto a = model.add_variable("a"); + auto b = model.add_variable("b"); + + model.add_objective(a * a + b); + model.add_constraint(a <= 0); + model.add_constraint(a * b <= 0); + model.add_constraint(b <= 0); + + coek::NLPModel nlp(model, ADNAME); + REQUIRE(nlp.num_nonzeros_Hessian_Lagrangian() == 2); + + // H = [ [ 2, 1 ] + // [ 1, 0 ] ] + std::vector w{1, 1, 1, 1}; + std::vector x{0, 1}; + std::vector h(nlp.num_nonzeros_Hessian_Lagrangian()); + nlp.compute_H(x, w, h); + REQUIRE(h[0] == 2); + REQUIRE(h[1] == 1); + } + + WHEN("nx > nc") + { + coek::Model model; + auto a = model.add_variable("a"); + auto b = model.add_variable("b"); + auto c = model.add_variable("c"); + auto d = model.add_variable("d"); + + model.add_objective(d * d * c * c); + model.add_constraint(a + a * b + b + a * d <= 0); + model.add_constraint(b + b * c + c + b * d <= 0); + + coek::NLPModel nlp(model, ADNAME); + REQUIRE(nlp.num_constraints() == 2); + REQUIRE(nlp.num_nonzeros_Jacobian() == 6); + REQUIRE(nlp.num_nonzeros_Hessian_Lagrangian() == 7); + + // Variable Ordering: c, d, a, b + // + // H = [ [ 2d^2, 4cd, 0, 1 ] + // [ 4cd, 2c^2, 1, 1 ] + // [ 0, 1, 0, 1 ] + // [ 1, 1, 1, 0 ] ] + // + // h = [ [ 0, 1, 0, 1 ] + // [ 1, 0, 1, 1 ] + // [ 0, 1, 2d^2, 4cd ] + // [ 1, 1, 4cd, 2c^2 ] ] + std::vector w{1, 1, 1}; + std::vector x{2, 3, 0, 1}; + std::vector h(nlp.num_nonzeros_Hessian_Lagrangian()); + nlp.compute_H(x, w, h); + REQUIRE(h[0] == 18); + REQUIRE(h[1] == 24); + REQUIRE(h[2] == 8); + REQUIRE(h[3] == 1); + REQUIRE(h[4] == 1); + REQUIRE(h[5] == 1); + REQUIRE(h[6] == 1); + } + + WHEN("nx > nc weighted") + { + coek::Model model; + auto a = model.add_variable("a"); + auto b = model.add_variable("b"); + auto c = model.add_variable("c"); + auto d = model.add_variable("d"); + + model.add_objective(d * c + c + b * b + c * c); + model.add_constraint(a + a * b <= 0); + + coek::NLPModel nlp(model, ADNAME); + REQUIRE(nlp.num_constraints() == 1); + REQUIRE(nlp.num_nonzeros_Hessian_Lagrangian() == 4); + // Variable Ordering: b, a, c, d + + // H = [ [ 2, 9, 0, 0 ] + // [ 9, 0, 0, 0 ] + // [ 0, 0, 2, 1 ] + // [ 0, 0, 1, 0 ] ] + // + // h = [ [ 0, 9, 0, 0 ] + // [ 9, 2, 0, 0 ] + // [ 0, 0, 2, 1 ] + // [ 0, 0, 1, 0 ] ] + std::vector w{1, 9}; + std::vector x{1, 0, 2, 3}; + std::vector h(nlp.num_nonzeros_Hessian_Lagrangian()); + nlp.compute_H(x, w, h); + REQUIRE(h[0] == 2); + REQUIRE(h[1] == 9); + REQUIRE(h[2] == 2); + REQUIRE(h[3] == 1); + } + + WHEN("other 1") + { + coek::Model model; + auto a = model.add_variable("a").lower(0.1).upper(100).value(1); + auto b = model.add_variable("b").lower(0.1).upper(100).value(2); + model.add_objective(pow(b - pow(a, 2), 2) + pow(a - 1, 2)); + + coek::NLPModel nlp(model, ADNAME); + REQUIRE(nlp.num_nonzeros_Hessian_Lagrangian() == 3); + + // H = [ [ -4b+12a^2+2, -4a] + // [ -4a, 2 ] ] + std::vector h(nlp.num_nonzeros_Hessian_Lagrangian()); + std::vector w{1}; + std::vector x{1, 2}; + nlp.compute_H(x, w, h); + REQUIRE(h[0] == 6); + REQUIRE(h[1] == -4); + REQUIRE(h[2] == 2); + } + } +} + +TEST_CASE("asl_diff_tests", "[smoke]") +{ + // TODO - test constant expression + + SECTION("constant") + { + coek::Model model; + coek::Expression f(3); + auto v = model.add_variable("v"); + model.add_objective(f * v); + coek::NLPModel nlp(model, ADNAME); + + std::vector x{0}; + std::vector baseline{3}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + + SECTION("param") + { + WHEN("simple") + { + coek::Model model; + auto p = coek::parameter().value(3); + coek::Expression f = p; + auto v = model.add_variable("v"); + model.add_objective(f * v); + coek::NLPModel nlp(model, ADNAME); + + std::vector x{0}; + std::vector baseline{3}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + + p.value(4); + nlp.reset(); + std::vector baseline2{4}; + nlp.compute_df(x, ans); + REQUIRE(ans == baseline2); + } + WHEN("many") + { + coek::Model model; + auto p1 = coek::parameter().value(1); + auto p2 = coek::parameter().value(2); + auto p3 = coek::parameter().value(3); + auto p4 = coek::parameter().value(4); + auto p5 = coek::parameter().value(5); + coek::Expression f = p1 + 2 * p2 + 3 * p3 + 4 * p4 + 5 * p5; + auto v = model.add_variable("v"); + model.add_objective(f * v); + coek::NLPModel nlp(model, ADNAME); + + std::vector x{0}; + std::vector baseline{1 + 4 + 9 + 16 + 25}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + + p5.value(10); + nlp.reset(); + std::vector baseline2{1 + 4 + 9 + 16 + 50}; + nlp.compute_df(x, ans); + REQUIRE(ans == baseline2); + } + } + + SECTION("var") + { + WHEN("fixed") + { + coek::Model model; + auto v = model.add_variable("v").value(0); + auto w = model.add_variable("w").value(0); + v.fixed(true); + coek::Expression f = v + 2 * w; + model.add_objective(f); + coek::NLPModel nlp(model, ADNAME); + + std::vector x{0}; + std::vector baseline{2}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + +#if 0 + TODO - Fix this test - multiple objectives + + WHEN("unfixed") + { + coek::Model model; + auto v = model.add_variable("v"); + auto w = model.add_variable("w"); + coek::Expression f = v; + model.add_objective(f); + model.add_objective(w); + coek::NLPModel nlp(model, ADNAME); + + std::vector x{0, 0}; + std::vector baseline{1, 0}; + std::vector ans(2); + nlp.compute_df(x, ans, 0); + REQUIRE(ans == baseline); + nlp.compute_df(x, ans, 1); + std::vector baseline2{0, 1}; + REQUIRE(ans == baseline2); + } +#endif + } + + SECTION("monomial") + { + WHEN("other") + { + coek::Model m; + auto v = m.add_variable("v"); + auto w = m.add_variable("w"); + coek::Expression f = 2 * v; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0}; + std::vector baseline{2}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + + WHEN("fixed") + { + coek::Model m; + auto v = m.add_variable("v").value(0); + auto w = m.add_variable("w").value(0); + v.fixed(true); + coek::Expression f = 2 * v; + m.add_objective(f + 3 * w); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0}; + std::vector baseline{3}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + } + + SECTION("plus") + { + WHEN("linear") + { + coek::Model m; + auto p = coek::parameter(); + auto v = m.add_variable("v"); + coek::Expression f = 2 * (v + v) + v; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0}; + std::vector baseline{5}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("simple") + { + coek::Model m; + auto p = coek::parameter(); + auto v = m.add_variable("v"); + coek::Expression f = 3 * p + 2 * v; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0}; + std::vector baseline{2}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("multiple") + { + coek::Model m; + auto v = m.add_variable("v"); + coek::Expression f = 7 * v + v; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0}; + std::vector baseline{8}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + } + + SECTION("negate") + { + WHEN("linear") + { + coek::Model m; + auto v = m.add_variable("v"); + coek::Expression f = -(v + 1); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0}; + std::vector baseline{-1}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + } + + SECTION("times") + { + WHEN("lhs zero") + { + coek::Model m; + coek::Expression p; + auto v = m.add_variable("v"); + coek::Expression f = v + p * v; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0}; + std::vector baseline{1}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("lhs constant") + { + coek::Model m; + auto p = coek::parameter("p").value(2); + auto v = m.add_variable("v"); + coek::Expression f = p * v; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0}; + std::vector baseline{2}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("rhs zero") + { + coek::Model m; + coek::Expression p; + auto v = m.add_variable("v"); + coek::Expression f = v + v * p; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0}; + std::vector baseline{1}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("rhs constant") + { + coek::Model m; + auto p = coek::parameter("p").value(2); + auto v = m.add_variable("v"); + coek::Expression f = v * p; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0}; + std::vector baseline{2}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("simple quadratic") + { + coek::Model m; + auto v = m.add_variable("v"); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = v * w; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2, 3}; + std::vector baseline{3, 2}; + std::vector ans(2); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + } + + SECTION("divide") + { + WHEN("lhs zero parameter") + { + coek::Model m; + auto p = coek::parameter("p"); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = w + (2 * p) / w; + m.add_objective(f); + m.add_constraint(2 * w <= 0); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{1}; + std::vector ans{999.0}; + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("lhs zero fixed-variable") + { + coek::Model m; + auto p = m.add_variable("p").lower(0).upper(1).value(0).fixed(true); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = w + (2 * p) / w; + m.add_objective(f); + m.add_constraint(2 * w <= 0); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{1}; + std::vector ans{999.0}; + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("lhs zero subexpression") + { + coek::Model m; + auto p = coek::subexpression(); + auto w = m.add_variable("W").lower(0).upper(1).value(0); + coek::Expression f = w + (2 * p) / w; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0}; + std::vector baseline{1}; + std::vector ans{999.0}; + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("rhs nonzero") + { + coek::Model m; + auto p = coek::parameter("p").value(2); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = w / p; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{1}; + std::vector baseline{0.5}; + std::vector ans{999.0}; + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("rhs polynomial") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = w / (1 + w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{1}; + std::vector baseline{0.25}; + std::vector ans{999.0}; + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + } + + SECTION("coverage") + { + WHEN("variable partial plus monomial - 1") + { + coek::Model m; + auto v = m.add_variable("v"); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = w * v + v * (2 * w + 1); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2, 3}; + std::vector baseline{10, 6}; + std::vector ans(2); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("variable partial plus monomial - 2") + { + coek::Model m; + auto v = m.add_variable("v"); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = v * (2 * w + 1); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2, 3}; + std::vector baseline{7, 4}; + std::vector ans(2); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("constant partial plus monomial") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = 3 * w + 2 * w; + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{5}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("negative monomial") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = -(-w) + (-(-w)); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{2}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + WHEN("shared subexpr") + { + coek::Model m; + auto v = m.add_variable("v"); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = v + 2 * w; + m.add_objective(2 * f + 3 * f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{10, 11}; + std::vector baseline{5, 10}; + std::vector ans(2); + nlp.compute_df(x, ans); + REQUIRE(ans == baseline); + } + } + + SECTION("intrinsic funcs") + { + WHEN("exp") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = abs(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{-1}; + std::vector baseline{-2}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + } + // REPN_INTRINSIC_TEST1(ceil) + // REPN_INTRINSIC_TEST1(floor) + WHEN("exp") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = exp(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{1}; + std::vector baseline{2 * pow(E, 2.0)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + } + WHEN("log") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = log(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{0.5}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + } + WHEN("log10") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = log10(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{0.5 / log(10.0)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + } + WHEN("sqrt") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = sqrt(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{0.5}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + } + WHEN("sin") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = sin(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{2 * cos(4)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + } + WHEN("cos") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = cos(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{-2 * sin(4)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + } + WHEN("tan") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = tan(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{2 / pow(cos(4), 2)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + } + WHEN("sinh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = sinh(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{2 * cosh(4)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + // static std::list baseline = { "[", "*", "2.000", "[", "cosh", "[", "*", + // "2", "w", "]", "]", "]" }; + } + WHEN("cosh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = cosh(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{2 * sinh(4)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + // static std::list baseline = { "[", "*", "2.000", "[", "sinh", "[", "*", + // "2", "w", "]", "]", "]" }; + } + WHEN("tanh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = tanh(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{2 * (1 - pow(tanh(4), 2))}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + // static std::list baseline = { "[", "*", "2.000", "[", "+", "1.000", "[", + // "*", "-1.000", "[", "pow", "[", "tan", "[", "*", "2", "w", "]", "]", "2.000", "]", + // "]", "]", "]" }; + } + WHEN("asin") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = asin(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0.25}; + std::vector baseline{2 / sqrt(3.0 / 4.0)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + // static std::list baseline = { "[", "*", "2.000", "[", "/", "1.000", "[", + // "sqrt", "[", "+", "1.000", "[", "-", "[", "*", "[", "*", "2", "w", "]", "[", "*", + // "2", "w", "]", "]", "]", "]", "]", "]", "]" }; + } + WHEN("acos") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = acos(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0.25}; + std::vector baseline{-2 / sqrt(3.0 / 4.0)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + + // static std::list baseline = { "[", "*", "2.000", "[", "-", "[", "/", + // "1.000", "[", "sqrt", "[", "+", "1.000", "[", "-", "[", "*", "[", "*", "2", "w", "]", + // "[", "*", "2", "w", "]", "]", "]", "]", "]", "]", "]", "]" }; + } + WHEN("atan") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = atan(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0.25}; + std::vector baseline{2 / (5.0 / 4.0)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + // static std::list baseline = { "[", "*", "2.000", "[", "/", "1.000", "[", + // "+", "1.000", "[", "*", "[", "*", "2", "w", "]", "[", "*", "2", "w", "]", "]", "]", + // "]", "]" }; + } + WHEN("asinh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = asinh(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0.25}; + std::vector baseline{2 / sqrt(5.0 / 4.0)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + + // static std::list baseline = { "[", "*", "2.000", "[", "/", "1.000", "[", + // "sqrt", "[", "+", "1.000", "[", "*", "[", "*", "2", "w", "]", "[", "*", "2", "w", + // "]", "]", "]", "]", "]", "]" }; + } + WHEN("acosh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = acosh(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{1}; + std::vector baseline{2 / sqrt(3.0)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + + // static std::list baseline = { "[", "*", "2.000", "[", "/", "1.000", "[", + // "sqrt", "[", "+", "[", "*", "[", "*", "2", "w", "]", "[", "*", "2", "w", "]", "]", + // "-1.000", "]", "]", "]", "]" }; + } + WHEN("atanh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = atanh(2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{0.25}; + std::vector baseline{2 / (3.0 / 4.0)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + + // static std::list baseline = { "[", "*", "2.000", "[", "/", "1.000", "[", + // "+", "[", "*", "[", "*", "2", "w", "]", "[", "*", "2", "w", "]", "]", "-1.000", "]", + // "]", "]" }; + } + WHEN("pow - 1") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = pow(w, 3); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{3 * 4}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + + // static std::list baseline = { "[", "*", "2.000", "[", "*", "3.000", "[", + // "pow", "[", "*", "2", "w", "]", "[", "+", "3.000", "-1.000", "]", "]", "]", "]" }; + } + WHEN("pow - 2") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = pow(3, 2 * w); + m.add_objective(f); + coek::NLPModel nlp(m, ADNAME); + + std::vector x{2}; + std::vector baseline{2 * log(3) * pow(3, 4)}; + std::vector ans(1); + nlp.compute_df(x, ans); + REQUIRE(ans[0] == Approx(baseline[0])); + + // static std::list baseline = { "[", "*", "2.000", "[", "*", "1.099", "[", + // "pow", "3.000", "[", "*", "2", "w", "]", "]", "]", "]" }; + } + } +} diff --git a/lib/coek/test/test_autograd_cppad.cpp b/lib/coek/test/test_autograd_cppad.cpp index fa95075f..ab748c29 100644 --- a/lib/coek/test/test_autograd_cppad.cpp +++ b/lib/coek/test/test_autograd_cppad.cpp @@ -7,6 +7,8 @@ const double PI = 3.141592653589793238463; const double E = exp(1.0); +#define ADNAME "cppad" + TEST_CASE("cppad_add", "[smoke]") { SECTION("error1") @@ -17,7 +19,7 @@ TEST_CASE("cppad_add", "[smoke]") coek::NLPModel nlp; REQUIRE_THROWS_WITH( - nlp.initialize(model, "cppad"), + nlp.initialize(model, ADNAME), "Model expressions contain variable 'v' that is not declared in the model."); } @@ -29,7 +31,7 @@ TEST_CASE("cppad_add", "[smoke]") coek::NLPModel nlp; REQUIRE_THROWS_WITH( - nlp.initialize(model, "cppad"), + nlp.initialize(model, ADNAME), "Model expressions contain variable 'v' that is not declared in the model."); } @@ -47,17 +49,14 @@ TEST_CASE("cppad_add", "[smoke]") auto y = model.add_variable("y").lower(0).upper(1).value(0); model.add_objective("o", x + y); coek::NLPModel m; - m.initialize(model, "cppad"); + m.initialize(model, ADNAME); - WHEN("Set vector of values") - { - REQUIRE(m.compute_f() == 0.0); + REQUIRE(m.compute_f() == 0.0); - std::vector tmp = {1.0, 2.0}; - m.set_variable_view(tmp); + std::vector tmp = {1.0, 2.0}; + m.set_variable_view(tmp); - REQUIRE(m.compute_f() == 3.0); - } + REQUIRE(m.compute_f() == 3.0); } SECTION("Add Objective") @@ -76,7 +75,7 @@ TEST_CASE("cppad_add", "[smoke]") REQUIRE_THROWS_WITH(m.num_objectives(), "Error accessing uninitialized NLPModel"); REQUIRE_THROWS_WITH(m.num_constraints(), "Error accessing uninitialized NLPModel"); - m.initialize(model, "cppad"); + m.initialize(model, ADNAME); REQUIRE(m.num_variables() == 2); REQUIRE(m.num_objectives() == 1); REQUIRE(m.num_constraints() == 0); @@ -94,12 +93,13 @@ TEST_CASE("cppad_add", "[smoke]") auto y = model.add_variable("y").lower(0).upper(1).value(0); auto e = x + y <= 0; + model.add_objective("o", x); model.add_constraint("c", e); coek::NLPModel m; - m.initialize(model, "cppad"); + m.initialize(model, ADNAME); REQUIRE(m.num_variables() == 2); - REQUIRE(m.num_objectives() == 0); + REQUIRE(m.num_objectives() == 1); REQUIRE(m.num_constraints() == 1); auto c = m.get_constraint(0); @@ -115,12 +115,13 @@ TEST_CASE("cppad_add", "[smoke]") auto y = model.add_variable("y").lower(0).upper(1).value(0); auto e = x + y == 0; + model.add_objective("o", x); model.add_constraint(e); coek::NLPModel m; - m.initialize(model, "cppad"); + m.initialize(model, ADNAME); REQUIRE(m.num_variables() == 2); - REQUIRE(m.num_objectives() == 0); + REQUIRE(m.num_objectives() == 1); REQUIRE(m.num_constraints() == 1); } } @@ -136,7 +137,7 @@ TEST_CASE("cppad_ad", "[smoke]") model.add_objective(a + b); model.add_objective(a * b); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); std::vector x{3, 5}; REQUIRE(nlp.compute_f(x) == 8.0); @@ -158,7 +159,7 @@ TEST_CASE("cppad_ad", "[smoke]") model.add_objective(a + b); model.add_objective(a * b); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); std::vector x{3, 5}; std::vector df(2); @@ -196,7 +197,7 @@ TEST_CASE("cppad_ad", "[smoke]") model.add_constraint(a + b <= 0); model.add_constraint(a * b == 0); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); std::vector x{3, 5}; std::vector c(2); @@ -225,7 +226,7 @@ TEST_CASE("cppad_ad", "[smoke]") model.add_constraint(a + b <= 0); model.add_constraint(a * b == 0); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); std::vector x{3, 5}; std::vector dc(2); @@ -264,7 +265,7 @@ TEST_CASE("cppad_ad", "[smoke]") model.add_constraint(a * b <= 0); model.add_constraint(b <= 0); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); REQUIRE(nlp.num_nonzeros_Jacobian() == 4); std::vector x{0, 1}; @@ -288,7 +289,7 @@ TEST_CASE("cppad_ad", "[smoke]") model.add_constraint(a + a * b + b <= 0); model.add_constraint(b + b * c + c <= 0); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); REQUIRE(nlp.num_nonzeros_Jacobian() == 4); std::vector x{0, 1, 2, 3}; @@ -314,7 +315,7 @@ TEST_CASE("cppad_ad", "[smoke]") model.add_constraint(a * b <= 0); model.add_constraint(b <= 0); - coek::NLPModel nlp(model, "cppad", false); + coek::NLPModel nlp(model, ADNAME, false); REQUIRE(nlp.num_nonzeros_Jacobian() == 6); std::vector x{0, 1}; @@ -339,7 +340,7 @@ TEST_CASE("cppad_ad", "[smoke]") model.add_constraint(a + a * b + b <= 0); model.add_constraint(b + b * c + c <= 0); - coek::NLPModel nlp(model, "cppad", false); + coek::NLPModel nlp(model, ADNAME, false); REQUIRE(nlp.num_nonzeros_Jacobian() == 6); std::vector x{0, 1, 2}; @@ -367,7 +368,7 @@ TEST_CASE("cppad_ad", "[smoke]") model.add_constraint(a * b <= 0); model.add_constraint(b <= 0); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); REQUIRE(nlp.num_nonzeros_Hessian_Lagrangian() == 2); // H = [ [ 2, 1 ] @@ -388,25 +389,32 @@ TEST_CASE("cppad_ad", "[smoke]") auto c = model.add_variable("c"); auto d = model.add_variable("d"); - model.add_objective(d * d * c); - model.add_constraint(a + a * b + b <= 0); - model.add_constraint(b + b * c + c <= 0); + model.add_objective(d * d * c * c); + model.add_constraint(a + a * b + b + a * d <= 0); + model.add_constraint(b + b * c + c + b * d <= 0); - coek::NLPModel nlp(model, "cppad"); - REQUIRE(nlp.num_nonzeros_Jacobian() == 4); - - // H = [ [ 0, 1, 0, 0 ] - // [ 1, 0, 1, 0 ] - // [ 0, 1, 0, 2d ] - // [ 0, 0, 2d, 2c ] ] + coek::NLPModel nlp(model, ADNAME); + REQUIRE(nlp.num_constraints() == 2); + REQUIRE(nlp.num_nonzeros_Jacobian() == 6); + REQUIRE(nlp.num_nonzeros_Hessian_Lagrangian() == 7); + + // Variable Ordering: a, b, c, d + // + // h = [ [ 0, 1, 0, 1 ] + // [ 1, 0, 1, 1 ] + // [ 0, 1, 2d^2, 4cd ] + // [ 1, 1, 4cd, 2c^2 ] ] std::vector w{1, 1, 1}; std::vector x{0, 1, 2, 3}; std::vector h(nlp.num_nonzeros_Hessian_Lagrangian()); nlp.compute_H(x, w, h); REQUIRE(h[0] == 1); REQUIRE(h[1] == 1); - REQUIRE(h[2] == 6); - REQUIRE(h[3] == 4); + REQUIRE(h[2] == 18); + REQUIRE(h[3] == 1); + REQUIRE(h[4] == 1); + REQUIRE(h[5] == 24); + REQUIRE(h[6] == 8); } WHEN("nx > nc weighted") @@ -417,25 +425,27 @@ TEST_CASE("cppad_ad", "[smoke]") auto c = model.add_variable("c"); auto d = model.add_variable("d"); - model.add_objective(d * d * c); - model.add_constraint(a + a * b + b <= 0); - model.add_constraint(b + b * c + c <= 0); - - coek::NLPModel nlp(model, "cppad"); - REQUIRE(nlp.num_nonzeros_Jacobian() == 4); - - // H = [ [ 0, 2, 0, 0 ] - // [ 2, 0, 3, 0 ] - // [ 0, 3, 0, 2d ] - // [ 0, 0, 2d, 2c ] ] - std::vector w{1, 2, 3}; + model.add_objective(d * c + c + b * b + c * c); + model.add_constraint(a + a * b <= 0); + + coek::NLPModel nlp(model, ADNAME); + REQUIRE(nlp.num_constraints() == 1); + REQUIRE(nlp.num_nonzeros_Hessian_Lagrangian() == 4); + // Variable Ordering: a, b, c, d + // + // h = [ [ 0, 9, 0, 0 ] + // [ 9, 2, 0, 0 ] + // [ 0, 0, 2, 1 ] + // [ 0, 0, 1, 0 ] ] + // + std::vector w{1, 9}; std::vector x{0, 1, 2, 3}; std::vector h(nlp.num_nonzeros_Hessian_Lagrangian()); nlp.compute_H(x, w, h); - REQUIRE(h[0] == 2); - REQUIRE(h[1] == 3); - REQUIRE(h[2] == 6); - REQUIRE(h[3] == 4); + REQUIRE(h[0] == 9); + REQUIRE(h[1] == 2); + REQUIRE(h[2] == 2); + REQUIRE(h[3] == 1); } WHEN("other 1") @@ -445,7 +455,8 @@ TEST_CASE("cppad_ad", "[smoke]") auto b = model.add_variable("b").lower(0.1).upper(100).value(2); model.add_objective(pow(b - pow(a, 2), 2) + pow(a - 1, 2)); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); + REQUIRE(nlp.num_nonzeros_Hessian_Lagrangian() == 3); // H = [ [ -4b+12a^2+2, -4a] // [ -4a, 2 ] ] @@ -472,7 +483,7 @@ TEST_CASE("cppad_ad", "[smoke]") model.add_constraint(a * b <= 0); model.add_constraint(b <= 0); - coek::NLPModel nlp(model, "cppad", false); + coek::NLPModel nlp(model, ADNAME, false); REQUIRE(nlp.num_nonzeros_Hessian_Lagrangian() == 3); // H = [ [ 2, 1 ] @@ -498,7 +509,7 @@ TEST_CASE("cppad_ad", "[smoke]") model.add_constraint(a + a * b + b <= 0); model.add_constraint(b + b * c + c <= 0); - coek::NLPModel nlp(model, "cppad", false); + coek::NLPModel nlp(model, ADNAME, false); REQUIRE(nlp.num_nonzeros_Hessian_Lagrangian() == 10); // H = [ [ 0, 1, 0, 0 ] @@ -523,7 +534,7 @@ TEST_CASE("cppad_ad", "[smoke]") } } -TEST_CASE("diff_tests", "[smoke]") +TEST_CASE("cppad_diff_tests", "[smoke]") { // TODO - test constant expression @@ -533,7 +544,7 @@ TEST_CASE("diff_tests", "[smoke]") coek::Expression f(3); auto v = model.add_variable("v"); model.add_objective(f * v); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); std::vector x{0}; std::vector baseline{3}; @@ -551,7 +562,7 @@ TEST_CASE("diff_tests", "[smoke]") coek::Expression f = p; auto v = model.add_variable("v"); model.add_objective(f * v); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); std::vector x{0}; std::vector baseline{3}; @@ -576,7 +587,7 @@ TEST_CASE("diff_tests", "[smoke]") coek::Expression f = p1 + 2 * p2 + 3 * p3 + 4 * p4 + 5 * p5; auto v = model.add_variable("v"); model.add_objective(f * v); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); std::vector x{0}; std::vector baseline{1 + 4 + 9 + 16 + 25}; @@ -602,7 +613,7 @@ TEST_CASE("diff_tests", "[smoke]") v.fixed(true); coek::Expression f = v + 2 * w; model.add_objective(f); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); std::vector x{0}; std::vector baseline{2}; @@ -619,7 +630,7 @@ TEST_CASE("diff_tests", "[smoke]") coek::Expression f = v; model.add_objective(f); model.add_objective(w); - coek::NLPModel nlp(model, "cppad"); + coek::NLPModel nlp(model, ADNAME); std::vector x{0, 0}; std::vector baseline{1, 0}; @@ -641,7 +652,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w"); coek::Expression f = 2 * v; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0}; std::vector baseline{2}; @@ -658,7 +669,7 @@ TEST_CASE("diff_tests", "[smoke]") v.fixed(true); coek::Expression f = 2 * v; m.add_objective(f + 3 * w); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0}; std::vector baseline{3}; @@ -677,7 +688,7 @@ TEST_CASE("diff_tests", "[smoke]") auto v = m.add_variable("v"); coek::Expression f = 2 * (v + v) + v; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0}; std::vector baseline{5}; @@ -692,7 +703,7 @@ TEST_CASE("diff_tests", "[smoke]") auto v = m.add_variable("v"); coek::Expression f = 3 * p + 2 * v; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0}; std::vector baseline{2}; @@ -706,7 +717,7 @@ TEST_CASE("diff_tests", "[smoke]") auto v = m.add_variable("v"); coek::Expression f = 7 * v + v; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0}; std::vector baseline{8}; @@ -724,7 +735,7 @@ TEST_CASE("diff_tests", "[smoke]") auto v = m.add_variable("v"); coek::Expression f = -(v + 1); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0}; std::vector baseline{-1}; @@ -741,12 +752,12 @@ TEST_CASE("diff_tests", "[smoke]") coek::Model m; coek::Expression p; auto v = m.add_variable("v"); - coek::Expression f = p * v; + coek::Expression f = v + p * v; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0}; - std::vector baseline{0}; + std::vector baseline{1}; std::vector ans(1); nlp.compute_df(x, ans); REQUIRE(ans == baseline); @@ -758,7 +769,7 @@ TEST_CASE("diff_tests", "[smoke]") auto v = m.add_variable("v"); coek::Expression f = p * v; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0}; std::vector baseline{2}; @@ -771,12 +782,12 @@ TEST_CASE("diff_tests", "[smoke]") coek::Model m; coek::Expression p; auto v = m.add_variable("v"); - coek::Expression f = v * p; + coek::Expression f = v + v * p; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0}; - std::vector baseline{0}; + std::vector baseline{1}; std::vector ans(1); nlp.compute_df(x, ans); REQUIRE(ans == baseline); @@ -788,7 +799,7 @@ TEST_CASE("diff_tests", "[smoke]") auto v = m.add_variable("v"); coek::Expression f = v * p; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0}; std::vector baseline{2}; @@ -803,7 +814,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = v * w; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2, 3}; std::vector baseline{3, 2}; @@ -820,12 +831,13 @@ TEST_CASE("diff_tests", "[smoke]") coek::Model m; auto p = coek::parameter("p"); auto w = m.add_variable("w").lower(0).upper(1).value(0); - coek::Expression f = (2 * p) / w; + coek::Expression f = w + (2 * p) / w; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + m.add_constraint(2 * w <= 0); + coek::NLPModel nlp(m, ADNAME); - std::vector x{2}; // TODO - Check if this should work when x==0 - std::vector baseline{0}; + std::vector x{2}; + std::vector baseline{1}; std::vector ans{999.0}; nlp.compute_df(x, ans); REQUIRE(ans == baseline); @@ -835,12 +847,13 @@ TEST_CASE("diff_tests", "[smoke]") coek::Model m; auto p = m.add_variable("p").lower(0).upper(1).value(0).fixed(true); auto w = m.add_variable("w").lower(0).upper(1).value(0); - coek::Expression f = (2 * p) / w; + coek::Expression f = w + (2 * p) / w; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + m.add_constraint(2 * w <= 0); + coek::NLPModel nlp(m, ADNAME); - std::vector x{2}; // TODO - Check if this should work when x==0 - std::vector baseline{0}; + std::vector x{2}; + std::vector baseline{1}; std::vector ans{999.0}; nlp.compute_df(x, ans); REQUIRE(ans == baseline); @@ -850,12 +863,12 @@ TEST_CASE("diff_tests", "[smoke]") coek::Model m; auto p = coek::subexpression(); auto w = m.add_variable("W").lower(0).upper(1).value(0); - coek::Expression f = (2 * p) / w; + coek::Expression f = w + (2 * p) / w; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0}; - std::vector baseline{0}; + std::vector baseline{1}; std::vector ans{999.0}; nlp.compute_df(x, ans); REQUIRE(ans == baseline); @@ -867,7 +880,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = w / p; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{1}; std::vector baseline{0.5}; @@ -881,7 +894,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = w / (1 + w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{1}; std::vector baseline{0.25}; @@ -900,7 +913,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = w * v + v * (2 * w + 1); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2, 3}; std::vector baseline{10, 6}; @@ -915,7 +928,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = v * (2 * w + 1); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2, 3}; std::vector baseline{7, 4}; @@ -929,7 +942,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = 3 * w + 2 * w; m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{5}; @@ -943,7 +956,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = -(-w) + (-(-w)); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{2}; @@ -958,7 +971,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = v + 2 * w; m.add_objective(2 * f + 3 * f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{10, 11}; std::vector baseline{5, 10}; @@ -976,7 +989,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = abs(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{-1}; std::vector baseline{-2}; @@ -992,7 +1005,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = exp(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{1}; std::vector baseline{2 * pow(E, 2.0)}; @@ -1006,7 +1019,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = log(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{0.5}; @@ -1020,7 +1033,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = log10(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{0.5 / log(10.0)}; @@ -1034,7 +1047,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = sqrt(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{0.5}; @@ -1048,7 +1061,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = sin(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{2 * cos(4)}; @@ -1062,7 +1075,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = cos(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{-2 * sin(4)}; @@ -1076,7 +1089,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = tan(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{2 / pow(cos(4), 2)}; @@ -1090,7 +1103,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = sinh(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{2 * cosh(4)}; @@ -1106,7 +1119,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = cosh(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{2 * sinh(4)}; @@ -1122,7 +1135,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = tanh(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{2 * (1 - pow(tanh(4), 2))}; @@ -1139,7 +1152,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = asin(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0.25}; std::vector baseline{2 / sqrt(3.0 / 4.0)}; @@ -1156,7 +1169,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = acos(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0.25}; std::vector baseline{-2 / sqrt(3.0 / 4.0)}; @@ -1174,7 +1187,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = atan(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0.25}; std::vector baseline{2 / (5.0 / 4.0)}; @@ -1191,7 +1204,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = asinh(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0.25}; std::vector baseline{2 / sqrt(5.0 / 4.0)}; @@ -1209,7 +1222,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = acosh(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{1}; std::vector baseline{2 / sqrt(3.0)}; @@ -1227,7 +1240,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = atanh(2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{0.25}; std::vector baseline{2 / (3.0 / 4.0)}; @@ -1245,7 +1258,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = pow(w, 3); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{3 * 4}; @@ -1262,7 +1275,7 @@ TEST_CASE("diff_tests", "[smoke]") auto w = m.add_variable("w").lower(0).upper(1).value(0); coek::Expression f = pow(3, 2 * w); m.add_objective(f); - coek::NLPModel nlp(m, "cppad"); + coek::NLPModel nlp(m, ADNAME); std::vector x{2}; std::vector baseline{2 * log(3) * pow(3, 4)}; diff --git a/lib/coek/test/test_con.cpp b/lib/coek/test/test_con.cpp index 66d3b9c2..024be36b 100644 --- a/lib/coek/test/test_con.cpp +++ b/lib/coek/test/test_con.cpp @@ -77,13 +77,13 @@ TEST_CASE("elementary_constraint", "[smoke]") REQUIRE(e.lower().value() == 1.0); } - /* TODO - WHEN("lower - unbounded") { + WHEN("has_lower") + { auto v = coek::variable("v"); - auto e = 1 > v; - REQUIRE( e.lower().isinf() ); - } - */ + auto e = 1 < v; + REQUIRE(e.has_lower()); + REQUIRE(not e.has_upper()); + } WHEN("upper") { @@ -92,6 +92,15 @@ TEST_CASE("elementary_constraint", "[smoke]") REQUIRE(e.upper().value() == 1.0); } + WHEN("has_upper") + { + auto v = coek::variable("v"); + auto e = 1 > v; + REQUIRE(e.upper().value() == 1.0); + REQUIRE(not e.has_lower()); + REQUIRE(e.has_upper()); + } + WHEN("body") { auto v = coek::variable("v").value(2.0); @@ -1681,7 +1690,12 @@ TEST_CASE("elementary_constraint", "[smoke]") } #ifdef COEK_WITH_COMPACT_MODEL -TEMPLATE_TEST_CASE("indexed_constraint", "[smoke]", coek::Model, coek::CompactModel) +# define MODEL_TYPES coek::Model, coek::CompactModel +#else +# define MODEL_TYPES coek::Model +#endif +#if __cpp_lib_variant +TEMPLATE_TEST_CASE("indexed_constraint", "[smoke]", MODEL_TYPES) { TestType m; @@ -1736,6 +1750,7 @@ TEMPLATE_TEST_CASE("indexed_constraint", "[smoke]", coek::Model, coek::CompactMo m.add(c); } +#ifdef COEK_WITH_COMPACT_MODEL WHEN("operator () - param") { auto p = coek::parameter(); @@ -1747,6 +1762,7 @@ TEMPLATE_TEST_CASE("indexed_constraint", "[smoke]", coek::Model, coek::CompactMo } m.add(c); } +#endif /* TODO - Should we allow this? @@ -1851,6 +1867,7 @@ TEMPLATE_TEST_CASE("indexed_constraint", "[smoke]", coek::Model, coek::CompactMo } } +#ifdef COEK_WITH_COMPACT_MODEL SECTION("map") { WHEN("constructor") @@ -1919,5 +1936,6 @@ TEMPLATE_TEST_CASE("indexed_constraint", "[smoke]", coek::Model, coek::CompactMo REQUIRE_THROWS_WITH(c(-1, -1), "Unexpected index value: (-1,-1)"); } } +#endif } #endif diff --git a/lib/coek/test/test_examples.cpp b/lib/coek/test/test_examples.cpp index ac950599..9f6c8aef 100644 --- a/lib/coek/test/test_examples.cpp +++ b/lib/coek/test/test_examples.cpp @@ -16,17 +16,19 @@ std::vector simplelp1_soln{375, 250}; void simplelp1_solve(); coek::Model invquad_vector(std::vector& p); +#if __cpp_lib_variant coek::Model invquad_array(std::vector& p); -std::vector invquad_soln_5{-10, -10, -10, -10, -10}; void invquad_array_solve(); void invquad_array_resolve(); +#endif +std::vector invquad_soln_5{-10, -10, -10, -10, -10}; void check(std::vector& variables, std::vector& soln) { for (size_t i = 0; i < variables.size(); i++) REQUIRE(variables[i].value() == Approx(soln[i])); } -TEST_CASE("ipopt", "[smoke]") +TEST_CASE("ipopt_cppad", "[smoke]") { coek::NLPSolver solver("ipopt"); if (solver.available()) { @@ -113,6 +115,7 @@ TEST_CASE("ipopt", "[smoke]") */ } +#if __cpp_lib_variant SECTION("invquad_array") { std::vector p(5); @@ -183,6 +186,174 @@ TEST_CASE("ipopt", "[smoke]") WHEN("invquad_solve") { invquad_array_solve(); } WHEN("invquad_resolve") { invquad_array_resolve(); } } +#endif + } + else { + REQUIRE(solver.error_status()); + REQUIRE(solver.error_code() != 0); + std::cerr << solver.error_message() << std::endl; + } +} + +TEST_CASE("ipopt_asl", "[smoke]") +{ + coek::NLPSolver solver("ipopt"); + if (solver.available()) { + REQUIRE(not solver.error_status()); + REQUIRE(solver.error_code() == 0); + + solver.set_option("print_level", 0); + + SECTION("rosenbr") + { + auto m = rosenbr(); + + coek::NLPModel nlp(m, "asl"); + solver.solve(nlp); + + check(m.get_variables(), rosenbr_soln); + } + + SECTION("invquad_vector") + { + std::vector p(5); + for (auto& param : p) param.value(0.5); + + WHEN("solve") + { + auto m = invquad_vector(p); + + coek::NLPModel nlp(m, "asl"); + solver.solve(nlp); + + check(m.get_variables(), invquad_soln_5); + } + WHEN("resolve - Same start") + { + auto m = invquad_vector(p); + + coek::NLPModel nlp(m, "asl"); + solver.solve(nlp); + + for (auto& param : p) param.value(-0.5); + + for (size_t i = 0; i < nlp.num_variables(); i++) nlp.get_variable(i).value(0); + solver.set_option("print_level", 0); + solver.resolve(); + + std::vector invquad_resolve_5{10, 10, 10, 10, 10}; + check(m.get_variables(), invquad_resolve_5); + } + WHEN("resolve - Current point") + { + auto m = invquad_vector(p); + + coek::NLPModel nlp(m, "asl"); + solver.solve(nlp); + + for (auto& param : p) param.value(0.5); + + solver.resolve(); + + check(m.get_variables(), invquad_soln_5); + } + /* + WHEN( "resolve - Warm Start" ) { + auto m = invquad_vector(p); + + coek::NLPModel nlp(m, "asl"); + solver.solve(nlp); + + for (auto& param : p) + param.value(-0.5); + + // Even though we set the value of the initial point, + // the warm starting option should ignore this and + // restart the solve from where it ended last time. + for (size_t i=0; i p(5); + for (auto& param : p) param.value(0.5); + + WHEN("solve") + { + auto m = invquad_array(p); + + coek::NLPModel nlp(m, "asl"); + solver.solve(nlp); + + check(m.get_variables(), invquad_soln_5); + } + WHEN("resolve - Same start") + { + auto m = invquad_array(p); + + coek::NLPModel nlp(m, "asl"); + solver.solve(nlp); + + for (auto& param : p) param.value(-0.5); + + for (size_t i = 0; i < nlp.num_variables(); i++) nlp.get_variable(i).value(0); + solver.set_option("print_level", 0); + solver.resolve(); + + std::vector invquad_resolve_5{10, 10, 10, 10, 10}; + check(m.get_variables(), invquad_resolve_5); + } + WHEN("resolve - Current point") + { + auto m = invquad_array(p); + + coek::NLPModel nlp(m, "asl"); + solver.solve(nlp); + + for (auto& param : p) param.value(0.5); + + solver.resolve(); + + check(m.get_variables(), invquad_soln_5); + } + /* + WHEN( "resolve - Warm Start" ) { + auto m = invquad_array(p); + + coek::NLPModel nlp(m, "asl"); + solver.solve(nlp); + + for (auto& param : p) + param.value(-0.5); + + // Even though we set the value of the initial point, + // the warm starting option should ignore this and + // restart the solve from where it ended last time. + for (size_t i=0; i baseline = {"[", "_", std::to_string(1.0), "]"}; REQUIRE(a.to_list() == baseline); } WHEN("equal") { - coek::SubExpression a(1.0); - coek::SubExpression b; - b = a; + auto a = coek::subexpression().value(1.0); static std::list baseline = {"[", "_", std::to_string(1.0), "]"}; REQUIRE(a.to_list() == baseline); } @@ -508,7 +508,7 @@ TEST_CASE("model_subexpression", "[smoke]") { WHEN("constant") { - coek::SubExpression a(1.0); + auto a = coek::subexpression().value(1.0); REQUIRE(a.is_constant() == false); REQUIRE(a.repn->body->is_constant() == true); } @@ -3949,7 +3949,7 @@ TEST_CASE("expression_value", "[smoke]") WHEN("2*x + 1 == 0") { auto x = coek::variable().value(1.0); - auto c = 2*x + 1 == 0; + auto c = 2 * x + 1 == 0; std::map, double> subexpr_value; REQUIRE(evaluate_expr(c.repn, subexpr_value) == 3.0); } @@ -3957,7 +3957,7 @@ TEST_CASE("expression_value", "[smoke]") WHEN("2*x + 1 <= 0") { auto x = coek::variable().value(1.0); - auto c = 2*x + 1 <= 0; + auto c = 2 * x + 1 <= 0; std::map, double> subexpr_value; REQUIRE(evaluate_expr(c.repn, subexpr_value) == 3.0); } diff --git a/lib/coek/test/test_expr_visitors.cpp b/lib/coek/test/test_expr_visitors.cpp index 70ae6280..800b5e97 100644 --- a/lib/coek/test/test_expr_visitors.cpp +++ b/lib/coek/test/test_expr_visitors.cpp @@ -195,6 +195,7 @@ #define ENV_MEMCHECK +#if 0 TEST_CASE("expr_writer", "[smoke]") { SECTION("constant") @@ -1834,22 +1835,78 @@ TEST_CASE("expr_to_MutableNLPExpr", "[smoke]") REQUIRE(repn.quadratic_rvars[0] == w.repn); } } + WHEN("complex quadratic 4") + { + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = (w - 3) * (w - 3); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(9.0)}; + static std::list lcoefval = {std::to_string(-3.0)}; + static std::list qcoefval = {std::to_string(1.0)}; + REQUIRE(repn.constval->to_list() == constval); + + REQUIRE(repn.linear_coefs.size() == 2); + REQUIRE(repn.linear_coefs[0]->to_list() == lcoefval); + REQUIRE(repn.linear_coefs[1]->to_list() == lcoefval); + + REQUIRE(repn.quadratic_coefs.size() == 1); + REQUIRE(repn.quadratic_coefs[0]->to_list() == qcoefval); + REQUIRE(repn.quadratic_lvars[0] == w.repn); + REQUIRE(repn.quadratic_rvars[0] == w.repn); + } + } + WHEN("complex quadratic 5") + { + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = (w - 3) * (w - 3) + (w - 5) * (w - 5); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(34.0)}; + static std::list lcoefval1 = {std::to_string(-3.0)}; + static std::list lcoefval2 = {std::to_string(-5.0)}; + static std::list qcoefval = {std::to_string(1.0)}; + REQUIRE(repn.constval->to_list() == constval); + + REQUIRE(repn.linear_coefs.size() == 4); + REQUIRE(repn.linear_coefs[0]->to_list() == lcoefval1); + REQUIRE(repn.linear_coefs[1]->to_list() == lcoefval1); + REQUIRE(repn.linear_coefs[2]->to_list() == lcoefval2); + REQUIRE(repn.linear_coefs[3]->to_list() == lcoefval2); + + REQUIRE(repn.quadratic_coefs.size() == 2); + REQUIRE(repn.quadratic_coefs[0]->to_list() == qcoefval); + REQUIRE(repn.quadratic_coefs[1]->to_list() == qcoefval); + REQUIRE(repn.quadratic_lvars[0] == w.repn); + REQUIRE(repn.quadratic_rvars[0] == w.repn); + } + } WHEN("complex nonlinear") { { // Force use of ceil and floor functions within a nested product. // Force expression of multiplication between constant parameter and quadratic term + // Force inclusion of multipliers in nonlinear terms coek::Model m; auto w = m.add_variable("w").lower(0).upper(1).value(0); auto p = coek::parameter("p"); - coek::Expression e = ceil(w) * floor(w) + p * (w * w); + coek::Expression e = ceil(w) * floor(w) + p * (w * w) - floor(w) * floor(w); coek::MutableNLPExpr repn; repn.collect_terms(e); REQUIRE(repn.linear_coefs.size() == 0); REQUIRE(repn.quadratic_coefs.size() == 1); static std::list baseline - = {"[", "*", "[", "ceil", "w", "]", "[", "floor", "w", "]", "]"}; + = {"[", "+", "[", "*", "[", "ceil", "w", "]", + "[", "floor", "w", "]", "]", "[", "*", std::to_string(-1.0), + "[", "*", "[", "floor", "w", "]", "[", "floor", + "w", "]", "]", "]", "]"}; REQUIRE(repn.nonlinear->to_list() == baseline); } } @@ -2155,7 +2212,7 @@ TEST_CASE("mutable_values", "[smoke]") REQUIRE(params == pbaseline); } -#ifdef DEBUG +# ifdef DEBUG WHEN("debug walker0") { auto v = coek::variable("v"); @@ -2211,7 +2268,7 @@ WHEN("debug walker2") REQUIRE(params == pbaseline); REQUIRE(num_visits == 7); } -#endif +# endif } SECTION("plus") @@ -2594,7 +2651,7 @@ TEST_CASE("find_vars_and_params", "[smoke]") REQUIRE(params == pbaseline); } -#ifdef DEBUG +# ifdef DEBUG WHEN("debug walker0") { auto v = coek::variable("v"); @@ -2660,7 +2717,7 @@ WHEN("debug walker2") REQUIRE(params == pbaseline); REQUIRE(num_visits == 7); } -#endif +# endif } SECTION("plus") @@ -2939,3 +2996,4 @@ SECTION("constraint") } } } +#endif diff --git a/lib/coek/test/test_model.cpp b/lib/coek/test/test_model.cpp index bf219299..c02fd7c2 100644 --- a/lib/coek/test/test_model.cpp +++ b/lib/coek/test/test_model.cpp @@ -22,84 +22,92 @@ TEMPLATE_TEST_CASE("model_add", "[smoke]", coek::Model) { TestType model; - SECTION("variables"){WHEN("elementary"){auto w = model.add(coek::variable()); - auto x = model.add(coek::variable().value(1)); - auto y = model.add(coek::variable()).value(1); -} + SECTION("variables") + { + WHEN("elementary") + { + auto w = model.add(coek::variable()); + auto x = model.add(coek::variable().value(1)); + auto y = model.add(coek::variable()).value(1); + } -WHEN("array") -{ - auto w = model.add(coek::variable(3)); - auto x = model.add(coek::variable(3).value(1)); - auto y = model.add(coek::variable(3)).value(1); - REQUIRE(w.size() == 3); - REQUIRE(x.size() == 3); - REQUIRE(y.size() == 3); -} +#if __cpp_lib_variant + WHEN("array") + { + auto w = model.add(coek::variable(3)); + auto x = model.add(coek::variable(3).value(1)); + auto y = model.add(coek::variable(3)).value(1); + REQUIRE(w.size() == 3); + REQUIRE(x.size() == 3); + REQUIRE(y.size() == 3); + } -WHEN("multi-dimensional array") -{ - auto w = model.add(coek::variable({3, 3})); - auto x = model.add(coek::variable({3, 3}).value(1)); - auto y = model.add(coek::variable({3, 3})).value(1); - REQUIRE(w.size() == 9); - REQUIRE(x.size() == 9); - REQUIRE(y.size() == 9); -} + WHEN("multi-dimensional array") + { + auto w = model.add(coek::variable({3, 3})); + auto x = model.add(coek::variable({3, 3}).value(1)); + auto y = model.add(coek::variable({3, 3})).value(1); + REQUIRE(w.size() == 9); + REQUIRE(x.size() == 9); + REQUIRE(y.size() == 9); + } -#ifdef COEK_WITH_COMPACT_MODEL -WHEN("map") -{ - auto A = coek::RangeSet(0, 2) * coek::RangeSet(0, 2); - auto w = model.add(coek::variable(A)); - auto x = model.add(coek::variable(A).value(1)); - auto y = model.add(coek::variable(A)).value(1); - REQUIRE(w.size() == 9); - REQUIRE(x.size() == 9); - REQUIRE(y.size() == 9); -} +# ifdef COEK_WITH_COMPACT_MODEL + WHEN("map") + { + auto A = coek::RangeSet(0, 2) * coek::RangeSet(0, 2); + auto w = model.add(coek::variable(A)); + auto x = model.add(coek::variable(A).value(1)); + auto y = model.add(coek::variable(A)).value(1); + REQUIRE(w.size() == 9); + REQUIRE(x.size() == 9); + REQUIRE(y.size() == 9); + } +# endif #endif -} - -SECTION("constraints") -{ - WHEN("elementary") - { - auto v = coek::variable(); - auto c1 = coek::constraint(v == 0); - model.add(c1); } - WHEN("array") + SECTION("constraints") { - auto v = coek::variable(); - auto c1 = coek::constraint(10); - c1(0) = v == 0; - model.add(c1); - REQUIRE(c1.size() == 1); - } + WHEN("elementary") + { + auto v = coek::variable(); + auto c1 = coek::constraint(v == 0); + model.add(c1); + } - WHEN("multi-dimensional array") - { - auto v = coek::variable(); - auto c1 = coek::constraint({10, 10}); - c1(0, 0) = v == 0; - model.add(c1); - REQUIRE(c1.size() == 1); - } +#if __cpp_lib_variant + WHEN("array") + { + auto v = coek::variable(); + auto c1 = coek::constraint(10); + c1(0) = v == 0; + model.add(c1); + REQUIRE(c1.size() == 1); + } -#ifdef COEK_WITH_COMPACT_MODEL - WHEN("map") - { - auto v = coek::variable(); - auto A = coek::RangeSet(0, 2) * coek::RangeSet(0, 2); - auto c1 = coek::constraint(A); - c1(0, 0) = v == 0; - model.add(c1); - REQUIRE(c1.size() == 1); - } + WHEN("multi-dimensional array") + { + auto v = coek::variable(); + auto c1 = coek::constraint({10, 10}); + c1(0, 0) = v == 0; + model.add(c1); + REQUIRE(c1.size() == 1); + } + +# ifdef COEK_WITH_COMPACT_MODEL + WHEN("map") + { + auto v = coek::variable(); + auto A = coek::RangeSet(0, 2) * coek::RangeSet(0, 2); + auto c1 = coek::constraint(A); + c1(0, 0) = v == 0; + model.add(c1); + REQUIRE(c1.size() == 1); + } +# endif #endif -} + } } #ifdef COEK_WITH_COMPACT_MODEL @@ -127,86 +135,106 @@ TEST_CASE("model_names", "[smoke]") #endif auto p = coek::parameter("p"); +#if __cpp_lib_variant auto q = coek::parameter("q", 10); auto r = coek::parameter("r", {10, 10}); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL auto qq = coek::parameter("qq", I); auto rr = coek::parameter("rr", I * I); +# endif #endif auto x = coek::variable("x"); +#if __cpp_lib_variant auto y = coek::variable("y", 10); auto z = coek::variable("z", {10, 10}); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL auto yy = coek::variable("yy", I); auto zz = coek::variable("zz", I * I); +# endif #endif auto c = coek::constraint("c", x == 1); +#if __cpp_lib_variant auto d = coek::constraint("d", 10); d(0) = x == 1; auto e = coek::constraint("e", {10, 10}); e(0, 0) = x == 1; -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL auto dd = coek::constraint("dd", I); dd(0) = x == 1; auto ee = coek::constraint("ee", I * I); ee(0, 0) = x == 1; +# endif #endif SECTION("simple") { // m.add(p); +#if __cpp_lib_variant m.add(q); m.add(r); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL m.add(qq); m.add(rr); +# endif #endif m.add(x); +#if __cpp_lib_variant m.add(y); m.add(z); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL m.add(yy); m.add(zz); +# endif #endif m.add(c); +#if __cpp_lib_variant m.add(d); m.add(e); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL m.add(dd); m.add(ee); +# endif #endif REQUIRE(m.name_generation() == coek::Model::NameGeneration::simple); +#if __cpp_lib_variant REQUIRE(m.repn->parameter_arrays.size() == 0); REQUIRE(m.repn->variable_arrays.size() == 0); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(m.repn->parameter_maps.size() == 0); REQUIRE(m.repn->variable_maps.size() == 0); -#endif +# endif REQUIRE(m.repn->constraint_maps.size() == 0); +#endif REQUIRE(coek::starts_with(p.name(), "p")); +#if __cpp_lib_variant REQUIRE(coek::starts_with(q(0).name(), "P")); REQUIRE(coek::starts_with(r(0, 0).name(), "P")); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(coek::starts_with(qq(0).name(), "P")); REQUIRE(coek::starts_with(rr(0, 0).name(), "P")); +# endif #endif REQUIRE(coek::starts_with(x.name(), "x")); +#if __cpp_lib_variant REQUIRE(coek::starts_with(y(0).name(), "X")); REQUIRE(coek::starts_with(z(0, 0).name(), "X")); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(coek::starts_with(yy(0).name(), "X")); REQUIRE(coek::starts_with(zz(0, 0).name(), "X")); +# endif #endif REQUIRE(coek::starts_with(c.name(), "c")); +#if __cpp_lib_variant REQUIRE(coek::starts_with(d(0).name(), "C")); REQUIRE(coek::starts_with(e(0, 0).name(), "C")); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(coek::starts_with(dd(0).name(), "C")); REQUIRE(coek::starts_with(ee(0, 0).name(), "C")); +# endif #endif } @@ -214,84 +242,104 @@ TEST_CASE("model_names", "[smoke]") { m.name_generation(coek::Model::NameGeneration::lazy); // m.add(p); +#if __cpp_lib_variant m.add(q); m.add(r); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL m.add(qq); m.add(rr); +# endif #endif m.add(x); +#if __cpp_lib_variant m.add(y); m.add(z); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL m.add(yy); m.add(zz); +# endif #endif m.add(c); +#if __cpp_lib_variant m.add(d); m.add(e); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL m.add(dd); m.add(ee); +# endif #endif REQUIRE(m.name_generation() == coek::Model::NameGeneration::lazy); +#if __cpp_lib_variant REQUIRE(m.repn->parameter_arrays.size() == 2); REQUIRE(m.repn->variable_arrays.size() == 2); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(m.repn->parameter_maps.size() == 2); REQUIRE(m.repn->variable_maps.size() == 2); -#endif -#ifdef COEK_WITH_COMPACT_MODEL +# endif +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(m.repn->constraint_maps.size() == 4); -#else +# else REQUIRE(m.repn->constraint_maps.size() == 2); +# endif #endif REQUIRE(coek::starts_with(p.name(), "p")); +#if __cpp_lib_variant REQUIRE(coek::starts_with(q(0).name(), "P")); REQUIRE(coek::starts_with(r(0, 0).name(), "P")); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(coek::starts_with(qq(0).name(), "P")); REQUIRE(coek::starts_with(rr(0, 0).name(), "P")); +# endif #endif REQUIRE(coek::starts_with(x.name(), "x")); +#if __cpp_lib_variant REQUIRE(coek::starts_with(y(0).name(), "X")); REQUIRE(coek::starts_with(z(0, 0).name(), "X")); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(coek::starts_with(yy(0).name(), "X")); REQUIRE(coek::starts_with(zz(0, 0).name(), "X")); +# endif #endif REQUIRE(coek::starts_with(c.name(), "c")); +#if __cpp_lib_variant REQUIRE(coek::starts_with(d(0).name(), "C")); REQUIRE(coek::starts_with(e(0, 0).name(), "C")); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(coek::starts_with(dd(0).name(), "C")); REQUIRE(coek::starts_with(ee(0, 0).name(), "C")); +# endif #endif m.generate_names(); REQUIRE(p.name() == "p"); +#if __cpp_lib_variant REQUIRE(q(0).name() == "q[0]"); REQUIRE(r(0, 0).name() == "r[0,0]"); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(qq(0).name() == "qq[0]"); REQUIRE(rr(0, 0).name() == "rr[0,0]"); +# endif #endif REQUIRE(x.name() == "x"); +#if __cpp_lib_variant REQUIRE(y(0).name() == "y[0]"); REQUIRE(z(0, 0).name() == "z[0,0]"); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(yy(0).name() == "yy[0]"); REQUIRE(zz(0, 0).name() == "zz[0,0]"); +# endif #endif REQUIRE(c.name() == "c"); +#if __cpp_lib_variant REQUIRE(d(0).name() == "d[0]"); REQUIRE(e(0, 0).name() == "e[0,0]"); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(dd(0).name() == "dd[0]"); REQUIRE(ee(0, 0).name() == "ee[0,0]"); +# endif #endif } @@ -299,56 +347,70 @@ TEST_CASE("model_names", "[smoke]") { m.name_generation(coek::Model::NameGeneration::eager); // m.add(p); +#if __cpp_lib_variant m.add(q); m.add(r); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL m.add(qq); m.add(rr); +# endif #endif m.add(x); +#if __cpp_lib_variant m.add(y); m.add(z); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL m.add(yy); m.add(zz); +# endif #endif m.add(c); +#if __cpp_lib_variant m.add(d); m.add(e); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL m.add(dd); m.add(ee); +# endif #endif REQUIRE(m.name_generation() == coek::Model::NameGeneration::eager); +#if __cpp_lib_variant REQUIRE(m.repn->parameter_arrays.size() == 0); REQUIRE(m.repn->variable_arrays.size() == 0); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(m.repn->parameter_maps.size() == 0); REQUIRE(m.repn->variable_maps.size() == 0); -#endif +# endif REQUIRE(m.repn->constraint_maps.size() == 0); +#endif REQUIRE(p.name() == "p"); +#if __cpp_lib_variant REQUIRE(q(0).name() == "q[0]"); REQUIRE(r(0, 0).name() == "r[0,0]"); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(qq(0).name() == "qq[0]"); REQUIRE(rr(0, 0).name() == "rr[0,0]"); +# endif #endif REQUIRE(x.name() == "x"); +#if __cpp_lib_variant REQUIRE(y(0).name() == "y[0]"); REQUIRE(z(0, 0).name() == "z[0,0]"); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(yy(0).name() == "yy[0]"); REQUIRE(zz(0, 0).name() == "zz[0,0]"); +# endif #endif REQUIRE(c.name() == "c"); +#if __cpp_lib_variant REQUIRE(d(0).name() == "d[0]"); REQUIRE(e(0, 0).name() == "e[0,0]"); -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL REQUIRE(dd(0).name() == "dd[0]"); REQUIRE(ee(0, 0).name() == "ee[0,0]"); +# endif #endif } } diff --git a/lib/coek/test/test_param.cpp b/lib/coek/test/test_param.cpp index fc9e8e6d..b8397a23 100644 --- a/lib/coek/test/test_param.cpp +++ b/lib/coek/test/test_param.cpp @@ -12,8 +12,6 @@ const double PI = 3.141592653589793238463; const double E = exp(1.0); -void xyz() {} - TEST_CASE("elementary_param", "[smoke]") { SECTION("values") @@ -25,6 +23,14 @@ TEST_CASE("elementary_param", "[smoke]") q.value(3); REQUIRE(q.value() == 3); } + WHEN("parameter - p+1") + { + auto p = coek::parameter("p").value(3); + auto q = coek::parameter("q").value(2); + REQUIRE(q.value() == 2); + q.value(p + 1); + REQUIRE(q.value() == 4); + } } SECTION("constructors") @@ -200,7 +206,7 @@ TEST_CASE("1D_param_map", "[smoke]") REQUIRE(typeid(params(1)).name() == typeid(coek::Parameter).name()); } - WHEN("value") + WHEN("value - 1") { auto s = coek::SetOf(v); auto params = coek::parameter(s).value(1); @@ -272,6 +278,7 @@ TEST_CASE("1D_param_map", "[smoke]") } #endif +#if __cpp_lib_variant TEST_CASE("1D_param_array", "[smoke]") { SECTION("int_vector") @@ -300,17 +307,54 @@ TEST_CASE("1D_param_array", "[smoke]") REQUIRE(typeid(params(1)).name() == typeid(coek::Parameter).name()); } - WHEN("index") + WHEN("value - 1") { - auto params = coek::parameter(4).value(1); + auto params = coek::parameter(4); + + // Set value using template + params.value(1); for (size_t i = 0; i < 4; i++) REQUIRE(params(i).value() == 1); - for (int i = 0; i < 4; i++) REQUIRE(params(i).value() == 1); + + // Set value for all indices + params.value(2); + for (size_t i = 0; i < 4; i++) REQUIRE(params(i).value() == 2); } + WHEN("value - q") + { + auto params = coek::parameter(4); + auto q = coek::parameter().value(2); + for (size_t i = 0; i < 4; i++) + params(i).value(q + (int)i); // TODO - generalize API to include unsigned ints + for (size_t i = 0; i < 4; i++) REQUIRE(params(i).value() == 2 + i); + } + WHEN("value all - q") + { + auto params = coek::parameter(4); + auto q = coek::parameter().value(2); + // Set value using template + params.value(q + (int)2); // TODO - generalize API to include unsigned ints + for (size_t i = 0; i < 4; i++) REQUIRE(params(i).value() == 4); + + // Set value for all indices + params.value(q + (int)3); + for (size_t i = 0; i < 4; i++) REQUIRE(params(i).value() == 5); + } WHEN("name") { - auto params = coek::parameter(4).name("v").generate_names(); + auto params = coek::parameter(4); + + params.name("v"); + params.generate_names(); for (int i = 0; i < 4; i++) REQUIRE(params(i).name() == "v[" + std::to_string(i) + "]"); + + // We don't need to call generate_names() again. Names are automatically generated + // after the first time. + params.name("w"); + for (int i = 0; i < 4; i++) REQUIRE(params(i).name() == "w[" + std::to_string(i) + "]"); + + params.name(""); + for (int i = 0; i < 4; i++) REQUIRE(params(i).name()[0] == 'P'); } } @@ -370,6 +414,7 @@ TEST_CASE("1D_param_array", "[smoke]") } } } +#endif #ifdef COEK_WITH_COMPACT_MODEL TEST_CASE("2D_param_map", "[smoke]") @@ -474,6 +519,7 @@ TEST_CASE("2D_param_map", "[smoke]") } #endif +#if __cpp_lib_variant TEST_CASE("2D_param_array", "[smoke]") { SECTION("int_vector_dim") @@ -673,6 +719,7 @@ TEST_CASE("3D_param_array", "[smoke]") } } } +#endif #ifdef COEK_WITH_COMPACT_MODEL TEST_CASE("3D_param_api", "[smoke]") @@ -786,5 +833,15 @@ TEST_CASE("3D_param_api", "[smoke]") } } } +#endif +#if __cpp_lib_variant +TEST_CASE("ND_param_array_errors", "[smoke]") +{ + SECTION("wrong index values") + { + auto p = coek::parameter("p", {10, 10}).value(1); + REQUIRE_THROWS_WITH(p(11, 11).value(), "Unknown index value: p[11,11]"); + } +} #endif diff --git a/lib/coek/test/test_subexpression.cpp b/lib/coek/test/test_subexpression.cpp index 5dd0da81..651050e4 100644 --- a/lib/coek/test/test_subexpression.cpp +++ b/lib/coek/test/test_subexpression.cpp @@ -18,21 +18,28 @@ TEST_CASE("mutable_subexpression", "[smoke]") { auto v = coek::variable("v"); auto e = coek::subexpression("e"); - e.value( v ); - auto E = e + 2*(e+1); + e.value(v); + auto E = e + 2 * (e + 1); static std::list baseline1 - = {"[", "+", "[", "_", "v", "]", "[", "*", "2.000000", "[", "+", "[", "_", "v", "]", "1.000000", "]", "]", "]"}; - REQUIRE( E.to_list() == baseline1 ); + = {"[", "+", "[", "_", "v", "]", "[", "*", "2.000000", "[", + "+", "[", "_", "v", "]", "1.000000", "]", "]", "]"}; + REQUIRE(E.to_list() == baseline1); e += v; static std::list baseline2 - = {"[", "+", "[", "_", "[", "+", "v", "v", "]", "]", "[", "*", "2.000000", "[", "+", "[", "_", "[", "+", "v", "v", "]", "]", "1.000000", "]", "]", "]"}; - REQUIRE( E.to_list() == baseline2 ); + = {"[", "+", "[", "_", "[", "+", "v", "v", "]", "]", "[", "*", "2.000000", "[", + "+", "[", "_", "[", "+", "v", "v", "]", "]", "1.000000", "]", "]", "]"}; + REQUIRE(E.to_list() == baseline2); + } + SECTION("name") + { + auto e = coek::subexpression("e"); + REQUIRE(e.name() == "e"); } } -#ifdef COEK_WITH_COMPACT_MODEL +#if __cpp_lib_variant TEST_CASE("indexed_subexpression", "[smoke]") { SECTION("simple array") @@ -85,6 +92,7 @@ TEST_CASE("indexed_subexpression", "[smoke]") } } +#ifdef COEK_WITH_COMPACT_MODEL WHEN("operator () - param") { auto p = coek::parameter(); @@ -95,6 +103,7 @@ TEST_CASE("indexed_subexpression", "[smoke]") e(p) = v(p) + 1.0 * i; } } +#endif /* TODO - Should we allow this? @@ -195,6 +204,7 @@ TEST_CASE("indexed_subexpression", "[smoke]") } } +#ifdef COEK_WITH_COMPACT_MODEL SECTION("map") { WHEN("constructor") @@ -259,5 +269,6 @@ TEST_CASE("indexed_subexpression", "[smoke]") REQUIRE_THROWS_WITH(e(-1, -1), "Unexpected index value: e(-1,-1)"); } } +#endif } #endif diff --git a/lib/coek/test/test_var.cpp b/lib/coek/test/test_var.cpp index 0fa33d7e..31ce8e47 100644 --- a/lib/coek/test/test_var.cpp +++ b/lib/coek/test/test_var.cpp @@ -140,6 +140,26 @@ TEST_CASE("elementary_variable", "[smoke]") a.upper(3.0); REQUIRE(a.upper() == 3.0); } + WHEN("bounds") + { + auto q = coek::parameter("q").value(1); + auto v = coek::variable("x").bounds(1, 2); + + REQUIRE(v.lower() == 1); + REQUIRE(v.upper() == 2); + v.bounds(q + 1, q + 2); + REQUIRE(v.lower() == 2); + REQUIRE(v.upper() == 3); + q.value(2); + REQUIRE(v.lower() == 3); + REQUIRE(v.upper() == 4); + v.bounds(q + 1, 10); + REQUIRE(v.lower() == 3); + REQUIRE(v.upper() == 10); + v.bounds(10, 10 * q); + REQUIRE(v.lower() == 10); + REQUIRE(v.upper() == 20); + } } SECTION("properties") @@ -310,6 +330,7 @@ TEST_CASE("1D_var_map", "[smoke]") } #endif +#if __cpp_lib_variant TEST_CASE("1D_var_array", "[smoke]") { SECTION("int_vector") @@ -338,18 +359,82 @@ TEST_CASE("1D_var_array", "[smoke]") REQUIRE(typeid(vars(1)).name() == typeid(coek::Variable).name()); } - WHEN("index") + WHEN("value 1") { auto vars = coek::variable(4).value(1); for (size_t i = 0; i < 4; i++) REQUIRE(vars(i).value() == 1); for (int i = 0; i < 4; i++) REQUIRE(vars(i).value() == 1); } + WHEN("value - q") + { + auto vars = coek::variable(4); + auto q = coek::parameter().value(2); + for (size_t i = 0; i < 4; i++) + vars(i).value(q + (int)i); // TODO - generalize API to include unsigned ints + for (size_t i = 0; i < 4; i++) REQUIRE(vars(i).value() == 2 + i); + } + + WHEN("value all - q") + { + auto vars = coek::variable(4); + auto q = coek::parameter().value(2); + + // Set value using template + vars.value(q + (int)2); // TODO - generalize API to include unsigned ints + for (size_t i = 0; i < 4; i++) REQUIRE(vars(i).value() == 4); + + // Set value for all indices + vars.value(q + (int)3); + for (size_t i = 0; i < 4; i++) REQUIRE(vars(i).value() == 5); + } + + WHEN("name") + { + auto vars = coek::variable(4); + + vars.name("v"); + vars.generate_names(); + for (int i = 0; i < 4; i++) REQUIRE(vars(i).name() == "v[" + std::to_string(i) + "]"); + + // We don't need to call generate_names() again. Names are automatically generated + // after the first time. + vars.name("w"); + REQUIRE(vars.name() == "w"); + for (int i = 0; i < 4; i++) REQUIRE(vars(i).name() == "w[" + std::to_string(i) + "]"); + + vars.name(""); + REQUIRE(vars.name() == ""); + for (int i = 0; i < 4; i++) REQUIRE(vars(i).name()[0] == 'X'); + } + WHEN("name") { auto vars = coek::variable(4).name("v").generate_names(); for (int i = 0; i < 4; i++) REQUIRE(vars(i).name() == "v[" + std::to_string(i) + "]"); } + + WHEN("iter") + { + auto vars = coek::variable(4).value(1); + for (auto& v : vars) REQUIRE(v.value() == 1); + + decltype(vars)::const_iterator it; + for (it = vars.cbegin(); it < vars.cend(); ++it) REQUIRE(it->value() == 1); + } + + WHEN("fixed") + { + auto a = coek::variable("a", 4).lower(0).upper(10).value(5).within(coek::Integers); + ; + REQUIRE(a(0).fixed() == false); + a.fixed(true); + REQUIRE(a(0).fixed() == true); + REQUIRE(a(0).value() == 5); + a(0).fix(3); + REQUIRE(a(0).fixed() == true); + REQUIRE(a(0).value() == 3); + } } SECTION("int_vector_dim") @@ -408,6 +493,7 @@ TEST_CASE("1D_var_array", "[smoke]") } } } +#endif #ifdef COEK_WITH_COMPACT_MODEL TEST_CASE("2D_var_map", "[smoke]") @@ -531,6 +617,7 @@ TEST_CASE("2D_var_map", "[smoke]") } #endif +#if __cpp_lib_variant TEST_CASE("2D_var_array", "[smoke]") { SECTION("int_vector_dim") @@ -730,9 +817,10 @@ TEST_CASE("3D_var_array", "[smoke]") } } } +#endif #ifdef COEK_WITH_COMPACT_MODEL -TEST_CASE("3D_var_api", "[smoke]") +TEST_CASE("3D_var_map_api", "[smoke]") { SECTION("map") { @@ -854,19 +942,6 @@ TEST_CASE("3D_var_api", "[smoke]") v.within(coek::Integers); REQUIRE(v(1).within() == coek::Integers); } - } - - SECTION("array") - { - auto p = coek::parameter("p").value(1); - - WHEN("add model") - { - auto v = coek::variable_array({4, 4, 4}).name("x").value(1); - coek::Model model; - auto vv = model.add(v); - REQUIRE(vv.size() == 64); - } WHEN("index api") { @@ -905,11 +980,29 @@ TEST_CASE("3D_var_api", "[smoke]") REQUIRE_THROWS_WITH(v(100, 100, 100), "Unknown index value: x[100,100,100]"); } + } +} +#endif + +#if __cpp_lib_variant +TEST_CASE("var_array_api", "[smoke]") +{ + SECTION("array") + { + auto p = coek::parameter("p").value(1); + + WHEN("add model") + { + auto v = coek::variable({4, 4, 4}).name("x").value(1); + coek::Model model; + auto vv = model.add(v); + REQUIRE(vv.size() == 64); + } WHEN("var value") { auto q = coek::parameter("q").value(1); - auto v = coek::variable_array(4).name("x").value(1); + auto v = coek::variable(4).name("x").value(1); REQUIRE(v(1).value() == 1); v.value(q + 1); @@ -918,10 +1011,20 @@ TEST_CASE("3D_var_api", "[smoke]") REQUIRE(v(1).value() == 3); } - WHEN("var lower") + WHEN("var lower - 3") { auto q = coek::parameter("q").value(1); - auto v = coek::variable_array(4).name("x").lower(1); + auto v = coek::variable(4).name("x").lower(1); + + REQUIRE(v(1).lower() == 1); + v.lower(3); + REQUIRE(v(1).lower() == 3); + } + + WHEN("var lower - q") + { + auto q = coek::parameter("q").value(1); + auto v = coek::variable(4).name("x").lower(1); REQUIRE(v(1).lower() == 1); v.lower(q + 1); @@ -930,10 +1033,20 @@ TEST_CASE("3D_var_api", "[smoke]") REQUIRE(v(1).lower() == 3); } - WHEN("var upper") + WHEN("var upper - 3") + { + auto q = coek::parameter("q").value(1); + auto v = coek::variable(4).name("x").upper(1); + + REQUIRE(v(1).upper() == 1); + v.upper(3); + REQUIRE(v(1).upper() == 3); + } + + WHEN("var upper - q") { auto q = coek::parameter("q").value(1); - auto v = coek::variable_array(4).name("x").upper(1); + auto v = coek::variable(4).name("x").upper(1); REQUIRE(v(1).upper() == 1); v.upper(q + 1); @@ -945,7 +1058,7 @@ TEST_CASE("3D_var_api", "[smoke]") WHEN("var bounds") { auto q = coek::parameter("q").value(1); - auto v = coek::variable_array(4).name("x").bounds(1, 2); + auto v = coek::variable(4).name("x").bounds(1, 2); REQUIRE(v(1).lower() == 1); REQUIRE(v(1).upper() == 2); @@ -961,12 +1074,15 @@ TEST_CASE("3D_var_api", "[smoke]") v.bounds(10, 10 * q); REQUIRE(v(1).lower() == 10); REQUIRE(v(1).upper() == 20); + v.bounds(11, 11); + REQUIRE(v(1).lower() == 11); + REQUIRE(v(1).upper() == 11); } WHEN("var within") { auto q = coek::parameter("q").value(1); - auto v = coek::variable_array(4).name("x"); + auto v = coek::variable(4).name("x"); REQUIRE(v(1).within() == coek::Reals); v.within(coek::Integers); @@ -974,5 +1090,15 @@ TEST_CASE("3D_var_api", "[smoke]") } } } +#endif +#if __cpp_lib_variant +TEST_CASE("ND_var_array_errors", "[smoke]") +{ + SECTION("wrong index values") + { + auto v = coek::variable("v", {10, 10}); + REQUIRE_THROWS_WITH(v(11, 11).value(), "Unknown index value: v[11,11]"); + } +} #endif diff --git a/lib/coek/test/test_visitor_eval.cpp b/lib/coek/test/test_visitor_eval.cpp new file mode 100644 index 00000000..3edc1d4c --- /dev/null +++ b/lib/coek/test/test_visitor_eval.cpp @@ -0,0 +1,390 @@ + +#include +#include + +#include "catch2/catch.hpp" +#include "coek/ast/base_terms.hpp" +#include "coek/ast/constraint_terms.hpp" +#include "coek/ast/value_terms.hpp" +#include "coek/ast/expr_terms.hpp" +#include "coek/ast/visitor_fns.hpp" +#include "coek/coek.hpp" +#include "coek/util/io_utils.hpp" + +#define INTRINSIC_TEST1(FN) \ + WHEN(#FN) \ + { \ + { \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1); \ + double tmp = 1.0 * FN(1.0); \ + REQUIRE(evaluate_expr(e.repn) == tmp); \ + } \ + } + +#define INTRINSIC_TEST2(FN) \ + WHEN(#FN " 2") \ + { \ + { \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1, v); \ + double tmp = 1.0 * FN(1.0, 0.0); \ + REQUIRE(evaluate_expr(e.repn) == tmp); \ + } \ + } + +TEST_CASE("evaluate_expr", "[smoke]") +{ + SECTION("constant") + { + { + auto p = coek::parameter("p").value(1); + coek::Expression e(3 + p); + + REQUIRE(evaluate_expr(e.repn) == 4.0); + } + } + + SECTION("param") + { + WHEN("simple") + { + { + auto p = coek::parameter("p").value(3); + coek::Expression e = p; + + REQUIRE(evaluate_expr(e.repn) == 3.0); + } + } + WHEN("nontrivial multiplier") + { + { + auto p = coek::parameter("p").value(3); + coek::Expression e = p / 2; + + REQUIRE(evaluate_expr(e.repn) == 1.5); + } + } + } + + SECTION("var") + { + WHEN("unfixed") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = v; + + REQUIRE(evaluate_expr(e.repn) == 3.0); + } + } + WHEN("fixed") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3).fixed(true); + coek::Expression e = v; + + REQUIRE(evaluate_expr(e.repn) == 3.0); + } + } + WHEN("fixed - nontrivial multiplier") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression f(2); + coek::Expression e = v / f; + + REQUIRE(evaluate_expr(e.repn) == 1.5); + } + } + } + + SECTION("monomial") + { + WHEN("unfixed") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = 2 * v; + + REQUIRE(evaluate_expr(e.repn) == 6.0); + } + } + WHEN("fixed") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3).fixed(true); + coek::Expression e = 2 * v; + + REQUIRE(evaluate_expr(e.repn) == 6.0); + } + } + } + + SECTION("negate") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3).fixed(true); + coek::Expression e = -(v + 1); + + REQUIRE(evaluate_expr(e.repn) == -4.0); + } + } + + SECTION("subexpression") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + auto E = coek::subexpression().value(v + 1); + coek::Expression e = E + 2 * (E + 1); + + REQUIRE(evaluate_expr(e.repn) == 14.0); + } + } + + SECTION("plus") + { + WHEN("2 terms") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = v + 1; + + REQUIRE(evaluate_expr(e.repn) == 4.0); + } + } + WHEN("combine sums") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = (v + 1) + (-2 - v); + + REQUIRE(evaluate_expr(e.repn) == -1.0); + } + } + } + + SECTION("times") + { + WHEN("lhs - param zero") + { + { + auto p = coek::parameter("p"); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = p * v; + + REQUIRE(evaluate_expr(e.repn) == 0.0); + } + } + WHEN("lhs - param one") + { + { + auto p = coek::parameter("p").value(1); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = p * v; + + REQUIRE(evaluate_expr(e.repn) == 3.0); + } + } + WHEN("lhs - param other") + { + { + auto p = coek::parameter("p").value(2.0); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = p * v; + + REQUIRE(evaluate_expr(e.repn) == 6.0); + } + } + WHEN("rhs - param zero") + { + { + auto p = coek::parameter("p"); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = v * p; + + REQUIRE(evaluate_expr(e.repn) == 0.0); + } + } + WHEN("rhs - param one") + { + { + auto p = coek::parameter("p").value(1); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = v * p; + + REQUIRE(evaluate_expr(e.repn) == 3.0); + } + } + WHEN("rhs - param other") + { + { + auto p = coek::parameter("p").value(2.0); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = v * p; + + REQUIRE(evaluate_expr(e.repn) == 6.0); + } + } + WHEN("complex quadratic 1a") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(0); + auto w = coek::variable("w").lower(0).upper(1).value(1).fixed(true); + coek::Expression e = (2 + 3 * w + v) * 4; + + REQUIRE(evaluate_expr(e.repn) == 20.0); + } + } + } + + SECTION("divide") + { + WHEN("lhs parameter - zero") + { + { + auto p = coek::parameter("p"); + auto w = coek::variable("w").lower(0).upper(1).value(0); + coek::Expression e = p / w; + + REQUIRE(evaluate_expr(e.repn) == 0.0); + } + } + WHEN("lhs constant - zero AND rhs constant") + { + { + auto p = coek::parameter("p").value(2.0); + auto w = coek::variable("w").lower(0).upper(1).value(1).fixed(true); + coek::Expression e = p / (w + 1); + + REQUIRE(evaluate_expr(e.repn) == 1.0); + } + } + WHEN("rhs constant - rhs 1.0") + { + { + auto p = coek::parameter("p").value(1.0); + auto w = coek::variable("w").lower(0).upper(1).value(1); + coek::Expression e = w / p; + + REQUIRE(evaluate_expr(e.repn) == 1.0); + } + } + WHEN("rhs constant") + { + { + auto p = coek::parameter("p").value(2.0); + auto w = coek::variable("w").lower(0).upper(1).value(1); + coek::Expression e = w / p; + + REQUIRE(evaluate_expr(e.repn) == 0.5); + } + } + } + + SECTION("constriaints") + { + WHEN("inequality") + { + { + auto p = coek::parameter("p"); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = w + p <= 2; + + REQUIRE(evaluate_expr(e.repn) == 3.0); + } + } + WHEN("equality") + { + { + auto p = coek::parameter("p"); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = w + p == 2; + + REQUIRE(evaluate_expr(e.repn) == 3.0); + } + } + } + + SECTION("intrinsic funcs"){ + INTRINSIC_TEST1(abs) INTRINSIC_TEST1(ceil) INTRINSIC_TEST1(floor) INTRINSIC_TEST1(exp) + INTRINSIC_TEST1(log) INTRINSIC_TEST1(log10) INTRINSIC_TEST1(sqrt) INTRINSIC_TEST1(sin) + INTRINSIC_TEST1(cos) INTRINSIC_TEST1(tan) INTRINSIC_TEST1(sinh) + INTRINSIC_TEST1(cosh) INTRINSIC_TEST1(tanh) INTRINSIC_TEST1(asin) + INTRINSIC_TEST1(acos) INTRINSIC_TEST1(atan) INTRINSIC_TEST1(asinh) + INTRINSIC_TEST1(acosh) INTRINSIC_TEST1(atanh) INTRINSIC_TEST2(pow)} + + SECTION("pow func") + { + WHEN("lhs constant - 0") + { + { + auto p = coek::parameter("p"); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(p, w); + + REQUIRE(evaluate_expr(e.repn) == 0.0); + } + } + WHEN("lhs constant - 1") + { + { + auto p = coek::parameter("p").value(1.0); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(p, w); + + REQUIRE(evaluate_expr(e.repn) == 1.0); + } + } + WHEN("lhs constant - other") + { + { + auto p = coek::parameter("p").value(2.0); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(p, w); + + REQUIRE(evaluate_expr(e.repn) == 8.0); + } + } + WHEN("rhs constant - 0") + { + { + auto p = coek::parameter("p").value(0.0); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(w, p); + + REQUIRE(evaluate_expr(e.repn) == 1.0); + } + } + WHEN("rhs constant - 1") + { + { + auto p = coek::parameter("p").value(1.0); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(w, p); + + REQUIRE(evaluate_expr(e.repn) == 3.0); + } + } + WHEN("rhs constant - other") + { + { + auto p = coek::parameter("p").value(2.0); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(w, p); + + REQUIRE(evaluate_expr(e.repn) == 9.0); + } + } + WHEN("general") + { + { + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(w, w - 1); + + REQUIRE(evaluate_expr(e.repn) == 9.0); + } + } + } +} diff --git a/lib/coek/test/test_visitor_findvarparam.cpp b/lib/coek/test/test_visitor_findvarparam.cpp new file mode 100644 index 00000000..4943f55b --- /dev/null +++ b/lib/coek/test/test_visitor_findvarparam.cpp @@ -0,0 +1,526 @@ + +#include +#include + +#include "catch2/catch.hpp" +#include "coek/ast/base_terms.hpp" +#include "coek/ast/constraint_terms.hpp" +#include "coek/ast/value_terms.hpp" +#include "coek/ast/expr_terms.hpp" +#include "coek/ast/visitor_fns.hpp" +#include "coek/coek.hpp" +#include "coek/util/io_utils.hpp" + +#define FVP_INTRINSIC_TEST1(FN) \ + WHEN(#FN) \ + { \ + coek::Model m; \ + auto v = m.add_variable("v").lower(0).upper(1).value(0).fixed(true); \ + coek::Expression e = FN(v + 1); \ + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); \ + static std::unordered_set> vbaseline{}; \ + static std::set> fvbaseline{v.repn}; \ + static std::set> pbaseline{}; \ + REQUIRE(vars == vbaseline); \ + REQUIRE(fixed_vars == fvbaseline); \ + REQUIRE(params == pbaseline); \ + } + +#define FVP_INTRINSIC_TEST2(FN) \ + WHEN(#FN) \ + { \ + coek::Model m; \ + auto w = m.add_variable("w").lower(0).upper(1).value(0); \ + auto v = m.add_variable("v").lower(0).upper(1).value(0).fixed(true); \ + coek::Expression e = FN(v + 1, w); \ + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); \ + static std::unordered_set> vbaseline{w.repn}; \ + static std::set> fvbaseline{v.repn}; \ + static std::set> pbaseline{}; \ + REQUIRE(vars == vbaseline); \ + REQUIRE(fixed_vars == fvbaseline); \ + REQUIRE(params == pbaseline); \ + } + +TEST_CASE("find_vars_and_params", "[smoke]") +{ + std::unordered_set> vars; + std::set> fixed_vars; + std::set> params; + std::set> visited_subexpressions; + + SECTION("constant") + { + coek::Expression e(3); + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + + SECTION("param") + { + auto p = coek::parameter().value(3); + coek::Expression e = p; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{}; + static std::set> pbaseline{p.repn}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + + SECTION("indexparam") + { + auto p = coek::set_element("p"); + coek::Expression e = p; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + + SECTION("var") + { + WHEN("fixed") + { + auto v = coek::variable(); + v.fixed(true); + coek::Expression e = v; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{v.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + + WHEN("same") + { + auto v = coek::variable(); + coek::Expression e = v; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::set> fvbaseline{}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + } + + SECTION("monomial") + { + WHEN("other") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = 2 * w; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{w.repn}; + static std::set> fvbaseline{}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + + WHEN("fixed") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + v.fixed(true); + coek::Expression e = 2 * v; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{v.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + } + + SECTION("subexpression") + { + WHEN("shared expression") + { + auto v = coek::variable("v"); + v.fixed(true); + auto E = coek::subexpression().value(v + 1); + coek::Expression e = E + 2 * (E + 1); + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{v.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + + WHEN("debug walker0") + { +#ifdef DEBUG + auto v = coek::variable("v"); + auto E = v + 1; + coek::Expression e = E + 2 * (E + 1); + size_t num_visits = 0; + find_vars_and_params_debug(e.repn, vars, fixed_vars, params, visited_subexpressions, + num_visits); + + static std::unordered_set> vbaseline{v.repn}; + static std::set> fvbaseline{}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + REQUIRE(num_visits == 10); +#endif + } + + WHEN("debug walker1") + { +#ifdef DEBUG + auto v = coek::variable("v"); + v.fixed(true); + auto E = coek::subexpression().value(v + 1); + coek::Expression e = E + 2 * (E + 1); + size_t num_visits = 0; + + find_vars_and_params_debug(e.repn, vars, fixed_vars, params, visited_subexpressions, + num_visits); + REQUIRE(num_visits == 10); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{v.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); +#endif + } + + WHEN("debug walker2") + { +#ifdef DEBUG + auto v = coek::variable("v"); + v.fixed(true); + auto E = coek::subexpression().value(v + 1); + coek::Expression e1 = E + 2 * (E + 1); + coek::Expression e2 = E + 2 * (E + 1); + + size_t num_visits = 0; + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{v.repn}; + static std::set> pbaseline{}; + + find_vars_and_params_debug(e1.repn, vars, fixed_vars, params, visited_subexpressions, + num_visits); + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + REQUIRE(num_visits == 10); + + find_vars_and_params_debug(e2.repn, vars, fixed_vars, params, visited_subexpressions, + num_visits); + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + REQUIRE(num_visits == 7); +#endif + } + } + + SECTION("plus") + { + WHEN("linear") + { + auto v = coek::variable("v").lower(0).upper(1).value(0); + v.fixed(true); + coek::Expression e = 2 * (v + v) + v; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{v.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + WHEN("simple") + { + auto p = coek::parameter(); + auto v = coek::variable("v").lower(0).upper(1).value(0); + v.fixed(true); + coek::Expression e = 3 * p + 2 * v; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{v.repn}; + static std::set> pbaseline{p.repn}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + } + + SECTION("negate") + { + WHEN("linear") + { + auto p = coek::parameter(); + auto v = coek::variable("v").lower(0).upper(1).value(0).fixed(true); + coek::Expression e = -(v + 1); + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{v.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + } + + SECTION("times") + { + WHEN("lhs constant") + { + coek::Model m; + auto p = coek::parameter().value(2); + auto v = m.add_variable("v").lower(0).upper(1).value(0); + coek::Expression e = p * v; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::set> fvbaseline{}; + static std::set> pbaseline{p.repn}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + WHEN("simple quadratic LHS") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0).fixed(true); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = v * w; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{w.repn}; + static std::set> fvbaseline{v.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + } + + SECTION("divide") + { + WHEN("lhs zero") + { + coek::Model m; + auto p = coek::parameter(); + auto w = m.add_variable("w").lower(0).upper(1).value(0).fixed(true); + coek::Expression e = p / w; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{w.repn}; + static std::set> pbaseline{p.repn}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + WHEN("rhs nonzero") + { + coek::Model m; + auto p = coek::parameter().value(2); + auto w = m.add_variable("w").lower(0).upper(1).value(0).fixed(true); + coek::Expression e = w / p; + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{w.repn}; + static std::set> pbaseline{p.repn}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + WHEN("rhs polynomial") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = w / (1 + w); + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{w.repn}; + static std::set> fvbaseline{}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + } + + SECTION("coverage") + { + WHEN("variable partial plus monomial - 1") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = w * v + v * (2 * w + 1); + w.fixed(true); + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::set> fvbaseline{w.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + WHEN("variable partial plus monomial - 2") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0).fixed(true); + coek::Expression e = v * (2 * w + 1); + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::set> fvbaseline{w.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + WHEN("constant partial plus monomial") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = 3 * w + 2 * w; + w.fixed(true); + coek::Expression e = v * (2 * w + 1); + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::set> fvbaseline{w.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + WHEN("negative monomial") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + w.fixed(true); + coek::Expression e = -(-w) + (-(-w)); + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::set> fvbaseline{w.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + } + + SECTION("intrinsic funcs"){ + + // clang-format off + FVP_INTRINSIC_TEST1(abs) + FVP_INTRINSIC_TEST1(ceil) + FVP_INTRINSIC_TEST1(floor) + FVP_INTRINSIC_TEST1(exp) + FVP_INTRINSIC_TEST1(log) + FVP_INTRINSIC_TEST1(log10) + FVP_INTRINSIC_TEST1(sqrt) + FVP_INTRINSIC_TEST1(sin) + FVP_INTRINSIC_TEST1(cos) + FVP_INTRINSIC_TEST1(tan) + FVP_INTRINSIC_TEST1(sinh) + FVP_INTRINSIC_TEST1(cosh) + FVP_INTRINSIC_TEST1(tanh) + FVP_INTRINSIC_TEST1(asin) + FVP_INTRINSIC_TEST1(acos) + FVP_INTRINSIC_TEST1(atan) + FVP_INTRINSIC_TEST1(asinh) + FVP_INTRINSIC_TEST1(acosh) + FVP_INTRINSIC_TEST1(atanh) + FVP_INTRINSIC_TEST2(pow)} + // clang-format on + + SECTION("objective") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto o = m.add_objective(w * v + v * (2 * w + 1)); + find_vars_and_params(o.expr().repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn, w.repn}; + static std::set> fvbaseline{}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + + SECTION("constraint") + { + WHEN("inequality") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto e = w * v + v * (2 * w + 1) <= 0; + w.fixed(true); + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::set> fvbaseline{w.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + WHEN("equality") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto e = w * v + v * (2 * w + 1) == 0; + w.fixed(true); + find_vars_and_params(e.repn, vars, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::set> fvbaseline{w.repn}; + static std::set> pbaseline{}; + REQUIRE(vars == vbaseline); + REQUIRE(fixed_vars == fvbaseline); + REQUIRE(params == pbaseline); + } + } +} diff --git a/lib/coek/test/test_visitor_mutable.cpp b/lib/coek/test/test_visitor_mutable.cpp new file mode 100644 index 00000000..ee8df004 --- /dev/null +++ b/lib/coek/test/test_visitor_mutable.cpp @@ -0,0 +1,460 @@ + +#include +#include + +#include "catch2/catch.hpp" +#include "coek/ast/base_terms.hpp" +#include "coek/ast/constraint_terms.hpp" +#include "coek/ast/value_terms.hpp" +#include "coek/ast/expr_terms.hpp" +#include "coek/ast/visitor_fns.hpp" +#include "coek/coek.hpp" +#include "coek/util/io_utils.hpp" + +#define MV_INTRINSIC_TEST1(FN) \ + WHEN(#FN) \ + { \ + coek::Model m; \ + auto v = m.add_variable("v").lower(0).upper(1).value(0).fixed(true); \ + coek::Expression e = FN(v + 1); \ + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); \ + static std::unordered_set> vbaseline{v.repn}; \ + static std::unordered_set> pbaseline{}; \ + REQUIRE(fixed_vars == vbaseline); \ + REQUIRE(params == pbaseline); \ + } + +#define MV_INTRINSIC_TEST2(FN) \ + WHEN(#FN) \ + { \ + coek::Model m; \ + auto v = m.add_variable("v").lower(0).upper(1).value(0).fixed(true); \ + coek::Expression e = FN(v + 1, v); \ + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); \ + static std::unordered_set> vbaseline{v.repn}; \ + static std::unordered_set> pbaseline{}; \ + REQUIRE(fixed_vars == vbaseline); \ + REQUIRE(params == pbaseline); \ + } + +TEST_CASE("mutable_values", "[smoke]") +{ + std::unordered_set> fixed_vars; + std::unordered_set> params; + std::unordered_set> visited_subexpressions; + + SECTION("constant") + { + coek::Expression e(3); + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + + SECTION("param") + { + auto p = coek::parameter().value(3); + coek::Expression e = p; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::unordered_set> pbaseline{p.repn}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + + SECTION("indexparam") + { + auto p = coek::set_element("p"); + coek::Expression e = p; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + + SECTION("var") + { + WHEN("fixed") + { + auto v = coek::variable(); + v.fixed(true); + coek::Expression e = v; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + + WHEN("same") + { + auto v = coek::variable(); + coek::Expression e = v; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + } + + SECTION("monomial") + { + WHEN("other") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = 2 * w; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + + WHEN("fixed") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + v.fixed(true); + coek::Expression e = 2 * v; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + } + + SECTION("subexpression"){WHEN("shared expression"){auto v = coek::variable("v"); + v.fixed(true); + auto E = coek::subexpression().value(v + 1); + coek::Expression e = E + 2 * (E + 1); + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); +} + +#ifdef DEBUG +WHEN("debug walker0") +{ + auto v = coek::variable("v"); + v.fixed(true); + auto E = v + 1; + coek::Expression e = E + 2 * (E + 1); + size_t num_visits = 0; + mutable_values_debug(e.repn, fixed_vars, params, visited_subexpressions, num_visits); + + static std::unordered_set> vbaseline{v.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + REQUIRE(num_visits == 10); +} + +WHEN("debug walker1") +{ + auto v = coek::variable("v"); + v.fixed(true); + auto E = coek::subexpression().value(v + 1); + coek::Expression e = E + 2 * (E + 1); + size_t num_visits = 0; + + mutable_values_debug(e.repn, fixed_vars, params, visited_subexpressions, num_visits); + REQUIRE(num_visits == 10); + + static std::unordered_set> vbaseline{v.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); +} + +WHEN("debug walker2") +{ + auto v = coek::variable("v"); + v.fixed(true); + auto E = coek::subexpression().value(v + 1); + coek::Expression e1 = E + 2 * (E + 1); + coek::Expression e2 = E + 2 * (E + 1); + + size_t num_visits = 0; + static std::unordered_set> vbaseline{v.repn}; + static std::unordered_set> pbaseline{}; + + mutable_values_debug(e1.repn, fixed_vars, params, visited_subexpressions, num_visits); + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + REQUIRE(num_visits == 10); + + mutable_values_debug(e2.repn, fixed_vars, params, visited_subexpressions, num_visits); + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + REQUIRE(num_visits == 7); +} +#endif +} + +SECTION("plus") +{ + WHEN("linear") + { + auto v = coek::variable("v").lower(0).upper(1).value(0); + v.fixed(true); + coek::Expression e = 2 * (v + v) + v; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + WHEN("simple") + { + auto p = coek::parameter(); + auto v = coek::variable("v").lower(0).upper(1).value(0); + v.fixed(true); + coek::Expression e = 3 * p + 2 * v; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::unordered_set> pbaseline{p.repn}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } +} + +SECTION("negate") +{ + WHEN("linear") + { + auto p = coek::parameter(); + auto v = coek::variable("v").lower(0).upper(1).value(0).fixed(true); + coek::Expression e = -(v + 1); + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } +} + +SECTION("times") +{ + WHEN("lhs constant") + { + coek::Model m; + auto p = coek::parameter().value(2); + auto v = m.add_variable("v").lower(0).upper(1).value(0).fixed(true); + coek::Expression e = p * v; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::unordered_set> pbaseline{p.repn}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + WHEN("simple quadratic LHS") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0).fixed(true); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = v * w; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } +} + +SECTION("divide") +{ + WHEN("lhs zero") + { + coek::Model m; + auto p = coek::parameter(); + auto w = m.add_variable("w").lower(0).upper(1).value(0).fixed(true); + coek::Expression e = p / w; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{w.repn}; + static std::unordered_set> pbaseline{p.repn}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + WHEN("rhs nonzero") + { + coek::Model m; + auto p = coek::parameter().value(2); + auto w = m.add_variable("w").lower(0).upper(1).value(0).fixed(true); + coek::Expression e = w / p; + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{w.repn}; + static std::unordered_set> pbaseline{p.repn}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + WHEN("rhs polynomial") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0).fixed(true); + coek::Expression e = w / (1 + w); + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{w.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } +} + +SECTION("coverage") +{ + WHEN("variable partial plus monomial - 1") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = w * v + v * (2 * w + 1); + v.fixed(true); + w.fixed(true); + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn, w.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + WHEN("variable partial plus monomial - 2") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0).fixed(true); + auto w = m.add_variable("w").lower(0).upper(1).value(0).fixed(true); + coek::Expression e = v * (2 * w + 1); + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn, w.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + WHEN("constant partial plus monomial") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = 3 * w + 2 * w; + w.fixed(true); + coek::Expression e = v * (2 * w + 1); + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{w.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + WHEN("negative monomial") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + w.fixed(true); + coek::Expression e = -(-w) + (-(-w)); + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{w.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } +} + +SECTION("intrinsic funcs"){ + + // clang-format off + MV_INTRINSIC_TEST1(abs) + MV_INTRINSIC_TEST1(ceil) + MV_INTRINSIC_TEST1(floor) + MV_INTRINSIC_TEST1(exp) + MV_INTRINSIC_TEST1(log) + MV_INTRINSIC_TEST1(log10) + MV_INTRINSIC_TEST1(sqrt) + MV_INTRINSIC_TEST1(sin) + MV_INTRINSIC_TEST1(cos) + MV_INTRINSIC_TEST1(tan) + MV_INTRINSIC_TEST1(sinh) + MV_INTRINSIC_TEST1(cosh) + MV_INTRINSIC_TEST1(tanh) + MV_INTRINSIC_TEST1(asin) + MV_INTRINSIC_TEST1(acos) + MV_INTRINSIC_TEST1(atan) + MV_INTRINSIC_TEST1(asinh) + MV_INTRINSIC_TEST1(acosh) + MV_INTRINSIC_TEST1(atanh) + MV_INTRINSIC_TEST2(pow)} // clang-format on + +SECTION("objective") +{ + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto o = m.add_objective(w * v + v * (2 * w + 1)); + v.fixed(true); + w.fixed(true); + mutable_values(o.expr().repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn, w.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); +} + +SECTION("constraint") +{ + WHEN("inequality") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto e = w * v + v * (2 * w + 1) <= 0; + v.fixed(true); + w.fixed(true); + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn, w.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } + WHEN("equality") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto e = w * v + v * (2 * w + 1) == 0; + v.fixed(true); + w.fixed(true); + mutable_values(e.repn, fixed_vars, params, visited_subexpressions); + + static std::unordered_set> vbaseline{v.repn, w.repn}; + static std::unordered_set> pbaseline{}; + REQUIRE(fixed_vars == vbaseline); + REQUIRE(params == pbaseline); + } +} +} diff --git a/lib/coek/test/test_visitor_nlpexpr.cpp b/lib/coek/test/test_visitor_nlpexpr.cpp new file mode 100644 index 00000000..c9a47ca0 --- /dev/null +++ b/lib/coek/test/test_visitor_nlpexpr.cpp @@ -0,0 +1,715 @@ + +#include +#include + +#include "catch2/catch.hpp" +#include "coek/ast/base_terms.hpp" +#include "coek/ast/constraint_terms.hpp" +#include "coek/ast/value_terms.hpp" +#include "coek/ast/expr_terms.hpp" +#include "coek/ast/visitor_fns.hpp" +#include "coek/coek.hpp" +#include "coek/util/io_utils.hpp" + +#define NLP_INTRINSIC_TEST1(FN) \ + WHEN(#FN " 1") \ + { \ + { \ + coek::Model m; \ + auto v = m.add_variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1); \ + coek::MutableNLPExpr repn; \ + static std::list constval = {std::to_string(0.0)}; \ + static std::list nonlinear \ + = {"[", #FN, "[", "+", "v", std::to_string(1.0), "]", "]"}; \ + REQUIRE(repn.constval->to_list() == constval); \ + REQUIRE(repn.linear_coefs.size() + repn.quadratic_coefs.size() == 0); \ + } \ + } \ + WHEN(#FN " 2") \ + { \ + { \ + coek::Model m; \ + auto v = m.add_variable("v").lower(0).upper(1).value(0).fixed(true); \ + coek::Expression e = FN(v + 1); \ + coek::MutableNLPExpr repn; \ + repn.collect_terms(e); \ + static std::list nonlinear = {std::to_string(0.0)}; \ + REQUIRE(repn.linear_coefs.size() + repn.quadratic_coefs.size() == 0); \ + REQUIRE(repn.nonlinear->to_list() == nonlinear); \ + } \ + } + +#define NLP_INTRINSIC_TEST2(FN) \ + WHEN(#FN " 1") \ + { \ + { \ + coek::Model m; \ + auto v = m.add_variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1, v); \ + coek::MutableNLPExpr repn; \ + static std::list constval = {std::to_string(0.0)}; \ + static std::list nonlinear \ + = {"[", #FN, "[", "+", "v", std::to_string(1.0), "]", "v", "]"}; \ + REQUIRE(repn.constval->to_list() == constval); \ + REQUIRE(repn.linear_coefs.size() + repn.quadratic_coefs.size() == 0); \ + } \ + } \ + WHEN(#FN " 2") \ + { \ + { \ + coek::Model m; \ + auto v = m.add_variable("v").lower(0).upper(1).value(0).fixed(true); \ + coek::Expression e = FN(v + 1, v); \ + coek::MutableNLPExpr repn; \ + repn.collect_terms(e); \ + static std::list nonlinear = {std::to_string(0.0)}; \ + REQUIRE(repn.linear_coefs.size() + repn.quadratic_coefs.size() == 0); \ + REQUIRE(repn.nonlinear->to_list() == nonlinear); \ + } \ + } + +TEST_CASE("expr_to_MutableNLPExpr", "[smoke]") +{ + SECTION("constant") + { + { + coek::Expression e(3); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(3.0)}; + REQUIRE(repn.mutable_values == false); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + + SECTION("param") + { + WHEN("simple") + { + { + auto p = coek::parameter("p").value(3); + coek::Expression e = p; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {"p"}; + REQUIRE(repn.mutable_values == true); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + WHEN("nontrivial multiplier") + { + { + auto p = coek::parameter("p").value(3); + coek::Expression e = p / 2; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval + = {"[", "*", std::to_string(0.500), "p", "]"}; + REQUIRE(repn.mutable_values == true); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + } + + SECTION("var") + { + WHEN("unfixed") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + coek::Expression e = v; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + static std::list coefval = {std::to_string(1.0)}; + REQUIRE(repn.mutable_values == false); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0]->to_list() == coefval); + REQUIRE(repn.linear_vars[0] == e.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + WHEN("fixed") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + v.fixed(true); + coek::Expression e = v; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {"v"}; + REQUIRE(repn.mutable_values == true); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + WHEN("fixed - nontrivial multiplier") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + v.fixed(true); + coek::Expression f(2); + coek::Expression e = v / f; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval + = {"[", "*", std::to_string(0.500), "v", "]"}; + REQUIRE(repn.mutable_values == true); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + } + + SECTION("monomial") + { + WHEN("unfixed") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + coek::Expression e = 2 * v; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + static std::list coefval = {std::to_string(2.0)}; + REQUIRE(repn.mutable_values == false); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0]->to_list() == coefval); + auto tmp = std::dynamic_pointer_cast(e.repn); + REQUIRE(repn.linear_vars[0]->index == tmp->var->index); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + WHEN("fixed") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + v.fixed(true); + coek::Expression e = 2 * v; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {"[", "*", std::to_string(2.0), "v", "]"}; + REQUIRE(repn.mutable_values == true); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + } + + SECTION("negate") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + coek::Expression e = -(v + 1); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(-1.0)}; + static std::list coefval = {std::to_string(-1.0)}; + REQUIRE(repn.mutable_values == false); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0]->to_list() == coefval); + REQUIRE(repn.linear_vars[0] == v.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + + SECTION("subexpression") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + auto E = coek::subexpression().value(v + 1); + coek::Expression e = E + 2 * (E + 1); + REQUIRE(e.value() == 14); + + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(5.0)}; + static std::list coefval0 = {std::to_string(1.0)}; + static std::list coefval1 = {std::to_string(2.0)}; + REQUIRE(repn.mutable_values == false); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 2); + REQUIRE(repn.linear_coefs[0]->to_list() == coefval0); + REQUIRE(repn.linear_coefs[1]->to_list() == coefval1); + REQUIRE(repn.linear_vars[0] == v.repn); + REQUIRE(repn.linear_vars[1] == v.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + + SECTION("plus") + { + WHEN("2 terms") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + coek::Expression e = v + 1; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(1.0)}; + static std::list coefval = {std::to_string(1.0)}; + REQUIRE(repn.mutable_values == false); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0]->to_list() == coefval); + REQUIRE(repn.linear_vars[0] == v.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + WHEN("combine sums") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + coek::Expression e = v + 1 + (-1 - v); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + static std::list coefval0 = {std::to_string(1.0)}; + static std::list coefval1 = {std::to_string(-1.0)}; + REQUIRE(repn.mutable_values == false); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 2); + REQUIRE(repn.linear_coefs[0]->to_list() == coefval0); + REQUIRE(repn.linear_coefs[1]->to_list() == coefval1); + REQUIRE(repn.linear_vars[0] == v.repn); + REQUIRE(repn.linear_vars[1] == v.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + } + + SECTION("times") + { + WHEN("lhs constant") + { + { + coek::Model m; + auto p = coek::parameter("p"); + auto w = m.add_variable("w").lower(0).upper(1).value(3); + coek::Expression e = p * w; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + static std::list coefval = {"p"}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0]->to_list() == coefval); + REQUIRE(repn.linear_vars[0] == w.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + WHEN("rhs constant") + { + { + coek::Model m; + auto p = coek::parameter("p"); + auto w = m.add_variable("w").lower(0).upper(1).value(3); + coek::Expression e = w * p; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + static std::list coefval = {"p"}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0]->to_list() == coefval); + REQUIRE(repn.linear_vars[0] == w.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + WHEN("simple quadratic") + { + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = w * w; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + static std::list qcoefval = {std::to_string(1.0)}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 1); + REQUIRE(repn.quadratic_coefs[0]->to_list() == qcoefval); + REQUIRE(repn.quadratic_lvars[0] == w.repn); + REQUIRE(repn.quadratic_rvars[0] == w.repn); + } + } + WHEN("complex quadratic 1a") + { + { + // Products of linear expressions are not expanded + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = (2 + 3 * w + v) * (4 + 5 * v + w); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + WHEN("complex quadratic 1b") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = 3 * w * (4 + 5 * v + w); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + static std::list lcoef0 = {std::to_string(12.000)}; + static std::list qcoef0 = {std::to_string(15.000)}; + static std::list qcoef1 = {std::to_string(3.000)}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0]->to_list() == lcoef0); + REQUIRE(repn.quadratic_coefs.size() == 2); + REQUIRE(repn.quadratic_coefs[0]->to_list() == qcoef0); + REQUIRE(repn.quadratic_coefs[1]->to_list() == qcoef1); + } + } + WHEN("complex quadratic 2") + { + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = 3 * (w * w + 2); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(6.000)}; + static std::list qcoefval = {std::to_string(3.000)}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 1); + REQUIRE(repn.quadratic_coefs[0]->to_list() == qcoefval); + REQUIRE(repn.quadratic_lvars[0] == w.repn); + REQUIRE(repn.quadratic_rvars[0] == w.repn); + } + } + WHEN("complex quadratic 3") + { + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = (w * w + 2) * 3; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(6.0)}; + static std::list qcoefval = {std::to_string(3.0)}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 1); + REQUIRE(repn.quadratic_coefs[0]->to_list() == qcoefval); + REQUIRE(repn.quadratic_lvars[0] == w.repn); + REQUIRE(repn.quadratic_rvars[0] == w.repn); + } + } + WHEN("complex quadratic 4") + { + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = (w - 3) * (w - 3); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(9.0)}; + static std::list lcoefval = {std::to_string(-3.0)}; + static std::list qcoefval = {std::to_string(1.0)}; + REQUIRE(repn.constval->to_list() == constval); + + REQUIRE(repn.linear_coefs.size() == 2); + REQUIRE(repn.linear_coefs[0]->to_list() == lcoefval); + REQUIRE(repn.linear_coefs[1]->to_list() == lcoefval); + + REQUIRE(repn.quadratic_coefs.size() == 1); + REQUIRE(repn.quadratic_coefs[0]->to_list() == qcoefval); + REQUIRE(repn.quadratic_lvars[0] == w.repn); + REQUIRE(repn.quadratic_rvars[0] == w.repn); + } + } + WHEN("complex quadratic 5") + { + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = (w - 3) * (w - 3) + (w - 5) * (w - 5); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(34.0)}; + static std::list lcoefval1 = {std::to_string(-3.0)}; + static std::list lcoefval2 = {std::to_string(-5.0)}; + static std::list qcoefval = {std::to_string(1.0)}; + REQUIRE(repn.constval->to_list() == constval); + + REQUIRE(repn.linear_coefs.size() == 4); + REQUIRE(repn.linear_coefs[0]->to_list() == lcoefval1); + REQUIRE(repn.linear_coefs[1]->to_list() == lcoefval1); + REQUIRE(repn.linear_coefs[2]->to_list() == lcoefval2); + REQUIRE(repn.linear_coefs[3]->to_list() == lcoefval2); + + REQUIRE(repn.quadratic_coefs.size() == 2); + REQUIRE(repn.quadratic_coefs[0]->to_list() == qcoefval); + REQUIRE(repn.quadratic_coefs[1]->to_list() == qcoefval); + REQUIRE(repn.quadratic_lvars[0] == w.repn); + REQUIRE(repn.quadratic_rvars[0] == w.repn); + } + } + WHEN("complex nonlinear") + { + { + // Force use of ceil and floor functions within a nested product. + // Force expression of multiplication between constant parameter and quadratic term + // Force inclusion of multipliers in nonlinear terms + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + auto p = coek::parameter("p"); + coek::Expression e = ceil(w) * floor(w) + p * (w * w) - floor(w) * floor(w); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 1); + static std::list baseline + = {"[", "+", "[", "*", "[", "ceil", "w", "]", + "[", "floor", "w", "]", "]", "[", "*", std::to_string(-1.0), + "[", "*", "[", "floor", "w", "]", "[", "floor", + "w", "]", "]", "]", "]"}; + REQUIRE(repn.nonlinear->to_list() == baseline); + } + } + } + + SECTION("divide") + { + WHEN("lhs parameter - zero") + { + { + coek::Model m; + auto p = coek::parameter("p"); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = p / w; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + static std::list nonlinear = {"[", "/", "p", "w", "]"}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + REQUIRE(repn.nonlinear->to_list() == nonlinear); + } + } + WHEN("rhs parameter - zero") + { + { + coek::Model m; + auto p = coek::parameter("p"); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = w / p; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + static std::list lcoef0 = {"[", "/", std::to_string(1.0), "p", "]"}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0]->to_list() == lcoef0); + REQUIRE(repn.quadratic_coefs.size() == 0); + REQUIRE(repn.nonlinear->to_list() == constval); + } + } + WHEN("lhs constant - zero") + { + { + coek::Model m; + coek::Expression p; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = p / w; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + REQUIRE(repn.nonlinear->to_list() == constval); + } + } + WHEN("lhs constant - zero AND rhs constant") + { + { + coek::Model m; + coek::Expression p; + auto w = coek::parameter("w").value(1); + coek::Expression e = p / (w + 1); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + REQUIRE(repn.nonlinear->to_list() == constval); + } + } + WHEN("rhs constant - zero") + { + { + coek::Model m; + coek::Expression p; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = w / p; + coek::MutableNLPExpr repn; + REQUIRE_THROWS_WITH(repn.collect_terms(e), "Division by zero error."); + } + } + WHEN("rhs polynomial") + { + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = w / (1 + w); + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(0.0)}; + static std::list nonlinear + = {"[", "/", "w", "[", "+", std::to_string(1.0), "w", "]", "]"}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + REQUIRE(repn.nonlinear->to_list() == nonlinear); + } + } + WHEN("rhs nonzero") + { + { + coek::Model m; + auto p = coek::parameter("p").value(2); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = (w * w + w + 1) / p; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {"[", "/", std::to_string(1.0), "p", "]"}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0]->to_list() == constval); + REQUIRE(repn.quadratic_coefs.size() == 1); + REQUIRE(repn.quadratic_coefs[0]->to_list() == constval); + } + } + } + + SECTION("constriaints") + { + WHEN("inequality") + { + { + coek::Model m; + auto p = coek::parameter("p"); + auto w = m.add_variable("w").lower(0).upper(1).value(3); + auto e = p * w + 1 <= 2; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(1.0)}; + static std::list coefval = {"p"}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0]->to_list() == coefval); + REQUIRE(repn.linear_vars[0] == w.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + WHEN("equality") + { + { + coek::Model m; + auto p = coek::parameter("p"); + auto w = m.add_variable("w").lower(0).upper(1).value(3); + auto e = p * w - 1 == 2; + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(-1.0)}; + static std::list coefval = {"p"}; + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0]->to_list() == coefval); + REQUIRE(repn.linear_vars[0] == w.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + } + + SECTION("intrinsic funcs") + { + NLP_INTRINSIC_TEST1(abs) + NLP_INTRINSIC_TEST1(ceil) + NLP_INTRINSIC_TEST1(floor) + NLP_INTRINSIC_TEST1(exp) + NLP_INTRINSIC_TEST1(log) + NLP_INTRINSIC_TEST1(log10) + NLP_INTRINSIC_TEST1(sqrt) + NLP_INTRINSIC_TEST1(sin) + NLP_INTRINSIC_TEST1(cos) + NLP_INTRINSIC_TEST1(tan) + NLP_INTRINSIC_TEST1(sinh) + NLP_INTRINSIC_TEST1(cosh) + NLP_INTRINSIC_TEST1(tanh) + NLP_INTRINSIC_TEST1(asin) + NLP_INTRINSIC_TEST1(acos) + NLP_INTRINSIC_TEST1(atan) + NLP_INTRINSIC_TEST1(asinh) + NLP_INTRINSIC_TEST1(acosh) + NLP_INTRINSIC_TEST1(atanh) + NLP_INTRINSIC_TEST2(pow) + } +} diff --git a/lib/coek/test/test_visitor_quadexpr.cpp b/lib/coek/test/test_visitor_quadexpr.cpp new file mode 100644 index 00000000..6282b91e --- /dev/null +++ b/lib/coek/test/test_visitor_quadexpr.cpp @@ -0,0 +1,401 @@ + +#include +#include + +#include "catch2/catch.hpp" +#include "coek/ast/base_terms.hpp" +#include "coek/ast/constraint_terms.hpp" +#include "coek/ast/value_terms.hpp" +#include "coek/ast/expr_terms.hpp" +#include "coek/ast/visitor_fns.hpp" +#include "coek/coek.hpp" +#include "coek/util/io_utils.hpp" + +#define REPN_INTRINSIC_TEST1(FN) \ + WHEN(#FN " 1") \ + { \ + coek::Model m; \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1); \ + coek::QuadraticExpr repn; \ + REQUIRE_THROWS_WITH(repn.collect_terms(e), \ + Catch::Matchers::StartsWith( \ + "Nonlinear expressions are not supported for QuadraticExpr")); \ + } \ + WHEN(#FN " 2") \ + { \ + coek::Model m; \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + v.fixed(true); \ + coek::Expression e = FN(v + 1); \ + coek::QuadraticExpr repn; \ + repn.collect_terms(e); \ + REQUIRE(repn.linear_coefs.size() + repn.quadratic_coefs.size() == 0); \ + } + +#define REPN_INTRINSIC_TEST2(FN) \ + WHEN(#FN " 1") \ + { \ + coek::Model m; \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1, v - 1); \ + coek::QuadraticExpr repn; \ + REQUIRE_THROWS_WITH(repn.collect_terms(e), \ + Catch::Matchers::StartsWith( \ + "Nonlinear expressions are not supported for QuadraticExpr")); \ + } \ + WHEN(#FN " 2") \ + { \ + coek::Model m; \ + auto v = coek::variable("v").lower(0).upper(1).value(0).fixed(true); \ + coek::Expression e = FN(v + 1, v - 1); \ + coek::QuadraticExpr repn; \ + repn.collect_terms(e); \ + REQUIRE(repn.linear_coefs.size() + repn.quadratic_coefs.size() == 0); \ + } + +TEST_CASE("expr_to_QuadraticExpr", "[smoke]") +{ + SECTION("constant") + { + coek::Expression e(3); + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 3); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + + SECTION("param") + { + auto p = coek::parameter().value(3); + coek::Expression e = p; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 3); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + + SECTION("var") + { + WHEN("unfixed") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + coek::Expression e = v; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 0); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0] == 1); + REQUIRE(repn.linear_vars[0] == e.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + WHEN("fixed") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + v.fixed(true); + coek::Expression e = v; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 3); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + + SECTION("monomial") + { + WHEN("unfixed") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + coek::Expression e = 2 * v; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 0); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0] == 2); + auto tmp = std::dynamic_pointer_cast(e.repn); + REQUIRE(repn.linear_vars[0]->index == tmp->var->index); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + WHEN("fixed") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + v.fixed(true); + coek::Expression e = 2 * v; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 6); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + + SECTION("subexpression") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + auto E = coek::subexpression().value(v + 1); + coek::Expression e = E + 2 * (E + 1); + REQUIRE(e.value() == 14); + + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 5); + REQUIRE(repn.linear_coefs.size() == 2); + REQUIRE(repn.linear_coefs[0] == 1); + REQUIRE(repn.linear_coefs[1] == 2); + REQUIRE(repn.linear_vars[0] == v.repn); + REQUIRE(repn.linear_vars[1] == v.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } + + SECTION("times") + { + WHEN("lhs zero") + { + auto p = coek::parameter(); + auto w = coek::variable("w").lower(0).upper(1).value(0); + coek::Expression e = p * w; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 0); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + WHEN("lhs constant") + { + coek::Model m; + auto p = coek::parameter().value(2); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = p * w; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 0); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0] == 2); + REQUIRE(repn.linear_vars[0] == w.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + WHEN("rhs zero") + { + coek::Model m; + auto p = coek::parameter(); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = w * p; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 0); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + WHEN("rhs constant") + { + coek::Model m; + auto p = coek::parameter().value(2); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = (w + w * w) * p; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 0); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0] == 2); + REQUIRE(repn.linear_vars[0] == w.repn); + REQUIRE(repn.quadratic_coefs.size() == 1); + REQUIRE(repn.quadratic_coefs[0] == 2); + REQUIRE(repn.quadratic_lvars[0] == w.repn); + REQUIRE(repn.quadratic_rvars[0] == w.repn); + } + WHEN("simple quadratic") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = w * w; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 0); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 1); + REQUIRE(repn.quadratic_coefs[0] == 1); + REQUIRE(repn.quadratic_lvars[0] == w.repn); + REQUIRE(repn.quadratic_rvars[0] == w.repn); + } + WHEN("complex quadratic 1") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = (2 + 3 * w + v) * (4 + 5 * v + w); + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 8); + REQUIRE(repn.linear_coefs.size() == 4); + REQUIRE(repn.linear_coefs[0] == 10); + REQUIRE(repn.linear_coefs[1] == 2); + REQUIRE(repn.linear_coefs[2] == 12); + REQUIRE(repn.linear_coefs[3] == 4); + REQUIRE(repn.quadratic_coefs.size() == 4); + REQUIRE(repn.quadratic_coefs[0] == 15); + REQUIRE(repn.quadratic_coefs[1] == 3); + REQUIRE(repn.quadratic_coefs[2] == 5); + REQUIRE(repn.quadratic_coefs[3] == 1); + } + WHEN("error 1") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = (w + v * v) * v; + coek::QuadraticExpr repn; + REQUIRE_THROWS_WITH( + repn.collect_terms(e), + "Non-quadratic expressions cannot be expressed in a QuadraticExpr object."); + } + WHEN("error 2") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = v * (w + v * v); + coek::QuadraticExpr repn; + REQUIRE_THROWS_WITH( + repn.collect_terms(e), + "Non-quadratic expressions cannot be expressed in a QuadraticExpr object."); + } + } + + SECTION("divide") + { + WHEN("lhs zero") + { + coek::Model m; + auto p = coek::parameter(); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = p / w; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 0); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + WHEN("rhs zero") + { + coek::Model m; + auto p = coek::parameter(); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = w / p; + coek::QuadraticExpr repn; + REQUIRE_THROWS_WITH(repn.collect_terms(e), "Division by zero error."); + } + WHEN("rhs polynomial") + { + coek::Model m; + auto p = coek::parameter(); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = w / (1 + w); + coek::QuadraticExpr repn; + REQUIRE_THROWS_WITH(repn.collect_terms(e), + "Non-constant expressions cannot appear in the denominator of " + "quadratic expressions."); + } + WHEN("rhs nonzero") + { + coek::Model m; + auto p = coek::parameter().value(2); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression e = (w * w + w + 1) / p; + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 0.5); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0] == 0.5); + REQUIRE(repn.quadratic_coefs.size() == 1); + REQUIRE(repn.quadratic_coefs[0] == 0.5); + } + } + + SECTION("intrinsic funcs") + { + REPN_INTRINSIC_TEST1(abs) + REPN_INTRINSIC_TEST1(ceil) + REPN_INTRINSIC_TEST1(floor) + REPN_INTRINSIC_TEST1(exp) + REPN_INTRINSIC_TEST1(log) + REPN_INTRINSIC_TEST1(log10) + REPN_INTRINSIC_TEST1(sqrt) + REPN_INTRINSIC_TEST1(sin) + REPN_INTRINSIC_TEST1(cos) + REPN_INTRINSIC_TEST1(tan) + REPN_INTRINSIC_TEST1(sinh) + REPN_INTRINSIC_TEST1(cosh) + REPN_INTRINSIC_TEST1(tanh) + REPN_INTRINSIC_TEST1(asin) + REPN_INTRINSIC_TEST1(acos) + REPN_INTRINSIC_TEST1(atan) + REPN_INTRINSIC_TEST1(asinh) + REPN_INTRINSIC_TEST1(acosh) + REPN_INTRINSIC_TEST1(atanh) + REPN_INTRINSIC_TEST2(pow) + + WHEN("pow(x,0)") + { + coek::Model m; + auto x = m.add_variable("x"); + auto p = coek::parameter(); + coek::Expression e = pow(x, p); + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 1); + REQUIRE(repn.linear_coefs.size() == 0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + + WHEN("pow(x,1)") + { + coek::Model m; + auto x = m.add_variable("x"); + auto p = coek::parameter().value(1); + coek::Expression e = pow(x, p); + coek::QuadraticExpr repn; + repn.collect_terms(e); + + REQUIRE(repn.constval == 0); + REQUIRE(repn.linear_coefs.size() == 1); + REQUIRE(repn.linear_coefs[0] == 1.0); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + + WHEN("pow(x,1)") + { + coek::Model m; + auto x = m.add_variable("x"); + auto p = coek::parameter().value(2); + coek::Expression e = pow(x * x, p); + coek::QuadraticExpr repn; + REQUIRE_THROWS(repn.collect_terms(e)); + } + } +} diff --git a/lib/coek/test/test_visitor_simplify.cpp b/lib/coek/test/test_visitor_simplify.cpp new file mode 100644 index 00000000..65737033 --- /dev/null +++ b/lib/coek/test/test_visitor_simplify.cpp @@ -0,0 +1,478 @@ + +#include +#include + +#include "catch2/catch.hpp" +#include "coek/ast/base_terms.hpp" +#include "coek/ast/constraint_terms.hpp" +#include "coek/ast/value_terms.hpp" +#include "coek/ast/expr_terms.hpp" +#include "coek/ast/visitor_fns.hpp" +#include "coek/coek.hpp" +#include "coek/util/io_utils.hpp" + +#define INTRINSIC_TEST1(FN) \ + WHEN(#FN " 1") \ + { \ + { \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1); \ + static std::list baseline \ + = {"[", #FN, "[", "+", "v", std::to_string(1.0), "]", "]"}; \ + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); \ + } \ + } \ + WHEN(#FN " 2") \ + { \ + { \ + auto v = coek::variable("v").lower(0).upper(1).value(0).fixed(true); \ + coek::Expression e = FN(v + 1); \ + double tmp = 1.0 * FN(1.0); \ + static std::list baseline = {std::to_string(tmp)}; \ + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); \ + } \ + } + +#define INTRINSIC_TEST2(FN) \ + WHEN(#FN " 1") \ + { \ + { \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1, v); \ + static std::list baseline \ + = {"[", #FN, "[", "+", "v", std::to_string(1.0), "]", "v", "]"}; \ + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); \ + } \ + } \ + WHEN(#FN " 2") \ + { \ + { \ + auto v = coek::variable("v").lower(0).upper(1).value(0).fixed(true); \ + coek::Expression e = FN(v + 1, v); \ + double tmp = 1.0 * FN(1.0, 0.0); \ + static std::list baseline = {std::to_string(tmp)}; \ + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); \ + } \ + } + +TEST_CASE("simplify_expr", "[smoke]") +{ + SECTION("constant") + { + { + auto p = coek::parameter("p").value(1); + coek::Expression e(3 + p); + + static std::list baseline = {std::to_string(4.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + + SECTION("param") + { + WHEN("simple") + { + { + auto p = coek::parameter("p").value(3); + coek::Expression e = p; + + static std::list baseline = {std::to_string(3.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("nontrivial multiplier") + { + { + auto p = coek::parameter("p").value(3); + coek::Expression e = p / 2; + + static std::list baseline = {std::to_string(1.5)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + } + + SECTION("var") + { + WHEN("unfixed") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = v; + + static std::list baseline = {"v"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("fixed") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3).fixed(true); + coek::Expression e = v; + + static std::list baseline = {std::to_string(3.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("fixed - nontrivial multiplier") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression f(2); + coek::Expression e = v / f; + + static std::list baseline = {"[", "/", "v", std::to_string(2.0), "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + } + + SECTION("monomial") + { + WHEN("unfixed") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = 2 * v; + + static std::list baseline = {"[", "*", std::to_string(2), "v", "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("fixed") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3).fixed(true); + coek::Expression e = 2 * v; + + static std::list baseline = {std::to_string(6.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + } + + SECTION("negate") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3).fixed(true); + coek::Expression e = -(v + 1); + + static std::list baseline = {std::to_string(-4.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + +#if 0 + SECTION("subexpression") + { + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(3); + auto E = coek::subexpression().value(v + 1); + coek::Expression e = E + 2 * (E + 1); + REQUIRE(e.value() == 14); + + coek::MutableNLPExpr repn; + repn.collect_terms(e); + + static std::list constval = {std::to_string(5.0)}; + static std::list coefval0 = {std::to_string(1.0)}; + static std::list coefval1 = {std::to_string(2.0)}; + REQUIRE(repn.mutable_values == false); + REQUIRE(repn.constval->to_list() == constval); + REQUIRE(repn.linear_coefs.size() == 2); + REQUIRE(repn.linear_coefs[0]->to_list() == coefval0); + REQUIRE(repn.linear_coefs[1]->to_list() == coefval1); + REQUIRE(repn.linear_vars[0] == v.repn); + REQUIRE(repn.linear_vars[1] == v.repn); + REQUIRE(repn.quadratic_coefs.size() == 0); + } + } +#endif + + SECTION("plus") + { + WHEN("2 terms") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = v + 1; + + static std::list baseline = {"[", "+", "v", std::to_string(1.0), "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("combine sums") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = (v + 1) + (-2 - v); + + static std::list baseline = {"[", + "+", + "v", + "[", + "+", + "[", + "*", + std::to_string(-1), + "v", + "]", + std::to_string(-2.0), + "]", + std::to_string(1.0), + "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + } + + SECTION("times") + { + WHEN("lhs - param zero") + { + { + auto p = coek::parameter("p"); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = p * v; + + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("lhs - param one") + { + { + auto p = coek::parameter("p").value(1); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = p * v; + + static std::list baseline = {"v"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("lhs - param other") + { + { + auto p = coek::parameter("p").value(2.0); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = p * v; + + static std::list baseline = {"[", "*", std::to_string(2.0), "v", "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("rhs - param zero") + { + { + auto p = coek::parameter("p"); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = v * p; + + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("rhs - param one") + { + { + auto p = coek::parameter("p").value(1); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = v * p; + + static std::list baseline = {"v"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("rhs - param other") + { + { + auto p = coek::parameter("p").value(2.0); + auto v = coek::variable("v").lower(0).upper(1).value(3); + coek::Expression e = v * p; + + static std::list baseline = {"[", "*", "v", std::to_string(2.0), "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("complex quadratic 1a") + { + { + auto v = coek::variable("v").lower(0).upper(1).value(0); + auto w = coek::variable("w").lower(0).upper(1).value(1).fixed(true); + coek::Expression e = (2 + 3 * w + v) * 4; + + static std::list baseline + = {"[", "*", "[", "+", "v", std::to_string(5.0), "]", std::to_string(4.0), "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + } + + SECTION("divide") + { + WHEN("lhs parameter - zero") + { + { + auto p = coek::parameter("p"); + auto w = coek::variable("w").lower(0).upper(1).value(0); + coek::Expression e = p / w; + + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("lhs constant - zero AND rhs constant") + { + { + auto p = coek::parameter("p").value(2.0); + auto w = coek::variable("w").lower(0).upper(1).value(1).fixed(true); + coek::Expression e = p / (w + 1); + + static std::list baseline = {std::to_string(1.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("rhs constant - rhs 1.0") + { + { + auto p = coek::parameter("p").value(1.0); + auto w = coek::variable("w").lower(0).upper(1).value(1); + coek::Expression e = w / p; + + static std::list baseline = {"w"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("rhs constant") + { + { + auto p = coek::parameter("p").value(2.0); + auto w = coek::variable("w").lower(0).upper(1).value(1); + coek::Expression e = w / p; + + static std::list baseline = {"[", "/", "w", std::to_string(2.0), "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + } + + SECTION("constriaints") + { + WHEN("inequality") + { + { + auto p = coek::parameter("p"); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = w + p <= 2; + + static std::list baseline + = {"[", "<=", "-Inf", "w", std::to_string(2.0), "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("equality") + { + { + auto p = coek::parameter("p"); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = w + p == 2; + + static std::list baseline + = {"[", "==", "w", std::to_string(2.000), "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + } + + SECTION("intrinsic funcs"){ + INTRINSIC_TEST1(abs) INTRINSIC_TEST1(ceil) INTRINSIC_TEST1(floor) INTRINSIC_TEST1(exp) + INTRINSIC_TEST1(log) INTRINSIC_TEST1(log10) INTRINSIC_TEST1(sqrt) INTRINSIC_TEST1(sin) + INTRINSIC_TEST1(cos) INTRINSIC_TEST1(tan) INTRINSIC_TEST1(sinh) + INTRINSIC_TEST1(cosh) INTRINSIC_TEST1(tanh) INTRINSIC_TEST1(asin) + INTRINSIC_TEST1(acos) INTRINSIC_TEST1(atan) INTRINSIC_TEST1(asinh) + INTRINSIC_TEST1(acosh) INTRINSIC_TEST1(atanh) INTRINSIC_TEST2(pow)} + + SECTION("pow func") + { + WHEN("lhs constant - 0") + { + { + auto p = coek::parameter("p"); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(p, w); + + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("lhs constant - 1") + { + { + auto p = coek::parameter("p").value(1.0); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(p, w); + + static std::list baseline = {std::to_string(1.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("lhs constant - other") + { + { + auto p = coek::parameter("p").value(2.0); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(p, w); + + static std::list baseline + = {"[", "pow", std::to_string(2.0), "w", "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("rhs constant - 0") + { + { + auto p = coek::parameter("p").value(0.0); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(w, p); + + static std::list baseline = {std::to_string(1.0)}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("rhs constant - 1") + { + { + auto p = coek::parameter("p").value(1.0); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(w, p); + + static std::list baseline = {"w"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("rhs constant - other") + { + { + auto p = coek::parameter("p").value(2.0); + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(w, p); + + static std::list baseline + = {"[", "pow", "w", std::to_string(2.0), "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + WHEN("general") + { + { + auto w = coek::variable("w").lower(0).upper(1).value(3); + auto e = coek::pow(w, 2 + w); + + static std::list baseline + = {"[", "pow", "w", "[", "+", "w", std::to_string(2.0), "]", "]"}; + REQUIRE(simplify_expr(e.repn)->to_list() == baseline); + } + } + } +} diff --git a/lib/coek/test/test_visitor_symdiff.cpp b/lib/coek/test/test_visitor_symdiff.cpp new file mode 100644 index 00000000..4bb4e703 --- /dev/null +++ b/lib/coek/test/test_visitor_symdiff.cpp @@ -0,0 +1,754 @@ + +#include +#include + +#include "catch2/catch.hpp" +#include "coek/ast/base_terms.hpp" +#include "coek/ast/constraint_terms.hpp" +#include "coek/ast/value_terms.hpp" +#include "coek/ast/expr_terms.hpp" +#include "coek/ast/visitor_fns.hpp" +#include "coek/coek.hpp" +#include "coek/util/io_utils.hpp" + +#define REPN_INTRINSIC_TEST1(FN) \ + WHEN(#FN " 1") \ + { \ + coek::Model m; \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1); \ + coek::QuadraticExpr repn; \ + REQUIRE_THROWS_WITH(repn.collect_terms(e), \ + Catch::Matchers::StartsWith( \ + "Nonlinear expressions are not supported for QuadraticExpr")); \ + } \ + WHEN(#FN " 2") \ + { \ + coek::Model m; \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + v.fixed(true); \ + coek::Expression e = FN(v + 1); \ + coek::QuadraticExpr repn; \ + repn.collect_terms(e); \ + REQUIRE(repn.linear_coefs.size() + repn.quadratic_coefs.size() == 0); \ + } + +#define REPN_INTRINSIC_TEST2(FN) \ + WHEN(#FN " 1") \ + { \ + coek::Model m; \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1, v - 1); \ + coek::QuadraticExpr repn; \ + REQUIRE_THROWS_WITH(repn.collect_terms(e), \ + Catch::Matchers::StartsWith( \ + "Nonlinear expressions are not supported for QuadraticExpr")); \ + } \ + WHEN(#FN " 2") \ + { \ + coek::Model m; \ + auto v = coek::variable("v").lower(0).upper(1).value(0).fixed(true); \ + coek::Expression e = FN(v + 1, v - 1); \ + coek::QuadraticExpr repn; \ + repn.collect_terms(e); \ + REQUIRE(repn.linear_coefs.size() + repn.quadratic_coefs.size() == 0); \ + } + +TEST_CASE("symbolic_diff", "[smoke]") +{ + SECTION("constant") + { + coek::Expression f(3); + auto v = coek::variable(); + auto e = f.diff(v); + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(e.to_list() == baseline); + } + + SECTION("param") + { + auto p = coek::parameter().value(3); + coek::Expression f = p; + auto v = coek::variable(); + auto e = f.diff(v); + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(e.to_list() == baseline); + } + + SECTION("var") + { + WHEN("fixed") + { + auto v = coek::variable(); + v.fixed(true); + coek::Expression f = v; + auto e = f.diff(v); + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(e.to_list() == baseline); + } + + WHEN("same") + { + auto v = coek::variable(); + coek::Expression f = v; + auto e = f.diff(v); + static std::list baseline = {std::to_string(1.0)}; + REQUIRE(e.to_list() == baseline); + } + + WHEN("different") + { + auto v = coek::variable(); + auto w = coek::variable(); + coek::Expression f = w; + auto e = f.diff(v); + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(e.to_list() == baseline); + } + } + + SECTION("monomial") + { + WHEN("other") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = 2 * v; + auto e = f.diff(w); + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(e.to_list() == baseline); + } + + WHEN("same") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + coek::Expression f = 2 * v; + auto e = f.diff(v); + static std::list baseline = {std::to_string(2.0)}; + REQUIRE(e.to_list() == baseline); + } + + WHEN("fixed") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + v.fixed(true); + coek::Expression f = 2 * v; + auto e = f.diff(v); + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(e.to_list() == baseline); + } + } + + SECTION("subexpression") + { + auto v = coek::variable("v"); + auto E = coek::subexpression().value(v + 1); + coek::Expression e = E + 2 * (E + v); + auto ans = e.diff(v); + static std::list baseline = {std::to_string(5.0)}; + REQUIRE(ans.to_list() == baseline); + } + + SECTION("plus") + { + WHEN("linear") + { + auto p = coek::parameter(); + auto v = coek::variable("v").lower(0).upper(1).value(0); + coek::Expression f = 2 * (v + v) + v; + auto e = f.diff(v); + static std::list baseline = {std::to_string(5.0)}; + REQUIRE(e.to_list() == baseline); + } + WHEN("simple") + { + auto p = coek::parameter(); + auto v = coek::variable("v").lower(0).upper(1).value(0); + coek::Expression f = 3 * p + 2 * v; + auto e = f.diff(v); + static std::list baseline = {std::to_string(2.0)}; + REQUIRE(e.to_list() == baseline); + } + WHEN("multiple") + { + auto p = coek::parameter(); + auto v = coek::variable("v").lower(0).upper(1).value(0); + coek::Expression f = 7 * v + v; + auto e = f.diff(v); + static std::list baseline = {std::to_string(8.000)}; + REQUIRE(e.to_list() == baseline); + } + } + + SECTION("negate") + { + WHEN("linear") + { + auto p = coek::parameter(); + auto v = coek::variable("v").lower(0).upper(1).value(0); + coek::Expression f = -(v + 1); + auto e = f.diff(v); + static std::list baseline = {std::to_string(-1.0)}; + REQUIRE(e.to_list() == baseline); + } + } + + SECTION("times") + { + WHEN("lhs zero") + { + coek::Expression p; + auto v = coek::variable("v").lower(0).upper(1).value(0); + coek::Expression f = p * v; + auto e = f.diff(v); + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(e.to_list() == baseline); + } + WHEN("lhs constant") + { + coek::Model m; + auto p = coek::parameter("p").value(2); + auto v = m.add_variable("v").lower(0).upper(1).value(0); + coek::Expression f = p * v; + auto e = f.diff(v); + static std::list baseline = {"p"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("rhs zero") + { + coek::Model m; + coek::Expression p; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + coek::Expression f = v * p; + auto e = f.diff(v); + static std::list baseline = {std::to_string(0.0)}; + REQUIRE(e.to_list() == baseline); + } + WHEN("rhs constant") + { + coek::Model m; + auto p = coek::parameter("p").value(2); + auto v = m.add_variable("v").lower(0).upper(1).value(0); + coek::Expression f = v * p; + auto e = f.diff(v); + static std::list baseline = {"p"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("simple quadratic LHS") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = v * w; + auto e = f.diff(v); + static std::list baseline = {"w"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("simple quadratic RHS") + { + coek::Model m; + auto v = m.add_variable("v").lower(0).upper(1).value(0); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = v * w; + auto e = f.diff(w); + static std::list baseline = {"v"}; + REQUIRE(e.to_list() == baseline); + } + } + + SECTION("divide") + { + WHEN("lhs zero") + { + coek::Model m; + auto p = coek::parameter("p"); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = p / w; + auto e = f.diff(w); + static std::list baseline = { + "[", "/", "[", "*", std::to_string(-1.0), "p", "]", "[", "*", "w", "w", "]", "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("rhs nonzero") + { + coek::Model m; + auto p = coek::parameter("p").value(2); + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = w / p; + auto e = f.diff(w); + static std::list baseline = {"[", "/", std::to_string(1.0), "p", "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("rhs polynomial") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = w / (1 + w); + auto e = f.diff(w); + static std::list baseline = {"[", + "+", + "[", + "/", + std::to_string(1.0), + "[", + "+", + std::to_string(1.0), + "w", + "]", + "]", + "[", + "/", + "[", + "*", + std::to_string(-1.0), + "w", + "]", + "[", + "*", + "[", + "+", + std::to_string(1.0), + "w", + "]", + "[", + "+", + std::to_string(1.0), + "w", + "]", + "]", + "]", + "]"}; + REQUIRE(e.to_list() == baseline); + } + } + + SECTION("coverage") + { + WHEN("variable partial plus monomial - 1") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + auto v = m.add_variable("v").lower(0).upper(1).value(0); + coek::Expression f = w * v + v * (2 * w + 1); + auto e = f.diff(w); + static std::list baseline + = {"[", "+", "v", "[", "*", std::to_string(2.0), "v", "]", "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("variable partial plus monomial - 2") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + auto v = m.add_variable("v").lower(0).upper(1).value(0); + coek::Expression f = v * (2 * w + 1); + auto e = f.diff(w); + static std::list baseline = {"[", "*", std::to_string(2.0), "v", "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("constant partial plus monomial") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = 3 * w + 2 * w; + auto e = f.diff(w); + static std::list baseline = {std::to_string(5.0)}; + REQUIRE(e.to_list() == baseline); + } + WHEN("negative monomial") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = -(-w) + (-(-w)); + auto e = f.diff(w); + static std::list baseline = {std::to_string(2.0)}; + REQUIRE(e.to_list() == baseline); + } + } + + SECTION("intrinsic funcs") + { + REPN_INTRINSIC_TEST1(abs) + REPN_INTRINSIC_TEST1(ceil) + REPN_INTRINSIC_TEST1(floor) + WHEN("exp") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = exp(2 * w); + auto e = f.diff(w); + static std::list baseline + = {"[", "*", std::to_string(2.0), "[", "exp", "[", "*", "2", "w", "]", "]", "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("log") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = log(2 * w); + auto e = f.diff(w); + static std::list baseline = {"[", "*", std::to_string(2.0), + "[", "/", std::to_string(1.0), + "[", "*", "2", + "w", "]", "]", + "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("log10") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = log10(2 * w); + auto e = f.diff(w); + static std::list baseline = {"[", "*", std::to_string(2.0), + "[", "/", std::to_string(1.0), + "[", "*", std::to_string(2.302585), + "[", "*", "2", + "w", "]", "]", + "]", "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("sqrt") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = sqrt(2 * w); + auto e = f.diff(w); + static std::list baseline + = {"[", "*", std::to_string(2.0), "[", "pow", "[", "*", "2", + "w", "]", std::to_string(-0.500), "]", "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("sin") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = sin(2 * w); + auto e = f.diff(w); + static std::list baseline + = {"[", "*", std::to_string(2.0), "[", "cos", "[", "*", "2", "w", "]", "]", "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("cos") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = cos(2 * w); + auto e = f.diff(w); + static std::list baseline = { + "[", "*", std::to_string(2.0), "[", "-", "[", "sin", "[", "*", "2", "w", "]", "]", + "]", "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("tan") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = tan(2 * w); + auto e = f.diff(w); + static std::list baseline = {"[", + "*", + std::to_string(2.0), + "[", + "/", + std::to_string(1.0), + "[", + "pow", + "[", + "cos", + "[", + "*", + "2", + "w", + "]", + "]", + std::to_string(2.0), + "]", + "]", + "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("sinh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = sinh(2 * w); + auto e = f.diff(w); + static std::list baseline + = {"[", "*", std::to_string(2.0), "[", "cosh", "[", "*", "2", "w", "]", "]", "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("cosh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = cosh(2 * w); + auto e = f.diff(w); + static std::list baseline + = {"[", "*", std::to_string(2.0), "[", "sinh", "[", "*", "2", "w", "]", "]", "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("tanh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = tanh(2 * w); + auto e = f.diff(w); + static std::list baseline = {"[", + "*", + std::to_string(2.0), + "[", + "+", + std::to_string(1.0), + "[", + "*", + std::to_string(-1.0), + "[", + "pow", + "[", + "tan", + "[", + "*", + "2", + "w", + "]", + "]", + std::to_string(2.0), + "]", + "]", + "]", + "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("asin") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = asin(2 * w); + auto e = f.diff(w); + static std::list baseline = {"[", + "*", + std::to_string(2.0), + "[", + "/", + std::to_string(1.0), + "[", + "sqrt", + "[", + "+", + std::to_string(1.0), + "[", + "-", + "[", + "*", + "[", + "*", + "2", + "w", + "]", + "[", + "*", + "2", + "w", + "]", + "]", + "]", + "]", + "]", + "]", + "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("acos") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = acos(2 * w); + auto e = f.diff(w); + static std::list baseline = {"[", + "*", + std::to_string(2.0), + "[", + "-", + "[", + "/", + std::to_string(1.0), + "[", + "sqrt", + "[", + "+", + std::to_string(1.0), + "[", + "-", + "[", + "*", + "[", + "*", + "2", + "w", + "]", + "[", + "*", + "2", + "w", + "]", + "]", + "]", + "]", + "]", + "]", + "]", + "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("atan") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = atan(2 * w); + auto e = f.diff(w); + static std::list baseline = {"[", "*", std::to_string(2.0), + "[", "/", std::to_string(1.0), + "[", "+", std::to_string(1.0), + "[", "*", "[", + "*", "2", "w", + "]", "[", "*", + "2", "w", "]", + "]", "]", "]", + "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("asinh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = asinh(2 * w); + auto e = f.diff(w); + static std::list baseline = {"[", + "*", + std::to_string(2.0), + "[", + "/", + std::to_string(1.0), + "[", + "sqrt", + "[", + "+", + std::to_string(1.0), + "[", + "*", + "[", + "*", + "2", + "w", + "]", + "[", + "*", + "2", + "w", + "]", + "]", + "]", + "]", + "]", + "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("acosh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = acosh(2 * w); + auto e = f.diff(w); + static std::list baseline = {"[", "*", std::to_string(2.0), + "[", "/", std::to_string(1.0), + "[", "sqrt", "[", + "+", "[", "*", + "[", "*", "2", + "w", "]", "[", + "*", "2", "w", + "]", "]", std::to_string(-1.0), + "]", "]", "]", + "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("atanh") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = atanh(2 * w); + auto e = f.diff(w); + static std::list baseline = {"[", + "*", + std::to_string(2.0), + "[", + "/", + std::to_string(1.0), + "[", + "+", + "[", + "*", + "[", + "*", + "2", + "w", + "]", + "[", + "*", + "2", + "w", + "]", + "]", + std::to_string(-1.0), + "]", + "]", + "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("pow - 1") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = pow(2 * w, 3); + auto e = f.diff(w); + static std::list baseline = {"[", + "*", + std::to_string(2.0), + "[", + "*", + std::to_string(3.0), + "[", + "pow", + "[", + "*", + "2", + "w", + "]", + "[", + "+", + std::to_string(3.0), + std::to_string(-1.0), + "]", + "]", + "]", + "]"}; + REQUIRE(e.to_list() == baseline); + } + WHEN("pow - 2") + { + coek::Model m; + auto w = m.add_variable("w").lower(0).upper(1).value(0); + coek::Expression f = pow(3, 2 * w); + auto e = f.diff(w); + static std::list baseline = {"[", "*", std::to_string(2.0), + "[", "*", std::to_string(1.098612), + "[", "pow", std::to_string(3.0), + "[", "*", "2", + "w", "]", "]", + "]", "]"}; + REQUIRE(e.to_list() == baseline); + } + } +} diff --git a/lib/coek/test/test_visitor_writer.cpp b/lib/coek/test/test_visitor_writer.cpp new file mode 100644 index 00000000..9db945b0 --- /dev/null +++ b/lib/coek/test/test_visitor_writer.cpp @@ -0,0 +1,247 @@ + +#include +#include + +#include "catch2/catch.hpp" +#include "coek/ast/base_terms.hpp" +#include "coek/ast/constraint_terms.hpp" +#include "coek/ast/value_terms.hpp" +#include "coek/ast/expr_terms.hpp" +#include "coek/ast/visitor_fns.hpp" +#include "coek/coek.hpp" +#include "coek/util/io_utils.hpp" + +#define WRITER_INTRINSIC_TEST1(FN) \ + WHEN(#FN) \ + { \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1); \ + std::stringstream sstr; \ + sstr << e; \ + REQUIRE(sstr.str() == #FN "(v + 1)"); \ + } + +#define WRITER_INTRINSIC_TEST2(FN) \ + WHEN(#FN) \ + { \ + auto v = coek::variable("v").lower(0).upper(1).value(0); \ + coek::Expression e = FN(v + 1, 2 * v); \ + std::stringstream sstr; \ + sstr << e; \ + REQUIRE(sstr.str() == #FN "(v + 1, 2*v)"); \ + } + +TEST_CASE("expr_writer", "[smoke]") +{ + SECTION("constant") + { + coek::Expression e(3); + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "3"); + } + + SECTION("param") + { + WHEN("unnamed ") + { + auto q = coek::parameter().value(3); + std::stringstream sstr; + sstr << q; + REQUIRE(sstr.str() == std::to_string(3.0)); + } + WHEN("named ") + { + auto q = coek::parameter("q").value(3); + std::stringstream sstr; + sstr << q; + REQUIRE(sstr.str() == "q"); + } + } + + SECTION("var") + { + WHEN("unnamed ") + { + auto v = coek::variable(""); + std::stringstream sstr; + sstr << v; + REQUIRE(sstr.str()[0] == 'X'); + } + WHEN("named ") + { + auto v = coek::variable("v").lower(0).upper(1).value(0); + std::stringstream sstr; + sstr << v; + REQUIRE(sstr.str() == "v"); + } + } + + SECTION("monomial") + { + WHEN("unweighted ") + { + auto v = coek::variable("v"); + coek::Expression e = 1 * v; + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "v"); + } + WHEN("weighted ") + { + auto v = coek::variable("v"); + coek::Expression e = 2 * v; + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "2*v"); + } + } + + SECTION("constraint") + { + WHEN("lt ") + { + auto v = coek::variable("v"); + auto e = v < 1; + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "v < 1"); + } + WHEN("leq ") + { + auto v = coek::variable("v"); + auto e = v <= 1; + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "v <= 1"); + } + WHEN("eq ") + { + auto v = coek::variable("v"); + auto e = v == 1; + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "v == 1"); + } + } + + SECTION("negate") + { + auto v = coek::variable("v"); + coek::Expression e = -(v + 1); + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "- (v + 1)"); + } + + SECTION("subexpression") + { + auto v = coek::variable("v"); + auto E = coek::subexpression().value(v + 1); + coek::Expression e = E + 2 * (E + 1); + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "v + 1 + (2)*(v + 1 + 1)"); + } + + SECTION("plus") + { + WHEN("2 terms") + { + auto v = coek::variable("v").lower(0).upper(1).value(0); + coek::Expression e = v + 1; + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "v + 1"); + } + WHEN("combine sums") + { + auto v = coek::variable("v").lower(0).upper(1).value(0); + coek::Expression e = v + 1 + (-1 - v); + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "v + 1 + -1 + -1*v"); + } + WHEN("3 terms sums") + { + auto v = coek::variable("v").lower(0).upper(1).value(0); + coek::Expression e = v + 1 + 3 * (-1 - v); + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "v + 1 + (3)*(-1 + -1*v)"); + } + } + + SECTION("times") + { + auto v = coek::variable("v").lower(0).upper(1).value(0); + auto w = coek::variable("w").lower(0).upper(1).value(0); + coek::Expression e = v * (w + 1); + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "(v)*(w + 1)"); + } + + SECTION("divide") + { + auto v = coek::variable("v").lower(0).upper(1).value(0); + auto w = coek::variable("w").lower(0).upper(1).value(0); + coek::Expression e = v / (w + 1); + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "(v)/(w + 1)"); + } + + SECTION("intrinsic funcs"){ + + // clang-format off + + WRITER_INTRINSIC_TEST1(abs) + WRITER_INTRINSIC_TEST1(ceil) + WRITER_INTRINSIC_TEST1(floor) + WRITER_INTRINSIC_TEST1(exp) + WRITER_INTRINSIC_TEST1(log) + WRITER_INTRINSIC_TEST1(log10) + WRITER_INTRINSIC_TEST1(sqrt) + WRITER_INTRINSIC_TEST1(sin) + WRITER_INTRINSIC_TEST1(cos) + WRITER_INTRINSIC_TEST1(tan) + WRITER_INTRINSIC_TEST1(sinh) + WRITER_INTRINSIC_TEST1(cosh) + WRITER_INTRINSIC_TEST1(tanh) + WRITER_INTRINSIC_TEST1(asin) + WRITER_INTRINSIC_TEST1(acos) + WRITER_INTRINSIC_TEST1(atan) + WRITER_INTRINSIC_TEST1(asinh) + WRITER_INTRINSIC_TEST1(acosh) + WRITER_INTRINSIC_TEST1(atanh) + WRITER_INTRINSIC_TEST2(pow)} + + // clang-format on + + SECTION("affine_expression1") + { + std::vector v(4); + std::vector w(4); + for (unsigned int i = 0; i < 4; i++) { + v[i] = coek::variable("v[" + std::to_string(i) + "]").lower(0).upper(1).value(0); + w[i] = i + 1; + } + coek::Expression e = affine_expression(w, v, 5.0); + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "5 + v[0] + 2*v[1] + 3*v[2] + 4*v[3]"); + } + + SECTION("affine_expression") + { + std::vector v(4); + for (unsigned int i = 0; i < 4; i++) { + v[i] = coek::variable("v[" + std::to_string(i) + "]").lower(0).upper(1).value(0); + } + coek::Expression e = affine_expression(v, 5.0); + std::stringstream sstr; + sstr << e; + REQUIRE(sstr.str() == "5 + v[0] + v[1] + v[2] + v[3]"); + } +} diff --git a/lib/coek/test/test_writers.cpp b/lib/coek/test/test_writers.cpp index f82536e3..cdf753c6 100644 --- a/lib/coek/test/test_writers.cpp +++ b/lib/coek/test/test_writers.cpp @@ -323,6 +323,13 @@ void testing5(coek::Model& model) model.add(coek::objective(x)); } +void testing6(coek::Model& model) +{ + auto x = model.add_variable("x").lower(0).upper(1).value(0); + auto q = coek::parameter("q").value(2); + model.add(coek::objective(-q * x * x)); +} + #ifdef COEK_WITH_COMPACT_MODEL void compact1(coek::CompactModel& model) { @@ -356,7 +363,7 @@ bool run_test(ModelType& model, const std::string& name, const std::string& suff std::string baseline = currdir + "/baselines/" + fname; model.write(fname); auto same = compare_files(fname, baseline); - // std::cout << "name " << name << " " << same << std::endl; + // std::cout << "name " << name << " " << same << " " << fname << std::endl; if (same) { if (std::remove(fname.c_str()) != 0) return false; } @@ -367,18 +374,18 @@ bool run_test(ModelType& model, const std::string& name, const std::string& suff TEST_CASE("model_writer", "[smoke]") { - std::vector nonlinear = {"nl", "ostrnl" + std::vector nonlinear = {"ostrnl" #ifdef WITH_FMTLIB , - "fmtnl" + "nl", "fmtnl" #endif }; - std::vector linear = {"lp", - "nl", - "ostrlp", + std::vector linear = {"ostrlp", "ostrnl" #ifdef WITH_FMTLIB , + "lp", + "nl", "fmtlp", "fmtnl" #endif @@ -388,6 +395,7 @@ TEST_CASE("model_writer", "[smoke]") SECTION("error1") { error1(model); +#ifdef WITH_FMTLIB REQUIRE_THROWS_WITH(model.write("error1.nl"), "Error writing NL file: Model expressions contain variable 'y' " "that is not declared in the model."); @@ -396,10 +404,12 @@ TEST_CASE("model_writer", "[smoke]") "Error writing NL file: Model expressions contain variable 'y' " "that is not declared in the model."); std::remove("error1.fmtnl"); +#endif REQUIRE_THROWS_WITH(model.write("error1.ostrnl"), "Error writing NL file: Model expressions contain variable 'y' " "that is not declared in the model."); std::remove("error1.ostrnl"); +#ifdef WITH_FMTLIB REQUIRE_THROWS_WITH(model.write("error1.lp"), "Error writing LP file: Model expressions contain variable that is " "not declared in the model."); @@ -408,12 +418,16 @@ TEST_CASE("model_writer", "[smoke]") "Error writing LP file: Model expressions contain variable that is " "not declared in the model."); std::remove("error1.fmtlp"); +#endif REQUIRE_THROWS_WITH(model.write("error1.ostrlp"), "Error writing LP file: Model expressions contain variable that is " "not declared in the model."); std::remove("error1.ostrlp"); } +#if 0 + TODO - Revisit this test + SECTION("error2") { error2(model); @@ -436,6 +450,10 @@ TEST_CASE("model_writer", "[smoke]") "Error writing LP file: No objectives specified!"); std::remove("error2.ostrlp"); } +#endif + +#if 0 + TODO - Add tests with multiple objectives SECTION("error3") { @@ -459,25 +477,30 @@ TEST_CASE("model_writer", "[smoke]") "Error writing LP file: More than one objective defined!"); std::remove("error3.ostrlp"); } +#endif SECTION("error4") { error4(model); +#ifdef WITH_FMTLIB REQUIRE_THROWS_WITH(model.write("error3.nl"), "Error writing NL file: Unexpected index parameter."); std::remove("error3.nl"); REQUIRE_THROWS_WITH(model.write("error3.fmtnl"), "Error writing NL file: Unexpected index parameter."); std::remove("error3.fmtnl"); +#endif REQUIRE_THROWS_WITH(model.write("error3.ostrnl"), "Error writing NL file: Unexpected index parameter."); std::remove("error3.ostrnl"); +#ifdef WITH_FMTLIB REQUIRE_THROWS_WITH(model.write("error3.lp"), "Error writing LP file: Unexpected index parameter."); std::remove("error3.lp"); REQUIRE_THROWS_WITH(model.write("error3.fmtlp"), "Error writing LP file: Unexpected index parameter."); std::remove("error3.fmtlp"); +#endif REQUIRE_THROWS_WITH(model.write("error3.ostrlp"), "Error writing LP file: Unexpected index parameter."); std::remove("error3.ostrlp"); @@ -585,6 +608,12 @@ TEST_CASE("model_writer", "[smoke]") for (const std::string& suffix : linear) REQUIRE(run_test(model, "testing5", suffix)); } + SECTION("testing6") + { + testing6(model); + for (const std::string& suffix : nonlinear) REQUIRE(run_test(model, "testing6", suffix)); + } + // TODO - Add separate NLP writer tests to confirm the variable mappings SECTION("testing1-nlp") { diff --git a/lib/poek/poek/tests/test_expr.py b/lib/poek/poek/tests/test_expr.py index db3e9a9c..2b9d7e06 100644 --- a/lib/poek/poek/tests/test_expr.py +++ b/lib/poek/poek/tests/test_expr.py @@ -17,7 +17,7 @@ def test_expr(self): self.assertEqual(p.value, 2) e = p + 3 self.assertEqual(e.value, 5) - with self.assertRaisesRegex(AttributeError, "can't set attribute"): + with self.assertRaisesRegex(AttributeError, "property of 'expression' object has no setter"): e.value = 0 def test_constraint(self): @@ -25,7 +25,7 @@ def test_constraint(self): self.assertEqual(p.value, 2) e = p < 3 self.assertEqual(e.value, 2) - with self.assertRaisesRegex(AttributeError, "can't set attribute"): + with self.assertRaisesRegex(AttributeError, "property of 'constraint' object has no setter"): e.value = 0 def test_param1(self): diff --git a/lib/pycoek/pybind11/pycoek_pybind11.cpp b/lib/pycoek/pybind11/pycoek_pybind11.cpp index c0df47e0..7ce66ffb 100644 --- a/lib/pycoek/pybind11/pycoek_pybind11.cpp +++ b/lib/pycoek/pybind11/pycoek_pybind11.cpp @@ -267,7 +267,7 @@ Expression construct_linear_expression(std::vector coefs, std::vector::iterator& it, std::list::iterator& end) { @@ -577,7 +577,12 @@ VariableMap variable_fn(coek::ConcreteSet& index_set, py::kwargs kwargs) VariableArray variable_fn(std::vector& dimen, py::kwargs kwargs) { - VariableArray tmp(dimen); + std::vector _dimen(dimen.size()); + for (size_t i=0; i= 0); + _dimen[i] = static_cast(dimen[i]); + } + VariableArray tmp(_dimen); set_kwargs(tmp, kwargs); return tmp; } @@ -1269,7 +1274,7 @@ PYBIND11_MODULE(pycoek_pybind11, m) "__iter__", [](const coek::VariableArray& va) { typedef coek::VecKeyIterator::const_iterator> vec_key_t; - return py::make_iterator(vec_key_t(va.begin()), vec_key_t(va.end())); + return py::make_iterator(vec_key_t(va.cbegin()), vec_key_t(va.cend())); /* if (va.dim() == 0) return py::make_iterator(vec_key_t(va.begin()), vec_key_t(va.end())); diff --git a/test/aml_comparisons/CMakeLists.txt b/test/aml_comparisons/CMakeLists.txt index 7896c11f..699a5993 100644 --- a/test/aml_comparisons/CMakeLists.txt +++ b/test/aml_comparisons/CMakeLists.txt @@ -4,7 +4,7 @@ PROJECT(aml_comparisons) # # For now, let's just assume we have C++17 # -set(CMAKE_CXX_STANDARD 17) +#set(CMAKE_CXX_STANDARD 17) ##################### SubDirectories ##################### # coek diff --git a/test/aml_comparisons/coek/CMakeLists.txt b/test/aml_comparisons/coek/CMakeLists.txt index cb9ffaaa..91974811 100644 --- a/test/aml_comparisons/coek/CMakeLists.txt +++ b/test/aml_comparisons/coek/CMakeLists.txt @@ -5,19 +5,19 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/..) set(sources - models/jump/lqcp_array.cpp models/jump/lqcp_scalar.cpp - models/jump/fac_array.cpp models/jump/fac_scalar.cpp - models/misc/pmedian_array.cpp models/misc/pmedian.cpp - models/misc/nqueens_array.cpp models/misc/nqueens.cpp - models/misc/knapsack_array.cpp models/misc/knapsack.cpp ) if (CMAKE_CXX_STANDARD GREATER_EQUAL 17) list(APPEND sources + models/jump/fac_array.cpp + models/jump/lqcp_array.cpp + models/misc/pmedian_array.cpp + models/misc/nqueens_array.cpp + models/misc/knapsack_array.cpp models/jump/lqcp_compact.cpp models/jump/lqcp_map.cpp ) diff --git a/test/aml_comparisons/coek/models/coek_models.hpp b/test/aml_comparisons/coek/models/coek_models.hpp index ef19bccf..f377e843 100644 --- a/test/aml_comparisons/coek/models/coek_models.hpp +++ b/test/aml_comparisons/coek/models/coek_models.hpp @@ -6,41 +6,54 @@ // // jump // -void fac_array(coek::Model& model, size_t F); void fac_scalar(coek::Model& model, size_t F); -void lqcp_array(coek::Model& model, size_t N); void lqcp_scalar(coek::Model& model, size_t N); -#ifdef COEK_WITH_COMPACT_MODEL +#if __cpp_lib_variant +void fac_array(coek::Model& model, size_t F); +void lqcp_array(coek::Model& model, size_t N); +# ifdef COEK_WITH_COMPACT_MODEL void lqcp_compact(coek::CompactModel& model, size_t N); void lqcp_map(coek::Model& model, size_t N); +# endif #endif // // misc // -void knapsack_array(coek::Model& model, size_t N); void knapsack_scalar(coek::Model& model, size_t N); -void nqueens_array(coek::Model& model, size_t N); void nqueens_scalar(coek::Model& model, size_t N); -void pmedian_array(coek::Model& model, size_t N, size_t P); void pmedian_scalar(coek::Model& model, size_t N, size_t P); +#if __cpp_lib_variant +void knapsack_array(coek::Model& model, size_t N); +void nqueens_array(coek::Model& model, size_t N); +void pmedian_array(coek::Model& model, size_t N, size_t P); +#endif inline void print_models(std::ostream& os) { - os << " fac-array N\n" - " fac-scalar N\n" - " knapsack-array N\n" - " knapsack-scalar N\n" - " lqcp-array N\n" + os << +#if __cpp_lib_variant + " fac-array N\n" +#endif + " fac-scalar N\n" +#if __cpp_lib_variant + " knapsack-array N\n" +#endif + " knapsack-scalar N\n" + " lqcp-array N\n" #ifdef COEK_WITH_COMPACT_MODEL - " lqcp-compact N\n" - " lqcp-map N\n" + " lqcp-compact N\n" + " lqcp-map N\n" +#endif + " lqcp-scalar N\n" +#if __cpp_lib_variant + " nqueens-array N\n" +#endif + " nqueens-scalar N\n" +#if __cpp_lib_variant + " pmedian-array N P\n" #endif - " lqcp-scalar N\n" - " nqueens-array N\n" - " nqueens-scalar N\n" - " pmedian-array N P\n" - " pmedian-scalar N P\n"; + " pmedian-scalar N P\n"; } inline void check_data(const std::string& name, const std::vector& data, size_t num) @@ -59,19 +72,22 @@ inline void create_instance(coek::Model& model, const std::string& name, // // jump // +#if __cpp_lib_variant else if (name == "fac-array") { check_data(name, data, 1); fac_array(model, data[0]); } +#endif else if (name == "fac-scalar") { check_data(name, data, 1); fac_scalar(model, data[0]); } +#if __cpp_lib_variant else if (name == "lqcp-array") { check_data(name, data, 1); lqcp_array(model, data[0]); } -#ifdef COEK_WITH_COMPACT_MODEL +# ifdef COEK_WITH_COMPACT_MODEL else if (name == "lqcp-compact") { check_data(name, data, 1); coek::CompactModel cmodel; @@ -82,6 +98,7 @@ inline void create_instance(coek::Model& model, const std::string& name, check_data(name, data, 1); lqcp_map(model, data[0]); } +# endif #endif else if (name == "lqcp-scalar") { check_data(name, data, 1); @@ -90,26 +107,32 @@ inline void create_instance(coek::Model& model, const std::string& name, // // misc // +#if __cpp_lib_variant else if (name == "knapsack-array") { check_data(name, data, 1); knapsack_array(model, data[0]); } +#endif else if (name == "knapsack-scalar") { check_data(name, data, 1); knapsack_scalar(model, data[0]); } +#if __cpp_lib_variant else if (name == "nqueens-array") { check_data(name, data, 1); nqueens_array(model, data[0]); } +#endif else if (name == "nqueens-scalar") { check_data(name, data, 1); nqueens_scalar(model, data[0]); } +#if __cpp_lib_variant else if (name == "pmedian-array") { check_data(name, data, 2); pmedian_array(model, data[0], data[1]); } +#endif else if (name == "pmedian-scalar") { check_data(name, data, 2); pmedian_scalar(model, data[0], data[1]); diff --git a/tpl/CMakeLists.txt b/tpl/CMakeLists.txt index e9726e52..bcb9c30c 100644 --- a/tpl/CMakeLists.txt +++ b/tpl/CMakeLists.txt @@ -1,6 +1,11 @@ cmake_minimum_required(VERSION 3.12) project(coek_tpl) +# Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24: +if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") + cmake_policy(SET CMP0135 NEW) +endif() + set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}) set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/lib/cmake;${CMAKE_MODULE_PATH}) @@ -14,13 +19,15 @@ option(insecure_downloads "If enabled, then downloads third party software witho option(BUILD_SHARED_LIBS "Build using shared libraries" ON) # Install options -option(install_catch2 "Install Catch2" OFF) +option(install_asl "Install the ASL library" OFF) +option(install_catch2 "Install the Catch2 library" OFF) option(install_cppad "Install the CppAD autograd library" OFF) option(install_fmtlib "Install the FMT library" OFF) option(install_pybind11 "Install the Pybind11 library" OFF) option(install_rapidjson "Install the RapidJson library" OFF) option(install_all_tpls "Install the all third-party libraries" ON) if (install_all_tpls) + set(install_asl ON) set(install_catch2 ON) set(install_cppad ON) set(install_fmtlib ON) @@ -30,21 +37,22 @@ endif() option(use_superbuild "Use superbuild logic to install dependencies" ON) set(install_dependencies OFF) if (use_superbuild) - if (install_catch2 OR install_cppad OR install_fmtlib OR install_pybind11 OR install_rapidjson) + if (install_catch2 OR install_cppad OR install_fmtlib OR install_pybind11 OR install_rapidjson OR install_asl) set(build_dependencies ON) endif() endif() -if (install_catch2 OR install_cppad OR install_fmtlib OR install_pybind11 OR install_rapidjson) +if (install_catch2 OR install_cppad OR install_fmtlib OR install_pybind11 OR install_rapidjson OR install_asl) SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib;${CMAKE_INSTALL_PREFIX}/lib64") endif() # Configuration options +option(with_asl "Use the ASL autograd library" OFF) option(with_catch2 "Add tests using Catch2" OFF) -option(with_fmtlib "Use the fmtlib library" OFF) -option(with_rapidjson "Add support for reading JSON/JPOF files" OFF) option(with_cppad "Use the CppAD autograd library" OFF) -option(with_pybind11 "Use the pybind11 library" OFF) option(with_cppyy "Use the cppyy library" OFF) +option(with_fmtlib "Use the fmtlib library" OFF) +option(with_pybind11 "Use the pybind11 library" OFF) +option(with_rapidjson "Add support for reading JSON/JPOF files" OFF) include(download_dir) @@ -56,6 +64,15 @@ if (build_dependencies) return() endif() +if (with_asl AND (NOT install_asl)) + find_package(ASL) + if (ASL_FOUND) + else() + message("ERROR - ASL was not installed") + set(with_asl OFF) + endif() +endif() + if (with_catch2 AND (NOT install_catch2)) find_package(Catch2) if (catch2_FOUND) @@ -84,6 +101,7 @@ if (with_rapidjson AND (NOT install_rapidjson)) endif() message("TPL SUMMARY") +message("-- ASL: ${with_asl}") message("-- Catch2: ${with_catch2}") message("-- CppAD: ${with_cppad}") message("-- RapidJSON: ${with_rapidjson}") diff --git a/tpl/cmake/BuildThirdParty.cmake b/tpl/cmake/BuildThirdParty.cmake index 87438c17..0fc40d2f 100644 --- a/tpl/cmake/BuildThirdParty.cmake +++ b/tpl/cmake/BuildThirdParty.cmake @@ -48,6 +48,27 @@ endmacro() macro(setup_builds) set(tpls) + # ASL + set(asl_available OFF CACHE BOOL "ASL is available") + if (install_asl) + ExternalProject_Add(libasl + #${asl_revision} # specified in thirdparty.cmake + PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build/ASL + DOWNLOAD_DIR ${download_dir} + GIT_REPOSITORY "https://github.com/whart222/asl.git" + SOURCE_DIR "downloads/ASL" + INSTALL_DIR ${CMAKE_INSTALL_PREFIX} + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} + -DBUILD_SHARED_LIBS=ON + -DCMAKE_BUILD_TYPE=Release + BUILD_ALWAYS FALSE + EXCLUDE_FROM_ALL TRUE + ) + set(asl_available ON) + list(APPEND tpls libasl) + endif() + # Catch set(catch2_available OFF CACHE BOOL "Catch2 is available") if (install_catch2) @@ -87,6 +108,7 @@ macro(setup_builds) CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -Dcppad_prefix=${CMAKE_INSTALL_PREFIX} + -DCMAKE_BUILD_TYPE=Release BUILD_ALWAYS FALSE EXCLUDE_FROM_ALL TRUE ) @@ -94,6 +116,7 @@ macro(setup_builds) list(APPEND tpls cppad) endif() + # TODO: Assess whether fmtlib should be installed with Release type: -DCMAKE_BUILD_TYPE=Release # fmtlib set(fmtlib_available OFF CACHE BOOL "FMT is available") if (install_fmtlib) @@ -131,7 +154,6 @@ macro(setup_builds) SOURCE_DIR ${source_dir} INSTALL_DIR ${CMAKE_INSTALL_PREFIX} CMAKE_ARGS - -DPYBIND11_MASTER_PROJECT=OFF -DPYBIND11_INSTALL=ON -DPYBIND11_TEST=OFF -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} @@ -167,6 +189,7 @@ macro(setup_builds) endif() MESSAGE("Configured to Install Third Party Packages (use 'make install_tpls')") + MESSAGE(" ASL Library: ${asl_available}") MESSAGE(" Catch2 Library: ${catch2_available}") MESSAGE(" CppAD Library: ${cppad_available}") MESSAGE(" FMT Library: ${fmtlib_available}") diff --git a/tpl/thirdparty.cmake b/tpl/thirdparty.cmake index 71da4008..e0216c68 100644 --- a/tpl/thirdparty.cmake +++ b/tpl/thirdparty.cmake @@ -11,30 +11,30 @@ add_revision(catch2 SRC Catch2-2.13.6 URL "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.6.tar.gz" - URL_MD5 c7c7ef181b9e08418fd9f2ef8159d03f + URL_HASH SHA256=48dfbb77b9193653e4e72df9633d2e0383b9b625a47060759668480fdf24fbd4 ) add_revision(cppad SRC CppAD-20220000.5 URL "https://github.com/coin-or/CppAD/archive/refs/tags/20220000.5.tar.gz" - URL_MD5 0e9694fc6f8e037b0416d9a4d57e8ef2 + URL_HASH SHA256=9fb4562f6169855eadcd86ac4671593d1c0edf97bb6ce7cbb28e19af2bfc165e ) -add_revision(rapidjson - SRC rapidjson-1.1.0 - URL "https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.tar.gz" - URL_MD5 badd12c511e081fec6c89c43a7027bce +add_revision(fmtlib + SRC "fmt-8.0.0" + URL "https://github.com/fmtlib/fmt/archive/refs/tags/8.0.0.tar.gz" + URL_HASH SHA256=7bce0e9e022e586b178b150002e7c2339994e3c2bbe44027e9abb0d60f9cce83 ) add_revision(pybind11 - SRC "pybind11-2.6.2" - URL "https://github.com/pybind/pybind11/archive/refs/tags/v2.6.2.tar.gz" - URL_MD5 c5ea9c4c57082e05efe14e4b34323bfd + SRC "pybind11-2.10.3" + URL "https://github.com/pybind/pybind11/archive/refs/tags/v2.10.3.tar.gz" + URL_HASH SHA256=5d8c4c5dda428d3a944ba3d2a5212cb988c2fae4670d58075a5a49075a6ca315 ) -add_revision(fmtlib - SRC "fmt-8.0.0" - URL "https://github.com/fmtlib/fmt/archive/refs/tags/8.0.0.tar.gz" - URL_MD5 001d59c967115682aed0e836ba5753a8 +add_revision(rapidjson + SRC rapidjson-1.1.0 + URL "https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.tar.gz" + URL_HASH SHA256=bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e )