From cc6b49b9beb57617fbd283d6b605cccd28e6930e Mon Sep 17 00:00:00 2001 From: Clara Witte <clara.witte@avt.rwth-aachen.de> Date: Fri, 26 May 2023 13:40:56 +0200 Subject: [PATCH] Release version v0.6.0 --- .gitlab-ci.yml | 32 +- .gitmodules | 15 +- AUTHORS | 9 +- CMakeLists.txt | 144 +- ReleaseNotes.txt | 23 + cmake/MAiNGOsourceFiles.cmake | 11 +- cmake/MAiNGOversion.cmake | 2 +- cmake/addDependency.cmake | 2 +- dep/babbase | 2 +- dep/blas | 2 +- dep/clp | 2 +- dep/cplex | 2 +- dep/fadbad | 2 +- dep/googletest | 1 + dep/ipopt | 2 +- dep/json | 2 +- dep/lapack | 2 +- dep/libale | 2 +- dep/mcpp | 2 +- dep/melon | 2 +- dep/pybind11 | 2 +- doc/Readme.md | 5 + doc/images/ParamEstProblem.png | Bin 0 -> 10438 bytes doc/manual.dox | 395 ++--- .../01_BasicExample/examplePythonInterface.py | 20 +- examples/01_BasicExample/problem.h | 14 +- examples/01_BasicExample/problem.txt | 11 +- .../problemCaseStudy2LCOE.txt | 320 ++--- .../bayesianOptimizationReducedSpace.py | 2 +- examples/07_GrowingDatasets/Readme.md | 29 + .../growingDatasets_simple.py | 40 + .../problem_growingDatasets_simple.h | 126 ++ .../problem_growingDatasets_simple.txt | 43 + examples/MAiNGOSettings.txt | 28 + examples/mainCppApi.cpp | 9 +- inc/MAiNGO.h | 55 +- inc/MAiNGOException.h | 10 +- inc/MAiNGOMpiException.h | 10 +- inc/MAiNGOevaluator.h | 604 ++++++-- inc/MAiNGOmodel.h | 46 +- inc/bab.h | 95 +- inc/cApi.h | 48 + inc/constraint.h | 2 +- inc/decayingProbability.h | 30 + inc/evaluationContainer.h | 12 +- inc/lbp.h | 43 +- inc/lbpClp.h | 7 +- inc/lbpCplex.h | 5 +- inc/lbpDagObj.h | 26 + inc/lbpInterval.h | 2 +- inc/logger.h | 192 +-- inc/mpiUtilities.h | 22 +- inc/nrtlSubroutines.h | 122 ++ inc/pointIsWithinNodeBounds.h | 45 + inc/program.h | 14 +- inc/programParser.h | 13 +- inc/settings.h | 105 +- inc/ubp.h | 26 + inc/ubpDagObj.h | 86 +- inc/ubpLazyQuadExpr.h | 1266 +++++++++++++++++ inc/ubpQuadExpr.h | 753 +--------- inc/usingAdditionalIntrinsicFunctions.h | 62 + inc/variableLister.h | 68 +- maingopy/__init__maingopy_and_melonpy.py.in | 2 +- maingopy/__init__only_maingopy.py.in | 2 +- maingopy/_maingopy.cpp | 7 + .../tests/individualPythonTests/testSolver.py | 59 +- src/MAiNGO.cpp | 431 ++++-- src/MAiNGOevaluationFunctions.cpp | 17 +- src/MAiNGOgetterFunctions.cpp | 166 +++ src/MAiNGOprintingFunctions.cpp | 256 ++-- src/MAiNGOsetOption.cpp | 122 +- src/MAiNGOtoOtherLanguage.cpp | 41 +- src/aleModel.cpp | 5 + src/bab.cpp | 446 +++++- src/babMpi.cpp | 71 +- src/cApi.cpp | 118 ++ src/lbp.cpp | 378 ++--- src/lbpClp.cpp | 149 +- src/lbpCplex.cpp | 143 +- src/lbpDagObj.cpp | 78 +- src/lbpFactory.cpp | 13 +- src/lbpInterval.cpp | 22 +- src/lbpLinearizationStrats.cpp | 27 +- src/logger.cpp | 237 ++- src/pointIsWithinNodeBounds.cpp | 48 + src/programParser.cpp | 39 +- src/ubp.cpp | 359 +++-- src/ubpClp.cpp | 29 +- src/ubpCplex.cpp | 47 +- src/ubpFactory.cpp | 17 +- src/ubpIpopt.cpp | 33 +- src/ubpKnitro.cpp | 9 +- src/ubpNLopt.cpp | 12 +- tests/cApi/main.cpp | 49 + tests/testProblems/libraries/HenryComponent.h | 2 + .../libraries/IdealLiquidStream.h | 3 + tests/testProblems/libraries/NRTL.h | 2 + tests/testProblems/libraries/PureComponent.h | 2 +- .../libraries/SubcriticalComponent.h | 7 +- tests/testProblems/main.cpp | 131 +- .../problem_Henry_RS_IdealGasFlash.h | 5 + tests/testProblems/problem_LP.h | 4 + tests/testProblems/problem_LP_random.h | 169 +++ tests/testProblems/problem_MILP.h | 4 + tests/testProblems/problem_NRTL_RS_Flash.h | 4 + .../problem_OME_RS_IdealGasFlash.h | 4 + tests/testProblems/problem_QP.h | 147 ++ tests/testProblems/problem_bin1.h | 7 +- tests/testProblems/problem_case1_lcoe.h | 4 + tests/testProblems/problem_case2_lcoe.h | 4 + tests/testProblems/problem_case3_wnet.h | 4 + tests/testProblems/problem_ex8_1_3.h | 7 +- .../problem_growingDatasets_AVM.h | 139 ++ .../problem_growingDatasets_simple.h | 127 ++ tests/testProblems/problem_int1.h | 4 + tests/testProblems/problem_nonsmooth.h | 67 + tests/testProblems/problem_sudoku.h | 207 +++ tests/testProblems/problem_unusedVars.h | 4 + tests/unitTests/testDecayingProbability.cpp | 58 + tests/unitTests/testLogger.cpp | 188 +++ .../unitTests/testPointIsWithinNodeBounds.cpp | 75 + .../inc/MAiNGOReaderWriter.h | 18 +- utilities/MAiNGO_Reader_Writer/main.cpp | 49 +- .../src/MAiNGOReaderWriter.cpp | 288 ++-- 125 files changed, 7681 insertions(+), 2559 deletions(-) create mode 160000 dep/googletest create mode 100644 doc/Readme.md create mode 100644 doc/images/ParamEstProblem.png create mode 100644 examples/07_GrowingDatasets/Readme.md create mode 100644 examples/07_GrowingDatasets/growingDatasets_simple.py create mode 100644 examples/07_GrowingDatasets/problem_growingDatasets_simple.h create mode 100644 examples/07_GrowingDatasets/problem_growingDatasets_simple.txt create mode 100644 inc/cApi.h create mode 100644 inc/decayingProbability.h create mode 100644 inc/nrtlSubroutines.h create mode 100644 inc/pointIsWithinNodeBounds.h create mode 100644 inc/ubpLazyQuadExpr.h create mode 100644 inc/usingAdditionalIntrinsicFunctions.h create mode 100644 src/cApi.cpp create mode 100644 src/pointIsWithinNodeBounds.cpp create mode 100644 tests/cApi/main.cpp create mode 100644 tests/testProblems/problem_LP_random.h create mode 100644 tests/testProblems/problem_QP.h create mode 100644 tests/testProblems/problem_growingDatasets_AVM.h create mode 100644 tests/testProblems/problem_growingDatasets_simple.h create mode 100644 tests/testProblems/problem_nonsmooth.h create mode 100644 tests/testProblems/problem_sudoku.h create mode 100644 tests/unitTests/testDecayingProbability.cpp create mode 100644 tests/unitTests/testLogger.cpp create mode 100644 tests/unitTests/testPointIsWithinNodeBounds.cpp diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 391ac47..14b7d90 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,12 +14,16 @@ build_windows: before_script: - git submodule init - git submodule update - script: + script: - mkdir build - - '$env:path += ";C:\Program Files\cmake\bin"' - - cmake -B build -G "Visual Studio 15 2017" -A x64 -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=TRUE -D MAiNGO_build_parser=false - cd build + - '$env:path += ";C:\Program Files\cmake\bin"' + - cmake .. -G "Visual Studio 15 2017" -A x64 -D MAiNGO_use_mpi=FALSE -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=TRUE -D MAiNGO_build_parser=TRUE -D MAiNGO_build_shared_c_api=TRUE - cmake --build . --config Release -j4 + - move Release/maingo-test-problems.exe Release/maingo-test-problems-sequential.exe + - cmake .. -G "Visual Studio 15 2017" -A x64 -D MAiNGO_use_mpi=TRUE -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=FALSE -D MAiNGO_build_parser=FALSE -D MAiNGO_build_shared_c_api=FALSE -D MAiNGO_use_melon=FALSE + - cmake --build . --config Release -j4 + - move Release/maingo-test-problems.exe Release/maingo-test-problems-parallel.exe - cd .. artifacts: paths: @@ -38,7 +42,10 @@ test_windows: - maingo script: - cd build\Release - - .\test-maingo.exe + - .\unit-test-maingo.exe + - .\maingo-test-problems-sequential.exe + - mpiexec -n 4 maingo-test-problems-parallel.exe + - .\test-c-api-maingo.exe - py testMaingopy.py @@ -57,12 +64,20 @@ build_linux: script: - mkdir build - cd build - - cmake -S .. -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=TRUE -D MAiNGO_build_parser=false + - cmake -S .. -D MAiNGO_use_mpi=FALSE -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=TRUE -D MAiNGO_build_parser=TRUE -D MAiNGO_build_shared_c_api=TRUE + - make -j4 + - mv maingo-test-problems maingo-test-problems-sequential + - cmake -S .. -D MAiNGO_use_mpi=TRUE -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=FALSE -D MAiNGO_build_parser=FALSE -D MAiNGO_build_shared_c_api=FALSE -D MAiNGO_use_melon=FALSE - make -j4 + - mv maingo-test-problems maingo-test-problems-parallel - cd .. artifacts: paths: - - build/test-maingo + - build/unit-test-maingo + - build/maingo-test-problems-sequential + - build/maingo-test-problems-parallel + - build/libmaingo-c-api.so + - build/test-c-api-maingo - build/maingopy/ - build/testMaingopy.py - build/individualPythonTests/ @@ -75,7 +90,10 @@ test_linux: - linux script: - cd build - - ./test-maingo + - ./unit-test-maingo + - ./maingo-test-problems-sequential + - mpiexec -n 4 ./maingo-test-problems-parallel + - ./test-c-api-maingo - python3 testMaingopy.py diff --git a/.gitmodules b/.gitmodules index f7bdebf..d8b069c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,15 +37,18 @@ [submodule "dep/pybind11"] path = dep/pybind11 url = https://github.com/pybind/pybind11 -[submodule "dep/libale"] - path = dep/libale - url = https://git.rwth-aachen.de/avt-svt/public/libale.git -[submodule "dep/melon"] - path = dep/melon - url = https://git.rwth-aachen.de/avt-svt/public/melon.git +[submodule "dep/googletest"] + path = dep/googletest + url = https://github.com/google/googletest [submodule "dep/babbase"] path = dep/babbase url = https://git.rwth-aachen.de/avt-svt/public/babbase.git +[submodule "dep/libale"] + path = dep/libale + url = https://git.rwth-aachen.de/avt-svt/public/libale.git [submodule "dep/mcpp"] path = dep/mcpp url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/mcpp.git +[submodule "dep/melon"] + path = dep/melon + url = https://git.rwth-aachen.de/avt-svt/public/melon.git diff --git a/AUTHORS b/AUTHORS index 885263e..db5a821 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,8 +1,9 @@ Main authors: -- Dominik Bongartz (AVT.SVT, RWTH Aachen University) +- Dominik Bongartz (AVT.SVT, RWTH Aachen University, KU Leuven) Jaromil Najman (AVT.SVT, RWTH Aachen University) Susanne Sass (AVT.SVT, RWTH Aachen University) + Clara Witte (AVT.SVT, RWTH Aachen University) Alexander Mitsos, project leader (AVT.SVT, RWTH Aachen University) Contributors: @@ -16,6 +17,8 @@ Contributors: - Aron Zingler (AVT.SVT, RWTH Aachen University) Re-implementation of branch-and-bound components + C-API + LazyQuadExpr - Ashutosh Manchanda (AVT.SVT, RWTH Aachen University) CLP interface @@ -23,6 +26,10 @@ Contributors: - Artur M. Schweidtmann (AVT.SVT, RWTH Aachen University) MPI Parallelization Testing and bug reports + +- Jana Hauer (AVT.SVT, RWTH Aachen University) + CI/CD & unit testing + Refactoring - Jannik Burre (AVT.SVT, RWTH Aachen University) Wolfgang R. Huster (AVT.SVT, RWTH Aachen University) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b6da7b..58de650 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,4 @@ +set(CMAKE_SYSTEM_VERSION 10.0) cmake_minimum_required(VERSION 3.15) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/MAiNGOversion.cmake) @@ -11,8 +12,10 @@ set(MAiNGO_use_melon TRUE CACHE BOOL "Build MAiNGO executable with the MeLOn too set(MAiNGO_use_cplex TRUE CACHE BOOL "Use CPLEX if it is available on the system.") set(MAiNGO_use_knitro TRUE CACHE BOOL "Use KNITRO if it is available on the system.") set(MAiNGO_use_mpi FALSE CACHE BOOL "Build parallel version of MAiNGO.") +set(MAiNGO_use_growing_datasets FALSE CACHE BOOL "Build MAiNGO with growing datasets for solving parameter estimation problems.") set(MAiNGO_build_python_interface FALSE CACHE BOOL "Build the Python package 'maingopy' that allows to call MAiNGO from Python.") set(MAiNGO_build_standalone FALSE CACHE BOOL "Build MAiNGOcpp executable as standalone solver with problem.h.") +set(MAiNGO_build_shared_c_api FALSE CACHE BOOL "Build library version of MAiNGO with parser callable from C.") if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) set(MAiNGO_build_parser TRUE CACHE BOOL "Build MAiNGO executable with parser.") set(MAiNGO_build_test TRUE CACHE BOOL "Build MAiNGO test cases.") @@ -33,6 +36,7 @@ if(SKBUILD) set(MAiNGO_use_knitro FALSE CACHE BOOL "Use KNITRO if it is available on the system." FORCE) # Disable stuff that is not compatible with the Python package to be installed set(MAiNGO_use_mpi FALSE CACHE INTERNAL "" FORCE) + set(MAiNGO_use_growing_datasets FALSE CACHE INTERNAL "" FORCE) set(MAiNGO_build_standalone FALSE CACHE INTERNAL "" FORCE) set(MAiNGO_build_parser FALSE CACHE INTERNAL "" FORCE) set(MAiNGO_build_test FALSE CACHE INTERNAL "" FORCE) @@ -43,7 +47,7 @@ if(SKBUILD) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") set(BUILD_SHARED_LIBS FALSE CACHE INTERNAL "" FORCE) endif() -if(MAiNGO_build_python_interface) +if(MAiNGO_build_python_interface OR MAiNGO_build_shared_c_api) # if building the python interface, the generated module needs PIC, even if building static libraries set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() @@ -80,7 +84,7 @@ endif() if(MAiNGO_use_cplex) # cplex has to be linked first, since on Linux/MacOS, it comes as a static library that contains BLAS etc. # if linking it after our blas, this will result in multiply defined symbols - target_link_libraries(maingo-core PRIVATE cplex-ilo cplex-concert cplex-lib) + target_link_libraries(maingo-core PRIVATE cplex) endif() target_link_libraries(maingo-core PUBLIC @@ -93,7 +97,7 @@ target_link_libraries(maingo-core ipopt clp ) -if(MAiNGO_build_parser) +if(MAiNGO_build_parser OR MAiNGO_build_shared_c_api) add_library(parser STATIC ${PARSER_SRC}) target_include_directories(parser PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc) target_link_libraries(parser PUBLIC ale babbase mcpp) @@ -114,6 +118,10 @@ if(MAiNGO_use_mpi) target_compile_definitions(maingo-core PUBLIC HAVE_MAiNGO_MPI) # Define pre-processor variable HAVE_MAiNGO_MPI target_link_libraries(maingo-core PUBLIC MPI::MPI_CXX) endif() +if(MAiNGO_use_growing_datasets) + message("Growing datasets will be used. See documentation for changes in model setup.") + target_compile_definitions(maingo-core PUBLIC HAVE_GROWING_DATASETS) # Define pre-processor variable HAVE_GROWING_DATASETS +endif() #----------------- If this is the top level, include dependencies -------------------- @@ -126,6 +134,9 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) message("=================================================================") include(${PROJECT_SOURCE_DIR}/cmake/addDependency.cmake) add_dependency_subdir(babbase) + if(MAiNGO_use_growing_datasets) + target_compile_definitions(babbase PUBLIC BABBASE_HAVE_GROWING_DATASETS) # Pass pre-processor variable to babbase + endif() add_dependency_subdir(fadbad) add_dependency_subdir(blas) add_dependency_subdir(lapack) @@ -136,7 +147,7 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) add_dependency_subdir(nlopt) add_dependency_subdir(clp) add_dependency_subdir(filib) - if(MAiNGO_build_parser) + if(MAiNGO_build_parser OR MAiNGO_build_shared_c_api) add_dependency_subdir(libale) endif() if(MAiNGO_use_cplex) @@ -162,6 +173,12 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) add_dependency_subdir(melon) add_dependency_subdir(json) endif() + if(MAiNGO_build_test) + set(INSTALL_GTEST FALSE CACHE INTERNAL "") + set(BUILD_GMOCK FALSE CACHE INTERNAL "") + set(gmock_build_tests FALSE CACHE INTERNAL "") + add_dependency_subdir(googletest) + endif() message("Done configuring dependencies.") message("=================================================================") message("=================================================================") @@ -176,7 +193,6 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) endif() - # --------------- MAiNGO executable (using ALE parser) --------------------------- if (MAiNGO_build_parser) @@ -198,6 +214,7 @@ if (MAiNGO_build_parser) endif() + # --------------- Standalone MAiNGO executable (for C++ API) --------------------------- if (MAiNGO_build_standalone) @@ -219,6 +236,35 @@ if (MAiNGO_build_standalone) endif() + +# --------------- Shared library for MAiNGO including parser callable from C --------------------------- +if (MAiNGO_build_shared_c_api) + # To build shared libraries in Windows, we set CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS to TRUE. + # See https://cmake.org/cmake/help/v3.4/variable/CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS.html + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + add_library(maingo-c-api SHARED + ${PROJECT_SOURCE_DIR}/src/cApi.cpp + ) + target_link_libraries(maingo-c-api maingo-core parser) + set_target_properties(maingo-c-api PROPERTIES CXX_STANDARD 17) + set_target_properties(maingo-c-api PROPERTIES POSITION_INDEPENDENT_CODE ON) + set_target_properties(maingo-c-api PROPERTIES PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/inc/cApi.h) + + if(NOT(MSVC)) + target_compile_options(maingo-c-api + PRIVATE + $<$<CXX_COMPILER_ID:Intel>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + $<$<CXX_COMPILER_ID:GNU>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + $<$<CXX_COMPILER_ID:AppleClang>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + $<$<CXX_COMPILER_ID:Clang>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + ) + else() + set_target_properties(maingo-c-api PROPERTIES LINK_FLAGS /ignore:4099) #/ignore:4099 disables annoying linker warning because cplex does not provide debugging information + endif() + +endif() + + # --------------- Python interface --------------------------- if(MAiNGO_build_python_interface) @@ -255,7 +301,7 @@ if(MAiNGO_build_python_interface) set_target_properties(melonpy PROPERTIES LIBRARY_OUTPUT_DIRECTORY $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}>/maingopy ARCHIVE_OUTPUT_DIRECTORY $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}>/maingopy - ) + ) add_custom_target(maingopy ALL COMMAND ${CMAKE_COMMAND} -E make_directory $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}>/maingopy @@ -265,7 +311,7 @@ if(MAiNGO_build_python_interface) ) add_dependencies(melonpy maingopy) add_dependencies(_maingopy maingopy) - else() + else() add_custom_target(maingopy ALL COMMAND ${CMAKE_COMMAND} -E make_directory $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}>/maingopy @@ -274,27 +320,36 @@ if(MAiNGO_build_python_interface) $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}>/maingopy/__init__.py ) add_dependencies(_maingopy maingopy) - endif() - if(${BLAS_usePrecompiledDll}) + endif() + if(BLAS_usePrecompiledDll) add_custom_target(copyBlasDllmaingopy ALL COMMAND ${CMAKE_COMMAND} -E copy $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}/blasd.dll>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}/blas.dll> $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}/maingopy/blasd.dll>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}/maingopy/blas.dll>) - add_dependencies(copyBlasDllmaingopy copyBlasDll melonpy) + add_dependencies(copyBlasDllmaingopy copyBlasDll) + if(MAiNGO_use_melon) + add_dependencies(copyBlasDllmaingopy melonpy) + endif() endif() - if(${LAPACK_usePrecompiledDlls}) + if(LAPACK_usePrecompiledDlls) add_custom_target(copyLapackDllmaingopy ALL COMMAND ${CMAKE_COMMAND} -E copy $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}/lapackd.dll>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}/lapack.dll> $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}/maingopy/lapackd.dll>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}/maingopy/lapack.dll>) - add_dependencies(copyLapackDllmaingopy copyLapackDll melonpy) + add_dependencies(copyLapackDllmaingopy copyLapackDll) + if(MAiNGO_use_melon) + add_dependencies(copyLapackDllmaingopy melonpy) + endif() endif() - if(${MUMPS_usePrecompiledDll}) + if(MUMPS_usePrecompiledDll) add_custom_target(copyMumpsDllmaingopy ALL COMMAND ${CMAKE_COMMAND} -E copy $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}/mumpsd.dll>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}/mumps.dll> $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}/maingopy/mumpsd.dll>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}/maingopy/mumps.dll>) - add_dependencies(copyMumpsDllmaingopy copyMumpsDll melonpy) + add_dependencies(copyMumpsDllmaingopy copyMumpsDll) + if(MAiNGO_use_melon) + add_dependencies(copyMumpsDllmaingopy melonpy) + endif() endif() endif() @@ -304,14 +359,15 @@ endif() # --------------- MAiNGO Tests --------------------------- if(MAiNGO_build_test) - add_executable(test-maingo ${PROJECT_SOURCE_DIR}/tests/testProblems/main.cpp) - target_link_libraries(test-maingo PRIVATE maingo-core) - target_compile_features(test-maingo PRIVATE cxx_std_17) + # Test MAiNGO by solving a number of test problems + add_executable(maingo-test-problems ${PROJECT_SOURCE_DIR}/tests/testProblems/main.cpp) + target_link_libraries(maingo-test-problems PRIVATE maingo-core) + target_compile_features(maingo-test-problems PRIVATE cxx_std_17) if(MSVC) - target_compile_options(test-maingo PRIVATE /MP;/Qpar) - set_target_properties(test-maingo PROPERTIES LINK_FLAGS /ignore:4099) #/ignore:4099 disables annoying linker warning because cplex does not provide debugging information + target_compile_options(maingo-test-problems PRIVATE /MP;/Qpar) + set_target_properties(maingo-test-problems PROPERTIES LINK_FLAGS /ignore:4099) #/ignore:4099 disables annoying linker warning because cplex does not provide debugging information else() - target_compile_options(test-maingo + target_compile_options(maingo-test-problems PRIVATE $<$<CXX_COMPILER_ID:Intel>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> $<$<CXX_COMPILER_ID:GNU>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> @@ -319,7 +375,53 @@ if(MAiNGO_build_test) $<$<CXX_COMPILER_ID:Clang>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> ) endif() + if(MAiNGO_use_cplex) + # Additionally linking cplex-available (part of the cplex target). This is merely to provide information whether the current MAiNGO build actually has CPLEX. + # This is required since some test problems are only run if a dedicated MILP or QP solver is available. + target_link_libraries(maingo-test-problems PRIVATE cplex-available) + endif() + # Unit tests of MAiNGO + add_executable(unit-test-maingo ${MAiNGO_UNIT_TEST_SRC}) + target_include_directories(unit-test-maingo PRIVATE ${PROJECT_SOURCE_DIR}/tests/unitTests) + target_link_libraries(unit-test-maingo PRIVATE maingo-core gtest_main) + target_compile_features(unit-test-maingo PRIVATE cxx_std_17) + target_compile_definitions(unit-test-maingo PRIVATE HAVE_GTEST) + if(MSVC) + target_compile_options(unit-test-maingo PRIVATE /MP;/Qpar) + set_target_properties(unit-test-maingo PROPERTIES LINK_FLAGS /ignore:4099) #/ignore:4099 disables annoying linker warning because cplex does not provide debugging information + else() + target_compile_options(unit-test-maingo + PRIVATE + $<$<CXX_COMPILER_ID:Intel>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + $<$<CXX_COMPILER_ID:GNU>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + $<$<CXX_COMPILER_ID:AppleClang>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + $<$<CXX_COMPILER_ID:Clang>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + ) + endif() + + # Test the C API of MAiNGO + if (MAiNGO_build_shared_c_api) + set(C_API_TEST_DIR ${PROJECT_SOURCE_DIR}/tests/cApi) + add_executable(test-c-api-maingo ${C_API_TEST_DIR}/main.cpp) + add_dependencies(test-c-api-maingo maingo-c-api) + target_link_libraries(test-c-api-maingo PUBLIC maingo-c-api) + target_compile_features(test-c-api-maingo PRIVATE cxx_std_17) + if(MSVC) + target_compile_options(test-c-api-maingo PRIVATE /MP;/Qpar) + set_target_properties(test-c-api-maingo PROPERTIES LINK_FLAGS /ignore:4099) #/ignore:4099 disables annoying linker warning because cplex does not provide debugging information + else() + target_compile_options(test-c-api-maingo + PRIVATE + $<$<CXX_COMPILER_ID:Intel>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + $<$<CXX_COMPILER_ID:GNU>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + $<$<CXX_COMPILER_ID:AppleClang>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + $<$<CXX_COMPILER_ID:Clang>: $<$<NOT:$<CONFIG:DEBUG>>:-O3> $<$<CONFIG:DEBUG>:-O0>> + ) + endif() + endif() + + # Test Python interface of MAiNGO if (MAiNGO_build_python_interface) set(PYTHON_TEST_DIR ${PROJECT_SOURCE_DIR}/maingopy/tests) add_custom_target(test-maingopy ALL @@ -342,4 +444,4 @@ if(MAiNGO_build_test) ) endif() -endif() \ No newline at end of file +endif() diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index 3509655..ec999c9 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,3 +1,25 @@ +Release version 0.6.0 (May 26th, 2023): + - New features & interfaces: + - MAiNGO now has a C-API. It mainly expects text input in ALE syntax. + - New algorithm for large parameter estimation problems, see documentation under Parameter estimation + - Updates in ALE syntax + - MAiNGO Algorithm: + - Now allowing OBBT to only be done with a probability that decrases exponentially with the depth of the given B&B node + - Minor improvements to rounding heuristic in mixed-integer part that may help find feasible solutions earlier + - Introduced lazy evaluation for quadratic problems + - Added intrinsic function mid(x,y,k), where k is a constant and x and y are variables + - Added intrinsic funciton pinch(x,y,z) = max(x,z)-max(y,z) + - Bugfixes: + - Fixed bug that could lead to a (incorrectly calculated) negative final optimality gap + - Fixed several minor bugs & compiler errrors/warning on different systems + - Fixed potential segmentation fault im MAiNGO_Reader_Writer + - Misc: + - Added tests for MPI-parallelized version + - Added support for newer CPLEX versions up to 20.1 + - Third-party libraries: + - Upgraded to Pybind11 v2.10.2 + + Release version 0.5.0 (June 14th, 2021): - New features & availability: - MAiNGO is now also available from PyPI for use via Python @@ -15,6 +37,7 @@ Release version 0.5.0 (June 14th, 2021): - Upgraded to MUMPS 5.4.0; also renamed all routines called MPI_* to FPI_* to avoid issues with the fake MPI implementation of MUMPS when using MAiNGO with actual MPI parallelization + Release version 0.4.0 (March 4th, 2021): - New features: - MAiNGO now has a Python API. It consists of Python bindings for the C++ API and thus works very similarly to the latter diff --git a/cmake/MAiNGOsourceFiles.cmake b/cmake/MAiNGOsourceFiles.cmake index 14cfb9b..c15607e 100644 --- a/cmake/MAiNGOsourceFiles.cmake +++ b/cmake/MAiNGOsourceFiles.cmake @@ -21,6 +21,7 @@ set(MAiNGO_SRC ${MAiNGO_SOURCE_DIR}/MAiNGOsetOption.cpp ${MAiNGO_SOURCE_DIR}/MAiNGOtoOtherLanguage.cpp ${MAiNGO_SOURCE_DIR}/MAiNGOwritingFunctions.cpp + ${MAiNGO_SOURCE_DIR}/pointIsWithinNodeBounds.cpp ${MAiNGO_SOURCE_DIR}/ubp.cpp ${MAiNGO_SOURCE_DIR}/ubpClp.cpp ${MAiNGO_SOURCE_DIR}/ubpCplex.cpp @@ -30,7 +31,7 @@ set(MAiNGO_SRC ${MAiNGO_SOURCE_DIR}/ubpNLopt.cpp ) -if(MAiNGO_build_parser) +if(MAiNGO_build_parser OR MAiNGO_build_shared_c_api) set(PARSER_SRC ${MAiNGO_SOURCE_DIR}/aleModel.cpp ${MAiNGO_SOURCE_DIR}/programParser.cpp @@ -41,4 +42,12 @@ if(MAiNGO_use_mpi) set(MAiNGO_SRC ${MAiNGO_SRC} ${MAiNGO_SOURCE_DIR}/babMpi.cpp ) +endif() + +if(MAiNGO_build_test) + set(MAiNGO_UNIT_TEST_SRC + ${PROJECT_SOURCE_DIR}/tests/unitTests/testDecayingProbability.cpp + ${PROJECT_SOURCE_DIR}/tests/unitTests/testLogger.cpp + ${PROJECT_SOURCE_DIR}/tests/unitTests/testPointIsWithinNodeBounds.cpp + ) endif() \ No newline at end of file diff --git a/cmake/MAiNGOversion.cmake b/cmake/MAiNGOversion.cmake index a5f825d..7528a74 100644 --- a/cmake/MAiNGOversion.cmake +++ b/cmake/MAiNGOversion.cmake @@ -1,3 +1,3 @@ set(MAiNGO_VERSION -0.5.0 +0.6.0 ) diff --git a/cmake/addDependency.cmake b/cmake/addDependency.cmake index f967a0e..b5585ef 100644 --- a/cmake/addDependency.cmake +++ b/cmake/addDependency.cmake @@ -4,6 +4,6 @@ function(add_dependency_subdir DEPENDENCY) message("${DEPENDENCY} OK.") message("=================================================================") else() - message(FATAL_ERROR "Error: Could not find CMakeLists.txt at ${PROJECT_SOURCE_DIR}/dep/${DEPENDENCY}. Did you initialize and update all submodules (cf. Readme.md or doc/html/index.html)?") + message(FATAL_ERROR "Error: Could not find CMakeLists.txt at ${PROJECT_SOURCE_DIR}/dep/${DEPENDENCY}. Did you initialize and update all submodules (cf. Readme.md)?") endif() endfunction(add_dependency_subdir DEPENDENCY) \ No newline at end of file diff --git a/dep/babbase b/dep/babbase index b9ac45c..3b9f338 160000 --- a/dep/babbase +++ b/dep/babbase @@ -1 +1 @@ -Subproject commit b9ac45c6ca1a1b2c7f8424ca929af4ad82aff552 +Subproject commit 3b9f338e6e66e5954500deced43cd3365da05cf9 diff --git a/dep/blas b/dep/blas index d0c480a..2275c1d 160000 --- a/dep/blas +++ b/dep/blas @@ -1 +1 @@ -Subproject commit d0c480a3feeeaae52a9e343436aa9d299dddfcb4 +Subproject commit 2275c1dfaef678e59f48697ab805039511c989bf diff --git a/dep/clp b/dep/clp index 3231f96..d1a6cf5 160000 --- a/dep/clp +++ b/dep/clp @@ -1 +1 @@ -Subproject commit 3231f96757010afcf0e24fcb1afe18567df4c943 +Subproject commit d1a6cf541f75815adbfe881984b8511b4d00845b diff --git a/dep/cplex b/dep/cplex index 77243cf..1d4f489 160000 --- a/dep/cplex +++ b/dep/cplex @@ -1 +1 @@ -Subproject commit 77243cfb7e99f6b24a5e919de9534e8d6a83e4fc +Subproject commit 1d4f489ea22949f22e9413ece215492ac7fabe8e diff --git a/dep/fadbad b/dep/fadbad index c88e0c2..806a371 160000 --- a/dep/fadbad +++ b/dep/fadbad @@ -1 +1 @@ -Subproject commit c88e0c2a9b155ce5a31e54141ae7fef8f9233a85 +Subproject commit 806a371cc8ce4a93cf1084745bb73071aa827cb9 diff --git a/dep/googletest b/dep/googletest new file mode 160000 index 0000000..53495a2 --- /dev/null +++ b/dep/googletest @@ -0,0 +1 @@ +Subproject commit 53495a2a7d6ba7e0691a7f3602e9a5324bba6e45 diff --git a/dep/ipopt b/dep/ipopt index 5752923..05d7744 160000 --- a/dep/ipopt +++ b/dep/ipopt @@ -1 +1 @@ -Subproject commit 5752923d046c9e05404a02ea2c87bea3b321d7f5 +Subproject commit 05d774495c44862dda90a98b429585093b6681fb diff --git a/dep/json b/dep/json index e713f0b..086cce3 160000 --- a/dep/json +++ b/dep/json @@ -1 +1 @@ -Subproject commit e713f0b04b7c097ac79df2310c17d2f303b13a33 +Subproject commit 086cce3d69e0ea0803cafad2cdff8221fd2bcb1e diff --git a/dep/lapack b/dep/lapack index b522cee..8e8901c 160000 --- a/dep/lapack +++ b/dep/lapack @@ -1 +1 @@ -Subproject commit b522ceed43f889eaa9adda8f279325fcafd770b9 +Subproject commit 8e8901c219324a0e078ef6e2e5911eb54f573434 diff --git a/dep/libale b/dep/libale index 36e4e9d..69e1c4c 160000 --- a/dep/libale +++ b/dep/libale @@ -1 +1 @@ -Subproject commit 36e4e9d47355c53fbf25ac4107bb18ff3c180c04 +Subproject commit 69e1c4ce4bb003ffda30283a60d07094dd5585a9 diff --git a/dep/mcpp b/dep/mcpp index dfbdc83..69a763e 160000 --- a/dep/mcpp +++ b/dep/mcpp @@ -1 +1 @@ -Subproject commit dfbdc830edbd11afe31c4283020ecdb0683c3ee2 +Subproject commit 69a763e55faf6557af0839a06fa82eb919b062e4 diff --git a/dep/melon b/dep/melon index 6a64bc9..cc3783b 160000 --- a/dep/melon +++ b/dep/melon @@ -1 +1 @@ -Subproject commit 6a64bc96dfce27a7ca84c26c1a465d7b677aa70e +Subproject commit cc3783b457a7d209d079303e1d5b67e570fea6e5 diff --git a/dep/pybind11 b/dep/pybind11 index 91a6972..8dcced2 160000 --- a/dep/pybind11 +++ b/dep/pybind11 @@ -1 +1 @@ -Subproject commit 91a697203c80b52086244bb385f4d477ffaac787 +Subproject commit 8dcced29ae4c7654c25bc50742a72556ee3ae36c diff --git a/doc/Readme.md b/doc/Readme.md new file mode 100644 index 0000000..aaf24d4 --- /dev/null +++ b/doc/Readme.md @@ -0,0 +1,5 @@ +# About + +This folder contains the information required to build the doxygen documentation of MAiNGO. +You can build the documentation for you version of MAiNGO by running doxygen on the Doxyfile in this folder. +The ready-to-read documentation for the most recent version of MAiNGO can also be found [here](https://avt-svt.pages.rwth-aachen.de/public/maingo). \ No newline at end of file diff --git a/doc/images/ParamEstProblem.png b/doc/images/ParamEstProblem.png new file mode 100644 index 0000000000000000000000000000000000000000..363c382080d2ac417bfda309795468ff4a5428f3 GIT binary patch literal 10438 zcmeAS@N?(olHy`uVBq!ia0y~yU^vUbz;J|vje&u|X7kd83=9m6#X;^)4C~IxykuZt zU`coMb!1@J*w6hZk(GggfwRCPvY3HEOcjI~J%dy}FfeFDd%8G=RK&fV%e^NwSn7bd zs^<D2#r~O&o<}AGobZ@rv*_fLs}h=1Ju9b{AFHrenm8lQ`H$8V%_UC^4TDxmPH|B> zrmgttL~_LDfA=F_-@E&APW5@a?{lip?|uIJf8Fox-{XGYsr`00y*9h{{`PCX_Vung ztkE^eQ2O`YMH-TeoEB>s1}+SoGGoOB4KJUN3=vf|Et4)GVNog9&Q7l0MMqp(-2#&q zIX&L->(%PBZqm6bw~tBZ#}v(3r!)EdnVH7N@5ippd|@JWd`sr#Go0#k6pruzefRyD z_<x_oE&C2HpI?`ic4kK6zklENpSJvMe9pqT@AJ_XPT@13o}NCN*e;vaXYq(5e$^A% z7PpPx{Of-S_qOM(JM{a~k3FAGX&3RUSL{=%-}C$3?y~c?-?y0Gt5~dmQbzsBx4+-- ze^2+439<dmBX4)-&#&wIr#=3B&ieeG@As;UWTw}CySaSH^;70H@8zoBJUsvC<N5l3 zm;d~}|Nk3EQ-)1+?$*%Gtgla}$4g!Re4@~7!J|M;Zqe%zvka4u{V-P5+d4-|Furm9 z!V5*#rN?dld^nu;^wiX(3(5?IKOeTsyJTz<wfQIaJLB%I(zM-gw|!p9nRaeY=D&~q z^&a{MjraZg^*XIxw#=Yirl{k8i=n^G$Ck>EN5#(u``fDi_dY#M_w2RkeBJB!s$T0B z-^yHma%=wnzt{Hs`E>f({{Mg1XWrelHRk`LZv8ViHYT5)r0RX<$H&J<!xn#k5<BIn z{J#(E#g{zQe{MdsG<J8{vmcN9&A-0B{#`Y4!i7t#+T7)9w>W)s-dFG{va`0iS4cd3 z=2~Hgda?eitHW>q{eJ(x-OneJ%T7#C{Pw6@|6BMPS(A(jZ?CVP|M%tc`A_a}_T*k! zak0wmY<PTa>gy{jlg0I7G@f&d>1?>2zkhG+=Cs~9>}7Wf-CwtypFg$eAglNdVSk&z zzWSd}#jD?JJU%VkIFUc?^So6|IhM=qqL=&rnDirL=7pO7f4_fv9As$!??-Zcl*EUm z*(RAv=bu$c#Y^?)+*W33_IjP8wez_A{~zv}^NrK{?v`BkeVabNR_%GCa-T&~vzKk@ ztB~J!%kQ83c;5bhj<iWe!u+~lnU?%Zgl9cHJ^gm&=Vw2Sv-5YqHG5rUSieot!ba=x z%Zc7|#3p=w&lQ?>c?Va*y8ZvEif^V)&t1?IV%jThZdU*M_WdUkrr8G?7>i`szvul8 zeD>#k{eR)9FBUZO8LbPN%`R7QV279X^|*?Mte3ZB25&xVcKbx7!qi(&s^lxvOx{ma zc9*)IEOFiA=jznWZ@1l^b#rsNwECWBZPIxHrUrNKKhinKx98QW)is`98N_W@{iywZ z_qlh{qzSUBq9u>_C~XT_TePlFG-TiDo6`H1h@_`?Xif7u-Y0wgb!OVxS*ouhuFQSI za?tMIkH_Dd`RzWazW#2qu5p5`OwEUb-!6IUe@(yECuQn&#Wm!RzunIx)72bwSLf~Z zX}G5rEgH2qJ=H<mtD8mQ>&;yYjZ-f!a^3X*ZGOG>Zw<fH$@gxq^xJo&L$TlZ!EyQe zFRI5Z9vb|9w|o7~%;j^R+*w)t?DKhhb3b3>^82;hr!elFxViqxkH!6VlWhGMWH@TV z6K-xw{j@!1wn^qBecuM>MM`nUR<+z?i>v(_dh@vAwC`uO#yyIxe6#WR6ZW#TlPdrH ze15i>-!5a0oUZ-7ce~%~%|EK$T-CU}U0%J?hVg5=e8s6Ln#*!;Z%f;J)=c;-ODWTN z!KZVrN+)?0zTf-(RPDXm@3EV&$5qda+M4xq_mk<nN?&`ix>N)N&e=G7y4Rs8v(x8Q zy4l2@+h@G{&8F_3=j;C^m))yO4_+T<`;+JW^A(HxjIPI3uZ`TAH8tef%gf78TNfVN zw_?iLsI5Z&Qd#ZBe_T)eocqK2dUA*O+S=dWiuRv1zklaW@p)S%o^|Py{!f1YV0+bz zh3zR(0{h=CY?ss8Z$9<)n$72g5}8>(+)SS@D!NYR+4=eU^AiuRZci7;mtpGQs{H@= z`|0vPvFYXaDv$HXT7}%>+|T=U;(eVDXU*@=@YdUTWKG=OTW|J$zqfCT$UW<?tBaqX z+o$4O(?3~I;>5f8TX(<PwYs!wd+F5MyX5`$=)7weF@5mucK-Hvt`XskOJvsb-d*tf z_@7vz2eMZGemr)S<;-dpmI{>Yv-_2yS#CdN@8@&YYlYnc`TJ}>dEDMxUH&sC^%2WX z7hh?+njQLQWh>^zEKZ1RPQJZ0TmATo$aU%;=SRrjdwFYX_EQOUu5*m{Vny~iue<bP zbIW5@$#)ai6fy3PzGB&ItXT1eNquff(9UBftLA14xW0Yz{^OaCe6ia3x3^>_$1dG| zZrN0$^vyr_pW=~GT_?Dq`1!f5;qkRw?>z1?KKCJ9c)_-ydut+%<2Tu@n0C{GWz(k2 zUteA-TW!pqSmVjG$L_`Db?F~eIWN>|`lo$*a`KbEYiVwQ?er<}^?$$aQ#mMGekXD6 z!)Voe+a9#tt9rfGRsNi5$Q9k~cP_=%{d~G_$%ccwPgr}lzfN59TJtEYoP>GaoITEF zdm4S1xt{y!ewlx&N6xm&qqadWcGn48tCANA&l#o4PE~(<^V0iTJKL=L#(I%AO;)(e z*Zp|NcK23(-Dl}QeF>Ycrd^IZf7``RQkXkS^i-hvqJsK&52M`UgDMvsO}cLo%^+%Y zeL>caY4KN<ykcmcmw3QLv_kgds-0~Irb}!tc<6NVvs|F?N49yD&t^W`^?Kc8$KyT= z`3w|J-%r|X`}fP`pnGeh&2KWViu|#!g!zh%{r>crO=T<#Zs%=0D)vu@ahGOQ;U?~x zMF+l0vbX4Z?ci_AOg(He`(QJ>^z}p2b~F}qT3Jp}H3@9qze+Q|?Z5;@XP@<Pd;df> zZHRgF;V}R7+PPMxJ+Et1ZYc8AnDcSJx__io`03u9O()f!J(=wPZN5Q~th8j)#rfKs z#Abb}>WrCix;j*Z=gGyz?%mAIhErmiANsHM$)Cl?Qz@g@>agirfLz6gX?n3*#~m|@ z82OYB+!T~O%*E7|eB|bXqB$T#l9<;^Zl1oo?CmS>B5qsWb<Z|MeQ=knRLQ?TKl_7O z(89pejR$WkWSrFf|91O*y|}G83pO&^YzUlJ{m$~{^Ig*!MQ8CGFztFb`~1ZGmSr~8 zYzwuf+?#%K<_+DKFYfQ(|MK46-77K=7%(l^_1KF)q5lD&dnosUW5#l!^Au{o-z`tt z?QmsjPp-t9nVVYLp8Kc2pW+(kbl86KEBQq{e~os&=9`~tpy#E*-<;DJFz=7-%XgFg zBe>3<PF2v4PdM`^!c*Pki-E-H;^*hSs)q7!J}CLvgZ;7b85O&yQ-Y72Y<$bebAjz% z&1c`@bC%CfSVU}TPxWb^m;B&B<31JBp2XIVviB4f3dG|o65YP>oR?=?pZrj(;+R45 zw2f1b^-4#lw?r<VeM;ikBvtPva|)$S@!Nh0u+F(*P^f7nx-|3hvY>Zbt)*+X_UwKz z?_TcqyI!YeO6<tHYxVPw-tIR_@4~-3t*Ku5>F$)r`OD{kYHGcwKUuCDCHwUFKNVhf z_CxTVuh*jYEtzs$uG(kaW4YYBySD1Dt*TJBDc*JSkd6N1%AVC9tBu|r5nwKD->6Y{ z{@dHz(aYn0?$>DDXy2x>!u{#SoSR0w@7Mi4xg_LRAD_(!hu3eMD$TC1{^`H;*w;I% z!JUr;yfeBuuXA-CF!(ToxA2xS+e3*PKCH>o1=k|ePgaC0Iv&-}&TPNTlfp3B&FGZ7 zd~M49f4{Qpr_J+uwzc--QSnXb6Q;Sf-@Mzy|M8#xt`|x>r*YNY&pDRi(5lAUBa;4U z@zTXM3lkm6KR!BI<g?zo<VAp`AFF82=d<ShwpsJ?Uvy@!{oES7`A=Y0#OuS=Wp4ZR zHyS<gn;CrflnhIHg4yl+;jL<{Tjq(b<w@n_4UP+l{K*%Ue5FHFJM715X``wy8M}|m zRi7|?9m~a8H9IF{&L@Yt-ln&$512^ZD|~xv>(QJB^LrJC^ERI~Tl7i!!pjBIAKqOa ztb5wIRgE*w?5>%<S@e(FpIX>z?@RtL%6+zO_q$KwL6>(Hr_bE|{pa)fw+}Y6|Gspn zq04>!oLp8P*6&*0TbZ66E`4?7<h!lc<C6F<7c1!82w0P$%gtgNy;V9SLrK9P<lCmG zl|Boe>Uw7|I9L7o@$uWw=kvd-zTEk+O*+L@{T0{gh1|Sam3nV=)uyZY3;41Z-;t|$ zz-V3l?M=@!l}9=&Iu10jaesO6?bzQF9rdnEE(P*6|9f^n`E=Fj>VC&&wtqe=y^54f zdQROdx$J9Hcc?+Qu83`Vgx2XL7T0W4&MS8xQqs6Cd-}!4f4|@BpI$K2n`N@P|Fny- z*Xo`KAM=<qFM8ouqjl|*4B3~@JGpb4yQxUaGNlI>53b-cbNK)7udAu$?>C#5=CsR} zP2kXanUJ&0wOh<6RA#$Y{b?!A1ED*whDCSg+s}7deEvyMaF&|rJh#u6y!B6>Iu-x- zRd~_AgY5Dr_W94ZD>}oG8~J&uglO6>*5JdDohP~u8Js%(>imLtPq^Lwbgo;eA^Esf zU46rZu8EOV?0cjO6Q0aIzHmF|1H(l<k?dVT8X-HDAAFGdkw5!teDzyX!-&pr64&+# zgg=m%JahZk_5J@;s~<739QyLE=9@{E&~z^DvmJuUXD0jGX&QUajNthFcKiG{GZQ)! zW#?9S3UH(yQ1jo#vpI(I69b2Gqin>NqgFz$nj%_nY@HJ#xXw(;4+{}0ZEFp3VXOT8 zcKgw7iIG_i9oihcg28>3&m`irjugA~w|)>TWLv2C&cLwg;{r{ug%dUx&RW0!->#IO z#;2Jr{QKA>6$AyDO21rmKW%x)O+uroU~`_#^MgyL$7yM+a{j)&)LZ=LoJAr`p+-AA zXR+I)DH<?yPkB*#&icJg<WcuIx=hzLKAU|bb1G|AO37aLWdctc4K*KSyM_iH*!KGF z?(&m6CI|gpS+ek;;ek~)SJp91eYfLr-{g5K%iiAFG*3pf??K5_aQ91h`yHioJwA$B ze9YVJ|9+XwBIWoX!l#Y7xkQYEXI;s)_1dQGS}!v%IogRurLZjD<QTAV`6;fPi98q8 z^Y?so+jyVJ=itkWi`%~}@#1A&7#MW;h;LEUr&))Nwd{8&VmUKCuF5mbLtTGYP?nfz znPqr+z{b42U&A7&%Uzqwys`Tu*P{gm?^mY${yhKx8~ce7i{)6?D@IPcTDS6{g5^;z zV~L3Ej^_{ju}o8Ci6|B<_vPh2I?4HymT7;}rc5pAnENf^s=BP3H(2tU@g}=InR`Y= z>C5IV4KoEJT`dAv@w)1oFskJWozXkO!gg)lh7bi&MS)n6X?h#P*spI6I_#LyC8;(| zXm860rzOi2g18o}JK8u!NLbzPOS+fuvKO+9XIhmn%-NiHMDf53Zi5e}5>nMB%;{P& z@gl<=o(n=8{wpg*_FkB<b1f*vr?#6qDyC=&`$js>;<HITJKJ3RVbV9Ire-g9w@u7^ z77k&XjdnY(tX$#yIN_c2=5>pAhA>38r}8H5;#crVpHtXoAKWDf>S}vlT%Fz}Bo1OC z_qqvn&X>GsZL6_b9kzB7L+eceUf0kS0&ZHDQ~A7pimbg5Q+hRYS=idBWp#gl2?b5x zkzS~H=iSrk@$b%aP0zf%Z06?kcF~uniD<>w{am`HDpmW*^Oh!YscZLyeGIP06dn~7 z^0#vExMQ&)ry+ttak`)4VJZ1V(>GmMcR`_D(C|^m*#kv|*AzCc>-<s35~t%4Ar)|9 z!kN-%XJ-D>*&b8#@u<-09+O)x%T9_cx?<7zG5sq0q?420?G1?7oYq@<D9QX*hVttw ziTGJ3)`c(klReom_w<^JI~P5up0%_1`LD%yocnw%-fTGB^ZVfw3*nCTu!(o(gtDGL zvss+$Nv3Mmc}I3x6-$5SC+14(7rR8YpBT5gIdb!6Nv^c~bkO{MO|qD7l*(}qUzv!r zk+Q97f9A6|>9BMxX4w69+w3=sTaFylQPwy4q-S!!{{P;czu)avJ#z7^`TZQXKAB9n zUMba|CYKL>O5UV0S(KOg;>z^2H#atJnz-0ld_twJ%M}&Qt6Q(fY3s^t<CWVe9$yo< zPWAAACdK0!S{IJ$?|x&{x$Wl8;`Gng_x}sM)UU9FrRsV0hLuhN{T2`2KCa_nIKQ*x zWl;T6k)n=A3kqhn=xera2-$OcRoji54{ru9P2u!D-)Z~(sCfK|!h;hoT>8ZPaAKd; zD~;D$yYxL@Y^;92cYEpDt@<|wl;V<(->G`NHmP_*_2go+>%V{ahxR0#U*Nwz^Ripc z@~zp|Z+*X4eLip3%VnE%qqm)@-zAb{X?^wJ{9KNiE2FpNWG?reoi;b%*2>`Jr@mVp zoZ@rcFXFARzfGd~%@pD4v!>U7>_5e$p0drko$uSa-R~w@T)ty|bK5NQ{CQJ0TR!}{ zbg9MHHD{i$*I9Jlvf@KRd{kF<iPyUG^X>0X7h|gwoyE#6cH`OE*}pdjtyqxC>D$0{ z%=>h^@`D4+{8Os)Hy#zcwo-eaiQ{6o-Y2tO3)XsS`pz<$DDrCMk5{YLzwxtvn-Z)4 z%6m=i^SR|-b$?c`-xqbNWAm|}rPpKCpU*t>c}~Tn&Z0L}xjWep$@1=~JE=O|!}9z@ zu6x#ww~SleRBh^)MXT9-K4Yvpe>tOf+KUSdRj<#l{T3M?CGjNGR=JB?lR<ySgQl-n zI6iG%(j6Bm>GD?cipKTNCk+>v39$2WoSedwn6vm|<|2V@oojN}J^!S7Oebc?1^Jni zG|X56-(O!pKd11)Kk?X-i8B7POgz7>sJ__n`ryuqdOP*c_nn%p=_34eUg0*Mg3|Ny zGmGXLTs$wvqGVOs!tl6;SNfIb`FXa%t87=E2=_ny_mJ`4Z@04dnKZ8MQN13t(tOLF z6&5oevADcz%{C4HaOR=pV#nK^0#Uomay8|R(|Yt{<-=D#tqOIHn)JbbNznN@zF|eO zq3b4`*sa{~CTT}6JFk>V<1ux=IU3AOFL~acQrOZav4F*@|I>@b{ip7&%%Ak%Sn~V1 zKY?iz7E1Bi|0%fnyh$^7*@=1c1l*k)%BAK=7uq&W^);ysD>~(>a4>wq1*TKaLo?#) z|CY`aySObk`s0fo*IeCx1fHE^_}X5yKgK7T(`mAeopaIM2W+Z;<tNN<+t}jcuz+Kc zT+FWQYil%5+DurZC%91fs;RWogs*?U->=`hrclwq`p~>ny|a1u%-<2Mmb3fqHle#B zLB6v&wN!2%`kA9RF??ZSmrK#2$hv?#YcF=4NbxMGk2xD?cE6_hru*s1(W-ZLb0jTG zIL~6Oyz^`QlZoy}mU^!{6YOsrs%87leXdpMA9bOZjF(@&{eHiGzvZVB%4atom(zZ} zq?_MXcwrXvj)0lm)8_oi{P^gor0<#|>zY>RvYktqRvWpO=lr~L_dHq8%a@;?@?l<( zshr6U0lQ$2C$YBD=6Q2|{&?K~H<D>nf9abW7w4;nSoEY%+UJ&ebJNn5(c9NmKJGPt zGH0FwhmZkVOO<Q3%f2%{4xv0+K1EMCr%t?>8Dc(<J^Aj<q#(ZPcRQCy?krkrA3gu) z=jW&AvFz?SXz)##Mej|e-?8ul2bWNh5bc%)wZFeT4YNtpbuNft5cWHKBRtaS@MiI@ zU5Bj`_H-XP8dy4ai~1jZ8}*eRx%s{`Kjr0lr`%_8XkPWZon>EMTr9h`Ci0QiQH#E3 zt84yzJbu*k!v$ylpOb>#?S60fI&MvmFOP)5fqnn~{r=~rG<jzBP1Uok$x1UnTnYB? zG<>hMTwn9()=x$2kM&C5){EVBqmh~YR79|ddPLHkWwV60*8D7b-4d~Bh2_^1_bMLu zCT*F#eRJgIG^1n9tF1%5_?~Sxyxw(w>h#95rgGoD@BhE|%=uThwyrL9Qc8}u5s!}S z6AS7{dGv0*jzszEdwX}Ujo-hoRBI!sN4z#$;%Tg<@6q~I8&0gg@Zqslse7NKNlK7h zvSeYRL&?QOuGhZm>|2twCTi;{AH~P*C2cG0Zws_2-96&YGSln4L#D{T{C(SP-tGJS zuE_rQZ-#d-L!<>0_DtgF*<Ac2N~>a>MY73M&UyCB=S8J6pDI+G9KLYn$_LweXMfQQ z*4y*JseX!7)%vKdS!<tvEBm%^ZTR|m9B040yj=UT%|Ju?c<j0*YVXZ_eOYER*3Lc} zyOSk4|M%PN-_uv_{C=<csG$&B#7CQlFAp~w=G-uleB;A<PG}Cpn$>|?ZCt+73|<PF zS80`JZky1#w*39Qe>$dntG*iXN}Gu+=MXk8y#KglhGBBsnLQrthYlI%Z@n6}&*h}p zS^r>00ST?gKR!OL^*pMuQRMXc)6J!7KFo(UcRb$^xjF5hSJsDXhWkQg!)AuDgGcUH z&C|p;hL5W?F-zxSir>+0E?=<>qvdxTuShO>y+QO-v0LE6&Q7)BQ@mG=W(GGbW%Nqh zaO@sukNs-9C&n{{KXOJm$v)pJ@OXihS}|9Kzc6#SrOC|6?F}6(R({#FBI0Gz!KI6K zsTFG-<nEuu=u)Jqzb9y+lmCZP+UtL8ezWj+k!bY0<Ar9nOp0q)RJmO6To%8-u4rxO z>acxFK4`p}*S7WRV)uTt*xhAQFYJ7AS>n9QbM@PiQ<D6iwKVqWw*Ng?z3}>?<qF(R zcV;kOwVD~MplE%RU6S$E)@=2K{T%n)100G@PMFYny=mXux`h8PANf3UIp1RI!hhOM zd~Z&v-!l;nuZ0H%a&K)}`NyFAUgdIq--4%8!@Vktjy+HEe&(Wl@UOtRm{m)zTFwk! zpmEhW$)x_vMfXK=U*6unK3z>gTzvtXtMUW!Xrtxra#bgG%yWC!p4EO)Uj0IiRm|Ma z?pF<F20OSc?{Ijk<fQuFs<3&nz_)FAcXt)#9_bL=XTtbOO3QEAl_e>Df!mD~D|d!( z3wfU6*SP3)W`?cvD?XlY8c)tE3Nxx~zws{i=i)OfJWtLs__lu6D=ou+k{U+^8-gF~ z`~9xlw(I<T)>Y9)%N;Iq`kLtbh0JZ5Afg%Cwq=vUf~+Vbbs^1#lUo04Hmo}N{h_O~ z>XM}S_5XfKuFeDd=|xN19P`E}@~O|x%-pnmk^1Y~I~F(Jo#5j-+4AA4y3c3L|3+46 z8P61U>eqEXe~2NaX#F7*C8h3lF*}3aEVcN)Zr#Z_UJmZsY6bV@icTmlGM+q3)`Rm= z)M=qutDB429GCmM{{Q#)>H9fc%5u8by-t+~ocgecQO>OBE0469&pGBLN{YJH_Y4;O zpYB@WcKz0Gq3pQ_rrCVA{eI`Mw>isn!)UXF!z|a0<&Jb!UEZL5(z0c4hqm2=kSNW^ zw!7JmO&61MRW~`%Z<cXEp^$O15wjwfUYOaZB`TBGr}*vID%o79<-POs;Z;^Mh5!Hl z@bK``pxyJ9I(@#mD%ohcLm-#0*18>!xK>v5N|{FG*QjwhO<cDq#AN2>OG~}qJ~=u0 zZ)6si=1gIx6Teqvybqo`$&$6pD`Ult74Egazui?>Ev5WDWZ%8L)xRh4I{Gd=dXD|^ zvEI#o^S`G{2dy|cM_}5fkn6KN0y%q)FDz}^xcAqq)%&j8vVOP2`P7Prrz_dEtP>3_ zo|MRSeuAR&pU5nU_YWll`y1lgG99L<-u4V!afPEwYirQSzC}W_w%@P2owxJpG^496 zD>aqf`?k!rE*E<&EGsLz(q=_XjoI|66KrNb5z*y-w%Ro3M!+|p(+gAcFE8tzYna^j z-z7`nQ_PvXFfYwre@qg2T0?4iQmmxij@d>$ouRoSE>dvi$vIuZzOR;O7;3)yYSJ}{ z7wfbijv`~O+u{(*mdjG+d2?7kSbV=zta|Uzrqt6ZefK%l=Olc)Q|$l$BopJpK(5KH z+~T)_{VZLT$~Pq$UsYOrb94G>u5+8Fu|E;Ic5Z`G>2cZeDWdCj{1aB1O?2j5;UUTW zNXk4<$6F|3=dSnre&3R_tvWHOE2H5sL!fE>kB97$OlPCir`a+muHv(Jz+kDT5@M?z zu*`pcUe(uEsi3i4MoW(SE-W)rGA2GhGcy@9W)eRsp5y73ijPT`7rAnOKE$o>@nc0Z zzuk=;TzS8~zt>M?X6G|$X5*ct(9UxH#pUJwN4C^G?lnJScw8pg?)Mwx`t2;BQTdhy zO6k78zr8&RnhAKiI&AGJt#_sS0vBoq#CYpnw5$I9F1L+GQYrOFs`s(44x8TJ+`Rma zHnY-K7j0uE#~a$yEDqe-n(exRi9^`iNMKp^`n_RFzg-p<R{VTA-PN|(c9Onn+PgbD zpB-T4|B~L?%;np->3-esnY!C<IQ2zu&(p20nyL{Jl+!Rfe_v!$f2i6?#dVX;Pge9$ zu;o--+i`PS(NnK_RmOv&FH^Iwu2Ox@p}JSMmuZ`C(fg38D=s|Y2r3kC?>!}KCf3+0 zA}gxsvh>f7$Nj1NpP!wb{H0o_P1>engZ|l`8oO*Ov)ehFC(X-vKDYdy!&{}YXp=Ij ztoXTBr7pIr2PYe={+YUJc}A<+^qnakE}rwGqiUKyweqe%-^9xOXZfb|-EX%|?&`g8 z;nkvNKJ#pD+I%{p{Ao_%iSL`2HBa5Nrthh&u#|<Rkj6nF>(W<G&iAY+w|=vsdFShO zyHlQK&Y8NkQvD&nhF;N3@3skkYp$O!f57GB{julV1hMr|W=}pvc1{*JkbKNIw&bE~ zk)NEb`h_B%BXI`~KX18I_U6V#yFVWe|MSv(eQoV$_oWX4m9IrF3@mYSKC>a_*`$MO zqb4q>oavCj^Xcgao14ePn`7*<E1jH80`ELM=eJQhf6vApD>^TE3LJgqcX4I<&U32G zs}3^6*m117u_AEs5nb*TmNzGzPxl2)xQd$_z3)Hp_Uvr)>5ON>)<*sG4~g&c%is{I zJ8%2_$7YtEVg|PI6DvDsPd{~QL!z^#J~vZ?d+)0k({fkwPSQ%8Gr#83$xnL}Klxmd zoWT3D;#x>Te~*mir8||+=T1^>*f{;k_qf==_cIqS+L^M5t@M-IryCZaIU|d$Zb3~J zyH9P&zV3Hw`~AA=o8ciDLAJ>^9@%AAChReGYo6R15X5u9D)dJD+G7=_Ycmu~bc6k! z7;G0^-W?Y9hE?f<`%I(Mu3B)}o@KBmw)JzmX!DtPA8*d{Vo$f-&YPT`a>{etlTVS4 zEtvXjzeT*hlGJ&#XKnN6rB=Zuw!W^#CshvLaMFv)$XVMbYwa~{W5~h3PI0ntCOO;* zvkpJc^>8lhr1Og(n5EftGPL*Tw*Faob?PGFS)gX_zcriBO)`{NR%+0}mU!Mo;0@=o z!bLCN1v#p^Y*jpO*H+zdCZX`s>s|@NLp7hznyYfonxwc<=*%or?{}vqg6>2z?5qmo zc+X|=Y_4GgrwhYVn})eJ)<&Bfg9g!gcHFynr|$RLe_5t2@0QJ2;h{IP<N@2Uxu;(9 zNUsvOKXGMu#i16?Yd3W#eNvWvKCgP6N`q47m9N+1_d7mo_F3^_^Q0YJ<*s^?SB$QB zZj#&}m_5sA-RC(n`~Uy@{i&R#^8J;Slc#LWy&bl%95h|Lc30q(*O@=2-|g3&x6xof zcZO`|`#VcKh2s}#NH6NXAfl@FswiyA3<9eua1^U(!#}Sxr+BsJF!<Ykosz<$5XY=o zc2etrS;7H^&3SjNj7wjI+`PUz+&}4ps0c@I&{qM~^eZa@H;L@<oo)8*l=k{58`i9x zVPC&5_v)(9d&<>plY}id3hpa^y>|OFk0mMvN;!I`mw6sK^7Yx-*|+z8zZc#2Gx{AP zXe@B=lS$rhcE8`34Vr7$-}AxgH)y!lbkWTTd%9F#C?=f+O-m#;adV2!VtTl2cHSms zx1I?GK`BCuZI$l`9-0DLu#kIqS849sTU%9^<fR@c7HGG;u+W*kv(~p_S=`>Lo8s{` z7vnby?PhrR|Ig?1Pv8Hr7Z8frmNT=a?Ae)_pX}E}Y;-#Ha8B{LjqLI@8;%Gw{3|=H zyM0UXvojMvJ2|#4V_4X(xK5(Uxavzr{4TrI;p?YOvz3b1xi4_U#(_U*xu5Ls2hIGy zCIxZq6RdKaXCG>lV|RILc6j}?hKS3_$9g`dhcV5&U?37DP$D{u>FL_-_a+^9`1{S~ ze*K#h8kyN|J)c*7ZvNBx84V6s46d9=smM!Te!{TH_xwb!v!JygZ$CUd{5Mi5L;3&v z+xh$7daqfrBE_>IqcU`{(`040J{ir<sF$*b+$=OsZEl@5_czN`HOWPvmV3p`?uxOA zFA6y;Fi+EKx?b#?NvhsYew;BrzvaB$?=>>+ifh?cMVd^~@XXmbb5X)}-&rP}EFY&u z=Y3on>L0jk&i>!;tUL963`&}q^kR2iiPvmBP<2GU?EQ)9OVr~#Cwb_xuAJ9D;~b0b z`uP3(^gaFe&C6dtx9pQUXAG<PxgXPiPuegop=afzWc~PkcTBRbc-ToRJWuPWl2XX9 zI+=UCPc}M2ZcED>5&nvNgQQOLH(GsLcHCeK>{#hzdAnV{?uB>o<kK#mCqX%Ko?_^( z;wG+TSw|<IeEM1SyGh`_U$3;QFF3N_%-{bv?4DuPP4)RTkJjgT6bY$Uoq52}(PX^v zg2t|S$0TP>a-A`)`x<Y?)=W`m#jh5+nU|OOKDChj;&CMY*}R=<NAmsr0#1LPl&3#g zhy7>GK8==>sy+ITwYJ|an{D~^O0eqsbSsWn=~lVRH%<Pnnc@>PH^eHgqN{E5dAr?5 zY`acR3BRzny4;fgLQ>GZ&FTK}ljQ8CpTE*5aa?^~#Ugv}1D}Oj?S+ee*1W8m<=QQF zGw<%MlOL<!?^WLxu_|uyvVbjp40mRRXly$!SAAx>+7*^NbEdf8o&S>KyT^7{?I<$= z;h!rO_kBvgX7x8N$tvAzReD!m;XM7R7ZhZsDC;zQm=J9&c||8=`z(ixev2Jb6|3r+ z*?7+^pI@i7^KIix*>#gn8M0*;i9Jd&IA*x&vg5OjAuElISicsm-}`OSbEmm)Zf{@j z<IE}e>$LLp^X*nY?i8OtY3OEk%r5iF$ufs$7T14xgfRzYt2F<*wk<ch_>AH4ACt0r zI{XBS?i3#P^p-v*!sPRD!JW{s_#iWhn-(IUSo0$8>`1Y`QTzRFs*5e(&YFEs=CL`; zH~&8_Uq2<TC5L-uh~u$L4?dr1&u2MYJXf@=ym7^CVW&EYI~Qj@(3&K1`{$uhi^p1v zzSK$Fc(T67S#SRNtK#)j8lL!=-zjKT*|+Q2tn8j(>0m3F6Q0gdM?Mw(4H54O%W%>= zectZ3&o_g_LoGu6QOlg1TV4L1bX~M0#NcxIy*-sdnkT(vr^ndEn@pViI`WB(X>Xy5 z(ayPLw=(}_IUP80tL)1~cj@cep3`=w^ha)W{WV*u<)_b?6{-J3iWhwNVW2%d{E(PC z@2ZSND(WlBj`c|XTO*QwJmPv+bokC%&$1_06NRNc=SW;lpJuG3!nyH~soI=Rk^9;u zFHHFuYudU<be3uMwG;CkEth33=zg5^to@n$dY?8v*{tYoIgv&ZJIdZhRsZ|>{4`H| z8nbq*YakczYtszP!&3YSb>VxfzPe;A^Pm6DTf6=F{Q9_k+w1=RGFm#X_Vu;3**y<q zE^Bzres-_=y{Y~GKbv<%GwzkEonx7)63kn?PmB8vQ_&mq{ChDuYqZzzxwPlYCGSbR zk(xGLMRVGE&ZUT}S%1>s|0n5KzkL3jce=-n+Ey=`;(C!UZ(YR3Lpo7gJZ!ACyf`=E zYq-eL9Fq-gi;gVGIG!n@8V&CD<7l~WG<f!ZoihKmgAX<zXJBAp@O1TaS?83{1OT|w BQk?(* literal 0 HcmV?d00001 diff --git a/doc/manual.dox b/doc/manual.dox index 7908d82..0a378b9 100644 --- a/doc/manual.dox +++ b/doc/manual.dox @@ -79,6 +79,7 @@ This manual is divided in the following sections: - \subpage maingo_output - \subpage algorithm - \subpage parallel_version +- \subpage growing_datasets - \subpage special_uses - \subpage maingo_in_your_software - \subpage maingo_settings @@ -141,7 +142,8 @@ If you obtain the binary distribution of <tt>maingopy</tt> from PyPI, there is n Building MAiNGO requires the following software that is not provided when obtaining MAiNGO: - CMake 3.15 or later -- Visual Studio 2017 or 2019 (Windows), or some other C++ compiler that supports C++17 (Linux & MacOS) +- Visual Studio 2017 (version 15.8) or 2019 (version 16.5), or some other C++ compiler that supports C++17 (Linux & MacOS) +- On Windows: Windows SDK 10.0.2 or later (https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/) - A Fortran Compiler (Linux & MacOS only; for Windows, we supply pre-compiled versions of all Fortran dependencies) CPLEX 12.8 or 12.9 and Knitro 11.0.1 are optional subsolvers that are also not provided with MAiNGO. @@ -174,7 +176,7 @@ Please see also section \ref embedded. @subsection cmake_win Windows -On Windows, only Microsoft Visual Studio 2017 and 2019 are supported. We supply pre-compiled versions for all Fortran libraries, so no Fortran compiler (or runtime) should be needed. +On Windows, only Microsoft Visual Studio 2017 (version 15.8) or 2019 (version 16.5) with SDK version 10.0.20348.0 or later are supported. We supply pre-compiled versions for all Fortran libraries, so no Fortran compiler (or runtime) should be needed. To generate the Visual Studio project and compile MAiNGO, you need to complete the following steps: 1. Start CMake and navigate or type the path to your MAiNGO directory (this is the one where the Readme.md is) and select your build directory. @@ -203,11 +205,10 @@ and setting it to be the starting project. If you get an error message saying th @subsection cmake_linux_os Linux and MacOS -On Linux and MacOS, the following compiler configurations have been tested: -- gcc 9.2, and 10.1 (Linux and MacOS) -- Intel C++ and Fortran Compiler 16.0.8, 17.0.7, 18.0.3, and 19.1 (Linux) - only works if building without the parser (<tt>MAiNGO_build_parser=false</tt>) and without the MeLOn toolbox - (<tt>MAiNGO_build_melon=false</tt>) because of missing C++17 features in Intel compilers -- Clang 7.0, 8.0, 9.0 (Linux) and clang-1001.0.46.4 (MacOS) with gfortran - only works if not using CPLEX (CPLEX seems to be incompatible with Clang) +On Linux and MacOS, you need a suitable C++ and Fortran compiler: +- gcc: minimum version required 8.3 +- Intel C++ and Fortran Compiler: minimum tested version 16.0.8 +- Clang: minimum tested version 7.0 (Linux) and clang-1001.0.46.4 (MacOS) with gfortran - only works if not using CPLEX (CPLEX seems to be incompatible with Clang) On Linux, the Fortran parts are usually not an issue thanks to gcc/gfortran. On MacOS, you may need to install gfortran separately. If you use gfortran and get a linker error when compiling the code stating that a library was not found for <tt>-lgfortran</tt>, execute \code{.sh} @@ -274,15 +275,21 @@ If you obtained the <tt>maingopy</tt> package from PyPI, it should be found auto \page writing_problem Modeling in MAiNGO + @section modeling_ALE Modeling with ALE A convenient way of modeling with MAiNGO is to use [ALE](https://git.rwth-aachen.de/avt-svt/public/libale.git), which provides a framework for writing logical-algebraic expressions. The input can be written as .txt files in ALE syntax. An exemplary problem.txt file can be found in <tt>examples/01_BasicExample/problem.txt</tt>. -An example for how MAiNGO can be used to read such as file can be found in <tt>examples/mainAleParser.cpp</tt>. +An example for calling MAiNGO using ALE can be found in <tt>examples/mainAleParser.cpp</tt>. + +The following sections give a short introduction to modeling with ALE. +This short introduction should be sufficient for most users. +However, it does not cover all features of ALE. +For a detailed description of the ALE modeling language, refer to the README.md in the git repository of [libALE](https://git.rwth-aachen.de/avt-svt/public/libale.git). The ALE syntax uses data types to decide which expressions can appear in which context. -All data types are constructed from the basic types **real**, **index**, and **boolean** and potentially derived types such as **set** and **tensor**. +All data types are constructed from the basic types **real**, **index**, and **boolean** and potentially derived types such as **set** and **tensor**. Every expression has one of the following types: - **scalars** of the basic data types (e.g., **real scalar**) - **tensors** of the basic data types up to a maximum dimension (default: 3) (e.g., **index matrix**) @@ -292,25 +299,88 @@ In the context of modeling in MAiNGO, optimization variables are real scalars or Furthermore, constraints are boolean scalars, which result from comparison operations (inequalities and equalities) on real scalars. Finally, indexes are used to dereference tensors and sets can be used in expressions that expand over all their elements. -@subsection ALE_general File Structure +@subsection ALE_section_headings File Structure and Section Headings -The input file is structured into sections, which are initiated with an appropriate keyword (e.g., **definitions**) followed by a **colon** (:). -Within each section, there may be zero, one, or more statements, which are each terminated by a **semicolon** (;). -At any point in the input file, the **pound symbol** (#) may be used to initiate an end-of-line comment, meaning that all input between the pound symbol and the end of the current line is ignored. -Finally, **whitespace** characters have no meaning except that they serve to separate keywords. +The ALE input file for modeling in MAiNGO is structured into sections (in contrast to the ALE input file for the ALE demo in [libALE](https://git.rwth-aachen.de/avt-svt/public/libale)), which are initiated with an appropriate keyword (e.g., **definitions**) followed by a **colon** (:). +Within each section, there may be zero, one, or more statements. +The different sections start with the appropriate keyword followed by a colon (:) and are used to differentiate between **definitions, objective, outputs, constraints, relaxation only constraints**, and **squashing constraints**. +A single input file may have arbitrarily many sections of the same type that may be empty or contain arbitrarily many statements. +At any point in the input file, the pound symbol (#) may be used to initiate an end-of-line comment, meaning that all input between the pound symbol and the end of the current line is ignored. +Finally, whitespace characters have no meaning except that they serve to separate keywords. -@subsection ALE_definitions Parameter, Variable, and Expression Symbols +@subsubsection definition_section Definition Section -Symbols are named entities that refer to fixed values, variables, or expressions of a particular type. -The names used are allowed to contain **letters**, **numbers**, and **underscores** and must start with a **letter**. All symbols must be defined in a **definitions** section of an input file which is initiated by \code{.sh} -definitions: + definitions: \endcode -A single input file may have arbitrarily many definitions sections that may be empty or contain arbitrarily many definitions. However, since symbols can only be used once they have been defined, it is advisable to collect all definitions in a single definitions section at the beginning of the input file. +The definition of symbols and variables is explained in a later section (see \ref ALE_definitions). + + +@subsubsection objective_section Objective section + +The objective function to be **minimized** must be defined in an **objective** section of an input file which is initiated by +\code{.sh} + objective: +\endcode +Each objective section may only contain a single real scalar expression with an optional description in **quotes** (" ") and terminated by a **semicolon** (;): +\code{.sh} + sin(x^2) "the objective function"; +\endcode +If a file contains multiple objectives, the last defined objective function will be used if multiple objective sections are present in a file. + +@subsubsection outputs_section Outputs Section + +Optionally, additional outputs can be defined in an **outputs** section of an input file which is initiated by +\code{.sh} + outputs: +\endcode +Each outputs section may contain arbitrarily many scalar real expressions, each with an optional description in **quotes** (" "), terminated by a **semicolon** (;): +\code{.sh} + 2 * sin(x^2) "twice the objective function"; +\endcode +Upon completion of optimization, the additional outputs will be evaluated at the optimal solution and reported. + +@subsubsection constraint_section Constraints Section + +Constraints must be defined in a **constraints** section of an input file which is initiated by +\code{.sh} + constraints: +\endcode +Each constraints section may contain arbitrarily many boolean scalar expressions, each with an optional description in **quotes** (" "), and terminated by a **semicolon** (;). +MAiNGO will only accept constraints that either result from a **weak inequality**, an **equality**, or a set-based expansion over a **weak inequality** or an **equality**. +Note that this restriction does not preclude the use of general boolean expressions in the context of indicator sets. +\code{.sh} + x <= yv[1] "a simple inequality"; + yv[3] = 4 "a simple equality"; + forall k in {1, 2} : yv[k] <= yv[k + 1] "a set-based inequality"; +\endcode -All definitions begin with a declarator indicating the desired data type (e.g., **real**, **index**, or **boolean**) and are terminated with a **semicolon**. +@subsubsection relaxation_section Relaxation-only Constraints Section + +Relaxation-only constraints (see \ref advanced_modeling) must be defined in a **relaxation only constraints** section of an input file which is initiated by +\code{.sh} + relaxation only constraints: +\endcode +These constraints must satisfy the same conditions as regular constraints and are denoted in the same way (see \ref constraint_section). + +@subsubsection squashing_section Squashing Constraint Section + +Squashing constraints (see \ref advanced_modeling) must be defined in a **squashing constraints** section of an input file which is initiated by +\code{.sh} + squashing constraints: +\endcode +These constraints must satisfy the same conditions as regular constraints and are denoted in the same way (see \ref constraint_section). +Furthermore, they must result from a **weak inequality** or a set-based expansion over a **weak inequality**. + + +@subsection ALE_definitions Parameter and Variable Definitions + +Symbols are named entities that refer to fixed values, variables, or expressions of a particular type. +The names used are allowed to contain **letters**, **numbers**, and **underscores** and must start with a **letter**. +All symbols must be defined in a **definitions** section of an input file (see \ref definition_section). +All definitions begin with a declarator indicating the desired data type (e.g., **real**, **index**, or **boolean**) and are terminated with a **semicolon** (;). @subsubsection parameter_definitions Parameter Symbol Definitions @@ -334,17 +404,9 @@ For convenience, tensors can also be initialized with a scalar: real[3,3] rm := 0; # defines a 3x3 real matrix parameter filled with zeros \endcode -Particular entries of tensors or values of scalars can later be changed using the **assignment operator** (<-). -A **colon** (:) can be used to indicate an assignment for all values of a particular index: -\code{.sh} -i <- 1; # assigns the value 1 to i -rm[1,2] <- 5.2; # assigns the value 5.2 to entry [1,2] of rm -rm[2,:] <- 2.2; # assigns the value 2.2 to entries [2,1], [2,2], [2,3] of rm -\endcode -In the context of assignments, it is important to note that all expressions provided for modeling are evaluated late, meaning that only the final value of a parameter symbol has any impact on the model. For the definition of sets, the declarator is composed of the keyword **set** and the desired element type in braces. -Therein, tensor-valued elements are denoted using the same notation as above with a **colon** instead of the tensor lengths. +Therein, tensor-valued elements are denoted using the same notation as above with a **colon** (:) (called wildcard operator in the README.md of [libALE](https://git.rwth-aachen.de/avt-svt/public/libale.git)) instead of the tensor lengths. Similarly, set values are denoted as comma-separated lists in braces. Contiguous index set values can be denoted in short-hand as shown below. The following are valid definitions of different **set parameters**: @@ -356,7 +418,8 @@ set{real[:]} v_set := {(1.3, 2.4), (3.5, 4.6)}; # defines real vector set contai @subsubsection variable_definitions Variable Symbol Definitions -Variable symbols can be defined using largely the same syntax as parameters with the difference that variables are not assigned values but bound intervals using the **in** operator. +Variable symbols can be defined using essentially the same syntax as parameters with the difference that variables are not assigned values but bound intervals using the **in** operator. +For unbounded variables, the bound assignment is omitted. Furthermore, variables can only be real scalars or tensors. The following are valid definitions of different **scalar** and **tensor variables**: \code{.sh} @@ -364,201 +427,90 @@ real x; # defines an unbounded real scalar v real[3] y in [(1, 2, 3), (2.5, 1e2, 5.4)]; # defines a bounded real vector variable real[2,2] z in [0, ((20, 10), (23, 15))]; # defines a bounded real matrix variable with all lower bounds being zero \endcode -Note in the above example that one or both bounds of a tensor variable may be provided as a single scalar. +In the above example, one or both bounds of a tensor variable may be provided as a single scalar. -Integral variables can be defined by replacing the **real** declarator by either *binary* or *integer*. -In the case of a binary variable, no bounds can be assigned ass they are implicit. +Integral variables can be defined by replacing the **real** declarator with either *binary* or *integer*. +In the case of a binary variable, no bounds can be assigned as they are implicit. The following are valid definitions of different **binary** and **integer variables**: \code{.sh} binary bx; # defines a binary scalar variable -integer[3] iy in [(1, 2, 3), (2, 10, 5)]; # defines a bounded integer vector variable +integer[3] is in [(1, 2, 3), (2, 10, 5)]; # defines a bounded integer vector variable \endcode -Similar to the assignment of parameter values, the **lower bound**, **upper bound**, and **initial point** of a variable symbol can be assigned using the **assignment operator** (<-) and the appropriate suffix for the variable name (**lb**, **ub**, or **init**): +The **lower bound**, **upper bound**, **branching priority**, and **initial point** of a variable symbol can be assigned using the **assignment operator** (<-) and the appropriate suffix for the variable name (**.lb**, **.ub**, **.prio** or **.init**): \code{.sh} x.lb <- -1; # assigns the lower bound of x to be -1 x.ub <- 3; # assigns the upper bound of x to be 3 +x.prio <- 2; # assings the branching priority of x to be 2; x.init <- 2; # assigns the initial point of x to be 2 z.lb[1,1] <- 2; # assigns the lower bound of entry [1,1] of z to be 2 \endcode -@subsubsection expression_definitions Expression Symbol Definitions -Expression symbols are similar to parameter symbols with the difference that they do not hold a basic value but rather an arbitrary expression of the appropriate type. -In contrast to parameter symbols, expression symbols can only be real, index, or boolean scalars. -The syntax for defining expression symbols is identical to the one for parameter symbols with the difference an expression is assigned instead of a constant. -For further information on what constitutes a valid expression, refer to \ref ALE_expressions. -The following are valid definitions of different **scalar expressions**: -\code{.sh} -real re := exp(x) - 4*x; # defines a real expression -index ie := 2*i + 1; # defines an index expression -\endcode -Note that upon evaluating the input, expression symbols are substituted by their expression and don't become optimization variables. -As such, they can be used to write reduced-space formulations in a procedural way. +@subsubsection function_definitions Intrinsic and User-defined Function Symbols -@subsection further_sections Further Section Headings +\note +So-called **expression symbols** were used in the past to encode functions without arguments. **Function symbols** will replace **expressions symbols** in the upcoming release\. +For further information on **expressions symbols** [depricated], please refer to the README.md of [libALE](https://git.rwth-aachen.de/avt-svt/public/libale.git) -Beyond the definitions section, there are several sections for defining expressions such as the objective function. -While the examples provided here should be self-explanatory, a more detailed discussion of possible expressions is provided in \ref ALE_expressions. +For convenience, ALE provides some basic and special functions, which are already intrinsically defined. +Additionally, user-defined function symbols can be defined similarly to definitions. +ALE permits the usage of functions in definitions, assignments, functions, or expressions. +As such, they can be used to write reduced-space formulations procedurally. -@subsubsection objective_section Objective section +For a complete list of implemented intrinsic functions beyond the standard functions (exp(), log(), sin(), asin(), etc.), refer to doc/implementedFunctions/Implemented_functions.pdf in the [MAiNGO](https://git.rwth-aachen.de/avt-svt/public/maingo) git repository. +The definition of user-defined functions is also possible. +Please refer to the README.md of [libALE](https://git.rwth-aachen.de/avt-svt/public/libale) for further information on user-defined functions and additional intrinsic functions provided by libALE. -The objective function to be **minimized** must be defined in an **objective** section of an input file which is initiated by -\code{.sh} -objective: -\endcode -Each objective section may only contain a single real scalar expression with an optional description in **quotes** (" ") and terminated by a **semicolon** (;): -\code{.sh} -sin(x^2) "the objective function"; -\endcode -If multiple objective sections are present in a file, the last defined objective function will be used. -@subsubsection outputs_section Outputs Ssection +@subsubsection advanced_modeling_with_ale Advanced Modeling with ALE +The ALE modeling language offers other powerful features that are not covered in this MAiNGO documentation. +The following gives a non-exhaustive preview of available features when modeling with ALE. -Optionally, additional outputs can be defined in an **outputs** section of an input file which is initiated by -\code{.sh} -outputs: -\endcode -Each outputs section may contain arbitrarily many scalar real expressions, each with an optional description in **quotes** (" ") and terminated by a **semicolon** (;): -\code{.sh} -2 * sin(x^2) "twice the objective function"; -\endcode -Upon completion of an optimization, the additional outputs will be evaluated at the optimal solution and reported. - -@subsubsection constraint_section Constraints Section - -Constraints must be defined in a **constraints** section of an input file which is initiated by -\code{.sh} -constraints: -\endcode -Each constraints section may contain arbitrarily many boolean scalar expressions, each with an optional description in **quotes** (" ") and terminated by a **semicolon** (;). -MAiNGO will only accept constraints that either result from a **weak inequality**, an **equality**, or a set-based expansion over a **weak inequality** or an **equality** (see \ref boolean_expressions). -Note that this restriction does not preclude the use of general boolean expressions in the context of indicator sets (see \ref set_expressions) -\code{.sh} -x <= y[1] "a simple inequality"; -y[3] = 4 "a simple equality"; -forall k in {1, 2} : y[k] <= y[k + 1] "a set-based inequality"; -\endcode - -@subsubsection relaxation_section Relaxation-only Constraints Section +Parameters can be used in the definition of other symbols, see [Program Flow](https://git.rwth-aachen.de/avt-svt/public/libale/-/tree/master#program-flow). +[Wildcards](https://git.rwth-aachen.de/avt-svt/public/libale/-/tree/master#assignments-and-wildcards) can be used to assign or access tensor ranges. +[Intrinsic and User-defined Function Symbols](https://git.rwth-aachen.de/avt-svt/public/libale/-/tree/master#intrinsic-and-user-defined-function-symbols) are powerful tools for reduced space formulations and for many intrinsic function symbols custom relaxations exist. +Sets can be constructed using logical conditions. Also, the union or intersection of sets is possible, see [Set Expressions](https://git.rwth-aachen.de/avt-svt/public/libale/-/tree/master#set-expressions). +Constraints can be elegantly generated over sets, also known as indexed equations, using the [forall](https://git.rwth-aachen.de/avt-svt/public/libale/-/tree/master#scalar-boolean-expressions) operator. +Any real expression can be differentiated using the [diff-function](https://git.rwth-aachen.de/avt-svt/public/libale/-/tree/master#symbolic-differentiation). +Finally, for a complete introduction to the ALE modeling language, please refer to the Git documentation of [libALE](https://git.rwth-aachen.de/avt-svt/public/libale/-/tree/master#program-flow) as well as the [example input file](https://git.rwth-aachen.de/avt-svt/public/libale/-/blob/master/demo/input_files/input.txt) mentioned therein. -Relaxation-only constraints (see \ref advanced_modeling) must be defined in a **relaxation only constraints** section of an input file which is initiated by -\code{.sh} -relaxation only constraints: -\endcode -These constraints must satisfy the same conditions as regular constraints and are denoted in the same way (see \ref constraint_section). - -@subsubsection squashing_section Squashing Constraint Section - -Squashing constraints (see \ref advanced_modeling) must be defined in a **squashing constraints** section of an input file which is initiated by -\code{.sh} -squashing constraints: -\endcode -These constraints must satisfy the same conditions as regular constraints and are denoted in the same way (see \ref constraint_section). -Furthermore, they must result from a **weak inequality** or a set-based expansion over a **weak inequality**. @subsection example_input Example Input File -The following are the contents of a valid input file +The following are the contents of a valid input file. \code{.sh} -definitions: -binary x; -real y in [-2,2]; - -real a := 20; -real p1 := 0.2; -real p2 := 3.14159265358979323846; # ~ PI - -real temp1 := -p1 * sqrt( (x^2 + y^2) / 2 ); # This is neither an optimization variable nor an equality constraint -real temp2 := (cos(p2*x) + cos(p2*y)) / 2; # This is neither an optimization variable nor an equality constraint - -x.init <- 0; -y.init <- 1; - -constraints: -x <= 1; - -pow(x,2) + sqr(y) = 1 "circle equality"; - -relaxation only constraints: -y - 1 <= 0; - -y + x = 1; - -outputs: -temp1 "Result of temp1"; + definitions: + binary x; + real y in [-2,2]; -objective: # Always minimizing --a * exp(temp1) - exp(temp2) + a + exp(1); + real a := 20; + real p1 := 0.2; + real p2 := 3.14159265358979323846; # ~ PI -\endcode - -@subsection ALE_expressions Expressions - -The ALE syntax for denoting expressions is similar to other established modeling languages and should be mostly intuitive. -In the following, the different expression types and the allowed operators are discussed: - -@subsubsection real_expressions Scalar Real Expressions - -The basic building blocks of real scalar expressions are **constants**, **symbol names**, and **entries of real tensors**. -Constants can be any real number written in floating-point or scientific notation as shown above in the definition of symbols. -Entries of tensors are accessed by bracket notation as shown above for the assignment of tensor entries. -Operators are defined for **addition** (+), **subtraction** (-), **multiplication** (*), **division** (/), and **exponentiation** (^). -Parentheses are used to enforce precedence and to denote arguments to functions. -For a complete list of implemented functions beyond the standard functions (exp(), log(), sin(), asin(), etc.), refer to doc/implementedFunctions/Implemented_functions.pdf in the <a href="https://git.rwth-aachen.de/avt-svt/public/maingo">MAiNGO git repository</a> . -The following are valid **real expressions**: -\code{.sh} - x + sin(y[1]) - 3 ^ (2 * z[1,1]) -\endcode -For convenience, ALE provides the following set-based expressions that expand over a set of any given type: -\code{.sh} - sum(k in {1 .. 3} : rv[k]) - sum(v in v_set : v[1]) - min(r in {1.2, 10.0, -1.5} : r) - max(k in {1 .. 3} : rv[k]) -\endcode -Note however, that the min and max function can also be used without set expansion: -\code{.sh} - min(y[2], 3e-2, 5.3, x) - max(rm[1,1], rm[2,2]) -\endcode + real func1() := -p1 * sqrt( (x^2 + y^2) / 2 ); # This defines a custom function without any arguments. x and y can be used within as they are defined above + real func2(real x, real y) := (cos(p2*y) + cos(p2*x)) / 2; # This defines a custom function with the arguments x and y. + x.init <- 0; + y.init <- 1; -@subsubsection index_expressions Scalar Index Expressions + constraints: + x <= 1; -Similar to real expressions, the basic building blocks of index expressions are **constants**, **symbol names**, and **entries of real tensors**. -However, index expressions and therefore also constants and the values of symbols can only take whole values. -By the same token, only the operators for **addition** (+), **subtraction** (-), and **multiplication** (*) are defined for indexes. -Parentheses are used to enforce precedence. + pow(x,2) + sqr(y) = 1 "circle equality"; -@subsubsection boolean_expressions Scalar Boolean Expressions + relaxation only constraints: + y - 1 <= 0; -The basic building blocks of boolean expressions are **constants**, **symbol names**, and **entries of real tensors**. -Furthermore, boolean expressions result from the **comparison operators** (<, <=, =, >=, >). -Boolean expressions and therefore also constants and the values of symbols can only be **true** or **false**. -Operators are defined for *conjunction* (&), *disjunction* (|), and *negation* (!). -Parentheses are used to enforce precedence. -The following are valid **boolean expressions**: -\code{.sh} - b & false - x * y[1] <= 2 -\endcode -Furthermore, ALE provides a set-based conjunction similar to the set-based real expressions: -\code{.sh} - forall k in {1, 2} : y[k] <= y[k + 1] - forall r in {1.2, 10.0, -1.5} : x >= r -\endcode + y + x = 1; -@subsubsection set_expressions Set Expressions + outputs: + func1() "Result of func1()"; -The basic building blocks of set expressions are **constants** and **symbol names**. -These basic sets can be refined by employing an **indicator set**, which only contains those elements that satisfy a logical condition: -\code{.sh} - {r in {1.2, 2.3, 3.4} : r <= 3} # only contains 1.2 and 2.3 + objective: # Always minimizing + -a * exp(func1()) - exp(func2(y,x)) + a + exp(1); \endcode - @section modeling_cpp_python Modeling via C++ or Python Another way of modeling with MAiNGO is to directly work with the C++ or Python APIs. @@ -620,6 +572,11 @@ but that you are interested in at the optimal solution. If you are using the <tt>squash_node</tt> function, it is neccessary to introduce appropriate squash inequalities to maintain correctness of the optimization problem. For more information refer to <tt>doc/implementedFunctions/Implemented_functions.pdf</tt>. +@section c_api C API + +MAiNGO has a basic C API that enables the solution of a problem defined by a C-style character array in \ref modeling_ALE "ALE syntax". The C API consists in the function \ref solve_problem_from_ale_string_with_maingo. +Please refer to the documentation of this function for details. + @section parsing_gams Parsing GAMS Files We also provide a tool for parsing GAMS convert files to ALE problem.txt or MAiNGO problem.h files. For detailed description please refer to <tt>utilities/MAiNGO_Reader_Writer/</tt> and the @@ -966,6 +923,43 @@ If you type in a wrong password or a wrong username resulting in MPI not allowin In this case you can reset it via <tt>\<mpiexec\> -remove</tt>. +\page growing_datasets MAiNGO - Parameter estimation + +MAiNGO implements the extension for handling parameter estimation problems considering large datasets proposed in + +S. Sass, A. Mitsos, D. Bongartz, I. H. Bell, N. I. Nikolov, A. Tsoukalas: A branch-and-bound algorithm with growing datasets for large-scale parameter estimation (Submitted in 2023) + +This approach focuses on general nonconvex optimization problems of the form +\htmlonly <style>div.image img[src="ParamEstProblem.PNG"]{width:6cm;align:left}</style> \endhtmlonly +\image html ParamEstProblem.png +where the summation index runs over an index set of data points and the objective is given by a sum over data-dependent terms. +Note that there is no special treatment of potentially data-dependent constraints in the current release of MAiNGO. + +When using the B&B algorithm with growing datasets, the procedures for accessing, compiling, and running MAiNGO remains the same for both the sequential and the parallel version. +In order to use growing datasets please set the CMake variable <tt>MAiNGO_use_growing_datasets=true</tt> and define an objective per data f<sub>i</sub><sup>data</sup> in the problem formulation. +See <tt>examples/07_GrowingDatasets/problem_growingDatasets_simple.h</tt> (modeling via C++), <tt>examples/07_GrowingDatasets/growingDatasets_simple.py</tt> (modeling via Python) or <tt>examples/07_GrowingDatasets/problem_growingDatasets_simple.txt</tt> (modeling with ALE) for an example. +When using <tt>modelWritingLanguage=LANG_ALE</tt> the objectivePerData section in the generated txt-file will repeat the objective function over the full dataset written down in the following objective section. +With this, we want to showcase the syntax, even though we have no means to extract the single data points. + +When using growing datasets, there is another column in the table in the MAiNGO output called <tt>NoData</tt>. +The number indicates the size of the dataset used in this iteration and the letter <tt>A</tt> or <tt>B</tt> whether <b>a</b>ugmenting or <b>b</b>ranching is triggered. +It may look like this: +\code{.sh} +| Entering branch-and-bound loop: +| Iteration NoData LBD UBD NodesLeft AbsGap RelGap CPU +| 1 1 B 1.807705e-01 5.066667e-01 2 3.258961e-01 6.432160e-01 1.720000e-01 +| 2 1 A 1.807705e-01 5.066667e-01 2 3.258961e-01 6.432160e-01 1.720000e-01 +| 3 1 A 1.807705e-01 5.066667e-01 2 3.258961e-01 6.432160e-01 1.880000e-01 +| 6 2 A 1.814041e-01 5.066667e-01 4 3.252625e-01 6.419655e-01 1.880000e-01 +| 7 2 A 1.814041e-01 5.066667e-01 4 3.252625e-01 6.419655e-01 2.030000e-01 +| 10 2 A 2.187275e-01 5.066667e-01 2 2.879392e-01 5.683010e-01 2.030000e-01 +| 11 2 A 2.187275e-01 5.066667e-01 2 2.879392e-01 5.683010e-01 2.030000e-01 +| 13 3 B 5.066494e-01 5.066667e-01 0 1.725081e-05 3.404764e-05 2.030000e-01 +| Done. +\endcode + +Additional settings allow to adapt this extension to the needs of the specific model solved, cf. settings with prefix <tt>growing</tt> in <tt>MAiNGOSettings.txt</tt>. These include, e.g., the choice when and how many data points to augment as well as the size of the initial dataset. For more details please refer to Sass et al. (Submitted in 2023) referenced above. + \page special_uses Special Uses of MAiNGO @section maingo_multistart Using MAiNGO as a multi-start local solver: @@ -1048,6 +1042,27 @@ Similarly, in the example main file for using text input based on ALE, <tt>examp <br> \page faq What can go wrong? +@section faq0 Errors Occured During Project Generation with CMake + +Please check the CMake error message. +The error message often already indicates a solution. +Try the following solutions if CMake reports an error message stating that + +- <b>"Could not find CMakeLists.txt at PathToMAiNGODirectory/dep/..."</b>\n +This error usually occurs when submodules have not been initialized and updated correctly. +Check if each dependency directory in the <tt>dep</tt> directory is non-empty. +Ensure you have carried out all the steps described in \ref get_maingo. +- <b>"Unsupported Visual Studio version VisualStudioVersionNumber"</b>\n +Visual Studio 2017 version 15.8 or later or Visual Studio 2019 version 16.5 or later are required for building MAiNGO on Windows, also see \ref req_software. +- <b>"Using incompatible Windows SDK version SDKVersionNumber"</b>\n +This error message indicates that you are using an incompatible Windows SDK version, also see \ref req_software. +Please try the following + -# Check whether a compatible Windows SDK version >= 10.0.20348.0 is installed; install compatible Windows SDK if necessary. + -# If CMake selects an incompatible Windows SDK version, manually select a compatible version >= 10.0.20348.0 as described below. + -# Uncomment or add the line: 'set(CMAKE_SYSTEM_VERSION 10.0)' in the CMakeLists.txt of the MAiNGO root directory. You can also select a specific Windows SDK version by replacing '10.0' with the desired version number. Note that this line must be inserted before the first use of the 'project' command. + -# If you run CMake from the command line, you can pass the desired Windows SDK version with, e.g., '-D CMAKE_SYSTEM_VERSION=10.0.20348.0'. + -# Delete cache, reconfigure, and regenerate CMake. + @section faq1 After starting the MAiNGO executable nothing happens This is usually the case if some of the libraries (in most cases CPLEX) are not found. Make sure that after you installed CPLEX, the directory containing the CPLEX .dll files can be found in your @@ -1173,4 +1188,4 @@ Examples of MAiNGO applications with machine-learning models using the "MeLOn" t - A.M. Schweidtmann, W.R. Huster, J.T. Lüthje and A. Mitsos, Deterministic global process optimization: Accurate (single-species) properties via artificial neural networks, Computers & Chemical Engineering 121 (2019) 67-74. - W.R. Huster, A.M. Schweidtmann, J.T. Lüthje and A. Mitsos, Deterministic global superstructure-based optimization of an organic Rankine cycle, Computers & Chemical Engineering 141 (2020) 106996. - A.M. Schweidtmann, D. Bongartz, G. Grothe, T. Kerkenhoff, X. Lin, J. Najman, and A. Mitsos, Global optimization of Gaussian processes, Submitted. Preprint available at https://arxiv.org/abs/2005.10902 (2020). - */ \ No newline at end of file + */ diff --git a/examples/01_BasicExample/examplePythonInterface.py b/examples/01_BasicExample/examplePythonInterface.py index d4953a0..8b7eb6a 100644 --- a/examples/01_BasicExample/examplePythonInterface.py +++ b/examples/01_BasicExample/examplePythonInterface.py @@ -2,7 +2,7 @@ from maingopy import * from math import pi -# Auxiliary class, just to highlight we can use other stuff in our models +# Auxiliary class, just to highlight we can use other classes, functions, etc. in our models class SomeExternalClass(): def __init__(self, p1, p2): self.p1 = p1 @@ -24,13 +24,13 @@ class Model(MAiNGOmodel): self.ext = SomeExternalClass(0.2, pi) - # We need to implement the get_variables functions for specifying the optimization varibles + # We need to implement the get_variables functions for specifying the optimization variables def get_variables(self): # We need to return a list of OptimizationVariable objects. # To define an optimization variable, we typically need to specify bounds, and optionally a variable type, a branching priority, and a name # # Variable bounds: - # This is mostly self-explanatory. The bounds have to be doubles and not NaN. + # Every optimization variable (except for binary variables, cf. below) requires finite lower and upper bounds. # # Variable type: # There are three variable types in MAiNGO. VT_CONTINUOUS variables, VT_BINARY variables, and VT_INTEGER variables. @@ -68,7 +68,7 @@ class Model(MAiNGOmodel): x = vars[0] y = vars[1] - # Here, we can do (almost, see documentation) kind of intermediate calculation. + # Here, we can do (almost, see documentation) any kind of intermediate calculation. # Any variables defined here are intermediates that are not optimization variables. temp1 = self.ext.functionOne(x, y) temp2 = self.ext.functionTwo(x, y) @@ -86,18 +86,18 @@ class Model(MAiNGOmodel): result.eq = [x**2 + y**2 - 1] # Circle equality with radius 1 - # Relaxation only inequalities and equalities are used for lower bounding only. + # Relaxation-only inequalities and equalities are used for lower bounding only. # None of the relaxation only (in)equalities are passed to the upper bounding solver. - # Only for the feasible point found in pre-processing and the final solution point, - # MAiNGO checks whether they satisfy relaxation only (in)equalities and warns the user - # if they do not. + # Only for the best feasible point (if any) found during pre-processing and for the + # final solution point, MAiNGO checks whether they satisfy relaxation-only + # (in)equalities and warns the user if they do not. # IMPORTANT: You thus need to make sure yourself that any relaxation-only constraints # are redundant with the "regular" constraints. # - # Relaxation only inequalities (<=0): + # Relaxation-only inequalities (<=0): # result.ineqRelaxationOnly = [y - 1]; # - # Relaxation only equalities (=0): + # Relaxation-only equalities (=0): # result.eqRelaxationOnly = [y + x - 1] # Additional output can be used to access intermediate variables after a problem has been solved. diff --git a/examples/01_BasicExample/problem.h b/examples/01_BasicExample/problem.h index 1c72534..6bd5d54 100644 --- a/examples/01_BasicExample/problem.h +++ b/examples/01_BasicExample/problem.h @@ -66,7 +66,7 @@ Model::get_variables() variables.push_back(maingo::OptimizationVariable(/*Variable bounds*/ maingo::Bounds(0, 1), /*Variable type*/ maingo::VT_BINARY, /*Variable name*/ "x")); variables.push_back(maingo::OptimizationVariable(/*Variable bounds*/ maingo::Bounds(-2, 2), /*Variable type*/ maingo::VT_CONTINUOUS, /*Branching priority*/ 1, /*Variable name*/ "y")); /* Variable bounds: - * This is mostly self-explanatory. The bounds have to be doubles and not NaN. + * Every variable (except for binary variables) requires finite lower and upper bounds. */ /* Variable type: * There are three variable types in MAiNGO. VT_CONTINUOUS variables, VT_BINARY variables and VT_INTEGER variables. @@ -146,16 +146,16 @@ Model::evaluate(const std::vector<Var> &optVars) // Equalities (=0) given as the circle equality with radius 1: result.eq.push_back(pow(y, 2) + pow(x, 2) - 1, "circle equality"); - /* Relaxation only inequalities and equalities are used for lower bounding only. + /* Relaxation-only inequalities and equalities are used for lower bounding only. * None of relaxation only (in)equalities are passed to the upper bounding solver. - * For the feasible point found in pre-processing and the final solution point - * MAiNGO checks whether they satisfy relaxation only (in)equalities and warns the user - * if they do not + * Only for the best feasible point (if any) found in pre-processing and for the + * final solution point, MAiNGO checks whether they satisfy relaxation-only + * (in)equalities and warns the user if they do not */ - // Relaxation only inequalities (<=0): + // Relaxation-only inequalities (<=0): // result.ineqRelaxationOnly.push_back(y - 1,"y <= 1"); - // Relaxation only equalities (=0): + // Relaxation-only equalities (=0): // result.eqRelaxationOnly.push_back(y + x - 1,"y + x = 1"); // Additional output can be used to access intermediate factors after a problem has been solved. diff --git a/examples/01_BasicExample/problem.txt b/examples/01_BasicExample/problem.txt index f45dfc9..735f098 100644 --- a/examples/01_BasicExample/problem.txt +++ b/examples/01_BasicExample/problem.txt @@ -23,9 +23,9 @@ real a := 20; real p1 := 0.2; real p2 := 3.14159265358979323846; # ~ PI -# Reduced-space variables -real temp1 := -p1 * sqrt( (x^2 + y^2) / 2 ); # This is neither an optimization variable nor an equality constraint -real temp2 := (cos(p2*x) + cos(p2*y)) / 2; # This is neither an optimization variable nor an equality constraint +# Functions for reduced-space +real f1() := -p1 * sqrt( (x^2 + y^2) / 2 ); # This is a function without arguments +real f2(real m, real l) := (cos(p2*m) + cos(p2*l)) / 2; # This is a function # Initial point x.init <- 0; @@ -47,8 +47,9 @@ relaxation only constraints: outputs: # Additional output -temp1 "Result of temp1"; +f1() "Result of f1()"; +f2(x,y) "Result of f2(x,y)"; objective: # Always minimizing # Objective given as the Ackley function --a * exp(temp1) - exp(temp2) + a + exp(1); \ No newline at end of file +-a * exp(f1()) - exp(f2(x,y)) + a + exp(1); \ No newline at end of file diff --git a/examples/02_FlowsheetPowerCycle/problemCaseStudy2LCOE.txt b/examples/02_FlowsheetPowerCycle/problemCaseStudy2LCOE.txt index 95c4614..b604000 100644 --- a/examples/02_FlowsheetPowerCycle/problemCaseStudy2LCOE.txt +++ b/examples/02_FlowsheetPowerCycle/problemCaseStudy2LCOE.txt @@ -85,199 +85,199 @@ definitions: #Reduced-space model #General - real p0 := antoine_psat(T0,A,B,C); - real deltaS := deltaH/T0; - real mBleed := mdot*k; - real mMain := mdot*(1-k); + real p0() := antoine_psat(T0,A,B,C); + real deltaS() := deltaH/T0; + real mBleed() := mdot*k; + real mMain() := mdot*(1-k); #Turbines #Inlet - real p7 := p4; - real h7vap := deltaH + cpig*(antoine_tsat(p7,A,B,C)-T0); - real T7 := T0 + (h7-deltaH)/cpig; - real s7 := deltaS + log(T7/T0)*cpig - log(p7/p0)*Rm; + real p7() := p4; + real h7vap() := deltaH + cpig*(antoine_tsat(p7(),A,B,C)-T0); + real T7() := T0 + (h7-deltaH)/cpig; + real s7() := deltaS() + log(T7()/T0)*cpig - log(p7()/p0())*Rm; #Turbine 1 (bleed) - real p8 := p2; - real s8iso := s7; - real s8liq := log(antoine_tsat(p8,A,B,C)/T0)*cif; - real s8vap := log(antoine_tsat(p8,A,B,C)/T0)*cpig+deltaS-log(p8/p0)*Rm; - real x8iso := (s8iso-s8liq)/(s8vap-s8liq); - real h8liq := cif*(antoine_tsat(p8,A,B,C)-T0) + 1e2*vif*(p8-p0); - real h8vap := deltaH + cpig*(antoine_tsat(p8,A,B,C)-T0); - real h8iso := h8liq*(1-x8iso) + x8iso*h8vap; - real wtBleed := etaT*(h7-h8iso); - real WorkTurbBleed := mBleed*wtBleed; - real h8 := h7*(1-etaT)+etaT*h8iso; - real x8 := (h8-h8liq)/(h8vap-h8liq); - real T8 := antoine_tsat(p8,A,B,C); + real p8() := p2; + real s8iso() := s7(); + real s8liq() := log(antoine_tsat(p8(),A,B,C)/T0)*cif; + real s8vap() := log(antoine_tsat(p8(),A,B,C)/T0)*cpig+deltaS()-log(p8()/p0())*Rm; + real x8iso() := (s8iso()-s8liq())/(s8vap()-s8liq()); + real h8liq() := cif*(antoine_tsat(p8(),A,B,C)-T0) + 1e2*vif*(p8()-p0()); + real h8vap() := deltaH + cpig*(antoine_tsat(p8(),A,B,C)-T0); + real h8iso() := h8liq()*(1-x8iso()) + x8iso()*h8vap(); + real wtBleed() := etaT*(h7-h8iso()); + real WorkTurbBleed() := mBleed()*wtBleed(); + real h8() := h7*(1-etaT)+etaT*h8iso(); + real x8() := (h8()-h8liq())/(h8vap()-h8liq()); + real T8() := antoine_tsat(p8(),A,B,C); #Turbine 2 (main) - real p9 := p1; - real s9iso := s7; - real s9liq := log(antoine_tsat(p9,A,B,C)/T0)*cif; - real s9vap := log(antoine_tsat(p9,A,B,C)/T0)*cpig+deltaS-log(p9/p0)*Rm; - real x9iso := (s9iso-s9liq)/(s9vap-s9liq); - real h9liq := cif*(antoine_tsat(p9,A,B,C)-T0) + 1e2*vif*(p9-p0); - real h9vap := deltaH + cpig*(antoine_tsat(p9,A,B,C)-T0); - real h9iso := h9liq*(1-x9iso) + x9iso*h9vap; - real wtMain := etaT*(h7-h9iso); - real WorkTurbMain := mMain*wtMain; - real h9 := h7*(1.-etaT) + etaT*h9iso; - real T9 := antoine_tsat(p9,A,B,C); - real x9 := (h9-h9liq)/(h9vap-h9liq); + real p9() := p1; + real s9iso() := s7(); + real s9liq() := log(antoine_tsat(p9(),A,B,C)/T0)*cif; + real s9vap() := log(antoine_tsat(p9(),A,B,C)/T0)*cpig+deltaS()-log(p9()/p0())*Rm; + real x9iso() := (s9iso()-s9liq())/(s9vap()-s9liq()); + real h9liq() := cif*(antoine_tsat(p9(),A,B,C)-T0) + 1e2*vif*(p9()-p0()); + real h9vap() := deltaH + cpig*(antoine_tsat(p9(),A,B,C)-T0); + real h9iso() := h9liq()*(1-x9iso()) + x9iso()*h9vap(); + real wtMain() := etaT*(h7-h9iso()); + real WorkTurbMain() := mMain()*wtMain(); + real h9() := h7*(1.-etaT) + etaT*h9iso(); + real T9() := antoine_tsat(p9(),A,B,C); + real x9() := (h9()-h9liq())/(h9vap()-h9liq()); #Feedwater line #Condenser - real T1 := antoine_tsat(p1,A,B,C); - real h1 := cif*(antoine_tsat(p1,A,B,C)-T0) + 1e2*vif*(p1-p0); - real s1 := cif*log(antoine_tsat(p1,A,B,C)/T0); - real QCond := mMain*(h9-h1); + real T1() := antoine_tsat(p1,A,B,C); + real h1() := cif*(antoine_tsat(p1,A,B,C)-T0) + 1e2*vif*(p1-p0()); + real s1() := cif*log(antoine_tsat(p1,A,B,C)/T0); + real QCond() := mMain()*(h9()-h1()); #Condensate pump - real s2iso := s1; - real T2iso := T0*exp(s2iso/cif); - real h2iso := cif*(T2iso-T0) + 1e2*vif*(p2-p0); - real wpCond := (h2iso-h1)/etaP; - real WorkPumpCond := mMain*wpCond; - real h2 := h1*(1-1/etaP) + h2iso/etaP; - real T2 := T0 + (h2-1e2*vif*(p2-p0))/cif; + real s2iso() := s1(); + real T2iso() := T0*exp(s2iso()/cif); + real h2iso() := cif*(T2iso()-T0) + 1e2*vif*(p2-p0()); + real wpCond() := (h2iso()-h1())/etaP; + real WorkPumpCond() := mMain()*wpCond(); + real h2() := h1()*(1-1/etaP) + h2iso()/etaP; + real T2() := T0 + (h2()-1e2*vif*(p2-p0()))/cif; #Deaerator - real p3 := p2; - real h3liq := cif*(antoine_tsat(p3,A,B,C)-T0) + 1e2*vif*(p3-p0); - real h3 := h3liq; - real s3liq := cif*log(antoine_tsat(p3,A,B,C)/T0); - real s3 := s3liq; - real T3 := antoine_tsat(p3,A,B,C); + real p3() := p2; + real h3liq() := cif*(antoine_tsat(p3(),A,B,C)-T0) + 1e2*vif*(p3()-p0()); + real h3() := h3liq(); + real s3liq() := cif*log(antoine_tsat(p3(),A,B,C)/T0); + real s3() := s3liq(); + real T3() := antoine_tsat(p3(),A,B,C); #Feedwater pump - real s4iso := s3; - real T4iso := T0*exp(s4iso/cif); - real h4iso := cif*(T4iso-T0) + 1e2*vif*(p4-p0); - real wpFeed := (h4iso-h3)/etaP; - real WorkPumpFeed := mdot*wpFeed; - real h4 := h3*(1-1/etaP) + h4iso/etaP; - real h4liq := cif*(antoine_tsat(p4,A,B,C)-T0) + 1e2*vif*(p4-p0); - real T4 := T0 + (h4-1e2*vif*(p4-p0))/cif; + real s4iso() := s3(); + real T4iso() := T0*exp(s4iso()/cif); + real h4iso() := cif*(T4iso()-T0) + 1e2*vif*(p4-p0()); + real wpFeed() := (h4iso()-h3())/etaP; + real WorkPumpFeed() := mdot*wpFeed(); + real h4() := h3()*(1-1/etaP) + h4iso()/etaP; + real h4liq() := cif*(antoine_tsat(p4,A,B,C)-T0) + 1e2*vif*(p4-p0()); + real T4() := T0 + (h4()-1e2*vif*(p4-p0()))/cif; #Boiler #Superheater - real p6 := p4; - real T6 := antoine_tsat(p6,A,B,C); - real h6 := deltaH + cpig*(antoine_tsat(p6,A,B,C)-T0); - real QSH := mdot*(h7-h6); - real TG2 := TGin - QSH/mcpG; + real p6() := p4; + real T6() := antoine_tsat(p6(),A,B,C); + real h6() := deltaH + cpig*(antoine_tsat(p6(),A,B,C)-T0); + real QSH() := mdot*(h7-h6()); + real TG2() := TGin - QSH()/mcpG; #Evaporator - real p5 := p4; - real T5 := antoine_tsat(p5,A,B,C) - dTap; - real h5 := cif*(T5-T0) + 1e2*vif*(p5-p0); - real QEvap := mdot*(h6-h5); - real TG3 := TGin - mdot*(h7-h5)/mcpG; + real p5() := p4; + real T5() := antoine_tsat(p5(),A,B,C) - dTap; + real h5() := cif*(T5()-T0) + 1e2*vif*(p5()-p0()); + real QEvap() := mdot*(h6()-h5()); + real TG3() := TGin - mdot*(h7-h5())/mcpG; #Eco - real QEco := mdot*(h5-h4); - real TGout := TGin - mdot*(h7-h4)/mcpG; + real QEco() := mdot*(h5()-h4()); + real TGout() := TGin - mdot*(h7-h4())/mcpG; #Overall - real WorkNet := mBleed*(wtBleed-wpFeed) + mMain*(wtMain-wpCond-wpFeed); - real Qzu := mdot*(h7-h4); - real eta := WorkNet/Qzu; + real WorkNet() := mBleed()*(wtBleed()-wpFeed()) + mMain()*(wtMain()-wpCond()-wpFeed()); + real Qzu() := mdot*(h7-h4()); + real eta() := WorkNet()/Qzu(); #Combined Cycle Power Plant - real WorkTotal := max(WorkNet + WorkGT,WorkGT); - real etaCC := WorkTotal / FuelHeat; + real WorkTotal() := max(WorkNet() + WorkGT,WorkGT); + real etaCC() := WorkTotal() / FuelHeat; #Investment cost #Condenser - real dT1cond := T9-Tcout; - real dT2cond := T1-Tcin; - real rLMTDcond := rlmtd(dT1cond,dT2cond); - real ACond := rLMTDcond*QCond/kCond; - real Cp0cond := cost_turton(max(Amin,ACond),k1A,k2A,k3A); - real FpCond := 1.0; - real InvCond := 1.18 * (B1A+B2A*FmA*FpCond) * Cp0cond; + real dT1cond() := T9()-Tcout; + real dT2cond() := T1()-Tcin; + real rLMTDcond() := rlmtd(dT1cond(),dT2cond()); + real ACond() := rLMTDcond()*QCond()/kCond; + real Cp0cond() := cost_turton(max(Amin,ACond()),k1A,k2A,k3A); + real FpCond() := 1.0; + real InvCond() := 1.18 * (B1A+B2A*FmA*FpCond()) * Cp0cond(); #Pumps - real InvPumpCond := 3540.*pow(max(WorkPumpCond,1e-3),0.71); - real InvPumpFeed := 3540.*pow(max(WorkPumpFeed,1e-3),0.71); + real InvPumpCond() := 3540.*pow(max(WorkPumpCond(),1e-3),0.71); + real InvPumpFeed() := 3540.*pow(max(WorkPumpFeed(),1e-3),0.71); #Turbines incl. generator - real InvTurb := 6000.*pow(max(WorkTurbBleed+WorkTurbMain,1e-3),0.7) + 60.*pow(max(WorkTurbBleed+WorkTurbMain,1e-3),0.95); + real InvTurb() := 6000.*pow(max(WorkTurbBleed()+WorkTurbMain(),1e-3),0.7) + 60.*pow(max(WorkTurbBleed()+WorkTurbMain(),1e-3),0.95); #Economizer - real dT1eco := max(dTmin,TGout - T4); - real dT2eco := max(dTmin,TG3 - T5); - real rLMTDeco := rlmtd(dT1eco,dT2eco); - real AEco := rLMTDeco*QEco / kEco; - real Cp0Eco := cost_turton(max(Amin,AEco),k1A,k2A,k3A); - real Fp := cost_turton(p4,c1A,c2A,c3A); - real InvEco := 1.18 * (B1A+B2A*FmA*Fp) * Cp0Eco; + real dT1eco() := max(dTmin,TGout() - T4()); + real dT2eco() := max(dTmin,TG3() - T5()); + real rLMTDeco() := rlmtd(dT1eco(),dT2eco()); + real AEco() := rLMTDeco()*QEco() / kEco; + real Cp0Eco() := cost_turton(max(Amin,AEco()),k1A,k2A,k3A); + real Fp() := cost_turton(p4,c1A,c2A,c3A); + real InvEco() := 1.18 * (B1A+B2A*FmA*Fp()) * Cp0Eco(); #Evaporator - real dT1evap := max(dTmin,TG3 - T6); - real dT2evap := max(dTmin,TG2 - T6); - real rLMTDevap := rlmtd(dT1evap,dT2evap); - real AEvap := rLMTDevap*QEvap / kEvap; - real Cp0evap := cost_turton(max(Amin,AEvap),k1A,k2A,k3A); - real InvEvap := 1.18 * (B1A+B2A*FmA*Fp) * Cp0evap; + real dT1evap() := max(dTmin,TG3() - T6()); + real dT2evap() := max(dTmin,TG2() - T6()); + real rLMTDevap() := rlmtd(dT1evap(),dT2evap()); + real AEvap() := rLMTDevap()*QEvap() / kEvap; + real Cp0evap() := cost_turton(max(Amin,AEvap()),k1A,k2A,k3A); + real InvEvap() := 1.18 * (B1A+B2A*FmA*Fp()) * Cp0evap(); #Superheater - real dT1SH := max(dTmin,TG2 - T6); - real dT2SH := max(dTmin,TGin - T7); - real rLMTDSH := rlmtd(dT1SH,dT2SH); - real ASH := rLMTDSH*QSH / kSH; - real Cp0SH := cost_turton(max(Amin,ASH),k1A,k2A,k3A); - real InvSH := 1.18 * (B1A+B2A*FmA*Fp) * Cp0SH; + real dT1SH() := max(dTmin,TG2() - T6()); + real dT2SH() := max(dTmin,TGin - T7()); + real rLMTDSH() := rlmtd(dT1SH(),dT2SH()); + real ASH() := rLMTDSH()*QSH() / kSH; + real Cp0SH() := cost_turton(max(Amin,ASH()),k1A,k2A,k3A); + real InvSH() := 1.18 * (B1A+B2A*FmA*Fp()) * Cp0SH(); #Deaerator - real VDae := 1.5 * 600 * mdot * vif; - real Cp0DAE := cost_turton(VDae,k1B,k2B,k3B); - real FpDAE := 1.25; - real InvDae := 1.18 * (B1B+B2B*FmB*FpDAE) * Cp0DAE; + real VDae() := 1.5 * 600 * mdot * vif; + real Cp0DAE() := cost_turton(VDae(),k1B,k2B,k3B); + real FpDAE() := 1.25; + real InvDae() := 1.18 * (B1B+B2B*FmB*FpDAE()) * Cp0DAE(); #Cycle - real Inv := InvCond + InvPumpCond + InvPumpFeed + InvEco + InvEvap + InvSH + InvTurb + InvDae; + real Inv() := InvCond() + InvPumpCond() + InvPumpFeed() + InvEco() + InvEvap() + InvSH() + InvTurb() + InvDae(); #Combined Cycle Plant - real InvTotal := Inv + InvGT; - real LCOE := (InvTotal*fPhi*fAnnu*1000/Teq+FuelPrice*FuelHeat)/WorkTotal + VarCost; + real InvTotal() := Inv() + InvGT; + real LCOE() := (InvTotal()*fPhi*fAnnu*1000/Teq+FuelPrice*FuelHeat)/WorkTotal() + VarCost; constraints: #Inequalities - (h7vap-h7)*0.001 <= 0 "hvap(p7)<=h7"; - (T7-Tmax)*0.01 <= 0 "T7<=Tmax"; - (s8liq-s8iso)*1 <= 0 "sliq(p8)<=s8iso"; - (s8iso-s8vap)*1 <= 0 "s8iso<=svap(p8)"; - (h8liq-h8)*0.01 <= 0 "hliq(p8)<=h8"; - (h8-h8vap)*0.001 <= 0 "h8<=hvap(p8)"; - (s9liq-s9iso)*1 <= 0 "sliq(p9)<=s9iso"; - (s9iso-s9vap)*1 <= 0 "s9iso<=svap(p9)"; - (h9liq-h9)*0.01 <= 0 "hliq(p9)<=h8"; - (h9-h9vap)*0.001 <= 0 "h9<=hvap(p9)"; - (xmin*h9vap-h9+h9liq*(1-xmin))*0.1 <= 0 "xmin<=x9"; - (p2-p4)*1 <= 0 "p2<=p4"; - (h4-h4liq)*0.01 <= 0 "h4<=hliq(p4)"; - (dTmin-(TG3-T6))*0.1 <= 0 "Pinch: Evaporator, cold end"; - (dTmin-(TGout-T4))*0.1 <= 0 "Pinch: Economizer, cold end"; - (WorkGT - WorkTotal)*1e-5 <= 0 "WorkGT<=WorkTotal"; - (Amin-ACond)*0.1 <= 0 "Amin<=ACond"; - (Amin-AEco)*0.1 <= 0 "Amin<=AEco"; - (Amin-AEvap)*0.1 <= 0 "Amin<=AEvap"; - (Amin-ASH)*0.1 <= 0 "Amin<=ASH"; - (Vmin-VDae)*1 <= 0 "Vmin<=VDae"; - (1e-3-WorkPumpCond)*1e-3 <= 0 "0.001<=WorkPumpCond"; - (1e-3-WorkPumpFeed)*1e-3 <= 0 "0.001<=WorkPumpFeed"; - (1e-3-WorkTurbBleed)*1e-5 <= 0 "0.001<=WorkTurbBleed"; - (1e-3-WorkTurbMain)*1e-5 <= 0 "0.001<=WorkTurbMain"; + (h7vap()-h7)*0.001 <= 0 "hvap(p7())<=h7"; + (T7()-Tmax)*0.01 <= 0 "T7()<=Tmax"; + (s8liq()-s8iso())*1 <= 0 "sliq(p8())<=s8iso()"; + (s8iso()-s8vap())*1 <= 0 "s8iso()<=svap(p8())"; + (h8liq()-h8())*0.01 <= 0 "hliq(p8())<=h8()"; + (h8()-h8vap())*0.001 <= 0 "h8()<=hvap(p8())"; + (s9liq()-s9iso())*1 <= 0 "sliq(p9())<=s9iso()"; + (s9iso()-s9vap())*1 <= 0 "s9iso()<=svap(p9())"; + (h9liq()-h9())*0.01 <= 0 "hliq(p9())<=h8()"; + (h9()-h9vap())*0.001 <= 0 "h9()<=hvap(p9())"; + (xmin*h9vap()-h9()+h9liq()*(1-xmin))*0.1 <= 0 "xmin<=x9()"; + (p2-p4)*1 <= 0 "p2<=p4"; + (h4()-h4liq())*0.01 <= 0 "h4()<=hliq(p4)"; + (dTmin-(TG3()-T6()))*0.1 <= 0 "Pinch: Evaporator, cold end"; + (dTmin-(TGout()-T4()))*0.1 <= 0 "Pinch: Economizer, cold end"; + (WorkGT - WorkTotal())*1e-5 <= 0 "WorkGT<=WorkTotal()"; + (Amin-ACond())*0.1 <= 0 "Amin<=ACond()"; + (Amin-AEco())*0.1 <= 0 "Amin<=AEco()"; + (Amin-AEvap())*0.1 <= 0 "Amin<=AEvap()"; + (Amin-ASH())*0.1 <= 0 "Amin<=ASH()"; + (Vmin-VDae())*1 <= 0 "Vmin<=VDae()"; + (1e-3-WorkPumpCond())*1e-3 <= 0 "0.001<=WorkPumpCond()"; + (1e-3-WorkPumpFeed())*1e-3 <= 0 "0.001<=WorkPumpFeed()"; + (1e-3-WorkTurbBleed())*1e-5 <= 0 "0.001<=WorkTurbBleed()"; + (1e-3-WorkTurbMain())*1e-5 <= 0 "0.001<=WorkTurbMain()"; #Equalities - (mBleed*(h3-h8)+mMain*(h3-h2))*0.001 = 0 "Deaerator energy balance"; - - + (mBleed()*(h3()-h8())+mMain()*(h3()-h2()))*0.001 = 0 "Deaerator energy balance"; + + objective: #Objective function - LCOE; - - + LCOE(); + + outputs: #Additional outputs - T1-273.15 "T1 [C]"; - T2-273.15 "T2 [C]"; - T3-273.15 "T3 [C]"; - T4-273.15 "T4 [C]"; - T5-273.15 "T5 [C]"; - T6-273.15 "T6 [C]"; - T7-273.15 "T7 [C]"; - T8-273.15 "T8 [C]"; - T9-273.15 "T9 [C]"; - x8 "x8"; - x9 "x9"; + T1()-273.15 "T1() [C]"; + T2()-273.15 "T2() [C]"; + T3()-273.15 "T3() [C]"; + T4()-273.15 "T4() [C]"; + T5()-273.15 "T5() [C]"; + T6()-273.15 "T6() [C]"; + T7()-273.15 "T7() [C]"; + T8()-273.15 "T8() [C]"; + T9()-273.15 "T9() [C]"; + x8() "x8()"; + x9() "x9()"; TGin-273.15 "TG1 [C]"; - TG2-273.15 "TG2 [C]"; - TG3-273.15 "TG3 [C]"; - TGout-273.15 "TG4 [C]"; - WorkNet "Work Steam Cycle [kW]"; - eta "eta [-]"; - etaCC "etaCC [-]"; \ No newline at end of file + TG2()-273.15 "TG2() [C]"; + TG3()-273.15 "TG3() [C]"; + TGout()-273.15 "TG4 [C]"; + WorkNet() "Work Steam Cycle [kW]"; + eta() "eta() [-]"; + etaCC() "etaCC() [-]"; \ No newline at end of file diff --git a/examples/06_BayesianOptimization/bayesianOptimizationReducedSpace.py b/examples/06_BayesianOptimization/bayesianOptimizationReducedSpace.py index c36fe69..2eb665c 100644 --- a/examples/06_BayesianOptimization/bayesianOptimizationReducedSpace.py +++ b/examples/06_BayesianOptimization/bayesianOptimizationReducedSpace.py @@ -4,7 +4,7 @@ from math import pi ##################################################### -# To define a model, we need to spcecialize the MAiNGOmodel class +# To define a model, we need to specialize the MAiNGOmodel class class Model(MAiNGOmodel): def __init__(self): MAiNGOmodel.__init__(self) diff --git a/examples/07_GrowingDatasets/Readme.md b/examples/07_GrowingDatasets/Readme.md new file mode 100644 index 0000000..2ed3380 --- /dev/null +++ b/examples/07_GrowingDatasets/Readme.md @@ -0,0 +1,29 @@ +# Example Problem Number 07 - Simple Problem with Growing Datasets + +## About + +This problem demonstrates the solution of a parameter estimation problem using growing datasets. +The problem to be solved is optimizing the slope of a linear function through the origin such that it fits the three data points (1,1), (1,0.6), and (1,0) best + + min [(slope*1-1)^2 + (slope*1-0.6)^2 + (slope*1-0)^2] + s.t. slope in [0,25] + +To avoid MAiNGO passing this problem as a QP to CPLEX, we use sqroot(slope) as optimization variable and obtain + + min [(x^2-1)^2 + (x^2-0.6)^2 + (x^2-0)^2] + s.t. x in [0,5] + +To solve the problem, you need to +* set compiler flag MAiNGO_use_growing_datasets to true +* include the file `problem_growingDatasets_simple.h` in your main file (e.g., `mainCppApi.cpp`) +* use an augmentation rule different from SCALING. + +Note that augmentation rule SCALING can not trigger augmentation and, thus, does not lead to convergence for this model due to conflicting data points. In particular, one data point can be fitted perfectly, while there is a deviation between predictions and data when using at least two data points. +For more details refer to Example 1 of Sass et al. (Submitted in 2023) + +The optimal slope for fitting all data points is slope = 0.533, i.e., x = 0.730. + +## References + +Sass, S., Mitsos, A., Bongartz, D., Bell, I. H., Nikolov, N., & Tsoukalas, A. (2023). A branch-and-bound algorithm with growing datasets for large-scale parameter estimation. *Submitted* +<!-- Sass, S., Mitsos, A., Bongartz, D., Bell, I. H., Nikolov, N., & Tsoukalas, A. (2023). [A branch-and-bound algorithm with growing datasets for large-scale parameter estimation](url to paper). *Journal* volume, firstPage–lastPage --> \ No newline at end of file diff --git a/examples/07_GrowingDatasets/growingDatasets_simple.py b/examples/07_GrowingDatasets/growingDatasets_simple.py new file mode 100644 index 0000000..15a2711 --- /dev/null +++ b/examples/07_GrowingDatasets/growingDatasets_simple.py @@ -0,0 +1,40 @@ +from maingopy import * + + +##################################################### +# Define a model +class ModelSimpleGrowing(MAiNGOmodel): + def __init__(self): + MAiNGOmodel.__init__(self) + + def get_variables(self): + variables = [OptimizationVariable(Bounds(0,5),VT_CONTINUOUS,"x")] + return variables + + def evaluate(self, vars): + + x= vars[0] + inputValues = [1,1,1] + outputValues = [1,0.6,0] + result = EvaluationContainer() + se = 0 + for i in range(3): + predictedValue = sqr(x)*inputValues[i] + se_per_data = sqr(predictedValue - outputValues[i]) + se = se + se_per_data + result.objData.append(se_per_data) + result.obj = se + result.output = [OutputVariable("Optimal slope: ", sqr(x))] + return result + + +##################################################### +# Work with the model +myModel = ModelSimpleGrowing() +myMAiNGO = MAiNGO(myModel) + +myMAiNGO.set_option("growing_augmentRule", AUG_RULE_CONST) +myMAiNGO.set_option("growing_augmentFreq", 1) + +maingoStatus = myMAiNGO.solve() +# print(maingoStatus) \ No newline at end of file diff --git a/examples/07_GrowingDatasets/problem_growingDatasets_simple.h b/examples/07_GrowingDatasets/problem_growingDatasets_simple.h new file mode 100644 index 0000000..79227a8 --- /dev/null +++ b/examples/07_GrowingDatasets/problem_growingDatasets_simple.h @@ -0,0 +1,126 @@ +/********************************************************************************** + * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + +#include "MAiNGOmodel.h" + +namespace data { +// Data pairs: inputValues and outputValues must have the same length +const std::vector<double> inputValues = { + 1.0, + 1.0, + 1.0}; +const std::vector<double> outputValues = { + 1.0, + 0.6, + 0.0}; +} // end namespace data + +/** +* @class Model +* @brief Simple test problem for MAiNGO with growing datasets +* +* This class defines a parameter estimation problem for optimizing the slope of a linear function +* output = slope * input through the origin and up to three data points, namely (1,1), (1,0.6), and (1,0). +* When using all data points, we expect optimal slope = 0.53 +* as we optimize min_slope [(slope*1-1)^2 + (slope*1-0.6)^2 + (slope*1-0)^2]. +* When using a single data point, we expect optimal slope = y value of the data point, and objective = 0. +* When using (1,0) and (1,0.6), we expect optimal slope = 0.3. +* When using (1,0) and (1,1.0), we expect optimal slope = 0.5. +* When using (1,0.6) and (1,1), we expect optimal slope = 0.8. +* To avoid MAiNGO passing this problem as a QP to CPLEX, we use sqroot(slope) as optimization variable. +* +* Note that augmentation rule SCALING can not trigger augmentation and, thus, does not give convergence +* for this model due to overfitting. In particular, 1 data point can be fitted perfectly, while there is +* a deviation between predictions and data when using at least 2 data points. +* +*/ +class Model: public maingo::MAiNGOmodel { + + public: + Model(); + + maingo::EvaluationContainer evaluate(const std::vector<Var> &optVars); + std::vector<maingo::OptimizationVariable> get_variables(); + std::vector<double> get_initial_point(); + + private: + size_t _noOfDataPoints; +}; + + +////////////////////////////////////////////////////////////////////////// +// function for providing optimization variable data to the Branch-and-Bound solver +std::vector<maingo::OptimizationVariable> +Model::get_variables() +{ + + std::vector<maingo::OptimizationVariable> variables; + + variables.push_back(maingo::OptimizationVariable( maingo::Bounds(0., 5.), maingo::VT_CONTINUOUS, "sqrt(slope)")); + + return variables; +} + + +////////////////////////////////////////////////////////////////////////// +// function for providing initial point data to the Branch-and-Bound solver +std::vector<double> +Model::get_initial_point() +{ + + std::vector<double> initialPoint; + + return initialPoint; +} + + +////////////////////////////////////////////////////////////////////////// +// constructor for the model +Model::Model(){ + + _noOfDataPoints = data::inputValues.size(); +} + + +////////////////////////////////////////////////////////////////////////// +// evaluate the model +maingo::EvaluationContainer +Model::evaluate(const std::vector<Var> &optVars) +{ + + // Rename inputs + Var sqrt_slope = optVars.at(0); + + // Model prediction of linear function + std::vector<Var> predictedValues; + for (auto inputValue: data::inputValues){ + predictedValues.push_back(sqr(sqrt_slope)*inputValue); + } + + // Prepare output + maingo::EvaluationContainer result; + + // Objective given as the summed squared error: + Var se = 0; + Var se_per_data = 0; + for (auto i = 0; i < _noOfDataPoints; i++){ + se_per_data = sqr(predictedValues[i] - data::outputValues[i]); + se += se_per_data; + result.objective_per_data.push_back(se_per_data); + } + + result.objective = se; + result.output.push_back(maingo::OutputVariable("slope", sqr(sqrt_slope))); + + return result; +} \ No newline at end of file diff --git a/examples/07_GrowingDatasets/problem_growingDatasets_simple.txt b/examples/07_GrowingDatasets/problem_growingDatasets_simple.txt new file mode 100644 index 0000000..1c35e97 --- /dev/null +++ b/examples/07_GrowingDatasets/problem_growingDatasets_simple.txt @@ -0,0 +1,43 @@ +# ********************************************************************************** +# * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University +# * +# * This program and the accompanying materials are made available under the +# * terms of the Eclipse Public License 2.0 which is available at +# * http://www.eclipse.org/legal/epl-2.0. +# * +# * SPDX-License-Identifier: EPL-2.0 +# * +# * @file problem_growingDatasets_simple.txt +# * +# * @brief File containing an exemplary optimization problem for MAiNGO with growing datasets in ALE syntax +# * +# ********************************************************************************** + +definitions: +# Optimization variables +real sqrt_slope in [0,5]; + +# Full dataset +index ndata := 3; +real[ndata] inputData := (1.0, 1.0, 1.0); +real[ndata] outputData := (1.0, 0.6, 0.0); + +# Model functions +real prediction(real input, real optvar) := optvar^2*input; + +# Additional variables +real slope() := sqrt_slope^2; + +outputs: +# Additional output +slope() "Final slope"; + +objectivePerData: # Always minimizing +# Objective per data for all data points +forall i in {1 .. ndata} : + (outputData[i] - prediction(inputData[i],sqrt_slope))^2 = 0; + +objective: # Always minimizing +# This objective will only be used when using a fixed dataset (MAiNGO_use_growing_datasets = false) +# It will be overwritten with objectivePerData defined above when using growing datasets +sum(i in {1 .. ndata} : (outputData[i] - prediction(inputData[i],sqrt_slope))^2); \ No newline at end of file diff --git a/examples/MAiNGOSettings.txt b/examples/MAiNGOSettings.txt index b616af4..21c217d 100644 --- a/examples/MAiNGOSettings.txt +++ b/examples/MAiNGOSettings.txt @@ -69,6 +69,9 @@ #range reduction in every node, has to be 0 for FALSE and 1 for TRUE (default: 1) #BAB_alwaysSolveObbt 1 +#coefficient to determine how fast the probability of doing OBBT decays with increasing depth of a given node in the B&B tree, has to be >=0 (default: 0) +#BAB_obbtDecayCoefficient 0 + #do duality based bound tightening rounds, has to be 0 for FALSE and 1 for TRUE (default: 1) #BAB_dbbt 1 @@ -202,6 +205,31 @@ #modelWritingLanguage 0 +#------------------------------------------------------------------------------------------------------------------------------- +#---------------Settings for extension "B&B alorithm with growing datasets for large-scale parameter estimation"---------------- + +#relative tolerance for considering dataset as the full dataset (default: 0.9) +#growing_dataSizeTol 0.9 + +#size of the smallest reduced dataset is x times size of full dataset (default: 0.1) +#at least one data point will be used +#growing_dataSizeInit 0.1 + +#rule when to augment the dataset: AUG_RULE_CONST = 0, AUG_RULE_SCALING = 1, AUG_RULE_SCALCST = 2 (default: 2) +#growing_augmentRule 2 + +#augment dataset in nodes whose depth is a multiple of this freq (default: 10) +#this setting will only be used when AUG_RULE_CONST or AUG_RULE_SCALCST has been chosen +#growing_augmentFreq 10 + +#weighting factor of heuristic lower bound, has to be > 0 and <= 1 (default: 1) +#this setting will only be used when AUG_RULE_SCALING or AUG_RULE_SCALCST has been chosen +#growing_augmentWeight 1 + +#size of new dataset to be added when augmenting relative to number of points of the full dataset (default: 0.25) +#growing_augmentPercentage 0.25 + + #------------------------------------------------------------------------------------------------------------------------------- # ASCII MAiNGO diff --git a/examples/mainCppApi.cpp b/examples/mainCppApi.cpp index a2c94e1..294e468 100644 --- a/examples/mainCppApi.cpp +++ b/examples/mainCppApi.cpp @@ -33,6 +33,11 @@ // #include "06_BayesianOptimization/problemBayesianOptimizationReducedSpace.h" // #include "06_BayesianOptimization/problemBayesianOptimizationFullspace.h" +/* +* The following example requires that the CMake flag MAiNGO_use_growing_datasets is set to true. +*/ +// #include "07_GrowingDatasets/problem_growingDatasets_simple.h" + #include <memory> @@ -116,7 +121,7 @@ main(int argc, char *argv[]) MAiNGO_IF_BAB_MANAGER // myMAiNGO->write_model_to_file_in_other_language(maingo::WRITING_LANGUAGE::LANG_ALE,"my_problem_file_MAiNGO.txt","dummySolverName(onlyUsedWhenWritingGAMS)",/*useMinMax*/true,/*useTrig*/true,/*ignoreBoundingFuncs*/true,/*useRelOnly*/false); MAiNGO_END_IF - maingoStatus = myMAiNGO->solve(); + maingoStatus = myMAiNGO->solve(); // Use this function instead of solve() for solving bi-objective problems using the epsilon-constraint method (don't forget to include the example problem in problemEpsCon.h): // maingoStatus = myMAiNGO->solve_epsilon_constraint(); } @@ -138,4 +143,4 @@ main(int argc, char *argv[]) } MAiNGO_MPI_FINALIZE return 0; -} \ No newline at end of file +} diff --git a/inc/MAiNGO.h b/inc/MAiNGO.h index c657f79..7c9ec18 100644 --- a/inc/MAiNGO.h +++ b/inc/MAiNGO.h @@ -226,6 +226,13 @@ class MAiNGO { */ double get_final_rel_gap() const; + /** + * @brief Function returning a desired setting value + * + * @param[in] option is the option name + */ + double get_option(const std::string &option) const; + /** * @brief Funcition returning whether MAiNGO solved the problem or not */ @@ -311,17 +318,17 @@ class MAiNGO { * @brief Construct DAG */ void _construct_DAG(); - + /** * @brief Print information about the major pieces of third-party software used when solving an (MI)NLP */ void _print_third_party_software_minlp(); - + /** * @brief Print information about the major pieces of third-party software used when solving an (MI)QP/(MI)LP */ void _print_third_party_software_miqp(); - + /** * @brief Evaluate model at initial point and print the values of the variables, objective, constraint residuals and outputs */ @@ -345,6 +352,15 @@ class MAiNGO { */ void _ensure_valid_objective_function_using_dummy_variable(const mc::FFVar &dummyVariable); +#ifdef HAVE_GROWING_DATASETS + /** + * @brief Ensures that constant objective_per_data is not removed + * + * @param[in] dummyVariable is a valid optimization variable that is used to ensure that a potential constant objective is associated to the correct DAG + */ + void _ensure_valid_objective_per_data_function_using_dummy_variable(const mc::FFVar &dummyVariable); +#endif // HAVE_GROWING_DATASETS + /** * @brief Checks if the constraints are non-zero (constant) after the DAG has been constructed (this may happen if some FFVars are equal). * Fills tmpDAGFunctions and tmpDAGoutputFunctions. @@ -355,6 +371,19 @@ class MAiNGO { */ bool _check_for_hidden_zero_constraints(const std::vector<mc::FFVar> &tmpDAGVars, std::vector<mc::FFVar> &tmpDAGFunctions, std::vector<mc::FFVar> &tmpDAGoutputFunctions); +#ifdef HAVE_GROWING_DATASETS + /** + * @brief Initializes objective from objective_per_data and saves the DAG function of each entry of objective_per_data + */ + void _initialize_objective_from_objective_per_data(); + + /** + * @brief Initializes full dataset (= largest set) and initial reduced dataset (= smallest set) + */ + void _initialize_dataset(); + +#endif + /** * @brief Modifies the lower bound DAG _DAGlbd by adding auxiliary optimization variables for intermediate factors occuring multiple times. */ @@ -611,6 +640,7 @@ class MAiNGO { unsigned _nvarOriginalBinary; /*!< number of original user-defined binary optimization variables */ unsigned _nvarOriginalInteger; /*!< number of original user-defined integer optimization variables */ unsigned _nvar; /*!< number of not-removed optimization variables participating in the problem */ + unsigned _nobj; /*!< number of objective functions */ unsigned _nineq; /*!< number of non-constant inequalities */ unsigned _neq; /*!< number of non-constant equalities */ unsigned _nineqRelaxationOnly; /*!< number of non-constant relaxation only inequalities */ @@ -622,6 +652,7 @@ class MAiNGO { unsigned _nconstantIneqRelOnly; /*!< number of constant relaxation only inequalities */ unsigned _nconstantEqRelOnly; /*!< number of constant relaxation only equalities */ unsigned _nconstantIneqSquash; /*!< number of constant inequalities used when the squash_node function is applied in the model */ + unsigned _ndata; /*!< number of data points defined by objective_per_data (only larger 0 when using growing datasets) */ unsigned _nconstantOutputVariables; /*!< number of constant output variables */ std::vector<std::string> _outputNames; /*!< strings for output variables */ std::shared_ptr<MAiNGOmodel> _myFFVARmodel; /*!< pointer to a MAiNGOmodel object which will be evaluated with mc::FFVar variables */ @@ -637,6 +668,9 @@ class MAiNGO { std::shared_ptr<std::vector<Constraint>> _nonconstantConstraintsUBP; /*!< vector holding all non-constant constraints for the UBS solver. This vector has only obj, ineq, squash ineq and eq (in this order) and is passed to the UBD wrappers. */ std::shared_ptr<std::vector<Constraint>> _constantOutputs; /*!< vector holding all constant outputs */ std::shared_ptr<std::vector<Constraint>> _nonconstantOutputs; /*!< vector holding all non-constant outputs */ +#ifdef HAVE_GROWING_DATASETS + std::shared_ptr<std::vector<std::set<unsigned int>>> _datasets; /*!< pointer to a vector containing all available datasets. Note: first data point has index 0 */ +#endif // HAVE_GROWING_MAiNGO /**@}*/ /** @@ -656,7 +690,7 @@ class MAiNGO { * @name Auxiliaries variables for storing information on the optimization */ /**@{*/ - std::vector<double> _solutionPoint; /*!< vector holding the the solution point */ + std::vector<double> _solutionPoint; /*!< vector holding the solution point */ double _solutionValue; /*!< double holding the solution value */ double _solutionTime; /*!< double holding the solution time in CPU s */ double _preprocessTime; /*!< double holding the solution time in CPU s for pre-processing only */ @@ -677,7 +711,10 @@ class MAiNGO { * @name LowerBoundingSolver, UpperBoundingSolver and the B&B solver */ /**@{*/ - std::shared_ptr<lbp::LowerBoundingSolver> _myLBS; /*!< pointer to lower bounding solver */ + std::shared_ptr<lbp::LowerBoundingSolver> _myLBS; /*!< pointer to lower bounding solver */ +#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) + std::shared_ptr<lbp::LowerBoundingSolver> _myLBSFull; /*!< pointer to lower bounding solver using full dataset only */ +#endif std::shared_ptr<ubp::UpperBoundingSolver> _myUBSPre; /*!< pointer to upper bounding solver to be used during pre-processing */ std::shared_ptr<ubp::UpperBoundingSolver> _myUBSBab; /*!< pointer to upper bounding solver to be used in B&B*/ std::shared_ptr<bab::BranchAndBound> _myBaB; /*!< pointer to B&B solver */ @@ -694,10 +731,10 @@ class MAiNGO { * @name Output */ /**@{*/ - std::shared_ptr<Logger> _logger = std::make_shared<Logger>(); /*!< object taking care of printing and saving information to logs */ - std::string _jsonFileName = "statisticsAndSolution.json"; /*!< name of the json file into which information about the problem and solution may be written */ - std::string _resultFileName = "MAiNGOresult.txt"; /*!< name of the text file into which the results (solution point, constraints residuals etc.) may be written */ - std::string _csvSolutionStatisticsName = "statisticsAndSolution.csv"; /*!< name of the csv file into which the solution as well as statistics may be written */ + std::shared_ptr<Logger> _logger = std::make_shared<Logger>(_maingoSettings); /*!< object taking care of printing and saving information to logs */ + std::string _jsonFileName = "statisticsAndSolution.json"; /*!< name of the json file into which information about the problem and solution may be written */ + std::string _resultFileName = "MAiNGOresult.txt"; /*!< name of the text file into which the results (solution point, constraints residuals etc.) may be written */ + std::string _csvSolutionStatisticsName = "statisticsAndSolution.csv"; /*!< name of the csv file into which the solution as well as statistics may be written */ /**@}*/ /** diff --git a/inc/MAiNGOException.h b/inc/MAiNGOException.h index 61286c1..34d8e86 100644 --- a/inc/MAiNGOException.h +++ b/inc/MAiNGOException.h @@ -35,12 +35,12 @@ namespace maingo { class MAiNGOException: public std::exception { public: - MAiNGOException() = delete; - MAiNGOException(const MAiNGOException&) = default; - MAiNGOException(MAiNGOException&&) = default; + MAiNGOException() = delete; + MAiNGOException(const MAiNGOException&) = default; + MAiNGOException(MAiNGOException&&) = default; MAiNGOException& operator=(const MAiNGOException&) = default; - MAiNGOException& operator=(MAiNGOException&&) = default; - virtual ~MAiNGOException() = default; + MAiNGOException& operator=(MAiNGOException&&) = default; + virtual ~MAiNGOException() = default; explicit MAiNGOException(const std::string& errorMessage) { diff --git a/inc/MAiNGOMpiException.h b/inc/MAiNGOMpiException.h index 98c5a82..b09a89f 100644 --- a/inc/MAiNGOMpiException.h +++ b/inc/MAiNGOMpiException.h @@ -31,12 +31,12 @@ namespace maingo { class MAiNGOMpiException: public MAiNGOException { public: - MAiNGOMpiException() = delete; - MAiNGOMpiException(const MAiNGOMpiException&) = default; - MAiNGOMpiException(MAiNGOMpiException&&) = default; + MAiNGOMpiException() = delete; + MAiNGOMpiException(const MAiNGOMpiException&) = default; + MAiNGOMpiException(MAiNGOMpiException&&) = default; MAiNGOMpiException& operator=(const MAiNGOMpiException&) = default; - MAiNGOMpiException& operator=(MAiNGOMpiException&&) = default; - ~MAiNGOMpiException() = default; + MAiNGOMpiException& operator=(MAiNGOMpiException&&) = default; + ~MAiNGOMpiException() = default; enum ORIGIN { ORIGIN_ME = 1, /*!< An exception (not necessarily this one) was originally thrown by the current process */ diff --git a/inc/MAiNGOevaluator.h b/inc/MAiNGOevaluator.h index 43d7112..97dcc15 100644 --- a/inc/MAiNGOevaluator.h +++ b/inc/MAiNGOevaluator.h @@ -14,7 +14,10 @@ #include "MAiNGOException.h" #include "symbol_table.hpp" +#include "nrtlSubroutines.h" #include "util/evaluator.hpp" +#include "util/visitor_utils.hpp" +#include <iterator> #include "ffunc.hpp" @@ -43,12 +46,12 @@ class MaingoEvaluator { public: /** - * @brief Constructor - * - * @param[in] symbols is the symbol_table for symbol lookup - * @param[in] variables is the vecor of MAiNGO variables - * @param[in] positions maps ALE symbol names to positions in the MAiNGO variable vector - */ + * @brief Constructor + * + * @param[in] symbols is the symbol_table for symbol lookup + * @param[in] variables is the vecor of MAiNGO variables + * @param[in] positions maps ALE symbol names to positions in the MAiNGO variable vector + */ MaingoEvaluator( symbol_table& symbols, const std::vector<Var>& variables, @@ -60,15 +63,21 @@ class MaingoEvaluator { } /** - * @name Dispatch functions - * @brief Functions dispatching to visit functions - */ + * @name Dispatch functions + * @brief Functions dispatching to visit functions + */ /**@{*/ Var dispatch(expression<real<0>>& expr) { return dispatch(expr.get()); } + template <unsigned IDim> + tensor<Var, IDim> dispatch(expression<real<IDim>>& expr) + { + return dispatch(expr.get()); + } + ConstraintContainer dispatch(expression<boolean<0>>& expr) { return dispatch(expr.get()); @@ -83,15 +92,18 @@ class MaingoEvaluator { template <unsigned IDim> typename ale::index<IDim>::ref_type dispatch(value_node<ale::index<IDim>>* node) { - evaluator eval(_symbols); - return eval.dispatch(node); + return util::evaluate_expression(node, _symbols); + } + + int dispatch(value_node<ale::index<0>>* node) + { + return util::evaluate_expression(node, _symbols); } template <typename TType> typename set<TType, 0>::basic_type dispatch(value_node<set<TType, 0>>* node) { - evaluator eval(_symbols); - return eval.dispatch(node); + return util::evaluate_expression(node, _symbols); } @@ -113,7 +125,6 @@ class MaingoEvaluator { return std::visit(*this, node->get_variant()); } - template <unsigned IDim> tensor<Var, IDim> dispatch(value_symbol<real<IDim>>* sym) { @@ -127,9 +138,9 @@ class MaingoEvaluator { /**@}*/ /** - * @name Visit functions - * @brief Specific visit implementations - */ + * @name Visit functions + * @brief Specific visit implementations + */ /**@{*/ template <unsigned IDim> tensor<Var, IDim> operator()(constant_node<real<IDim>>* node) @@ -163,6 +174,59 @@ class MaingoEvaluator { return dispatch(sym); } + template <unsigned IDim> + tensor<Var, IDim> operator()(attribute_node<real<IDim>>* node) + { + variable_symbol<real<IDim>>* sym = cast_variable_symbol<real<IDim>>(_symbols.resolve(node->variable_name)); + if (!sym) { + throw std::invalid_argument("Error: MaingoEvaluator -- Symbol " + node->variable_name + " has unexpected type in attribute call."); + } + tensor<Var, IDim> result(sym->shape()); + switch (node->attribute) { + case variable_attribute_type::INIT: + result.ref().assign(sym->init()); + return result; + case variable_attribute_type::LB: + result.ref().assign(sym->lower()); + return result; + case variable_attribute_type::UB: + result.ref().assign(sym->upper()); + return result; + case variable_attribute_type::PRIO: + result.ref().assign(sym->prio()); + return result; + default: + throw std::invalid_argument("Error: MaingoEvaluator -- Symbol " + node->variable_name + " has unexpected attribute."); + } + } + + Var operator()(attribute_node<real<0>>* node) + { + variable_symbol<real<0>>* sym = cast_variable_symbol<real<0>>(_symbols.resolve(node->variable_name)); + if (!sym) { + throw std::invalid_argument("Error: MaingoEvaluator -- Symbol " + node->variable_name + " has unexpected type in attribute call."); + } + switch (node->attribute) { + case variable_attribute_type::INIT: + return sym->init(); + case variable_attribute_type::LB: + return sym->lower(); + case variable_attribute_type::UB: + return sym->upper(); + case variable_attribute_type::PRIO: + return sym->prio(); + default: + throw std::invalid_argument("Error: MaingoEvaluator -- Symbol " + node->variable_name + " has unexpected attribute."); + } + } + + template <unsigned IDim> + ConstraintContainer operator()(attribute_node<boolean<0>>* node) + { + throw MAiNGOException(" Error: MaingoEvaluator -- Evaluated unsupported general logical expression"); + return ConstraintContainer(); + } + Var operator()(parameter_node<real<0>>* node) { auto sym = _symbols.resolve<real<0>>(node->name); @@ -225,6 +289,11 @@ class MaingoEvaluator { return _variables[_positions.at(sym->m_name)]; } + template <unsigned IDim> + tensor<Var, IDim> operator()(expression_symbol<real<IDim>>* sym) + { + return dispatch(sym->m_value.get()); + } Var operator()(expression_symbol<real<0>>* sym) { @@ -237,17 +306,206 @@ class MaingoEvaluator { return dispatch(sym->m_value.get()); } + template <unsigned IDim> + tensor<Var, IDim> dispatch(function_symbol<real<IDim>>* sym) + { + throw MAiNGOException(" Error: MaingoEvaluator -- Used unsupported dispatch"); + } + + Var dispatch(function_symbol<real<0>>* sym) + { + throw MAiNGOException(" Error: MaingoEvaluator -- Used unsupported dispatch"); + } + template <unsigned IDim> + std::string getBaseIdentifier(entry_node<real<IDim>>* node) + { + auto child_node = dynamic_cast<entry_node<real<IDim + 1>>*>(node->template get_child<0>()); + if (child_node == nullptr) { + return expression_to_string(node->template get_child<0>()); + } + return getBaseIdentifier(child_node); + } + + std::string getBaseIdentifier(entry_node<real<LIBALE_MAX_DIM - 1>>* node) + { + return expression_to_string(node->template get_child<0>()); + } + + // non-terminal visits template <unsigned IDim> tensor<Var, IDim> operator()(entry_node<real<IDim>>* node) { - return dispatch(node->template get_child<0>())[dispatch(node->template get_child<1>()) - 1]; + auto tensor = dispatch(node->template get_child<0>()); + auto access_index = dispatch(node->template get_child<1>()); + if (1 > access_index || access_index > tensor.shape(0)) { + std::string name = getBaseIdentifier(node); + std::string err_msg = "Dimension access violation in tensor \"" + name + "\": index " + std::to_string(access_index) + " is out of bounds"; + size_t access_dim; + std::vector<size_t> para_sizes; + std::ostringstream sizes_str; + try { + para_sizes = get_parameter_shape(name, _symbols); + access_dim = para_sizes.size() - IDim; + if (!para_sizes.empty()) { + std::copy(para_sizes.begin(), para_sizes.end() - 1, std::ostream_iterator<size_t>(sizes_str, ", ")); + sizes_str << para_sizes.back(); + } + err_msg += " at access dimension " + std::to_string(access_dim) + ". tensor dimension is {" + sizes_str.str() + "}."; + } + catch (std::invalid_argument& e) { + sizes_str << tensor.shape(0); + err_msg += ". tensor dimension at access is {" + sizes_str.str() + "}."; + } + throw std::invalid_argument(err_msg); + } + return tensor[access_index - 1]; } Var operator()(entry_node<real<0>>* node) { - return dispatch(node->get_child<0>())[dispatch(node->get_child<1>()) - 1]; + auto tensor = dispatch(node->template get_child<0>()); + auto access_index = dispatch(node->template get_child<1>()); + if (1 > access_index || access_index > tensor.shape(0)) { + std::string name = getBaseIdentifier(node); + std::string err_msg = "Dimension access violation in tensor \"" + name + "\": index " + std::to_string(access_index) + " is out of bounds"; + size_t access_dim; + std::vector<size_t> para_sizes; + std::ostringstream sizes_str; + try { + para_sizes = get_parameter_shape(name, _symbols); + access_dim = para_sizes.size() - (tensor.shape().size() - 1); + if (!para_sizes.empty()) { + std::copy(para_sizes.begin(), para_sizes.end() - 1, std::ostream_iterator<size_t>(sizes_str, ", ")); + sizes_str << para_sizes.back(); + } + err_msg += " at access dimension " + std::to_string(access_dim) + ". tensor dimension is {" + sizes_str.str() + "}."; + } + catch (std::invalid_argument& e) { + sizes_str << tensor.shape(0); + err_msg += ". tensor dimension at access is {" + sizes_str.str() + "}."; + } + throw std::invalid_argument(err_msg); + } + return tensor[access_index - 1]; + } + + template <unsigned IDim> + tensor<Var, IDim> operator()(function_node<real<IDim>>* node) + { + return evaluate_function(*this, node, _symbols); + } + + Var operator()(function_node<real<0>>* node) + { + return evaluate_function(*this, node, _symbols); + } + + ConstraintContainer operator()(function_node<boolean<0>>* node) + { + throw MAiNGOException(" Error: MaingoEvaluator -- Evaluated unsupported function_node expression"); + return ConstraintContainer(); + } + + tensor<Var, 1> operator()(tensor_node<real<1>>* node) + { + size_t shape[1]; + std::vector<Var> entries; + for (auto it = node->children.begin(); it != node->children.end(); ++it) { //evaluate Var-children of tensor in their dimension (one lower) + Var child = dispatch(it->get()); + entries.push_back(child); + } + shape[0] = entries.size(); + tensor_ref<Var, 1> res = tensor<Var, 1>(shape).ref(); //put evaluated entries in result-tensor + for (int i = 0; i < entries.size(); ++i) { + res[i] = entries[i]; + } + tensor<Var, 1> result(res.shape()); + result.ref().assign(res); + return result; + } + + template <unsigned IDim> + tensor<Var, IDim> operator()(tensor_node<real<IDim>>* node) + { + size_t shape[IDim]; + for (int i = 0; i < IDim; ++i) { + shape[i] = 0; + } + std::vector<tensor<Var, IDim - 1>> entries; + for (auto it = node->children.begin(); it != node->children.end(); ++it) { //evaluate 'children' of tensor in their dimension (one lower) + tensor<Var, IDim - 1> child = dispatch(it->get()); + if (it == node->children.begin()) { + for (int i = 1; i < IDim; ++i) { + shape[i] = child.shape(i - 1); + } + } + if (it != node->children.begin()) { //check for the right shape + for (int i = 1; i < IDim; ++i) { + if (shape[i] != child.shape(i - 1)) { + throw std::invalid_argument("different shapes in tensor_node"); + } + } + } + entries.push_back(child); + } + shape[0] = entries.size(); + tensor_ref<Var, IDim> res = tensor<Var, IDim>(shape).ref(); //put evaluated entries in result-tensor + for (int i = 0; i < entries.size(); ++i) { + res[i].assign(entries[i]); + } + tensor<Var, IDim> result(res.shape()); + result.ref().assign(res); + return result; + } + + template <unsigned IDim> + tensor<Var, 1> operator()(vector_node<real<IDim>>* node) + { + auto res = util::evaluate_expression(node, _symbols); + tensor<Var, 1> result(res.shape()); + result.ref().assign(res); + return result; + } + + tensor<Var, 1> operator()(vector_node<real<0>>* node) + { + size_t dim[1] = {1}; + using basic_type = typename real<0>::basic_type; + Var child = dispatch(node->template get_child<0>()); + std::array<size_t, 1> shape; + shape[0] = 1; + tensor<Var, 1> result(shape, child); + return result; + } + + template <unsigned IDim> + tensor<Var, IDim> operator()(index_shift_node<real<IDim>>* node) + { + auto res = util::evaluate_expression(node, _symbols); + tensor<Var, IDim> result(res.shape()); + result.ref().assign(res); + return result; + } + + + Var operator()(index_to_real_node* node) + { + int value = dispatch(node->get_child<0>()); + return (double)value; + } + + + Var operator()(real_to_index_node* node) + { + throw MAiNGOException(" Error: MaingoEvaluator -- Evaluated unsupported real_to_index_node"); + } + + + Var operator()(round_node* node) + { + throw MAiNGOException(" Error: MaingoEvaluator -- Evaluated unsupported round_node"); } @@ -304,7 +562,7 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Called xlog_sum with odd number of arguments"); } if (node->children.size() < 2) { - throw MAiNGOException(" Error: MaingoEvaluator -- Called xlog_sum with less than arguments"); + throw MAiNGOException(" Error: MaingoEvaluator -- Called xlog_sum with less than 2 arguments"); } std::vector<Var> vars; std::vector<double> coeff; @@ -337,7 +595,8 @@ class MaingoEvaluator { { Var result = 1; for (auto it = node->children.rbegin(); it != node->children.rend(); ++it) { - result = pow(dispatch(it->get()), result); + result = pow(dispatch(it->get()), + result); } return result; } @@ -352,7 +611,8 @@ class MaingoEvaluator { Var result = dispatch(it->get()); it++; for (; it != node->children.end(); ++it) { - result = mc::min(dispatch(it->get()), result); + result = mc::min(dispatch(it->get()), + result); } return result; } @@ -367,7 +627,8 @@ class MaingoEvaluator { Var result = dispatch(it->get()); it++; for (; it != node->children.end(); ++it) { - result = mc::max(dispatch(it->get()), result); + result = mc::max(dispatch(it->get()), + result); } return result; } @@ -387,7 +648,8 @@ class MaingoEvaluator { ++it; for (; it != elements.end(); ++it) { _symbols.define(node->name, new parameter_symbol<TType>(node->name, *it)); - result = mc::min(dispatch(node->template get_child<1>()), result); + result = mc::min(dispatch(node->template get_child<1>()), + result); } _symbols.pop_scope(); return result; @@ -408,7 +670,8 @@ class MaingoEvaluator { ++it; for (; it != elements.end(); ++it) { _symbols.define(node->name, new parameter_symbol<TType>(node->name, *it)); - result = mc::max(dispatch(node->template get_child<1>()), result); + result = mc::max(dispatch(node->template get_child<1>()), + result); } _symbols.pop_scope(); return result; @@ -471,7 +734,8 @@ class MaingoEvaluator { Var operator()(lmtd_node* node) { - return mc::lmtd(dispatch(node->get_child<0>()), dispatch(node->get_child<1>())); + return mc::lmtd(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>())); } @@ -480,7 +744,8 @@ class MaingoEvaluator { if (!dispatch(node->get_child<1>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Second argument in xexpax is not a constant"); } - return mc::xexpax(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val()); + return mc::xexpax(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val()); } @@ -489,7 +754,8 @@ class MaingoEvaluator { if (!dispatch(node->get_child<1>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Second argument in arh is not a constant"); } - return mc::Op<mc::FFVar>::arh(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val()); + return mc::Op<mc::FFVar>::arh(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val()); } @@ -498,7 +764,8 @@ class MaingoEvaluator { if (!dispatch(node->get_child<1>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Second argument in lb_func is not a constant"); } - return mc::lb_func(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val()); + return mc::lb_func(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val()); } @@ -507,7 +774,8 @@ class MaingoEvaluator { if (!dispatch(node->get_child<1>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Second argument in ub_func is not a constant"); } - return mc::ub_func(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val()); + return mc::ub_func(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val()); } @@ -519,7 +787,9 @@ class MaingoEvaluator { if (!dispatch(node->get_child<2>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Third argument in bounding_func is not a constant"); } - return mc::bounding_func(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val()); + return mc::bounding_func(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val()); } @@ -531,7 +801,9 @@ class MaingoEvaluator { if (!dispatch(node->get_child<2>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Third argument in squash_node is not a constant"); } - return mc::squash_node(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val()); + return mc::squash_node(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val()); } @@ -540,7 +812,10 @@ class MaingoEvaluator { if (!dispatch(node->get_child<2>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Third argument in af_lcb_node is not a constant"); } - return mc::acquisition_function(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()), 1, dispatch(node->get_child<2>()).num().val()); + return mc::acquisition_function(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()), + 1, + dispatch(node->get_child<2>()).num().val()); } @@ -549,7 +824,10 @@ class MaingoEvaluator { if (!dispatch(node->get_child<2>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Third argument in af_ei_node is not a constant"); } - return mc::acquisition_function(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()), 2, dispatch(node->get_child<2>()).num().val()); + return mc::acquisition_function(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()), + 2, + dispatch(node->get_child<2>()).num().val()); } @@ -558,7 +836,10 @@ class MaingoEvaluator { if (!dispatch(node->get_child<2>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Third argument in af_pi_node is not a constant"); } - return mc::acquisition_function(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()), 3, dispatch(node->get_child<2>()).num().val()); + return mc::acquisition_function(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()), + 3, + dispatch(node->get_child<2>()).num().val()); } @@ -570,23 +851,9 @@ class MaingoEvaluator { if (!dispatch(node->get_child<2>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Third argument in regnormal_node is not a constant"); } - return mc::regnormal(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val()); - } - - - Var operator()(nrtl_dtau_node* node) - { - if (!dispatch(node->get_child<1>()).cst()) { - throw MAiNGOException(" Error: MaingoEvaluator -- Second argument in nrtl_dtau is not a constant"); - } - if (!dispatch(node->get_child<2>()).cst()) { - throw MAiNGOException(" Error: MaingoEvaluator -- Third argument in nrtl_dtau is not a constant"); - } - if (!dispatch(node->get_child<3>()).cst()) { - throw MAiNGOException(" Error: MaingoEvaluator -- Fourth argument in nrtl_dtau is not a constant"); - } - return mc::nrtl_dtau(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val()); + return mc::regnormal(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val()); } @@ -614,9 +881,15 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p7 in ext_antoine_psat is not a constant"); } // ext_antoine_psat = type 1 - return mc::vapor_pressure(dispatch(node->get_child<0>()), 1, dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val(), - dispatch(node->get_child<6>()).num().val(), dispatch(node->get_child<7>()).num().val()); + return mc::vapor_pressure(dispatch(node->get_child<0>()), + 1, + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val(), + dispatch(node->get_child<6>()).num().val(), + dispatch(node->get_child<7>()).num().val()); } @@ -632,7 +905,10 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p3 in antoine_psat is not a constant"); } // antoine_psat = type 2 - return mc::vapor_pressure(dispatch(node->get_child<0>()), 2, dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), + return mc::vapor_pressure(dispatch(node->get_child<0>()), + 2, + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), dispatch(node->get_child<3>()).num().val()); } @@ -658,8 +934,13 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p6 in wagner_psat is not a constant"); } // wagner_psat = type 3 - return mc::vapor_pressure(dispatch(node->get_child<0>()), 3, dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val(), + return mc::vapor_pressure(dispatch(node->get_child<0>()), + 3, + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val(), dispatch(node->get_child<6>()).num().val()); } @@ -697,10 +978,18 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p10 in ik_cape_psat is not a constant"); } // ik_cape_psat = type 4 - return mc::vapor_pressure(dispatch(node->get_child<0>()), 4, dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val(), - dispatch(node->get_child<6>()).num().val(), dispatch(node->get_child<7>()).num().val(), dispatch(node->get_child<8>()).num().val(), - dispatch(node->get_child<9>()).num().val(), dispatch(node->get_child<10>()).num().val()); + return mc::vapor_pressure(dispatch(node->get_child<0>()), + 4, + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val(), + dispatch(node->get_child<6>()).num().val(), + dispatch(node->get_child<7>()).num().val(), + dispatch(node->get_child<8>()).num().val(), + dispatch(node->get_child<9>()).num().val(), + dispatch(node->get_child<10>()).num().val()); } @@ -728,9 +1017,15 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p7 in aspen_hig is not a constant"); } // aspen_hig = type 1 - return mc::ideal_gas_enthalpy(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), 1, dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val(), - dispatch(node->get_child<6>()).num().val(), dispatch(node->get_child<7>()).num().val()); + return mc::ideal_gas_enthalpy(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + 1, + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val(), + dispatch(node->get_child<6>()).num().val(), + dispatch(node->get_child<7>()).num().val()); } @@ -761,9 +1056,16 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p8 in nasa9_hig is not a constant"); } // nasa9_hig = type 2 - return mc::ideal_gas_enthalpy(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), 2, dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val(), - dispatch(node->get_child<6>()).num().val(), dispatch(node->get_child<7>()).num().val(), dispatch(node->get_child<8>()).num().val()); + return mc::ideal_gas_enthalpy(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + 2, + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val(), + dispatch(node->get_child<6>()).num().val(), + dispatch(node->get_child<7>()).num().val(), + dispatch(node->get_child<8>()).num().val()); } @@ -788,8 +1090,13 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p6 in dippr107_hig is not a constant"); } // dippr107_hig_node = type 3 - return mc::ideal_gas_enthalpy(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), 3, dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val(), + return mc::ideal_gas_enthalpy(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + 3, + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val(), dispatch(node->get_child<6>()).num().val()); } @@ -821,9 +1128,16 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p8 in dippr127_hig is not a constant"); } // dippr127_hig = type 4 - return mc::ideal_gas_enthalpy(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), 4, dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val(), - dispatch(node->get_child<6>()).num().val(), dispatch(node->get_child<7>()).num().val(), dispatch(node->get_child<8>()).num().val()); + return mc::ideal_gas_enthalpy(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + 4, + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val(), + dispatch(node->get_child<6>()).num().val(), + dispatch(node->get_child<7>()).num().val(), + dispatch(node->get_child<8>()).num().val()); } @@ -839,7 +1153,10 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p3 in antoine_tsat is not a constant"); } // antoine_tsat = type 2 - return mc::saturation_temperature(dispatch(node->get_child<0>()), 2, dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), + return mc::saturation_temperature(dispatch(node->get_child<0>()), + 2, + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), dispatch(node->get_child<3>()).num().val()); } @@ -862,8 +1179,13 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p5 in watson_dhvap is not a constant"); } // watson_dhvap = type 1 - return mc::enthalpy_of_vaporization(dispatch(node->get_child<0>()), 1, dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val()); + return mc::enthalpy_of_vaporization(dispatch(node->get_child<0>()), + 1, + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val()); } @@ -888,8 +1210,13 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p6 in dippr106_dhvap is not a constant"); } // dippr106_dhvap = type 2 - return mc::enthalpy_of_vaporization(dispatch(node->get_child<0>()), 2, dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val(), + return mc::enthalpy_of_vaporization(dispatch(node->get_child<0>()), + 2, + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val(), dispatch(node->get_child<6>()).num().val()); } @@ -906,39 +1233,52 @@ class MaingoEvaluator { throw MAiNGOException(" Error: MaingoEvaluator -- Parameter p3 in cost_turton is not a constant"); } // cost_turton = type 1 - return mc::cost_function(dispatch(node->get_child<0>()), 1, dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), + return mc::cost_function(dispatch(node->get_child<0>()), + 1, + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), dispatch(node->get_child<3>()).num().val()); } + Var operator()(covar_matern_1_node* node) { // covar_matern_1 = type 1 - return mc::covariance_function(dispatch(node->get_child<0>()), 1); + return mc::covariance_function(dispatch(node->get_child<0>()), + 1); } + Var operator()(covar_matern_3_node* node) { - // covar_matern_3 = type 1 - return mc::covariance_function(dispatch(node->get_child<0>()), 2); + // covar_matern_3 = type 2 + return mc::covariance_function(dispatch(node->get_child<0>()), + 2); } + Var operator()(covar_matern_5_node* node) { - // covar_matern_5 = type 1 - return mc::covariance_function(dispatch(node->get_child<0>()), 3); + // covar_matern_5 = type 3 + return mc::covariance_function(dispatch(node->get_child<0>()), + 3); } + Var operator()(covar_sqrexp_node* node) { - // covar_sqrexp = type 1 - return mc::covariance_function(dispatch(node->get_child<0>()), 4); + // covar_sqrexp = type 4 + return mc::covariance_function(dispatch(node->get_child<0>()), + 4); } + Var operator()(gpdf_node* node) { return mc::gaussian_probability_density_function(dispatch(node->get_child<0>())); } + Var operator()(nrtl_tau_node* node) { if (!dispatch(node->get_child<1>()).cst()) { @@ -953,8 +1293,29 @@ class MaingoEvaluator { if (!dispatch(node->get_child<4>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Fifth argument in nrtl_tau is not a constant"); } - return mc::nrtl_tau(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val()); + return nrtl_subroutine_tau(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val()); + } + + + Var operator()(nrtl_dtau_node* node) + { + if (!dispatch(node->get_child<1>()).cst()) { + throw MAiNGOException(" Error: MaingoEvaluator -- Second argument in nrtl_dtau is not a constant"); + } + if (!dispatch(node->get_child<2>()).cst()) { + throw MAiNGOException(" Error: MaingoEvaluator -- Third argument in nrtl_dtau is not a constant"); + } + if (!dispatch(node->get_child<3>()).cst()) { + throw MAiNGOException(" Error: MaingoEvaluator -- Fourth argument in nrtl_dtau is not a constant"); + } + return nrtl_subroutine_dtau(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val()); } @@ -975,8 +1336,12 @@ class MaingoEvaluator { if (!dispatch(node->get_child<5>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Sixth argument in nrtl_g is not a constant"); } - return mc::nrtl_G(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val()); + return nrtl_subroutine_G(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val()); } @@ -997,8 +1362,12 @@ class MaingoEvaluator { if (!dispatch(node->get_child<5>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Sixth argument in nrtl_gtau is not a constant"); } - return mc::nrtl_Gtau(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val()); + return nrtl_subroutine_Gtau(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val()); } @@ -1019,11 +1388,14 @@ class MaingoEvaluator { if (!dispatch(node->get_child<5>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Sixth argument in nrtl_gdtau is not a constant"); } - return mc::nrtl_Gdtau(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val()); + return nrtl_subroutine_Gdtau(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val()); } - Var operator()(nrtl_dgtau_node* node) { if (!dispatch(node->get_child<1>()).cst()) { @@ -1041,14 +1413,19 @@ class MaingoEvaluator { if (!dispatch(node->get_child<5>()).cst()) { throw MAiNGOException(" Error: MaingoEvaluator -- Sixth argument in nrtl_dgtau is not a constant"); } - return mc::nrtl_dGtau(dispatch(node->get_child<0>()), dispatch(node->get_child<1>()).num().val(), dispatch(node->get_child<2>()).num().val(), - dispatch(node->get_child<3>()).num().val(), dispatch(node->get_child<4>()).num().val(), dispatch(node->get_child<5>()).num().val()); + return nrtl_subroutine_dGtau(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>()).num().val(), + dispatch(node->get_child<2>()).num().val(), + dispatch(node->get_child<3>()).num().val(), + dispatch(node->get_child<4>()).num().val(), + dispatch(node->get_child<5>()).num().val()); } Var operator()(norm2_node* node) { - return mc::euclidean_norm_2d(dispatch(node->get_child<0>()), dispatch(node->get_child<1>())); + return mc::euclidean_norm_2d(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>())); } @@ -1144,13 +1521,15 @@ class MaingoEvaluator { Var operator()(rlmtd_node* node) { - return mc::rlmtd(dispatch(node->get_child<0>()), dispatch(node->get_child<1>())); + return mc::rlmtd(dispatch(node->get_child<0>()), + dispatch(node->get_child<1>())); } Var operator()(xexpy_node* node) { - return mc::expx_times_y(dispatch(node->get_child<1>()), dispatch(node->get_child<0>())); + return mc::expx_times_y(dispatch(node->get_child<1>()), + dispatch(node->get_child<0>())); } @@ -1185,6 +1564,9 @@ class MaingoEvaluator { Var operator()(sum_node<TType>* node) { auto elements = dispatch(node->template get_child<0>()); + if (elements.begin() == elements.end()) { + std::cout << "called sum with emtpy set (by convention equals 0)\n"; + } _symbols.push_scope(); Var result = 0; for (auto it = elements.begin(); it != elements.end(); ++it) { @@ -1196,6 +1578,24 @@ class MaingoEvaluator { } + template <typename TType> + Var operator()(product_node<TType>* node) + { + auto elements = dispatch(node->template get_child<0>()); + if (elements.begin() == elements.end()) { + std::cout << "called product with emtpy set (by convention equals 1)\n"; + } + _symbols.push_scope(); + Var result = 1; + for (auto it = elements.begin(); it != elements.end(); ++it) { + _symbols.define(node->name, new parameter_symbol<TType>(node->name, *it)); + result *= dispatch(node->template get_child<1>()); + } + _symbols.pop_scope(); + return result; + } + + ConstraintContainer operator()(negation_node* node) { throw MAiNGOException(" Error: MaingoEvaluator -- Evaluated unsupported negation expression"); @@ -1289,8 +1689,8 @@ class MaingoEvaluator { return ConstraintContainer(); } - - ConstraintContainer operator()(element_node* node) + template <typename TType> + ConstraintContainer operator()(element_node<TType>* node) { throw MAiNGOException(" Error: MaingoEvaluator -- Evaluated unsupported general logical expression"); return ConstraintContainer(); diff --git a/inc/MAiNGOmodel.h b/inc/MAiNGOmodel.h index 8305b31..ca6ad91 100644 --- a/inc/MAiNGOmodel.h +++ b/inc/MAiNGOmodel.h @@ -12,6 +12,7 @@ #pragma once #include "evaluationContainer.h" +#include "usingAdditionalIntrinsicFunctions.h" #include "babOptVar.h" #include "babUtils.h" @@ -19,54 +20,9 @@ #include "ffunc.hpp" #include "functionWrapper.h" - #include <vector> -// Using declarations of all additional functions defined in MC++ for a comfortable use of these functions in the model -using mc::acquisition_function; -using mc::arh; -using mc::bounding_func; -using mc::bstep; -using mc::cost_function; -using mc::covariance_function; -using mc::enthalpy_of_vaporization; -using mc::euclidean_norm_2d; -using mc::expx_times_y; -using mc::fabsx_times_x; -using mc::fstep; -using mc::gaussian_probability_density_function; -using mc::iapws; -using mc::ideal_gas_enthalpy; -using mc::lb_func; -using mc::lmtd; -using mc::mc_print; -using mc::neg; -using mc::nrtl_dGtau; -using mc::nrtl_dtau; -using mc::nrtl_G; -using mc::nrtl_Gdtau; -using mc::nrtl_Gtau; -using mc::nrtl_tau; -using mc::p_sat_ethanol_schroeder; -using mc::pos; -using mc::regnormal; -using mc::rho_liq_sat_ethanol_schroeder; -using mc::rho_vap_sat_ethanol_schroeder; -using mc::rlmtd; -using mc::saturation_temperature; -using mc::sqr; -using mc::squash_node; -using mc::sum_div; -using mc::ub_func; -using mc::vapor_pressure; -using mc::xexpax; -using mc::xlog; -using mc::xlog_sum; -using std::max; -using std::min; - - /** * @namespace maingo * @brief namespace holding all essentials of MAiNGO diff --git a/inc/bab.h b/inc/bab.h index b35ad45..5d86779 100644 --- a/inc/bab.h +++ b/inc/bab.h @@ -132,6 +132,29 @@ class BranchAndBound { */ double get_nodes_left() { return _nNodesLeft; } +#ifdef HAVE_GROWING_DATASETS + /** + * @brief Function for passing pointer of vector containing datasets to B&B + * + * @param[in] datasetsIn is a pointer to a vector containing all available datasets + */ + void pass_datasets_to_bab(const std::shared_ptr<std::vector<std::set<unsigned int>>> datasetsIn) + { + _datasets = datasetsIn; + } +#endif // HAVE_GROWING_DATASETS +#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) + /** + * @brief Function for passing pointer of LBS with full dataset to B&B + * + * @param[in] LBSFullIn is a pointer to an LBS object using the full dataset only + */ + void pass_LBSFull_to_bab(const std::shared_ptr<lbp::LowerBoundingSolver> LBSFullIn) + { + _LBSFull = LBSFullIn; + } +#endif + private: /** * @enum _TERMINATION_TYPE @@ -186,7 +209,6 @@ class BranchAndBound { */ bool _postprocess_node(babBase::BabNode ¤tNodeInOut, const std::vector<double> &lbpSolutionPoint, const lbp::LbpDualInfo &dualInfo); - /** * @brief Function for updating the incumbent and fathoming accordingly * @@ -201,6 +223,25 @@ class BranchAndBound { */ void _update_lowest_lbd(); +#ifdef HAVE_GROWING_DATASETS + /** + * @brief Function which checks whether to augment the dataset + * + * @param[in] currentNode is the node to be processed + * @param[in] lbpSolutionPoint is the solution point of the LBP of the current node + * @param[in] currentLBD is the objective value of the LBP of the current node + */ + bool _check_whether_to_augment(const babBase::BabNode ¤tNode, const std::vector<double> &lbpSolutionPoint, const double currentLBD); + + /** + * @brief Function for augmenting dataset of node + * + * @param[in] current index of dataset + * @param[out] new index of dataset after augmentation + */ + unsigned int _augment_dataset(babBase::BabNode ¤tNode); +#endif // HAVE_GROWING_DATASETS + /** * @brief Function which checks whether it is necessary to activate scaling within the LBD solver. This is a heuristic approach, which does not affect any deterministic optimization assumptions */ @@ -219,6 +260,20 @@ class BranchAndBound { */ void _display_and_log_progress(const double currentNodeLBD, const babBase::BabNode ¤tNode); +#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) + /** + * @brief Function for printing the current node to a separate log file + * + * @param[in] currentNode is the current node + * @param[in] currentNodeLbd is the lower bound calculated in the current node + * @param[in] currentNodeLbdFull is the lower bound calculated based on the full dataset in the current node + * @param[in] currentNodeUbdFull is the upper bound of the current node + * @param[in] lbpSolutionPoint is the solution point of the lower bound calculated und used in the current node + * @param[in] lbpSolutionPointFullis the solution point of the lower bound calculated based on the full dataset in the current node + */ + void _log_nodes(const babBase::BabNode ¤tNode, const double currentNodeLbd, const double currentNodeLbdFull, const double currentNodeUbdFull, const std::vector<double> lbpSolutionPoint, const std::vector<double> lbpSolutionPointFull); +#endif + /** * @brief Function printing a termination message * @@ -253,7 +308,10 @@ class BranchAndBound { * @param[in] theLBD is the lower bound of the node * @param[in] theNode is the node to be printed */ - void _print_one_node(const double theLBD, const babBase::BabNode &theNode) { _print_one_node(theLBD, theNode.get_ID(), theNode.get_lower_bounds(), theNode.get_upper_bounds()); } + void _print_one_node(const double theLBD, const babBase::BabNode &theNode) + { + _print_one_node(theLBD, theNode.get_ID(), theNode.get_lower_bounds(), theNode.get_upper_bounds()); + } /** * @brief Function printing one node @@ -262,7 +320,10 @@ class BranchAndBound { * @param[in] theNode is the node to be printed * @param[in] outstream is the stream to be written to, e.g., an error message */ - void _print_one_node(const double theLBD, const babBase::BabNode &theNode, std::ostream &outstream) { _print_one_node(theLBD, theNode.get_ID(), theNode.get_lower_bounds(), theNode.get_upper_bounds(), outstream); } + void _print_one_node(const double theLBD, const babBase::BabNode &theNode, std::ostream &outstream) + { + _print_one_node(theLBD, theNode.get_ID(), theNode.get_lower_bounds(), theNode.get_upper_bounds(), outstream); + } #ifdef HAVE_MAiNGO_MPI /** @@ -298,6 +359,16 @@ class BranchAndBound { */ void _send_new_problem(const babBase::BabNode &node, const int dest); +#ifdef HAVE_GROWING_DATASETS + /** + * @brief Auxiliary function for sending new dataset to a worker + * + * @param[in] newDataset is the dataset that is sent to the worker for appending the datasets vector + * @param[in] dest is the worker to whom the problem is sent + */ + void _send_new_dataset(const std::set<unsigned int> &newDataset, const int dest); +#endif // HAVE_GROWING_DATASETS + /** * @brief Auxillary function for informing workers about occuring events * @@ -318,6 +389,13 @@ class BranchAndBound { */ void _recv_new_problem(babBase::BabNode &node); +#ifdef HAVE_GROWING_DATASETS + /** + * @brief Auxiliary function for receiving a new dataset from the manager + */ + void _recv_new_dataset(); +#endif // HAVE_GROWING_DATASETS + /** * @brief Auxiliary function for sending a new incumbent to the manager * @@ -360,6 +438,11 @@ class BranchAndBound { std::unique_ptr<babBase::Brancher> _brancher; /*!< pointer to brancher object that holds and manages the branch-and-bound tree */ std::shared_ptr<ubp::UpperBoundingSolver> _UBS; /*!< pointer to upper bounding solver */ std::shared_ptr<lbp::LowerBoundingSolver> _LBS; /*!< pointer to lower bounding solver */ +#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) + std::shared_ptr<lbp::LowerBoundingSolver> _LBSFull; /*!< pointer to lower bounding solver using the full dataset only */ + double _currentLBDFull; /*!< lower bound of the current node when using the full dataset */ + std::vector<double> _lbpSolutionPointFull; /*!< solution point of the LBP in the current node when using the full dataset */ +#endif std::shared_ptr<Settings> _maingoSettings; /*!< pointer to object storing settings */ @@ -372,6 +455,9 @@ class BranchAndBound { const unsigned _nvarWOaux; /*!< stores number of optimization variables without additional auxiliary variables */ std::vector<double> _lowerVarBoundsOrig; /*!< vector storing original lower bounds */ std::vector<double> _upperVarBoundsOrig; /*!< vector storing upper bounds */ +#ifdef HAVE_GROWING_DATASETS + std::shared_ptr<std::vector<std::set<unsigned int>>> _datasets; /*!< pointer to a vector containing all available datasets */ +#endif // HAVE_GROWING_DATASETS /**@}*/ /** @@ -452,6 +538,9 @@ class BranchAndBound { bool _confirmedTermination; /*!< stores whether termination was already confirmed by the user */ unsigned _workCount; /*!< number of active workers */ std::vector<std::pair<bool, double>> _nodesGivenToWorkers; /*!< vector holding whether worker i currently has a node and the double value of the lbd of this node */ +#ifdef HAVE_GROWING_DATASETS + std::vector<bool> _informedWorkerAboutDataset; /*!< stores information about which worker already knows about the current dataset vector */ +#endif // HAVE_GROWING_DATASETS /**@}*/ /** diff --git a/inc/cApi.h b/inc/cApi.h new file mode 100644 index 0000000..fd5d254 --- /dev/null +++ b/inc/cApi.h @@ -0,0 +1,48 @@ +/********************************************************************************** + * Copyright (c) 2021 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + + +extern "C" { + + +/** + * @brief Struct used for communication options to MAiNGO via the C-API + **/ +struct OptionPair { + const char* optionName; /*!< The name of the option to be changed*/ + double optionValue; /*!< The value this option is to be set to*/ +}; + +/** + * @brief Main function with C-API for calling MAiNGO for passing the problem by string + * + * Parses the string, calls the branch-and-bound solver and returns the result. + * + * @param[in] aleString is a valid string containing the problem definition in ALE syntax + * @param[out] objectiveValue is an allocated pointer that is used to receive the objective value [allocated outside] + * @param[out] solutionPoint is the solution of the optimization [allocated outside] + * @param[in ] solutionPointLength is the allocated size of the solutionPoint array, must be greater than or equal to the number of optimization variables + * @param[out] cpuSolutionTime is the cpu time needed to solve the problem [allocated outside] + * @param[out] wallSolutionTime is the wall time needed to solve the problem [allocated outside] + * @param[out] upperBound is the upper bound of the objective function [allocated outside] + * @param[out] lowerBound is the lower bound of the objective function [allocated outside] + * @param[in] resultFileName is the name of the file that results are saved into + * @param[in] logFileName is the name of the file that logs are saved into + * @param[in] settingsFileName is the name of the file that logs are saved into + * @param[in] options are pairs of C-string and double values for options that are forwarded to MAiNGO + * @param[in] numberOptions is the length of the array passed in options + * @return returns either an integer corresponding to the MAiNGO return code in case of success, or -1 in case an unhandled exception occurred + **/ + +int solve_problem_from_ale_string_with_maingo(const char* aleString, double* objectiveValue, double* solutionPoint, unsigned solutionPointLength, double* cpuSolutionTime, double* wallSolutionTime, double* upperBound, double* lowerBound, const char* resultFileName, const char* logFileName, const char* settingsFileName, const OptionPair* options, unsigned numberOptions); +} \ No newline at end of file diff --git a/inc/constraint.h b/inc/constraint.h index ecb5785..6762e01 100644 --- a/inc/constraint.h +++ b/inc/constraint.h @@ -198,7 +198,7 @@ struct Constraint { } } - Constraint(const Constraint&) = default; /*!< Use default copy constructor */ + Constraint(const Constraint&) = default; /*!< Use default copy constructor */ Constraint& operator=(const Constraint& constraintIn) = default; /*!< Use default copy constructor */ std::string name; /*!< Name of the constraint */ diff --git a/inc/decayingProbability.h b/inc/decayingProbability.h new file mode 100644 index 0000000..87a8fd6 --- /dev/null +++ b/inc/decayingProbability.h @@ -0,0 +1,30 @@ +/********************************************************************************** + * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + +#include <cmath> +#include <cstdlib> + + +namespace maingo { + + +bool +do_based_on_decaying_probability(const double decayCoefficient, const double decisionVariable) +{ + const double probabilityThreshold = std::exp(-decayCoefficient * decisionVariable); + const double randomNumber = std::rand() / ((double)RAND_MAX); + return randomNumber <= probabilityThreshold; +} + + +} // end namespace maingo \ No newline at end of file diff --git a/inc/evaluationContainer.h b/inc/evaluationContainer.h index c709ac1..1f70342 100644 --- a/inc/evaluationContainer.h +++ b/inc/evaluationContainer.h @@ -27,12 +27,12 @@ namespace maingo { */ struct ModelFunction { - ModelFunction() = default; - ~ModelFunction() = default; - ModelFunction(const ModelFunction &) = default; - ModelFunction(ModelFunction &&) = default; + ModelFunction() = default; + ~ModelFunction() = default; + ModelFunction(const ModelFunction &) = default; + ModelFunction(ModelFunction &&) = default; ModelFunction &operator=(const ModelFunction &) = default; - ModelFunction &operator=(ModelFunction &&) = default; + ModelFunction &operator=(ModelFunction &&) = default; /** * @brief Constructor with FFVar value only @@ -207,6 +207,7 @@ struct ModelFunction { */ struct EvaluationContainer { ModelFunction objective; /*!< value of objective function f(var) */ + ModelFunction objective_per_data; /*!< vector of values of objective function f(var) per data point used in MAiNGO with growing datasets (empty otherwise)*/ ModelFunction ineq; /*!< vector of residuals of inequality constraints g(var) */ ModelFunction eq; /*!< vector of residuals of equality constraints h(var) */ ModelFunction ineqRelaxationOnly; /*!< vector of residuals of inequality constraints to be used only in the relaxed problem */ @@ -220,6 +221,7 @@ struct EvaluationContainer { void clear() { objective.clear(); + objective_per_data.clear(); ineq.clear(); eq.clear(); ineqRelaxationOnly.clear(); diff --git a/inc/lbp.h b/inc/lbp.h index f2abfa1..8d68218 100644 --- a/inc/lbp.h +++ b/inc/lbp.h @@ -152,6 +152,32 @@ class LowerBoundingSolver { */ void preprocessor_check_options(const babBase::BabNode &rootNode); +#ifdef HAVE_GROWING_DATASETS + /** + * @brief Function for changing objective in dependence of a (reduced) dataset + * + * @param[in] indexDataset is the index number of the (reduced) dataset to be used + */ + void change_growing_objective(const unsigned int indexDataset); + + /** + * @brief Function for passing dataset vector and position of data points to solver + * + * @param[in] datasets is a pointer to a vector containing all available datasets + * @param[in] indexFirstData is the position of the first objective per data in MAiNGO::_DAGfunctions + */ + void pass_data_position_to_solver(const std::shared_ptr<std::vector<std::set<unsigned int>>> datasets, const unsigned int indexFirstData); + +#ifdef HAVE_MAiNGO_MPI + /** + * @brief Function for updating dataset vector and adding subgraph after adding new dataset, needed for parallel version + * + * @param[in] newDataset is the new dataset to be added + */ + void update_datasets_and_storedSubgraphs(std::set<unsigned int> &newDataset); +#endif //HAVE_MAiNGO_MPI +#endif //HAVE_GROWING_DATASETS + protected: /** * @brief Calls the proper function for computing linearization points and modifying the coefficients of the LP problem @@ -491,10 +517,9 @@ class LowerBoundingSolver { * * @param[in] lowerVarBounds is the vector of lower bounds on your variables * @param[in] upperVarBounds is the vector of upper bounds on your variables - * @param[in] holdsIncumbent tells whether the current node holds the incumbent * @return Returns LINEARIZATION_UNKNOWN, since the problem is not solved in this function */ - LINEARIZATION_RETCODE _linearize_model_at_incumbent(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds, const bool holdsIncumbent); + LINEARIZATION_RETCODE _linearize_model_at_incumbent_or_at_midpoint(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds); /** * @brief This function adds linearizations to LP with the use of an adapted version of Kelley's algorithm. @@ -612,7 +637,10 @@ class LowerBoundingSolver { * @param[in] value ist the double to be truncated * @param[in] tolerance is a given tolerance up to which the value will be truncated, e.g., 10e9 means that 9 digits after comma will be cut off */ - void _truncate_value(double &value, const double tolerance) { value = std::trunc(value * (tolerance)) / (tolerance); } + void _truncate_value(double &value, const double tolerance) + { + value = std::trunc(value * (tolerance)) / (tolerance); + } #ifdef LP__OPTIMALITY_CHECK /** @@ -652,6 +680,13 @@ class LowerBoundingSolver { */ void _print_LP(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds); + /** + * @brief Checks if a branch-and-bound node contains the current incumbent (if any). Also writes output to the logger. + * + * @param[in] node is the BabNode to be checked + */ + bool _contains_incumbent(const babBase::BabNode &node); + #endif #ifdef LP__WRITE_CHECK_FILES @@ -660,7 +695,7 @@ class LowerBoundingSolver { * * @param[in] fileName is the name of the written file */ - virtual void _write_LP_to_file(std::string &fileName); + virtual void _write_LP_to_file(const std::string &fileName); #endif /** diff --git a/inc/lbpClp.h b/inc/lbpClp.h index 2cae1ad..d72ab91 100644 --- a/inc/lbpClp.h +++ b/inc/lbpClp.h @@ -321,6 +321,11 @@ class LbpClp: public LowerBoundingSolver { */ SUBSOLVER_RETCODE _check_infeasibility(const babBase::BabNode ¤tNode); + /** + * @brief helper function that connects _check_feasibility to the logger + */ + void _print_check_feasibility(const std::shared_ptr<Logger> logger, const VERB verbosity, const std::vector<double> &solution, const std::vector<std::vector<double>> rhs, const std::string name, const double value, const unsigned i, unsigned k, const unsigned nvar); + /** * @brief Function for checking if the solution point returned by CLP solver is really feasible * @@ -349,7 +354,7 @@ class LbpClp: public LowerBoundingSolver { * * @param[in] fileName is the name of the written file */ - virtual void _write_LP_to_file(std::string &fileName); + virtual void _write_LP_to_file(const std::string &fileName); #endif private: diff --git a/inc/lbpCplex.h b/inc/lbpCplex.h index 3335a30..22d0900 100644 --- a/inc/lbpCplex.h +++ b/inc/lbpCplex.h @@ -319,6 +319,9 @@ class LbpCplex: public LowerBoundingSolver { */ SUBSOLVER_RETCODE _check_infeasibility(const babBase::BabNode ¤tNode); + + void _print_check_feasibility(const std::shared_ptr<Logger> logger, const VERB verbosity, const std::vector<double> &solution, const std::vector<std::vector<double>> rhs, const std::string name, const double value, const unsigned i, unsigned k, const unsigned nvar); + /** * @brief Function for checking if the solution point returned by CPLEX solver is really feasible * @@ -347,7 +350,7 @@ class LbpCplex: public LowerBoundingSolver { * * @param[in] fileName is the name of the written file */ - virtual void _write_LP_to_file(std::string &fileName); + virtual void _write_LP_to_file(const std::string &fileName); #endif private: diff --git a/inc/lbpDagObj.h b/inc/lbpDagObj.h index 70a4a78..c01a2b7 100644 --- a/inc/lbpDagObj.h +++ b/inc/lbpDagObj.h @@ -67,6 +67,16 @@ struct DagObj { std::vector<mc::FFSubgraph> subgraphEqRelaxationOnly; /*!< subgraph holding the list of operations of the relaxation only equalities */ std::vector<mc::FFSubgraph> subgraphIneqSquash; /*!< subgraph holding the list of operations of the squash inequalities */ +#ifdef HAVE_GROWING_DATASETS + //Additional variables for MAiNGO with growing datasets + unsigned int indexFirstData; /*!< position of the first objective per data in MAiNGO::_DAGfunctions */ + std::shared_ptr<std::vector<std::set<unsigned int>>> datasets; /*!< pointer to a vector containing all available datasets */ + std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraph; /*!< vector containing pointers to subgraph of all previously used datasets. Note: position in this vector = index of dataset */ + std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraphObj; /*!< vector containing pointers to subgraphObj of all previously used datasets. Note: position in this vector = index of dataset */ + std::vector<std::vector<mc::FFVar>> storedFunctions; /*!< vector containing functions of all previously used datasets. Note: position in this vector = index of dataset */ + std::vector<std::vector<mc::FFVar>> storedFunctionsObj; /*!< vector containing functionsObj of all previously used datasets. Note: position in this vector = index of dataset */ +#endif // HAVE_GROWING_DATASETS + MC infinityMC; /*!< dummy MC object holding all zeros and infinity */ double validIntervalLowerBound; /*!< variable holding a valid interval lower bound of the objective function */ @@ -98,6 +108,22 @@ struct DagObj { * @brief Function for additional stuff neeeded when using vector McCormick */ void initialize_vMcCormick(); + +#ifdef HAVE_GROWING_DATASETS + /** + * @brief Function for adding subgraph corresponding to a new reduced dataset + * + * @param[in] indexDataset is the index number of the reduced dataset to be used + */ + void add_subgraph_for_new_dataset(const unsigned int indexDataset); + + /** + * @brief Function for changing objective in dependence of a (reduced) dataset + * + * @param[in] indexDataset is the index number of the (reduced) dataset to be used + */ + void change_growing_objective(const unsigned int indexDataset); +#endif //HAVE_GROWING_DATASETS }; diff --git a/inc/lbpInterval.h b/inc/lbpInterval.h index 623cf7e..7dc1366 100644 --- a/inc/lbpInterval.h +++ b/inc/lbpInterval.h @@ -201,7 +201,7 @@ class LbpInterval: public LowerBoundingSolver { * * @param[in] fileName is the name of the written file */ - void _write_LP_to_file(std::string &fileName); + void _write_LP_to_file(const std::string &fileName); #endif private: diff --git a/inc/logger.h b/inc/logger.h index 632b4b7..1764f3d 100644 --- a/inc/logger.h +++ b/inc/logger.h @@ -17,6 +17,7 @@ #include <iostream> #include <map> +#include <memory> #include <queue> #include <vector> @@ -24,135 +25,80 @@ namespace maingo { -/** -* @enum SETTING_NAMES -* @brief Enum for representing the setting names and making the tracking of set settings easier -*/ -enum SETTING_NAMES { - // The first name has to be 1 and the names have to be increasing (in numbering) - EPSILONA = 1, /*!< absolute optimality tolerance */ - EPSILONR, /*!< relative optimality tolerance */ - DELTAINEQ, /*!< absolute inequality tolerance */ - DELTAEQ, /*!< absolute equality tolerance */ - RELNODETOL, /*!< relative minimal node size tolerance */ - INFTY, /*!< infinity value */ - TARGETLOWERBOUND, /*!< target value for LBD at which MAiNGO terminates */ - TARGETUPPERBOUND, /*!< target value for UBD at which MAiNGO terminates */ - BAB_MAXNODES, /*!< max number of nodes */ - BAB_MAXITERATIONS, /*!< max number of iterations */ - MAXTIME, /*!< max time */ - CONFIRMTERMINATION, /*!< whether to confirm termination */ - TERMINATEONFEASIBLEPOINT, /*!< whether to terminate once a feasible point was found */ - PRE_MAXLOCALSEARCHES, /*!< max local searches in pre-processing */ - PRE_OBBTMAXROUNDS, /*!< max number of obbt rounds in pre-processing */ - PRE_PUREMULTISTART, /*!< whether to do a pure multistart */ - BAB_NODESELECTION, /*!< node selection heuristic */ - BAB_BRANCHVARIABLE, /*!< branching variable heuristic */ - BAB_ALWAYSSOLVEOBBT, /*!< whether to always solve obbt */ - BAB_PROBING, /*!< whether to do probing */ - BAB_DBBT, /*!< whether to do dbbt */ - BAB_CONSTRAINTPROPAGATION, /*!< whether to do constraint propagation */ - LBP_SOLVER, /*!< lower bounding solver */ - LBP_LINPOINTS, /*!< linearization point strategy */ - LBP_SUBGRADIENTINTERVALS, /*!< whether to use subgradient intervals heuristic */ - LBP_OBBTMINIMPROVEMENT, /*!< minimal obbt improvement */ - LBP_ACTIVATEMORESCALING, /*!< number of consecutive iterations with no lbd imrovement needed to activate more aggressive scaling in LP solver (e.g., CPLEX) */ - LBP_ADDAUXILIARYVARS, /*!< whether to add auxiliary variables for common factors in the lower bounding problem */ - LBP_MINFACTORSFORAUX, /*!< minimum number of common factors to add an auxiliary variable */ - LBP_MAXNUMBEROFADDEDFACTORS, /*!< maximum number of added factor as auxiliaries */ - MC_MVCOMPUSE, /*!< whether to use multivariate mccormick */ - MC_MVCOMPTOL, /*!< mccormick computational tolerance */ - MC_ENVELTOL, /*!< mccormick envelope computation tolerance */ - UBP_SOLVERPRE, /*!< upper bounding solver in pre-processing */ - UBP_MAXSTEPSPRE, /*!< max steps for upper bounding solver in pre-processing */ - UBP_MAXTIMEPRE, /*!< max time for upper bounding solver in pre-processing */ - UBP_SOLVERBAB, /*!< upper bounding solver in B&B */ - UBP_MAXSTEPSBAB, /*!< max steps for upper bounding solver in B&B */ - UBP_MAXTIMEBAB, /*!< max time for upper bounding solver in B&B */ - UBP_IGNORENODEBOUNDS, /*!< whether to ignore bounds in upper bounding */ - EC_NPOINTS, /*!< number of points on the Pareto front in epsilon-constraint method */ - LBP_VERBOSITY, /*!< lower bounding verbosity */ - UBP_VERBOSITY, /*!< upper bounding verbosity */ - BAB_VERBOSITY, /*!< b&b verbosity */ - BAB_PRINTFREQ, /*!< frequency of printed b&b iterations */ - BAB_LOGFREQ, /*!< frequency of written b&b iterations */ - OUTSTREAMVERBOSITY, /*!< verbosity for outstream */ - WRITECSV, /*!< whether to write csv */ - WRITEJSON, /*!< whether to write json */ - writeResultFile, /*!< whether to write an additional log file containing non-standard information about the problem */ - WRITETOLOGSEC, /*!< write to log/csv every x seconds */ - PRE_PRINTEVERYLOCALSEARCH, /*!< whether to print every local search */ - WRITETOOTHERLANGUAGE, /*!< write a file in a different modeling language */ - UNKNOWN_SETTING = 500 /*!< unknown setting */ -}; - /** * @class Logger * @brief This class contains all logging and output information * -* This class is used by the MAiNGO, BranchAndBound, LowerBoundingSolver and UpperBoundingSolver classes for a central and proper storing of output and logging information. +* This class is used for a central and proper storing of output and logging information. */ class Logger { + public: /** - * @brief Default constructor. + * @brief Constructor. + * @param[in] settings shared pointer to _maingoSettings */ - Logger(){}; - /** - * @brief Default copy constructor. - */ - Logger(const Logger&) = default; + Logger(const std::shared_ptr<Settings> settings): + _settings(settings) {} /** - * @brief Default copy assignment. + * @brief The main function used for printing a given message and storing it in log and/or csv + * + * @param[in] message is the message to be printed or written + * @param[in] settingType sets if the LBP, UBP or BAB verbosity should be used + * @param[in] verbosityNeeded is the least verbosity needed for the message to be printed/written */ - Logger& operator=(const Logger&) = default; + void print_message(const std::string& message, const VERB verbosityNeeded, const SETTING_NAMES settingType); /** - * @brief Default destructor. + * @brief The main function used for printing a given message and storing it in log and/or csv + * + * @param[in] message is the message to be printed or written + * @param[in] settingType sets if the LBP, UBP or BAB verbosity should be used + * @param[in] verbosityNeeded is the least verbosity needed for the message to be printed/written + * @param[in] optSettingType1 is an optinal argument that can be used if the logger needs to choose the maximum verbosity of two or three verbosity types */ - ~Logger() {} + void print_message(const std::string& message, const VERB verbosityNeeded, const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1); /** * @brief The main function used for printing a given message and storing it in log and/or csv * * @param[in] message is the message to be printed or written - * @param[in] verbosityGiven is the verbosity given by, e.g., settings + * @param[in] settingType sets if the LBP, UBP or BAB verbosity should be used * @param[in] verbosityNeeded is the least verbosity needed for the message to be printed/written - * @param[in] givenOutstreamVerbosity tells whether to print to _outStream and/or write files + * @param[in] optSettingType1 is an optinal argument that can be used if the logger needs to choose the maximum verbosity of two or three verbosity types + * @param[in] optSettingType2 is an optinal argument that can be used if the logger needs to choose the maximum verbosity of three verbosity types */ - void print_message(const std::string& message, const VERB verbosityGiven, const VERB verbosityNeeded, const LOGGING_DESTINATION givenOutstreamVerbosity); + void print_message(const std::string& message, const VERB verbosityNeeded, const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1, const SETTING_NAMES optSettingType2); /** - * @brief The main function used for printing a given message and storing it in log and/or csv + * @brief Function used for printing a given message only to the stream specified in the Logger * - * @param[in] message is the message to be printed or written - * @param[in] givenOutstreamVerbosity tells whether to print to _outStream and/or write files + * @param[in] message is the message to be printed */ - void print_message_to_stream_only(const std::string& message, const LOGGING_DESTINATION givenOutstreamVerbosity); + void print_message_to_stream_only(const std::string& message); /** - * @brief Sets output stream. + * @brief Sets output stream * * @param[in] outputStream is the new output stream to be used by MAiNGO. */ - void set_output_stream(std::ostream* const outputStream); + void set_output_stream(std::ostream* const outputStream) { _outStream = outputStream; } /** * @brief Function used for creating the log file * - * @param[in] givenOutstreamVerbosity tells whether to print to _outStream and/or write files */ - void create_log_file(const LOGGING_DESTINATION givenOutstreamVerbosity); + void create_log_file() const; /** * @brief Function used for creating the csv file with information on the B&B iterations * * @param[in] writeCsv says whether to write the csv file */ - void create_iterations_csv_file(const bool writeCsv); + void create_iterations_csv_file(const bool writeCsv) const; /** * @brief Function used for writing all lines stored in queue babLine to log @@ -185,11 +131,42 @@ class Logger { /** * @brief Function for printing and writing user-set settings * - * @param[in] verbosityGiven is the verbosity given by, e.g., settings * @param[in] verbosityNeeded is the least verbosity needed for the message to be printed/written - * @param[in] givenOutstreamVerbosity tells whether to print to _outStream and/or write files + * @param[in] settingType sets if the LBP, UBP or BAB verbosity should be used + */ + void print_settings(const VERB verbosityNeeded, const SETTING_NAMES settingType); + + /** + * @brief Function for printing and writing user-set settings + * + * @param[in] verbosityNeeded is the least verbosity needed for the message to be printed/written + * @param[in] settingType sets if the LBP, UBP or BAB verbosity should be used + * @param[in] optSettingType1 is an optinal argument that can be used if the logger needs to choose the maximum verbosity of two or three verbosity types + */ + void print_settings(const VERB verbosityNeeded, const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1); + + /** + * @brief Function for printing and writing user-set settings + * + * @param[in] verbosityNeeded is the least verbosity needed for the message to be printed/written + * @param[in] settingType sets if the LBP, UBP or BAB verbosity should be used + * @param[in] optSettingType1 is an optinal argument that can be used if the logger needs to choose the maximum verbosity of two or three verbosity types + * @param[in] optSettingType2 is an optinal argument that can be used if the logger needs to choose the maximum verbosity of three verbosity types */ - void print_settings(const VERB verbosityGiven, const VERB verbosityNeeded, const LOGGING_DESTINATION givenOutstreamVerbosity); + void print_settings(const VERB verbosityNeeded, const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1, const SETTING_NAMES optSettingType2); + + /** + * @brief Prints any vector<double> in a standardized form + * + * @param[in] length is the length of the arrary if it is printed as a whole or the index of the last double that is written if not the whole vector is printed + * @param[in] vec is the vector to be printed or written + * @param[in] preString is a string to be printed/written before vec + * @param[in] verbosityNeeded is the least verbosity needed for the message to be printed/written + * @param[in] settingType sets if the LBP, UBP or BAB verbosity should be used + */ + + void print_vector(const unsigned length, const std::vector<double>& vec, const std::string& preString, const VERB verbosityNeeded, const SETTING_NAMES settingType); + /** * @brief Clears all logging information @@ -207,7 +184,42 @@ class Logger { bool reachedMinNodeSize; /*!< bool for saving information if minimum node size has been reached within B&B */ /**@}*/ + private: + /** + * @brief Gives the max verbosity from one to three three diffrent settingTypes + * + * @param[in] settingType sets if the LBP, UBP or BAB verbosity should be used + */ + VERB _get_verb(const SETTING_NAMES settingType) const; + + /** + * @brief Gives the max verbosity from one to three three diffrent settingTypes + * + * @param[in] settingType sets if the LBP, UBP or BAB verbosity should be used + * @param[in] optSettingType1 is an optinal argument that can be used if the logger needs to choose the maximum verbosity of two or three verbosity types + */ + VERB _get_max_verb(const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1) const; + + /** + * @brief Gives the max verbosity from one to three three diffrent settingTypes + * + * @param[in] settingType sets if the LBP, UBP or BAB verbosity should be used + * @param[in] optSettingType1 is an optinal argument that can be used if the logger needs to choose the maximum verbosity of two or three verbosity types + * @param[in] optSettingType2 is an optinal argument that can be used if the logger needs to choose the maximum verbosity of three verbosity types + */ + VERB _get_max_verb(const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1, const SETTING_NAMES optSettingType2) const; + + + /** + * @brief Helper function for printing a given message and storing it in log and/or csv + * + * @param[in] message is the message to be printed or written + * @param[in] verbosityNeeded is the least verbosity needed for the message to be printed/written + * @param[in] verbosityGiven is the verbosity settings that should be considered to decide whether the message should be printed + */ + void _print_message_if_verbosity_exceeds_needed(const std::string& message, const VERB verbosityNeeded, const VERB verbosityGiven); + /** * @name Private variable storing the output */ @@ -215,9 +227,9 @@ class Logger { std::ostream* _outStream = &std::cout; /*!< default MAiNGO output stream is set to std::cout */ unsigned int _nSettingFiles = 0; /*!< number of setting files from which the user has read, default is set to 0 */ std::map<int, std::string> _userSetSettings; /*!< map holding settings set by the user */ + std::shared_ptr<Settings> _settings; /*!< shared pointer to _maingoSettings*/ /**@}*/ - -}; // end of class Logger +}; } // end namespace maingo \ No newline at end of file diff --git a/inc/mpiUtilities.h b/inc/mpiUtilities.h index 421069b..9391a19 100644 --- a/inc/mpiUtilities.h +++ b/inc/mpiUtilities.h @@ -70,6 +70,8 @@ enum COMMUNICATION_TAG { TAG_NODE_REQUEST, /*!< Worker requests a new node */ TAG_NEW_NODE_NO_INCUMBENT, /*!< Manager will send new node, but no new incumbent */ TAG_NEW_NODE_NEW_INCUMBENT, /*!< Manager will send new node and a new incumbent */ + TAG_NEW_NODE_NO_DATASET, /*!< Manager will send new node, but no new dataset */ + TAG_NEW_NODE_NEW_DATASET, /*!< Manager will send new node and a new dataset */ TAG_NEW_NODE_UBD, /*!< New global UBD value*/ TAG_SOLVED_NODE_STATUS_NORMAL, /*!< The solved node is normal */ TAG_SOLVED_NODE_STATUS_CONVERGED, /*!< The solved node converged */ @@ -83,6 +85,9 @@ enum COMMUNICATION_TAG { TAG_NODE_UPPER_BOUNDS, /*!< Variable upper bounds of the node being sent */ TAG_NODE_HAS_INCUMBENT, /*!< Whether the node being sent holds the incumbent */ TAG_NODE_DEPTH, /*!< Depth of the node being sent */ + TAG_NODE_IDXDATA, /*!< Index of dataset used in node being sent */ + TAG_NODE_AUGMENT_DATA, /*!< Whether dataset has been augmented to get the dataset of the node being sent */ + TAG_NODE_DATAPOINT, /*!< Data point of a new dataset being sent */ TAG_WORKER_FINISHED, /*!< Worker finished BAB-loop */ TAG_MS_STOP_SOLVING, /*!< Multistart: worker should stop searching for local solutions */ TAG_MS_NEW_POINT, /*!< Multistart: new initial point for local search */ @@ -104,16 +109,18 @@ send_babnode(const babBase::BabNode &node, const int dest) int id = node.get_ID(); std::vector<double> lb = node.get_lower_bounds(); std::vector<double> ub = node.get_upper_bounds(); - int hI = node.holds_incumbent() ? 1 : 0; double pruningScore = node.get_pruning_score(); int depth = node.get_depth(); + int idData = node.get_index_dataset(); + int aD = node.get_augment_data() ? 1 : 0; // Send all information MPI_Ssend(&id, 1, MPI_INT, dest, TAG_NODE_ID, MPI_COMM_WORLD); MPI_Ssend(&pruningScore, 1, MPI_DOUBLE, dest, TAG_NODE_PRUNING_SCORE, MPI_COMM_WORLD); MPI_Ssend(lb.data(), lb.size(), MPI_DOUBLE, dest, TAG_NODE_LOWER_BOUNDS, MPI_COMM_WORLD); MPI_Ssend(ub.data(), lb.size(), MPI_DOUBLE, dest, TAG_NODE_UPPER_BOUNDS, MPI_COMM_WORLD); - MPI_Ssend(&hI, 1, MPI_INT, dest, TAG_NODE_HAS_INCUMBENT, MPI_COMM_WORLD); MPI_Ssend(&depth, 1, MPI_INT, dest, TAG_NODE_DEPTH, MPI_COMM_WORLD); + MPI_Ssend(&idData, 1, MPI_INT, dest, TAG_NODE_IDXDATA, MPI_COMM_WORLD); + MPI_Ssend(&aD, 1, MPI_INT, dest, TAG_NODE_AUGMENT_DATA, MPI_COMM_WORLD); } /** @@ -130,19 +137,20 @@ recv_babnode(babBase::BabNode &node, const int source, const unsigned nvar) int id; std::vector<double> lb(nvar, 0); std::vector<double> ub(nvar, 0); - int hI; double pruningScore; int depth; - + int idData; + int aD; MPI_Recv(&id, 1, MPI_INT, source, TAG_NODE_ID, MPI_COMM_WORLD, MPI_STATUS_IGNORE); MPI_Recv(&pruningScore, 1, MPI_DOUBLE, source, TAG_NODE_PRUNING_SCORE, MPI_COMM_WORLD, MPI_STATUS_IGNORE); MPI_Recv(lb.data(), nvar, MPI_DOUBLE, source, TAG_NODE_LOWER_BOUNDS, MPI_COMM_WORLD, MPI_STATUS_IGNORE); MPI_Recv(ub.data(), nvar, MPI_DOUBLE, source, TAG_NODE_UPPER_BOUNDS, MPI_COMM_WORLD, MPI_STATUS_IGNORE); - MPI_Recv(&hI, 1, MPI_INT, source, TAG_NODE_HAS_INCUMBENT, MPI_COMM_WORLD, MPI_STATUS_IGNORE); MPI_Recv(&depth, 1, MPI_INT, source, TAG_NODE_DEPTH, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + MPI_Recv(&idData, 1, MPI_INT, source, TAG_NODE_IDXDATA, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + MPI_Recv(&aD, 1, MPI_INT, source, TAG_NODE_AUGMENT_DATA, MPI_COMM_WORLD, MPI_STATUS_IGNORE); - node = babBase::BabNode(pruningScore, lb, ub, id, depth, hI == 1); + node = babBase::BabNode(pruningScore, lb, ub, idData, id, depth, aD == 1); } /** @@ -225,4 +233,4 @@ struct WorkerNodeComparator { } // end namespace maingo -#endif \ No newline at end of file +#endif diff --git a/inc/nrtlSubroutines.h b/inc/nrtlSubroutines.h new file mode 100644 index 0000000..f42a832 --- /dev/null +++ b/inc/nrtlSubroutines.h @@ -0,0 +1,122 @@ +#pragma once + +#include "MAiNGOException.h" +#include "symbol_table.hpp" + +#include "util/evaluator.hpp" + +#include "ffunc.hpp" + +namespace maingo { + + +using namespace ale; +using namespace ale::util; +using Var = mc::FFVar; +using VarType = std::vector<std::vector<Var>>; +using ParaType = std::vector<std::vector<double>>; + +VarType +nrtl_subroutine_tau(Var T, ParaType a, ParaType b, ParaType e, ParaType f) +{ + VarType tau(a.size(), std::vector<Var>(a[0].size(), 0.0)); + int it = a.size(); + int jt = a[0].size(); + for (int i = 0; i < it; i++) { + for (int j = 0; j < jt; j++) { + if (i != j) { + tau[i][j] = mc::nrtl_tau(T, a[i][j], b[i][j], e[i][j], f[i][j]); + } + } + } + return tau; +} + +Var +nrtl_subroutine_tau(Var T, double a, double b, double e, double f) +{ + return mc::nrtl_tau(T, a, b, e, f); +} + +Var +nrtl_subroutine_dtau(Var T, double b, double e, double f) +{ + return mc::nrtl_dtau(T, b, e, f); +} + +VarType +nrtl_subroutine_G(Var T, VarType tau, ParaType a, ParaType b, ParaType c, ParaType d, ParaType e, ParaType f) +{ + VarType G(tau.size(), std::vector<Var>(tau[0].size(), 1.0)); + int it = tau.size(); + int jt = tau[0].size(); + for (int i = 0; i < it; i++) { + for (int j = 0; j < jt; j++) { + if (i != j) { + double alpha = c[i][j] + d[i][j] * (T.num().val() - 273.15); + G[i][j] = mc::nrtl_G(T, a[i][j], b[i][j], e[i][j], f[i][j], alpha); + } + } + } + return G; +} + +Var +nrtl_subroutine_G(Var T, double a, double b, double e, double f, double alpha) +{ + return mc::nrtl_G(T, a, b, e, f, alpha); +} + +VarType +nrtl_subroutine_Gtau(Var T, VarType tau, VarType G, ParaType a, ParaType b, ParaType c, ParaType d, ParaType e, ParaType f) +{ + VarType Gtau(tau.size(), std::vector<Var>(tau[0].size(), 0.0)); + int it = tau.size(); + int jt = tau[0].size(); + for (int i = 0; i < it; i++) { + for (int j = 0; j < jt; j++) { + if (i != j) { + double alpha = c[i][j] + d[i][j] * (T.num().val() - 273.15); + Gtau[i][j] = mc::nrtl_Gtau(T, a[i][j], b[i][j], e[i][j], f[i][j], alpha); + } + } + } + return Gtau; +} + +Var +nrtl_subroutine_Gtau(Var T, double a, double b, double e, double f, double alpha) +{ + return mc::nrtl_Gtau(T, a, b, e, f, alpha); +} + +VarType +nrtl_subroutine_Gdtau(Var T, VarType G, ParaType a, ParaType b, ParaType c, ParaType d, ParaType e, ParaType f) +{ + VarType Gdtau(G.size(), std::vector<Var>(G[0].size(), 0.0)); + int it = G.size(); + int jt = G[0].size(); + for (int i = 0; i < it; i++) { + for (int j = 0; j < jt; j++) { + if (i != j) { + double alpha = c[i][j] + d[i][j] * (T.num().val() - 273.15); + Gdtau[i][j] = mc::nrtl_Gdtau(T, a[i][j], b[i][j], e[i][j], f[i][j], alpha); + } + } + } + return Gdtau; +} + +Var +nrtl_subroutine_Gdtau(Var T, double a, double b, double e, double f, double alpha) +{ + return mc::nrtl_Gdtau(T, a, b, e, f, alpha); +} + +Var +nrtl_subroutine_dGtau(Var T, double a, double b, double e, double f, double alpha) +{ + return mc::nrtl_dGtau(T, a, b, e, f, alpha); +} + +} // namespace maingo \ No newline at end of file diff --git a/inc/pointIsWithinNodeBounds.h b/inc/pointIsWithinNodeBounds.h new file mode 100644 index 0000000..72d141e --- /dev/null +++ b/inc/pointIsWithinNodeBounds.h @@ -0,0 +1,45 @@ +/********************************************************************************** + * Copyright (c) 2023 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + +#include "babNode.h" + +#include <vector> + + +namespace maingo { + + +/** + * @brief Checks if a given point lies wihtin the variable bounds of a branch-and-bound node. + * Integrality of variables is not considered (e.g., if a binary variable has value 0.5 at the point, + * this is considered to be within the node bounds). + * + * @param[in] point is the point to be checked + * @param[in] lowerBounds is the vector of lower variable bounds of the node + * @param[in] upperBounds is the vector of upper variable bounds of the node + */ +bool point_is_within_node_bounds(const std::vector<double>& point, const std::vector<double>& lowerBounds, const std::vector<double>& upperBounds); + + +/** + * @brief Checks if a given point lies wihtin the variable bounds of a branch-and-bound node. + * Integrality of variables is not considered (e.g., if a binary variable has value 0.5 at the point, + * this is considered to be within the node bounds). + * + * @param[in] point is the point to be checked + * @param[in] node is the branch-and-bound node to be checked + */ +bool point_is_within_node_bounds(const std::vector<double>& point, const babBase::BabNode& node); + + +} // end namespace maingo \ No newline at end of file diff --git a/inc/program.h b/inc/program.h index 377cf75..b30a1e5 100644 --- a/inc/program.h +++ b/inc/program.h @@ -11,6 +11,9 @@ #pragma once +// Note: need to include mpiUtilities *before* expression.hpp to avoid conflicts in preprocessor definition of BOOL (at least when using openmpi) +#include "mpiUtilities.h" + #include "expression.hpp" #include <list> @@ -22,11 +25,12 @@ namespace maingo { * @brief Container Class for ALE expressions comprising an optimization problem */ struct Program { - std::list<ale::expression<ale::real<0>>> mObjective; /*!< Objective function expression*/ - std::list<ale::expression<ale::boolean<0>>> mConstraints; /*!< Constraint expressions*/ - std::list<ale::expression<ale::boolean<0>>> mRelaxations; /*!< Relaxation-only constraint expressions*/ - std::list<ale::expression<ale::boolean<0>>> mSquashes; /*!< Squash constraint expressions*/ - std::list<ale::expression<ale::real<0>>> mOutputs; /*!< Additional output expressions*/ + std::list<ale::expression<ale::real<0>>> mObjective; /*!< Objective function expression*/ + std::list<ale::expression<ale::boolean<0>>> mObjectivePerData; /*!< Objective per data function expression for using growing datasets*/ + std::list<ale::expression<ale::boolean<0>>> mConstraints; /*!< Constraint expressions*/ + std::list<ale::expression<ale::boolean<0>>> mRelaxations; /*!< Relaxation-only constraint expressions*/ + std::list<ale::expression<ale::boolean<0>>> mSquashes; /*!< Squash constraint expressions*/ + std::list<ale::expression<ale::real<0>>> mOutputs; /*!< Additional output expressions*/ }; diff --git a/inc/programParser.h b/inc/programParser.h index badf65f..e787653 100644 --- a/inc/programParser.h +++ b/inc/programParser.h @@ -32,12 +32,13 @@ class ProgramParser: private ale::parser { using ale::parser::print_errors; private: - void parse_definitions(); /*!< Function parsing a definition block*/ - void parse_objective(Program&); /*!< Function parsing an objective block*/ - void parse_constraints(Program&); /*!< Function parsing a constraint block*/ - void parse_relaxations(Program&); /*!< Function parsing a relaxation-only constraint block*/ - void parse_squashes(Program&); /*!< Function parsing a squash constraint block*/ - void parse_outputs(Program&); /*!< Function parsing an output block*/ + void parse_definitions(); /*!< Function parsing a definition block*/ + void parse_objective(Program&); /*!< Function parsing an objective block*/ + void parse_objective_per_data(Program&); /*!< Function parsing an objective per data block for MAiNGO with growing datasets*/ + void parse_constraints(Program&); /*!< Function parsing a constraint block*/ + void parse_relaxations(Program&); /*!< Function parsing a relaxation-only constraint block*/ + void parse_squashes(Program&); /*!< Function parsing a squash constraint block*/ + void parse_outputs(Program&); /*!< Function parsing an output block*/ void recover_block(); /*!< Function to recover from an error in block syntax*/ }; diff --git a/inc/settings.h b/inc/settings.h index de343f3..df2ed3e 100644 --- a/inc/settings.h +++ b/inc/settings.h @@ -1,5 +1,5 @@ /********************************************************************************** - * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * Copyright (c) 2019-2023 Process Systems Engineering (AVT.SVT), RWTH Aachen University * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -21,6 +21,75 @@ namespace maingo { +/** +* @enum SETTING_NAMES +* @brief Enum for representing the setting names and making the tracking of set settings easier +*/ +enum SETTING_NAMES { + // The first name has to be 1 and the names have to be increasing (in numbering) + EPSILONA = 1, /*!< absolute optimality tolerance */ + EPSILONR, /*!< relative optimality tolerance */ + DELTAINEQ, /*!< absolute inequality tolerance */ + DELTAEQ, /*!< absolute equality tolerance */ + RELNODETOL, /*!< relative minimal node size tolerance */ + INFTY, /*!< infinity value */ + TARGETLOWERBOUND, /*!< target value for LBD at which MAiNGO terminates */ + TARGETUPPERBOUND, /*!< target value for UBD at which MAiNGO terminates */ + BAB_MAXNODES, /*!< max number of nodes */ + BAB_MAXITERATIONS, /*!< max number of iterations */ + MAXTIME, /*!< max time */ + CONFIRMTERMINATION, /*!< whether to confirm termination */ + TERMINATEONFEASIBLEPOINT, /*!< whether to terminate once a feasible point was found */ + PRE_MAXLOCALSEARCHES, /*!< max local searches in pre-processing */ + PRE_OBBTMAXROUNDS, /*!< max number of obbt rounds in pre-processing */ + PRE_PUREMULTISTART, /*!< whether to do a pure multistart */ + BAB_NODESELECTION, /*!< node selection heuristic */ + BAB_BRANCHVARIABLE, /*!< branching variable heuristic */ + BAB_ALWAYSSOLVEOBBT, /*!< whether to always solve obbt */ + BAB_OBBTDECAYCOEFFICIENT, /*!<parameter for depth-dependent OBBT*/ + BAB_PROBING, /*!< whether to do probing */ + BAB_DBBT, /*!< whether to do dbbt */ + BAB_CONSTRAINTPROPAGATION, /*!< whether to do constraint propagation */ + LBP_SOLVER, /*!< lower bounding solver */ + LBP_LINPOINTS, /*!< linearization point strategy */ + LBP_SUBGRADIENTINTERVALS, /*!< whether to use subgradient intervals heuristic */ + LBP_OBBTMINIMPROVEMENT, /*!< minimal obbt improvement */ + LBP_ACTIVATEMORESCALING, /*!< number of consecutive iterations with no lbd imrovement needed to activate more aggressive scaling in LP solver (e.g., CPLEX) */ + LBP_ADDAUXILIARYVARS, /*!< whether to add auxiliary variables for common factors in the lower bounding problem */ + LBP_MINFACTORSFORAUX, /*!< minimum number of common factors to add an auxiliary variable */ + LBP_MAXNUMBEROFADDEDFACTORS, /*!< maximum number of added factor as auxiliaries */ + MC_MVCOMPUSE, /*!< whether to use multivariate mccormick */ + MC_MVCOMPTOL, /*!< mccormick computational tolerance */ + MC_ENVELTOL, /*!< mccormick envelope computation tolerance */ + UBP_SOLVERPRE, /*!< upper bounding solver in pre-processing */ + UBP_MAXSTEPSPRE, /*!< max steps for upper bounding solver in pre-processing */ + UBP_MAXTIMEPRE, /*!< max time for upper bounding solver in pre-processing */ + UBP_SOLVERBAB, /*!< upper bounding solver in B&B */ + UBP_MAXSTEPSBAB, /*!< max steps for upper bounding solver in B&B */ + UBP_MAXTIMEBAB, /*!< max time for upper bounding solver in B&B */ + UBP_IGNORENODEBOUNDS, /*!< whether to ignore bounds in upper bounding */ + EC_NPOINTS, /*!< number of points on the Pareto front in epsilon-constraint method */ + LBP_VERBOSITY, /*!< lower bounding verbosity */ + UBP_VERBOSITY, /*!< upper bounding verbosity */ + BAB_VERBOSITY, /*!< b&b verbosity */ + BAB_PRINTFREQ, /*!< frequency of printed b&b iterations */ + BAB_LOGFREQ, /*!< frequency of written b&b iterations */ + OUTSTREAMVERBOSITY, /*!< verbosity for outstream */ + WRITECSV, /*!< whether to write csv */ + WRITEJSON, /*!< whether to write json */ + writeResultFile, /*!< whether to write an additional log file containing non-standard information about the problem */ + WRITETOLOGSEC, /*!< write to log/csv every x seconds */ + PRE_PRINTEVERYLOCALSEARCH, /*!< whether to print every local search */ + WRITETOOTHERLANGUAGE, /*!< write a file in a different modeling language */ + GROWING_DATASIZETOL, /*!< relative tolerance for considering dataset as full dataset */ + GROWING_DATASIZEINIT, /*!< relative size of initial dataset (= smallest reduced dataset) compared to full dataset */ + GROWING_AUGMENTRULE, /*!< choose augmentation rule */ + GROWING_AUGMENTFREQ, /*!< frequency of augmenting dataset (regarding depth of nodes) */ + GROWING_AUGMENTWEIGHT, /*!< weighting factor of heuristic lower bound, see augmentation rule SCALING */ + GROWING_AUGMENTPERCENTAGE, /*!< percentage of full dataset to be augmented */ + UNKNOWN_SETTING = 500 /*!< unknown setting */ +}; + /** * @enum VERB * @brief Enum for controlling the output level of solvers (i.e., how much should be printed on the screen and, possibly, to the log file). @@ -53,6 +122,17 @@ enum WRITING_LANGUAGE { }; +/** +* @enum AUGMENTATION_RULE +* @brief Enum for controlling the augmentation rule used in MAiNGO with growing datasets +*/ +enum AUGMENTATION_RULE { + AUG_RULE_CONST = 0, //!< (=0): Augment every growing_augmentFreq by growing_augmentPercentage data points + AUG_RULE_SCALING, //!< (=1): Augment if linear scaling of reduced LB to full LB suggests pruning + AUG_RULE_SCALCST //!< (=2): Augment if SCALING or CONST is triggered +}; + + /** * @namespace maingo::lbp * @brief namespace holding all essentials of the lower bounding solver @@ -188,10 +268,11 @@ struct Settings { * @name B&B settings - Range reduction */ /**@{*/ - bool BAB_alwaysSolveObbt = true; //!< Whether to solve OBBT (feasibility- and, once a feasible point has been found, also optimality-based) at every BaB node - bool BAB_dbbt = true; //!< Whether to do a single round of duality based bound tightening (DBBT, cf. Ryoo&Sahinidis, Comput. Chem. Eng. 19 (1995) 551). If false, no DBBT is used. If true, multipliers from CPLEX are used to tighten bounds (essentially for free). we tried additional rounds but without reasonable improvement. - bool BAB_probing = false; //!< Whether to do probing (cf. Ryoo&Sahinidis, Comput. Chem. Eng. 19 (1995) 551) at every node (can only be done if BAB_DBBT_maxrounds>=1) - bool BAB_constraintPropagation = true; //!< Whether to do constraint propagation. If false, no constraint propagation is executed. + bool BAB_alwaysSolveObbt = true; //!< Whether to solve OBBT (feasibility- and, once a feasible point has been found, also optimality-based) at every BaB node. + double BAB_obbtDecayCoefficient = 0.; //!< Parameter that influences the exponential distribution with which the probability that OBBT is preformed decays with increasing depth of the B&B tree. + bool BAB_dbbt = true; //!< Whether to do a single round of duality based bound tightening (DBBT, cf. Ryoo&Sahinidis, Comput. Chem. Eng. 19 (1995) 551). If false, no DBBT is used. If true, multipliers from CPLEX are used to tighten bounds (essentially for free). we tried additional rounds but without reasonable improvement. + bool BAB_probing = false; //!< Whether to do probing (cf. Ryoo&Sahinidis, Comput. Chem. Eng. 19 (1995) 551) at every node (can only be done if BAB_DBBT_maxrounds>=1) + bool BAB_constraintPropagation = true; //!< Whether to do constraint propagation. If false, no constraint propagation is executed. /**@}*/ /** @@ -240,7 +321,19 @@ struct Settings { /**@{*/ unsigned EC_nPoints = 10; //!< Number of points on the Pareto front to be computed in epsilon-constraint method (only available via the C++ API) /**@}*/ + + /** + * @name Settings of MAiNGO with growing datasets + */ + /**@{*/ + double growing_dataSizeTol = 0.9; //!< Dataset of this relative size to full dataset considered as full dataset. + double growing_dataSizeInit = 0.1; //!< Relative size of initial dataset (= smallest reduced dataset) compared to full dataset. + AUGMENTATION_RULE growing_augmentRule = AUG_RULE_SCALCST; //!< Rule when to augment the dataset + unsigned growing_augmentFreq = 10; //!< Augment dataset of all nodes whose depths is multiple of this frequency. + double growing_augmentWeight = 1.; //!< Weight of heuristic lower bound calculated in augmentation rule SCALING. + double growing_augmentPercentage = 0.25; //!< How many dat apoints to be added when augmenting (fraction of the size of the full data). + /**@}*/ }; -} // end namespace maingo \ No newline at end of file +} // end namespace maingo diff --git a/inc/ubp.h b/inc/ubp.h index 5ecc248..df61bde 100644 --- a/inc/ubp.h +++ b/inc/ubp.h @@ -107,6 +107,32 @@ class UpperBoundingSolver { */ SUBSOLVER_RETCODE check_feasibility(const std::vector<double> ¤tPoint, double &objectiveValue) const; +#ifdef HAVE_GROWING_DATASETS + /** + * @brief Function for changing objective in dependence of a (reduced) dataset + * + * @param[in] indexDataset is the index number of the (reduced) dataset to be used + */ + void change_growing_objective(const unsigned int indexDataset); + + /** + * @brief Function for passing position of data points to solver + * + * @param[in] datasets is a pointer to a vector containing all available datasets + * @param[in] indexFirstData is the position of the first objective per data in MAiNGO::_DAGfunctions + */ + void pass_data_position_to_solver(const std::shared_ptr<std::vector<std::set<unsigned int>>> datasets, const unsigned int indexFirstData); + +#ifdef HAVE_MAiNGO_MPI + /** + * @brief Function for updating dataset vector and adding subgraph after adding new dataset, needed for parallel version + * + * @param[in] newDataset is the new dataset to be added + */ + void update_datasets_and_storedSubgraphs(std::set<unsigned int> &newDataset); +#endif //HAVE_MAiNGO_MPI +#endif //HAVE_GROWING_DATASETS + protected: /** * @brief Function for actually solving the NLP sub-problem. This needs to be re-defined in derived classes to call specific sub-solvers diff --git a/inc/ubpDagObj.h b/inc/ubpDagObj.h index 085b00d..15a6cd4 100644 --- a/inc/ubpDagObj.h +++ b/inc/ubpDagObj.h @@ -11,6 +11,7 @@ #pragma once +#include "MAiNGOException.h" #include "logger.h" #include "settings.h" @@ -82,6 +83,15 @@ struct DagObj { mc::FFSubgraph subgraphIneqSquashIneq; /*!< subgraph holding the list of operations of the normal and squash inequalities */ mc::FFSubgraph subgraphIneqEq; /*!< subgraph holding the list of operations of the (squash) inequalities & equalities */ +#ifdef HAVE_GROWING_DATASETS + unsigned int indexFirstData; /*!< position of the first objective per data in MAiNGO::_DAGfunctions */ + std::shared_ptr<std::vector<std::set<unsigned int>>> datasets; /*!< pointer to a vector containing all available datasets */ + std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraph; /*!< vector containing pointers to subgraph of all previously used datasets. Note: position in this vector = index of dataset */ + std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraphObj; /*!< vector containing pointers to subgraphObj of all previously used datasets. Note: position in this vector = index of dataset */ + std::vector<std::vector<mc::FFVar>> storedFunctions; /*!< vector containing functions of all previously used datasets. Note: position in this vector = index of dataset */ + std::vector<std::vector<mc::FFVar>> storedFunctionsObj; /*!< vector containing functionsObj of all previously used datasets. Note: position in this vector = index of dataset */ +#endif // HAVE_GROWING_DATASETS + std::shared_ptr<Settings> maingoSettings; /*!< pointer to settings file, used for exception handling */ std::shared_ptr<Logger> logger; /*!< pointer to logger, used for exception handling */ bool warningFlag; /*!< flag indicating whether the user already got a warning triggered by an exception in mc::fadbad, used to avoid excessive output */ @@ -145,7 +155,18 @@ struct DagObj { // Get the list of operations used in the DAG // It is needed for the call of proper DAG functions +#ifndef HAVE_GROWING_DATASETS this->subgraph = this->DAG.subgraph(this->functions.size(), this->functions.data()); +#else + storedFunctions.clear(); + storedFunctions.push_back(this->functions); + + storedSubgraph.clear(); + auto pointerToSubgraph = std::make_shared<mc::FFSubgraph>(this->DAG.subgraph(this->functions.size(), this->functions.data())); + storedSubgraph.push_back(pointerToSubgraph); + this->subgraph = *storedSubgraph[0]; +#endif // HAVE_GROWING_DATASETS + // Get operations of each function in the DAG this->functionsObj.resize(nobj); this->functionsIneq.resize(nineq); @@ -182,8 +203,20 @@ struct DagObj { break; } } + // Get operations of each function - this->subgraphObj = this->DAG.subgraph(this->functionsObj.size(), this->functionsObj.data()); +#ifndef HAVE_GROWING_DATASETS + this->subgraphObj = this->DAG.subgraph(this->functionsObj.size(), this->functionsObj.data()); +#else + storedFunctionsObj.clear(); + storedFunctionsObj.push_back(this->functionsObj); + + storedSubgraphObj.clear(); + auto pointerToSubgraphObj = std::make_shared<mc::FFSubgraph>(this->DAG.subgraph(this->functionsObj.size(), this->functionsObj.data())); + storedSubgraphObj.push_back(pointerToSubgraphObj); + this->subgraphObj = *storedSubgraphObj[0]; +#endif // HAVE_GROWING_DATASETS + this->subgraphIneq = this->DAG.subgraph(this->functionsIneq.size(), this->functionsIneq.data()); this->subgraphEq = this->DAG.subgraph(this->functionsEq.size(), this->functionsEq.data()); this->subgraphIneqSquash = this->DAG.subgraph(this->functionsIneqSquash.size(), this->functionsIneqSquash.data()); @@ -215,6 +248,57 @@ struct DagObj { this->warningFlag = false; } + +#ifdef HAVE_GROWING_DATASETS + /** + * @brief Function for adding a subgraph corresponding to a new reduced dataset + * + * @param[in] indexDataset is the index number of the reduced dataset to be used + */ + void add_subgraph_for_new_dataset(const unsigned int indexDataset) + { + mc::FFVar obj = 0; + for (auto idxDataPoint : (*datasets)[indexDataset]) { + obj += resultVars[idxDataPoint + indexFirstData]; + } + functions[0] = obj; + functionsObj[0] = obj; + + // Build new subgraphs + storedFunctions.push_back(functions); + auto pointerToSubgraph = std::make_shared<mc::FFSubgraph>(DAG.subgraph(functions.size(), functions.data())); + storedSubgraph.push_back(pointerToSubgraph); + + storedFunctionsObj.push_back(functionsObj); + auto pointerToSubgraphObj = std::make_shared<mc::FFSubgraph>(DAG.subgraph(functionsObj.size(), functionsObj.data())); + storedSubgraphObj.push_back(pointerToSubgraphObj); + } + + /** + * @brief Function for changing objective in dependence of a (reduced) dataset + * + * @param[in] indexDataset is the index number of the (reduced) dataset to be used + */ + void change_growing_objective(const unsigned int indexDataset) + { + // Build subgraphs if necessary + if (indexDataset == storedSubgraphObj.size()) { + add_subgraph_for_new_dataset(indexDataset); + } + else if (indexDataset > storedSubgraphObj.size()) { + std::ostringstream errmsg; + errmsg << " Error in UpperBoundingSolver - change of objective: subgraph for dataset with index " << indexDataset - 1 << " missing. " << std::endl; + throw MAiNGOException(errmsg.str()); + } + + // Update subgraphs + subgraph = *storedSubgraph[indexDataset]; + functions = storedFunctions[indexDataset]; + + subgraphObj = *storedSubgraphObj[indexDataset]; + functionsObj = storedFunctionsObj[indexDataset]; + } +#endif //HAVE_GROWING_DATASETS }; diff --git a/inc/ubpLazyQuadExpr.h b/inc/ubpLazyQuadExpr.h new file mode 100644 index 0000000..36fb29a --- /dev/null +++ b/inc/ubpLazyQuadExpr.h @@ -0,0 +1,1266 @@ +/********************************************************************************** + * Copyright (c) 2021 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + +#include "MAiNGOException.h" + +#include "mcop.hpp" + +#include <cassert> +#include <exception> +#include <functional> +#include <iostream> +#include <limits> +#include <map> +#include <memory> +#include <sstream> +#include <vector> + + +///Overview: The goal of this module is to enable memory efficient assembly of sparse quadratic and linear expressions that are propagated through an MCPP DAG. +/// An linear expression is for example (c+a0x0+a1x1...+anxn). Often only a few coefficients ai are not zero. +/// The class LinExpr respresents a linear expression by only saving c and values and indices of nonzero coefficients ai. +/// +/// Multiplying two linear expressions yields a quadratic expression. +/// Quadratic expressions can be represented by +/// (1): (c0+ax)*(c1+bx)=x.T*(a.T*b)*x + (c1*a+c2*b)*x + c0*c1 =x.T*Q*x+d*x+e +/// Avoiding a dense representation of the matrix Q saves memory in most cases. This is archieved with the class SparseMatrix. +/// +/// The struct QuadExpr represents a full quadratic expression by combining an LinExpr part (representing d and e) and a SparseMatrix reprenting Q. +/// Higher orders (e.g. cubic ) are not supported and result in an exception. +/// +/// Because the linear expressions are propagated through an MCPP DAG, every intermediate expression will normally be saved in the DAG with all coefficients. +/// This is useful for interval propagation, but here we are only interested in the final values. +/// The DAG is only used to enable an uniform model definition API. +/// +/// To avoid the excessive memory usage, we evaluate the DAG with LazyQuadExpr. +/// This class does not save the results of the computation, but only the expression graph(recipe and original LinExpr objects needed to calculate the result), i.e., supports lazy evaluation. +/// The computation of the results can be triggered by calling an eval function. +/// The class also makes sure that no cubic or quartic expressions are formed. +/// +/// The class LazyQuadExprTreeNode is the building block for the expression tree. If initialized with an LinExpr, it saves it to the heap and creates an shared pointer to it. Such LazyExprTreeNodes are leafs of the expression tree. +/// Nodes of higher level represent intermediate expressions and only contain the last arithmetic operation and shared pointers to the operands. +/// Example: (a+b)*c-> a,b,c are saved on the heap. d=a+b (saves PLUS and location of a and b) e=d*c(saves TIMES and location of d and e). +/// +/// The reason for splitting LazyQuadExprTreeNode and LazyQuadExpr is that we do not want to call make_shared all the time, but want to make sure all LinExpr are saved only once. +/// Also and operator overloading is easier this way. + + +namespace maingo { +namespace ubp { + + +/** + * @struct LinExpr + * @brief A simple sparse vector class with an additional constant field. Represents c+a0*x0+a1*x1... where nonzero a0...an and c are saved. + * This struct is used to avoid the need of propagating the IloExpr object resulting in HUGE RAM usage. + * + * The nonzero values and indices are stored in ascending index order. + * Elementwise binary arithetic operations are supported, which skip elements that are zero in both operands. + * Elements can be efficently be appended. + */ +class LinExpr { + public: + /** + * @brief Default constructor, LinExpr only contains a constant term + */ + LinExpr(double scalar = 0.0): + _constant(scalar) + { + } + /** @brief Returns true if expression only contains a constant and no linear term*/ + bool is_scalar() const + { + return _values.empty() && _ids.empty(); + } + /** @brief Getter for the constant term of the linear expression*/ + double& constant() + { + return _constant; + } + /** @brief Getter for the constant term of the linear expression*/ + double constant() const + { + return _constant; + } + /** + * @brief Getter for elments in the sparse vector, corresponding to the linear part of the expression + * + * @param[in] index is the index of the value requested. Function returns a_index. + */ + double get_value(unsigned index) const + { + for (unsigned i = 0; i < _ids.size(); i++) { + if (_ids[i] == index) { + return _values[i]; + } + if (_ids[i] > index) //not found + { + break; + } + } + return 0; + } + /** + * @brief Setter for elments in the sparse vector, corresponding to the linear part of the expression + * + * @param[in] index is the index of the value to change. + * @param[in] value is the value that should be saved + */ + void set_value(unsigned index, double value) + { + //insert at the end if we know this is were the value belongs + if (_ids.empty() || index > _ids.back()) { + _ids.push_back(index); + _values.push_back(value); + } + else { //look if index is already saved + //if yes, replace value + for (unsigned i = 0; i < _ids.size(); i++) { + if (_ids[i] == index) { + _values[i] = value; + } + if (_ids[i] > index) //not found, since _ids is sorted insert to keep _ids sorted + { + std::vector<unsigned>::const_iterator it = _ids.begin() + i; + _ids.insert(it, value); + std::vector<double>::const_iterator it2 = _values.begin() + i; + _values.insert(it2, value); + break; + } + } + } + } + /** @brief Getter for the ids of nonzero entries*/ + const std::vector<unsigned>& get_non_zero_ids() const + { + return _ids; + } + /** @brief Getter for the ids of nonzero entries*/ + const std::vector<double>& get_non_zero_values() const + { + return _values; + } + /** @brief Function to print content of sparse linear expression*/ + std::string print() const + { + std::stringstream ss; + + for (unsigned i = 0; i < _ids.size(); i++) { + ss << "(" << _ids[i] << "):" << _values[i] << ","; + } + ss << "(const):" << _constant << std::endl; + return ss.str(); + } + /** + * @brief Helper function: apply a function f(unsigned id,double az,double bz) to all coefficients of a and b that are related to an index that occurs in either a or b. + * + * For example if we want to calculate ci=ai+bi, we must be careful, because a and b are saved as sparse vectors, i.e. the first entry in a.values does not correspond to a_0, but to a_{a._indices[0]}. + * Assume that we want have a=(0):1,(3):4 and b=(3):1,(4):2, then this f will be called with f(0,1,0), f(3,4,1) and f(4,0,2), skipping over indices that are neither in a or b. + * This function is an efficient alternative to writing both sparse vectors to full vectors apply a function that acts elementswise on both vectors and create a sparse vector from that. + */ + //typedef void(*forallOperation)(int/*id*/,double&/*a.z*/,double&/*b.z*/);// necessary signature + template <typename forallOperation> + static void for_all_coeffs(forallOperation f, const LinExpr& a, const LinExpr& b) + { + int i1 = 0; + int i2 = 0; + const std::vector<unsigned>& a_ids = a._ids; + const std::vector<unsigned>& b_ids = b._ids; + //go from small id to large id through both id vectors + //which are both sorted in ascending order + //replace missing z[i] with zero if one of the vectors does not contain it + while (i1 != a_ids.size() || i2 != b_ids.size()) { + if (i1 == a_ids.size()) //no more in a + { + f(b._ids[i2], 0, b._values[i2]); + i2++; + } + else if (i2 == b_ids.size()) //no more in b + { + f(a._ids[i1], a._values[i1], 0); + i1++; + } + else if ((a_ids[i1] < b_ids[i2])) // a.ids[i1] has the next ID, b does not have a coefficient for that id. + { + f(a._ids[i1], a._values[i1], 0); + i1++; + } + else if ((a_ids[i1] > b_ids[i2])) // b.ids[i2] has the next ID, a does not have a coefficient for that id + { + f(b._ids[i2], 0, b._values[i2]); + i2++; + } + else //(a._ids[i1]==b._ids[i2]) // current ID is contained in both id vectors + { + f(a._ids[i1], a._values[i1], b._values[i2]); + i1++; + i2++; + } + } + } + + /** + * @brief Helper function: apply a function + to all coefficients of a and b that are related to an index that occurs in either a or b. + * + * Added to decrease overhead because plus is called frequently + */ + + inline static void for_all_coeffs_plus(LinExpr& out, const LinExpr& a, + const LinExpr& b) + { + int i1 = 0; + int i2 = 0; + // go from small id to large id through both id vectors + // which are both sorted in ascending order + // replace missing z[i] with zero if one of the vectors does not contain it + while (i1 != a._ids.size() || i2 != b._ids.size()) { + if (i1 == a._ids.size()) { // no more in a + out.set_value(b._ids[i2], 0 + b._values[i2]); + i2++; + } + else if (i2 == b._ids.size()) { // no more in b + + out.set_value(a._ids[i1], a._values[i1] + 0); + i1++; + } + else if ((a._ids[i1] < b._ids[i2])) { // a.ids[i1] has the next ID, b does not have a coefficient for that id. + out.set_value(a._ids[i1], a._values[i1] + 0); + i1++; + } + else if ((a._ids[i1] > b._ids[i2])) { // b.ids[i2] has the next ID, a does not have a coefficient for that id + out.set_value(b._ids[i2], 0 + b._values[i2]); + i2++; + } + else { //(a._ids[i1]==b._ids[i2]) // current ID is contained in both id vectors + out.set_value(a._ids[i1], a._values[i1] + b._values[i2]); + i1++; + i2++; + } + } + } + + protected: + std::vector<double> _values; /*!< The linear coefficients saved in the vector*/ + + std::vector<unsigned> _ids; /*!< Sorted list of all indices indentifing nonzero entries*/ + double _constant; /*!< The constant term in linear (more exactly affine) expression*/ + friend LinExpr scale(const LinExpr& LHS, double scale); + friend std::ostream& operator<<(std::ostream& os, const LinExpr& dt); +}; + + +/** @brief Function to print content of LinExpr*/ +inline std::ostream& +operator<<(std::ostream& os, const LinExpr& dt) +{ + os << dt.print(); + return os; +} + +/** @brief Helper function:do an elementwise binary operation on LHS and RHS and save result in returned variable*/ +inline LinExpr +calculate_binary_operation_element_wise(const LinExpr& LHS, const LinExpr& RHS, const std::function<double(double, double)>& op) +{ + LinExpr Out(op(LHS.constant(), RHS.constant())); + auto assingElementwiseOpToOut = [&Out, op](int id, double a, double b) { + Out.set_value(id, op(a, b)); + }; + LinExpr::for_all_coeffs(assingElementwiseOpToOut, LHS, RHS); + return Out; +} + +/** @brief Operator+ for LinExpr: Elementwise addition */ +inline LinExpr +operator+(const LinExpr& LHS, const LinExpr& RHS) +{ + LinExpr Out(LHS.constant() + RHS.constant()); + LinExpr::for_all_coeffs_plus(Out, LHS, RHS); + return Out; +} + +/** @brief Operator- for LinExpr: Elementwise subtraction */ +inline LinExpr +operator-(const LinExpr& LHS, const LinExpr& RHS) +{ + return calculate_binary_operation_element_wise(LHS, RHS, std::minus<double>()); +} + +/** @brief Operator* for LinExpr: Elementwise multiplication*/ +inline LinExpr +operator*(const LinExpr& LHS, const LinExpr& RHS) +{ + return calculate_binary_operation_element_wise(LHS, RHS, std::multiplies<double>()); +} + +/** @brief Operator/ for LinExpr: Elementwise division */ +inline LinExpr +operator/(const LinExpr& LHS, const LinExpr& RHS) +{ + return calculate_binary_operation_element_wise(LHS, RHS, std::divides<double>()); +} + +/** @brief scale LinExpression by scalar*/ +inline LinExpr +scale(const LinExpr& LHS, double scale) +{ + LinExpr result(LHS); + result.constant() *= scale; + + for (double& val : result._values) { + val *= scale; + } + + return result; +} + +/** @brief Operator* for LinExpr and scalar double : Elementwise multiplication*/ +inline LinExpr +operator*(const LinExpr& LHS, double scalar) +{ + return scale(LHS, scalar); +} + +/** @brief Operator* for LinExpr and scalar double : Elementwise multiplication*/ +inline LinExpr +operator*(double scalar, const LinExpr& RHS) +{ + return scale(RHS, scalar); +} + +/** @brief Operator/ for LinExpr and scalar double : Elementwise division*/ +inline LinExpr +operator/(const LinExpr& LHS, double divisor) +{ + return scale(LHS, 1 / divisor); +} + + +/** +* @struct SparseMatrix +* @brief A simple Sparse Matrix using directory of keys format. +* +* Elementwise operations (except division) are supported; +*/ + +class SparseMatrix { + public: + /** + * @brief Default constructor + * @param[in] nCols: number of columns of the matrix, unused in directory of key format. + */ + SparseMatrix(unsigned nCols = 0){}; + /** + * @brief Getter for element of given row and column + * @param[in] row is the row of the element requested + * @param[in] col is the column of the element requested + */ + double get_element(unsigned row, unsigned col) const + { + if (row > get_number_of_rows()) { + return 0; + } + else { + auto it = _matrix.find(std::make_pair(row, col)); + if (it == _matrix.end()) + return 0; + else + return it->second; + } + } + /** + * @brief Setter for element of given row and column. Already set values are overridden. + * @param[in] row is the row of the element to be changed + * @param[in] col is the column of the element to be changed + * @param[in] value is the value the element is to be changed to + */ + void setElement(unsigned row, unsigned col, double value) + { + _matrix[std::make_pair(row, col)] = value; + } + /* @brief Getter for a whole row of the matrix as a LinExpr object*/ + LinExpr getRow(unsigned row) const + { + LinExpr result; + std::map<std::pair<unsigned, unsigned>, double>::const_iterator firstNonZeroElementInRowIt = _matrix.lower_bound(std::make_pair(row, 0)); + for (std::map<std::pair<unsigned, unsigned>, double>::const_iterator it = firstNonZeroElementInRowIt; it != _matrix.end() && it->first.first == row; it++) { + result.set_value(it->first.second, it->second); + } + return result; + } + /* @brief Gets the number of rows saved in the matrix. There is at least one row with row id smaller than the number of rows*/ + unsigned get_number_of_rows() const + { + if (!_matrix.empty()) { + unsigned highestCurrentRowIndex = _matrix.rbegin()->first.first; + return highestCurrentRowIndex + 1; + } + else { + return 0; + } + } + /* @brief Append a row to the bottom of the sparse matrix*/ + void append_row(const LinExpr& row) + { + unsigned newRowIndex = get_number_of_rows(); // known row with highest index is get_number_of_rows-1; + append_row(row, newRowIndex); + } + /* @brief Append a row to the matrix with a given row index, but leave empty rows between the previous last row and the newly added row. + * + * Insetion is not allowed, i.e., can only append to the matrix not insert. + */ + void append_row(const LinExpr& row, unsigned rowNumber) + { + unsigned minNumber = get_number_of_rows(); //we only allow appending, not replacing. + unsigned nnz = row.get_non_zero_ids().size(); + unsigned newRowIndex = rowNumber; + if (newRowIndex < minNumber) { + throw MAiNGOException("Tried to append a row to a sparse matrix, but given row index lead to an insertion. Requested row index to append: " + std::to_string(rowNumber) + " index of last row already in matrix: " + std::to_string(minNumber - 1)); + } + for (unsigned i = 0; i < nnz; i++) { + //efficently inserts at the end of the map! + _matrix.insert(std::end(_matrix), std::make_pair(std::make_pair(newRowIndex, row.get_non_zero_ids()[i]), row.get_non_zero_values()[i])); + } + } + /** @brief Function to print content of sparse matrix*/ + std::string print() const + { + std::stringstream ss; + if (!_matrix.empty()) { + + int previousRow = _matrix.begin()->first.first; + ss << "\n"; + for (auto& t : _matrix) { + unsigned row = t.first.first; + unsigned col = t.first.second; + double val = t.second; + if (row != previousRow) { + ss << "\n"; + previousRow = row; + } + ss << "(" << row << "," << col << "): " << val << ","; + } + } + ss << std::endl; + return ss.str(); + } + + + private: + std::map<std::pair<unsigned, unsigned>, double> _matrix; /*!< sorted map saving the entries of the matrix, sorted first after rows, then after columns*/ + friend SparseMatrix scale(SparseMatrix LHS, double scalar); + friend SparseMatrix add(SparseMatrix LHS, double scalar); + friend std::ostream& operator<<(std::ostream& os, const SparseMatrix& dt); +}; + +/** @brief Function to print content of sparse matrix*/ +inline std::ostream& +operator<<(std::ostream& os, const SparseMatrix& dt) +{ + os << dt.print(); + return os; +} + +/** @brief Operator+ for SparseMatrix: Elementwise addition*/ +inline SparseMatrix +operator+(const SparseMatrix& LHS, const SparseMatrix& RHS) +{ + SparseMatrix result; + unsigned maxRows = std::max(LHS.get_number_of_rows(), RHS.get_number_of_rows()); + for (unsigned i = 0; i < maxRows; i++) { + result.append_row(LHS.getRow(i) + RHS.getRow(i), i); //if both rows are empty, this will do nothing + } + return result; +} + +/** @brief Operator- for SparseMatrix: Elementwise subtraction */ +inline SparseMatrix +operator-(const SparseMatrix& LHS, const SparseMatrix& RHS) +{ + SparseMatrix result; + unsigned maxRows = std::max(LHS.get_number_of_rows(), RHS.get_number_of_rows()); + for (unsigned i = 0; i < maxRows; i++) { + result.append_row(LHS.getRow(i) - RHS.getRow(i), i); + } + return result; +} + +/** @brief Operator* for SparseMatrix: Elementwise multiplication */ +inline SparseMatrix +operator*(const SparseMatrix& LHS, const SparseMatrix& RHS) +{ + SparseMatrix result; + unsigned maxRows = std::max(LHS.get_number_of_rows(), RHS.get_number_of_rows()); + for (unsigned i = 0; i < maxRows; i++) { + result.append_row(LHS.getRow(i) * RHS.getRow(i), i); + } + return result; +} + +/** @brief Operator+ for SparseMatrix and scalar: Elementwise addition*/ +inline SparseMatrix +add(SparseMatrix LHS, double scalar) +{ + for (auto& p : LHS._matrix) { + p.second += scalar; + } + return LHS; +} + +/** @brief Operator* for SparseMatrix and scalar: Elementwise multiplication*/ +inline SparseMatrix +scale(SparseMatrix LHS, double scale) +{ + for (auto& p : LHS._matrix) { + p.second *= scale; + } + return LHS; +} + +/** @brief Operator+ for SparseMatrix and scalar: Elementwise addition*/ +inline SparseMatrix +operator+(const SparseMatrix& LHS, double scalar) +{ + return add(LHS, scalar); +} + +/** @brief Operator+ for SparseMatrix and scalar: Elementwise addition*/ +inline SparseMatrix +operator+(double scalar, const SparseMatrix& RHS) +{ + return add(RHS, scalar); +} + +/** @brief Operator- for SparseMatrix and scalar: Elementwise subtraction */ +inline SparseMatrix +operator-(const SparseMatrix& LHS, double scalar) +{ + return add(LHS, -scalar); +} + +/** @brief Operator- for SparseMatrix: Elementwise negation*/ +inline SparseMatrix +operator-(const SparseMatrix& LHS) +{ + return scale(LHS, -1.0); +} + +/** @brief Operator- for SparseMatrix and scalar: Elementwise subtraction */ +inline SparseMatrix +operator-(double scalar, const SparseMatrix& RHS) +{ + return scalar + (-RHS); +} + +/** @brief Operator* for SparseMatrix and scalar: Elementwise multiplication*/ +inline SparseMatrix +operator*(const SparseMatrix& LHS, double scalar) +{ + return scale(LHS, scalar); +} + +/** @brief Operator* for SparseMatrix and scalar: Elementwise multiplication*/ +inline SparseMatrix +operator*(double scalar, const SparseMatrix& RHS) +{ + return scale(RHS, scalar); +} + +/** +* @struct QuadExpr +* @brief General quadratic expression with a sparse matrix for the quadratic part and a sparse linear expression for the linear part (including a constant term) +* +* Elementwise operations (except division) are supported; +*/ +struct QuadExpr { + SparseMatrix quadraticPart; + LinExpr linearPart; +}; + +/** +* @struct LazyQuadExprTreeNode +* @brief A class that represents a node of the expression tree. +* +*Saves the operation and pointers to its children, i.e., the nodes that the operation needs to be applied to to calculate the expression the node represents. +*If this expression is a leaf node (an original operand for example a scalar) the operation type is identity +*This is outside LazyQuadExpr because we must make sure only shared_ptr to this class are held +*/ +class LazyQuadExprTreeNode { + public: + /* @brief Types of operations that can be saved in the tree */ + enum class OperationType { + PLUS, + MINUS, + TIMES, + NEGATE, + DIVISION_BY_SCALAR, + IDENITY //IDENITY means we ourselves have content in our linearContent member + }; + /* @brief Order of expression*/ + enum class Order { + SCALAR, //only a constant + LINEAR, // a constant and linear terms + QUADRATIC // any expression with quadratic terms + }; + /* @brief Returns true if expression contains quadratic terms*/ + bool is_quadratic() const + { + return _order == Order::QUADRATIC; + } + /* @brief Returns true if expression contains linear terms but no quadratic terms*/ + bool is_linear() const + { + return _order == Order::LINEAR; + } + /* @brief Returns true if expression contains only constants*/ + bool is_scalar() const + { + return _order == Order::SCALAR; + } + /** + * @brief default constructor for LazyQuadExprTreeNode. + * + * @param[in] linExpr The linear expression this node is representing. The linear expression will be saved on the heap with a shader pointer + */ + LazyQuadExprTreeNode(LinExpr linExpr): + _op(OperationType::IDENITY), _linearContent(std::make_shared<LinExpr>(linExpr)), + _order(linExpr.is_scalar() ? Order::SCALAR : Order::LINEAR) + { + } + /** + * @brief default constructor for LazyQuadExprTreeNode given all members + */ + LazyQuadExprTreeNode(std::shared_ptr<LazyQuadExprTreeNode> LHS, std::shared_ptr<LazyQuadExprTreeNode> RHS, OperationType op, Order order = Order::LINEAR): + _op(op), _leftChild(LHS), _rightChild(RHS), _order(order) + { + } + + /** + * @brief Calculate the constant element of the quadratic expression, by going through the expression tree. + */ + double eval_element_constant() const + { + switch (_op) { + case (OperationType::IDENITY): + assert(_linearContent); //IDENITY means the lazy element is a leaf and should have a linearContent saved + return _linearContent->constant(); + case (OperationType::MINUS): + return _leftChild->eval_element_constant() - _rightChild->eval_element_constant(); + case (OperationType::PLUS): + return _leftChild->eval_element_constant() + _rightChild->eval_element_constant(); + case (OperationType::TIMES): + return _leftChild->eval_element_constant() * _rightChild->eval_element_constant(); + case (OperationType::NEGATE): + return -_leftChild->eval_element_constant(); + case (OperationType::DIVISION_BY_SCALAR): + return _leftChild->eval_element_constant() / _rightChild->eval_element_constant(); + } + assert(false); + return std::numeric_limits<double>::quiet_NaN(); + } + + /** + * @brief Calculate a specific linear element of the quadratic expression, by going through the expression tree. + */ + double eval_element_linear(unsigned index) const + { + if (this->is_scalar()) { + return 0.0; + } + switch (_op) { + case (OperationType::IDENITY): + assert(_linearContent); //IDENITY means the lazy element is a leaf and should have a linearContent saved + return _linearContent->get_value(index); + case (OperationType::MINUS): + return _leftChild->eval_element_linear(index) - _rightChild->eval_element_linear(index); + case (OperationType::PLUS): + return _leftChild->eval_element_linear(index) + _rightChild->eval_element_linear(index); + case (OperationType::TIMES): + return _leftChild->eval_element_linear(index) * _rightChild->eval_element_constant() + _rightChild->eval_element_linear(index) * _leftChild->eval_element_constant(); + case (OperationType::DIVISION_BY_SCALAR): + return _leftChild->eval_element_linear(index) / _rightChild->eval_element_constant(); + case (OperationType::NEGATE): + return -_leftChild->eval_element_linear(index); + } + + assert(false); + return std::numeric_limits<double>::quiet_NaN(); + } + /** + * @brief Calculate a specific quadratic element of the quadratic expression, by going through the expression tree. + * + * For evaluating the whole quadratic matrix, this method is more memory efficient, but much slower than using assemble_quadratic_expression_matrix_wise + */ + double eval_element_quadratic(unsigned row, unsigned col) const + { + if (_order != Order::QUADRATIC) { + return 0.0; + } + else { + switch (_op) { + case (OperationType::MINUS): + return _leftChild->eval_element_quadratic(row, col) - _rightChild->eval_element_quadratic(row, col); + case (OperationType::PLUS): + return _leftChild->eval_element_quadratic(row, col) + _rightChild->eval_element_quadratic(row, col); + case (OperationType::NEGATE): + return -_leftChild->eval_element_quadratic(row, col); + case (OperationType::TIMES): { + if (_leftChild->is_quadratic()) { + assert(_rightChild->is_scalar()); + double left = _leftChild->eval_element_quadratic(row, col); + if (left == 0.0) + return 0.0; + else + return left * _rightChild->eval_element_constant(); + } + else if (_rightChild->is_quadratic()) { + assert(_leftChild->is_scalar()); + double right = _rightChild->eval_element_quadratic(row, col); + if (right == 0.0) + return 0.0; + else + return right * _leftChild->eval_element_constant(); + } + else { + double left = _leftChild->eval_element_linear(row); + if (left == 0.0) + return 0.0; + else + return left * _rightChild->eval_element_linear(col); + } + } + case (OperationType::DIVISION_BY_SCALAR): + return _leftChild->eval_element_quadratic(row, col) / _rightChild->eval_element_constant(); + case (OperationType::IDENITY): + throw MAiNGOException(std::string("It should be impossible to create a lazy quadratic expression without creating it from linear expressions") + std::string("but the lazy quadratic expression tree still has an element that claims to be quadratic and an original expression.")); + } + } + assert(false); + return std::numeric_limits<double>::quiet_NaN(); + } + /** + * @brief Calculate the whole quadratic expression (all elements of the quadratic matrix and the linear part, as well as the constant term) by going elementwise through the expression tree. + * + * When trivial,e.g., when calculating bij=aij*0 with aij jet unknown, evaluation of parts of the expression tree is avoided, but still many calculations well need to be repeated for the calculation of the elements. + * The elmentwise calculation avoids building the full QuadExpr objects for the operants, e.g. for C=A+B we dont ever have full A and B in memory, but onyl a_ij b_ij, reducing the peak memory consumption. + * Most likely only worth the time penalty for extremly memory restricted problems. + */ + QuadExpr assemble_quadratic_expression_element_wise(unsigned nVariables) const + { + QuadExpr quadraticExpression; + for (unsigned row = 0; row < nVariables; row++) { + + //The quadratic expression is the result of [a0 a1 a0].T x [b0 b1 b2] and is thus Q is not symmetric + for (unsigned col = 0; col < nVariables; col++) { + double element = eval_element_quadratic(row, col); + if (element != 0.0) { + quadraticExpression.quadraticPart.setElement(row, col, element); + } + } + } + for (unsigned index = 0; index < nVariables; index++) { + double element = eval_element_linear(index); + if (element != 0.0) { + quadraticExpression.linearPart.set_value(index, element); + } + } + quadraticExpression.linearPart.constant() = eval_element_constant(); + return quadraticExpression; + } + + /** + * @brief Calculate the whole quadratic expression (all elements of the quadratic matrix and the linear part, as well as the constant term) by going through the expression tree. To save computations, operands are fully constructed. + * + */ + QuadExpr assemble_quadratic_expression_matrix_wise(unsigned nVariables) const + { + QuadExpr result; + + { + + switch (_op) { + case (OperationType::IDENITY): { + if (_order == Order::QUADRATIC) { + throw MAiNGOException(std::string("It should be impossible to create a lazy quadratic expression without creating it from linear expressions") + std::string("but the lazy quadratic expression tree still has an element that claims to be quadratic and an original expression.")); + } + result.linearPart = *_linearContent; + return result; + } + case (OperationType::MINUS): { + QuadExpr left = _leftChild->assemble_quadratic_expression_matrix_wise(nVariables); + QuadExpr right = _rightChild->assemble_quadratic_expression_matrix_wise(nVariables); + result.linearPart = left.linearPart - right.linearPart; + result.quadraticPart = left.quadraticPart - right.quadraticPart; + return result; + } + case (OperationType::PLUS): { + QuadExpr left = _leftChild->assemble_quadratic_expression_matrix_wise(nVariables); + QuadExpr right = _rightChild->assemble_quadratic_expression_matrix_wise(nVariables); + result.linearPart = left.linearPart + right.linearPart; + result.quadraticPart = left.quadraticPart + right.quadraticPart; + return result; + } + case (OperationType::NEGATE): { + QuadExpr left = _leftChild->assemble_quadratic_expression_matrix_wise(nVariables); + result.quadraticPart = left.quadraticPart * -1.0; + result.linearPart = left.linearPart * -1.0; + return result; + } + case (OperationType::TIMES): { + if (_leftChild->is_quadratic()) { + assert(_rightChild->is_scalar()); + QuadExpr right = _rightChild->assemble_quadratic_expression_matrix_wise(nVariables); + if (right.linearPart.constant() == 0.0) //can stop early if one operand is full zero + return result; //Is still empty + QuadExpr left = _leftChild->assemble_quadratic_expression_matrix_wise(nVariables); + result.linearPart = left.linearPart * right.linearPart.constant(); + result.quadraticPart = left.quadraticPart * right.linearPart.constant(); + } + else if (_rightChild->is_quadratic()) { + assert(_leftChild->is_scalar()); + QuadExpr left = _leftChild->assemble_quadratic_expression_matrix_wise(nVariables); + if (left.linearPart.constant() == 0.0) //can stop early if one operand is pure zero + return result; //Is still empty + QuadExpr right = _rightChild->assemble_quadratic_expression_matrix_wise(nVariables); + result.linearPart = right.linearPart * left.linearPart.constant(); + result.quadraticPart = right.quadraticPart * left.linearPart.constant(); + } + else { + QuadExpr left = _leftChild->assemble_quadratic_expression_matrix_wise(nVariables); + QuadExpr right = _rightChild->assemble_quadratic_expression_matrix_wise(nVariables); + result.linearPart = left.linearPart * right.linearPart.constant() + right.linearPart * left.linearPart.constant(); + result.linearPart.constant() = left.linearPart.constant() * right.linearPart.constant(); + std::vector<unsigned> indLeft = left.linearPart.get_non_zero_ids(); + std::vector<double> valLeft = left.linearPart.get_non_zero_values(); + unsigned indexInIndLeft = 0; + for (unsigned row = 0; row <= indLeft.back(); row++) //indLeft is sorted, going from 0 to left.back means going through + // all possible nonzero row indices. But only rows i with i in indLeft have any nonzero elements. + { + if (row == indLeft[indexInIndLeft]) //row is in left + { + + result.quadraticPart.append_row(valLeft[indexInIndLeft] * right.linearPart, row); + indexInIndLeft++; + } + } + } + return result; + } + case (OperationType::DIVISION_BY_SCALAR): { + QuadExpr left = _leftChild->assemble_quadratic_expression_matrix_wise(nVariables); + result.linearPart = left.linearPart / _rightChild->eval_element_constant(); + return result; + } + } + } + assert(false); //should be inpossible to reach since each case returns + return result; + } + + protected: + OperationType _op; /*!< The operation saved in this node that needs to be applied to the children to generate the result*/ + + + //The operands used to calculate the current expression. this=_op(_leftChild,_rightChild) + const std::shared_ptr<LazyQuadExprTreeNode> _leftChild; /*!< left child of the current node in the expression tree: this=_op(_leftChild,_rightChild */ + const std::shared_ptr<LazyQuadExprTreeNode> _rightChild; /*!< right child of the current node in the expression tree: this=_op(_leftChild,_rightChild. If op_==NEGATE _rightChild is ignored */ + + const std::shared_ptr<LinExpr> _linearContent; /*!<In the leafs of the expression tree, the original linear expressions is saved, is empty except when _Op=IDENITY */ + + const Order _order; /*!< Saves if the expression is a scalar, linear or quadratic, to make sure no cubic or higher orders are calculated */ +}; + +/** @brief Operator+ for LazyQuadExprTreeNode*/ +inline std::shared_ptr<LazyQuadExprTreeNode> +operator+(std::shared_ptr<LazyQuadExprTreeNode> LHS, std::shared_ptr<LazyQuadExprTreeNode> RHS) +{ + LazyQuadExprTreeNode::Order order = LazyQuadExprTreeNode::Order::LINEAR; + if (LHS->is_quadratic() || RHS->is_quadratic()) { + order = LazyQuadExprTreeNode::Order::QUADRATIC; + } + else if (LHS->is_scalar() && RHS->is_scalar()) { + order = LazyQuadExprTreeNode::Order::SCALAR; + } + + return std::make_shared<LazyQuadExprTreeNode>(LHS, RHS, LazyQuadExprTreeNode::OperationType::PLUS, order); +} + +/** @brief Operator- for LazyQuadExprTreeNode*/ +inline std::shared_ptr<LazyQuadExprTreeNode> +operator-(std::shared_ptr<LazyQuadExprTreeNode> LHS, std::shared_ptr<LazyQuadExprTreeNode> RHS) +{ + LazyQuadExprTreeNode::Order order = LazyQuadExprTreeNode::Order::LINEAR; + if (LHS->is_quadratic() || RHS->is_quadratic()) { + order = LazyQuadExprTreeNode::Order::QUADRATIC; + } + else if (LHS->is_scalar() && RHS->is_scalar()) { + order = LazyQuadExprTreeNode::Order::SCALAR; + } + + return std::make_shared<LazyQuadExprTreeNode>(LHS, RHS, LazyQuadExprTreeNode::OperationType::MINUS, order); +} + +/** @brief Operator- for LazyQuadExprTreeNode*/ +inline std::shared_ptr<LazyQuadExprTreeNode> +operator-(std::shared_ptr<LazyQuadExprTreeNode> LHS) +{ + LazyQuadExprTreeNode::Order order = LazyQuadExprTreeNode::Order::SCALAR; + if (LHS->is_linear()) + order = LazyQuadExprTreeNode::Order::LINEAR; + else if (LHS->is_quadratic()) + order = LazyQuadExprTreeNode::Order::QUADRATIC; + return std::make_shared<LazyQuadExprTreeNode>(LHS, LHS, LazyQuadExprTreeNode::OperationType::NEGATE, order); +} + +/** + *@brief Operator* for LazyQuadExprTreeNode + * + * Will throw if total order of the resulting expression would be higher than quadratic. + */ +inline std::shared_ptr<LazyQuadExprTreeNode> +operator*(std::shared_ptr<LazyQuadExprTreeNode> LHS, std::shared_ptr<LazyQuadExprTreeNode> RHS) +{ + bool resultMoreThanQuadratic = (LHS->is_quadratic() && !RHS->is_scalar()) || (RHS->is_quadratic() && !LHS->is_scalar()); + bool resultQuadratic = (LHS->is_linear() && RHS->is_linear()) || (LHS->is_quadratic() && RHS->is_scalar()) || (LHS->is_scalar() && RHS->is_quadratic()); + bool resultScalar = LHS->is_scalar() && RHS->is_scalar(); + LazyQuadExprTreeNode::Order resultOrder; + if (resultMoreThanQuadratic) { + throw MAiNGOException(("Cant multiply already quadratic expressions to generate a quadratic expression")); + } + else if (resultQuadratic) { + resultOrder = LazyQuadExprTreeNode::Order::QUADRATIC; + } + else if (resultScalar) { + resultOrder = LazyQuadExprTreeNode::Order::SCALAR; + } + else { + resultOrder = LazyQuadExprTreeNode::Order::LINEAR; + } + return std::make_shared<LazyQuadExprTreeNode>(LHS, RHS, LazyQuadExprTreeNode::OperationType::TIMES, resultOrder); +} + +/** +*@brief Operator/ for LazyQuadExprTreeNode +* +* Will throw if RHS is not a scalar. +*/ +inline std::shared_ptr<LazyQuadExprTreeNode> +operator/(std::shared_ptr<LazyQuadExprTreeNode> LHS, std::shared_ptr<LazyQuadExprTreeNode> RHS) +{ + LazyQuadExprTreeNode::Order resultOrder = LazyQuadExprTreeNode::Order::SCALAR; + if (!RHS->is_scalar()) { + throw MAiNGOException("Function 1/x not allowed in (MIQ)Ps."); + } + if (LHS->is_linear()) { + resultOrder = LazyQuadExprTreeNode::Order::LINEAR; + } + else if (LHS->is_quadratic()) { + resultOrder = LazyQuadExprTreeNode::Order::QUADRATIC; + } + return std::make_shared<LazyQuadExprTreeNode>(LHS, RHS, LazyQuadExprTreeNode::OperationType::DIVISION_BY_SCALAR, resultOrder); +} + +/** +* @struct LazyQuadExpr +* @brief A class can be used as in to generate quadratic expressions that can be lazily evaluated, which enables memory savings when evaluating in DAG. +* +* Lazy behavior is archieved by building a pointer structure on the heap that saves an expression tree. This class encapsulates a node of an the expression tree. +*/ +class LazyQuadExpr { + public: + /** + * @brief Constructor for LazyQuadExpr that represents a variable or an identity expression + * + * @param[in] numberVars Is the total number of variables (currently unused) + * @param[in] active_index Is the index of the variable this expression represents. Should be smaller then numberVars; + */ + LazyQuadExpr(unsigned numberVars, unsigned active_index) + { + if (active_index >= numberVars) { + throw MAiNGOException("Tried to create an lazy quadratic expresion for a variable with an index that is inconsistent with the specified number of variables"); + } + LinExpr lin(0.0); + lin.set_value(active_index, 1.0); + _tree = std::make_shared<LazyQuadExprTreeNode>(lin); + } + /* @brief Constructor for LazyQuadExpr from a linear expression */ + LazyQuadExpr(LinExpr linExpr) + { + _tree = std::make_shared<LazyQuadExprTreeNode>(linExpr); + } + /* @brief Constructor for LazyQuadExpr from a scalar*/ + LazyQuadExpr(double scalar = 0.0) + { + _tree = std::make_shared<LazyQuadExprTreeNode>(LinExpr(scalar)); + /* @brief Constructor for LazyQuadExpr from a shared pointer to LazyQuadExprTreeNode, making them convertible to each other*/ + } + LazyQuadExpr(std::shared_ptr<LazyQuadExprTreeNode> tree): + _tree(tree) {} + + /* @brief Conversion for LazyQuadExpr to a shared pointer to LazyQuadExprTreeNode, making them convertible to each other*/ + operator std::shared_ptr<LazyQuadExprTreeNode>() + { + return _tree; + } + /*! @copydoc LazyQuadExprTreeNode::assemble_quadratic_expression_element_wise()*/ + QuadExpr assemble_quadratic_expression_element_wise(unsigned numberVars) const + { + return _tree->assemble_quadratic_expression_element_wise(numberVars); + } + /*! @copydoc LazyQuadExprTreeNode::assemble_quadratic_expression_matrix_wise()*/ + QuadExpr assemble_quadratic_expression_matrix_wise(unsigned numberVars) const + { + return _tree->assemble_quadratic_expression_matrix_wise(numberVars); + } + /*! @copydoc LazyQuadExprTreeNode::eval_element_linear()*/ + double eval_element_linear(unsigned index) const + { + return _tree->eval_element_linear(index); + } + /*! @copydoc LazyQuadExprTreeNode::eval_element_constant()*/ + double eval_element_constant() const + { + return _tree->eval_element_constant(); + } + /*! @copydoc LazyQuadExprTreeNode::eval_element_quadratic()*/ + double eval_element_quadratic(unsigned int row, unsigned int col) const + { + return _tree->eval_element_quadratic(row, col); + } + /** @brief Operator+= for LazyQuadExpr*/ + LazyQuadExpr& operator+=(const LazyQuadExpr& RHS) + { + _tree = _tree + RHS._tree; + return *this; + } + /** @brief Operator*= for LazyQuadExpr*/ + LazyQuadExpr& operator*=(const LazyQuadExpr& RHS) + { + _tree = _tree * RHS._tree; + return *this; + } + /** @brief Operator-= for LazyQuadExpr*/ + LazyQuadExpr& operator-=(const LazyQuadExpr& RHS) + { + _tree = _tree - RHS._tree; + return *this; + } + /** @brief Operator/= for LazyQuadExpr for scalar RHS*/ + LazyQuadExpr& operator/=(double scalar) + { + //if RHS not scalar, this throws + _tree = _tree / LazyQuadExpr(scalar)._tree; + return *this; + } + + private: + std::shared_ptr<LazyQuadExprTreeNode> _tree; /*!< The root node to the expression tree that can be evaluated to compute the expression*/ + friend LazyQuadExpr operator+(const LazyQuadExpr&, const LazyQuadExpr&); + friend LazyQuadExpr operator-(const LazyQuadExpr&, const LazyQuadExpr&); + friend LazyQuadExpr operator*(const LazyQuadExpr&, const LazyQuadExpr&); + friend LazyQuadExpr operator-(const LazyQuadExpr&); + friend LazyQuadExpr operator+(const LazyQuadExpr&); + friend LazyQuadExpr operator/(const LazyQuadExpr& LHS, double scalar); +}; + +/** @brief Operator+ for LazyQuadExpr*/ +inline LazyQuadExpr +operator+(const LazyQuadExpr& LHS) +{ + return LazyQuadExpr(LHS._tree); +} + +/** @brief Operator+ for LazyQuadExpr*/ +inline LazyQuadExpr +operator+(const LazyQuadExpr& LHS, const LazyQuadExpr& RHS) +{ + return LazyQuadExpr(LHS._tree + RHS._tree); +} + +/** @brief Operator- for LazyQuadExpr*/ +inline LazyQuadExpr +operator-(const LazyQuadExpr& LHS, const LazyQuadExpr& RHS) +{ + return LazyQuadExpr(LHS._tree - RHS._tree); +} + +/** @brief Operator* for LazyQuadExpr*/ +inline LazyQuadExpr +operator*(const LazyQuadExpr& LHS, const LazyQuadExpr& RHS) +{ + return LazyQuadExpr(LHS._tree * RHS._tree); +} + +/** @brief Operator- for LazyQuadExpr*/ +inline LazyQuadExpr +operator-(const LazyQuadExpr& LHS) +{ + return LazyQuadExpr(-LHS._tree); +} + +/** @brief Operator/ for LazyQuadExpr with scalar RHS*/ +inline LazyQuadExpr +operator/(const LazyQuadExpr& LHS, double scalar) +{ + return LazyQuadExpr(LHS._tree / LazyQuadExpr(scalar)._tree); +} + +/** @brief Operator/ for division of a double by an LazyQuadExpr */ +inline LazyQuadExpr +operator/(const double in1, const LazyQuadExpr& in2) +{ + throw MAiNGOException(" Error: LazyQuadExpr -- function 1/x not allowed in (MIQ)Ps."); +} + +/** @brief Operator/ for division of an int by an LazyQuadExpr */ +inline LazyQuadExpr +operator/(const int in1, const LazyQuadExpr& in2) +{ + throw MAiNGOException(" Error: LazyQuadExpr -- function 1/x not allowed in (MIQ)Ps."); +} + + +} // namespace ubp +} // namespace maingo + + +namespace mc { + + +//! @brief Specialization of the structure mc::Op for use of the type UbpQuadExpr as a template parameter in other MC++ types +template <> +struct Op<maingo::ubp::LazyQuadExpr> { + typedef maingo::ubp::LazyQuadExpr QE; /*!< typedef for easier usage */ + static QE sqr(const QE& x) { return x * x; } /*!< x^2 */ + static QE pow(const QE& x, const int n) + { + if (n == 0) { + return QE(1.0); + } + if (n == 1) { + return x; + } + if (n == 2) { + return x * x; + } + throw std::runtime_error(" Error: UbpQuadExpr -- function pow with n <> 0,1,2 not allowed in (MIQ)Ps."); + } /*!< powers are allowed up to order 2 */ + static QE pow(const QE& x, const double a) + { + if (a == 0) { + return QE(1.0); + } + if (a == 1) { + return x; + } + if (a == 2) { + return x * x; + } + throw std::runtime_error(" Error: UbpQuadExpr -- function pow with a <> 0,1,2 not allowed in (MIQ)Ps."); + } /*!< power are allowed up to order 2 */ + static QE pow(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function pow(x,y) not allowed in (MIQ)Ps."); } /*!< x^y is not allowed */ + static QE pow(const double x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function pow(a,y) not allowed in (MIQ)Ps."); } /*!< c^x is not allowed */ + static QE pow(const int x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function pow(n,y) not allowed in (MIQ)Ps."); } /*!< d^x is not allowed */ + static QE prod(const unsigned int n, const QE* x) { throw std::runtime_error(" Error: UbpQuadExpr -- function prod not allowed in (MIQ)Ps."); } /*!< prod could be allowed but is currently not implemented */ + static QE monom(const unsigned int n, const QE* x, const unsigned* k) { throw std::runtime_error(" Error: UbpQuadExpr -- function monom not allowed in (MIQ)Ps."); } /*!< monom could be allowed but is currently not implemented */ + static QE point(const double c) { throw std::runtime_error(" Error: UbpQuadExpr -- function point not allowed in (MIQ)Ps."); } /*!< point is not needed at all */ + static QE zeroone() { throw std::runtime_error(" Error: UbpQuadExpr -- function zeroone not allowed in (MIQ)Ps."); } /*!< zeroone is not needed at all */ + static void I(QE& x, const QE& y) { x = y; } /*!< even thou I should be understood as interval, it is implemented here as assignment */ + static double l(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function l not allowed in (MIQ)Ps."); } /*!< no lower bound given */ + static double u(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function u not allowed in (MIQ)Ps."); } /*!< no upper bound given */ + static double abs(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function abs not allowed in (MIQ)Ps."); } /*!< abs is not allowed */ + static double mid(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function mid not allowed in (MIQ)Ps."); } /*!< mid not given */ + static double diam(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function diam not allowed in (MIQ)Ps."); } /*!< diam not given */ + static QE inv(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function inv not allowed in (MIQ)Ps."); } /*!< inv is not allowed */ + static QE sqrt(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function sqrt not allowed in (MIQ)Ps."); } /*!< sqrt is not allowed */ + static QE exp(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function exp not allowed in (MIQ)Ps."); } /*!< exp is not allowed */ + static QE log(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function log not allowed in (MIQ)Ps."); } /*!< log is not allowed */ + static QE xlog(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function xlog not allowed in (MIQ)Ps."); } /*!< xlog is not allowed */ + static QE fabsx_times_x(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function fabsx_times_x not allowed in (MIQ)Ps."); } /*!< x*|x| is not allowed */ + static QE xexpax(const QE& x, const double a) { throw std::runtime_error(" Error: UbpQuadExpr -- function xexpax not allowed in (MIQ)Ps."); } /*!< x*exp(a*x) is not allowed */ + static QE lmtd(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function lmtd not allowed in (MIQ)Ps."); } /*!< lmtd is not allowed */ + static QE rlmtd(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function rlmtd not allowed in (MIQ)Ps."); } /*!< rlmtd is not allowed */ + static QE mid(const QE& x, const QE& y, const double k) { throw std::runtime_error(" Error: UbpQuadExpr -- function mid not allowed in (MIQ)Ps."); } + static QE pinch(const QE& Th, const QE& Tc, const QE& Tp) { throw std::runtime_error(" Error: UbpQuadExpr -- function pinch not allowed in (MIQ)Ps."); } + static QE euclidean_norm_2d(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function euclidean_norm_2d not allowed in (MIQ)Ps."); } /*!< euclidean is not allowed */ + static QE expx_times_y(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function expx_times_y not allowed in (MIQ)Ps."); } /*!< exp(x)*y is not allowed */ + static QE vapor_pressure(const QE& x, const double type, const double p1, const double p2, const double p3, const double p4 = 0, const double p5 = 0, const double p6 = 0, + const double p7 = 0, const double p8 = 0, const double p9 = 0, const double p10 = 0) { throw std::runtime_error(" Error: UbpQuadExpr -- function vapor_pressure not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE ideal_gas_enthalpy(const QE& x, const double x0, const double type, const double p1, const double p2, const double p3, const double p4, const double p5, const double p6 = 0, + const double p7 = 0) { throw std::runtime_error(" Error: UbpQuadExpr -- function ideal_gas_enthalpy not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE saturation_temperature(const QE& x, const double type, const double p1, const double p2, const double p3, const double p4 = 0, const double p5 = 0, const double p6 = 0, + const double p7 = 0, const double p8 = 0, const double p9 = 0, const double p10 = 0) { throw std::runtime_error(" Error: UbpQuadExpr -- function saturation_temperature not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE enthalpy_of_vaporization(const QE& x, const double type, const double p1, const double p2, const double p3, const double p4, const double p5, const double p6 = 0) { throw std::runtime_error(" Error: UbpQuadExpr -- function enthalpy_of_vaporization not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE cost_function(const QE& x, const double type, const double p1, const double p2, const double p3) { throw std::runtime_error(" Error: UbpQuadExpr -- function cost_function not allowed in (MIQ)Ps."); } /*!< no cost function function is not allowed */ + static QE nrtl_tau(const QE& x, const double a, const double b, const double e, const double f) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_tau not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE nrtl_dtau(const QE& x, const double b, const double e, const double f) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_dtau not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE nrtl_G(const QE& x, const double a, const double b, const double e, const double f, const double alpha) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_G not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE nrtl_Gtau(const QE& x, const double a, const double b, const double e, const double f, const double alpha) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_Gtau not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE nrtl_Gdtau(const QE& x, const double a, const double b, const double e, const double f, const double alpha) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_Gdtau not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE nrtl_dGtau(const QE& x, const double a, const double b, const double e, const double f, const double alpha) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_dGtau not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE iapws(const QE& x, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function iapws not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE iapws(const QE& x, const QE& y, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function iapws not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE p_sat_ethanol_schroeder(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function p_sat_ethanol_schroeder not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE rho_vap_sat_ethanol_schroeder(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function rho_vap_sat_ethanol_schroeder not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE rho_liq_sat_ethanol_schroeder(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function rho_liq_sat_ethanol_schroeder not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE covariance_function(const QE& x, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function covariance_function not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE acquisition_function(const QE& x, const QE& y, const double type, const double fmin) { throw std::runtime_error(" Error: UbpQuadExpr -- function acquisition_function not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE gaussian_probability_density_function(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function gaussian_probability_density_function not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE regnormal(const QE& x, const double a, const double b) { throw std::runtime_error(" Error: UbpQuadExpr -- function regnormal not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ + static QE fabs(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function fabs not allowed in (MIQ)Ps."); } /*!< fabs function is not allowed */ + static QE sin(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function sin not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE cos(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function cos not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE tan(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function tan not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE asin(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function asin not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE acos(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function acos not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE atan(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function atan not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE sinh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function sinh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE cosh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function cosh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE tanh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function tanh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE coth(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function coth not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE asinh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function asinh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE acosh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function acosh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE atanh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function atanh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE acoth(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function acoth not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ + static QE erf(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function erf not allowed in (MIQ)Ps."); } /*!< erf function is not allowed */ + static QE erfc(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function erfc not allowed in (MIQ)Ps."); } /*!< erfc function is not allowed */ + static QE fstep(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function fstep not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ + static QE bstep(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function bstep not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ + static QE hull(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function hull not allowed in (MIQ)Ps."); } /*!< hull is not given */ + static QE min(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function min not allowed in (MIQ)Ps."); } /*!< min function is not allowed */ + static QE max(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function max not allowed in (MIQ)Ps."); } /*!< max function is not allowed */ + static QE pos(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function pos not allowed in (MIQ)Ps."); } /*!< pos function is not allowed */ + static QE neg(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function neg not allowed in (MIQ)Ps."); } /*!< neg function is not allowed */ + static QE lb_func(const QE& x, const double lb) { throw std::runtime_error(" Error: UbpQuadExpr -- function lb_func not allowed in (MIQ)Ps."); } /*!< lb_func function is not allowed */ + static QE ub_func(const QE& x, const double ub) { throw std::runtime_error(" Error: UbpQuadExpr -- function ub_func not allowed in (MIQ)Ps."); } /*!< ub_func function is not allowed */ + static QE bounding_func(const QE& x, const double lb, const double ub) { throw std::runtime_error(" Error: UbpQuadExpr -- function bounding_func not allowed in (MIQ)Ps."); } /*!< bounding_func function is not allowed */ + static QE squash_node(const QE& x, const double lb, const double ub) { throw std::runtime_error(" Error: UbpQuadExpr -- function squash_node not allowed in (MIQ)Ps."); } /*!< squash_node function is not allowed */ + static QE sum_div(const std::vector<QE>& x, const std::vector<double>& coeff) { throw std::runtime_error(" Error: UbpQuadExpr -- function sum_div not allowed in (MIQ)Ps."); } /*!< sum_div function is not allowed */ + static QE xlog_sum(const std::vector<QE>& x, const std::vector<double>& coeff) { throw std::runtime_error(" Error: UbpQuadExpr -- function xlog_sum not allowed in (MIQ)Ps."); } /*!< xlog_sum function is not allowed */ + static QE mc_print(const QE& x, const int number) { throw std::runtime_error(" Error: UbpQuadExpr -- function mc_print not allowed in (MIQ)Ps."); } /*!< printing function is not allowed */ + static QE arh(const QE& x, const double k) { throw std::runtime_error(" Error: UbpQuadExpr -- function arh not allowed in (MIQ)Ps."); } /*!< arh function is not allowed */ + static QE cheb(const QE& x, const unsigned n) { throw std::runtime_error(" Error: UbpQuadExpr -- function cheb not allowed in (MIQ)Ps."); } /*!< cheb function is not allowed */ + static bool inter(QE& xIy, const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function inter not allowed in (MIQ)Ps."); } /*!< interior is not given */ + static bool eq(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function eq not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ + static bool ne(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function ne not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ + static bool lt(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function lt not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ + static bool le(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function le not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ + static bool gt(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function gt not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ + static bool ge(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function ge not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ + static QE centerline_deficit(const QE& x, const double xLim, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function centerline_deficit not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ + static QE wake_profile(const QE& x, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function wake_profile not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ + static QE wake_deficit(const QE& x, const QE& r, const double a, const double alpha, const double rr, const double type1, const double type2) { throw std::runtime_error(" Error: UbpQuadExpr -- function wake_deficit not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ + static QE power_curve(const QE& x, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function power_curve not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ +}; + + +} // namespace mc diff --git a/inc/ubpQuadExpr.h b/inc/ubpQuadExpr.h index 26e9589..07ca540 100644 --- a/inc/ubpQuadExpr.h +++ b/inc/ubpQuadExpr.h @@ -7,752 +7,27 @@ * * SPDX-License-Identifier: EPL-2.0 * + * @file ubpQuadExpr.h + * + * @brief File containing declaration of structure UbpQuadExpr used to compute + * coefficients of linear and quadratic terms in (MIQ)Ps. + * **********************************************************************************/ +#define LAZYQUAD + #pragma once #include "MAiNGOException.h" #include "mcop.hpp" - -#include <vector> - - +#ifdef LAZYQUAD +#include "ubpLazyQuadExpr.h" namespace maingo { - - namespace ubp { - - -/** @brief Operator- for a double vector */ -inline std::vector<double> -operator-(const std::vector<double>& in) -{ - std::vector<double> out(in.size()); - for (size_t i = 0; i < in.size(); i++) { - out[i] = -in[i]; - } - return out; -} - -/** @brief Operator- for a double matrix */ -inline std::vector<std::vector<double>> -operator-(const std::vector<std::vector<double>>& in) -{ - std::vector<std::vector<double>> out(in.size()); - for (size_t i = 0; i < in.size(); i++) { - out[i] = -in[i]; - } - return out; -} - -/** @brief Operator+ for addition of two double vectors */ -inline std::vector<double> -operator+(const std::vector<double>& in1, const std::vector<double>& in2) -{ - if (in1.size() != in2.size()) - throw MAiNGOException(" Error: UbpQuadExpr -- inconsistent sizes in vector + operator."); - std::vector<double> out(in1.size()); - for (size_t i = 0; i < in1.size(); i++) { - out[i] = in1[i] + in2[i]; - } - return out; -} - -/** @brief Operator+ for addition of two double matrices */ -inline std::vector<std::vector<double>> -operator+(const std::vector<std::vector<double>>& in1, const std::vector<std::vector<double>>& in2) -{ - if (in1.size() != in2.size()) - throw MAiNGOException(" Error: UbpQuadExpr -- inconsistent sizes in vector<vector> + operator."); - std::vector<std::vector<double>> out(in1.size()); - for (size_t i = 0; i < in1.size(); i++) { - if (in1[i].size() != in2[i].size()) - throw MAiNGOException(" Error: UbpQuadExpr -- inconsistent sizes in vector<vector> + operator."); - out[i] = in1[i] + in2[i]; - } - return out; -} - -/** @brief Operator- for subtraction of two double vectors */ -inline std::vector<double> -operator-(const std::vector<double>& in1, const std::vector<double>& in2) -{ - if (in1.size() != in2.size()) - throw MAiNGOException(" Error: UbpQuadExpr -- inconsistent sizes in vector - operator."); - std::vector<double> out(in1.size()); - for (size_t i = 0; i < in1.size(); i++) { - out[i] = in1[i] - in2[i]; - } - return out; -} - -/** @brief Operator- for subtraction of two double matrices */ -inline std::vector<std::vector<double>> -operator-(const std::vector<std::vector<double>>& in1, const std::vector<std::vector<double>>& in2) -{ - if (in1.size() != in2.size()) - throw MAiNGOException(" Error: UbpQuadExpr -- inconsistent sizes in vector<vector> - operator."); - std::vector<std::vector<double>> out(in1.size()); - for (size_t i = 0; i < in1.size(); i++) { - if (in1[i].size() != in2[i].size()) - throw MAiNGOException(" Error: UbpQuadExpr -- inconsistent sizes in vector<vector> - operator."); - out[i] = in1[i] - in2[i]; - } - return out; -} - -/** @brief Operator* for multiplication of a double vector with a double constant */ -inline std::vector<double> -operator*(const std::vector<double>& in1, const double in2) -{ - std::vector<double> out(in1.size()); - for (size_t i = 0; i < in1.size(); i++) { - out[i] = in1[i] * in2; - } - return out; -} - -/** @brief Operator* for multiplication of a double matrix with a double constant */ -inline std::vector<std::vector<double>> -operator*(const std::vector<std::vector<double>>& in1, const double in2) -{ - std::vector<std::vector<double>> out(in1.size()); - for (size_t i = 0; i < in1.size(); i++) { - out[i] = in1[i] * in2; - } - return out; -} - -/** -* @struct UbpQuadExpr -* @brief Struct used to compute coefficients of linear and quadratic/bilinear terms in (MIQ)Ps. -* This struct is used to avoid the need of propagating the IloExpr object resulting in HUGE RAM usage. -*/ -struct UbpQuadExpr { - - public: - /** - * @brief Default constructor - */ - UbpQuadExpr(){}; - - /** - * @brief Constructor accepting a number of variables - * - * @param[in] nvarIn is the number of variables - */ - UbpQuadExpr(const size_t nvarIn) - { - nvar = nvarIn; - coeffsLin.resize(nvar, 0); - coeffsQuad.resize(nvar, std::vector<double>(nvar, 0)); - constant = 0; - hasQuad = false; - } - - /** - * @brief Constructor for a specific variable participating linearly - * - * @param[in] nvarIn is the number of variables - * @param[in] iLin is the number of the variable participating linearly - */ - UbpQuadExpr(const size_t nvarIn, const size_t iLin) - { - if (iLin >= nvarIn) { - throw MAiNGOException(" Error: UbpQuadExpr -- iLin >= nvarIn."); - } - nvar = nvarIn; - coeffsLin.resize(nvar, 0); - coeffsLin[iLin] = 1; - coeffsQuad.resize(nvar, std::vector<double>(nvar, 0)); - constant = 0; - hasQuad = false; - } - - /** - * @brief Constructor for a constant - * - * @param[in] in is the value of the constant - */ - UbpQuadExpr(const double in) - { - nvar = 0; - coeffsLin.clear(); - coeffsQuad.clear(); - constant = in; - hasQuad = false; - } - - /** @brief Operator= for a double constant */ - UbpQuadExpr& operator=(const double in) - { - nvar = 0; - coeffsLin.clear(), coeffsQuad.clear(); - constant = in; - hasQuad = false; - return *this; - } - - /** @brief Operator= for an integer constant */ - UbpQuadExpr& operator=(const int in) - { - nvar = 0; - coeffsLin.clear(), coeffsQuad.clear(); - constant = (double)in; - hasQuad = false; - return *this; - } - - /** @brief Operator+= for UbpQuadExpr */ - UbpQuadExpr& operator+=(const UbpQuadExpr& in) - { - if (nvar != in.nvar && (nvar != 0 && in.nvar != 0)) - throw MAiNGOException(" Error: UbpQuadExpr -- nvar does not fit in += operator."); - - if (nvar == 0) { - coeffsLin = in.coeffsLin; - coeffsQuad = in.coeffsQuad; - constant += in.constant; - } - else if (in.nvar == 0) { - constant += in.constant; - } - else { - coeffsLin = coeffsLin + in.coeffsLin; - coeffsQuad = coeffsQuad + in.coeffsQuad; - constant += in.constant; - } - hasQuad = hasQuad || in.hasQuad; - return *this; - } - - /** @brief Operator+= for double */ - UbpQuadExpr& operator+=(const double in) - { - constant += in; - return *this; - } - - /** @brief Operator+= for int */ - UbpQuadExpr& operator+=(const int in) - { - constant += in; - return *this; - } - - /** @brief Operator-= for UbpQuadExpr */ - UbpQuadExpr& operator-=(const UbpQuadExpr& in) - { - if (nvar != in.nvar && (nvar != 0 && in.nvar != 0)) - throw MAiNGOException(" Error: UbpQuadExpr -- nvar does not fit in += operator."); - - if (nvar == 0) { - coeffsLin = -in.coeffsLin; - coeffsQuad = -in.coeffsQuad; - constant -= in.constant; - } - else if (in.nvar == 0) { - constant -= in.constant; - } - else { - coeffsLin = coeffsLin - in.coeffsLin; - coeffsQuad = coeffsQuad - in.coeffsQuad; - constant -= in.constant; - } - hasQuad = hasQuad || in.hasQuad; - return *this; - } - - /** @brief Operator-= for double */ - UbpQuadExpr& operator-=(const double in) - { - constant -= in; - return *this; - } - - /** @brief Operator-= for int */ - UbpQuadExpr& operator-=(const int in) - { - constant -= in; - return *this; - } - - /** @brief Operator*= for UbpQuadExpr */ - UbpQuadExpr& operator*=(const UbpQuadExpr& in) - { - if (nvar != in.nvar && (nvar != 0 && in.nvar != 0)) - throw MAiNGOException(" Error: UbpQuadExpr -- nvar does not fit in * operator."); - - if (nvar == 0) { - coeffsLin = in.coeffsLin * constant; - coeffsQuad = in.coeffsQuad * constant; - constant = in.constant * constant; - hasQuad = in.hasQuad; - } - else if (in.nvar == 0) { - coeffsLin = coeffsLin * in.constant; - coeffsQuad = coeffsQuad * in.constant; - constant = constant * in.constant; - } - else { - if (hasQuad || in.hasQuad) - throw MAiNGOException(" Error: UbpQuadExpr -- multiplications higher than second order are not allowed in (MIQ)Ps."); - - for (size_t i = 0; i < nvar; i++) { - for (size_t j = 0; j < in.nvar; j++) { - coeffsQuad[i][j] = coeffsLin[i] * in.coeffsLin[j]; - } - coeffsLin[i] = coeffsLin[i] * in.constant + in.coeffsLin[i] * constant; - } - constant = in.constant * constant; - hasQuad = true; - } - return *this; - } - - /** @brief Operator*= for double */ - UbpQuadExpr& operator*=(const double in) - { - coeffsLin = coeffsLin * in; - coeffsQuad = coeffsQuad * in; - constant = constant * in; - return *this; - } - - /** @brief Operator*= for int */ - UbpQuadExpr& operator*=(const int in) - { - coeffsLin = coeffsLin * (double)in; - coeffsQuad = coeffsQuad * (double)in; - constant = constant * (double)in; - return *this; - } - - /** @brief Operator/= for UbpQuadExpr */ - UbpQuadExpr& operator/=(const UbpQuadExpr& in) { throw MAiNGOException(" Error: UbpQuadExpr -- function x/y not allowed in (MIQ)Ps."); } - /** @brief Operator/= for double */ - UbpQuadExpr& operator/=(const double in) - { - *this *= (1. / in); - return *this; - } - /** @brief Operator/= for int */ - UbpQuadExpr& operator/=(const int in) - { - *this *= (1. / (double)in); - return *this; - } - - /** - * @name Internal CPLEX variables - */ - /**@{*/ - size_t nvar; /*!< number of variables */ - double constant; /*!< value of numeric constant */ - std::vector<double> coeffsLin; /*!< vector holding linear coefficients */ - std::vector<std::vector<double>> coeffsQuad; /*!< matrix holding coefficient of quadratic/bilinear terms */ - bool hasQuad; /*!< flag indicating whether a quadratic/bilinear term is already present */ - /**@}*/ -}; - -/** @brief Operator+ for UbpQuadExpr */ -inline UbpQuadExpr -operator+(const UbpQuadExpr& in) -{ - return in; -} - -/** @brief Operator+ for two UbpQuadExpr objects */ -inline UbpQuadExpr -operator+(const UbpQuadExpr& in1, const UbpQuadExpr& in2) -{ - if (in1.nvar != in2.nvar && (in1.nvar != 0 && in2.nvar != 0)) - throw MAiNGOException(" Error: UbpQuadExpr -- nvar does not fit in + operator."); - - UbpQuadExpr res(in1.nvar); - if (in1.nvar == 0) { - res.coeffsLin = in2.coeffsLin; - res.coeffsQuad = in2.coeffsQuad; - res.constant = in1.constant + in2.constant; - } - else if (in2.nvar == 0) { - res.coeffsLin = in1.coeffsLin; - res.coeffsQuad = in1.coeffsQuad; - res.constant = in1.constant + in2.constant; - } - else { - res.coeffsLin = in1.coeffsLin + in2.coeffsLin; - res.coeffsQuad = in1.coeffsQuad + in2.coeffsQuad; - res.constant = in1.constant + in2.constant; - } - res.hasQuad = in1.hasQuad || in2.hasQuad; - return res; -} - -/** @brief Operator+ for addition of an UbpQuadExpr and a double */ -inline UbpQuadExpr -operator+(const UbpQuadExpr& in1, const double& in2) -{ - UbpQuadExpr res(in1.nvar); - res.coeffsLin = in1.coeffsLin; - res.coeffsQuad = in1.coeffsQuad; - res.constant = in1.constant + in2; - res.hasQuad = in1.hasQuad; - return res; +using UbpQuadExpr = LazyQuadExpr; } - -/** @brief Operator+ for addition of an UbpQuadExpr and an int */ -inline UbpQuadExpr -operator+(const UbpQuadExpr& in1, const int& in2) -{ - UbpQuadExpr res(in1.nvar); - res.coeffsLin = in1.coeffsLin; - res.coeffsQuad = in1.coeffsQuad; - res.constant = in1.constant + in2; - res.hasQuad = in1.hasQuad; - return res; -} - -/** @brief Operator+ for addition of an UbpQuadExpr and a double */ -inline UbpQuadExpr -operator+(const double& in1, const UbpQuadExpr& in2) -{ - return in2 + in1; -} - -/** @brief Operator+ for addition of an UbpQuadExpr and an int */ -inline UbpQuadExpr -operator+(const int& in1, const UbpQuadExpr& in2) -{ - return in2 + in1; -} - -/** @brief Operator- for UbpQuadExpr */ -inline UbpQuadExpr -operator-(const UbpQuadExpr& in) -{ - UbpQuadExpr res(in.nvar); - res.coeffsLin = -in.coeffsLin; - res.coeffsQuad = -in.coeffsQuad; - res.constant = -in.constant; - res.hasQuad = in.hasQuad; - return res; -} - -/** @brief Operator- for two UbpQuadExpr objects */ -inline UbpQuadExpr -operator-(const UbpQuadExpr& in1, const UbpQuadExpr& in2) -{ - if (in1.nvar != in2.nvar && (in1.nvar != 0 && in2.nvar != 0)) - throw MAiNGOException(" Error: UbpQuadExpr -- nvar does not fit in - operator."); - - UbpQuadExpr res(in1.nvar); - - if (in1.nvar == 0) { - res.coeffsLin = -in2.coeffsLin; - res.coeffsQuad = -in2.coeffsQuad; - res.constant = in1.constant - in2.constant; - } - else if (in2.nvar == 0) { - res.coeffsLin = in1.coeffsLin; - res.coeffsQuad = in1.coeffsQuad; - res.constant = in1.constant - in2.constant; - } - else { - res.coeffsLin = in1.coeffsLin - in2.coeffsLin; - res.coeffsQuad = in1.coeffsQuad - in2.coeffsQuad; - res.constant = in1.constant - in2.constant; - } - res.hasQuad = in1.hasQuad || in2.hasQuad; - return res; -} - -/** @brief Operator- for subtraction of an UbpQuadExpr and a double */ -inline UbpQuadExpr -operator-(const UbpQuadExpr& in1, const double& in2) -{ - UbpQuadExpr res(in1.nvar); - res.coeffsLin = in1.coeffsLin; - res.coeffsQuad = in1.coeffsQuad; - res.constant = in1.constant - in2; - res.hasQuad = in1.hasQuad; - return res; -} - -/** @brief Operator- for subtraction of an UbpQuadExpr and an int */ -inline UbpQuadExpr -operator-(const UbpQuadExpr& in1, const int& in2) -{ - UbpQuadExpr res(in1.nvar); - res.coeffsLin = in1.coeffsLin; - res.coeffsQuad = in1.coeffsQuad; - res.constant = in1.constant - in2; - res.hasQuad = in1.hasQuad; - return res; -} - -/** @brief Operator- for subtraction of an UbpQuadExpr and a double */ -inline UbpQuadExpr -operator-(const double& in1, const UbpQuadExpr& in2) -{ - UbpQuadExpr res(in2.nvar); - res.coeffsLin = -in2.coeffsLin; - res.coeffsQuad = -in2.coeffsQuad; - res.constant = in1 - in2.constant; - res.hasQuad = in2.hasQuad; - return res; -} - -/** @brief Operator- for subtraction of an UbpQuadExpr and an int */ -inline UbpQuadExpr -operator-(const int& in1, const UbpQuadExpr& in2) -{ - UbpQuadExpr res(in2.nvar); - res.coeffsLin = -in2.coeffsLin; - res.coeffsQuad = -in2.coeffsQuad; - res.constant = in1 - in2.constant; - res.hasQuad = in2.hasQuad; - return res; -} - -/** @brief Operator* for two UbpQuadExpr objects */ -inline UbpQuadExpr -operator*(const UbpQuadExpr& in1, const UbpQuadExpr& in2) -{ - if (in1.nvar != in2.nvar && (in1.nvar != 0 && in2.nvar != 0)) - throw MAiNGOException(" Error: UbpQuadExpr -- nvar does not fit in * operator."); - - UbpQuadExpr res(in1.nvar); - if (in1.nvar == 0) { - res.coeffsLin = in2.coeffsLin * in1.constant; - res.coeffsQuad = in2.coeffsQuad * in1.constant; - res.constant = in2.constant * in1.constant; - res.hasQuad = in2.hasQuad; - } - else if (in2.nvar == 0) { - res.coeffsLin = in1.coeffsLin * in2.constant; - res.coeffsQuad = in1.coeffsQuad * in2.constant; - res.constant = in1.constant * in2.constant; - res.hasQuad = in1.hasQuad; - } - else { - if (in1.hasQuad || in2.hasQuad) - throw MAiNGOException(" Error: UbpQuadExpr -- multiplications higher than second order are not allowed in (MIQ)Ps."); - - for (size_t i = 0; i < in1.nvar; i++) { - for (size_t j = 0; j < in2.nvar; j++) { - res.coeffsQuad[i][j] = in1.coeffsLin[i] * in2.coeffsLin[j]; - } - res.coeffsLin[i] = in1.coeffsLin[i] * in2.constant + in2.coeffsLin[i] * in1.constant; - } - res.constant = in1.constant * in2.constant; - res.hasQuad = true; - } - return res; -} - -/** @brief Operator* for multiplication of an UbpQuadExpr and a double */ -inline UbpQuadExpr -operator*(const UbpQuadExpr& in1, const double in2) -{ - UbpQuadExpr res(in1.nvar); - res.coeffsLin = in1.coeffsLin * in2; - res.coeffsQuad = in1.coeffsQuad * in2; - res.constant = in1.constant * in2; - res.hasQuad = in1.hasQuad; - return res; -} - -/** @brief Operator* for subtraction of an UbpQuadExpr and an int */ -inline UbpQuadExpr -operator*(const UbpQuadExpr& in1, const int in2) -{ - return in1 * ((double)in2); -} - -/** @brief Operator* for multiplication of an UbpQuadExpr and a double */ -inline UbpQuadExpr -operator*(const double in1, const UbpQuadExpr& in2) -{ - return in2 * in1; -} - -/** @brief Operator* for subtraction of an UbpQuadExpr and an int */ -inline UbpQuadExpr -operator*(const int in1, const UbpQuadExpr& in2) -{ - return in2 * ((double)in1); -} - -/** @brief Operator/ for two UbpQuadExpr*/ -inline UbpQuadExpr -operator/(const UbpQuadExpr& in1, const UbpQuadExpr& in2) -{ - throw MAiNGOException(" Error: UbpQuadExpr -- function x/y not allowed in (MIQ)Ps."); -} - -/** @brief Operator/ for division of an UbpQuadExpr by a double */ -inline UbpQuadExpr -operator/(const UbpQuadExpr& in1, const double in2) -{ - return in1 * (1. / in2); -} - -/** @brief Operator/ for division of an UbpQuadExpr by a double */ -inline UbpQuadExpr -operator/(const UbpQuadExpr& in1, const int in2) -{ - return in1 * (1. / (double)in2); -} - -/** @brief Operator/ for division of a double by an UbpQuadExpr */ -inline UbpQuadExpr -operator/(const double in1, const UbpQuadExpr& in2) -{ - throw MAiNGOException(" Error: UbpQuadExpr -- function 1/x not allowed in (MIQ)Ps."); -} - -/** @brief Operator/ for division of an int by an UbpQuadExpr */ -inline UbpQuadExpr -operator/(const int in1, const UbpQuadExpr& in2) -{ - throw MAiNGOException(" Error: UbpQuadExpr -- function 1/x not allowed in (MIQ)Ps."); -} - - -} // end namespace ubp - - -} // end namespace maingo - - -namespace mc { - - -//! @brief Specialization of the structure mc::Op for use of the type UbpQuadExpr as a template parameter in other MC++ types -template <> -struct Op<maingo::ubp::UbpQuadExpr> { - typedef maingo::ubp::UbpQuadExpr QE; /*!< typedef for easier usage */ - static QE sqr(const QE& x) { return x * x; } /*!< x^2 */ - static QE pow(const QE& x, const int n) - { - if (n == 0) { - return QE(1.0); - } - if (n == 1) { - return x; - } - if (n == 2) { - return x * x; - } - throw std::runtime_error(" Error: UbpQuadExpr -- function pow with n <> 0,1,2 not allowed in (MIQ)Ps."); - } /*!< powers are allowed up to order 2 */ - static QE pow(const QE& x, const double a) - { - if (a == 0) { - return QE(1.0); - } - if (a == 1) { - return x; - } - if (a == 2) { - return x * x; - } - throw std::runtime_error(" Error: UbpQuadExpr -- function pow with a <> 0,1,2 not allowed in (MIQ)Ps."); - } /*!< power are allowed up to order 2 */ - static QE pow(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function pow(x,y) not allowed in (MIQ)Ps."); } /*!< x^y is not allowed */ - static QE pow(const double x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function pow(a,y) not allowed in (MIQ)Ps."); } /*!< c^x is not allowed */ - static QE pow(const int x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function pow(n,y) not allowed in (MIQ)Ps."); } /*!< d^x is not allowed */ - static QE prod(const unsigned int n, const QE* x) { throw std::runtime_error(" Error: UbpQuadExpr -- function prod not allowed in (MIQ)Ps."); } /*!< prod could be allowed but is currently not implemented */ - static QE monom(const unsigned int n, const QE* x, const unsigned* k) { throw std::runtime_error(" Error: UbpQuadExpr -- function monom not allowed in (MIQ)Ps."); } /*!< monom could be allowed but is currently not implemented */ - static QE point(const double c) { throw std::runtime_error(" Error: UbpQuadExpr -- function point not allowed in (MIQ)Ps."); } /*!< point is not needed at all */ - static QE zeroone() { throw std::runtime_error(" Error: UbpQuadExpr -- function zeroone not allowed in (MIQ)Ps."); } /*!< zeroone is not needed at all */ - static void I(QE& x, const QE& y) { x = y; } /*!< even thou I should be understood as interval, it is implemented here as assignment */ - static double l(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function l not allowed in (MIQ)Ps."); } /*!< no lower bound given */ - static double u(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function u not allowed in (MIQ)Ps."); } /*!< no upper bound given */ - static double abs(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function abs not allowed in (MIQ)Ps."); } /*!< abs is not allowed */ - static double mid(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function mid not allowed in (MIQ)Ps."); } /*!< mid not given */ - static double diam(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function diam not allowed in (MIQ)Ps."); } /*!< diam not given */ - static QE inv(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function inv not allowed in (MIQ)Ps."); } /*!< inv is not allowed */ - static QE sqrt(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function sqrt not allowed in (MIQ)Ps."); } /*!< sqrt is not allowed */ - static QE exp(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function exp not allowed in (MIQ)Ps."); } /*!< exp is not allowed */ - static QE log(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function log not allowed in (MIQ)Ps."); } /*!< log is not allowed */ - static QE xlog(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function xlog not allowed in (MIQ)Ps."); } /*!< xlog is not allowed */ - static QE fabsx_times_x(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function fabsx_times_x not allowed in (MIQ)Ps."); } /*!< x*|x| is not allowed */ - static QE xexpax(const QE& x, const double a) { throw std::runtime_error(" Error: UbpQuadExpr -- function xexpax not allowed in (MIQ)Ps."); } /*!< x*exp(a*x) is not allowed */ - static QE lmtd(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function lmtd not allowed in (MIQ)Ps."); } /*!< lmtd is not allowed */ - static QE rlmtd(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function rlmtd not allowed in (MIQ)Ps."); } /*!< rlmtd is not allowed */ - static QE euclidean_norm_2d(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function euclidean_norm_2d not allowed in (MIQ)Ps."); } /*!< euclidean is not allowed */ - static QE expx_times_y(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function expx_times_y not allowed in (MIQ)Ps."); } /*!< exp(x)*y is not allowed */ - static QE vapor_pressure(const QE& x, const double type, const double p1, const double p2, const double p3, const double p4 = 0, const double p5 = 0, const double p6 = 0, - const double p7 = 0, const double p8 = 0, const double p9 = 0, const double p10 = 0) { throw std::runtime_error(" Error: UbpQuadExpr -- function vapor_pressure not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE ideal_gas_enthalpy(const QE& x, const double x0, const double type, const double p1, const double p2, const double p3, const double p4, const double p5, const double p6 = 0, - const double p7 = 0) { throw std::runtime_error(" Error: UbpQuadExpr -- function ideal_gas_enthalpy not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE saturation_temperature(const QE& x, const double type, const double p1, const double p2, const double p3, const double p4 = 0, const double p5 = 0, const double p6 = 0, - const double p7 = 0, const double p8 = 0, const double p9 = 0, const double p10 = 0) { throw std::runtime_error(" Error: UbpQuadExpr -- function saturation_temperature not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE enthalpy_of_vaporization(const QE& x, const double type, const double p1, const double p2, const double p3, const double p4, const double p5, const double p6 = 0) { throw std::runtime_error(" Error: UbpQuadExpr -- function enthalpy_of_vaporization not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE cost_function(const QE& x, const double type, const double p1, const double p2, const double p3) { throw std::runtime_error(" Error: UbpQuadExpr -- function cost_function not allowed in (MIQ)Ps."); } /*!< no cost function function is not allowed */ - static QE nrtl_tau(const QE& x, const double a, const double b, const double e, const double f) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_tau not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE nrtl_dtau(const QE& x, const double b, const double e, const double f) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_dtau not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE nrtl_G(const QE& x, const double a, const double b, const double e, const double f, const double alpha) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_G not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE nrtl_Gtau(const QE& x, const double a, const double b, const double e, const double f, const double alpha) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_Gtau not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE nrtl_Gdtau(const QE& x, const double a, const double b, const double e, const double f, const double alpha) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_Gdtau not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE nrtl_dGtau(const QE& x, const double a, const double b, const double e, const double f, const double alpha) { throw std::runtime_error(" Error: UbpQuadExpr -- function nrtl_dGtau not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE iapws(const QE& x, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function iapws not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE iapws(const QE& x, const QE& y, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function iapws not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE p_sat_ethanol_schroeder(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function p_sat_ethanol_schroeder not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE rho_vap_sat_ethanol_schroeder(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function rho_vap_sat_ethanol_schroeder not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE rho_liq_sat_ethanol_schroeder(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function rho_liq_sat_ethanol_schroeder not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE covariance_function(const QE& x, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function covariance_function not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE acquisition_function(const QE& x, const QE& y, const double type, const double fmin) { throw std::runtime_error(" Error: UbpQuadExpr -- function acquisition_function not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE gaussian_probability_density_function(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function gaussian_probability_density_function not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE regnormal(const QE& x, const double a, const double b) { throw std::runtime_error(" Error: UbpQuadExpr -- function regnormal not allowed in (MIQ)Ps."); } /*!< no thermodynamic function is not allowed */ - static QE fabs(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function fabs not allowed in (MIQ)Ps."); } /*!< fabs function is not allowed */ - static QE sin(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function sin not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE cos(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function cos not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE tan(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function tan not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE asin(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function asin not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE acos(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function acos not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE atan(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function atan not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE sinh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function sinh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE cosh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function cosh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE tanh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function tanh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE coth(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function coth not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE asinh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function asinh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE acosh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function acosh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE atanh(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function atanh not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE acoth(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function acoth not allowed in (MIQ)Ps."); } /*!< trigonometric function is not allowed */ - static QE erf(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function erf not allowed in (MIQ)Ps."); } /*!< erf function is not allowed */ - static QE erfc(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function erfc not allowed in (MIQ)Ps."); } /*!< erfc function is not allowed */ - static QE fstep(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function fstep not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ - static QE bstep(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function bstep not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ - static QE hull(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function hull not allowed in (MIQ)Ps."); } /*!< hull is not given */ - static QE min(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function min not allowed in (MIQ)Ps."); } /*!< min function is not allowed */ - static QE max(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function max not allowed in (MIQ)Ps."); } /*!< max function is not allowed */ - static QE pos(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function pos not allowed in (MIQ)Ps."); } /*!< pos function is not allowed */ - static QE neg(const QE& x) { throw std::runtime_error(" Error: UbpQuadExpr -- function neg not allowed in (MIQ)Ps."); } /*!< neg function is not allowed */ - static QE lb_func(const QE& x, const double lb) { throw std::runtime_error(" Error: UbpQuadExpr -- function lb_func not allowed in (MIQ)Ps."); } /*!< lb_func function is not allowed */ - static QE ub_func(const QE& x, const double ub) { throw std::runtime_error(" Error: UbpQuadExpr -- function ub_func not allowed in (MIQ)Ps."); } /*!< ub_func function is not allowed */ - static QE bounding_func(const QE& x, const double lb, const double ub) { throw std::runtime_error(" Error: UbpQuadExpr -- function bounding_func not allowed in (MIQ)Ps."); } /*!< bounding_func function is not allowed */ - static QE squash_node(const QE& x, const double lb, const double ub) { throw std::runtime_error(" Error: UbpQuadExpr -- function squash_node not allowed in (MIQ)Ps."); } /*!< squash_node function is not allowed */ - static QE sum_div(const std::vector<QE>& x, const std::vector<double>& coeff) { throw std::runtime_error(" Error: UbpQuadExpr -- function sum_div not allowed in (MIQ)Ps."); } /*!< sum_div function is not allowed */ - static QE xlog_sum(const std::vector<QE>& x, const std::vector<double>& coeff) { throw std::runtime_error(" Error: UbpQuadExpr -- function xlog_sum not allowed in (MIQ)Ps."); } /*!< xlog_sum function is not allowed */ - static QE mc_print(const QE& x, const int number) { throw std::runtime_error(" Error: UbpQuadExpr -- function mc_print not allowed in (MIQ)Ps."); } /*!< printing function is not allowed */ - static QE arh(const QE& x, const double k) { throw std::runtime_error(" Error: UbpQuadExpr -- function arh not allowed in (MIQ)Ps."); } /*!< arh function is not allowed */ - static QE cheb(const QE& x, const unsigned n) { throw std::runtime_error(" Error: UbpQuadExpr -- function cheb not allowed in (MIQ)Ps."); } /*!< cheb function is not allowed */ - static bool inter(QE& xIy, const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function inter not allowed in (MIQ)Ps."); } /*!< interior is not given */ - static bool eq(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function eq not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ - static bool ne(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function ne not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ - static bool lt(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function lt not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ - static bool le(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function le not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ - static bool gt(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function gt not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ - static bool ge(const QE& x, const QE& y) { throw std::runtime_error(" Error: UbpQuadExpr -- function ge not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ - static QE centerline_deficit(const QE& x, const double xLim, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function centerline_deficit not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ - static QE wake_profile(const QE& x, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function wake_profile not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ - static QE wake_deficit(const QE& x, const QE& r, const double a, const double alpha, const double rr, const double type1, const double type2) { throw std::runtime_error(" Error: UbpQuadExpr -- function wake_deficit not allowed in (MIQ)Ps."); } /*!< discontinuous function is not allowed */ - static QE power_curve(const QE& x, const double type) { throw std::runtime_error(" Error: UbpQuadExpr -- function power_curve not allowed in (MIQ)Ps."); } /*!< compare function is not allowed */ -}; - - -} // end namespace mc \ No newline at end of file +} // namespace maingo +#else +#include <vector> +#endif diff --git a/inc/usingAdditionalIntrinsicFunctions.h b/inc/usingAdditionalIntrinsicFunctions.h new file mode 100644 index 0000000..e60a89f --- /dev/null +++ b/inc/usingAdditionalIntrinsicFunctions.h @@ -0,0 +1,62 @@ +/********************************************************************************** + * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + + +// Using declarations of all additional functions defined in MC++ for a comfortable use of these functions in the model +using mc::acoth; +using mc::acquisition_function; +using mc::arh; +using mc::bounding_func; +using mc::bstep; +using mc::centerline_deficit; +using mc::cost_function; +using mc::coth; +using mc::covariance_function; +using mc::enthalpy_of_vaporization; +using mc::euclidean_norm_2d; +using mc::expx_times_y; +using mc::fabsx_times_x; +using mc::fstep; +using mc::gaussian_probability_density_function; +using mc::iapws; +using mc::ideal_gas_enthalpy; +using mc::lb_func; +using mc::lmtd; +using mc::mc_print; +using mc::neg; +using mc::nrtl_dGtau; +using mc::nrtl_dtau; +using mc::nrtl_G; +using mc::nrtl_Gdtau; +using mc::nrtl_Gtau; +using mc::nrtl_tau; +using mc::p_sat_ethanol_schroeder; +using mc::pos; +using mc::power_curve; +using mc::regnormal; +using mc::rho_liq_sat_ethanol_schroeder; +using mc::rho_vap_sat_ethanol_schroeder; +using mc::rlmtd; +using mc::saturation_temperature; +using mc::sqr; +using mc::squash_node; +using mc::sum_div; +using mc::ub_func; +using mc::vapor_pressure; +using mc::wake_deficit; +using mc::wake_profile; +using mc::xexpax; +using mc::xlog; +using mc::xlog_sum; +using std::max; +using std::min; \ No newline at end of file diff --git a/inc/variableLister.h b/inc/variableLister.h index 4fbd2c7..e63b62d 100644 --- a/inc/variableLister.h +++ b/inc/variableLister.h @@ -29,9 +29,9 @@ using namespace ale; */ template <unsigned IDim> std::string -var_indexes(size_t* indexes) +var_indexes(size_t* indexes, char delimiter) { - return '_' + std::to_string(indexes[0] + 1) + var_indexes<IDim - 1>(indexes + 1); + return std::to_string(indexes[0] + 1) + delimiter + var_indexes<IDim - 1>(indexes + 1, delimiter); } /** @@ -41,9 +41,9 @@ var_indexes(size_t* indexes) */ template <> inline std::string -var_indexes<1>(size_t* indexes) +var_indexes<1>(size_t* indexes, char delimiter) { - return '_' + std::to_string(indexes[0] + 1); + return std::to_string(indexes[0] + 1); } /** @@ -54,9 +54,9 @@ var_indexes<1>(size_t* indexes) */ template <unsigned IDim> std::string -var_name(std::string base, size_t* indexes) +var_name(std::string base, size_t* indexes, char delimiter) { - return base + var_indexes<IDim>(indexes); + return base + delimiter + var_indexes<IDim>(indexes, delimiter); } /** @@ -116,11 +116,29 @@ class VariableLister { { } + template <unsigned IDim> void operator()(expression_symbol<real<IDim>>* sym) { } + + template <unsigned IDim> + void operator()(function_symbol<real<IDim>>* sym) + { + } + + template <unsigned IDim> + void operator()(function_symbol<index<IDim>>* sym) + { + } + + template <unsigned IDim> + void operator()(function_symbol<boolean<IDim>>* sym) + { + } + + template <unsigned IDim> void operator()(variable_symbol<real<IDim>>* sym) { @@ -136,7 +154,7 @@ class VariableLister { } while (indexes[0] < sym->shape(0)) { if (sym->lower()[indexes] == -std::numeric_limits<double>::infinity() || sym->upper()[indexes] == std::numeric_limits<double>::infinity()) { - throw MAiNGOException(" Error: VariableLister -- Entry of variable " + sym->m_name + " is unbounded"); + throw MAiNGOException(" Error: VariableLister -- Entry of variable " + sym->m_name + "[" + var_indexes<IDim>(indexes, ',') + "] is unbounded"); } maingo::VT vartype = VT_CONTINUOUS; if (sym->integral()) { @@ -147,13 +165,26 @@ class VariableLister { vartype = VT_INTEGER; } } - double lower = sym->lower()[indexes]; - double upper = sym->upper()[indexes]; + double lower = sym->lower()[indexes]; + double upper = sym->upper()[indexes]; + double branching_prio = sym->prio()[indexes]; + if (std::isnan(branching_prio)) { + branching_prio = 1; + } + else if (branching_prio < 0) { + throw MAiNGOException(" Error: VariableLister -- Branching priority of variable entry " + sym->m_name + "[" + var_indexes<IDim>(indexes, ',') + "] is less than 0"); + } + else if (static_cast<unsigned int>(branching_prio) != branching_prio) { + std::cout << " Warning: VariableLister -- Non-integer branching priority of variable entry " << sym->m_name << "[" + var_indexes<IDim>(indexes, ',') + << "].prio = " << branching_prio << ". Setting branching priority to " << static_cast<unsigned int>(branching_prio) << ".\n"; + branching_prio = static_cast<unsigned int>(branching_prio); + } _variables.push_back( OptimizationVariable( Bounds(lower, upper), vartype, - var_name<IDim>(sym->m_name, indexes))); + branching_prio, + var_name<IDim>(sym->m_name, indexes, '_'))); double initial = sym->init()[indexes]; if (std::isnan(initial)) { initial = 0.5 * (lower + upper); @@ -186,12 +217,25 @@ class VariableLister { vartype = VT_INTEGER; } } - double lower = sym->lower(); - double upper = sym->upper(); + double lower = sym->lower(); + double upper = sym->upper(); + double branching_prio = sym->prio(); + if (std::isnan(branching_prio)) { + branching_prio = 1; + } + else if (branching_prio < 0) { + throw MAiNGOException(" Error: VariableLister -- Branching priority of variable " + sym->m_name + " is less than 0"); + } + else if (static_cast<unsigned int>(branching_prio) != branching_prio) { + std::cout << " Warning: VariableLister -- Non-integer branching priority of variable " << sym->m_name + << ".prio = " << branching_prio << ". Setting branching priority to " << static_cast<unsigned int>(branching_prio) << ".\n"; + branching_prio = static_cast<unsigned int>(branching_prio); + } _variables.push_back( OptimizationVariable( Bounds(lower, upper), vartype, + branching_prio, sym->m_name)); double initial = sym->init(); if (std::isnan(initial)) { diff --git a/maingopy/__init__maingopy_and_melonpy.py.in b/maingopy/__init__maingopy_and_melonpy.py.in index 312bede..2ef6261 100644 --- a/maingopy/__init__maingopy_and_melonpy.py.in +++ b/maingopy/__init__maingopy_and_melonpy.py.in @@ -1,7 +1,7 @@ '''A Python package for using MAiNGO - McCormick-based Algorithm for mixed-integer Nonlinear Global Optimization MAiNGO is a deterministic global optimization solver for mixed-integer nonlinear programming problems. -For information about the capabilities and use of MAiNGO, please refer to the documentation at https://git.rwth-aachen.de/avt.svt/public/maingo. +For information about the capabilities and use of MAiNGO, please refer to the documentation at https://avt-svt.pages.rwth-aachen.de/public/maingo/. This version of maingopy also contains the extension module melonpy, which enables the use of MeLOn (https://git.rwth-aachen.de/avt.svt/public/melon), a toolbox containing machine learning models for use in optimization problems to be solved by MAiNGO. ''' diff --git a/maingopy/__init__only_maingopy.py.in b/maingopy/__init__only_maingopy.py.in index 87703ba..bbef2d1 100644 --- a/maingopy/__init__only_maingopy.py.in +++ b/maingopy/__init__only_maingopy.py.in @@ -1,6 +1,6 @@ '''A Python package for using MAiNGO - McCormick-based Algorithm for mixed-integer Nonlinear Global Optimization MAiNGO is a deterministic global optimization solver for mixed-integer nonlinear programming problems. -For information about the capabilities and use of MAiNGO, please refer to the documentation at https://git.rwth-aachen.de/avt.svt/public/maingo.; +For information about the capabilities and use of MAiNGO, please refer to the documentation at https://avt-svt.pages.rwth-aachen.de/public/maingo/.; ''' from ._maingopy import * \ No newline at end of file diff --git a/maingopy/_maingopy.cpp b/maingopy/_maingopy.cpp index c31b678..efe172f 100644 --- a/maingopy/_maingopy.cpp +++ b/maingopy/_maingopy.cpp @@ -151,6 +151,11 @@ PYBIND11_MODULE(_maingopy, m) .value("UBP_SOLVER_IPOPT", maingo::ubp::UBP_SOLVER::UBP_SOLVER_IPOPT) .value("UBP_SOLVER_KNITRO", maingo::ubp::UBP_SOLVER::UBP_SOLVER_KNITRO) .export_values(); + py::enum_<maingo::AUGMENTATION_RULE>(m, "AUGMENTATION_RULE") + .value("AUG_RULE_CONST", maingo::AUGMENTATION_RULE::AUG_RULE_CONST) + .value("AUG_RULE_SCALING", maingo::AUGMENTATION_RULE::AUG_RULE_SCALING) + .value("AUG_RULE_SCALCST", maingo::AUGMENTATION_RULE::AUG_RULE_SCALCST) + .export_values(); // Expose the MAiNGOmodel class via the dummy class defined above py::class_<maingo::MAiNGOmodel, PyMAiNGOmodel, std::shared_ptr<maingo::MAiNGOmodel>>(m, "MAiNGOmodel") @@ -366,6 +371,8 @@ PYBIND11_MODULE(_maingopy, m) .def(py::init<>()) .def_readwrite("objective", &maingo::EvaluationContainer::objective) .def_readwrite("obj", &maingo::EvaluationContainer::objective) + .def_readwrite("objective_per_data", &maingo::EvaluationContainer::objective_per_data) + .def_readwrite("objData", &maingo::EvaluationContainer::objective_per_data) .def_readwrite("eq", &maingo::EvaluationContainer::eq) .def_readwrite("equalities", &maingo::EvaluationContainer::eq) .def_readwrite("ineq", &maingo::EvaluationContainer::ineq) diff --git a/maingopy/tests/individualPythonTests/testSolver.py b/maingopy/tests/individualPythonTests/testSolver.py index 8c68663..befd17b 100644 --- a/maingopy/tests/individualPythonTests/testSolver.py +++ b/maingopy/tests/individualPythonTests/testSolver.py @@ -32,7 +32,7 @@ class TestMAiNGO(unittest.TestCase): myRetCode = NOT_SOLVED_YET myRetCode = JUST_A_WORKER_DONT_ASK_ME except: - self.fail("Value of enum RETCODE not avilable") + self.fail("Value of enum RETCODE not available") def test_VERB_enum(self): try: @@ -40,7 +40,7 @@ class TestMAiNGO(unittest.TestCase): verbosity = VERB_NORMAL verbosity = VERB_ALL except: - self.fail("Value of enum VERB not avilable") + self.fail("Value of enum VERB not available") def test_LOGGING_DESTINATION_enum(self): try: @@ -49,7 +49,7 @@ class TestMAiNGO(unittest.TestCase): verbosity = LOGGING_FILE verbosity = LOGGING_FILE_AND_STREAM except: - self.fail("Value of enum LOGGING_DESTINATION not avilable") + self.fail("Value of enum LOGGING_DESTINATION not available") def test_LBP_SOLVER_enum(self): try: @@ -58,7 +58,7 @@ class TestMAiNGO(unittest.TestCase): lbpSolver = LBP_SOLVER_CPLEX lbpSolver = LBP_SOLVER_CLP except: - self.fail("Value of enum LBP_SOLVER not avilable") + self.fail("Value of enum LBP_SOLVER not available") def test_UBP_SOLVER_enum(self): try: @@ -70,7 +70,7 @@ class TestMAiNGO(unittest.TestCase): ubpSolver = UBP_SOLVER_IPOPT ubpSolver = UBP_SOLVER_KNITRO except: - self.fail("Value of enum UBP_SOLVER not avilable") + self.fail("Value of enum UBP_SOLVER not available") def test_LBP_SOLVER_enum(self): try: @@ -79,7 +79,7 @@ class TestMAiNGO(unittest.TestCase): lbpSolver = LBP_SOLVER_CPLEX lbpSolver = LBP_SOLVER_CLP except: - self.fail("Value of enum LBP_SOLVER not avilable") + self.fail("Value of enum LBP_SOLVER not available") def test_WRITING_LANGUAGE_enum(self): try: @@ -87,7 +87,7 @@ class TestMAiNGO(unittest.TestCase): myLanguage = LANG_ALE myLanguage = LANG_GAMS except: - self.fail("Value of enum PARSING_LANGUAGE not avilable") + self.fail("Value of enum PARSING_LANGUAGE not available") def test_initialize_maingo(self): try: @@ -253,5 +253,50 @@ class TestMAiNGO(unittest.TestCase): +# ----- Testing extension "B&B algorithm with growing datasets" + +class ModelSimpleGrowing(MAiNGOmodel): + def __init__(self): + MAiNGOmodel.__init__(self) + + def get_variables(self): + variables = [OptimizationVariable(Bounds(0,5))] + return variables + + def evaluate(self,vars): + inputValues = [ 1,1,1] + outputValues = [1,0.6,0] + result = EvaluationContainer() + se = 0 + for i in range(3): + predictedValue = sqr(vars[0])*inputValues[i] + se_per_data = sqr(predictedValue - outputValues[i]) + se = se + se_per_data + result.objData.append(se_per_data) + result.obj = se + result.out = [OutputVariable("slope", sqr(vars[0]))] + return result + + + +class TestMAiNGOgrowingDatasets(unittest.TestCase): + def test_AUGMENTATION_RULE_enum(self): + try: + myRetCode = AUG_RULE_CONST + myRetCode = AUG_RULE_SCALING + myRetCode = AUG_RULE_SCALCST + except: + self.fail("Value of enum AUG_RULE not available") + + def test_initialize_maingo(self): + try: + myMAiNGO = MAiNGO(ModelSimpleGrowing()) + except: + self.fail("Initialization of MAiNGO raised exception unexpectedly") + +# ----- End of tests for growing datasets + + + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/src/MAiNGO.cpp b/src/MAiNGO.cpp index 78087fd..a583863 100644 --- a/src/MAiNGO.cpp +++ b/src/MAiNGO.cpp @@ -32,6 +32,9 @@ MAiNGO::MAiNGO(std::shared_ptr<MAiNGOmodel> myModel) // Set MPI variables MPI_Comm_rank(MPI_COMM_WORLD, &_rank); MPI_Comm_size(MPI_COMM_WORLD, &_nProcs); + if (_nProcs < 2) { + throw MAiNGOException(" Error initializing MAiNGO: The parallel version of MAiNGO requires at least 2 MPI processes."); + } #endif // Initialize internal model representation @@ -57,23 +60,23 @@ MAiNGO::solve() _preprocessTime = get_cpu_time(); _solutionTimeWallClock = get_wall_time(); _logger->clear(); - _logger->create_log_file(_maingoSettings->loggingDestination); + _logger->create_log_file(); _print_MAiNGO_header(); #ifdef HAVE_MAiNGO_MPI _logger->print_message("\n You are using the parallel MAiNGO version. This run uses " + std::to_string(_nProcs) + " processes ( 1 manager and " + std::to_string(_nProcs - 1) + " workers ).\n", - _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + VERB_NORMAL, BAB_VERBOSITY); #endif MAiNGO_END_IF MAiNGO_MPI_BARRIER - // --------------------------------------------------------------------------------- - // 1: Preliminaries: inform about changed settings and write model to file in other language - // --------------------------------------------------------------------------------- - _maingoOriginalSettings = *_maingoSettings; // Save original settings + // --------------------------------------------------------------------------------- + // 1: Preliminaries: inform about changed settings and write model to file in other language + // --------------------------------------------------------------------------------- + _maingoOriginalSettings = *_maingoSettings; // Save original settings MAiNGO_IF_BAB_MANAGER - _logger->print_settings(_maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - + _logger->print_settings(VERB_NORMAL, BAB_VERBOSITY); + // Write MAiNGO model to other language if desired if (_maingoSettings->modelWritingLanguage != LANG_NONE) { _inMAiNGOsolve = true; @@ -94,7 +97,7 @@ MAiNGO::solve() outstr << " Writing to file \"MAiNGO_written_model" + str + "\" took:\n"; outstr << " CPU time: " << std::fixed << std::setprecision(3) << tmpTimeCPU << " seconds.\n"; outstr << " Wall-clock time: " << std::fixed << std::setprecision(3) << tmpTimeWall << " seconds.\n"; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY); // Reset times, since we don't want to add the file writing to the final MAiNGO solution time _preprocessTime = get_cpu_time(); _solutionTimeWallClock = get_wall_time(); @@ -126,7 +129,16 @@ MAiNGO::solve() throw; MAiNGO_END_IF } - _print_info_about_initial_point(); + if (_nobj > 1) { + MAiNGO_IF_BAB_MANAGER + throw MAiNGOException(" Error: Problem contains more than one objective. Did you want to call solve_epsilon_constraint instead of solve?"); + MAiNGO_ELSE + throw; + MAiNGO_END_IF + } + MAiNGO_IF_BAB_MANAGER + _print_info_about_initial_point(); + MAiNGO_END_IF // --------------------------------------------------------------------------------- // 3: Determine structure, set constraint properties, and invoke internal solution routine @@ -178,12 +190,12 @@ MAiNGO::solve_epsilon_constraint() cpuTimeEpsCon = 0.; // We compute the CPU time by suming over all runs wallTimeEpsCon = get_wall_time(); _logger->clear(); - _logger->create_log_file(_maingoSettings->loggingDestination); + _logger->create_log_file(); _logger->create_iterations_csv_file(_maingoSettings->writeCsv); _print_MAiNGO_header(); #ifdef HAVE_MAiNGO_MPI _logger->print_message("\n You are using the parallel MAiNGO version. This run uses " + std::to_string(_nProcs) + " processes ( 1 manager and " + std::to_string(_nProcs - 1) + " workers ).\n", - _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + VERB_NORMAL, BAB_VERBOSITY); #endif MAiNGO_END_IF MAiNGO_MPI_BARRIER @@ -194,10 +206,10 @@ MAiNGO::solve_epsilon_constraint() // --------------------------------------------------------------------------------- _maingoOriginalSettings = *_maingoSettings; // Save original settings MAiNGO_IF_BAB_MANAGER - _logger->print_settings(_maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_settings(VERB_NORMAL, BAB_VERBOSITY); // Don't write MAiNGO model to other language, even if desired if (_maingoSettings->modelWritingLanguage != LANG_NONE) { - _logger->print_message(" Warning: Not writing to other language when solving multi-objective problem.", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Warning: Not writing to other language when solving multi-objective problem.", VERB_NORMAL, BAB_VERBOSITY); } MAiNGO_END_IF MAiNGO_MPI_BARRIER @@ -495,7 +507,7 @@ MAiNGO::solve_epsilon_constraint() _write_files(); _write_epsilon_constraint_result(optimalObjectives, solutionPoints); // Print time (slight abuse of regular time variables...) - _logger->print_message("\n Overall time for epsilon constraint method:", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n Overall time for epsilon constraint method:", VERB_NORMAL, BAB_VERBOSITY); _preprocessTime = 0.; _babTime = cpuTimeEpsCon; _outputTime = get_cpu_time(); @@ -551,45 +563,60 @@ MAiNGO::_analyze_and_solve_problem() #ifdef HAVE_CPLEX // If we have CPLEX, we can use of it directly for problems of type LP, MIP, QP, or MIQP case LP: MAiNGO_IF_BAB_MANAGER - _logger->print_message("\n Recognized the problem to be a linear program.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n Recognized the problem to be a linear program.\n", VERB_NORMAL, BAB_VERBOSITY); +#ifdef HAVE_GROWING_DATASETS + _logger->print_message("\n Growing datasets will not be used.\n", VERB_NORMAL, BAB_VERBOSITY); +#endif //HAVE_GROWING_DATASETS MAiNGO_END_IF return _solve_MIQP(); break; case MIP: MAiNGO_IF_BAB_MANAGER - _logger->print_message("\n Recognized the problem to be a mixed-integer linear program.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n Recognized the problem to be a mixed-integer linear program.\n", VERB_NORMAL, BAB_VERBOSITY); +#ifdef HAVE_GROWING_DATASETS + _logger->print_message("\n Growing datasets will not be used.\n", VERB_NORMAL, BAB_VERBOSITY); +#endif //HAVE_GROWING_DATASETS MAiNGO_END_IF return _solve_MIQP(); break; case QP: MAiNGO_IF_BAB_MANAGER - _logger->print_message("\n Recognized the problem to be a quadratic program.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n Recognized the problem to be a quadratic program.\n", VERB_NORMAL, BAB_VERBOSITY); +#ifdef HAVE_GROWING_DATASETS + _logger->print_message("\n Growing datasets will not be used.\n", VERB_NORMAL, BAB_VERBOSITY); +#endif //HAVE_GROWING_DATASETS MAiNGO_END_IF return _solve_MIQP(); break; case MIQP: MAiNGO_IF_BAB_MANAGER - _logger->print_message("\n Recognized the problem to be a mixed-integer quadratic program.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n Recognized the problem to be a mixed-integer quadratic program.\n", VERB_NORMAL, BAB_VERBOSITY); +#ifdef HAVE_GROWING_DATASETS + _logger->print_message("\n Growing datasets will not be used.\n", VERB_NORMAL, BAB_VERBOSITY); +#endif //HAVE_GROWING_DATASETS MAiNGO_END_IF return _solve_MIQP(); break; #else // If we don't CPLEX, we only pass LPs to CLP and solve all other problems as general MINLP case LP: MAiNGO_IF_BAB_MANAGER - _logger->print_message("\n Recognized the problem to be a linear program.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n Recognized the problem to be a linear program.\n", VERB_NORMAL, BAB_VERBOSITY); +#ifdef HAVE_GROWING_DATASETS + _logger->print_message("\n Growing datasets will not be used.\n", VERB_NORMAL, BAB_VERBOSITY); +#endif //HAVE_GROWING_DATASETS MAiNGO_END_IF return _solve_MIQP(); break; case QP: - _logger->print_message("\n Recognized the problem to be a quadratic program, but no dedicated QP solver is available.\n Solving it as an NLP.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n Recognized the problem to be a quadratic program, but no dedicated QP solver is available.\n Solving it as an NLP.\n", VERB_NORMAL, BAB_VERBOSITY); _problemStructure = NLP; return _solve_MINLP(); case MIP: - _logger->print_message("\n Recognized the problem to be a mixed-integer linear program, but no dedicated MILP solver is available.\n Solving it as an MINLP.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n Recognized the problem to be a mixed-integer linear program, but no dedicated MILP solver is available.\n Solving it as an MINLP.\n", VERB_NORMAL, BAB_VERBOSITY); _problemStructure = MINLP; return _solve_MINLP(); case MIQP: - _logger->print_message("\n Recognized the problem to be a mixed-integer quadratic program, but no dedicated MIQP solver is available.\n Solving it as an MINLP.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n Recognized the problem to be a mixed-integer quadratic program, but no dedicated MIQP solver is available.\n Solving it as an MINLP.\n", VERB_NORMAL, BAB_VERBOSITY); _problemStructure = MINLP; return _solve_MINLP(); #endif @@ -629,38 +656,38 @@ MAiNGO::_solve_MIQP() // --------------------------------------------------------------------------------- // 1a: Initialize & start timing + std::string aboutSolver; #ifdef HAVE_CPLEX switch (_maingoSettings->LBP_solver) { case lbp::LBP_SOLVER_MAiNGO: { - _logger->print_message("\n MAiNGO solver is not available as (mixed-integer) linear/quadratic solver. Calling CPLEX.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + aboutSolver = " MAiNGO solver is not available as (mixed-integer) linear/quadratic solver. Calling CPLEX.\n"; _maingoSettings->UBP_solverPreprocessing = ubp::UBP_SOLVER_CPLEX; break; } case lbp::LBP_SOLVER_INTERVAL: { - _logger->print_message("\n Interval solver is not available as (mixed-integer) linear/quadratic solver. Calling CPLEX.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + aboutSolver = " Interval solver is not available as (mixed-integer) linear/quadratic solver. Calling CPLEX.\n"; _maingoSettings->UBP_solverPreprocessing = ubp::UBP_SOLVER_CPLEX; break; } case lbp::LBP_SOLVER_CPLEX: { - _logger->print_message("\n Calling CPLEX.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + aboutSolver = " Calling CPLEX.\n"; _maingoSettings->UBP_solverPreprocessing = ubp::UBP_SOLVER_CPLEX; break; } case lbp::LBP_SOLVER_CLP: { if (_problemStructure > LP) { - _logger->print_message("\n CLP is not available as (mixed-integer) linear/quadratic solver. Calling CPLEX.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + aboutSolver = " CLP is not available as (mixed-integer) linear/quadratic solver. Calling CPLEX.\n"; _maingoSettings->UBP_solverPreprocessing = ubp::UBP_SOLVER_CPLEX; } else { - _logger->print_message("\n Calling CLP.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + aboutSolver = " Calling CLP.\n"; _maingoSettings->UBP_solverPreprocessing = ubp::UBP_SOLVER_CLP; } break; } - default: - { + default: { std::ostringstream errmsg; - errmsg << " Error in _solve_MIQP: Unknown lower bounding solver: " << _maingoSettings->LBP_solver; + errmsg << " Error in _solve_MIQP: Unknown lower bounding solver: " << _maingoSettings->LBP_solver; throw MAiNGOException(errmsg.str()); } } @@ -668,35 +695,35 @@ MAiNGO::_solve_MIQP() // It is not possible to reach this point with a problem which is not an LP due to the code in lines 564-601 switch (_maingoSettings->LBP_solver) { case lbp::LBP_SOLVER_MAiNGO: { - _logger->print_message("\n MAiNGO solver is not available as a linear solver. Calling CLP.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + aboutSolver = " MAiNGO solver is not available as a linear solver. Calling CLP.\n"; _maingoSettings->UBP_solverPreprocessing = ubp::UBP_SOLVER_CLP; break; } case lbp::LBP_SOLVER_INTERVAL: { - _logger->print_message("\n Interval solver is not available as a linear solver. Calling CLP.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + aboutSolver = " Interval solver is not available as a linear solver. Calling CLP.\n"; _maingoSettings->UBP_solverPreprocessing = ubp::UBP_SOLVER_CLP; break; } case lbp::LBP_SOLVER_CPLEX: { - _logger->print_message("\n CPLEX is not available on your machine. Calling CLP.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + aboutSolver = " CPLEX is not available on your machine. Calling CLP.\n"; _maingoSettings->UBP_solverPreprocessing = ubp::UBP_SOLVER_CLP; break; } case lbp::LBP_SOLVER_CLP: { - _logger->print_message("\n Calling CLP.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + aboutSolver = " Calling CLP.\n"; _maingoSettings->UBP_solverPreprocessing = ubp::UBP_SOLVER_CLP; break; } - default: - { + default: { std::ostringstream errmsg; - errmsg << " Error in _solve_MIQP: Unknown lower bounding solver: " << _maingoSettings->LBP_solver; + errmsg << " Error in _solve_MIQP: Unknown lower bounding solver: " << _maingoSettings->LBP_solver; throw MAiNGOException(errmsg.str()); } } #endif _print_third_party_software_miqp(); _initialize_solve(); + _logger->print_message(aboutSolver, VERB_NORMAL, BAB_VERBOSITY); _preprocessTime = get_cpu_time() - _preprocessTime; // --------------------------------------------------------------------------------- @@ -791,7 +818,7 @@ MAiNGO::_solve_MINLP() MAiNGO_IF_BAB_MANAGER _print_third_party_software_minlp(); - _logger->print_message("\n Pre-processing at root node:\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n Pre-processing at root node:\n", VERB_NORMAL, BAB_VERBOSITY); if (_maingoSettings->LBP_addAuxiliaryVars) { std::ostringstream ostr; if (_nvarLbd - _nvar == 1) { @@ -800,10 +827,10 @@ MAiNGO::_solve_MINLP() else { ostr << " Added " << _nvarLbd - _nvar << " auxiliary variables...\n"; } - _logger->print_message(ostr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(ostr.str(), VERB_NORMAL, BAB_VERBOSITY); } // This stands BEFORE _initialize_solve, since it is checked in _initialize_solve() whether the user has CPLEX installed - _logger->print_message(" Initialize subsolvers...\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Initialize subsolvers...\n", VERB_NORMAL, BAB_VERBOSITY); MAiNGO_END_IF _initialize_solve(); @@ -867,7 +894,7 @@ MAiNGO::_solve_MINLP() std::ostringstream outstr; outstr << " CPU time: " << std::setprecision(6) << _preprocessTime << " s." << std::endl; outstr << " Done." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY); MAiNGO_END_IF #ifdef HAVE_MAiNGO_MPI @@ -887,6 +914,14 @@ MAiNGO::_solve_MINLP() if (_rootConPropStatus != TIGHTENING_INFEASIBLE && _rootObbtStatus != TIGHTENING_INFEASIBLE && !_maingoSettings->PRE_pureMultistart && !(_maingoSettings->terminateOnFeasiblePoint && _rootMultistartStatus == SUBSOLVER_FEASIBLE) && _solutionValue > _maingoSettings->targetUpperBound) { +#ifdef HAVE_GROWING_DATASETS + _myBaB->pass_datasets_to_bab(_datasets); + + //Change full dataset (root for pre-processing) to smallest reduced dataset (root of BaB) if these are different + if (_ndata > 1) { + _rootNode.set_index_dataset(1); + } +#endif _logger->create_iterations_csv_file(_maingoSettings->writeCsv); _babStatus = _myBaB->solve(_rootNode, _solutionValue, _solutionPoint, _preprocessTime, _babTime); _babTime -= _preprocessTime; // Get the B&B time only @@ -1053,6 +1088,9 @@ MAiNGO::_construct_DAG() _modelOutput.clear(); try { _modelOutput = _myFFVARmodel->evaluate(tmpDAGVars); +#ifdef HAVE_GROWING_DATASETS + _initialize_objective_from_objective_per_data(); +#endif } catch (std::exception& e) { throw MAiNGOException(" MAiNGO: Error while evaluating specified model to construct DAG.", e); @@ -1062,6 +1100,9 @@ MAiNGO::_construct_DAG() } _classify_objective_and_constraints(tmpFunctions, tmpDAGVars); +#ifdef HAVE_GROWING_DATASETS + _initialize_dataset(); +#endif // Recognize and remove variables that do not participate in the actual problem // Recognize first @@ -1168,7 +1209,66 @@ MAiNGO::_construct_DAG() // Just to make sure, check one last time _check_for_hidden_zero_constraints(_DAGvarsLbd, _DAGfunctionsLbd, _DAGoutputFunctionsLbd); - _add_auxiliary_variables_to_lbd_dag(); + try { + _add_auxiliary_variables_to_lbd_dag(); + } + catch (const filib::interval_io_exception& e) { + MAiNGO_IF_BAB_MANAGER + const std::string errmsg = " Encountered a fatal error in intervals while adding auxiliary variables to the DAG used for lower bounding."; + std::ostringstream completeMessage; + completeMessage << e.what() << std::endl + << errmsg; + _write_files_error(completeMessage.str()); + throw MAiNGOException(completeMessage.str()); + MAiNGO_ELSE + throw; + MAiNGO_END_IF + } + catch (const MC::Exceptions& e) { + MAiNGO_IF_BAB_MANAGER + const std::string errmsg = " Encountered a fatal error in McCormick relaxations while adding auxiliary variables to the DAG used for lower bounding."; + std::ostringstream completeMessage; + completeMessage << e.what() << std::endl + << errmsg; + _write_files_error(completeMessage.str()); + throw MAiNGOException(completeMessage.str()); + MAiNGO_ELSE + throw; + MAiNGO_END_IF + } + catch (const vMC::Exceptions& e) { + MAiNGO_IF_BAB_MANAGER + const std::string errmsg = " Encountered a fatal error in vMcCormick relaxations while adding auxiliary variables to the DAG used for lower bounding."; + std::ostringstream completeMessage; + completeMessage << e.what() << std::endl + << errmsg; + _write_files_error(completeMessage.str()); + throw MAiNGOException(completeMessage.str()); + MAiNGO_ELSE + throw; + MAiNGO_END_IF + } + catch (const std::exception& e) { + MAiNGO_IF_BAB_MANAGER + const std::string errmsg = " Encountered a fatal error while adding auxiliary variables to the DAG used for lower bounding."; + std::ostringstream completeMessage; + completeMessage << e.what() << std::endl + << errmsg; + _write_files_error(completeMessage.str()); + throw MAiNGOException(errmsg, e); + MAiNGO_ELSE + throw; + MAiNGO_END_IF + } + catch (...) { + MAiNGO_IF_BAB_MANAGER + const std::string errmsg = " Encountered an unknown fatal error while adding auxiliary variables to the DAG used for lower bounding."; + _write_files_error(errmsg); + throw MAiNGOException(errmsg); + MAiNGO_ELSE + throw; + MAiNGO_END_IF + } } _DAGconstructed = true; @@ -1190,6 +1290,11 @@ MAiNGO::_initialize_solve() // Initialize subsolvers (upper bounding is always needed, lower bounding and B&B are not) _myUBSPre = ubp::make_ubp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _nineq, _neq, _nineqSquash, _maingoSettings, _logger, _nonconstantConstraintsUBP, ubp::UpperBoundingSolver::USE_PRE); +#ifdef HAVE_GROWING_DATASETS + //objective per data saved after obj and non-constant constraints + _myUBSPre->pass_data_position_to_solver(_datasets, 1 + _nineq + _neq + _nineqSquash); +#endif // HAVE_GROWING_DATASETS + _myUBSBab = nullptr; _myLBS = nullptr; _myBaB = nullptr; @@ -1204,10 +1309,30 @@ MAiNGO::_initialize_solve() _myLBS = lbp::make_lbp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly, _nineqSquash, _maingoSettings, _logger, _nonconstantConstraints); } +#ifdef HAVE_GROWING_DATASETS + //objective per data saved after obj and non-constant constraints + _myUBSBab->pass_data_position_to_solver(_datasets, 1 + _nineq + _neq + _nineqSquash); + _myLBS->pass_data_position_to_solver(_datasets, 1 + _nineq + _neq + _nineqSquash + _nineqRelaxationOnly + _neqRelaxationOnly + _nauxiliaryRelOnlyEqs); +#endif // HAVE_GROWING_DATASETS + _myBaB = std::make_shared<bab::BranchAndBound>(_variablesLbd, _myLBS, _myUBSBab, _maingoSettings, _logger, /*number of variables w/o auxiliaries*/ _nvar); + +#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) + _myLBSFull = nullptr; + if (_maingoSettings->LBP_addAuxiliaryVars) { + _myLBSFull = lbp::make_lbp_solver(_DAGlbd, _DAGvarsLbd, _DAGfunctionsLbd, _variablesLbd, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly + _nauxiliaryRelOnlyEqs, + _nineqSquash, _maingoSettings, _logger, _nonconstantConstraints); + } + else { + _myLBSFull = lbp::make_lbp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly, + _nineqSquash, _maingoSettings, _logger, _nonconstantConstraints); + } + _myBaB->pass_LBSFull_to_bab(_myLBSFull); +#endif } } + // Initialize solution variables _solutionPoint.clear(); _solutionValue = _maingoSettings->infinity; @@ -1228,11 +1353,12 @@ MAiNGO::_initialize_solve() _babStatus = babBase::enums::NOT_SOLVED_YET; // Initialize root node + // with full dataset (MAiNGO with growing datasets) if (_maingoSettings->LBP_addAuxiliaryVars) { - _rootNode = babBase::BabNode(-_maingoSettings->infinity, _variablesLbd, 0, 0, false); + _rootNode = babBase::BabNode(-_maingoSettings->infinity, _variablesLbd, 0 /*index dataset*/, 0 /*ID*/, 0 /* depth */, false /*augment data*/); } else { - _rootNode = babBase::BabNode(-_maingoSettings->infinity, _variables, 0, 0, false); + _rootNode = babBase::BabNode(-_maingoSettings->infinity, _variables, 0 /*index dataset*/, 0 /*ID*/, 0 /* depth */, false /*augment data*/); } // Clear logging (except for settings) @@ -1246,19 +1372,23 @@ MAiNGO::_initialize_solve() switch (_originalVariables[i].get_variable_type()) { case babBase::enums::VT_BINARY: variableType = "binary"; + break; case babBase::enums::VT_INTEGER: variableType = "integer"; + break; default: break; } if (_originalVariables[i].bounds_changed_from_user_input()) { std::ostringstream ostr; - ostr << " Warning: bounds of " << variableType << " variable " << _originalVariables[i].get_name() << " are changed from user input " + ostr << " Changing bounds of " << variableType << " variable " << _originalVariables[i].get_name() << " from " << "[" << _originalVariables[i].get_user_lower_bound() << ", " << _originalVariables[i].get_user_upper_bound() << "] to " << "[" << _originalVariables[i].get_lower_bound() << ", " << _originalVariables[i].get_upper_bound() << "].\n"; - _logger->print_message(ostr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(ostr.str(), VERB_NORMAL, BAB_VERBOSITY); } } + + std::srand(42); // For reproducible results despite (pseudo-)random starting points } @@ -1269,11 +1399,11 @@ MAiNGO::_root_obbt_feasibility() { MAiNGO_IF_BAB_MANAGER - _logger->print_message(" Optimization-based bound tightening (feasibility only)...\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Optimization-based bound tightening (feasibility only)...\n", VERB_NORMAL, BAB_VERBOSITY); for (unsigned iLP = 0; iLP < _maingoSettings->PRE_obbtMaxRounds; iLP++) { - _logger->print_message(" Run " + std::to_string(iLP + 1) + "\n", _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(" Run " + std::to_string(iLP + 1) + "\n", VERB_ALL, BAB_VERBOSITY); try { _rootObbtStatus = _myLBS->solve_OBBT(_rootNode, _maingoSettings->infinity, lbp::OBBT_FEAS); } @@ -1300,7 +1430,7 @@ MAiNGO::_root_obbt_feasibility() } if (_rootObbtStatus == TIGHTENING_INFEASIBLE) { - _logger->print_message(" Found problem to be infeasible.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Found problem to be infeasible.\n", VERB_NORMAL, BAB_VERBOSITY); } #ifdef HAVE_MAiNGO_MPI // Send results to workers @@ -1340,7 +1470,7 @@ MAiNGO::_root_obbt_feasibility_optimality() { MAiNGO_IF_BAB_MANAGER - _logger->print_message(" Optimization-based bound tightening (feasibility and optimality)...\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Optimization-based bound tightening (feasibility and optimality)...\n", VERB_NORMAL, BAB_VERBOSITY); babBase::BabNode tmpNode(_rootNode); try { @@ -1366,7 +1496,7 @@ MAiNGO::_root_obbt_feasibility_optimality() std::string str = " Warning: OBBT declared the problem infeasible although a feasible point was found.\n"; str += " This may be caused by numerical difficulties or an isolated optimum in your model.\n"; str += " Turning off OBBT, restoring valid bounds and proceeding...\n"; - _logger->print_message(str, _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(str, VERB_NORMAL, BAB_VERBOSITY); _maingoSettings->PRE_obbtMaxRounds = 0; _maingoSettings->BAB_alwaysSolveObbt = false; _rootObbtStatus = TIGHTENING_UNCHANGED; @@ -1412,7 +1542,7 @@ MAiNGO::_root_constraint_propagation() { MAiNGO_IF_BAB_MANAGER - _logger->print_message(" Constraint propagation...\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Constraint propagation...\n", VERB_NORMAL, BAB_VERBOSITY); babBase::BabNode tmpNode(_rootNode); if (_rootMultistartStatus == SUBSOLVER_FEASIBLE) { @@ -1429,7 +1559,7 @@ MAiNGO::_root_constraint_propagation() std::string str = " Warning: Constraint propagation declared the problem infeasible although a feasible point was found.\n"; str += " This may be caused by numerical difficulties.\n"; str += " Turning off constraint propagation, restoring valid bounds and proceeding...\n"; - _logger->print_message(str, _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(str, VERB_NORMAL, BAB_VERBOSITY); _maingoSettings->BAB_constraintPropagation = false; _rootConPropStatus = TIGHTENING_UNCHANGED; #ifdef HAVE_MAiNGO_MPI @@ -1438,7 +1568,7 @@ MAiNGO::_root_constraint_propagation() #endif } else { - _logger->print_message(" Found problem to be infeasible.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Found problem to be infeasible.\n", VERB_NORMAL, BAB_VERBOSITY); #ifdef HAVE_MAiNGO_MPI BCAST_TAG tag = BCAST_INFEASIBLE; MPI_Bcast(&tag, 1, MPI_INT, 0, MPI_COMM_WORLD); @@ -1484,18 +1614,18 @@ MAiNGO::_root_multistart() if (_maingoSettings->PRE_maxLocalSearches > 0) { std::ostringstream outstr; outstr << " Multistart with " << _maingoSettings->PRE_maxLocalSearches << " initial points...\n"; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY); } else { // users... :-/ - _logger->print_message(" Requested pure multistart with 0 local searches. Only checking user-specified initial point for feasibility ...\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Requested pure multistart with 0 local searches. Only checking user-specified initial point for feasibility ...\n", VERB_NORMAL, BAB_VERBOSITY); } } else if (_maingoSettings->PRE_maxLocalSearches > 0) { - _logger->print_message(" Multistart local searches...\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Multistart local searches...\n", VERB_NORMAL, BAB_VERBOSITY); } else if (_initialPoint.size() == _nvar) { - _logger->print_message(" Checking user-specified initial point...\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Checking user-specified initial point...\n", VERB_NORMAL, BAB_VERBOSITY); } MAiNGO_END_IF _solutionPoint = _initialPoint; @@ -1531,7 +1661,6 @@ MAiNGO::_root_multistart() _solutionPoint.clear(); } else { - _rootNode.set_holds_incumbent(true); if (!_maingoSettings->PRE_pureMultistart) { _myLBS->update_incumbent_LBP(_solutionPoint); } @@ -1542,7 +1671,7 @@ MAiNGO::_root_multistart() std::string str; std::string whitespaces = " "; _check_feasibility_of_relaxation_only_constraints(_solutionPoint, str, whitespaces); - _logger->print_message(str, _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(str, VERB_NORMAL, BAB_VERBOSITY); } MAiNGO_END_IF } @@ -1617,28 +1746,28 @@ MAiNGO::_recognize_structure() switch (_problemStructure) { case LP: - _logger->print_message("\n The problem is an LP", _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message("\n The problem is an LP", VERB_ALL, BAB_VERBOSITY); break; case MIP: - _logger->print_message("\n The problem is an MIP", _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message("\n The problem is an MIP", VERB_ALL, BAB_VERBOSITY); break; case QP: - _logger->print_message("\n The problem is a QP", _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message("\n The problem is a QP", VERB_ALL, BAB_VERBOSITY); break; case MIQP: - _logger->print_message("\n The problem is an MIQP", _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message("\n The problem is an MIQP", VERB_ALL, BAB_VERBOSITY); break; case NLP: - _logger->print_message("\n The problem is an NLP", _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message("\n The problem is an NLP", VERB_ALL, BAB_VERBOSITY); break; case DNLP: - _logger->print_message("\n The problem is a DNLP", _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message("\n The problem is a DNLP", VERB_ALL, BAB_VERBOSITY); break; case MINLP: - _logger->print_message("\n The problem is an MINLP", _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message("\n The problem is an MINLP", VERB_ALL, BAB_VERBOSITY); break; default: - _logger->print_message("\n Could not recognize structure", _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message("\n Could not recognize structure", VERB_ALL, BAB_VERBOSITY); break; } } @@ -1727,6 +1856,7 @@ MAiNGO::_classify_objective_and_constraints(std::vector<mc::FFVar>& tmpFunctions _constantOutputs = std::make_shared<std::vector<Constraint>>(); _nonconstantOutputs = std::make_shared<std::vector<Constraint>>(); unsigned indexNonconstant = 0, indexOriginal = 0, indexConstant = 0, indexType = 0, indexTypeNonconstant = 0, indexTypeConstant = 0; + _nobj = 0; _nineq = 0; _neq = 0; _nineqRelaxationOnly = 0; @@ -1737,12 +1867,14 @@ MAiNGO::_classify_objective_and_constraints(std::vector<mc::FFVar>& tmpFunctions _nconstantIneqRelOnly = 0; _nconstantEqRelOnly = 0; _nconstantIneqSquash = 0; + _ndata = 0; // Objective(s) _ensure_valid_objective_function_using_dummy_variable(tmpDAGVars[0]); for (size_t i = 0; i < _modelOutput.objective.size(); i++) { tmpFunctions.push_back(_modelOutput.objective.value[i]); _originalConstraints->push_back(Constraint(CONSTRAINT_TYPE::OBJ, indexOriginal, indexType, indexNonconstant, indexTypeNonconstant, _modelOutput.objective.name[i])); _nonconstantConstraints->push_back(Constraint(CONSTRAINT_TYPE::OBJ, indexOriginal++, indexType++, indexNonconstant++, indexTypeNonconstant++, _modelOutput.objective.name[i])); + _nobj++; } // Inequalities indexType = 0; @@ -1899,6 +2031,16 @@ MAiNGO::_classify_objective_and_constraints(std::vector<mc::FFVar>& tmpFunctions _nineqSquash++; } } +#ifdef HAVE_GROWING_DATASETS + // Objective per data + _ndata = _modelOutput.objective_per_data.size(); + indexType = 0; + indexTypeNonconstant = 0; + _ensure_valid_objective_per_data_function_using_dummy_variable(tmpDAGVars[0]); + for (size_t i = 0; i < _modelOutput.objective_per_data.size(); i++) { + tmpFunctions.push_back(_modelOutput.objective_per_data[i]); + } +#endif // Output variables indexType = 0; @@ -1937,7 +2079,7 @@ MAiNGO::_ensure_valid_objective_function_using_dummy_variable(const mc::FFVar& d _feasibilityProblem = true; _maingoSettings->terminateOnFeasiblePoint = true; _logger->print_message("\n Warning: No objective function has been specified. Assuming this is a feasibility problem.\n During solution, a constant dummy objective with value 0 will be used.", - _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + VERB_NORMAL, BAB_VERBOSITY); } else { for (size_t i = 0; i < _modelOutput.objective.size(); i++) { @@ -1947,7 +2089,7 @@ MAiNGO::_ensure_valid_objective_function_using_dummy_variable(const mc::FFVar& d // Adding and substracting the 1.0 avoids errors in the case of non-defined objective. // An alternative would be to define a flag telling whether the objective is constant and propagate it to the LBD and UBD solvers. _logger->print_message("\n Warning: Objective function is a constant with value " + std::to_string(_modelOutput.objective[i].num().val()) + ".", - _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + VERB_NORMAL, BAB_VERBOSITY); _modelOutput.objective.set_value(dummyVariable + 1.0 + _modelOutput.objective[i].num().val() - dummyVariable - 1.0, i); } } @@ -1955,6 +2097,27 @@ MAiNGO::_ensure_valid_objective_function_using_dummy_variable(const mc::FFVar& d } +#ifdef HAVE_GROWING_DATASETS +///////////////////////////////////////////////////////////////////////// +// Ensures that constant objective_per_data is not removed +void +MAiNGO::_ensure_valid_objective_per_data_function_using_dummy_variable(const mc::FFVar& dummyVariable) +{ + for (size_t i = 0; i < _modelOutput.objective_per_data.size(); i++) { + if (!_modelOutput.objective_per_data[i].dag()) { // Check if DAG pointer is set, if not the objective is a constant + // This is basically saying objective = x1 + 1 + constant - x1 - 1 = constant. + // We are doing this since 0*x1 or x1 - x1 will be recognized by the DAG (this may change in future). + // Adding and substracting the 1.0 avoids errors in the case of non-defined objective. + // An alternative would be to define a flag telling whether the objective is constant and propagate it to the LBD and UBD solvers. + _logger->print_message("\n Warning: Objective_per_data [" + std::to_string(i) + "] is a constant with value " + std::to_string(_modelOutput.objective[i].num().val()) + ".", + VERB_NORMAL, BAB_VERBOSITY); + _modelOutput.objective_per_data.set_value(dummyVariable + 1.0 + _modelOutput.objective_per_data[i].num().val() - dummyVariable - 1.0, i); + } + } +} +#endif + + ///////////////////////////////////////////////////////////////////////// // checks whether some constraints (or output) is not indeed 0 and also fills the _DAGoutputFunctions vector bool @@ -2160,6 +2323,74 @@ MAiNGO::_check_for_hidden_zero_constraints(const std::vector<mc::FFVar>& tmpDAGV } +#ifdef HAVE_GROWING_DATASETS +///////////////////////////////////////////////////////////////////////// +// initializes objective via the user-defined objective_per_data when using MAiNGO with growing datasets +// currently: objective = sum of objective_per_data +void +MAiNGO::_initialize_objective_from_objective_per_data() +{ + if (_modelOutput.objective_per_data.size() == 0) { + throw MAiNGOException(" Error initializing MAiNGO: MAiNGO with growing datasets requires setting objective per data."); + } + else { // Objective per data is defined: print warning (in manager process) + MAiNGO_IF_BAB_MANAGER + if (_modelOutput.objective.size() > 0) { + _logger->print_message("\n Warning: Objective is overwritten based on objective_per_data. \n", VERB_NORMAL, BAB_VERBOSITY); + } + MAiNGO_END_IF // End of MAiNGO_IF_BAB_MANAGER + _modelOutput.objective.clear(); + + //objective considering complete dataset for setting up complete DAG + mc::FFVar obj = 0; + for (auto i = 0; i < _modelOutput.objective_per_data.size(); i++) { + obj += _modelOutput.objective_per_data[i]; + } + _modelOutput.objective = obj; + } +} + + +//////////////////////////////////////////////////////////////////////// +// initializes full dataset (= largest set, index 0) and initial reduced dataset (= smallest set, index 1) +void +MAiNGO::_initialize_dataset() +{ + _datasets = std::make_shared<std::vector<std::set<unsigned int>>>(); + + // Full dataset is range 0,...,_ndata-1 + std::set<unsigned int> tmpFullSet; + for (unsigned int i = 0; i < _ndata; i++) { + tmpFullSet.insert(tmpFullSet.end(), i); + } + _datasets->push_back(tmpFullSet); + + // Smallest reduced dataset + // Random set with size given by settings + if (_ndata > 1) { // Only if smaller than full dataset + std::set<unsigned int> tmpReducedSet; + int noAddedData = std::round((double)_maingoSettings->growing_dataSizeInit * (double)_ndata); + noAddedData = std::max(1, noAddedData); + + std::srand(5); // For reproducible results + std::pair<std::set<unsigned int>::iterator, bool> ans; + int count = 0; + + while (count < noAddedData) { + double tmpRand = std::rand() / ((double)RAND_MAX + 1); // Random value between 0. and 1. + + // Choose from unused data points (set difference) + size_t idxRand = tmpRand * _ndata; + ans = tmpReducedSet.insert(*(std::next(tmpFullSet.begin(), idxRand))); + if (ans.second == true) { + count++; + } + } + _datasets->push_back(tmpReducedSet); + } +} +#endif //HAVE_GROWING_DATASETS + ///////////////////////////////////////////////////////////////////////// // modifies the lower bound DAG _DAGlbd by adding auxiliary optimization variables for intermediate factors occuring multiple times void @@ -2168,14 +2399,14 @@ MAiNGO::_add_auxiliary_variables_to_lbd_dag() // First, we evaluate the model at mid point // This is done to get interval bounds for auxiliary factors and compute a score - std::vector<MC> independentVariablesMC(_DAGvarsLbd.size()); // Vector holding the bounds of optimization variables as intervals + std::vector<MC> independentVariablesMC(_DAGvarsLbd.size()); // Vector holding the bounds of optimization variables as intervals std::vector<double> lowerVarBounds(_nvar); std::vector<double> upperVarBounds(_nvar); - std::vector<double> referencePoint(_nvar); // This is the point at which we evaluate the relaxations to assess how far away they are from the functions + std::vector<double> referencePoint(_nvar); // This is the point at which we evaluate the relaxations to assess how far away they are from the functions for (unsigned int i = 0; i < _nvar; i++) { - lowerVarBounds[i] = _variables[i].get_lower_bound(); - upperVarBounds[i] = _variables[i].get_upper_bound(); - referencePoint[i] = 0.5 * (lowerVarBounds[i] + upperVarBounds[i]); + lowerVarBounds[i] = _variables[i].get_lower_bound(); + upperVarBounds[i] = _variables[i].get_upper_bound(); + referencePoint[i] = 0.5 * (lowerVarBounds[i] + upperVarBounds[i]); independentVariablesMC[i] = MC(I(lowerVarBounds[i], upperVarBounds[i]), referencePoint[i]); independentVariablesMC[i].sub(_nvar, i); // Set subgradient dimension } @@ -2198,7 +2429,7 @@ MAiNGO::_add_auxiliary_variables_to_lbd_dag() MC::subHeur.referencePoint = &referencePoint; // Get the subgraph of those things that actually occur in objective and constraints - mc::FFSubgraph MCSubgraph = _DAGlbd.subgraph(_DAGfunctionsLbd.size(), _DAGfunctionsLbd.data()); + mc::FFSubgraph MCSubgraph = _DAGlbd.subgraph(_DAGfunctionsLbd.size(), _DAGfunctionsLbd.data()); // Evaluate in McCormick arithmetic std::vector<MC> objectiveAndConstraintsMC(_DAGfunctionsLbd.size()); // Dummy vector holding the McCormick objects of all functions in the model @@ -2223,20 +2454,20 @@ MAiNGO::_add_auxiliary_variables_to_lbd_dag() // 1. the number of operations the variable // 2. the sum of absolute differences between the function and the convex and concave relaxation at the reference point mc::FFGraph::t_Vars ffVars = _DAGlbd.Vars(); // Set of all FFVars in the DAG - mc::FFGraph::it_Vars itv = ffVars.begin(); + mc::FFGraph::it_Vars itv = ffVars.begin(); std::multimap<std::pair<unsigned, double>, std::pair<mc::FFVar*, mc::FFDep::TYPE>> factorRanking; for (; itv != ffVars.end(); ++itv) { // We are interested in non-constant dependent FFVars only if ((*itv)->id().first != mc::FFVar::VAR && (*itv)->id().first != mc::FFVar::CINT && (*itv)->id().first != mc::FFVar::CREAL) { const typename mc::FFVar::t_Ops operationsUsedIn = (*itv)->ops().second; // This is a list of all operations where this FFVar is used - const mc::FFOp* pOperation = (*itv)->ops().first; // This is the operation of this FFVar + const mc::FFOp* pOperation = (*itv)->ops().first; // This is the operation of this FFVar if (operationsUsedIn.size() >= _maingoSettings->LBP_minFactorsForAux) { // Determine how the dependent variable depends on the independent ones const std::map<int, int> dependenceOnIndependentVars = (*itv)->dep().dep(); - mc::FFDep::TYPE functionStructure = mc::FFDep::L; + mc::FFDep::TYPE functionStructure = mc::FFDep::L; std::vector<size_t> participatingVars; for (size_t j = 0; j < _nvar; j++) { auto ito2 = dependenceOnIndependentVars.find(j); @@ -2268,26 +2499,36 @@ MAiNGO::_add_auxiliary_variables_to_lbd_dag() const double averageRelaxationOffset = std::fabs(operationResultsMC[index].cv() - operationResultsDouble[index]) + std::fabs(operationResultsMC[index].cc() - operationResultsDouble[index]); factorRanking.insert(std::make_pair(std::make_pair(operationsUsedIn.size(), averageRelaxationOffset), std::make_pair((*itv), functionStructure))); } - } } } } // Go through factor ranking to select the most promising candidates - std::vector<MC> auxVariablesMCBounds; // Vector holding McCormick objects to derive the bounds for auxiliary variables which will be added - unsigned indexOriginal = _DAGfunctions.size(); - unsigned indexType = 0; - unsigned indexTypeNonconstant = _neqRelaxationOnly; // Auxiliary relaxation only constraints are handled as normal eq rel only constraints - unsigned indexNonconstant = 1 + _nineq + _neq + _nineqRelaxationOnly + _neqRelaxationOnly + _nineqSquash; - unsigned counter = 0; + std::vector<MC> auxVariablesMCBounds; // Vector holding McCormick objects to derive the bounds for auxiliary variables which will be added + unsigned indexOriginal = _DAGfunctions.size() - _ndata; // objective_per_data are not considered as constraints (without growing datasets: _ndata == 0 by initialization and never changed) + unsigned indexType = 0; + unsigned indexTypeNonconstant = _neqRelaxationOnly; // Auxiliary relaxation only constraints are handled as normal eq rel only constraints + unsigned indexNonconstant = 1 + _nineq + _neq + _nineqRelaxationOnly + _neqRelaxationOnly + _nineqSquash; + unsigned counter = 0; std::multimap<std::pair<unsigned, double>, std::pair<mc::FFVar*, mc::FFDep::TYPE>>::reverse_iterator rit = factorRanking.rbegin(); - for ( ; rit != factorRanking.rend() && counter < _maingoSettings->LBP_maxNumberOfAddedFactors; ++rit) { +#ifdef HAVE_GROWING_DATASETS + // Keep obj_per_data behind auxiliary variables + // Save function pointers into temporary vector + unsigned int tmpSize = _DAGfunctionsLbd.size(); + std::vector<mc::FFVar> dataFunctions; + dataFunctions.resize(_ndata); + for (auto idx = 0; idx < _ndata; idx++) { + dataFunctions[idx] = _DAGfunctionsLbd[tmpSize - _ndata + idx]; + } + _DAGfunctionsLbd.resize(tmpSize - _ndata); +#endif // HAVE_GROWING_DATASETS + for (; rit != factorRanking.rend() && counter < _maingoSettings->LBP_maxNumberOfAddedFactors; ++rit) { - mc::FFVar* itv = (*rit).second.first; - mc::FFDep::TYPE functionStructure = (*rit).second.second; + mc::FFVar* itv = (*rit).second.first; + mc::FFDep::TYPE functionStructure = (*rit).second.second; typename mc::FFVar::t_Ops operationsUsedIn = itv->ops().second; // This is a list of all operations where this FFVar is used - mc::FFOp* pOperation = itv->ops().first; // This is the operation of this FFVar + mc::FFOp* pOperation = itv->ops().first; // This is the operation of this FFVar // Get new independent variable and add it to list of DAG variables mc::FFVar newIndependentVar = _DAGlbd.replace_intermediate_variable_by_independent_copy(itv); @@ -2316,6 +2557,12 @@ MAiNGO::_add_auxiliary_variables_to_lbd_dag() } auxVariablesMCBounds.push_back(operationResultsMC[index]); } +#ifdef HAVE_GROWING_DATASETS + // Append obj_per_data once again + for (auto dataPointer : dataFunctions) { + _DAGfunctionsLbd.push_back(dataPointer); + } +#endif // HAVE_GROWING_DATASETS // Get valid variable bounds _variablesLbd.clear(); @@ -2458,4 +2705,4 @@ MAiNGO::_set_constraint_properties() break; // We don't use relaxation only constraints in the ubp } } -} \ No newline at end of file +} diff --git a/src/MAiNGOevaluationFunctions.cpp b/src/MAiNGOevaluationFunctions.cpp index be9f4ea..3b82467 100644 --- a/src/MAiNGOevaluationFunctions.cpp +++ b/src/MAiNGOevaluationFunctions.cpp @@ -89,13 +89,12 @@ MAiNGO::evaluate_model_at_point(const std::vector<double> &point) if (!_removedVariables[i]) { pointUsed.push_back(point[i]); } - else - { - if ( (point[i] > _originalVariables[i].get_upper_bound()) || (point[i] < _originalVariables[i].get_lower_bound()) ) { + else { + if ((point[i] > _originalVariables[i].get_upper_bound()) || (point[i] < _originalVariables[i].get_lower_bound())) { removedVariablesFeasible = false; } babBase::enums::VT varType(_originalVariables[i].get_variable_type()); - if ( (varType == babBase::enums::VT_BINARY) && (point[i] != 0) && (point[i] != 1) ) { + if ((varType == babBase::enums::VT_BINARY) && (point[i] != 0) && (point[i] != 1)) { removedVariablesFeasible = false; } else if (varType == babBase::enums::VT_INTEGER) { @@ -106,8 +105,8 @@ MAiNGO::evaluate_model_at_point(const std::vector<double> &point) } } - std::pair< std::vector<double>, bool> result = _evaluate_model_at_point(pointUsed); - result.second = result.second && removedVariablesFeasible; + std::pair<std::vector<double>, bool> result = _evaluate_model_at_point(pointUsed); + result.second = result.second && removedVariablesFeasible; return result; } @@ -192,11 +191,11 @@ MAiNGO::_evaluate_model_at_point(const std::vector<double> &pointUsed) bool isFeasible = true; // First check feasibility w.r.t. variable bounds & integrality for (unsigned int i = 0; i < _nvar; i++) { - if ( (pointUsed[i] > _variables[i].get_upper_bound()) || (pointUsed[i] < _variables[i].get_lower_bound()) ) { + if ((pointUsed[i] > _variables[i].get_upper_bound()) || (pointUsed[i] < _variables[i].get_lower_bound())) { isFeasible = false; } babBase::enums::VT varType(_variables[i].get_variable_type()); - if ( (varType == babBase::enums::VT_BINARY) && (pointUsed[i] != 0) && (pointUsed[i] != 1) ) { + if ((varType == babBase::enums::VT_BINARY) && (pointUsed[i] != 0) && (pointUsed[i] != 1)) { isFeasible = false; } else if (varType == babBase::enums::VT_INTEGER) { @@ -205,7 +204,7 @@ MAiNGO::_evaluate_model_at_point(const std::vector<double> &pointUsed) } } } - + // Don't forget the constant functions std::vector<double> evaluationResult(1 + _nineq + _nconstantIneq + _neq + _nconstantEq + _nineqRelaxationOnly + _nconstantIneqRelOnly + _neqRelaxationOnly + _nconstantEqRelOnly + _nineqSquash + _nconstantIneqSquash); evaluationResult[0] = result[0]; // Objective first diff --git a/src/MAiNGOgetterFunctions.cpp b/src/MAiNGOgetterFunctions.cpp index e95602d..6ff2bd2 100644 --- a/src/MAiNGOgetterFunctions.cpp +++ b/src/MAiNGOgetterFunctions.cpp @@ -219,6 +219,172 @@ MAiNGO::get_final_rel_gap() const } +//////////////////////////////////////////////////////////////////////////////////////// +// function returning the value of a desired option +double +MAiNGO::get_option(const std::string& option) const +{ + if (option == "epsilonA") { + return _maingoSettings->epsilonA; + } + else if (option == "epsilonR") { + return _maingoSettings->epsilonR; + } + else if (option == "deltaIneq") { + return _maingoSettings->deltaIneq; + } + else if (option == "deltaEq") { + return _maingoSettings->deltaEq; + } + else if (option == "relNodeTol") { + return _maingoSettings->relNodeTol; + } + else if (option == "BAB_maxNodes") { + return _maingoSettings->BAB_maxNodes; + } + else if (option == "BAB_maxIterations") { + return _maingoSettings->BAB_maxIterations; + } + else if (option == "maxTime") { + return _maingoSettings->maxTime; + } + else if (option == "confirmTermination") { + return _maingoSettings->confirmTermination; + } + else if (option == "terminateOnFeasiblePoint") { + return _maingoSettings->terminateOnFeasiblePoint; + } + else if (option == "targetLowerBound") { + return _maingoSettings->targetLowerBound; + } + else if (option == "targetUpperBound") { + return _maingoSettings->targetUpperBound; + } + else if (option == "PRE_maxLocalSearches") { + return _maingoSettings->PRE_maxLocalSearches; + } + else if (option == "PRE_obbtMaxRounds") { + return _maingoSettings->PRE_obbtMaxRounds; + } + else if (option == "PRE_pureMultistart") { + return _maingoSettings->PRE_pureMultistart; + } + else if (option == "BAB_nodeSelection") { + return _maingoSettings->BAB_nodeSelection; + } + else if (option == "BAB_branchVariable") { + return _maingoSettings->BAB_branchVariable; + } + else if (option == "BAB_alwaysSolveObbt") { + return _maingoSettings->BAB_alwaysSolveObbt; + } + else if (option == "BAB_probing") { + return _maingoSettings->BAB_probing; + } + else if (option == "BAB_dbbt") { + return _maingoSettings->BAB_dbbt; + } + else if (option == "BAB_constraintPropagation") { + return _maingoSettings->BAB_constraintPropagation; + } + else if (option == "LBP_solver") { + return _maingoSettings->LBP_solver; + } + else if (option == "LBP_linPoints") { + return _maingoSettings->LBP_linPoints; + } + else if (option == "LBP_subgradientIntervals") { + return _maingoSettings->LBP_subgradientIntervals; + } + else if (option == "LBP_obbtMinImprovement") { + return _maingoSettings->LBP_obbtMinImprovement; + } + else if (option == "LBP_activateMoreScaling") { + return _maingoSettings->LBP_activateMoreScaling; + } + else if (option == "LBP_addAuxiliaryVars") { + return _maingoSettings->LBP_addAuxiliaryVars; + } + else if (option == "LBP_minFactorsForAux") { + return _maingoSettings->LBP_minFactorsForAux; + } + else if (option == "LBP_maxNumberOfAddedFactors") { + return _maingoSettings->LBP_maxNumberOfAddedFactors; + } + else if (option == "MC_mvcompUse") { + return _maingoSettings->MC_mvcompUse; + } + else if (option == "MC_mvcompTol") { + return _maingoSettings->MC_mvcompTol; + } + else if (option == "MC_envelTol") { + return _maingoSettings->MC_envelTol; + } + else if (option == "UBP_solverPreprocessing") { + return _maingoSettings->UBP_solverPreprocessing; + } + else if (option == "UBP_maxStepsPreprocessing") { + return _maingoSettings->UBP_maxStepsPreprocessing; + } + else if (option == "UBP_maxTimePreprocessing") { + return _maingoSettings->UBP_maxTimePreprocessing; + } + else if (option == "UBP_solverBab") { + return _maingoSettings->UBP_solverBab; + } + else if (option == "UBP_maxStepsBab") { + return _maingoSettings->UBP_maxStepsBab; + } + else if (option == "UBP_maxTimeBab") { + return _maingoSettings->UBP_maxTimeBab; + } + else if (option == "UBP_ignoreNodeBounds") { + return _maingoSettings->UBP_ignoreNodeBounds; + } + else if (option == "EC_nPoints") { + return _maingoSettings->EC_nPoints; + } + else if (option == "LBP_verbosity") { + return _maingoSettings->LBP_verbosity; + } + else if (option == "UBP_verbosity") { + return _maingoSettings->UBP_verbosity; + } + else if (option == "BAB_verbosity") { + return _maingoSettings->BAB_verbosity; + } + else if (option == "BAB_printFreq") { + return _maingoSettings->BAB_printFreq; + } + else if (option == "BAB_logFreq") { + return _maingoSettings->BAB_logFreq; + } + else if (option == "loggingDestination") { + return _maingoSettings->loggingDestination; + } + else if (option == "writeCsv") { + return _maingoSettings->writeCsv; + } + else if (option == "writeJson") { + return _maingoSettings->writeJson; + } + else if (option == "writeResultFile") { + return _maingoSettings->writeResultFile; + } + else if (option == "writeToLogSec") { + return _maingoSettings->writeToLogSec; + } + else if (option == "PRE_printEveryLocalSearch") { + return _maingoSettings->PRE_printEveryLocalSearch; + } + else if (option == "modelWritingLanguage") { + return _maingoSettings->modelWritingLanguage; + } + std::cout << "Warning: No setting \"" << option << "\" found. \n"; + return -1; +} + + //////////////////////////////////////////////////////////////////////////////////////// // function returning the current MAiNGO status RETCODE diff --git a/src/MAiNGOprintingFunctions.cpp b/src/MAiNGOprintingFunctions.cpp index f11c4c6..14116e9 100644 --- a/src/MAiNGOprintingFunctions.cpp +++ b/src/MAiNGOprintingFunctions.cpp @@ -19,79 +19,75 @@ using namespace maingo; - //////////////////////////////////////////////////////////////////////// // Evaluate model at initial point and print the values of the variables, objective, constraint residuals and outputs void MAiNGO::_print_info_about_initial_point() { - if (_initialPointOriginal.empty()) { - std::ostringstream outstream; - outstream << std::endl << " No initial point given." << std::endl << std::endl; - _logger->print_message(outstream.str(), _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - return; - } - if (_initialPointOriginal.size() != _originalVariables.size()) { - std::ostringstream outstream; - outstream << std::endl << " WARNING: Initial point discarded because its dimension (" << _initialPointOriginal.size() << ")"; - outstream << "does not match the number of variables (" << _originalVariables.size() << ")." << std::endl << std::endl; - _logger->print_message(outstream.str(), _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - return; - } + if (_initialPointOriginal.empty()) { + std::ostringstream outstream; + outstream << std::endl + << " No initial point given." << std::endl + << std::endl; + _logger->print_message(outstream.str(), VERB_ALL, BAB_VERBOSITY); + return; + } + if (_initialPointOriginal.size() != _originalVariables.size()) { + std::ostringstream outstream; + outstream << std::endl + << " WARNING: Initial point discarded because its dimension (" << _initialPointOriginal.size() << ")"; + outstream << "does not match the number of variables (" << _originalVariables.size() << ")." << std::endl + << std::endl; + _logger->print_message(outstream.str(), VERB_ALL, BAB_VERBOSITY); + return; + } std::ostringstream outstream; // Variables: - outstream << std::endl << " Initial point:" << std::endl; - for (size_t i=0; i<_originalVariables.size(); ++i) - { + outstream << std::endl + << " Initial point:" << std::endl; + for (size_t i = 0; i < _originalVariables.size(); ++i) { outstream << " '" << _originalVariables[i].get_name() << "' = " << _initialPointOriginal[i]; babBase::enums::VT varType(_originalVariables[i].get_variable_type()); if (varType == babBase::enums::VT_BINARY) { - outstream << (( (_initialPointOriginal[i] == 0) || (_initialPointOriginal[i] == 1) ) ? " in " : " NOT in ") << "{0, 1}" << std::endl; + outstream << (((_initialPointOriginal[i] == 0) || (_initialPointOriginal[i] == 1)) ? " in " : " NOT in ") << "{0, 1}" << std::endl; } else if (varType == babBase::enums::VT_INTEGER) { - const bool feasible = (_initialPointOriginal[i]==std::round(_initialPointOriginal[i])) && (_initialPointOriginal[i]>=_originalVariables[i].get_lower_bound()) && (_initialPointOriginal[i]<=_originalVariables[i].get_upper_bound()); + const bool feasible = (_initialPointOriginal[i] == std::round(_initialPointOriginal[i])) && (_initialPointOriginal[i] >= _originalVariables[i].get_lower_bound()) && (_initialPointOriginal[i] <= _originalVariables[i].get_upper_bound()); outstream << (feasible ? " in " : " NOT in ") << "{" << _originalVariables[i].get_lower_bound() << ", ..., " << _originalVariables[i].get_upper_bound() << "}" << std::endl; - } else { - outstream << (( (_initialPointOriginal[i]>=_originalVariables[i].get_lower_bound()) && (_initialPointOriginal[i]<=_originalVariables[i].get_upper_bound()) ) ? " in " : " NOT in "); + } + else { + outstream << (((_initialPointOriginal[i] >= _originalVariables[i].get_lower_bound()) && (_initialPointOriginal[i] <= _originalVariables[i].get_upper_bound())) ? " in " : " NOT in "); outstream << "[" << _originalVariables[i].get_lower_bound() << ", " << _originalVariables[i].get_upper_bound() << "]" << std::endl; } - } // Objective & constraint residuals: const std::pair<std::vector<double>, bool> result = evaluate_model_at_point(_initialPointOriginal); outstream << " Model evaluated at initial point:" << std::endl; outstream << " Objective = " << result.first[0] << std::endl; - outstream << " Initial point feasible? " << ((result.second==true) ? "yes" : "no") << std::endl; - if (result.first.size() > 1) - { + outstream << " Initial point feasible? " << ((result.second == true) ? "yes" : "no") << std::endl; + if (result.first.size() > 1) { outstream << " Constraint residuals:" << std::endl; - for (size_t i=1; i<=_nineq+_nconstantIneq; ++i) - { + for (size_t i = 1; i <= _nineq + _nconstantIneq; ++i) { outstream << " ineq # " << i; - if (_modelOutput.ineq.name[i-1] != "") - { + if (_modelOutput.ineq.name[i - 1] != "") { outstream << ", '" << _modelOutput.ineq.name[i - 1] << "'"; } outstream << ": " << result.first[i]; - if (result.first[i] > _maingoSettings->deltaIneq) - { + if (result.first[i] > _maingoSettings->deltaIneq) { outstream << " (VIOLATED)"; } outstream << std::endl; } - for (size_t i=1+_nineq+_nconstantIneq; i<=_nineq+_nconstantIneq+_neq+_nconstantEq; ++i) - { - outstream << " eq # " << i-_nineq-_nconstantIneq; - if (_modelOutput.eq.name[i - (_nineq+_nconstantIneq) - 1] != "") - { - outstream << ", '" << _modelOutput.eq.name[i - (_nineq+_nconstantIneq) - 1] << "'"; + for (size_t i = 1 + _nineq + _nconstantIneq; i <= _nineq + _nconstantIneq + _neq + _nconstantEq; ++i) { + outstream << " eq # " << i - _nineq - _nconstantIneq; + if (_modelOutput.eq.name[i - (_nineq + _nconstantIneq) - 1] != "") { + outstream << ", '" << _modelOutput.eq.name[i - (_nineq + _nconstantIneq) - 1] << "'"; } outstream << ": " << result.first[i]; - if (std::fabs(result.first[i]) > _maingoSettings->deltaEq) - { + if (std::fabs(result.first[i]) > _maingoSettings->deltaEq) { outstream << " (VIOLATED)"; } outstream << std::endl; @@ -100,18 +96,15 @@ MAiNGO::_print_info_about_initial_point() // Additional outputs const std::vector<std::pair<std::string, double>> outputs = evaluate_additional_outputs_at_point(_initialPointOriginal); - if (outputs.size() != 0) - { + if (outputs.size() != 0) { outstream << " Outputs at initial point:" << std::endl; - for (size_t i = 0; i < outputs.size(); ++i) - { + for (size_t i = 0; i < outputs.size(); ++i) { outstream << " " << outputs[i].first << ": " << outputs[i].second << std::endl; } } outstream << std::endl; - _logger->print_message(outstream.str(), _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - + _logger->print_message(outstream.str(), VERB_ALL, BAB_VERBOSITY); } @@ -202,7 +195,7 @@ MAiNGO::_print_statistics() outstream << std::endl << "===================================================================" << std::endl; - _logger->print_message(outstream.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstream.str(), VERB_NORMAL, BAB_VERBOSITY); } @@ -399,7 +392,7 @@ MAiNGO::_print_solution() outstream << std::endl << "===================================================================" << std::endl; - _logger->print_message(outstream.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstream.str(), VERB_NORMAL, BAB_VERBOSITY); } @@ -425,7 +418,7 @@ MAiNGO::_print_time() unsigned int minutes = (unsigned int)((_solutionTime - hours * 3600) / 60); outstr << minutes << "m " << std::fixed << std::setprecision(3) << (_solutionTime - hours * 3600. - minutes * 60.) << "s (CPU).\n"; } - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY); // Wall-clock time outstr.str(""); @@ -445,7 +438,8 @@ MAiNGO::_print_time() outstr << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + + _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY); } @@ -478,7 +472,7 @@ MAiNGO::_print_additional_output() << "===================================================================" << std::endl; } - _logger->print_message(outstream.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstream.str(), VERB_NORMAL, BAB_VERBOSITY); } @@ -529,7 +523,7 @@ MAiNGO::_print_MAiNGO_header() outstream << "* *" << std::endl; outstream << "************************************************************************************************************************" << std::endl; - _logger->print_message(outstream.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstream.str(), VERB_NORMAL, BAB_VERBOSITY); } @@ -554,8 +548,8 @@ MAiNGO::_print_message(const std::string& message) outstream << "*" << leftString << message << rightString << "*" << std::endl; outstream << "* *" << std::endl; outstream << "************************************************************************************************************************" << std::endl; - VERB verbosityGiven = std::max(_maingoSettings->BAB_verbosity, std::max(_maingoSettings->UBP_verbosity, _maingoSettings->LBP_verbosity)); - _logger->print_message(outstream.str(), verbosityGiven, VERB_NORMAL, _maingoSettings->loggingDestination); + + _logger->print_message(outstream.str(), VERB_NORMAL, BAB_VERBOSITY, UBP_VERBOSITY, LBP_VERBOSITY); } @@ -565,53 +559,127 @@ MAiNGO::_print_message(const std::string& message) void MAiNGO::_print_third_party_software_minlp() { - _logger->print_message("\n Major third-party software used:\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - + _logger->print_message("\n Major third-party software used:\n", VERB_NORMAL, BAB_VERBOSITY); + + // First, determine which third-party UBP and LBP solvers are used: + bool usingCobyla = false, usingBobyqa = false, usingLbfgs = false, usingSlsqp = false, usingIpopt = false, usingKnitro = false; + if (_maingoSettings->PRE_maxLocalSearches > 0) { + switch (_maingoSettings->UBP_solverPreprocessing) { + case ubp::UBP_SOLVER_EVAL: + // Just evaluating, nothing to report + break; + case ubp::UBP_SOLVER_COBYLA: + usingCobyla = true; + break; + case ubp::UBP_SOLVER_BOBYQA: + usingBobyqa = true; + break; + case ubp::UBP_SOLVER_LBFGS: + usingLbfgs = true; + break; + case ubp::UBP_SOLVER_SLSQP: + usingSlsqp = true; + break; + case ubp::UBP_SOLVER_IPOPT: + usingIpopt = true; + break; + case ubp::UBP_SOLVER_KNITRO: + usingKnitro = true; + break; + default: + throw MAiNGOException(" ERROR printing third-party software: unknown upper bounding solver for pre-processing"); + } + } + if (!_maingoSettings->PRE_pureMultistart) { + switch (_maingoSettings->UBP_solverBab) { + case ubp::UBP_SOLVER_EVAL: + // Just evaluating, nothing to report + break; + case ubp::UBP_SOLVER_COBYLA: + usingCobyla = true; + break; + case ubp::UBP_SOLVER_BOBYQA: + usingBobyqa = true; + break; + case ubp::UBP_SOLVER_LBFGS: + usingLbfgs = true; + break; + case ubp::UBP_SOLVER_SLSQP: + usingSlsqp = true; + break; + case ubp::UBP_SOLVER_IPOPT: + usingIpopt = true; + break; + case ubp::UBP_SOLVER_KNITRO: + usingKnitro = true; + break; + default: + throw MAiNGOException(" ERROR printing third-party software: unknown upper bounding solver for B&B"); + } + } + bool usingCplex = false, usingClp = false; + switch (_maingoSettings->LBP_solver) { + case lbp::LBP_SOLVER_MAiNGO: + // Nothing to report + break; + case lbp::LBP_SOLVER_INTERVAL: + // Nothing to report + break; + case lbp::LBP_SOLVER_CLP: + usingClp = true; + break; + case lbp::LBP_SOLVER_CPLEX: + usingCplex = true; + break; + default: + throw MAiNGOException(" ERROR printing third-party software: unknown lower bounding solver"); + } + + // Always using MC++ and Filib++ if (_maingoSettings->LBP_solver != lbp::LBP_SOLVER_INTERVAL) { - _logger->print_message(" - MC++ by B. Chachuat et al. (DAG & relaxations)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } else { - _logger->print_message(" - MC++ by B. Chachuat et al. (DAG)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" - MC++ by B. Chachuat et al. (DAG & relaxations)\n", VERB_NORMAL, BAB_VERBOSITY); + } + else { + _logger->print_message(" - MC++ by B. Chachuat et al. (DAG)\n", VERB_NORMAL, BAB_VERBOSITY); } - _logger->print_message(" - Filib++ by M. Lerch et al. (interval extensions)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - if (((_maingoSettings->UBP_solverPreprocessing != ubp::UBP_SOLVER_EVAL) && (_maingoSettings->UBP_solverPreprocessing != ubp::UBP_SOLVER_COBYLA) && (_maingoSettings->UBP_solverPreprocessing != ubp::UBP_SOLVER_BOBYQA)) - || ((_maingoSettings->UBP_solverBab != ubp::UBP_SOLVER_EVAL) && (_maingoSettings->UBP_solverBab != ubp::UBP_SOLVER_COBYLA) && (_maingoSettings->UBP_solverBab != ubp::UBP_SOLVER_BOBYQA))) { - _logger->print_message(" - FADBAD++ by O. Stauning and C. Bendtsen (automatic differentiation)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" - Filib++ by M. Lerch et al. (interval extensions)\n", VERB_NORMAL, BAB_VERBOSITY); + + // Depending on UBP and LBP solvers, may use Fadbad++, MUMPS, BLAS, and LAPACK + if (usingLbfgs || usingSlsqp || usingIpopt || usingKnitro) { + _logger->print_message(" - FADBAD++ by O. Stauning and C. Bendtsen (automatic differentiation)\n", VERB_NORMAL, BAB_VERBOSITY); } - if ( (_maingoSettings->UBP_solverPreprocessing == ubp::UBP_SOLVER_IPOPT) || (_maingoSettings->UBP_solverBab == ubp::UBP_SOLVER_IPOPT) || - (_maingoSettings->LBP_solver == lbp::LBP_SOLVER_CLP) ) { - _logger->print_message(" - MUMPS by P.R. Amestoy et al. (sparse linear solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - _logger->print_message(" - Netlib BLAS and LAPACK (linear algebra)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + if (usingIpopt || usingClp) { + _logger->print_message(" - MUMPS by P.R. Amestoy et al. (sparse linear solver)\n", VERB_NORMAL, BAB_VERBOSITY); + _logger->print_message(" - Netlib BLAS and LAPACK (linear algebra)\n", VERB_NORMAL, BAB_VERBOSITY); } - // UBP solvers: - if ( (_maingoSettings->UBP_solverBab == ubp::UBP_SOLVER_COBYLA) || (_maingoSettings->UBP_solverPreprocessing == ubp::UBP_SOLVER_COBYLA) ) { - _logger->print_message(" - COBYLA by M.J.D. Powell implemented in NLopt by S.G. Johnson (local NLP solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + // Print UBP solvers + if (usingCobyla) { + _logger->print_message(" - COBYLA by M.J.D. Powell implemented in NLopt by S.G. Johnson (local NLP solver)\n", VERB_NORMAL, BAB_VERBOSITY); } - if ( (_maingoSettings->UBP_solverBab == ubp::UBP_SOLVER_BOBYQA) || (_maingoSettings->UBP_solverPreprocessing == ubp::UBP_SOLVER_BOBYQA) ) { - _logger->print_message(" - BOBYQA by M.J.D. Powell implemented in NLopt by S.G. Johnson (local NLP solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + if (usingBobyqa) { + _logger->print_message(" - BOBYQA by M.J.D. Powell implemented in NLopt by S.G. Johnson (local NLP solver)\n", VERB_NORMAL, BAB_VERBOSITY); } - if ( (_maingoSettings->UBP_solverBab == ubp::UBP_SOLVER_LBFGS) || (_maingoSettings->UBP_solverPreprocessing == ubp::UBP_SOLVER_LBFGS) ) { - _logger->print_message(" - L-BFGS by L. Luksan implemented in NLopt by S.G. Johnson (local NLP solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + if (usingLbfgs) { + _logger->print_message(" - L-BFGS by L. Luksan implemented in NLopt by S.G. Johnson (local NLP solver)\n", VERB_NORMAL, BAB_VERBOSITY); } - if ( (_maingoSettings->UBP_solverBab == ubp::UBP_SOLVER_SLSQP) || (_maingoSettings->UBP_solverPreprocessing == ubp::UBP_SOLVER_SLSQP) ) { - _logger->print_message(" - SLSQP by D. Kraft implemented in NLopt by S.G. Johnson (local NLP solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + if (usingSlsqp) { + _logger->print_message(" - SLSQP by D. Kraft implemented in NLopt by S.G. Johnson (local NLP solver)\n", VERB_NORMAL, BAB_VERBOSITY); } - if ( (_maingoSettings->UBP_solverBab == ubp::UBP_SOLVER_IPOPT) || (_maingoSettings->UBP_solverPreprocessing == ubp::UBP_SOLVER_IPOPT) ) { - _logger->print_message(" - IPOPT by A. Waechter and L.T. Biegler (local NLP solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + if (usingIpopt) { + _logger->print_message(" - IPOPT by A. Waechter and L.T. Biegler (local NLP solver)\n", VERB_NORMAL, BAB_VERBOSITY); } - if ( (_maingoSettings->UBP_solverBab == ubp::UBP_SOLVER_KNITRO) || (_maingoSettings->UBP_solverPreprocessing == ubp::UBP_SOLVER_KNITRO) ) { - _logger->print_message(" - Artelys KNITRO by R.H. Byrd, J. Nocedal, and R.A. Waltz (local NLP solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + if (usingKnitro) { + _logger->print_message(" - Artelys KNITRO by R.H. Byrd, J. Nocedal, and R.A. Waltz (local NLP solver)\n", VERB_NORMAL, BAB_VERBOSITY); } - // LBP solvers: - if (_maingoSettings->LBP_solver == lbp::LBP_SOLVER_CPLEX) { - _logger->print_message(" - IBM CPLEX (LP solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + // Print LBP solvers + if (usingCplex) { + _logger->print_message(" - IBM CPLEX (LP solver)\n", VERB_NORMAL, BAB_VERBOSITY); } - if (_maingoSettings->LBP_solver == lbp::LBP_SOLVER_CLP) { - _logger->print_message(" - CLP by J.J. Forrest et al. (LP solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + if (usingClp) { + _logger->print_message(" - CLP by J.J. Forrest et al. (LP solver)\n", VERB_NORMAL, BAB_VERBOSITY); } - - } @@ -621,22 +689,20 @@ MAiNGO::_print_third_party_software_minlp() void MAiNGO::_print_third_party_software_miqp() { - _logger->print_message("\n This MAiNGO run uses the following major pieces of third-party software:\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n This MAiNGO run uses the following major pieces of third-party software:\n", VERB_NORMAL, BAB_VERBOSITY); - _logger->print_message(" - MC++ by B. Chachuat et al. (DAG)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - _logger->print_message(" - FADBAD++ by O. Stauning and C. Bendtsen (automatic differentiation)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" - MC++ by B. Chachuat et al. (DAG)\n", VERB_NORMAL, BAB_VERBOSITY); if (_maingoSettings->UBP_solverPreprocessing == ubp::UBP_SOLVER_CLP) { - _logger->print_message(" - MUMPS by P.R. Amestoy et al. (sparse linear solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - _logger->print_message(" - Netlib BLAS and LAPACK (linear algebra)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" - MUMPS by P.R. Amestoy et al. (sparse linear solver)\n", VERB_NORMAL, BAB_VERBOSITY); + _logger->print_message(" - Netlib BLAS and LAPACK (linear algebra)\n", VERB_NORMAL, BAB_VERBOSITY); } // UBP solvers: if (_maingoSettings->UBP_solverPreprocessing == ubp::UBP_SOLVER_CPLEX) { - _logger->print_message(" - IBM CPLEX ((MI)LP/(MI)QP solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" - IBM CPLEX ((MI)LP/(MI)QP solver)\n", VERB_NORMAL, BAB_VERBOSITY); } if (_maingoSettings->UBP_solverPreprocessing == ubp::UBP_SOLVER_CLP) { - _logger->print_message(" - CLP by J.J. Forrest et al. (LP solver)\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" - CLP by J.J. Forrest et al. (LP solver)\n", VERB_NORMAL, BAB_VERBOSITY); } - - + _logger->print_message("\n", VERB_NORMAL, BAB_VERBOSITY); } \ No newline at end of file diff --git a/src/MAiNGOsetOption.cpp b/src/MAiNGOsetOption.cpp index 194ef29..a495fef 100644 --- a/src/MAiNGOsetOption.cpp +++ b/src/MAiNGOsetOption.cpp @@ -254,6 +254,16 @@ MAiNGO::set_option(const std::string& option, const double value) } } } + else if (option == "BAB_obbtDecayCoefficient") { + if (value < 0) { + _logger->save_setting(BAB_OBBTDECAYCOEFFICIENT, "BAB_obbtDecayCoefficient has to be at least 0 (= no depth-dependent OBBT)"); + _maingoSettings->BAB_obbtDecayCoefficient = 0; + } + else { + _maingoSettings->BAB_obbtDecayCoefficient = value; + _logger->save_setting(BAB_OBBTDECAYCOEFFICIENT, option + " " + oss.str()); + } + } else if (option == "BAB_probing") { if (value < 0) { _logger->save_setting(BAB_PROBING, "BAB_probing has to be 0 or 1, setting to 0"); @@ -319,8 +329,8 @@ MAiNGO::set_option(const std::string& option, const double value) #ifdef HAVE_CPLEX _maingoSettings->LBP_solver = lbp::LBP_SOLVER_CPLEX; #else - logMessage = "Cannot use LBP_solver 2 (LBP_SOLVER_CPLEX) because your MAiNGO build does not contain CPLEX. Setting it to 3 (CLP)"; - _maingoSettings->LBP_solver = lbp::LBP_SOLVER_CLP; + logMessage = "Cannot use LBP_solver 2 (LBP_SOLVER_CPLEX) because your MAiNGO build does not contain CPLEX. Setting it to 3 (CLP)"; + _maingoSettings->LBP_solver = lbp::LBP_SOLVER_CLP; #endif } else if ((int)value == 3) { @@ -493,11 +503,11 @@ MAiNGO::set_option(const std::string& option, const double value) #ifdef HAVE_KNITRO _maingoSettings->UBP_solverPreprocessing = ubp::UBP_SOLVER_KNITRO; #else - logMessage = "Cannot use UBP_solverPreprocessing 6 (UBP_SOLVER_KNITRO) because your MAiNGO build does not contain KNITRO. Setting it to 5"; + logMessage = "Cannot use UBP_solverPreprocessing 6 (UBP_SOLVER_KNITRO) because your MAiNGO build does not contain KNITRO. Setting it to 5"; _maingoSettings->UBP_solverPreprocessing = ubp::UBP_SOLVER_IPOPT; #endif } - + _logger->save_setting(UBP_SOLVERPRE, logMessage); } } @@ -789,10 +799,112 @@ MAiNGO::set_option(const std::string& option, const double value) _logger->save_setting(WRITETOOTHERLANGUAGE, option + " " + oss.str()); } } + else if (option == "growing_dataSizeTol") { +#ifdef HAVE_GROWING_DATASETS + if (value < 0. || value > 1.) { + _logger->save_setting(GROWING_DATASIZETOL, "growing_dataSizeTol has to be between 0 and 1, setting it to default (0.9)"); + _maingoSettings->growing_dataSizeTol = 0.9; + } + else { + _maingoSettings->growing_dataSizeTol = value; + _logger->save_setting(GROWING_DATASIZETOL, option + " " + oss.str()); + } +#else + _logger->save_setting(GROWING_AUGMENTFREQ, "MAiNGO is used without growing datasets: changes of growing_dataSizeTol will not have an effect"); +#endif // HAVE_GROWING_DATASETS + } + else if (option == "growing_dataSizeInit") { +#ifdef HAVE_GROWING_DATASETS + if (value < 0. || value > 1.) { + _logger->save_setting(GROWING_DATASIZEINIT, "growing_dataSizeInit has to be between 0 and 1, setting it to default (0.1)"); + _maingoSettings->growing_dataSizeInit = 0.1; + } + else { + _maingoSettings->growing_dataSizeInit = value; + _logger->save_setting(GROWING_DATASIZEINIT, option + " " + oss.str()); + } +#else + _logger->save_setting(GROWING_AUGMENTFREQ, "MAiNGO is used without growing datasets: changes of growing_dataSizeTol will not have an effect"); +#endif // HAVE_GROWING_DATASETS + } + else if (option == "growing_augmentRule") { +#ifdef HAVE_GROWING_DATASETS + if (value != 0 && value != 1 && value != 2) { + _logger->save_setting(GROWING_AUGMENTRULE, "growing_augmentRule has to be 0, 1, 2, setting it to 2"); + _maingoSettings->growing_augmentRule = AUG_RULE_SCALCST; + } + else { + if ((int)value == 0) { + _maingoSettings->growing_augmentRule = AUG_RULE_CONST; + } + else if ((int)value == 1) { + _maingoSettings->growing_augmentRule = AUG_RULE_SCALING; + } + else if ((int)value == 2) { + _maingoSettings->growing_augmentRule = AUG_RULE_SCALCST; + } + _logger->save_setting(GROWING_AUGMENTRULE, option + " " + oss.str()); + } +#else + _logger->save_setting(GROWING_AUGMENTRULE, "MAiNGO is used without growing datasets: changes of growing_augmentRule will not have an effect"); +#endif // HAVE_GROWING_DATASETS + } + else if (option == "growing_augmentFreq") { +#ifdef HAVE_GROWING_DATASETS + if (_maingoSettings->growing_augmentRule == AUG_RULE_CONST || _maingoSettings->growing_augmentRule == AUG_RULE_SCALCST) { + if (value < 1.) { + _logger->save_setting(GROWING_AUGMENTFREQ, "growing_augmentFreq has to be at least 1, setting it to default (10)"); + _maingoSettings->growing_augmentFreq = 10; + } + else { + _maingoSettings->growing_augmentFreq = (int)value; + _logger->save_setting(GROWING_AUGMENTFREQ, option + " " + oss.str()); + } + } + else { + _logger->save_setting(GROWING_AUGMENTFREQ, "Neither AUG_RULE_CONST nor AUG_RULE_SCALCST is used: changes of growing_augmentFreq will not have an effect"); + } +#else + _logger->save_setting(GROWING_AUGMENTFREQ, "MAiNGO is used without growing datasets: changes of growing_augmentFreq will not have an effect"); +#endif // HAVE_GROWING_DATASETS + } + else if (option == "growing_augmentWeight") { +#ifdef HAVE_GROWING_DATASETS + if (_maingoSettings->growing_augmentRule == AUG_RULE_SCALING || _maingoSettings->growing_augmentRule == AUG_RULE_SCALCST) { + if (value <= 0. || value > 1.) { + _logger->save_setting(GROWING_AUGMENTWEIGHT, "growing_augmentWeight has to be > 0 and <= 1, setting it to default (1)"); + _maingoSettings->growing_augmentWeight = 1.; + } + else { + _maingoSettings->growing_augmentWeight = value; + _logger->save_setting(GROWING_AUGMENTWEIGHT, option + " " + oss.str()); + } + } + else { + _logger->save_setting(GROWING_AUGMENTWEIGHT, "Neither AUG_RULE_SCALING nor AUG_RULE_SCALCST is used: changes of growing_augmentWeight will not have an effect"); + } +#else + _logger->save_setting(GROWING_AUGMENTWEIGHT, "MAiNGO is used without growing datasets: changes of growing_augmentWeight will not have an effect"); +#endif // HAVE_GROWING_DATASETS + } + else if (option == "growing_augmentPercentage") { +#ifdef HAVE_GROWING_DATASETS + if (value < 0. && value > 1.) { + _logger->save_setting(GROWING_AUGMENTPERCENTAGE, "growing_augmentPercentage has to be between 0 and 1, setting it to default (0.25)"); + _maingoSettings->growing_augmentPercentage = 0.25; + } + else { + _maingoSettings->growing_augmentPercentage = value; + _logger->save_setting(GROWING_AUGMENTPERCENTAGE, option + " " + oss.str()); + } +#else + _logger->save_setting(GROWING_AUGMENTPERCENTAGE, "MAiNGO is used without growing datasets: changes of growing_augmentPercentage will not have an effect"); +#endif // HAVE_GROWING_DATASETS + } else { _logger->save_setting(UNKNOWN_SETTING, "Could not find setting " + option + ". Proceeding."); return false; } return true; -} \ No newline at end of file +} diff --git a/src/MAiNGOtoOtherLanguage.cpp b/src/MAiNGOtoOtherLanguage.cpp index 5b90233..f90c437 100644 --- a/src/MAiNGOtoOtherLanguage.cpp +++ b/src/MAiNGOtoOtherLanguage.cpp @@ -52,10 +52,10 @@ MAiNGO::write_model_to_file_in_other_language(const WRITING_LANGUAGE writingLang std::ostringstream ostr; ostr << "\n You need to set your model before writing it to file in a different modeling language. Writing of model to file aborted. Proceeding...\n"; if (_inMAiNGOsolve) { - _logger->print_message(ostr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(ostr.str(), VERB_NORMAL, BAB_VERBOSITY); } else { - _logger->print_message_to_stream_only(ostr.str(), _maingoSettings->loggingDestination); + _logger->print_message_to_stream_only(ostr.str()); } return; } @@ -85,10 +85,10 @@ MAiNGO::write_model_to_file_in_other_language(const WRITING_LANGUAGE writingLang std::ostringstream ostr; ostr << "\n Unknown or not supported modeling language. Writing of model to file aborted. Proceeding...\n"; if (_inMAiNGOsolve) { - _logger->print_message(ostr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(ostr.str(), VERB_NORMAL, BAB_VERBOSITY); } else { - _logger->print_message_to_stream_only(ostr.str(), _maingoSettings->loggingDestination); + _logger->print_message_to_stream_only(ostr.str()); } break; } @@ -98,10 +98,10 @@ MAiNGO::write_model_to_file_in_other_language(const WRITING_LANGUAGE writingLang std::ostringstream ostr; ostr << " Warning: Function ENTHALPY_OF_VAPORIZATION is piecewise defined in MAiNGO. Only the subcritical part will be used.\n"; if (_inMAiNGOsolve) { - _logger->print_message(ostr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(ostr.str(), VERB_NORMAL, BAB_VERBOSITY); } else { - _logger->print_message_to_stream_only(ostr.str(), _maingoSettings->loggingDestination); + _logger->print_message_to_stream_only(ostr.str()); } mc::FFToString::options.USED_ENTHALPY_OF_VAPORIZATION = false; // Reset this value } @@ -117,10 +117,10 @@ MAiNGO::_write_gams_file(const std::string gamsFileName, const std::string solve std::ostringstream ostr; ostr << "\n Writing GAMS file. Depending on your model size and complexity, this may need a lot of memory and time...\n"; if (_inMAiNGOsolve) { - _logger->print_message(ostr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(ostr.str(), VERB_NORMAL, BAB_VERBOSITY); } else { - _logger->print_message_to_stream_only(ostr.str(), _maingoSettings->loggingDestination); + _logger->print_message_to_stream_only(ostr.str()); } std::string str; @@ -459,10 +459,10 @@ MAiNGO::_write_gams_functions(std::ofstream &gamsFile, bool writeRelaxationOnly) outstr << " Warning: Your model contains relaxation-only constraints. These will be written to the GAMS model, but it is up to you to mark them as relaxation-only!" << std::endl << " Otherwise, they will be treated as regular constraints. To our knowledge, BARON is the only solver in GAMS that supports relaxation-only constraints." << std::endl; if (_inMAiNGOsolve) { - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY); } else { - _logger->print_message_to_stream_only(outstr.str(), _maingoSettings->loggingDestination); + _logger->print_message_to_stream_only(outstr.str()); } gamsFile << "*Warning: Your model contains relaxation-only constraints. These have been written to the GAMS model, but it is up to you to mark them as relaxation-only!" << std::endl << "* Otherwise, they will be treated as regular constraints. To our knowledge, BARON is the only solver in GAMS that supports relaxation-only constraints." << std::endl; @@ -517,10 +517,10 @@ MAiNGO::_write_gams_functions(std::ofstream &gamsFile, bool writeRelaxationOnly) outstr << " Warning: Your model contains squash inequalities. These have been written to the GAMS model and will be treated as regular constraints." << std::endl << " To our knowledge, GAMS does not supports squash inequalities." << std::endl; if (_inMAiNGOsolve) { - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY); } else { - _logger->print_message_to_stream_only(outstr.str(), _maingoSettings->loggingDestination); + _logger->print_message_to_stream_only(outstr.str()); } str = str + "*Warning: Your model contains squash inequalities. These have been written to the GAMS model and will be treated as regular constraints. To our knowledge, GAMS does not supports squash inequalities.\n"; str = str + "*Squash Inequalities\n"; @@ -648,10 +648,10 @@ MAiNGO::_write_ale_file(const std::string aleFileName, const std::string solverN std::ostringstream ostr; ostr << "\n Writing ALE file. Depending on your model size and complexity, this may need a lot of memory and time...\n"; if (_inMAiNGOsolve) { - _logger->print_message(ostr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(ostr.str(), VERB_NORMAL, BAB_VERBOSITY); } else { - _logger->print_message_to_stream_only(ostr.str(), _maingoSettings->loggingDestination); + _logger->print_message_to_stream_only(ostr.str()); } std::string str; @@ -944,6 +944,19 @@ MAiNGO::_write_ale_functions(std::ofstream &aleFile, bool writeRelaxationOnly) aleFile << str; str.clear(); +#ifdef HAVE_GROWING_DATASETS + aleFile << "objectivePerData:\n"; + // Objective per data + aleFile << "#The sum over all objective per data functions,\n"; + aleFile << "#i.e., equal to the objective considering the full dataset stated below \n "; + longstr = resultString[0].get_function_string(); + _add_linebreaks_to_gams_string(longstr); + str = str + " " + longstr + " = 0;\n"; + aleFile << " " << str << "\n"; + longstr.clear(); + str.clear(); +#endif // HAVE_GROWING_DATASETS + aleFile << "objective:\n"; // Objective function last aleFile << "#Objective function\n"; diff --git a/src/aleModel.cpp b/src/aleModel.cpp index 7221f59..99df280 100644 --- a/src/aleModel.cpp +++ b/src/aleModel.cpp @@ -61,6 +61,11 @@ AleModel::evaluate(const std::vector<Var>& optVars) result.objective.push_back(obj, it->m_note); } + for (auto it = _prog.mObjectivePerData.begin(); it != _prog.mObjectivePerData.end(); ++it) { + auto obj = eval.dispatch(*it); + result.objective_per_data.push_back(obj.eq, it->m_note); + } + for (auto it = _prog.mConstraints.begin(); it != _prog.mConstraints.end(); ++it) { auto cons = eval.dispatch(*it); result.eq.push_back(cons.eq, it->m_note); diff --git a/src/bab.cpp b/src/bab.cpp index 56d5c48..c9ef229 100644 --- a/src/bab.cpp +++ b/src/bab.cpp @@ -11,11 +11,13 @@ #include "bab.h" #include "MAiNGOException.h" +#include "decayingProbability.h" #include "getTime.h" #include "lbp.h" #include "mpiUtilities.h" #include "ubp.h" +#include <iterator> #include <limits> using namespace maingo; @@ -47,7 +49,11 @@ BranchAndBound::BranchAndBound(const std::vector<babBase::OptimizationVariable> MAiNGO_IF_BAB_MANAGER _informedWorkerAboutIncumbent = std::vector<bool>(_nProcs - 1, false); - _nodesGivenToWorkers = std::vector<std::pair<bool, double>>(_nProcs - 1, std::make_pair(false, _maingoSettings->infinity)); +#ifdef HAVE_GROWING_DATASETS + _informedWorkerAboutDataset = std::vector<bool>(_nProcs - 1, true); // Initial dataset is always build - no need to send it +#endif // HAVE_GROWING_DATASETS + + _nodesGivenToWorkers = std::vector<std::pair<bool, double>>(_nProcs - 1, std::make_pair(false, _maingoSettings->infinity)); MAiNGO_ELSE _brancher = nullptr; MAiNGO_END_IF @@ -116,6 +122,10 @@ BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std:: double startTime, currentTime, lastTime; double currentLBD; // This is declared here to be available for the Manager and Workers in the parallel version +#ifdef HAVE_GROWING_DATASETS + std::shared_ptr<babBase::BabNode> incumbentNode; // Auxiliary variable for storing node containing the best incumbent (may already have been fathomed) +#endif // HAVE_GROWING_DATASETS + MAiNGO_IF_BAB_MANAGER // Store initial feasible point if given if (!solutionPoint.empty()) { @@ -128,10 +138,15 @@ BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std:: // Hand root node to _brancher // Change id from 0 to 1 since we start B&B - rootNodeIn = babBase::BabNode(rootNodeIn.get_pruning_score(), rootNodeIn.get_lower_bounds(), rootNodeIn.get_upper_bounds(), 1, 0, rootNodeIn.holds_incumbent()); + rootNodeIn = babBase::BabNode(rootNodeIn.get_pruning_score(), rootNodeIn.get_lower_bounds(), rootNodeIn.get_upper_bounds(), 0 /*index data set*/, 1 /*ID*/, 0 /*depth*/, rootNodeIn.get_augment_data()); _brancher->insert_root_node(rootNodeIn); _nNodesLeft = _brancher->get_nodes_in_tree(); +#ifdef HAVE_GROWING_DATASETS + // Initialize node containing solution with root node + incumbentNode = std::make_shared<babBase::BabNode>(rootNodeIn); +#endif // HAVE_GROWING_DATASETS + // Initialize stuff for timing and logging _timePreprocess = preprocessTime; _timePassed = preprocessTime; @@ -152,7 +167,7 @@ BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std:: // 1-10: Main Branch-and-Bound loop // --------------------------------------------------------------------------------- MAiNGO_IF_BAB_MANAGER - _logger->print_message("\n Entering branch-and-bound loop:\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message("\n Entering branch-and-bound loop:\n", VERB_NORMAL, BAB_VERBOSITY); MAiNGO_END_IF // Check termination criteria: when terminating regularly, no nodes should be left; otherwise, termination occurs when hitting CPU time or node limit @@ -235,6 +250,9 @@ BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std:: // Send node to worker // ----------------------------------- _send_new_problem(currentNode, status.MPI_SOURCE); +#ifdef HAVE_GROWING_DATASETS + _send_new_dataset((*_datasets).back(), status.MPI_SOURCE); +#endif // HAVE_GROWING_DATASETS _nNodesLeft = _brancher->get_nodes_in_tree(); _nodesGivenToWorkers[status.MPI_SOURCE - 1].first = true; _nodesGivenToWorkers[status.MPI_SOURCE - 1].second = currentNode.get_pruning_score(); @@ -261,6 +279,9 @@ BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std:: // Receive new problem and update _incumbent if necessary _recv_new_problem(currentNode); +#ifdef HAVE_GROWING_DATASETS + _recv_new_dataset(); +#endif // HAVE_GROWING_DATASETS #endif //------------------------------------- // Process Node: @@ -282,8 +303,8 @@ BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std:: bool foundNewFeasiblePoint = false; double ubpObjectiveValue = _maingoSettings->infinity; std::vector<double> ubpSolutionPoint; - std::tie(nodeProvenInfeasible, nodeConverged, nAddedLBSolves, nAddedUBSolves, currentLBD, lbpSolutionPoint, foundNewFeasiblePoint, ubpObjectiveValue, ubpSolutionPoint) = _process_node(currentNode); + std::tie(nodeProvenInfeasible, nodeConverged, nAddedLBSolves, nAddedUBSolves, currentLBD, lbpSolutionPoint, foundNewFeasiblePoint, ubpObjectiveValue, ubpSolutionPoint) = _process_node(currentNode); #ifdef HAVE_MAiNGO_MPI // ----------------------------------- @@ -359,27 +380,61 @@ BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std:: _update_incumbent_and_fathom(ubpObjectiveValue, ubpSolutionPoint, currentNode.get_ID()); } #endif - // Update incumbent node information + +#ifdef HAVE_GROWING_DATASETS if (currentNode.get_ID() == _incumbentNodeId) { - currentNode.set_holds_incumbent(true); + incumbentNode = std::make_shared<babBase::BabNode>(currentNode); + } + + // ----------------------------------- + // MAiNGO with growing datasets: Augmenting + // ----------------------------------- + if (nodeProvenInfeasible == false) { + currentNode.set_augment_data(_check_whether_to_augment(currentNode, lbpSolutionPoint, currentLBD)); + if (currentNode.get_augment_data()) { + unsigned int newDataIndex = _augment_dataset(currentNode); + if ((nodeConverged == false) || (currentNode.get_index_dataset() > 0)) { + // If not converged with full data + _brancher->add_node_with_new_data_index(currentNode, newDataIndex); + } + } } else { - currentNode.set_holds_incumbent(false); + // For logging: infeasible node is never augmented (but discarded) + currentNode.set_augment_data(false); } - +#endif // HAVE_GROWING_DATASETS // ----------------------------------- // 7. Branching // ----------------------------------- - // Inform brancher about changes in node - _brancher->register_node_change(currentNode.get_ID(), currentNode); - // If node is feasible but it did not converge yet branch it bool nodeReachedMinRelNodeSize = false; // nodeReachedMinRelNodeSize indicates whether the brancher chose not to branch on the node because it is too small - if ((nodeProvenInfeasible == false) && (nodeConverged == false)) { - currentNode.set_pruning_score(currentLBD); - bool dummy; - std::tie(dummy, nodeReachedMinRelNodeSize) = _brancher->branch_on_node(currentNode, lbpSolutionPoint, currentLBD, _incumbentNodeId, _maingoSettings->relNodeTol); + + // Either branch or augment + // Without growing datasets: always branch (_augmentData == false by initialization and never changed) + if (!currentNode.get_augment_data()) { + // Inform brancher about changes in node + _brancher->register_node_change(currentNode.get_ID(), currentNode); + // If node is feasible but it did not converge yet branch it + bool nodeReachedMinRelNodeSize = false; // nodeReachedMinRelNodeSize indicates whether the brancher chose not to branch on the node because it is too small + if ((nodeProvenInfeasible == false) && (nodeConverged == false)) { + currentNode.set_pruning_score(currentLBD); + bool dummy; + std::tie(dummy, nodeReachedMinRelNodeSize) = _brancher->branch_on_node(currentNode, lbpSolutionPoint, currentLBD, _maingoSettings->relNodeTol); + } + } + +#ifdef HAVE_GROWING_DATASETS + _nNodesLeft = _brancher->get_nodes_in_tree(); + if (_nNodesLeft == 0 && currentNode.get_index_dataset() > 0) { + // If the optimality gap can't be closed based on the reduced problem + // and rules like SCALING are not able to trigger augmenting, + // use node containing incumbent to get correct LB + currentNode.set_augment_data(true); + _brancher->add_node_with_new_data_index(*incumbentNode, 0); } +#endif // !HAVE_GROWING_DATASETS + // ----------------------------------- // 8. Heuristic checks @@ -420,7 +475,7 @@ BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std:: std::ostringstream outstr; outstr << " Days spent: " << _daysPassed << std::endl << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY); MAiNGO_END_IF } _timePassed = _timePreprocess + _daysPassed * 86400 + (currentTime - startTime); @@ -428,6 +483,9 @@ BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std:: MAiNGO_IF_BAB_MANAGER // Print output _display_and_log_progress(currentLBD, currentNode); +#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) && !defined(HAVE_MAiNGO_MPI) + _log_nodes(currentNode, currentLBD, _currentLBDFull, ubpObjectiveValue, lbpSolutionPoint, _lbpSolutionPointFull); +#endif // ----------------------------------- // 11. Check termination @@ -546,6 +604,12 @@ BranchAndBound::_process_node(babBase::BabNode ¤tNode) double ubpObjectiveValue = _maingoSettings->infinity; std::vector<double> ubpSolutionPoint; +#ifdef HAVE_GROWING_DATASETS + unsigned int indexDataset = currentNode.get_index_dataset(); + + _UBS->change_growing_objective(indexDataset); + _LBS->change_growing_objective(indexDataset); +#endif // HAVE_GROWING_DATASETS // ----------------------------------- // 2. Node pre-processing: Constraint propagation and Optimization-based bound tightening (OBBT) @@ -591,6 +655,12 @@ BranchAndBound::_process_node(babBase::BabNode ¤tNode) currentLBD = _maingoSettings->infinity; } +#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) && !defined(HAVE_MAiNGO_MPI) + if (currentNode.get_index_dataset() == 0) { + _currentLBDFull = currentLBD; + _lbpSolutionPointFull = lbpSolutionPoint; + } +#endif return std::make_tuple(nodeProvenInfeasible, nodeConverged, lbdcnt, ubdcnt, currentLBD, lbpSolutionPoint, foundNewFeasiblePoint, ubpObjectiveValue, ubpSolutionPoint); } @@ -624,7 +694,9 @@ BranchAndBound::_preprocess_node(babBase::BabNode ¤tNode) nodeProvenInfeasible = true; } - else { // If constraint propagation did not prove node to be infeasible, proceed with obbt + // If constraint propagation did not prove node to be infeasible, + // proceed with OBBT with a probability dependent on the depth of the current node. + else if (do_based_on_decaying_probability(_maingoSettings->BAB_obbtDecayCoefficient, currentNode.get_depth())) { // ----------------------------------- // 2b Optimization-based bound tightening (OBBT) @@ -667,11 +739,11 @@ BranchAndBound::_solve_LBP(const babBase::BabNode ¤tNode) SUBSOLVER_RETCODE lbpStatus = _LBS->solve_LBP(currentNode, currentLBD, lbpSolutionPoint, dualInfo); if (currentLBD < parentLBD) { - if (_maingoSettings->BAB_verbosity > VERB_NORMAL) { - std::ostringstream outstr; - outstr << " LBD obtained for node " << currentNode.get_ID() << " is lower than LBD of its parent node. Using parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + + std::ostringstream outstr; + outstr << " LBD obtained for node " << currentNode.get_ID() << " is lower than LBD of its parent node. Using parent LBD." << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, BAB_VERBOSITY); + currentLBD = parentLBD; } @@ -683,15 +755,23 @@ BranchAndBound::_solve_LBP(const babBase::BabNode ¤tNode) // Check if LBP proved that node cannot contain a better point if (babBase::larger_or_equal_within_rel_and_abs_tolerance(currentLBD, _ubd, _maingoSettings->epsilonR, _maingoSettings->epsilonA)) { // Ok, fathomed by value dominance - if (_maingoSettings->BAB_verbosity > VERB_NORMAL) { - std::ostringstream outstr; - outstr << " Node #" << currentNode.get_ID() << " converged with LBD " << currentLBD << " to UBD " << _ubd << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + + std::ostringstream outstr; + outstr << " Node #" << currentNode.get_ID() << " converged with LBD " << currentLBD << " to UBD " << _ubd << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, BAB_VERBOSITY); nodeConverged = true; } +#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) && !defined(HAVE_MAiNGO_MPI) + //catch information for full dataset (!= reduced dataset) for this node + lbp::LbpDualInfo dualInfoFull; //dummy for calling solve_LBP + + if (currentNode.get_index_dataset() > 0) { + _currentLBDFull = parentLBD; + SUBSOLVER_RETCODE lbpStatusFull = _LBSFull->solve_LBP(currentNode, _currentLBDFull, _lbpSolutionPointFull, dualInfoFull); + } +#endif return std::make_tuple(nodeProvenInfeasible, nodeConverged, currentLBD, lbpSolutionPoint, dualInfo); } @@ -730,7 +810,7 @@ BranchAndBound::_solve_UBP(const babBase::BabNode ¤tNode, std::vector<doub std::ostringstream outstr; outstr << " Warning: UBD found in node " << currentNode.get_ID() << " is lower than the MAiNGO infinity value " << -_maingoSettings->infinity << ".\n"; outstr << " Please consider scaling your objective function.\n"; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY); } } @@ -743,11 +823,11 @@ BranchAndBound::_solve_UBP(const babBase::BabNode ¤tNode, std::vector<doub // Check if this feasible point is better than the incumbent & fathom by value dominance (first remaining tree and then current node): if (babBase::larger_or_equal_within_rel_and_abs_tolerance(currentLBD, std::min(_ubd, ubpObjectiveValue), _maingoSettings->epsilonR, _maingoSettings->epsilonA)) { // Ok, fathomed by value dominance - if (_maingoSettings->BAB_verbosity > VERB_NORMAL) { - std::ostringstream outstr; - outstr << " Node #" << currentNode.get_ID() << " converged with LBD " << currentLBD << " to UBD " << _ubd << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + + std::ostringstream outstr; + outstr << " Node #" << currentNode.get_ID() << " converged with LBD " << currentLBD << " to UBD " << _ubd << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, BAB_VERBOSITY); + nodeConverged = true; } } @@ -817,7 +897,6 @@ BranchAndBound::_update_incumbent_and_fathom(const double solval, const std::vec #endif // Inform _brancher about new incumbent - _brancher->set_new_incumbent_point(_incumbent); size_t nodesBefore = _brancher->get_nodes_in_tree(); double smallestFathomedLBD = _brancher->decrease_pruning_score_threshold_to(_ubd); // Here, nodes with lbd exceeding the new _ubd are fathomed _bestLbdFathomed = std::min(smallestFathomedLBD, _bestLbdFathomed); @@ -860,6 +939,104 @@ BranchAndBound::_update_lowest_lbd() } +#ifdef HAVE_GROWING_DATASETS +//////////////////////////////////////////////////////////////////////////////////////// +// function which checks whether to augment the dataset +bool +BranchAndBound::_check_whether_to_augment(const babBase::BabNode ¤tNode, const std::vector<double> &lbpSolutionPoint, const double currentLBD) +{ + bool augment = false; + + if (!(currentNode.get_ID() == 1 || currentNode.get_augment_data() == true || currentNode.get_index_dataset() == 0)) { + // Do not augment in root node, if data already augmented in this node, or if already full dataset considered + // Otherwise follow augmentation rule + switch (_maingoSettings->growing_augmentRule) { + case AUG_RULE_CONST: { + // Augment nodes in depth which is a multiple of freq + augment = !((int)fmod(currentNode.get_depth(), (double)_maingoSettings->growing_augmentFreq)); + break; + } + case AUG_RULE_SCALING: { + double scaledLBD = _maingoSettings->growing_augmentWeight * currentLBD * (double)(*_datasets)[0].size() / (double)(*_datasets)[currentNode.get_index_dataset()].size(); + augment = babBase::larger_or_equal_within_rel_and_abs_tolerance(scaledLBD, _ubd, _maingoSettings->epsilonR, _maingoSettings->epsilonA); + break; + } + case AUG_RULE_SCALCST: { + // Augment if SCALING condition is met + double scaledLBD = _maingoSettings->growing_augmentWeight * currentLBD * (double)(*_datasets)[0].size() / (double)(*_datasets)[currentNode.get_index_dataset()].size(); + augment = babBase::larger_or_equal_within_rel_and_abs_tolerance(scaledLBD, _ubd, _maingoSettings->epsilonR, _maingoSettings->epsilonA); + // Or if depth of node is a multiple of freq + augment = augment || (!((int)fmod(currentNode.get_depth(), (double)_maingoSettings->growing_augmentFreq))); + break; + } + default: { + break; + } + } + } + + return augment; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// function for augmenting dataset of node +unsigned int +BranchAndBound::_augment_dataset(babBase::BabNode ¤tNode) +{ + // Catch current state + unsigned int newIndex = currentNode.get_index_dataset(); + unsigned int noData = (*_datasets)[0].size(); + unsigned int noDataOld = (*_datasets)[newIndex].size(); + + // Use next larger reduced dataset + newIndex++; + if (newIndex >= (*_datasets).size()) { // If dataset doesn't exist: build it + + // Simplest rule: augment by x%*nData data points, but at least 1 point + int noAddedData = std::round((double)_maingoSettings->growing_augmentPercentage * (double)noData); + noAddedData = std::max(1, noAddedData); + + if (noDataOld + noAddedData < _maingoSettings->growing_dataSizeTol * noData) { // Add a new dataset + + // Simplest rule: new dataset extends previous dataset + std::set<unsigned int> newDataset = (*_datasets)[newIndex - 1]; + + std::set<unsigned int> unusedDataPoints; + std::set_difference((*_datasets)[0].begin(), (*_datasets)[0].end(), (*_datasets)[newIndex - 1].begin(), (*_datasets)[newIndex - 1].end(), std::inserter(unusedDataPoints, unusedDataPoints.end())); + + // Pick (pseudo-)random data points + std::srand(5 + _iterations); // For reproducible results + std::pair<std::set<unsigned int>::iterator, bool> ans; + int count = 0; + + while (count < noAddedData) { + double tmpRand = std::rand() / ((double)RAND_MAX + 1); // Random value between 0. and 1. + + // Choose from unused data points (set difference) + unsigned int idxRand = tmpRand * unusedDataPoints.size(); + ans = newDataset.insert(*(std::next(unusedDataPoints.begin(), idxRand))); + if (ans.second == true) { + count++; + } + } + (*_datasets).push_back(newDataset); + +#ifdef HAVE_MAiNGO_MPI + // There is a new dataset for all future worker processes + _informedWorkerAboutDataset = std::vector<bool>(_nProcs - 1, false); +#endif + } + else { // Next dataset is the full dataset + newIndex = 0; + } + } + + return newIndex; +} +#endif // HAVE_GROWING_DATASETS + + //////////////////////////////////////////////////////////////////////////////////////// // function which checks whether it is necessary to activate scaling within the LBD solver. This is a heuristic approach, which does not affect any deterministic optimization assumptions void @@ -882,8 +1059,7 @@ BranchAndBound::_check_if_more_scaling_needed() _inform_worker_about_event(BCAST_SCALING_NEEDED, true); #endif if (_maingoSettings->LBP_solver > 1) { // LBP_solver = 0 is MAiNGO default, LBP_solver = 1 is interval-based - VERB verbosityGiven = std::max(_maingoSettings->BAB_verbosity, _maingoSettings->LBP_verbosity); - _logger->print_message(" Warning: Additional scaling in the lower bounding solver activated.\n", verbosityGiven, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Warning: Additional scaling in the lower bounding solver activated.\n", VERB_NORMAL, BAB_VERBOSITY, LBP_VERBOSITY); } } } @@ -905,8 +1081,11 @@ BranchAndBound::_display_and_log_progress(const double currentNodeLBD, const bab bool maxTimeReached = (_timePassed >= _maingoSettings->maxTime && _workCount == 0); bool maxIterationsReached = (_iterations >= _maingoSettings->BAB_maxIterations && _workCount == 0); bool specialOccasion = _printNewIncumbent || solved || maxNodesReached || maxTimeReached || maxIterationsReached || _iterations == 1; - bool printToStream = specialOccasion || (!((int)fmod((double)_iterations, (double)_maingoSettings->BAB_printFreq))); - bool printToLog = specialOccasion || (!((int)fmod((double)_iterations, (double)_maingoSettings->BAB_logFreq))); +#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) + specialOccasion = specialOccasion || currentNode.get_augment_data(); +#endif + bool printToStream = specialOccasion || (!((int)fmod((double)_iterations, (double)_maingoSettings->BAB_printFreq))); + bool printToLog = specialOccasion || (!((int)fmod((double)_iterations, (double)_maingoSettings->BAB_logFreq))); std::ostringstream strout; std::ostringstream stroutCsv; @@ -920,6 +1099,11 @@ BranchAndBound::_display_and_log_progress(const double currentNodeLBD, const bab << std::setw(15) << "NodeLBD " << " " #endif +#ifdef HAVE_GROWING_DATASETS + << std::setw(6) << " NoData" + << std::setw(1) + << " " +#endif // HAVE_GROWING_DATASETS << std::setw(15) << "LBD " << " " << std::setw(15) << "UBD " @@ -949,6 +1133,17 @@ BranchAndBound::_display_and_log_progress(const double currentNodeLBD, const bab << std::setw(9) << currentNode.get_ID() << " " << std::setw(15) << currentNodeLBD << " " #endif +#ifdef HAVE_GROWING_DATASETS + << std::setw(6) << (*_datasets)[currentNode.get_index_dataset()].size() + << " "; + if (currentNode.get_augment_data()) { + strout << "A"; + } + else { + strout << "B"; + } + strout << " " +#endif // HAVE_GROWING_DATASETS << std::setw(15) << _lbd << " " << std::setw(15) << _ubd << " " << std::setw(9) << _nNodesLeft << " " @@ -968,6 +1163,17 @@ BranchAndBound::_display_and_log_progress(const double currentNodeLBD, const bab << std::setw(9) << currentNode.get_ID() << "," << std::setw(15) << currentNodeLBD << "," #endif +#ifdef HAVE_GROWING_DATASETS + << std::setw(6) << (*_datasets)[currentNode.get_index_dataset()].size() + << " "; + if (currentNode.get_augment_data()) { + stroutCsv << "A"; + } + else { + stroutCsv << "B"; + } + stroutCsv << " " +#endif // HAVE_GROWING_DATASETS << std::setw(15) << _lbd << "," << std::setw(15) << _ubd << "," << std::setw(9) << _nNodesLeft << "," @@ -986,7 +1192,7 @@ BranchAndBound::_display_and_log_progress(const double currentNodeLBD, const bab _linesprinted++; if (printToStream) { // This is done instead of print_message, since the logging freq and print freq may differ - _logger->print_message_to_stream_only(strout.str(), _maingoSettings->loggingDestination); + _logger->print_message_to_stream_only(strout.str()); } } @@ -1012,6 +1218,138 @@ BranchAndBound::_display_and_log_progress(const double currentNodeLBD, const bab } +#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) +//////////////////////////////////////////////////////////////////////////////////////// +// function for printing the current node to a separate log file +void +BranchAndBound::_log_nodes(const babBase::BabNode ¤tNode, const double currentNodeLbd, const double currentNodeLbdFull, const double currentNodeUbdFull, const std::vector<double> lbpSolutionPoint, const std::vector<double> lbpSolutionPointFull) +{ + +#ifndef HAVE_MAiNGO_MPI + int _workCount = 0; +#endif + // Print whenever B&B progress is logged + bool solved = (_nNodesLeft == 0 && _workCount == 0); + bool maxNodesReached = (_nNodesLeft >= _maingoSettings->BAB_maxNodes && _workCount == 0); + bool maxTimeReached = (_timePassed >= _maingoSettings->maxTime && _workCount == 0); + bool maxIterationsReached = (_iterations >= _maingoSettings->BAB_maxIterations && _workCount == 0); + bool specialOccasion = _printNewIncumbent || solved || maxNodesReached || maxTimeReached || maxIterationsReached || _iterations == 1; + specialOccasion = specialOccasion || currentNode.get_augment_data(); + bool printToLog = specialOccasion || (!((int)fmod((double)_iterations, (double)_maingoSettings->BAB_logFreq))); + + std::ostringstream strout; + std::ofstream logFile; + std::string fileName = "maingoNodes.log"; + + if (_iterations == 1) { + // Create new file + logFile.open(fileName, std::ios::out); + logFile.close(); + + // Print header + strout << " " << std::setw(9) << "Iteration" + << " " + << std::setw(9) << " NodeId " + << " " + << std::setw(6) << "Depth" + << " " + << std::setw(9) << "NoData" + << " " + << std::setw(15) << "NodeLB_red " + << " " + << std::setw(15) << "NodeLB_full " + << " " + << std::setw(15) << "LB " + << " " + << std::setw(15) << "NodeUB " + << " " + << std::setw(15) << "UB " + << " "; + for (size_t i = 0; i < _incumbent.size(); i++) { + strout << std::setw(9) << "lb[" + << i << "] " + << " "; + } + for (size_t i = 0; i < _incumbent.size(); i++) { + strout << std::setw(9) << "ub[" + << i << "] " + << " "; + } + for (size_t i = 0; i < _incumbent.size(); i++) { + strout << std::setw(9) << "pointLb[" + << i << "] " + << " "; + } + for (size_t i = 0; i < _incumbent.size(); i++) { + strout << std::setw(12) << "pointLbFull[" + << i << "]" + << " "; + } + strout << std::endl; + } + + // Information of current node + std::vector<double> lbdVar = currentNode.get_lower_bounds(); + std::vector<double> ubdVar = currentNode.get_upper_bounds(); + if (printToLog) { + strout.setf(std::ios_base::scientific); + strout << std::setw(9) << _iterations << " " + << std::setw(9) << currentNode.get_ID() << " " + << std::setw(6) << currentNode.get_depth() << " " + << std::setw(6) << (*_datasets)[currentNode.get_index_dataset()].size() + << " "; + if (currentNode.get_augment_data()) { + strout << "A"; + } + else { + strout << "B"; + } + strout << " " + << std::setw(15) << currentNodeLbd << " " + << std::setw(15) << currentNodeLbdFull << " " + << std::setw(15) << _lbd << " " + << std::setw(15) << currentNodeUbdFull << " " + << std::setw(15) << _ubd << " "; + for (size_t i = 0; i < _incumbent.size(); i++) { + strout << std::setw(15) << lbdVar[i] << " "; + } + for (size_t i = 0; i < _incumbent.size(); i++) { + strout << std::setw(15) << ubdVar[i] << " "; + } + if (lbpSolutionPoint.size() > 0) { // Feasible point found + for (size_t i = 0; i < _incumbent.size(); i++) { + strout << std::setw(15) << lbpSolutionPoint[i] << " "; + } + } + else { // No feasible point found + for (size_t i = 0; i < _incumbent.size(); i++) { + strout << std::setw(15) << "N/A" + << " "; + } + } + if (lbpSolutionPointFull.size() > 0) { // Feasible point found + for (size_t i = 0; i < _incumbent.size(); i++) { + strout << std::setw(15) << lbpSolutionPointFull[i] << " "; + } + } + else { // No feasible point found + for (size_t i = 0; i < _incumbent.size(); i++) { + strout << std::setw(15) << "N/A" + << " "; + } + } + strout << std::endl; + strout.unsetf(std::ios_base::scientific); + + // Write line to separate log file + logFile.open(fileName, std::ios::app); + logFile << strout.str(); + logFile.close(); + } +} +#endif + + //////////////////////////////////////////////////////////////////////////////////////// // function for printing exactly one node void @@ -1022,7 +1360,7 @@ BranchAndBound::_print_one_node(const double theLBD, const int ID, const std::ve for (unsigned int i = 0; i < _nvar; i++) { outstr << " " << std::setprecision(16) << "var " << i + 1 << " " << lowerVarBounds[i] << "..." << upperVarBounds[i] << std::endl; } - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_ALL, BAB_VERBOSITY); } @@ -1085,12 +1423,12 @@ BranchAndBound::_check_termination() if (_status == babBase::enums::GLOBALLY_OPTIMAL || _status == babBase::enums::INFEASIBLE) { // Print on screen and write to log - _logger->print_message(" Done.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Done.\n", VERB_NORMAL, BAB_VERBOSITY); _print_termination("* *** Regular termination. *** *"); } else { // Print on screen and write to log - _logger->print_message(" Done.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Done.\n", VERB_NORMAL, BAB_VERBOSITY); _print_termination("* *** Regular termination. *** *\n* *** Reached minimum node size. User-defined optimality tolerance could not be reached *** *"); } } @@ -1100,7 +1438,7 @@ BranchAndBound::_check_termination() } terminate = _TERMINATED; _status = babBase::enums::TARGET_UBD; - _logger->print_message(" Done.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Done.\n", VERB_NORMAL, BAB_VERBOSITY); _print_termination("* *** Reached target upper bound. *** *"); } else if (reachedTargetLbd) { // Reached user-defined lower bound @@ -1109,7 +1447,7 @@ BranchAndBound::_check_termination() } terminate = _TERMINATED; _status = babBase::enums::TARGET_LBD; - _logger->print_message(" Done.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Done.\n", VERB_NORMAL, BAB_VERBOSITY); _print_termination("* *** Reached target lower bound. *** *"); } else if (_maingoSettings->terminateOnFeasiblePoint && _foundFeas) { // Found feasible point and this is enough for the user @@ -1118,7 +1456,7 @@ BranchAndBound::_check_termination() } terminate = _TERMINATED; _status = babBase::enums::FEASIBLE_POINT_ONLY; - _logger->print_message(" Done.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Done.\n", VERB_NORMAL, BAB_VERBOSITY); _print_termination("* *** Found feasible point. *** *"); } else if (maxNodesReached || maxTimeReached || maxIterationsReached) { // Reached some other termination criterion. @@ -1175,9 +1513,9 @@ BranchAndBound::_check_termination() // Query input outstr << " Do you want to continue (y/n)? "; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NONE, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NONE, BAB_VERBOSITY); while (getline(std::cin, userInput) && userInput != "y" && userInput != "n") { - _logger->print_message(" Invalid input. Please type 'y' or 'n' and press enter: ", _maingoSettings->BAB_verbosity, VERB_NONE, _maingoSettings->loggingDestination); + _logger->print_message(" Invalid input. Please type 'y' or 'n' and press enter: ", VERB_NONE, BAB_VERBOSITY); } // Interpret input @@ -1185,18 +1523,18 @@ BranchAndBound::_check_termination() outstr.str(""); outstr.clear(); outstr << " User input: yes\n Enter new limit for " << criterion << ": "; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NONE, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NONE, BAB_VERBOSITY); while (getline(std::cin, userInput) && (*limit >= atof(userInput.c_str()))) { outstr.str(""); outstr.clear(); outstr << " Invalid input (has to be greater than " << *limit << "), please revise: "; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NONE, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NONE, BAB_VERBOSITY); } if (userInput == "") { throw MAiNGOException(" Error while checking termination: Querying user input while checking termination criteria."); } else if (atof(userInput.c_str()) > std::numeric_limits<int>::max()) { - _logger->print_message(" Value is too high for integer type, set to maximum.\n", _maingoSettings->BAB_verbosity, VERB_NONE, _maingoSettings->loggingDestination); + _logger->print_message(" Value is too high for integer type, set to maximum.\n", VERB_NONE, BAB_VERBOSITY); *limit = std::numeric_limits<int>::max(); } else { @@ -1206,12 +1544,12 @@ BranchAndBound::_check_termination() outstr.clear(); outstr << " User input: " << (*limit) << "\n Okay, Resuming solution..." << std::endl << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NONE, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NONE, BAB_VERBOSITY); _linesprinted = 0; // To make sure header line is printed before continuing to print progress terminate = _NOT_TERMINATED; } else if (userInput == "n") { - _logger->print_message(" User input: no\n ", _maingoSettings->BAB_verbosity, VERB_NONE, _maingoSettings->loggingDestination); + _logger->print_message(" User input: no\n ", VERB_NONE, BAB_VERBOSITY); terminate = _TERMINATED; _print_termination(message); _status = potentialStatus; @@ -1241,5 +1579,5 @@ BranchAndBound::_print_termination(std::string message) << "************************************************************************************************************************" << std::endl; } - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); -} \ No newline at end of file + _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY); +} diff --git a/src/babMpi.cpp b/src/babMpi.cpp index 757f8e9..28f7df5 100644 --- a/src/babMpi.cpp +++ b/src/babMpi.cpp @@ -16,6 +16,7 @@ #include "getTime.h" #include "lbp.h" #include "mpiUtilities.h" +#include "ubp.h" #include <limits> @@ -66,7 +67,7 @@ BranchAndBound::_recv_solved_problem(babBase::BabNode &node, double &lbd, std::v // In case the node converged or was infeasible and therefore not sent back, create a dummy node if (status != TAG_SOLVED_NODE_STATUS_NORMAL) { unsigned nodeID = statistics[2]; - node = babBase::BabNode(node.get_pruning_score(), node.get_lower_bounds(), node.get_upper_bounds(), nodeID, node.get_depth(), node.holds_incumbent()); + node = babBase::BabNode(node.get_pruning_score(), node.get_lower_bounds(), node.get_upper_bounds(), node.get_index_dataset(), nodeID, node.get_depth(), node.get_augment_data()); } } @@ -109,6 +110,39 @@ BranchAndBound::_send_new_problem(const babBase::BabNode &node, const int dest) } +#ifdef HAVE_GROWING_DATASETS +//////////////////////////////////////////////////////////////////////////////////////// +// function for sending a new dataset to worker dest +void +BranchAndBound::_send_new_dataset(const std::set<unsigned int> &newDataset, const int dest) +{ + // Check if worker knows current dataset vector + bool workerKnowsDataset = _informedWorkerAboutDataset[dest - 1]; + + COMMUNICATION_TAG answerTag; + if (!workerKnowsDataset) { + answerTag = TAG_NEW_NODE_NEW_DATASET; + _informedWorkerAboutDataset[dest - 1] = true; + } + else { + answerTag = TAG_NEW_NODE_NO_DATASET; + } + // Send Request Answer + MPI_Ssend(NULL, 0, MPI_INT, dest, answerTag, MPI_COMM_WORLD); + + // Only send new dataset if worker does not know it yet + if (!workerKnowsDataset) { + // Push set into vector to send dataset at once + unsigned int ndataNew = newDataset.size(); + MPI_Ssend(&ndataNew, 1, MPI_UNSIGNED, dest, TAG_NODE_DATAPOINT, MPI_COMM_WORLD); + + std::vector<unsigned int> datasetBuf(newDataset.begin(), newDataset.end()); + MPI_Ssend(datasetBuf.data(), ndataNew, MPI_UNSIGNED, dest, TAG_NODE_DATAPOINT, MPI_COMM_WORLD); + } +} +#endif // HAVE_GROWING_DATASETS + + //////////////////////////////////////////////////////////////////////////////////////// // auxillary function for informing workers about occuring events void @@ -212,6 +246,37 @@ BranchAndBound::_recv_new_problem(babBase::BabNode &node) } +#ifdef HAVE_GROWING_DATASETS +//////////////////////////////////////////////////////////////////////////////////////// +// function for recieving a new dataset from the master +void +BranchAndBound::_recv_new_dataset() +{ + MPI_Status status; + MPI_Recv(NULL, 0, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status); + + if (status.MPI_TAG == TAG_NEW_NODE_NEW_DATASET) { + // Receive dataset + unsigned int ndataNew; + MPI_Recv(&ndataNew, 1, MPI_UNSIGNED, 0, TAG_NODE_DATAPOINT, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + std::vector<unsigned int> datasetBuf; + datasetBuf.resize(ndataNew); + MPI_Recv(datasetBuf.data(), datasetBuf.size(), MPI_UNSIGNED, 0, TAG_NODE_DATAPOINT, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + // Add dataset and corresponding subgraph to solvers + std::set<unsigned int> newDataset; + for (auto idx : datasetBuf) { + newDataset.insert(newDataset.end(), idx); + } + + _LBS->update_datasets_and_storedSubgraphs(newDataset); + _UBS->update_datasets_and_storedSubgraphs(newDataset); + } +} +#endif // HAVE_GROWING_DATASETS + + //////////////////////////////////////////////////////////////////////////////////////// // function for syncing with master void @@ -242,7 +307,7 @@ BranchAndBound::_sync_with_master(MPI_Request &req, bool &terminate) MPI_Request_get_status(req, &flag, &status); if (_bcastTag == BCAST_EXCEPTION) { // Other worker ran into an exception - _logger->print_message(" Received exception flag from master.\n", _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Received exception flag from master.\n", VERB_NORMAL, BAB_VERBOSITY); // Cancel pending node request MPI_Cancel(&req); MPI_Request_free(&req); @@ -290,4 +355,4 @@ BranchAndBound::_communicate_exception_and_throw(const maingo::MAiNGOMpiExceptio throw e; } -#endif \ No newline at end of file +#endif diff --git a/src/cApi.cpp b/src/cApi.cpp new file mode 100644 index 0000000..d7ef661 --- /dev/null +++ b/src/cApi.cpp @@ -0,0 +1,118 @@ +/********************************************************************************** + * Copyright (c) 2021 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#include "cApi.h" +#include "MAiNGO.h" +#include "aleModel.h" + +#include "programParser.h" + +#include "symbol_table.hpp" + +#include <iostream> +#include <memory> + + +///////////////////////////////////////////// +// Allows calling MAiNGO from C +extern "C" int +solve_problem_from_ale_string_with_maingo(const char* aleString, double* objectiveValue, double* solutionPoint, unsigned solutionPointLength, + double* cpuSolutionTime, double* wallSolutionTime, double* upperBound, double* lowerBound, + const char* resultFileName, const char* logFileName, const char* settingsFileName, + const OptionPair* options, unsigned numberOptions) +{ + + // Define model and MAiNGO pointers + std::shared_ptr<maingo::AleModel> myModel; + std::shared_ptr<maingo::MAiNGO> myMAiNGO; + + ale::symbol_table symbols; + try { + //convert given string to ifstream + std::istringstream input(aleString); + std::ofstream output; + if (true) { + + + maingo::ProgramParser par(input, symbols); + maingo::Program prog; + par.parse(prog); + + if (par.fail()) { + throw std::invalid_argument(" Error: Encountered an error while parsing the problem file"); + } + myModel = std::make_shared<maingo::AleModel>(prog, symbols); // define a model object implemented in ale interface + myMAiNGO = std::make_shared<maingo::MAiNGO>(myModel); // define a MAiNGO object with the corresponding model + } + else { + throw std::invalid_argument(" Error: Could not convert given problem string"); + } + } + catch (std::exception& e) { + + std::cerr << std::endl + << " Encountered exception:" << std::endl + << e.what() << std::endl; + } + + catch (...) { + + std::cerr << std::endl + << " Encountered an unknown fatal error during initialization. Terminating." << std::endl; + } + // Set output file names + myMAiNGO->set_result_file_name(std::string(resultFileName)); + myMAiNGO->set_log_file_name(std::string(logFileName)); + // Read settings from file and set further options + myMAiNGO->read_settings(std::string(settingsFileName)); + for (unsigned i = 0; i < numberOptions; i++) { + myMAiNGO->set_option(options[i].optionName, options[i].optionValue); + } + + + // Solve the problem + maingo::RETCODE maingoStatus; + try { + maingoStatus = myMAiNGO->solve(); + } + catch (std::exception& e) { + + std::cerr << std::endl + << e.what() << std::endl; + + return (-1); + } + catch (...) { + + std::cerr << std::endl + << " Encountered an unknown fatal error during solution. Terminating." << std::endl; + return (-1); + } + + // Get solution info + if ((maingoStatus == maingo::GLOBALLY_OPTIMAL) || (maingoStatus == maingo::FEASIBLE_POINT)) { + *objectiveValue = myMAiNGO->get_objective_value(); + unsigned numberOfVariables = myMAiNGO->get_solution_point().size(); + if (solutionPointLength >= numberOfVariables) { + for (size_t i = 0; i < numberOfVariables; i++) { + solutionPoint[i] = myMAiNGO->get_solution_point()[i]; + } + } + } + + + *cpuSolutionTime = myMAiNGO->get_cpu_solution_time(); + *wallSolutionTime = myMAiNGO->get_wallclock_solution_time(); + *upperBound = myMAiNGO->get_final_abs_gap() + myMAiNGO->get_final_LBD(); + *lowerBound = myMAiNGO->get_final_LBD(); + + return maingoStatus; +} \ No newline at end of file diff --git a/src/lbp.cpp b/src/lbp.cpp index efbc2a9..9bc12f8 100644 --- a/src/lbp.cpp +++ b/src/lbp.cpp @@ -12,6 +12,7 @@ #include "lbp.h" #include "MAiNGOException.h" #include "lbpDagObj.h" +#include "pointIsWithinNodeBounds.h" #include "version.h" #include <fstream> @@ -371,8 +372,7 @@ LowerBoundingSolver::_set_number_of_linpoints(const unsigned int LBP_linPoints) //vMC::additionalLins.initialize(_DAGobj->chosenLinPoints.size(), _nvar, /*midIndex*/0, _DAGobj->subgraphNonlinear.l_op.size(), vMC::additionalLins.COMPOSITION_BEST_POINT); break; } - default: - { + default: { throw MAiNGOException(" Error initializing LowerBoundingSolver: Unknown linearization point for LBP."); } } // End of switch(LBP_linPoints) @@ -407,53 +407,47 @@ LowerBoundingSolver::solve_LBP(const babBase::BabNode ¤tNode, double &lowe _LPstatus = _get_LP_status(); if (_LPstatus == LP_INFEASIBLE) { - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - // The if-statement seems like doubling of work but in the most common case LBP_verbosity <= VERB_NORMAL we don't want to always allocate the string - _logger->print_message(" LBP status: Infeasible", _maingoSettings->LBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } - if (currentNode.holds_incumbent()) { - bool reallyInfeasible = _check_if_LP_really_infeasible(); + _logger->print_message(" LBP status: Infeasible", VERB_ALL, LBP_VERBOSITY); + +#ifdef MAiNGO_DEBUG_MODE + if (_contains_incumbent(currentNode)) { + const bool reallyInfeasible = _check_if_LP_really_infeasible(); if (reallyInfeasible) { #ifdef LP__WRITE_CHECK_FILES - std::string str = "solve_LBP_infeas_with_incumbent_in_node"; - _write_LP_to_file(str); + _write_LP_to_file("solve_LBP_infeas_with_incumbent_in_node"); #endif std::ostringstream outstr; outstr << " Warning: Node with id " << currentNode.get_ID() << " declared infeasible by LBP although it contains the incumbent. Proceeding with parent LBD..." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - - return SUBSOLVER_FEASIBLE; + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } else { - // If it was possible to somehow prove optimality, restore previous settings - _LPstatus = LP_OPTIMAL; + _logger->print_message(" Found node to not actually be infeasible. Problem seems to be difficult numerically. Proceeding with parent LBD...", VERB_ALL, LBP_VERBOSITY); } + return SUBSOLVER_FEASIBLE; } - else { +#endif + #ifdef LP__OPTIMALITY_CHECK - if (_maingoSettings->LBP_solver == lbp::LBP_SOLVER_CLP) { // For CLP, we need this additional check to avoid weird behavior for some problems - bool reallyInfeasible = _check_if_LP_really_infeasible(); - if (!reallyInfeasible) - return SUBSOLVER_FEASIBLE; - } - return _check_infeasibility(currentNode); + if (_maingoSettings->LBP_solver == lbp::LBP_SOLVER_CLP) { // For CLP, we need this additional check to avoid weird behavior for some problems + bool reallyInfeasible = _check_if_LP_really_infeasible(); + if (!reallyInfeasible) + _logger->print_message(" Found node to not actually be infeasible. Problem seems to be difficult numerically. Proceeding with parent LBD...", VERB_ALL, LBP_VERBOSITY); + return SUBSOLVER_FEASIBLE; + } + return _check_infeasibility(currentNode); #endif - return SUBSOLVER_INFEASIBLE; - } // end of if (currentNode.holds_incumbent()) - } // end of if(_LPstatus == LP_INFEASIBLE) + } else if (_LPstatus == LP_UNKNOWN) { #ifdef LP__WRITE_CHECK_FILES - std::string str = "solve_LBP_unknown_status_code"; - _write_LP_to_file(str); + _write_LP_to_file("solve_LBP_unknown_status_code"); #endif - _logger->print_message(" Warning: LP solver returned unknown status code. Using interval bounds instead.\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Warning: LP solver returned unknown status code. Using interval bounds instead.\n", VERB_NORMAL, LBP_VERBOSITY); return _fallback_to_intervals(lowerBound); } - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - // The if-statement seems like doubling of work but in the most common case LBP_verbosity <= VERB_NORMAL we don't want to always allocate the string - _logger->print_message(" LBP status: Optimal", _maingoSettings->LBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + + _logger->print_message(" LBP status: Optimal", VERB_ALL, LBP_VERBOSITY); + // Process solution: solution point (If we got here, the LP solver declared that an optimal solution was found) double etaVal = 0; @@ -463,21 +457,14 @@ LowerBoundingSolver::solve_LBP(const babBase::BabNode ¤tNode, double &lowe catch (std::exception &e) { std::ostringstream outstr; outstr << " Warning: Variables at solution of LBP could be not obtained by LP solver: " << e.what() << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); // Return empty solution instead solution.clear(); return SUBSOLVER_FEASIBLE; } // Ok, successfully obtained solution point - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - // Here we ask the verbosity by hand, since we don't want to alwys iterate over all variables - std::ostringstream outstr; - outstr << " LBP solution point: " << std::endl; - for (unsigned int i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + _logger->print_vector(_nvar, solution, " LBP solution point: ", VERB_ALL, LBP_VERBOSITY); + #ifdef LP__OPTIMALITY_CHECK // Feasibility check if (_check_feasibility(solution) == SUBSOLVER_INFEASIBLE) { @@ -491,7 +478,7 @@ LowerBoundingSolver::solve_LBP(const babBase::BabNode ¤tNode, double &lowe if (!(newLBD >= (-_maingoSettings->infinity))) { // Note that all comparisons return false if one operand is NAN std::ostringstream outstr; outstr << " Warning: Objective obtained from LP solver in LBP is out of bounds (" << newLBD << ") although the LP solver solution status is optimal. Keeping parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); return SUBSOLVER_FEASIBLE; } @@ -510,10 +497,8 @@ LowerBoundingSolver::solve_LBP(const babBase::BabNode ¤tNode, double &lowe // This is okay, not providing multipliers std::ostringstream outstr; outstr << " No multipliers obtained from LP solver: " << e.what() << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - // The if-statement seems like doubling of work but in the most common case LBP_verbosity <= VERB_NORMAL we don't want to always allocate the string - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + _logger->print_message(outstr.str(), VERB_ALL, LBP_VERBOSITY); + dualInfo.multipliers.clear(); return SUBSOLVER_FEASIBLE; } @@ -529,12 +514,11 @@ LowerBoundingSolver::solve_LBP(const babBase::BabNode ¤tNode, double &lowe lowerBound = std::max(newLBD, _DAGobj->validIntervalLowerBound); // In case the interval bound is better, use that one dualInfo.lpLowerBound = newLBD; // Here, we need the actual lower bound from the LP, since this is the one we need for DBBT - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - // The if-statement seems like doubling of work but in the most common case LBP_verbosity <= VERB_NORMAL we don't want to always allocate the string - std::ostringstream outstr; - outstr << " LBD: " << lowerBound << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + + std::ostringstream outstr; + outstr << " LBD: " << lowerBound << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, LBP_VERBOSITY); + return SUBSOLVER_FEASIBLE; } @@ -570,36 +554,35 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr bool foundInfeasible = false; if (linStatus == LINEARIZATION_INFEASIBLE) { - if (currentNode.holds_incumbent()) { - bool reallyInfeasible = _check_if_LP_really_infeasible(); + _logger->print_message(" OBBT linearization status: Infeasible", VERB_ALL, LBP_VERBOSITY); + +#ifdef MAiNGO_DEBUG_MODE + if (_contains_incumbent(currentNode)) { + const bool reallyInfeasible = _check_if_LP_really_infeasible(); if (reallyInfeasible) { #ifdef LP__WRITE_CHECK_FILES - std::string str = "solve_OBBT_infeas_at_linearization_with_incumbent_in_node"; - _write_LP_to_file(str); + _write_LP_to_file("solve_OBBT_infeas_at_linearization_with_incumbent_in_node"); #endif if (currentNode.get_ID() == 0) { return TIGHTENING_INFEASIBLE; // For the root node, we immediately want to report this false infeasibility claim since we want to completeley disable OBBT based on this information. } std::ostringstream outstr; outstr << " Warning: Node with id " << currentNode.get_ID() << " declared infeasible by linearization technique in OBBT although it contains the incumbent. Skipping OBBT..." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - return TIGHTENING_UNCHANGED; + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } else { - std::ostringstream outstr; - outstr << " Warning: Node with id " << currentNode.get_ID() << " is numerically sensitive in linearization technique in OBBT. Proceeding..." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - foundInfeasible = false; + _logger->print_message(" Found node to not actually be infeasible. Problem seems to be difficult numerically. Skipping OBBT...", VERB_ALL, LBP_VERBOSITY); } + return TIGHTENING_UNCHANGED; } - else { - foundInfeasible = true; -#ifdef LP__OPTIMALITY_CHECK - if (_check_infeasibility(currentNode) == SUBSOLVER_FEASIBLE) { - foundInfeasible = false; - } #endif + + foundInfeasible = true; +#ifdef LP__OPTIMALITY_CHECK + if (_check_infeasibility(currentNode) == SUBSOLVER_FEASIBLE) { + foundInfeasible = false; } +#endif } // Only do OBBT if the LP was not found to be infeasible during the linearization @@ -626,8 +609,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr _modify_LP_for_feasopt_OBBT(currentUBD, toTreatMax, toTreatMin); break; } - default: - { + default: { std::ostringstream errmsg; errmsg << " Unknown OBBT range reduction type: " << reductionType; throw MAiNGOException(errmsg.str(), currentNode); @@ -725,17 +707,15 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr _LPstatus = _get_LP_status(); if (_LPstatus == LP_INFEASIBLE) { - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - std::ostringstream outstr; - outstr << " LP Obbt status: Infeasible" << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } - if (currentNode.holds_incumbent()) { - bool reallyInfeasible = _check_if_LP_really_infeasible(); + _logger->print_message(" OBBT tightening LP status: Infeasible", VERB_ALL, LBP_VERBOSITY); + +#ifdef MAiNGO_DEBUG_MODE + if (_contains_incumbent(currentNode)) { + + const bool reallyInfeasible = _check_if_LP_really_infeasible(); if (reallyInfeasible) { #ifdef LP__WRITE_CHECK_FILES - std::string str = "solve_OBBT_infeas_with_incumbent_in_node"; - _write_LP_to_file(str); + _write_LP_to_file("solve_OBBT_infeas_with_incumbent_in_node"); #endif if (currentNode.get_ID() == 0) { _restore_LP_coefficients_after_OBBT(); @@ -743,17 +723,18 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr } std::ostringstream outstr; outstr << " Warning: Node with id " << currentNode.get_ID() << " declared infeasible by OBBT although it contains the incumbent. Skipping OBBT..." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); break; } else { std::ostringstream outstr; outstr << " Warning: Node with id " << currentNode.get_ID() << " is numerically sensitive in OBBT for bound " << iVar << " with sense " << optimizationSense << ". Skipping this bound..." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); _set_optimization_sense_of_variable(iVar, 0); continue; } - } // end of if (currentNode.holds_incumbent()) + } +#endif foundInfeasible = true; #ifdef LP__OPTIMALITY_CHECK @@ -762,17 +743,14 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr break; } #endif - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - std::ostringstream outstr; - outstr << " OBBT status: " << _LPstatus << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + _logger->print_message(" OBBT status: " + std::to_string(_LPstatus), VERB_ALL, LBP_VERBOSITY); + break; } // end of if(_LPstatus == LP_INFEASIBLE) else if (_LPstatus != LP_OPTIMAL) { std::ostringstream outstr; outstr << " Warning: No optimal solution found in OBBT. Status: " << _LPstatus << ". Skipping OBBT..." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); break; } else { @@ -786,7 +764,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr catch (std::exception &e) { std::ostringstream outstr; outstr << " Warning: Variables at solution of OBBT could be not obtained by LP solver: " << e.what() << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); _set_optimization_sense_of_variable(iVar, 0); continue; } @@ -805,26 +783,27 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr if (!(objectiveValue >= (-_maingoSettings->infinity))) { // Note that all comparisons return false if one operand is NaN std::ostringstream outstr; outstr << " Warning: Objective obtained from LP solver in OBBT is out of bounds (" << objectiveValue << ") although LP solution status is optimal. Skipping this bound." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); _set_optimization_sense_of_variable(iVar, 0); continue; } double newBound = optimizationSense * objectiveValue; // Again depending on whether we want to change upper or lower bound, need to account for sign - if (currentNode.holds_incumbent()) { + +#ifdef MAiNGO_DEBUG_MODE + if (_contains_incumbent(currentNode)) { if (optimizationSense > 0) { // Lower bound if (iVar < _incumbent.size()) { if (_incumbent[iVar] < newBound) { // We only need to tell the user something if we are not within computational tolerances, meaning that something really went wrong if (!mc::isequal(_incumbent[iVar], newBound, _computationTol, _computationTol)) { #ifdef LP__WRITE_CHECK_FILES - std::string str = "solve_OBBT_bound_infeas_with_incumbent_in_node"; - _write_LP_to_file(str); + _write_LP_to_file("solve_OBBT_bound_infeas_with_incumbent_in_node"); #endif std::ostringstream outstr; outstr << " Warning: Node #" << currentNode.get_ID() << " contains the incumbent and OBBT computed a lower bound for variable " << iVar << " which cuts off the incumbent. " << std::endl << " Correcting this bound and skipping OBBT... " << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // We skip the bound, even if the bound is within computational tolerances break; @@ -839,7 +818,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr std::ostringstream outstr; outstr << " Warning: Node #" << currentNode.get_ID() << " contains the incumbent and OBBT computed an upper bound for variable " << iVar << " which cuts off the incumbent. " << std::endl << " Correcting this bound and skipping OBBT... " << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // We skip the bound, even if the bound is within computational tolerances break; @@ -847,6 +826,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr } } } +#endif double remainingWidth = optimizationSense * (*otherBoundVector)[iVar] - optimizationSense * newBound; if (remainingWidth < -_computationTol) { @@ -867,7 +847,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr else { outstr << std::setprecision(16) << " Upper Bound = " << newBound << " < " << std::setprecision(16) << (*otherBoundVector)[iVar] << " = Lower Bound. Skipping this bound." << std::endl; } - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } _set_optimization_sense_of_variable(iVar, 0); continue; @@ -974,7 +954,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr return TIGHTENING_UNCHANGED; } else { - currentNode = babBase::BabNode(currentNode.get_pruning_score(), lowerVarBounds, upperVarBounds, currentNode.get_ID(), currentNode.get_depth(), currentNode.holds_incumbent()); + currentNode = babBase::BabNode(currentNode.get_pruning_score(), lowerVarBounds, upperVarBounds, currentNode.get_index_dataset(), currentNode.get_ID(), currentNode.get_depth(), currentNode.get_augment_data()); return TIGHTENING_CHANGED; } } @@ -1022,7 +1002,7 @@ LowerBoundingSolver::do_constraint_propagation(babBase::BabNode ¤tNode, co // 3 is the number of maximum rounds and 0.01 stands for a minimum improvement of 1% unsigned int maxpass = pass; int flag = _DAGobj->DAG.reval(_DAGobj->subgraph, _DAGobj->intervalArray, _DAGobj->functions.size(), _DAGobj->functions.data(), - _DAGobj->constraintIntervals.data(), _nvar, _DAGobj->vars.data(), _DAGobj->currentIntervals.data(), maxpass, 0.001); + _DAGobj->constraintIntervals.data(), _nvar, _DAGobj->vars.data(), _DAGobj->currentIntervals.data(), maxpass, 0.001); // The node has been found to be infeasible std::vector<double> lowerVarBounds(currentNode.get_lower_bounds()); @@ -1033,25 +1013,27 @@ LowerBoundingSolver::do_constraint_propagation(babBase::BabNode ¤tNode, co if (mc::Op<I>::l(_DAGobj->currentIntervals[i]) > mc::Op<I>::u(_DAGobj->currentIntervals[i])) { std::ostringstream outstr; outstr << " Warning: Something went wrong in constraint propagation. Skipping constraint propagation..."; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); break; } double newLowerBound = mc::Op<I>::l(_DAGobj->currentIntervals[i]); double newUpperBound = mc::Op<I>::u(_DAGobj->currentIntervals[i]); - // Check if we not mistakenly drop the incumbent - if (currentNode.holds_incumbent()) { + + +#ifdef MAiNGO_DEBUG_MODE + if (_contains_incumbent(currentNode)) { if (i < _incumbent.size()) { if (_incumbent[i] < newLowerBound && !mc::isequal(_incumbent[i], newLowerBound, _computationTol, _computationTol) || _incumbent[i] > newUpperBound && !mc::isequal(_incumbent[i], newUpperBound, _computationTol, _computationTol)) { - // std::cout << "incumbent[" << i << "]: " << _incumbent[i] << " lb: " << newLowerBound << " ub: " << newUpperBound - // << " node lb: " << currentNode.get_lower_bounds()[i] << " currentUBD: " << currentUBD << std::endl; std::ostringstream outstr; outstr << " Warning: Node #" << currentNode.get_ID() << " contains the incumbent and constraint propagation computed a bound for variable " << i << " which cuts off the incumbent. " << std::endl; outstr << " Correcting this bound and skipping constraint propagation... " << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); break; } } } +#endif + switch (_originalVariables[i].get_variable_type()) { case babBase::enums::VT_CONTINUOUS: // Only update bounds if difference is larger than tolerance @@ -1122,17 +1104,21 @@ LowerBoundingSolver::do_constraint_propagation(babBase::BabNode ¤tNode, co if (flag < -(int)maxpass) { return TIGHTENING_UNCHANGED; // Means that an error occured and we cannot make a statement } - // Check if we not mistakenely discard the incumbent - if (currentNode.holds_incumbent()) { + + +#ifdef MAiNGO_DEBUG_MODE + if (_contains_incumbent(currentNode)) { if (currentNode.get_ID() == 0) { return TIGHTENING_INFEASIBLE; } std::ostringstream outstr; outstr << " Warning: Constraint propagation declared node #" << currentNode.get_ID() << " as infeasible although it holds the incumbent. Skipping constraint propagation..." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); return TIGHTENING_UNCHANGED; } +#endif + return TIGHTENING_INFEASIBLE; } // Otherwise we get 0 meaning that constraint propagation did not provide any improvement @@ -1141,7 +1127,7 @@ LowerBoundingSolver::do_constraint_propagation(babBase::BabNode ¤tNode, co return TIGHTENING_UNCHANGED; } else { - currentNode = babBase::BabNode(currentNode.get_pruning_score(), lowerVarBounds, upperVarBounds, currentNode.get_ID(), currentNode.get_depth(), currentNode.holds_incumbent()); + currentNode = babBase::BabNode(currentNode.get_pruning_score(), lowerVarBounds, upperVarBounds, currentNode.get_index_dataset(), currentNode.get_ID(), currentNode.get_depth(), currentNode.get_augment_data()); return TIGHTENING_CHANGED; } } @@ -1225,7 +1211,7 @@ LowerBoundingSolver::do_dbbt_and_probing(babBase::BabNode ¤tNode, const st // Lower bound: LbpDualInfo probingDualInfo; // Declare a tmp node to work on - babBase::BabNode tmpNode(currentNode.get_pruning_score(), newLowerBounds, newUpperBounds, currentNode.get_ID(), currentNode.get_depth(), currentNode.holds_incumbent()); + babBase::BabNode tmpNode(currentNode.get_pruning_score(), newLowerBounds, newUpperBounds, currentNode.get_index_dataset(), currentNode.get_ID(), currentNode.get_depth(), currentNode.get_augment_data()); SUBSOLVER_RETCODE probingStatus = _solve_probing_LBP(tmpNode, probingDualInfo, iVar, true); if ((probingStatus == SUBSOLVER_FEASIBLE) && (probingDualInfo.multipliers.size() == _nvar)) { if (probingDualInfo.multipliers[iVar] < 0) { @@ -1261,14 +1247,14 @@ LowerBoundingSolver::do_dbbt_and_probing(babBase::BabNode ¤tNode, const st } } // Update the bound of the tmp node - tmpNode.set_lower_bound(iVar, newUpperBounds[iVar]); + tmpNode.set_upper_bound(iVar, newUpperBounds[iVar]); } } } } // end of for loop over variables if (changedBounds) { - currentNode = babBase::BabNode(currentNode.get_pruning_score(), newLowerBounds, newUpperBounds, currentNode.get_ID(), currentNode.get_depth(), currentNode.holds_incumbent()); + currentNode = babBase::BabNode(currentNode.get_pruning_score(), newLowerBounds, newUpperBounds, currentNode.get_index_dataset(), currentNode.get_ID(), currentNode.get_depth(), currentNode.get_augment_data()); return TIGHTENING_CHANGED; } else { @@ -1322,24 +1308,20 @@ LowerBoundingSolver::_solve_probing_LBP(babBase::BabNode ¤tNode, LbpDualIn // If LP solver says the problem is infeasible, it's ok, since probing is just a heuristic if (_LPstatus == LP_INFEASIBLE) { - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - // The if-statement seems like doubling of work but in the most common case LBP_verbosity <= VERB_NORMAL we don't want to always allocate the string - _logger->print_message(" Probing LBP status: Infeasible", _maingoSettings->LBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + _logger->print_message(" Probing LBP status: Infeasible", VERB_ALL, LBP_VERBOSITY); + return SUBSOLVER_INFEASIBLE; } // end of if(_LPstatus == LP_INFEASIBLE) else if (_LPstatus == LP_UNKNOWN) { - _logger->print_message(" Warning: LP solver returned unknown status code. Proceeding with parent LBD.\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Warning: LP solver returned unknown status code. Proceeding with parent LBD.\n", VERB_NORMAL, LBP_VERBOSITY); #ifdef LP__WRITE_CHECK_FILES - std::string str = "solve_probing_LBP_unknown_status_code"; - _write_LP_to_file(str); + _write_LP_to_file("solve_probing_LBP_unknown_status_code"); #endif return SUBSOLVER_INFEASIBLE; } - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - // The if-statement seems like doubling of work but in the most common case LBP_verbosity <= VERB_NORMAL we don't want to always allocate the string - _logger->print_message(" Probing LBP status: Optimal", _maingoSettings->LBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + + _logger->print_message(" Probing LBP status: Optimal", VERB_ALL, LBP_VERBOSITY); + // Process solution: solution point (If we got here, the LP solver declared that an optimal solution was found) double etaVal = 0; @@ -1350,21 +1332,14 @@ LowerBoundingSolver::_solve_probing_LBP(babBase::BabNode ¤tNode, LbpDualIn catch (std::exception &e) { std::ostringstream outstr; outstr << " Warning: Variables at solution of Probing LBP could be not obtained by LP solver: " << e.what() << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); // Return empty solution instead solution.clear(); return SUBSOLVER_INFEASIBLE; } // Ok, successfully obtained solution point - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - // Here we ask the verbosity by hand, since we don't want to alwys iterate over all variables - std::ostringstream outstr; - outstr << " Probing LBP solution point: " << std::endl; - for (unsigned int i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + _logger->print_vector(_nvar, solution, " Probing LBP solution point: ", VERB_ALL, LBP_VERBOSITY); + #ifdef LP__OPTIMALITY_CHECK // Feasibility check if (_check_feasibility(solution) == SUBSOLVER_INFEASIBLE) { @@ -1379,7 +1354,7 @@ LowerBoundingSolver::_solve_probing_LBP(babBase::BabNode ¤tNode, LbpDualIn if (!(newLBD >= (-_maingoSettings->infinity))) { // Note that all comparisons return false if one operand is NAN std::ostringstream outstr; outstr << " Warning: Objective obtained from LP solver in Probing LBP is out of bounds (" << newLBD << ") although the LP solver solution status is optimal. Keeping parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); return SUBSOLVER_INFEASIBLE; } @@ -1398,10 +1373,8 @@ LowerBoundingSolver::_solve_probing_LBP(babBase::BabNode ¤tNode, LbpDualIn // This is okay, not providing multipliers std::ostringstream outstr; outstr << " No multipliers obtained from LP solver for probing: " << e.what() << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - // The if-statement seems like doubling of work but in the most common case LBP_verbosity <= VERB_NORMAL we don't want to always allocate the string - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + _logger->print_message(outstr.str(), VERB_ALL, LBP_VERBOSITY); + dualInfo.multipliers.clear(); return SUBSOLVER_INFEASIBLE; } @@ -1434,12 +1407,10 @@ LowerBoundingSolver::_solve_probing_LBP(babBase::BabNode ¤tNode, LbpDualIn #endif dualInfo.lpLowerBound = newLBD; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - // The if-statement seems like doubling of work but in the most common case LBP_verbosity <= VERB_NORMAL we don't want to always allocate the string - std::ostringstream outstr; - outstr << " Probing LBD: " << dualInfo.lpLowerBound << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + std::ostringstream outstr; + outstr << " Probing LBD: " << dualInfo.lpLowerBound << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, LBP_VERBOSITY); + return SUBSOLVER_FEASIBLE; } @@ -1470,7 +1441,7 @@ LowerBoundingSolver::_update_LP(const babBase::BabNode ¤tNode) case LINP_INCUMBENT: // 1. Incumbent values if in node, else mid { MC::subHeur.clear(); - return _linearize_model_at_incumbent(lowerVarBounds, upperVarBounds, currentNode.holds_incumbent()); + return _linearize_model_at_incumbent_or_at_midpoint(lowerVarBounds, upperVarBounds); break; } case LINP_KELLEY: // 2. Linearization points are computed via an adapted version of Kelley's algorithm @@ -1498,8 +1469,7 @@ LowerBoundingSolver::_update_LP(const babBase::BabNode ¤tNode) return _linearization_points_Kelley_Simplex(currentNode); break; } - default: - { + default: { throw MAiNGOException(" Error while updating LP: Unknown linearization strategy."); break; } @@ -1562,7 +1532,7 @@ LowerBoundingSolver::_update_LP_obj(const MC &resultRelaxation, const std::vecto if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_obj in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -1578,7 +1548,7 @@ LowerBoundingSolver::_update_LP_ineq(const MC &resultRelaxation, const std::vect if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_ineq in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -1594,7 +1564,7 @@ LowerBoundingSolver::_update_LP_eq(const MC &resultRelaxationCv, const MC &resul if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_eq in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -1610,7 +1580,7 @@ LowerBoundingSolver::_update_LP_ineqRelaxationOnly(const MC &resultRelaxation, c if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_ineqRelaxationOnly in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -1626,7 +1596,7 @@ LowerBoundingSolver::_update_LP_eqRelaxationOnly(const MC &resultRelaxationCv, c if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_eqRelaxationOnly in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -1642,7 +1612,7 @@ LowerBoundingSolver::_update_LP_ineq_squash(const MC &resultRelaxation, const st if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_ineq_squash in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -1709,7 +1679,7 @@ LowerBoundingSolver::_update_LP_obj(const vMC &resultRelaxationVMC, const std::v if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_obj for vector McCormick in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -1725,7 +1695,7 @@ LowerBoundingSolver::_update_LP_ineq(const vMC &resultRelaxationVMC, const std:: if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_ineq for vector McCormick in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -1741,7 +1711,7 @@ LowerBoundingSolver::_update_LP_eq(const vMC &resultRelaxationCvVMC, const vMC & if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_eq for vector McCormick in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -1757,7 +1727,7 @@ LowerBoundingSolver::_update_LP_ineqRelaxationOnly(const vMC &resultRelaxationVM if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_ineqRelaxationOnly for vector McCormick in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -1773,7 +1743,7 @@ LowerBoundingSolver::_update_LP_eqRelaxationOnly(const vMC &resultRelaxationCvVM if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_eqRelaxationOnly for vectpr McCormick in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -1789,7 +1759,7 @@ LowerBoundingSolver::_update_LP_ineq_squash(const vMC &resultRelaxationVMC, cons if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { std::ostringstream outstr; outstr << " You need to define function _update_LP_ineq_squash for vector McCormick in the derived lower bounding solver " << _maingoSettings->LBP_solver << " !"; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); } // Not needed in the default solver @@ -2050,7 +2020,7 @@ LowerBoundingSolver::preprocessor_check_options(const babBase::BabNode &rootNode } if (_maingoSettings->LBP_addAuxiliaryVars && !_maingoSettings->BAB_constraintPropagation) { - _logger->print_message(" The option BAB_constraintPropagation has to be 1 when using option LBP_addAuxiliaryVars. Setting it to 1.\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" The option BAB_constraintPropagation has to be 1 when using option LBP_addAuxiliaryVars. Setting it to 1.\n", VERB_NORMAL, LBP_VERBOSITY); _maingoSettings->BAB_constraintPropagation = true; } @@ -2072,6 +2042,58 @@ LowerBoundingSolver::preprocessor_check_options(const babBase::BabNode &rootNode } +#ifdef HAVE_GROWING_DATASETS +///////////////////////////////////////////////////////////////////////// +// passes index of new dataset to DagObj routine +void +LowerBoundingSolver::change_growing_objective(const unsigned int indexDataset) +{ + _DAGobj->change_growing_objective(indexDataset); +} + + +///////////////////////////////////////////////////////////////////////// +// passes dataset and position of first data point to DagObj routine +void +LowerBoundingSolver::pass_data_position_to_solver(const std::shared_ptr<std::vector<std::set<unsigned int>>> datasetsIn, const unsigned int indexFirstDataIn) +{ + _DAGobj->datasets = datasetsIn; + _DAGobj->indexFirstData = indexFirstDataIn; +} + + +#ifdef HAVE_MAiNGO_MPI +///////////////////////////////////////////////////////////////////////////////////////////// +// update dataset vector and add respective subgraph after adding new dataset +// needed to ensure correct order of subgraphs in storedSubgraphs in parallel version +void +LowerBoundingSolver::update_datasets_and_storedSubgraphs(std::set<unsigned int> &newDataset) +{ + size_t ndataNew = newDataset.size(); + + bool datasetExists = false; + for (int i = (*_DAGobj->datasets).size() - 1; i > -1; i--) { + if ((*_DAGobj->datasets)[i].size() == ndataNew) { + datasetExists = true; + break; + } + } + if (!datasetExists) { + (*_DAGobj->datasets).push_back(newDataset); + } + + if (_DAGobj->storedSubgraph.size() != (*_DAGobj->datasets).size()) { + // If we do not have a subgraph for each dataset + + for (auto idx = _DAGobj->storedSubgraph.size(); idx < (*_DAGobj->datasets).size(); idx++) { + _DAGobj->add_subgraph_for_new_dataset(idx); + } + } +} +#endif //HAVE_MAiNGO_MPI +#endif //HAVE_GROWING_DATASETS + + ///////////////////////////////////////////////////////////////////////////////////////////// // check need for options in preprocessing void @@ -2079,19 +2101,19 @@ LowerBoundingSolver::_turn_off_specific_options() { if (_maingoSettings->LBP_solver != LBP_SOLVER_MAiNGO) { - _logger->print_message(" Warning: Function for turning off specific options not implemented. Not changing any settings. Proceeding...\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Warning: Function for turning off specific options not implemented. Not changing any settings. Proceeding...\n", VERB_NORMAL, LBP_VERBOSITY); } else { if (_maingoSettings->LBP_linPoints != LINP_MID) { - _logger->print_message(" The option LBP_linPoints has to be 0 when using the default MAiNGO solver (LBP_solver = 0). Setting it to 0.\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" The option LBP_linPoints has to be 0 when using the default MAiNGO solver (LBP_solver = 0). Setting it to 0.\n", VERB_NORMAL, LBP_VERBOSITY); _maingoSettings->LBP_linPoints = LINP_MID; // Note that this already has been used in the constructor! } if (_maingoSettings->PRE_obbtMaxRounds > 0) { - _logger->print_message(" The option PRE_obbtMaxRounds has to be 0 when using the default MAiNGO solver (LBP_solver = 0). Setting it to 0.\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" The option PRE_obbtMaxRounds has to be 0 when using the default MAiNGO solver (LBP_solver = 0). Setting it to 0.\n", VERB_NORMAL, LBP_VERBOSITY); _maingoSettings->PRE_obbtMaxRounds = 0; } if (_maingoSettings->BAB_alwaysSolveObbt) { - _logger->print_message(" The option BAB_alwaysSolveObbt has to be 0 when using the default MAiNGO solver (LBP_solver = 0). Setting it to 0.\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" The option BAB_alwaysSolveObbt has to be 0 when using the default MAiNGO solver (LBP_solver = 0). Setting it to 0.\n", VERB_NORMAL, LBP_VERBOSITY); _maingoSettings->BAB_alwaysSolveObbt = false; } } @@ -2327,7 +2349,7 @@ LowerBoundingSolver::_print_LP(const std::vector<double> &lowerVarBounds, const outstr << " x(" << i << "): " << lowerVarBounds[i] << " : " << upperVarBounds[i] << std::endl; } - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NONE, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NONE, LBP_VERBOSITY); } #endif @@ -2335,7 +2357,7 @@ LowerBoundingSolver::_print_LP(const std::vector<double> &lowerVarBounds, const ///////////////////////////////////////////////////////////////////////////////////////////// // write current LP to file void -LowerBoundingSolver::_write_LP_to_file(std::string &fileName) +LowerBoundingSolver::_write_LP_to_file(const std::string &fileName) { std::string str; @@ -2395,4 +2417,26 @@ LowerBoundingSolver::_write_LP_to_file(std::string &fileName) lpFile << "End\n"; lpFile.close(); } -#endif \ No newline at end of file +#endif + + +///////////////////////////////////////////////////////////////////////////////////////////// +// check if a branch-and-bound node contains the incumbent (if any) +bool +LowerBoundingSolver::_contains_incumbent(const babBase::BabNode &node) +{ + _logger->print_message(" Checking if node contains incumbent.", VERB_ALL, LBP_VERBOSITY); + + if (_incumbent.empty()) { + _logger->print_message(" No incumbent available.", VERB_ALL, LBP_VERBOSITY); + return false; + } + else if (point_is_within_node_bounds(_incumbent, node)) { + _logger->print_message(" Node contains incumbent.", VERB_ALL, LBP_VERBOSITY); + return true; + } + else { + _logger->print_message(" Node does not contain incumbent.", VERB_ALL, LBP_VERBOSITY); + return false; + } +} diff --git a/src/lbpClp.cpp b/src/lbpClp.cpp index 1c5502e..7b267f5 100644 --- a/src/lbpClp.cpp +++ b/src/lbpClp.cpp @@ -1117,7 +1117,7 @@ LbpClp::_check_infeasibility(const babBase::BabNode ¤tNode) farkasVals = _clp.infeasibilityRay(); if (!farkasVals) { - _logger->print_message(" Warning: Could not retrieve Farkas' values from CLP. Continuing with parent LBD...\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Warning: Could not retrieve Farkas' values from CLP. Continuing with parent LBD...\n", VERB_NORMAL, LBP_VERBOSITY); return SUBSOLVER_FEASIBLE; } @@ -1282,13 +1282,12 @@ LbpClp::_check_infeasibility(const babBase::BabNode ¤tNode) if (res1 - res2 <= 0. && !mc::isequal(res1, res2, _computationTol * 1e1, _computationTol * 1e1)) { #ifdef LP__WRITE_CHECK_FILES - std::string str = "clp_infeas_check"; - _write_LP_to_file(str); + _write_LP_to_file("clp_infeas_check"); #endif std::ostringstream outstr; outstr << " Warning: CLP provided an invalid Farkas' certificate. Continuing with parent LBD..." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); return SUBSOLVER_FEASIBLE; } return SUBSOLVER_INFEASIBLE; @@ -1299,6 +1298,26 @@ LbpClp::_check_infeasibility(const babBase::BabNode ¤tNode) } +void +LbpClp::_print_check_feasibility(const std::shared_ptr<Logger> logger, const VERB verbosity, const std::vector<double> &solution, const std::vector<std::vector<double>> rhs, const std::string name, const double value, const unsigned i, unsigned k, const unsigned nvar) +{ + std::ostringstream outstr; + outstr << " Warning: CLP returned FEASIBLE although the point is an infeasible one!" << std::endl; + + if (verbosity > VERB_NORMAL) { + outstr << std::setprecision(16) << " value: " << value << " _" << name << "[" << i << "][" << k << "]: " << rhs[i][k] << std::endl; + outstr << " LBP solution point: " << std::endl; + for (unsigned i = 0; i < nvar; i++) { + outstr << " x(" << i << "): " << solution[i] << std::endl; + } + } + + + outstr << " Continuing with parent LBD." << std::endl; + logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); +} + + ///////////////////////////////////////////////////////////////////////////////////////////// // feasibility check SUBSOLVER_RETCODE @@ -1313,19 +1332,7 @@ LbpClp::_check_feasibility(const std::vector<double> &solution) value += _matrixIneq[i][k][j] * solution[j]; } if (value - _rhsIneq[i][k] > _maingoSettings->deltaIneq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CLP returned FEASIBLE although the point is an infeasible one!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << std::setprecision(16) << " value: " << value << " _rhsIneq[" << i << "][" << k << "]: " << _rhsIneq[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsIneq, "rhsIneq", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1338,19 +1345,7 @@ LbpClp::_check_feasibility(const std::vector<double> &solution) value += _matrixEq1[i][k][j] * solution[j]; } if (value - _rhsEq1[i][k] > _maingoSettings->deltaEq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CLP returned FEASIBLE although the point is an infeasible one!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << std::setprecision(16) << " value: " << value << " _rhsEq1[" << i << "][" << k << "]: " << _rhsEq1[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsEq1, "rhsEq1", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1358,19 +1353,7 @@ LbpClp::_check_feasibility(const std::vector<double> &solution) value += _matrixEq2[i][k][j] * solution[j]; } if (value - _rhsEq2[i][k] > _maingoSettings->deltaEq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CLP returned FEASIBLE although the point is an infeasible one!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << std::setprecision(16) << " value: " << value << " _rhsEq2[" << i << "][" << k << "]: " << _rhsEq2[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsEq2, "rhsEq2", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1383,19 +1366,7 @@ LbpClp::_check_feasibility(const std::vector<double> &solution) value += _matrixIneqRelaxationOnly[i][k][j] * solution[j]; } if (value - _rhsIneqRelaxationOnly[i][k] > _maingoSettings->deltaIneq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CLP returned FEASIBLE although the point is an infeasible one!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << " value: " << value << " _rhsIneqRelaxationOnly[" << i << "][" << k << "]: " << _rhsIneqRelaxationOnly[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsIneqRelaxationOnly, "rhsIneqRelaxationOnly", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1408,19 +1379,7 @@ LbpClp::_check_feasibility(const std::vector<double> &solution) value += _matrixEqRelaxationOnly1[i][k][j] * solution[j]; } if (value - _rhsEqRelaxationOnly1[i][k] > _maingoSettings->deltaEq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CLP returned FEASIBLE although the point is an infeasible one!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << " value: " << value << " _rhsEqRelaxationOnly1[" << i << "][" << k << "]: " << _rhsEqRelaxationOnly1[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsEqRelaxationOnly1, "rhsEqRelaxationOnly1", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1428,19 +1387,7 @@ LbpClp::_check_feasibility(const std::vector<double> &solution) value += _matrixEqRelaxationOnly2[i][k][j] * solution[j]; } if (value - _rhsEqRelaxationOnly2[i][k] > _maingoSettings->deltaEq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CLP returned FEASIBLE although the point is an infeasible one!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << " value: " << value << " _rhsEqRelaxationOnly2[" << i << "][" << k << "]: " << _rhsEqRelaxationOnly2[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsEqRelaxationOnly2, "rhsEqRelaxationOnly2", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1453,19 +1400,7 @@ LbpClp::_check_feasibility(const std::vector<double> &solution) value += _matrixIneqSquash[i][k][j] * solution[j]; } if (value - _rhsIneqSquash[i][k] > 1e-9) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CLP returned FEASIBLE although the point is an infeasible one!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << std::setprecision(16) << " value: " << value << " _rhsIneqSquash[" << i << "][" << k << "]: " << _rhsIneqSquash[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsIneqSquash, "rhsIneqSquash", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1475,6 +1410,7 @@ LbpClp::_check_feasibility(const std::vector<double> &solution) return SUBSOLVER_FEASIBLE; } + ///////////////////////////////////////////////////////////////////////////////////////////// // optimality check SUBSOLVER_RETCODE @@ -1597,12 +1533,12 @@ LbpClp::_check_optimality(const babBase::BabNode ¤tNode, const double newL // Check if our dual and CLP solution are the same if (!mc::isequal(dual, newLBD, _computationTol, _computationTol)) { std::ostringstream outstr; - outstr << " Calculated dual: " << dual << " does not equal the solution value returned by CLP: " << newLBD << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + outstr << " Warning: Calculated dual: " << dual << " does not equal the solution value returned by CLP: " << newLBD << "." << std::endl; + outstr << " Not using this bound." << std::endl; + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); #ifdef LP__WRITE_CHECK_FILES - std::string str = "clp_optim_check"; - _write_LP_to_file(str); + _write_LP_to_file("clp_optim_check"); #endif return SUBSOLVER_INFEASIBLE; @@ -1627,11 +1563,10 @@ LbpClp::_check_optimality(const babBase::BabNode ¤tNode, const double newL } } outstr << " CLP failed in returning a correct objective value! Falling back to interval arithmetic and proceeding." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); #ifdef LP__WRITE_CHECK_FILES - std::string str = "clp_optim_check"; - _write_LP_to_file(str); + _write_LP_to_file("clp_optim_check"); #endif return SUBSOLVER_INFEASIBLE; @@ -1659,13 +1594,13 @@ LbpClp::_terminate_Clp() ///////////////////////////////////////////////////////////////////////////////////////////// // write current LP to file void -LbpClp::_write_LP_to_file(std::string &fileName) +LbpClp::_write_LP_to_file(const std::string &fileName) { - if (fileName.empty()) { - fileName = "MAiNGO_LP_WRITE_CHECK_FILES"; + _clp.writeLp("MAiNGO_LP_WRITE_CHECK_FILES", "lp", 0); + } + else { + _clp.writeLp(fileName.c_str(), "lp", 0); } - - _clp.writeLp(fileName.c_str(), "lp", 0); } #endif diff --git a/src/lbpCplex.cpp b/src/lbpCplex.cpp index fabb5c6..ef23fff 100644 --- a/src/lbpCplex.cpp +++ b/src/lbpCplex.cpp @@ -133,6 +133,8 @@ LbpCplex::LbpCplex(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, cons cplex.setParam(IloCplex::Param::Simplex::Limits::Iterations, 100000); cplex.setParam(IloCplex::Param::Barrier::Limits::Iteration, 100000); cplex.setParam(IloCplex::Param::Network::Iterations, 100000); + // Set max time to half of the available - Sometimes CPLEX just can't solve the LP in considerable time + cplex.setParam(IloCplex::Param::TimeLimit, _maingoSettings->maxTime / 2); // Set options cplex.setParam(IloCplex::EpOpt, 1e-9); // Tightest possible opt tolerance in CPLEX cplex.setParam(IloCplex::EpRHS, 1e-9); // Tightest possible feas tolerance in CPLEX @@ -969,10 +971,16 @@ LbpCplex::_get_LP_status() IloAlgorithm::Status cplexStatus = cplex.getStatus(); switch (cplexStatus) { - case IloAlgorithm::Infeasible: - return LP_INFEASIBLE; case IloAlgorithm::Optimal: return LP_OPTIMAL; + case IloAlgorithm::Infeasible: + return LP_INFEASIBLE; + case IloAlgorithm::InfeasibleOrUnbounded: + // Note that our LPs can never be unbounded, since we always give finite variable bounds. + // Therefore, this return code (which apparently is caused by CPLEX identifying dual infeasibility + // without further analyzing (for time reasons, apparently) whether this is caused by the primal + // being infeasible or unbounded) can only be caused by infeasible problems. + return LP_INFEASIBLE; default: return LP_UNKNOWN; } @@ -1353,14 +1361,13 @@ LbpCplex::_check_infeasibility(const babBase::BabNode ¤tNode) } if (res1 - res2 <= 0. && !mc::isequal(res1, res2, _computationTol * 1e1, _computationTol * 1e1)) { #ifdef LP__WRITE_CHECK_FILES - std::string str = "lpex_infeas_check"; - _write_LP_to_file(str); + _write_LP_to_file("lpex_infeas_check"); #endif std::ostringstream outstr; outstr << " Warning: Infeasibility condition violated" << std::endl << " It holds that (" << std::setprecision(16) << res1 << " =) y^T * b - y^T *A *x (= " << std::setprecision(16) << res2 << ") <= 0. For further information, see Farkas' Lemma." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); return SUBSOLVER_FEASIBLE; } return SUBSOLVER_INFEASIBLE; @@ -1371,6 +1378,25 @@ LbpCplex::_check_infeasibility(const babBase::BabNode ¤tNode) } +void +LbpCplex::_print_check_feasibility(const std::shared_ptr<Logger> logger, const VERB verbosity, const std::vector<double> &solution, const std::vector<std::vector<double>> rhs, const std::string name, const double value, const unsigned i, unsigned k, const unsigned nvar) +{ + std::ostringstream outstr; + outstr << " Warning: CPLEX returned FEASIBLE although the point is an infeasible one w.r.t. inequality " << i << "!" << std::endl; + + if (verbosity > VERB_NORMAL) { + outstr << std::setprecision(16) << " value: " << value << " _" << name << "[" << i << "][" << k << "]: " << rhs[i][k] << std::endl; + outstr << " LBP solution point: " << std::endl; + for (unsigned i = 0; i < nvar; i++) { + outstr << " x(" << i << "): " << solution[i] << std::endl; + } + } + + outstr << " Continuing with parent LBD." << std::endl; + logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); +} + + ///////////////////////////////////////////////////////////////////////////////////////////// // feasibility check SUBSOLVER_RETCODE @@ -1385,19 +1411,7 @@ LbpCplex::_check_feasibility(const std::vector<double> &solution) value += _matrixIneq[i][k][j] * solution[j]; } if (value - _rhsIneq[i][k] > _maingoSettings->deltaIneq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CPLEX returned FEASIBLE although the point is an infeasible one w.r.t. inequality " << i << "!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << std::setprecision(16) << " value: " << value << " _rhsIneq[" << i << "][" << k << "]: " << _rhsIneq[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned int i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsIneq, "rhsIneq", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1410,19 +1424,7 @@ LbpCplex::_check_feasibility(const std::vector<double> &solution) value += _matrixEq1[i][k][j] * solution[j]; } if (value - _rhsEq1[i][k] > _maingoSettings->deltaEq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CPLEX returned FEASIBLE although the point is an infeasible one w.r.t. equality " << i << "!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << std::setprecision(16) << " value: " << value << " _rhsEq1[" << i << "][" << k << "]: " << _rhsEq1[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned int i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsEq1, "rhsEq1", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1430,19 +1432,7 @@ LbpCplex::_check_feasibility(const std::vector<double> &solution) value += _matrixEq2[i][k][j] * solution[j]; } if (value - _rhsEq2[i][k] > _maingoSettings->deltaEq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CPLEX returned FEASIBLE although the point is an infeasible one w.r.t. equality " << i << "!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << std::setprecision(16) << " value: " << value << " _rhsEq2[" << i << "][" << k << "]: " << _rhsEq2[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned int i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsEq2, "rhsEq2", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1455,19 +1445,7 @@ LbpCplex::_check_feasibility(const std::vector<double> &solution) value += _matrixIneqRelaxationOnly[i][k][j] * solution[j]; } if (value - _rhsIneqRelaxationOnly[i][k] > _maingoSettings->deltaIneq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CPLEX returned FEASIBLE although the point is an infeasible one w.r.t. inequality (rel only) " << i << "!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << " value: " << value << " _rhsIneqRelaxationOnly[" << i << "][" << k << "]: " << _rhsIneqRelaxationOnly[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned int i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsIneqRelaxationOnly, "rhsIneqRelaxationOnly", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1480,19 +1458,7 @@ LbpCplex::_check_feasibility(const std::vector<double> &solution) value += _matrixEqRelaxationOnly1[i][k][j] * solution[j]; } if (value - _rhsEqRelaxationOnly1[i][k] > _maingoSettings->deltaEq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CPLEX returned FEASIBLE although the point is an infeasible one w.r.t. equality (rel only) " << i << "!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << " value: " << value << " _rhsEqRelaxationOnly1[" << i << "][" << k << "]: " << _rhsEqRelaxationOnly1[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned int i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsEqRelaxationOnly1, "rhsEqRelaxationOnly1", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1500,19 +1466,7 @@ LbpCplex::_check_feasibility(const std::vector<double> &solution) value += _matrixEqRelaxationOnly2[i][k][j] * solution[j]; } if (value - _rhsEqRelaxationOnly2[i][k] > _maingoSettings->deltaEq) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CPLEX returned FEASIBLE although the point is an infeasible one w.r.t. equality (rel only) " << i << "!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << " value: " << value << " _rhsEqRelaxationOnly2[" << i << "][" << k << "]: " << _rhsEqRelaxationOnly2[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned int i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsEqRelaxationOnly2, "rhsEqRelaxationOnly2", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1525,19 +1479,7 @@ LbpCplex::_check_feasibility(const std::vector<double> &solution) value += _matrixIneqSquash[i][k][j] * solution[j]; } if (value - _rhsIneqSquash[i][k] > 1e-9) { - if (_maingoSettings->LBP_verbosity > VERB_NONE) { - std::ostringstream outstr; - outstr << " Warning: CPLEX returned FEASIBLE although the point is an infeasible one w.r.t. inequality (squash) " << i << "!" << std::endl; - if (_maingoSettings->LBP_verbosity > VERB_NORMAL) { - outstr << std::setprecision(16) << " value: " << value << " _rhsIneqSquash[" << i << "][" << k << "]: " << _rhsIneqSquash[i][k] << std::endl; - outstr << " LBP solution point: " << std::endl; - for (unsigned int i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solution[i] << std::endl; - } - } - outstr << " Continuing with parent LBD." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); - } + _print_check_feasibility(_logger, _maingoSettings->LBP_verbosity, solution, _rhsIneqSquash, "rhsIneqSquash", value, i, k, _nvar); return SUBSOLVER_INFEASIBLE; } value = 0.; @@ -1643,8 +1585,9 @@ LbpCplex::_check_optimality(const babBase::BabNode ¤tNode, const double ne // Check if our dual and CPLEX solution are the same if (!mc::isequal(dual, newLBD, _computationTol * 1e1, _computationTol * 1e1)) { std::ostringstream outstr; - outstr << " Calculated dual: " << dual << " does not equal the solution value returned by CPLEX: " << newLBD << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + outstr << " Warning: Calculated dual: " << dual << " does not equal the solution value returned by CPLEX: " << newLBD << "." << std::endl; + outstr << " Not using this bound." << std::endl; + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); #ifdef LP__WRITE_CHECK_FILES cplex.exportModel("lpex_optim_check"); #endif @@ -1669,7 +1612,7 @@ LbpCplex::_check_optimality(const babBase::BabNode ¤tNode, const double ne } } outstr << " CPLEX failed in returning a correct objective value! Falling back to interval arithmetic and proceeding." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); #ifdef LP__WRITE_CHECK_FILES cplex.exportModel("lpex_optim_check"); #endif @@ -1684,7 +1627,7 @@ LbpCplex::_check_optimality(const babBase::BabNode ¤tNode, const double ne ///////////////////////////////////////////////////////////////////////////////////////////// // write current LP to file void -LbpCplex::_write_LP_to_file(std::string &fileName) +LbpCplex::_write_LP_to_file(const std::string &fileName) { std::string str; diff --git a/src/lbpDagObj.cpp b/src/lbpDagObj.cpp index d30d674..4d01644 100644 --- a/src/lbpDagObj.cpp +++ b/src/lbpDagObj.cpp @@ -10,6 +10,7 @@ **********************************************************************************/ #include "lbpDagObj.h" +#include "MAiNGOException.h" namespace maingo { @@ -35,13 +36,23 @@ DagObj::DagObj(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const st } this->resultVars.resize(DAGfunctions.size()); DAG.eval(DAGfunctions.size(), DAGfunctions.data(), this->resultVars.data(), nvar, DAGvars.data(), this->vars.data()); // Get functions and write them to resultVars - for (unsigned int i = 0; i < this->resultVars.size(); i++) { - this->functions.push_back(this->resultVars[i]); // Get functions + for (unsigned int i = 0; i < 1 + nineq + neq + nineqRelaxationOnly + neqRelaxationOnly + nineqSquash; i++) { // Do not track obj_per_data and output var's + this->functions.push_back(this->resultVars[i]); // Get functions } // Get the list of operations used in the DAG // It is needed for the call of proper DAG functions +#ifndef HAVE_GROWING_DATASETS this->subgraph = this->DAG.subgraph(this->functions.size(), this->functions.data()); +#else + storedFunctions.clear(); + storedFunctions.push_back(this->functions); + + storedSubgraph.clear(); + auto pointerToSubgraph = std::make_shared<mc::FFSubgraph>(this->DAG.subgraph(this->functions.size(), this->functions.data())); + storedSubgraph.push_back(pointerToSubgraph); + this->subgraph = *storedSubgraph[0]; +#endif // HAVE_GROWING_DATASETS // Get operations of each function in the DAG this->functionsObj.clear(); @@ -96,11 +107,24 @@ DagObj::DagObj(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const st this->subgraphIneqRelaxationOnly.resize(nineqRelaxationOnly); this->subgraphEqRelaxationOnly.resize(neqRelaxationOnly); this->subgraphIneqSquash.resize(nineqSquash); +#ifdef HAVE_GROWING_DATASETS + std::shared_ptr<mc::FFSubgraph> pointerToSubgraphObj; +#endif // HAVE_GROWING_DATASETS + for (unsigned int i = 0; i < this->functions.size(); i++) { unsigned indexType = (*_constraintProperties)[i].indexTypeNonconstant; switch ((*_constraintProperties)[i].type) { case OBJ: +#ifndef HAVE_GROWING_DATASETS this->subgraphObj[indexType] = this->DAG.subgraph(this->functionsObj[indexType].size(), this->functionsObj[indexType].data()); +#else + storedFunctionsObj.clear(); + storedFunctionsObj.push_back(this->functionsObj[indexType]); + storedSubgraphObj.clear(); + pointerToSubgraphObj = std::make_shared<mc::FFSubgraph>(this->DAG.subgraph(this->functionsObj[indexType].size(), this->functionsObj[indexType].data())); + storedSubgraphObj.push_back(pointerToSubgraphObj); + this->subgraphObj[indexType] = *storedSubgraphObj[0]; +#endif // HAVE_GROWING_DATASETS break; case INEQ: this->subgraphIneq[indexType] = this->DAG.subgraph(this->functionsIneq[indexType].size(), this->functionsIneq[indexType].data()); @@ -122,6 +146,7 @@ DagObj::DagObj(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const st break; } } + // Allocate memory for the corresponding vectors (in dependence on LBP_linpoints) and also set settings // We use these always, e.g., for option check this->McPoint.resize(nvar); @@ -170,6 +195,55 @@ DagObj::initialize_vMcCormick() } +#ifdef HAVE_GROWING_DATASETS +///////////////////////////////////////////////////////////////////////// +// function for adding subgraph corresponding to a new reduced dataset +void +DagObj::add_subgraph_for_new_dataset(const unsigned int indexDataset) +{ + mc::FFVar obj = 0; + for (auto idxDataPoint : (*datasets)[indexDataset]) { + obj += resultVars[idxDataPoint + indexFirstData]; + } + functions[0] = obj; + functionsObj[0][0] = obj; + + // Build new subgraphs + storedFunctions.push_back(functions); + auto pointerToSubgraph = std::make_shared<mc::FFSubgraph>(DAG.subgraph(functions.size(), functions.data())); + storedSubgraph.push_back(pointerToSubgraph); + + storedFunctionsObj.push_back(functionsObj[0]); + auto pointerToSubgraphObj = std::make_shared<mc::FFSubgraph>(DAG.subgraph(functionsObj[0].size(), functionsObj[0].data())); + storedSubgraphObj.push_back(pointerToSubgraphObj); +} + + +///////////////////////////////////////////////////////////////////////// +// function for changing objective in dependence of a (reduced) dataset +void +DagObj::change_growing_objective(const unsigned int indexDataset) +{ + // Build subgraphs if necessary + if (indexDataset == storedSubgraphObj.size()) { + add_subgraph_for_new_dataset(indexDataset); + } + else if (indexDataset > storedSubgraphObj.size()) { + std::ostringstream errmsg; + errmsg << " Error in UpperBoundingSolver - change of objective: subgraph for dataset with index " << indexDataset - 1 << " missing. " << std::endl; + throw MAiNGOException(errmsg.str()); + } + + // Update subgraphs + subgraph = *storedSubgraph[indexDataset]; + functions = storedFunctions[indexDataset]; + + subgraphObj[0] = *storedSubgraphObj[indexDataset]; + functionsObj[0] = storedFunctionsObj[indexDataset]; +} +#endif //HAVE_GROWING_DATASETS + + } // end namespace lbp diff --git a/src/lbpFactory.cpp b/src/lbpFactory.cpp index bf7dfb8..7e583a4 100644 --- a/src/lbpFactory.cpp +++ b/src/lbpFactory.cpp @@ -36,18 +36,18 @@ lbp::make_lbp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, co switch (settingsIn->LBP_solver) { case LBP_SOLVER_MAiNGO: { loggerIn->print_message(" Lower bounding: MAiNGO internal solver (McCormick relaxations for objective, intervals for constraints)\n", - settingsIn->BAB_verbosity, VERB_NORMAL, settingsIn->loggingDestination); + VERB_NORMAL, BAB_VERBOSITY); return std::make_shared<LowerBoundingSolver>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn); } case LBP_SOLVER_INTERVAL: { - loggerIn->print_message(" Lower bounding: Interval extensions\n", settingsIn->BAB_verbosity, VERB_NORMAL, settingsIn->loggingDestination); + loggerIn->print_message(" Lower bounding: Interval extensions\n", VERB_NORMAL, BAB_VERBOSITY); return std::make_shared<LbpInterval>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn); } case LBP_SOLVER_CPLEX: { #ifdef HAVE_CPLEX - loggerIn->print_message(" Lower bounding: CPLEX\n", settingsIn->BAB_verbosity, VERB_NORMAL, settingsIn->loggingDestination); + loggerIn->print_message(" Lower bounding: CPLEX\n", VERB_NORMAL, BAB_VERBOSITY); return std::make_shared<LbpCplex>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn); #else @@ -55,15 +55,14 @@ lbp::make_lbp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, co #endif } case LBP_SOLVER_CLP: { - loggerIn->print_message(" Lower bounding: CLP\n", settingsIn->BAB_verbosity, VERB_NORMAL, settingsIn->loggingDestination); + loggerIn->print_message(" Lower bounding: CLP\n", VERB_NORMAL, BAB_VERBOSITY); return std::make_shared<LbpClp>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn); } - default: - { + default: { std::ostringstream errmsg; errmsg << " Error in LbpFactory: Unknown lower bounding solver: " << settingsIn->LBP_solver; throw MAiNGOException(errmsg.str()); } } -} \ No newline at end of file +} diff --git a/src/lbpInterval.cpp b/src/lbpInterval.cpp index b3fe00c..ac29ff7 100644 --- a/src/lbpInterval.cpp +++ b/src/lbpInterval.cpp @@ -226,27 +226,27 @@ LbpInterval::_turn_off_specific_options() { if (_maingoSettings->LBP_solver != LBP_SOLVER_INTERVAL) { - _logger->print_message(" Warning: Function for turning off specific options not implemented. Not changing any settings. Procedding...\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" Warning: Function for turning off specific options not implemented. Not changing any settings. Procedding...\n", VERB_NORMAL, LBP_VERBOSITY); } else { if (_maingoSettings->LBP_linPoints != LINP_MID) { - _logger->print_message(" The option LBP_linPoints has to be 0 when using the interval-based solver (LBP_solver = 1). Setting it to 0.\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" The option LBP_linPoints has to be 0 when using the interval-based solver (LBP_solver = 1). Setting it to 0.\n", VERB_NORMAL, LBP_VERBOSITY); _maingoSettings->LBP_linPoints = LINP_MID; // Note that this already has been used in the constructor! } if (_maingoSettings->PRE_obbtMaxRounds > 0) { - _logger->print_message(" The option PRE_obbtMaxRounds has to be 0 when using the interval-based solver (LBP_solver = 1). Setting it to 0.\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" The option PRE_obbtMaxRounds has to be 0 when using the interval-based solver (LBP_solver = 1). Setting it to 0.\n", VERB_NORMAL, LBP_VERBOSITY); _maingoSettings->PRE_obbtMaxRounds = 0; } if (_maingoSettings->BAB_alwaysSolveObbt) { - _logger->print_message(" The option BAB_alwaysSolveObbt has to be 0 when using the interval-based solver (LBP_solver = 1). Setting it to 0.\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" The option BAB_alwaysSolveObbt has to be 0 when using the interval-based solver (LBP_solver = 1). Setting it to 0.\n", VERB_NORMAL, LBP_VERBOSITY); _maingoSettings->BAB_alwaysSolveObbt = false; } if (_maingoSettings->BAB_probing) { - _logger->print_message(" The option BAB_probing has to be 0 when using the interval-based solver (LBP_solver = 1). Setting it to 0.\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" The option BAB_probing has to be 0 when using the interval-based solver (LBP_solver = 1). Setting it to 0.\n", VERB_NORMAL, LBP_VERBOSITY); _maingoSettings->BAB_probing = false; } if (_maingoSettings->BAB_dbbt) { - _logger->print_message(" The option BAB_dbbt has to be 0 when using the interval-based solver (LBP_solver = 1). Setting it to 0.\n", _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" The option BAB_dbbt has to be 0 when using the interval-based solver (LBP_solver = 1). Setting it to 0.\n", VERB_NORMAL, LBP_VERBOSITY); _maingoSettings->BAB_dbbt = false; } } @@ -289,18 +289,18 @@ LbpInterval::_check_optimality(const babBase::BabNode ¤tNode, const double ///////////////////////////////////////////////////////////////////////////////////////////// // write current LP to file void -LbpInterval::_write_LP_to_file(std::string &fileName) +LbpInterval::_write_LP_to_file(const std::string &fileName) { - std::string str; + std::string fileNameWithExtension; if (fileName.empty()) { - str = "MAiNGO_LP_WRITE_CHECK_FILES.lp"; + fileNameWithExtension = "MAiNGO_LP_WRITE_CHECK_FILES.lp"; } else { - str = fileName + ".lp"; + fileNameWithExtension = fileName + ".lp"; } - std::ofstream lpFile(str); + std::ofstream lpFile(fileNameWithExtension); lpFile << "\\ This file was generated by MAiNGO " << get_version() << "\n\n"; diff --git a/src/lbpLinearizationStrats.cpp b/src/lbpLinearizationStrats.cpp index 2bc673f..bd85f9f 100644 --- a/src/lbpLinearizationStrats.cpp +++ b/src/lbpLinearizationStrats.cpp @@ -1,5 +1,5 @@ /********************************************************************************** - * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * Copyright (c) 2019-2023 Process Systems Engineering (AVT.SVT), RWTH Aachen University * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -12,6 +12,7 @@ #include "MAiNGOException.h" #include "lbp.h" #include "lbpDagObj.h" +#include "pointIsWithinNodeBounds.h" #include <algorithm> @@ -41,10 +42,13 @@ LowerBoundingSolver::_linearize_model_at_midpoint(const std::vector<double> &low ///////////////////////////////////////////////////////////////////////// // linearizes each function of the model at the incumbent, if the incumbent is not in the node, linearize at mid point of the underlying box LINEARIZATION_RETCODE -LowerBoundingSolver::_linearize_model_at_incumbent(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds, const bool holdsIncumbent) +LowerBoundingSolver::_linearize_model_at_incumbent_or_at_midpoint(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds) { - if (holdsIncumbent) { + _logger->print_message(" Checking if node contains incumbent.", VERB_ALL, LBP_VERBOSITY); + if (point_is_within_node_bounds(_incumbent, lowerVarBounds, upperVarBounds)) { + _logger->print_message(" Node contains incumbent, linearizing there.", VERB_ALL, LBP_VERBOSITY); + _linearize_functions_at_linpoint(_DAGobj->resultRelaxation, _incumbent, lowerVarBounds, upperVarBounds, _DAGobj->subgraph, _DAGobj->functions); _update_whole_LP_at_linpoint(_DAGobj->resultRelaxation, _incumbent, lowerVarBounds, upperVarBounds, 0); @@ -52,6 +56,7 @@ LowerBoundingSolver::_linearize_model_at_incumbent(const std::vector<double> &lo return LINEARIZATION_UNKNOWN; } else { + _logger->print_message(" Node does not contain incumbent, linearizing at midpoint.", VERB_ALL, LBP_VERBOSITY); return _linearize_model_at_midpoint(lowerVarBounds, upperVarBounds); } } @@ -91,6 +96,9 @@ LowerBoundingSolver::_linearize_functions_at_linpoint(std::vector<MC> &resultRel catch (const MC::Exceptions &e) { throw MAiNGOException(std::string(" Error in evaluation of McCormick relaxations: ") + e.what()); } + catch (const vMC::Exceptions &e) { + throw MAiNGOException(std::string(" Error in evaluation of vMcCormick relaxations: ") + e.what()); + } catch (const std::exception &e) { throw MAiNGOException(" Error in evaluation of relaxed model equations.", e); } @@ -135,6 +143,9 @@ LowerBoundingSolver::_linearize_functions_at_preset_vector_linpoint(std::vector< catch (const MC::Exceptions &e) { throw MAiNGOException(std::string(" Error in evaluation of McCormick relaxations: ") + e.what()); } + catch (const vMC::Exceptions &e) { + throw MAiNGOException(std::string(" Error in evaluation of vMcCormick relaxations: ") + e.what()); + } catch (const std::exception &e) { throw MAiNGOException(" Error in evaluation of relaxed model equations. ", e); } @@ -184,8 +195,7 @@ LowerBoundingSolver::_linearization_points_Kelley(const babBase::BabNode ¤ } else if (_LPstatus != LP_OPTIMAL) { #ifdef LP__WRITE_CHECK_FILES - std::string str = "Kelley_not_optimal_or_infeas"; - _write_LP_to_file(str); + _write_LP_to_file("Kelley_not_optimal_or_infeas"); #endif break; } @@ -202,7 +212,7 @@ LowerBoundingSolver::_linearization_points_Kelley(const babBase::BabNode ¤ catch (std::exception &e) { std::ostringstream outstr; outstr << " Warning: Variables at solution of auxiliary LP in Kelley's algorithm could be not obtained by LP solver:" << e.what() << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); // Return empty solution instead break; } @@ -471,8 +481,7 @@ LowerBoundingSolver::_linearization_points_Kelley_Simplex(const babBase::BabNode } else if (_LPstatus != LP_OPTIMAL) { #ifdef LP__WRITE_CHECK_FILES - std::string str = "Kelley_Simplex_not_optimal_or_infeas"; - _write_LP_to_file(str); + _write_LP_to_file("Kelley_Simplex_not_optimal_or_infeas"); #endif break; } @@ -489,7 +498,7 @@ LowerBoundingSolver::_linearization_points_Kelley_Simplex(const babBase::BabNode catch (std::exception &e) { std::ostringstream outstr; outstr << " Warning: Variables at solution of auxiliary LP in Kelley's algorithm (with Simplex starting points) could be not obtained by LP solver:" << e.what() << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->LBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY); // Return empty solution instead break; } diff --git a/src/logger.cpp b/src/logger.cpp index 1a11560..3c6ffda 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -10,8 +10,10 @@ **********************************************************************************/ #include "logger.h" +#include "MAiNGOException.h" #include <fstream> +#include <memory> #include <sstream> @@ -19,13 +21,12 @@ using namespace maingo; ///////////////////////////////////////////////////////////////////////// -// writes a message to outstream and possibly bablog +// helper function for print_message which handels printing to the right logging destinations void -Logger::print_message(const std::string& message, const VERB verbosityGiven, const VERB verbosityNeeded, const LOGGING_DESTINATION givenOutstreamVerbosity) +Logger::_print_message_if_verbosity_exceeds_needed(const std::string& message, const VERB verbosityNeeded, const VERB verbosityGiven) { - switch (givenOutstreamVerbosity) { - + switch (_settings->loggingDestination) { case LOGGING_OUTSTREAM: // Print to _outStream only if (verbosityGiven >= verbosityNeeded) { @@ -54,30 +55,146 @@ Logger::print_message(const std::string& message, const VERB verbosityGiven, con ///////////////////////////////////////////////////////////////////////// -// writes a message to outstream only without asking for vorbosities +// writes a message to outstream and possibly bablog void -Logger::print_message_to_stream_only(const std::string& message, const LOGGING_DESTINATION givenOutstreamVerbosity) +Logger::print_message(const std::string& message, const VERB verbosityNeeded, const SETTING_NAMES settingType) { - if ((givenOutstreamVerbosity == LOGGING_FILE_AND_STREAM) || (givenOutstreamVerbosity == LOGGING_OUTSTREAM)) { - (*_outStream) << message << std::flush; + const VERB verbosityGiven = _get_verb(settingType); + _print_message_if_verbosity_exceeds_needed(message, verbosityNeeded, verbosityGiven); +} + + +///////////////////////////////////////////////////////////////////////// +// writes a message to outstream and possibly bablog +void +Logger::print_message(const std::string& message, const VERB verbosityNeeded, const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1) +{ + const VERB verbosityGiven = _get_max_verb(settingType, optSettingType1); + _print_message_if_verbosity_exceeds_needed(message, verbosityNeeded, verbosityGiven); +} + + +///////////////////////////////////////////////////////////////////////// +// writes a message to outstream and possibly bablog +void +Logger::print_message(const std::string& message, const VERB verbosityNeeded, const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1, const SETTING_NAMES optSettingType2) +{ + const VERB verbosityGiven = _get_max_verb(settingType, optSettingType1, optSettingType2); + _print_message_if_verbosity_exceeds_needed(message, verbosityNeeded, verbosityGiven); +} + + +///////////////////////////////////////////////////////////////////////// +// get verbosities corresponding to the given setting name +VERB +Logger::_get_verb(const SETTING_NAMES settingType) const +{ + + VERB verbosity = VERB_NONE; + + switch (settingType) { + case LBP_VERBOSITY: + verbosity = _settings->LBP_verbosity; + break; + case UBP_VERBOSITY: + verbosity = _settings->UBP_verbosity; + break; + case BAB_VERBOSITY: + verbosity = _settings->BAB_verbosity; + break; + default: + break; } + + return verbosity; } ///////////////////////////////////////////////////////////////////////// -// sets output stream +// get maximum verbosities according to the given setting names +VERB +Logger::_get_max_verb(const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1) const +{ + + const std::vector<SETTING_NAMES> verbEnums = {settingType, optSettingType1}; + std::vector<VERB> verbosities = {VERB_NONE, VERB_NONE}; + + for (int i = 0; i < verbEnums.size(); i++) { + + switch (verbEnums[i]) { + case LBP_VERBOSITY: + verbosities[i] = _settings->LBP_verbosity; + break; + case UBP_VERBOSITY: + verbosities[i] = _settings->UBP_verbosity; + break; + case BAB_VERBOSITY: + verbosities[i] = _settings->BAB_verbosity; + break; + default: + break; + } + } + + const VERB maxVerb = std::max(verbosities[0], verbosities[1]); + + return maxVerb; +} + + +///////////////////////////////////////////////////////////////////////// +// get maximum verbosities according to the given setting names +VERB +Logger::_get_max_verb(const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1, const SETTING_NAMES optSettingType2) const +{ + + const std::vector<SETTING_NAMES> verbEnums = {settingType, optSettingType1, optSettingType2}; + std::vector<VERB> verbosities = {VERB_NONE, VERB_NONE, VERB_NONE}; + + for (int i = 0; i < 3; i++) { + + switch (verbEnums[i]) { + case LBP_VERBOSITY: + verbosities[i] = _settings->LBP_verbosity; + break; + case UBP_VERBOSITY: + verbosities[i] = _settings->UBP_verbosity; + break; + case BAB_VERBOSITY: + verbosities[i] = _settings->BAB_verbosity; + break; + default: + break; + } + } + + const VERB maxVerb = std::max(verbosities[2], std::max(verbosities[0], verbosities[1])); + + return maxVerb; +} + + +///////////////////////////////////////////////////////////////////////// +// writes a message to outstream only without asking for vorbosities void -Logger::set_output_stream(std::ostream* const outputStream) +Logger::print_message_to_stream_only(const std::string& message) { - _outStream = outputStream; + + const LOGGING_DESTINATION givenOutstreamVerbosity = _settings->loggingDestination; + + if ((givenOutstreamVerbosity == LOGGING_FILE_AND_STREAM) || (givenOutstreamVerbosity == LOGGING_OUTSTREAM)) { + (*_outStream) << message << std::flush; + } } ///////////////////////////////////////////////////////////////////////// // creates the log file void -Logger::create_log_file(const LOGGING_DESTINATION givenOutstreamVerbosity) +Logger::create_log_file() const { + const LOGGING_DESTINATION givenOutstreamVerbosity = _settings->loggingDestination; + if ((givenOutstreamVerbosity == LOGGING_FILE_AND_STREAM) || (givenOutstreamVerbosity == LOGGING_FILE)) { std::ofstream logFile; logFile.open(logFileName, std::ios::out); @@ -89,7 +206,7 @@ Logger::create_log_file(const LOGGING_DESTINATION givenOutstreamVerbosity) ///////////////////////////////////////////////////////////////////////// // creates the csv file void -Logger::create_iterations_csv_file(const bool writeCsv) +Logger::create_iterations_csv_file(const bool writeCsv) const { if (writeCsv) { std::ofstream iterationsFile(csvIterationsName, std::ios::out); @@ -163,7 +280,8 @@ Logger::save_settings_file_name(const std::string& fileName, const bool fileFoun // If file has not been found generate a different string if (fileName == "MAiNGOSettings.txt") { str = "\n Warning: Could not open settings file with default name " + fileName + ".\n"; - } else { + } + else { str = "\n Warning: Could not open settings file " + fileName + ".\n"; } str += " Proceeding with default settings."; @@ -196,9 +314,9 @@ Logger::save_setting(const SETTING_NAMES settingName, const std::string& str) ///////////////////////////////////////////////////////////////////////// -// print and or write user-set settings +// print and/or write user-set settings void -Logger::print_settings(const VERB verbosityGiven, const VERB verbosityNeeded, const LOGGING_DESTINATION givenOutstreamVerbosity) +Logger::print_settings(const VERB verbosityNeeded, const SETTING_NAMES settingType) { // First check if any setting was changed at all @@ -222,7 +340,92 @@ Logger::print_settings(const VERB verbosityGiven, const VERB verbosityNeeded, co if (someSettingChanged) { str += " Done.\n"; } - print_message(str, verbosityGiven, verbosityNeeded, givenOutstreamVerbosity); + print_message(str, verbosityNeeded, settingType); + } +} + + +///////////////////////////////////////////////////////////////////////// +// print and/or write user-set settings +void +Logger::print_settings(const VERB verbosityNeeded, const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1) +{ + + // First check if any setting was changed at all + if (!_userSetSettings.empty()) { + bool someSettingChanged = (_userSetSettings.rbegin()->first > 0); // This checks if there is at least one entry with a positive key, since these belong to the actual settings + + + // If so, we print two additional lines two frame the actual settings (otherwise, we only give output about potential read attempts to empty or non-existing settings files + if (someSettingChanged) { + _userSetSettings[0] = "Settings set by the user:"; + } + std::string str = ""; + for (std::map<int, std::string>::iterator it = _userSetSettings.begin(); it != _userSetSettings.end(); ++it) { + if (it->first > 0) { + str += " " + (it->second) + "\n"; + } + else { + str += " " + (it->second) + "\n"; + } + } + if (someSettingChanged) { + str += " Done.\n"; + } + print_message(str, verbosityNeeded, settingType, optSettingType1); + } +} + + +///////////////////////////////////////////////////////////////////////// +// print and/or write user-set settings +void +Logger::print_settings(const VERB verbosityNeeded, const SETTING_NAMES settingType, const SETTING_NAMES optSettingType1, const SETTING_NAMES optSettingType2) +{ + + // First check if any setting was changed at all + if (!_userSetSettings.empty()) { + bool someSettingChanged = (_userSetSettings.rbegin()->first > 0); // This checks if there is at least one entry with a positive key, since these belong to the actual settings + + + // If so, we print two additional lines two frame the actual settings (otherwise, we only give output about potential read attempts to empty or non-existing settings files + if (someSettingChanged) { + _userSetSettings[0] = "Settings set by the user:"; + } + std::string str = ""; + for (std::map<int, std::string>::iterator it = _userSetSettings.begin(); it != _userSetSettings.end(); ++it) { + if (it->first > 0) { + str += " " + (it->second) + "\n"; + } + else { + str += " " + (it->second) + "\n"; + } + } + if (someSettingChanged) { + str += " Done.\n"; + } + print_message(str, verbosityNeeded, settingType, optSettingType1, optSettingType2); + } +} + + +///////////////////////////////////////////////////////////////////////// +// print and/or write vector in standardized format +void +Logger::print_vector(const unsigned length, const std::vector<double>& vec, const std::string& preString, const VERB verbosityNeeded, const SETTING_NAMES settingType) +{ + if (_get_verb(settingType) >= verbosityNeeded) { + if (vec.size() < length) { + throw MAiNGOException("Given length is greater than the actual size of the given vec"); + } + else { + std::ostringstream outstr; + outstr << preString << std::endl; + for (unsigned int i = 0; i < length; i++) { + outstr << " x(" << i << "): " << vec[i] << std::endl; + } + print_message(outstr.str(), verbosityNeeded, settingType); + } } } diff --git a/src/pointIsWithinNodeBounds.cpp b/src/pointIsWithinNodeBounds.cpp new file mode 100644 index 0000000..bbf3d05 --- /dev/null +++ b/src/pointIsWithinNodeBounds.cpp @@ -0,0 +1,48 @@ +/********************************************************************************** + * Copyright (c) 2023 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#include "pointIsWithinNodeBounds.h" + +#include <cassert> +#include <string> + + +namespace maingo { + + +///////////////////////////////////////////////////////////////////////// +// version using two vectors for the node bounds +bool +point_is_within_node_bounds(const std::vector<double>& point, const std::vector<double>& lowerBounds, const std::vector<double>& upperBounds) +{ + assert(lowerBounds.size() == upperBounds.size()); + assert(lowerBounds.size() == point.size()); + + for (size_t i = 0; i < point.size(); ++i) { + if ((point[i] > upperBounds[i]) || (point[i] < lowerBounds[i])) { + return false; + } + } + + return true; +} + + +///////////////////////////////////////////////////////////////////////// +// version using a BabNode for the node bounds +bool +point_is_within_node_bounds(const std::vector<double>& point, const babBase::BabNode& node) +{ + return point_is_within_node_bounds(point, node.get_lower_bounds(), node.get_upper_bounds()); +} + + +} // end namespace maingo \ No newline at end of file diff --git a/src/programParser.cpp b/src/programParser.cpp index 5d379ce..6852b2d 100644 --- a/src/programParser.cpp +++ b/src/programParser.cpp @@ -24,7 +24,7 @@ ProgramParser::ProgramParser(std::istream& input, ale::symbol_table& symbols): parser(input, symbols) { lex.reserve_keywords( - {"definitions", "objective", "constraints", "outputs", + {"definitions", "objective", "objectivePerData", "constraints", "outputs", "relaxation", "only", "squashing"}); } @@ -57,6 +57,16 @@ ProgramParser::parse(Program& prog) continue; } + if (match_keyword("objectivePerData")) { + if (!match(token::COLON)) { + report_syntactical(); + recover_block(); + continue; + } + parse_objective_per_data(prog); + continue; + } + if (match_keyword("constraints")) { if (!match(token::COLON)) { report_syntactical(); @@ -105,7 +115,7 @@ ProgramParser::parse(Program& prog) void ProgramParser::parse_definitions() { - while (!check(token::END) && !check_any_keyword("definitions", "objective", "constraints", "relaxation", "squashing", "outputs")) { + while (!check(token::END) && !check_any_keyword("definitions", "objective", "objectivePerData", "constraints", "relaxation", "squashing", "outputs")) { if (match_any_definition<LIBALE_MAX_DIM>()) { continue; } @@ -130,10 +140,25 @@ ProgramParser::parse_objective(Program& prog) recover(); } +void +ProgramParser::parse_objective_per_data(Program& prog) +{ + while (!check(token::END) && !check_any_keyword("definitions", "objective", "objectivePerData", "constraints", "relaxation", "squashing", "outputs")) { + std::unique_ptr<value_node<boolean<0>>> expr; + std::string note; + if (match_expression(expr, note)) { + prog.mObjectivePerData.emplace_back(expr.release(), note); + return; + } + report_syntactical(); + recover(); + } +} + void ProgramParser::parse_constraints(Program& prog) { - while (!check(token::END) && !check_any_keyword("definitions", "objective", "constraints", "relaxation", "squashing", "outputs")) { + while (!check(token::END) && !check_any_keyword("definitions", "objective", "objectivePerData", "constraints", "relaxation", "squashing", "outputs")) { std::unique_ptr<value_node<boolean<0>>> expr; std::string note; if (match_expression(expr, note)) { @@ -148,7 +173,7 @@ ProgramParser::parse_constraints(Program& prog) void ProgramParser::parse_relaxations(Program& prog) { - while (!check(token::END) && !check_any_keyword("definitions", "objective", "constraints", "relaxation", "squashing", "outputs")) { + while (!check(token::END) && !check_any_keyword("definitions", "objective", "objectivePerData", "constraints", "relaxation", "squashing", "outputs")) { std::unique_ptr<value_node<boolean<0>>> expr; std::string note; if (match_expression(expr, note)) { @@ -163,7 +188,7 @@ ProgramParser::parse_relaxations(Program& prog) void ProgramParser::parse_squashes(Program& prog) { - while (!check(token::END) && !check_any_keyword("definitions", "objective", "constraints", "relaxation", "squashing", "outputs")) { + while (!check(token::END) && !check_any_keyword("definitions", "objective", "objectivePerData", "constraints", "relaxation", "squashing", "outputs")) { std::unique_ptr<value_node<boolean<0>>> expr; std::string note; if (match_expression(expr, note)) { @@ -178,7 +203,7 @@ ProgramParser::parse_squashes(Program& prog) void ProgramParser::parse_outputs(Program& prog) { - while (!check(token::END) && !check_any_keyword("definitions", "objective", "constraints", "relaxation", "squashing", "outputs")) { + while (!check(token::END) && !check_any_keyword("definitions", "objective", "objectivePerData", "constraints", "relaxation", "squashing", "outputs")) { std::unique_ptr<value_node<real<0>>> expr; std::string note; if (match_expression(expr, note)) { @@ -193,7 +218,7 @@ ProgramParser::parse_outputs(Program& prog) void ProgramParser::recover_block() { - while (current().type != token::END && !(current().type == token::KEYWORD && (current().lexeme == "definitions" || current().lexeme == "objective" || current().lexeme == "constraints" || current().lexeme == "relaxations" || current().lexeme == "squashing" || current().lexeme == "outputs"))) { + while (current().type != token::END && !(current().type == token::KEYWORD && (current().lexeme == "definitions" || current().lexeme == "objective" || current().lexeme == "objectivePerData" || current().lexeme == "constraints" || current().lexeme == "relaxations" || current().lexeme == "squashing" || current().lexeme == "outputs"))) { consume(); } buf.clear(); diff --git a/src/ubp.cpp b/src/ubp.cpp index 6ca7244..b685bf9 100644 --- a/src/ubp.cpp +++ b/src/ubp.cpp @@ -1,5 +1,5 @@ /********************************************************************************** - * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * Copyright (c) 2019-2023 Process Systems Engineering (AVT.SVT), RWTH Aachen University * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -12,10 +12,13 @@ #include "ubp.h" #include "MAiNGOException.h" #include "mpiUtilities.h" +#include "pointIsWithinNodeBounds.h" #include "ubpDagObj.h" #include "ubpEvaluators.h" #include "ubpStructure.h" +#include <algorithm> +#include <numeric> #include <set> @@ -81,8 +84,8 @@ UpperBoundingSolver::solve(babBase::BabNode const ¤tNode, double &objectiv std::vector<double> initialPoint(solutionPoint); // store initial point for later // Check if the initial point already is feasible - double initialObjective = _maingoSettings->infinity; - SUBSOLVER_RETCODE startingPointFeasible = check_feasibility(initialPoint, initialObjective); + double initialObjective = _maingoSettings->infinity; + SUBSOLVER_RETCODE initialPointFeasible = check_feasibility(initialPoint, initialObjective); // Call the subsolver to potentially find a better point SUBSOLVER_RETCODE subsolverFoundFeasiblePoint; @@ -94,7 +97,7 @@ UpperBoundingSolver::solve(babBase::BabNode const ¤tNode, double &objectiv } // Make sure we use the best point we found (be it from the local solver or the initial point...) - if (startingPointFeasible == SUBSOLVER_FEASIBLE) { + if (initialPointFeasible == SUBSOLVER_FEASIBLE) { if (subsolverFoundFeasiblePoint == SUBSOLVER_FEASIBLE) { if (initialObjective < objectiveValue) { objectiveValue = initialObjective; @@ -108,7 +111,7 @@ UpperBoundingSolver::solve(babBase::BabNode const ¤tNode, double &objectiv } // Treat integers: if we are not integer feasible yet, round to the nearest integers, fix the integer variables and re-solve considering only the continuous variables. - if ((startingPointFeasible == SUBSOLVER_INFEASIBLE) && (subsolverFoundFeasiblePoint == SUBSOLVER_INFEASIBLE)) { + if ((initialPointFeasible == SUBSOLVER_INFEASIBLE) && (subsolverFoundFeasiblePoint == SUBSOLVER_INFEASIBLE)) { bool isInteger = false; std::vector<double> fixedIntegersLowerBounds, fixedIntegersUpperBounds; if (_maingoSettings->UBP_ignoreNodeBounds) { @@ -134,12 +137,31 @@ UpperBoundingSolver::solve(babBase::BabNode const ¤tNode, double &objectiv } } if (isInteger) { + // Check if new point obtained after rounding is feasible + initialPoint = solutionPoint; + initialPointFeasible = check_feasibility(initialPoint, initialObjective); + + // Solve NLP again from rounded starting point, using variable bounds that fix the integers to one value subsolverFoundFeasiblePoint = _solve_nlp(fixedIntegersLowerBounds, fixedIntegersUpperBounds, objectiveValue, solutionPoint); + + // Again make sure we use the best point (be it from the local solver or the rounded initial point...) + if (initialPointFeasible == SUBSOLVER_FEASIBLE) { + if (subsolverFoundFeasiblePoint == SUBSOLVER_FEASIBLE) { + if (initialObjective < objectiveValue) { + objectiveValue = initialObjective; + solutionPoint = initialPoint; + } + } + else { + objectiveValue = initialObjective; + solutionPoint = initialPoint; + } + } } } // Return wether we found anything at all - if ((startingPointFeasible == SUBSOLVER_FEASIBLE) || (subsolverFoundFeasiblePoint == SUBSOLVER_FEASIBLE)) { + if ((initialPointFeasible == SUBSOLVER_FEASIBLE) || (subsolverFoundFeasiblePoint == SUBSOLVER_FEASIBLE)) { return SUBSOLVER_FEASIBLE; } else { @@ -194,7 +216,6 @@ UpperBoundingSolver::multistart(const babBase::BabNode ¤tNode, double &obj unsigned nDifferentFeasible = 0; double worstFeasible = -_maingoSettings->infinity; double bestFeasible = _maingoSettings->infinity; - std::srand(42); // For reproducible results despite (pseudo-)random starting points std::ostringstream outstr; // First, check user-specified initial point for feasibility @@ -211,7 +232,7 @@ UpperBoundingSolver::multistart(const babBase::BabNode ¤tNode, double &obj foundFeasible = SUBSOLVER_FEASIBLE; optimalObjectives.push_back(initialObjective); outstr << " User-specified initial point is feasible with objective value " << initialObjective << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->BAB_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); if (_maingoSettings->PRE_printEveryLocalSearch) { ++nDifferentFeasible; } @@ -225,7 +246,7 @@ UpperBoundingSolver::multistart(const babBase::BabNode ¤tNode, double &obj outstr.str(""); outstr.clear(); outstr << " User-specified initial point is infeasible." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); } initialPointFeasible = false; } @@ -300,7 +321,7 @@ UpperBoundingSolver::multistart(const babBase::BabNode ¤tNode, double &obj outstr.str(""); outstr.clear(); outstr << " Run " << iRun << ": Found feasible point with objective value " << currentObjective << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); feasible.push_back(SUBSOLVER_FEASIBLE); // Check if we found this point before bool foundPreviously = false; @@ -319,7 +340,7 @@ UpperBoundingSolver::multistart(const babBase::BabNode ¤tNode, double &obj outstr.str(""); outstr.clear(); outstr << " Found feasible point with objective value " << currentObjective << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); optimalObjectives.push_back(currentObjective); } @@ -342,7 +363,7 @@ UpperBoundingSolver::multistart(const babBase::BabNode ¤tNode, double &obj outstr.str(""); outstr.clear(); outstr << " Run " << iLoc << ": No feasible point found." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); feasible.push_back(SUBSOLVER_INFEASIBLE); optimalObjectives.push_back(-42); } @@ -388,24 +409,24 @@ UpperBoundingSolver::multistart(const babBase::BabNode ¤tNode, double &obj outstr.str(""); outstr.clear(); outstr << " Out of " << iLoc << " local searches, " << nInfeasible << " (i.e., " << nInfeasible * 100. / (double)iLoc << "%) failed to find a feasible point." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); if (nDifferentFeasible > 1) { outstr.str(""); outstr.clear(); outstr << " The successful ones (including user-specified initial point) returned points with " << nDifferentFeasible << " different objective values ranging from " << bestFeasible << " to " << worstFeasible << "." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); } else { outstr.str(""); outstr.clear(); outstr << " The successful ones (including user-specified initial point) returned exactly one feasible point with objective value " << bestFeasible << "." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); } } } else { if (_maingoSettings->PRE_maxLocalSearches > 0) { - _logger->print_message(" No feasible point found.\n", _maingoSettings->UBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(" No feasible point found.\n", VERB_NORMAL, UBP_VERBOSITY); } } @@ -518,11 +539,11 @@ maingo::ubp::evaluate_objective(const double *currentPoint, const unsigned nvar, dagObj->DAG.eval(dagObj->subgraphObj, dagObj->fadbadArray, dagObj->functionsObj.size(), dagObj->functionsObj.data(), dagObj->resultADobj.data(), nvar, dagObj->vars.data(), dagObj->adPoint.data()); } catch (std::exception &e) { - if (dagObj->maingoSettings->UBP_verbosity >= VERB_NORMAL && !dagObj->warningFlag) { + if (!dagObj->warningFlag) { dagObj->warningFlag = true; std::ostringstream outstr; outstr << " Warning: Evaluation of derivatives of objective resulted in an exception. \n Reason: " << e.what() << std::endl; - dagObj->logger->print_message(outstr.str(), dagObj->maingoSettings->UBP_verbosity, VERB_NORMAL, dagObj->maingoSettings->loggingDestination); + dagObj->logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); } } for (unsigned i = 0; i < nvar; i++) { @@ -554,11 +575,11 @@ maingo::ubp::evaluate_inequalities(const double *currentPoint, const unsigned nv dagObj->resultADineqSquashIneq.data(), nvar, dagObj->vars.data(), dagObj->adPoint.data()); } catch (std::exception &e) { - if (dagObj->maingoSettings->UBP_verbosity >= VERB_NORMAL && !dagObj->warningFlag) { + if (!dagObj->warningFlag) { dagObj->warningFlag = true; std::ostringstream outstr; outstr << " Warning: Evaluation of derivatives of inequalities resulted in an exception. \n Reason: " << e.what() << std::endl; - dagObj->logger->print_message(outstr.str(), dagObj->maingoSettings->UBP_verbosity, VERB_NORMAL, dagObj->maingoSettings->loggingDestination); + dagObj->logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); } } if (result) { @@ -598,11 +619,11 @@ maingo::ubp::evaluate_equalities(const double *currentPoint, const unsigned nvar dagObj->DAG.eval(dagObj->subgraphEq, dagObj->fadbadArray, dagObj->functionsEq.size(), dagObj->functionsEq.data(), dagObj->resultADeq.data(), nvar, dagObj->vars.data(), dagObj->adPoint.data()); } catch (std::exception &e) { - if (dagObj->maingoSettings->UBP_verbosity >= VERB_NORMAL && !dagObj->warningFlag) { + if (!dagObj->warningFlag) { dagObj->warningFlag = true; std::ostringstream outstr; outstr << " Warning: Evaluation of derivatives of equalities resulted in an exception. \n Reason: " << e.what() << std::endl; - dagObj->logger->print_message(outstr.str(), dagObj->maingoSettings->UBP_verbosity, VERB_NORMAL, dagObj->maingoSettings->loggingDestination); + dagObj->logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); } } if (result) { @@ -642,11 +663,11 @@ maingo::ubp::evaluate_constraints(const double *currentPoint, const unsigned nva dagObj->DAG.eval(dagObj->subgraphIneqEq, dagObj->fadbadArray, dagObj->functionsIneqEq.size(), dagObj->functionsIneqEq.data(), dagObj->resultADineqEq.data(), nvar, dagObj->vars.data(), dagObj->adPoint.data()); } catch (std::exception &e) { - if (dagObj->maingoSettings->UBP_verbosity >= VERB_NORMAL && !dagObj->warningFlag) { + if (!dagObj->warningFlag) { dagObj->warningFlag = true; std::ostringstream outstr; outstr << " Warning: Evaluation of derivatives of constraints resulted in an exception. \n Reason: " << e.what() << std::endl; - dagObj->logger->print_message(outstr.str(), dagObj->maingoSettings->UBP_verbosity, VERB_NORMAL, dagObj->maingoSettings->loggingDestination); + dagObj->logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); } } if (result) { @@ -687,11 +708,11 @@ maingo::ubp::evaluate_problem(const double *currentPoint, const unsigned nvar, c dagObj->DAG.eval(dagObj->subgraph, dagObj->fadbadArray, dagObj->functions.size(), dagObj->functions.data(), dagObj->resultAD.data(), nvar, dagObj->vars.data(), dagObj->adPoint.data()); } catch (std::exception &e) { - if (dagObj->maingoSettings->UBP_verbosity >= VERB_NORMAL && !dagObj->warningFlag) { + if (!dagObj->warningFlag) { dagObj->warningFlag = true; std::ostringstream outstr; outstr << " Warning: Evaluation of derivatives resulted in an exception. \n Reason: " << e.what() << std::endl; - dagObj->logger->print_message(outstr.str(), dagObj->maingoSettings->UBP_verbosity, VERB_NORMAL, dagObj->maingoSettings->loggingDestination); + dagObj->logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); } } if (result) { @@ -714,7 +735,7 @@ maingo::ubp::evaluate_problem(const double *currentPoint, const unsigned nvar, c ///////////////////////////////////////////////////////////////////////// -// method for evaluating the Hessian of the Lagranian +// method for evaluating the Hessian of the Lagrangian void maingo::ubp::evaluate_hessian(const double *currentPoint, const unsigned nvar, const unsigned ncon, double *hessian, std::shared_ptr<DagObj> dagObj) { @@ -729,11 +750,11 @@ maingo::ubp::evaluate_hessian(const double *currentPoint, const unsigned nvar, c dagObj->DAG.eval(dagObj->subgraph, dagObj->fadbadArray2ndOrder, dagObj->functions.size(), dagObj->functions.data(), dagObj->resultAD2ndOrder.data(), nvar, dagObj->vars.data(), dagObj->adPoint2ndOrder.data()); } catch (std::exception &e) { - if (dagObj->maingoSettings->UBP_verbosity >= VERB_NORMAL && !dagObj->warningFlag) { + if (!dagObj->warningFlag) { dagObj->warningFlag = true; std::ostringstream outstr; outstr << " Warning: Evaluation of second derivatives resulted in an exception. \n Reason: " << e.what() << std::endl; - dagObj->logger->print_message(outstr.str(), dagObj->maingoSettings->UBP_verbosity, VERB_NORMAL, dagObj->maingoSettings->loggingDestination); + dagObj->logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); } } for (unsigned i = 0; i < ncon + 1; i++) { @@ -754,11 +775,9 @@ UpperBoundingSolver::_check_eq(const std::vector<double> &modelOutput) const // Equalities for (unsigned int i = 0; i < _neq; i++) { if (std::fabs(modelOutput[i + 1 + _nineq + _nineqSquash]) > _maingoSettings->deltaEq) { - if (_maingoSettings->UBP_verbosity >= VERB_ALL) { - std::ostringstream outstr; - outstr << " No feasible point found for UBP. First constraint violation in equality constraint " << i << "." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + std::ostringstream outstr; + outstr << " No feasible point found for UBP. First constraint violation in equality constraint " << i << "." << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); return SUBSOLVER_INFEASIBLE; } } @@ -773,11 +792,9 @@ UpperBoundingSolver::_check_ineq(const std::vector<double> &modelOutput) const { for (unsigned int i = 0; i < _nineq; i++) { if (modelOutput[i + 1] > _maingoSettings->deltaIneq) { - if (_maingoSettings->UBP_verbosity >= VERB_ALL) { - std::ostringstream outstr; - outstr << " No feasible point found for UBP. First constraint violation in inequality constraint " << i << "." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + std::ostringstream outstr; + outstr << " No feasible point found for UBP. First constraint violation in inequality constraint " << i << "." << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); return SUBSOLVER_INFEASIBLE; } } @@ -793,11 +810,11 @@ UpperBoundingSolver::_check_ineq_squash(const std::vector<double> &modelOutput) // Squash Inequalities for (unsigned int i = 0; i < _nineqSquash; i++) { if (modelOutput[i + 1 + _nineq] > 0) { - if (_maingoSettings->UBP_verbosity >= VERB_ALL) { - std::ostringstream outstr; - outstr << " No feasible point found for UBP. First constraint violation in squash inequality constraint " << i << "." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + + std::ostringstream outstr; + outstr << " No feasible point found for UBP. First constraint violation in squash inequality constraint " << i << "." << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); + return SUBSOLVER_INFEASIBLE; } } @@ -810,25 +827,14 @@ UpperBoundingSolver::_check_ineq_squash(const std::vector<double> &modelOutput) SUBSOLVER_RETCODE UpperBoundingSolver::_check_bounds(const std::vector<double> ¤tPoint) const { - for (unsigned int i = 0; i < _nvar; i++) { - if ((currentPoint[i] > _originalUpperBounds[i])) { - if (_maingoSettings->UBP_verbosity >= VERB_ALL) { - std::ostringstream outstr; - outstr << " No feasible point found for UBP. Violation of original upper bound for variable " << i << ": " << currentPoint[i] << " > " << _originalUpperBounds[i] << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } - return SUBSOLVER_INFEASIBLE; - } - if ((currentPoint[i] < _originalLowerBounds[i])) { - if (_maingoSettings->UBP_verbosity >= VERB_ALL) { - std::ostringstream outstr; - outstr << " No feasible point found for UBP. Violation of original lower bound for variable " << i << ": " << currentPoint[i] << " < " << _originalLowerBounds[i] << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } - return SUBSOLVER_INFEASIBLE; - } + _logger->print_message(" Checking feasibility with respect to original variable bounds.", VERB_ALL, UBP_VERBOSITY); + if (point_is_within_node_bounds(currentPoint, _originalLowerBounds, _originalUpperBounds)) { + return SUBSOLVER_FEASIBLE; + } + else { + _logger->print_message(" No feasible point found for UBP. Variable bounds violated.", VERB_ALL, UBP_VERBOSITY); + return SUBSOLVER_INFEASIBLE; } - return SUBSOLVER_FEASIBLE; } @@ -843,7 +849,26 @@ UpperBoundingSolver::_check_integrality(const std::vector<double> ¤tPoint) int varType(_originalVariables[i].get_variable_type()); if (varType == babBase::enums::VT_BINARY && currentPoint[i] != 0 && currentPoint[i] != 1) { // Only exact 0 and 1 are permitted - if (_maingoSettings->UBP_verbosity >= VERB_ALL) { + + std::ostringstream outstr; + outstr << " No feasible point found for UBP. First constraint violation in binary feasibility of variable "; + std::string varName(_originalVariables[i].get_name()); + if (varName != "") { + outstr << " " << varName; + } + else { + outstr << " var(" << i + 1 << ")"; + } + outstr << " with index " << i << ": " << currentPoint[i] << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); + + return SUBSOLVER_INFEASIBLE; + } + else if (varType == babBase::enums::VT_INTEGER) { + // Only integers are permitted + double temp = round(currentPoint[i]); + if (currentPoint[i] != temp) { + std::ostringstream outstr; outstr << " No feasible point found for UBP. First constraint violation in binary feasibility of variable "; std::string varName(_originalVariables[i].get_name()); @@ -854,27 +879,8 @@ UpperBoundingSolver::_check_integrality(const std::vector<double> ¤tPoint) outstr << " var(" << i + 1 << ")"; } outstr << " with index " << i << ": " << currentPoint[i] << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } - return SUBSOLVER_INFEASIBLE; - } - else if (varType == babBase::enums::VT_INTEGER) { - // Only integers are permitted - double temp = round(currentPoint[i]); - if (currentPoint[i] != temp) { - if (_maingoSettings->UBP_verbosity >= VERB_ALL) { - std::ostringstream outstr; - outstr << " No feasible point found for UBP. First constraint violation in binary feasibility of variable "; - std::string varName(_originalVariables[i].get_name()); - if (varName != "") { - outstr << " " << varName; - } - else { - outstr << " var(" << i + 1 << ")"; - } - outstr << " with index " << i << ": " << currentPoint[i] << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); + return SUBSOLVER_INFEASIBLE; } } @@ -894,8 +900,14 @@ UpperBoundingSolver::check_feasibility(const std::vector<double> ¤tPoint, if (_check_integrality(currentPoint) == SUBSOLVER_FEASIBLE) { // Ok, discrete variables take discrete values. // Check constraints only for binary/integer-feasible points. +#ifndef HAVE_GROWING_DATASETS _DAGobj->DAG.eval(_DAGobj->subgraph, _DAGobj->doubleArray, _DAGobj->functions.size(), _DAGobj->functions.data(), _DAGobj->resultDouble.data(), _nvar, _DAGobj->vars.data(), currentPoint.data()); +#else + // Evaluation of UB and feasibility check always with full dataset + _DAGobj->DAG.eval(*_DAGobj->storedSubgraph[0], _DAGobj->doubleArray, _DAGobj->storedFunctions[0].size(), _DAGobj->storedFunctions[0].data(), _DAGobj->resultDouble.data(), + _nvar, _DAGobj->vars.data(), currentPoint.data()); +#endif // !HAVE_GROWING_DATASETS if (_check_eq(_DAGobj->resultDouble) == SUBSOLVER_FEASIBLE) { // Ok, equalities are satisfied @@ -915,19 +927,17 @@ UpperBoundingSolver::check_feasibility(const std::vector<double> ¤tPoint, // Ok, the objective is non NaN (isgreaterequal returns false for NaN) // Return the objective value and print solution if desired - if (_maingoSettings->UBP_verbosity >= VERB_ALL) { - std::ostringstream outstr; - outstr << " Found valid UBD: " << objectiveValue << std::endl; - outstr << " UBP solution point: " << std::endl; - for (unsigned int i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << currentPoint[i] << std::endl; - } - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + + std::ostringstream outstr; + outstr << " Found valid UBD: " << objectiveValue << std::endl + << " UBP solution point: " << std::endl; + _logger->print_vector(_nvar, currentPoint, outstr.str(), VERB_ALL, UBP_VERBOSITY); + + return SUBSOLVER_FEASIBLE; } else { - _logger->print_message(" Warning: found point that is feasible but returns objective that is NaN.", _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(" Warning: found point that is feasible but returns objective that is NaN.", VERB_ALL, UBP_VERBOSITY); } } } @@ -947,62 +957,141 @@ UpperBoundingSolver::check_feasibility(const std::vector<double> ¤tPoint, } +#ifdef HAVE_GROWING_DATASETS +///////////////////////////////////////////////////////////////////////// +// passes index of new dataset to respective DagObj routine +void +UpperBoundingSolver::change_growing_objective(const unsigned int indexDataset) +{ + _DAGobj->change_growing_objective(indexDataset); +} + + +///////////////////////////////////////////////////////////////////////// +// passes index of new dataset to respective DagObj routine +void +UpperBoundingSolver::pass_data_position_to_solver(const std::shared_ptr<std::vector<std::set<unsigned int>>> datasetsIn, const unsigned int indexFirstDataIn) +{ + _DAGobj->datasets = datasetsIn; + _DAGobj->indexFirstData = indexFirstDataIn; +} + + +#ifdef HAVE_MAiNGO_MPI +///////////////////////////////////////////////////////////////////////////////////////////// +// update dataset vector and add respective subgraph after adding new dataset +// needed to ensure correct order of subgraphs in storedSubgraphs in parallel version +void +UpperBoundingSolver::update_datasets_and_storedSubgraphs(std::set<unsigned int> &newDataset) +{ + size_t ndataNew = newDataset.size(); + + bool datasetExists = false; + for (int i = (*_DAGobj->datasets).size() - 1; i > -1; i--) { + if ((*_DAGobj->datasets)[i].size() == ndataNew) { + datasetExists = true; + break; + } + } + if (!datasetExists) { + (*_DAGobj->datasets).push_back(newDataset); + } + + if (_DAGobj->storedSubgraph.size() != (*_DAGobj->datasets).size()) { + // If we do not have a subgraph for each dataset + + for (auto idx = _DAGobj->storedSubgraph.size(); idx < (*_DAGobj->datasets).size(); idx++) { + _DAGobj->add_subgraph_for_new_dataset(idx); + } + } +} +#endif //HAVE_MAiNGO_MPI +#endif //HAVE_GROWING_DATASETS + + ///////////////////////////////////////////////////////////////////////// // sets function properties, number of variables and type (linear, bilinear...) void UpperBoundingSolver::_determine_structure() { - _structure = UbpStructure(); - _determine_sparsity_jacobian(); - const mc::FFVar *jacobian = _DAGobj->DAG.FAD(_DAGobj->functions.size(), _DAGobj->functions.data(), _nvar, _DAGobj->vars.data()); - std::vector<std::map<int, int>> funcDep; - funcDep.resize(_DAGobj->functions.size() * _nvar); // Jacobian has #funcs * #vars entries - _structure.jacProperties.resize(_DAGobj->functions.size(), std::vector<std::pair<std::vector<unsigned>, CONSTRAINT_DEPENDENCY>>(_nvar)); - for (unsigned int i = 0; i < funcDep.size(); i++) { - funcDep[i] = jacobian[i].dep().dep(); + bool allFunctionsLinear = true; + for (const Constraint &a : *_constraintProperties) { + if (a.dependency != CONSTRAINT_DEPENDENCY::LINEAR) { + allFunctionsLinear = false; + break; + } } - unsigned int jacIndex = 0; - // Loop over all functions in the Jacobian, there are #funcs * #vars many - for (unsigned int jacFuncs = 0; jacFuncs < _DAGobj->functions.size(); jacFuncs++) { - for (unsigned int iVars = 0; iVars < _nvar; iVars++) { - std::vector<unsigned> participatingVars; - mc::FFDep::TYPE functionStructure = mc::FFDep::L; - for (unsigned int k = 0; k < _nvar; k++) { - auto ito = funcDep[jacIndex].find(k); - // Count all participating variables - if (ito != funcDep[jacIndex].end()) { - participatingVars.push_back(k); - mc::FFDep::TYPE variableDep = (mc::FFDep::TYPE)(ito->second); - // Update function type - if (functionStructure < variableDep) { - functionStructure = variableDep; + if (allFunctionsLinear) { + for (unsigned int jacFuncs = 0; jacFuncs < _DAGobj->functions.size(); jacFuncs++) { + for (unsigned int iVars = 0; iVars < _nvar; iVars++) { + std::vector<unsigned> pseudoParticipating(_nvar); + std::iota(pseudoParticipating.begin(), pseudoParticipating.end(), 0); //Fill with 0...nVar + _structure.jacProperties.resize(_DAGobj->functions.size(), std::vector<std::pair<std::vector<unsigned>, CONSTRAINT_DEPENDENCY>>(_nvar)); + _structure.jacProperties[jacFuncs][iVars] = std::make_pair(pseudoParticipating, LINEAR); + } + } + } + else { + + + const mc::FFVar *jacobian = _DAGobj->DAG.FAD(_DAGobj->functions.size(), _DAGobj->functions.data(), _nvar, _DAGobj->vars.data()); + std::vector<std::map<int, int>> funcDep; + funcDep.resize(_DAGobj->functions.size() * _nvar); // Jacobian has #funcs * #vars entries + _structure.jacProperties.resize(_DAGobj->functions.size(), std::vector<std::pair<std::vector<unsigned>, CONSTRAINT_DEPENDENCY>>(_nvar)); + for (unsigned int i = 0; i < funcDep.size(); i++) { + funcDep[i] = jacobian[i].dep().dep(); + } + + unsigned int jacIndex = 0; + // Loop over all functions in the Jacobian, there are #funcs * #vars many + for (unsigned int jacFuncs = 0; jacFuncs < _DAGobj->functions.size(); jacFuncs++) { + for (unsigned int iVars = 0; iVars < _nvar; iVars++) { + std::vector<unsigned> participatingVars; + mc::FFDep::TYPE functionStructure = mc::FFDep::L; + for (unsigned int k = 0; k < _nvar; k++) { + auto ito = funcDep[jacIndex].find(k); + // Count all participating variables + if (ito != funcDep[jacIndex].end()) { + participatingVars.push_back(k); + mc::FFDep::TYPE variableDep = (mc::FFDep::TYPE)(ito->second); + // Update function type + if (functionStructure < variableDep) { + functionStructure = variableDep; + } } } + switch (functionStructure) { + case mc::FFDep::L: + _structure.jacProperties[jacFuncs][iVars] = std::make_pair(participatingVars, LINEAR); + break; + case mc::FFDep::B: + case mc::FFDep::Q: + case mc::FFDep::P: + case mc::FFDep::R: + case mc::FFDep::N: + case mc::FFDep::D: + default: + _structure.jacProperties[jacFuncs][iVars] = std::make_pair(participatingVars, NONLINEAR); + break; + } + jacIndex++; } - switch (functionStructure) { - case mc::FFDep::L: - _structure.jacProperties[jacFuncs][iVars] = std::make_pair(participatingVars, LINEAR); - break; - case mc::FFDep::B: - case mc::FFDep::Q: - case mc::FFDep::P: - case mc::FFDep::R: - case mc::FFDep::N: - case mc::FFDep::D: - default: - _structure.jacProperties[jacFuncs][iVars] = std::make_pair(participatingVars, NONLINEAR); - break; - } - jacIndex++; } - } - delete[] jacobian; - _determine_sparsity_hessian(); + delete[] jacobian; + } + if (allFunctionsLinear) { + _structure.nnonZeroHessian = 0; + _structure.nonZeroHessianIRow.clear(); + _structure.nonZeroHessianJCol.clear(); + } + else { + _determine_sparsity_hessian(); + } } diff --git a/src/ubpClp.cpp b/src/ubpClp.cpp index f7f630e..2b160b6 100644 --- a/src/ubpClp.cpp +++ b/src/ubpClp.cpp @@ -12,8 +12,8 @@ #include "ubpClp.h" #include "MAiNGOException.h" #include "ubpDagObj.h" +#include "ubpLazyQuadExpr.h" #include "ubpQuadExpr.h" - #include <limits> @@ -113,33 +113,34 @@ UbpClp::_solve_nlp(const std::vector<double> &lowerVarBounds, const std::vector< // Add coefficients and right-hand sides to the model size_t irow = 0; for (size_t i = 0; i < _constraintProperties->size(); i++) { - const size_t index = (*_constraintProperties)[i].indexNonconstant; + const size_t index = (*_constraintProperties)[i].indexNonconstant; + QuadExpr evaluatedResult = resultCoefficients[index].assemble_quadratic_expression_matrix_wise(_nvar); switch ((*_constraintProperties)[i].type) { case OBJ: - _objectiveConstant = resultCoefficients[index].constant; + _objectiveConstant = evaluatedResult.linearPart.constant(); for (size_t k = 0; k < _nvar; k++) { - _objectiveCoeffs[k] = resultCoefficients[index].coeffsLin[k]; + _objectiveCoeffs[k] = evaluatedResult.linearPart.get_value(k); } break; case INEQ: - _upperRowBounds[irow] = -resultCoefficients[index].constant; + _upperRowBounds[irow] = -evaluatedResult.linearPart.constant(); for (size_t k = 0; k < _nvar; k++) { - constraintCoeffs[k * _numrows + irow] = resultCoefficients[index].coeffsLin[k]; + constraintCoeffs[k * _numrows + irow] = evaluatedResult.linearPart.get_value(k); } irow++; break; case EQ: - _lowerRowBounds[irow] = -resultCoefficients[index].constant; - _upperRowBounds[irow] = -resultCoefficients[index].constant; + _lowerRowBounds[irow] = -evaluatedResult.linearPart.constant(); + _upperRowBounds[irow] = -evaluatedResult.linearPart.constant(); for (size_t k = 0; k < _nvar; k++) { - constraintCoeffs[k * _numrows + irow] = resultCoefficients[index].coeffsLin[k]; + constraintCoeffs[k * _numrows + irow] = evaluatedResult.linearPart.get_value(k); } irow++; break; case INEQ_SQUASH: - _upperRowBounds[irow] = -resultCoefficients[index].constant; + _upperRowBounds[irow] = -evaluatedResult.linearPart.constant(); for (size_t k = 0; k < _nvar; k++) { - constraintCoeffs[k * _numrows + irow] = resultCoefficients[index].coeffsLin[k]; + constraintCoeffs[k * _numrows + irow] = evaluatedResult.linearPart.get_value(k); } irow++; break; @@ -188,7 +189,7 @@ UbpClp::_solve_nlp(const std::vector<double> &lowerVarBounds, const std::vector< if (_maingoSettings->LBP_verbosity >= VERB_ALL) { std::ostringstream outstr; outstr << " UBP status: " << clpStatus << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); } // Get objective value @@ -202,7 +203,7 @@ UbpClp::_solve_nlp(const std::vector<double> &lowerVarBounds, const std::vector< catch (std::exception &e) { std::ostringstream outstr; outstr << " Warning: Variables at solution of UBP could not be extracted from CLP:" << e.what() << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); // Return empty solution instead solutionPoint.clear(); return SUBSOLVER_FEASIBLE; @@ -217,7 +218,7 @@ UbpClp::_solve_nlp(const std::vector<double> &lowerVarBounds, const std::vector< for (unsigned int i = 0; i < _nvar; i++) { outstr << " x(" << i << "): " << solutionPoint[i] << std::endl; } - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); return SUBSOLVER_FEASIBLE; } \ No newline at end of file diff --git a/src/ubpCplex.cpp b/src/ubpCplex.cpp index cd040e3..e0576b6 100644 --- a/src/ubpCplex.cpp +++ b/src/ubpCplex.cpp @@ -57,14 +57,36 @@ UbpCplex::UbpCplex(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, cons // Add functions to the model for (size_t i = 0; i < _constraintProperties->size(); i++) { unsigned index = (*_constraintProperties)[i].indexNonconstant; +#ifdef LAZYQUAD + ///QuadExpr constructedResult=resultCoefficients[index].assemble_quadratic_expression_element_wise(_nvar); + QuadExpr constructedResult = resultCoefficients[index].assemble_quadratic_expression_matrix_wise(_nvar); +#endif IloExpr expr(cplxEnv); - for (size_t k = 0; k < resultCoefficients[index].nvar; k++) { - expr += resultCoefficients[index].coeffsLin[k] * cplxVars[k]; - for (size_t j = 0; j < resultCoefficients[index].nvar; j++) { - expr += resultCoefficients[index].coeffsQuad[k][j] * cplxVars[k] * cplxVars[j]; + for (size_t k = 0; k < _nvar; k++) { +#ifdef LAZYQUAD + double linearCoeff = constructedResult.linearPart.get_value(k); +#else + double linearCoeff = resultCoefficients[index].coeffsLin[k]; +#endif + if (linearCoeff != 0.0) { + expr += linearCoeff * cplxVars[k]; + } + for (size_t j = 0; j < _nvar; j++) { +#ifdef LAZYQUAD + double quadCoeff = constructedResult.quadraticPart.get_element(k, j); +#else + double quadCoeff = resultCoefficients[index].coeffsQuad[k][j]; +#endif + if (quadCoeff != 0.0) { + expr += quadCoeff * cplxVars[k] * cplxVars[j]; + } } } +#ifdef LAZYQUAD + expr += constructedResult.linearPart.constant(); +#else expr += resultCoefficients[index].constant; +#endif switch ((*_constraintProperties)[i].type) { case OBJ: cplxModel.add(IloMinimize(cplxEnv, expr)); @@ -95,7 +117,11 @@ UbpCplex::UbpCplex(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, cons cplex.setParam(IloCplex::Param::Barrier::ConvergeTol, _maingoSettings->epsilonA); cplex.setParam(IloCplex::Param::Barrier::QCPConvergeTol, _maingoSettings->epsilonA); cplex.setParam(IloCplex::EpRHS, std::max(_maingoSettings->deltaIneq, _maingoSettings->deltaEq)); - cplex.setParam(IloCplex::Param::RandomSeed, 42); // Make the behavior of CPLEX deterministic + cplex.setParam(IloCplex::Param::TimeLimit, _maingoSettings->maxTime); // Preprocessing not considered + cplex.setParam(IloCplex::EpAGap, _maingoSettings->epsilonA); // Absolute gap for MILP case + cplex.setParam(IloCplex::EpGap, _maingoSettings->epsilonR); // Relative gap for MILP case + cplex.setParam(IloCplex::Param::RandomSeed, 42); // Make the behavior of CPLEX deterministic + // Suppress output: // Suppress output - unfortunately we cannot redirect the output of CPLEX to our log file right now... if ((_maingoSettings->LBP_verbosity <= VERB_NORMAL) || (_maingoSettings->loggingDestination == LOGGING_NONE) || (_maingoSettings->loggingDestination == LOGGING_FILE)) { @@ -142,7 +168,7 @@ UbpCplex::_solve_nlp(const std::vector<double> &lowerVarBounds, const std::vecto if (_maingoSettings->LBP_verbosity >= VERB_ALL) { std::ostringstream outstr; outstr << " UBP status: " << cplexStatus << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); } // Get objective value @@ -172,7 +198,7 @@ UbpCplex::_solve_nlp(const std::vector<double> &lowerVarBounds, const std::vecto if (isFeasible != SUBSOLVER_FEASIBLE) { std::ostringstream outstr; outstr << " Warning: Variables at solution of UBP could not be extracted from CPLEX:" << e << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_NORMAL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_NORMAL, UBP_VERBOSITY); // Return empty solution instead vals.end(); solutionPoint.clear(); @@ -192,12 +218,7 @@ UbpCplex::_solve_nlp(const std::vector<double> &lowerVarBounds, const std::vecto } vals.end(); - std::ostringstream outstr; - outstr << " UBP solution point: " << std::endl; - for (unsigned int i = 0; i < _nvar; i++) { - outstr << " x(" << i << "): " << solutionPoint[i] << std::endl; - } - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_vector(_nvar, solutionPoint, " UBP solution point: ", VERB_ALL, UBP_VERBOSITY); return SUBSOLVER_FEASIBLE; } diff --git a/src/ubpFactory.cpp b/src/ubpFactory.cpp index 4717e8a..ccc467b 100644 --- a/src/ubpFactory.cpp +++ b/src/ubpFactory.cpp @@ -55,32 +55,32 @@ ubp::make_ubp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, co switch (desiredSolver) { case UBP_SOLVER_EVAL: { - loggerIn->print_message(" " + useDescription + ": Function evaluation\n", settingsIn->BAB_verbosity, VERB_NORMAL, settingsIn->loggingDestination); + loggerIn->print_message(" " + useDescription + ": Function evaluation\n", VERB_NORMAL, BAB_VERBOSITY); return std::make_shared<UpperBoundingSolver>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn); } case UBP_SOLVER_COBYLA: { - loggerIn->print_message(" " + useDescription + ": COBYLA\n", settingsIn->BAB_verbosity, VERB_NORMAL, settingsIn->loggingDestination); + loggerIn->print_message(" " + useDescription + ": COBYLA\n", VERB_NORMAL, BAB_VERBOSITY); return std::make_shared<UbpNLopt>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn); } case UBP_SOLVER_BOBYQA: { - loggerIn->print_message(" " + useDescription + ": BOBYQA\n", settingsIn->BAB_verbosity, VERB_NORMAL, settingsIn->loggingDestination); + loggerIn->print_message(" " + useDescription + ": BOBYQA\n", VERB_NORMAL, BAB_VERBOSITY); return std::make_shared<UbpNLopt>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn); } case UBP_SOLVER_LBFGS: { - loggerIn->print_message(" " + useDescription + ": LBFGS\n", settingsIn->BAB_verbosity, VERB_NORMAL, settingsIn->loggingDestination); + loggerIn->print_message(" " + useDescription + ": LBFGS\n", VERB_NORMAL, BAB_VERBOSITY); return std::make_shared<UbpNLopt>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn); } case UBP_SOLVER_SLSQP: { - loggerIn->print_message(" " + useDescription + ": SLSQP\n", settingsIn->BAB_verbosity, VERB_NORMAL, settingsIn->loggingDestination); + loggerIn->print_message(" " + useDescription + ": SLSQP\n", VERB_NORMAL, BAB_VERBOSITY); return std::make_shared<UbpNLopt>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn); } case UBP_SOLVER_IPOPT: { - loggerIn->print_message(" " + useDescription + ": IPOPT\n", settingsIn->BAB_verbosity, VERB_NORMAL, settingsIn->loggingDestination); + loggerIn->print_message(" " + useDescription + ": IPOPT\n", VERB_NORMAL, BAB_VERBOSITY); return std::make_shared<UbpIpopt>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn); } case UBP_SOLVER_KNITRO: { #ifdef HAVE_KNITRO - loggerIn->print_message(" " + useDescription + ": KNITRO\n", settingsIn->BAB_verbosity, VERB_NORMAL, settingsIn->loggingDestination); + loggerIn->print_message(" " + useDescription + ": KNITRO\n", VERB_NORMAL, BAB_VERBOSITY); return std::make_shared<UbpKnitro>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn); #else throw MAiNGOException(" Error in UbpFactory: Cannot use upper bounding strategy UBP_SOLVER_KNITRO: Your MAiNGO build does not contain KNITRO."); @@ -96,8 +96,7 @@ ubp::make_ubp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, co case UBP_SOLVER_CLP: { return std::make_shared<UbpClp>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn); } - default: - { + default: { std::ostringstream errmsg; errmsg << " Error in UbpFactory: Unknown upper bounding strategy: " << desiredSolver << std::endl; throw MAiNGOException(" Error in UbpFactory: Unknown upper bounding strategy: " + std::to_string(desiredSolver)); diff --git a/src/ubpIpopt.cpp b/src/ubpIpopt.cpp index 7f0a6cb..84e2952 100644 --- a/src/ubpIpopt.cpp +++ b/src/ubpIpopt.cpp @@ -77,8 +77,7 @@ UbpIpopt::UbpIpopt(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, cons _Ipopt->Options()->SetNumericValue("max_cpu_time", _maingoSettings->UBP_maxTimePreprocessing); break; } - default: - { + default: { throw " Unknown USAGE setting " + std::to_string(_intendedUse); } } @@ -110,11 +109,11 @@ UbpIpopt::_solve_nlp(const std::vector<double> &lowerVarBounds, const std::vecto // Run optimization Ipopt::ApplicationReturnStatus status = _Ipopt->OptimizeTNLP(_theIpoptProblem); - if (_maingoSettings->UBP_verbosity >= VERB_ALL) { - std::ostringstream outstr; - outstr << " Ipopt status: " << status << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + + std::ostringstream outstr; + outstr << " Ipopt status: " << status << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); + if (status == Ipopt::ApplicationReturnStatus::Internal_Error) { throw MAiNGOException(" An unknown internal error occurred within Ipopt. Please contact Ipopt mailing list."); } @@ -123,19 +122,17 @@ UbpIpopt::_solve_nlp(const std::vector<double> &lowerVarBounds, const std::vecto } } catch (const std::exception &e) { - if (_maingoSettings->UBP_verbosity >= VERB_ALL) { - std::ostringstream outstr; - outstr << " Warning: Local optimization using Ipopt failed. Continuing without a feasible point (unless initial point happens to be feasible)." << std::endl; - outstr << " Reason: " << e.what() << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + + std::ostringstream outstr; + outstr << " Warning: Local optimization using Ipopt failed. Continuing without a feasible point (unless initial point happens to be feasible)." << std::endl; + outstr << " Reason: " << e.what() << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); } catch (...) { - if (_maingoSettings->UBP_verbosity >= VERB_ALL) { - std::ostringstream outstr; - outstr << " Warning: Local optimization using Ipopt failed. Continuing without a feasible point (unless initial point happens to be feasible)." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); - } + + std::ostringstream outstr; + outstr << " Warning: Local optimization using Ipopt failed. Continuing without a feasible point (unless initial point happens to be feasible)." << std::endl; + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); } // Check if point returned by local solver is actually feasible. If it is, the objective function value will be stored as well. diff --git a/src/ubpKnitro.cpp b/src/ubpKnitro.cpp index 3ee5f25..d2ee783 100644 --- a/src/ubpKnitro.cpp +++ b/src/ubpKnitro.cpp @@ -70,8 +70,7 @@ UbpKnitro::UbpKnitro(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, co _Knitro.setParam("maxtime_cpu", _maingoSettings->UBP_maxTimePreprocessing); break; } - default: - { + default: { std::ostringstream errmsg; errmsg << " Unknown USAGE setting " << _intendedUse << std::endl; throw MAiNGOException(errmsg.str()); @@ -104,7 +103,7 @@ UbpKnitro::_solve_nlp(const std::vector<double> &lowerVarBounds, const std::vect if (_maingoSettings->UBP_verbosity >= VERB_ALL) { std::ostringstream outstr; outstr << "Knitro status: " << _solverStatus << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); } if (_solverStatus <= -500) { throw MAiNGOException(" An unknown internal error occurred within Knitro. Please contact Knitro mailing list."); @@ -123,14 +122,14 @@ UbpKnitro::_solve_nlp(const std::vector<double> &lowerVarBounds, const std::vect std::ostringstream outstr; outstr << " Warning: Local optimization using Knitro failed. Continuing without a feasible point (unless initial point happens to be feasible)." << std::endl; outstr << " Reason: " << e.what() << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); } } catch (...) { if (_maingoSettings->UBP_verbosity >= VERB_ALL) { std::ostringstream outstr; outstr << " Warning: Local optimization using Knitro failed. Continuing without a feasible point (unless initial point happens to be feasible)." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); } } diff --git a/src/ubpNLopt.cpp b/src/ubpNLopt.cpp index 3c3fe19..6f0d29a 100644 --- a/src/ubpNLopt.cpp +++ b/src/ubpNLopt.cpp @@ -64,8 +64,7 @@ UbpNLopt::UbpNLopt(mc::FFGraph& DAG, const std::vector<mc::FFVar>& DAGvars, cons _NLopt.set_local_optimizer(_NLoptSubopt); break; } - default: - { + default: { throw MAiNGOException(" Unknown upper bounding solver selected for usage " + std::to_string(_intendedUse) + ": " + std::to_string(desiredSolver)); } } @@ -107,8 +106,7 @@ UbpNLopt::UbpNLopt(mc::FFGraph& DAG, const std::vector<mc::FFVar>& DAGvars, cons _NLoptSubopt.set_maxeval(_maingoSettings->UBP_maxStepsPreprocessing); _NLoptSubopt.set_maxtime(_maingoSettings->UBP_maxTimePreprocessing); break; - default: - { + default: { throw MAiNGOException(" Unknown USAGE setting " + std::to_string(_intendedUse)); } } @@ -137,7 +135,7 @@ UbpNLopt::_solve_nlp(const std::vector<double>& lowerVarBounds, const std::vecto if (_maingoSettings->UBP_verbosity >= VERB_ALL) { std::ostringstream outstr; outstr << " Status of local optimization: " << solveStatus << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); } } catch (const std::exception& e) { @@ -145,7 +143,7 @@ UbpNLopt::_solve_nlp(const std::vector<double>& lowerVarBounds, const std::vecto std::ostringstream outstr; outstr << " Warning: Local optimization using NLOPT failed. Continuing without a feasible point (unless last point happens to be feasible)." << std::endl; outstr << " Reason: " << e.what() << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); } if (solutionPoint.size() != _nvar) { return SUBSOLVER_INFEASIBLE; @@ -155,7 +153,7 @@ UbpNLopt::_solve_nlp(const std::vector<double>& lowerVarBounds, const std::vecto if (_maingoSettings->UBP_verbosity >= VERB_ALL) { std::ostringstream outstr; outstr << " Warning: Local optimization using NLOPT failed. Continuing without a feasible point (unless last point happens to be feasible)." << std::endl; - _logger->print_message(outstr.str(), _maingoSettings->UBP_verbosity, VERB_ALL, _maingoSettings->loggingDestination); + _logger->print_message(outstr.str(), VERB_ALL, UBP_VERBOSITY); } if (solutionPoint.size() != _nvar) { return SUBSOLVER_INFEASIBLE; diff --git a/tests/cApi/main.cpp b/tests/cApi/main.cpp new file mode 100644 index 0000000..57dfd03 --- /dev/null +++ b/tests/cApi/main.cpp @@ -0,0 +1,49 @@ +#include "cApi.h" +#include <exception> +#include <iostream> +#include <sstream> +#include <vector> + +int +main() +{ + + std::stringstream input; + input << "definitions:\n"; + input << "real x in [-10, 10];\n"; + input << "set{index} kdisc := {};\n"; + input << "real[0] ydisc := ();\n"; + input << "objective:\n"; + input << "x;\n"; + input << "constraints:\n"; + input << "x >= -1;\n"; + input << "forall k in kdisc : x >= ydisc[k];\n"; + double obj_val, cpu, wall, ub, lb; + std::vector<double> sol = {0.0}; + OptionPair p{}; + std::string file, log, settings; + file = "result.txt"; + log = "log.txt"; + settings = "settings.txt"; + + try { + int a = solve_problem_from_ale_string_with_maingo(input.str().c_str(), &obj_val, sol.data(), 1, &cpu, &wall, + &ub, &lb, file.c_str(), log.c_str(), settings.c_str(), &p, 0); + } + catch (std::exception e) { + std::cerr << std::endl + << "Encountered an unknown fatal error C-API test. Terminating." << std::endl; + return -1; + } + + if (obj_val == sol[0] && obj_val<-0.999 & obj_val> - 1.001) { + std::cout << "Correct solution: -1" << std::endl; + std::cout << "LB:" << lb << " UB:" << ub << std::endl; + return 0; + } + else { + std::cout << "Wrong solution. Correct would be: -1" << std::endl; + std::cout << "LB:" << lb << " UB:" << ub << std::endl; + return -1; + } +} \ No newline at end of file diff --git a/tests/testProblems/libraries/HenryComponent.h b/tests/testProblems/libraries/HenryComponent.h index 6f0c587..d6028a1 100644 --- a/tests/testProblems/libraries/HenryComponent.h +++ b/tests/testProblems/libraries/HenryComponent.h @@ -13,6 +13,8 @@ #ifndef HENRYCOMPONENT_H_ #define HENRYCOMPONENT_H_ +#include "usingAdditionalIntrinsicFunctions.h" + #include "PureComponent.h" diff --git a/tests/testProblems/libraries/IdealLiquidStream.h b/tests/testProblems/libraries/IdealLiquidStream.h index 810f309..dbc4e7d 100644 --- a/tests/testProblems/libraries/IdealLiquidStream.h +++ b/tests/testProblems/libraries/IdealLiquidStream.h @@ -12,6 +12,9 @@ #ifndef IDEALLIQUIDSTREAM_H_ #define IDEALLIQUIDSTREAM_H_ + +#include "usingAdditionalIntrinsicFunctions.h" + #include "IdealFluid.h" using T = mc::FFVar; diff --git a/tests/testProblems/libraries/NRTL.h b/tests/testProblems/libraries/NRTL.h index 356e0d5..da5bcba 100644 --- a/tests/testProblems/libraries/NRTL.h +++ b/tests/testProblems/libraries/NRTL.h @@ -11,6 +11,8 @@ #pragma once +#include "usingAdditionalIntrinsicFunctions.h" + #include <iostream> #define NRTL_ENV // use this to set whether to use envelopes for nrtl functions (tau,G,Gtau,dGtau,Gdtau) or not -- TO USE THIS IT HAS TO HOLD THAT d = 0 diff --git a/tests/testProblems/libraries/PureComponent.h b/tests/testProblems/libraries/PureComponent.h index 0a1be5a..fce7be6 100644 --- a/tests/testProblems/libraries/PureComponent.h +++ b/tests/testProblems/libraries/PureComponent.h @@ -13,7 +13,7 @@ #ifndef PURECOMPONENT_H_ #define PURECOMPONENT_H_ -using namespace mc; +#include "usingAdditionalIntrinsicFunctions.h" /** diff --git a/tests/testProblems/libraries/SubcriticalComponent.h b/tests/testProblems/libraries/SubcriticalComponent.h index 22b0832..ea55e16 100644 --- a/tests/testProblems/libraries/SubcriticalComponent.h +++ b/tests/testProblems/libraries/SubcriticalComponent.h @@ -15,6 +15,9 @@ #include "PureComponent.h" +#include "usingAdditionalIntrinsicFunctions.h" + + /** * @class SubcriticalComponent * @brief Class representing a pure chemical component below (or only "somewhat" above) its critical point. @@ -199,9 +202,9 @@ SubcriticalComponent<U>::calculate_vaporization_enthalpy(const U &T) const U Tr = T / PureComponent<U>::_Tc; switch (_dhvapModel) { case DHVAP_WATSON: - return _paramsDhvap[4] * pow(max(1 - Tr, machprec()) / (1 - _paramsDhvap[3] / PureComponent<U>::_Tc), _paramsDhvap[1] + _paramsDhvap[2] * (1 - Tr)); + return _paramsDhvap[4] * pow(max(1 - Tr, mc::machprec()) / (1 - _paramsDhvap[3] / PureComponent<U>::_Tc), _paramsDhvap[1] + _paramsDhvap[2] * (1 - Tr)); case DHVAP_DIPPR106: - return _paramsDhvap[1] * pow(max(1 - Tr, machprec()), _paramsDhvap[2] + _paramsDhvap[3] * Tr + _paramsDhvap[4] * pow(Tr, 2) + _paramsDhvap[5] * pow(Tr, 3)); + return _paramsDhvap[1] * pow(max(1 - Tr, mc::machprec()), _paramsDhvap[2] + _paramsDhvap[3] * Tr + _paramsDhvap[4] * pow(Tr, 2) + _paramsDhvap[5] * pow(Tr, 3)); case DHVAP_UNDEF: throw(std::runtime_error("Error: No enthalpy of vaporization model specified.")); default: diff --git a/tests/testProblems/main.cpp b/tests/testProblems/main.cpp index fe7bdf1..92ba170 100644 --- a/tests/testProblems/main.cpp +++ b/tests/testProblems/main.cpp @@ -11,17 +11,24 @@ #include "mpiUtilities.h" + #include "problem_Henry_RS_IdealGasFlash.h" #include "problem_LP.h" +#include "problem_LP_random.h" #include "problem_MILP.h" #include "problem_NRTL_RS_Flash.h" #include "problem_OME_RS_IdealGasFlash.h" +#include "problem_QP.h" #include "problem_bin1.h" #include "problem_case1_lcoe.h" #include "problem_case2_lcoe.h" #include "problem_case3_wnet.h" #include "problem_ex8_1_3.h" +#include "problem_growingDatasets_AVM.h" +#include "problem_growingDatasets_simple.h" #include "problem_int1.h" +#include "problem_nonsmooth.h" +#include "problem_sudoku.h" #include "problem_unusedVars.h" #include "MAiNGO.h" @@ -38,6 +45,12 @@ #include <string> +#if defined(HAVE_CPLEX) || defined(HAVE_GUROBI) +#define HAVE_QP_SOLVER 1 +#define HAVE_MILP_SOLVER 1 +#endif + + bool print_testResults(std::shared_ptr<maingo::MAiNGO> theMAiNGO, maingo::RETCODE maingoStatus, const double correctSolution, const double epsilonA, const double epsilonR, double &CPUofAllProcesses) { @@ -59,7 +72,6 @@ print_testResults(std::shared_ptr<maingo::MAiNGO> theMAiNGO, maingo::RETCODE mai else { if (std::fabs(theMAiNGO->get_objective_value() - correctSolution) > epsilonA) { correctA = false; - } if ((std::fabs(theMAiNGO->get_objective_value() - correctSolution)) > epsilonR * std::fabs(theMAiNGO->get_objective_value())) { correctR = false; @@ -94,9 +106,8 @@ run_test(std::shared_ptr<maingo::MAiNGO> theMAiNGO, const std::string &name, con #endif std::cout << std::left << std::setw(40) << name << ": "; - const maingo::RETCODE maingoStatus = theMAiNGO->solve(); - MAiNGO_MPI_BARRIER - const bool default_success = print_testResults(theMAiNGO, maingoStatus, correctSolution, epsilonA, epsilonR, CPUofAllProcesses); + const maingo::RETCODE maingoStatus = theMAiNGO->solve(); + MAiNGO_MPI_BARRIER const bool default_success = print_testResults(theMAiNGO, maingoStatus, correctSolution, epsilonA, epsilonR, CPUofAllProcesses); #ifdef HAVE_MAiNGO_PARSER MAiNGO_IF_BAB_MANAGER @@ -136,12 +147,11 @@ run_test(std::shared_ptr<maingo::MAiNGO> theMAiNGO, const std::string &name, con std::cout << std::left << std::setw(40) << name + " (parsed)" << ": "; const maingo::RETCODE parsed_maingoStatus = theMAiNGO->solve(); - const bool parsed_success = print_testResults(theMAiNGO, parsed_maingoStatus, correctSolution, epsilonA, epsilonR, CPUofAllProcesses); + const bool parsed_success = print_testResults(theMAiNGO, parsed_maingoStatus, correctSolution, epsilonA, epsilonR, CPUofAllProcesses); return (default_success && parsed_success); #else return default_success; #endif - } @@ -166,6 +176,7 @@ main(int argc, char *argv[]) // Define model and MAiNGO objects std::shared_ptr<Model_bin1> myModel_bin1; std::shared_ptr<Model_int1> myModel_int1; + std::shared_ptr<Model_nonsmooth> myModel_nonsmooth; std::shared_ptr<Model_case1_lcoe> myModel_case1_lcoe; std::shared_ptr<Model_case2_lcoe> myModel_case2_lcoe; std::shared_ptr<Model_case3_wnet> myModel_case3_wnet; @@ -175,27 +186,38 @@ main(int argc, char *argv[]) std::shared_ptr<Model_OME_RS_IdealGasFlash> myModel_OME_RS_IdealGasFlash; std::shared_ptr<Model_unusedVars> myModel_unusedVars; std::shared_ptr<Model_LP> myModel_LP; + std::shared_ptr<Model_QP> myModel_QP; + std::shared_ptr<Model_LP_random> myModel_LP_random; std::shared_ptr<Model_MILP> myModel_MILP; + std::shared_ptr<Model_MILP_sudoku> myModel_MILP_sudoku; + std::shared_ptr<Model_growing_simple> myModel_growing_simple; + std::shared_ptr<Model_growing_AVM> myModel_growing_AVM; + std::shared_ptr<maingo::MAiNGO> myMAiNGO; try { - myModel_bin1 = std::make_shared<Model_bin1>(); // Define a model object implemented in problem_bin1.h - myModel_int1 = std::make_shared<Model_int1>(); // Define a model object implemented in problem_int1.h - myModel_case1_lcoe = std::make_shared<Model_case1_lcoe>(); // Define a model object implemented in problem_case1_lcoe.h - myModel_case2_lcoe = std::make_shared<Model_case2_lcoe>(); // Define a model object implemented in problem_case2_lcoe.h - myModel_case3_wnet = std::make_shared<Model_case3_wnet>(); // Define a model object implemented in problem_case3_wnet.h - myModel_ex8_1_3 = std::make_shared<Model_ex8_1_3>(); // Define a model object implemented in problem_ex8_1_3.h - myModel_Henry_RS_IdealGasFlash = std::make_shared<Model_Henry_RS_IdealGasFlash>(); // Define a model object implemented in problem_Henry_RS_IdealGasFlash.h - myModel_NRTL_RS_Flash = std::make_shared<Model_NRTL_RS_Flash>(); // Define a model object implemented in problem_NRTL_RS_Flash.h - myModel_OME_RS_IdealGasFlash = std::make_shared<Model_OME_RS_IdealGasFlash>(); // Define a model object implemented in problem_OME_RS_IdealGasFlash.h - myModel_unusedVars = std::make_shared<Model_unusedVars>(); // Define a model object implemented in problem_unusedVars.h - myModel_LP = std::make_shared<Model_LP>(); // Define a model object implemented in problem_LP.h - myModel_MILP = std::make_shared<Model_MILP>(); // Define a model object implemented in problem_MILP.h - + myModel_bin1 = std::make_shared<Model_bin1>(); + myModel_int1 = std::make_shared<Model_int1>(); + myModel_nonsmooth = std::make_shared<Model_nonsmooth>(); + myModel_case1_lcoe = std::make_shared<Model_case1_lcoe>(); + myModel_case2_lcoe = std::make_shared<Model_case2_lcoe>(); + myModel_case3_wnet = std::make_shared<Model_case3_wnet>(); + myModel_ex8_1_3 = std::make_shared<Model_ex8_1_3>(); + myModel_Henry_RS_IdealGasFlash = std::make_shared<Model_Henry_RS_IdealGasFlash>(); + myModel_NRTL_RS_Flash = std::make_shared<Model_NRTL_RS_Flash>(); + myModel_OME_RS_IdealGasFlash = std::make_shared<Model_OME_RS_IdealGasFlash>(); + myModel_unusedVars = std::make_shared<Model_unusedVars>(); + myModel_LP = std::make_shared<Model_LP>(); + myModel_MILP = std::make_shared<Model_MILP>(); + myModel_QP = std::make_shared<Model_QP>(); + myModel_LP_random = std::make_shared<Model_LP_random>(50, 1); + myModel_MILP_sudoku = std::make_shared<Model_MILP_sudoku>(); + myModel_growing_simple = std::make_shared<Model_growing_simple>(); // Define a model object implemented in problem_growingDatasets_simple.h + myModel_growing_AVM = std::make_shared<Model_growing_AVM>(); // Define a model object implemented in problem_growingDatasets_AVM.h // Start with problem_bin1 and initialize MAiNGO object myMAiNGO = std::shared_ptr<maingo::MAiNGO>(new maingo::MAiNGO(myModel_bin1)); } catch (std::exception &e) { - exceptionCounter ++; + exceptionCounter++; MAiNGO_IF_BAB_MANAGER std::cerr << std::endl << e.what() << std::endl; @@ -203,7 +225,7 @@ main(int argc, char *argv[]) MAiNGO_MPI_FINALIZE return -1; } catch (...) { - exceptionCounter ++; + exceptionCounter++; MAiNGO_IF_BAB_MANAGER std::cerr << std::endl << "Encountered an unknown fatal error during initialization. Terminating." << std::endl; @@ -236,19 +258,22 @@ main(int argc, char *argv[]) myMAiNGO->set_option("LBP_solver", maingo::lbp::LBP_SOLVER_CPLEX); std::cout << "Disabling all output." << std::endl; myMAiNGO->set_option("loggingDestination", maingo::LOGGING_NONE); + myMAiNGO->set_option("growing_augmentRule", 0); + myMAiNGO->set_option("growing_augmentFreq", 1); std::cout << std::endl; double CPUofAllProcesses = 0; - const double startCPU = maingo::get_cpu_time(); - const double startWall = maingo::get_wall_time(); - + const double startCPU = maingo::get_cpu_time(); + const double startWall = maingo::get_wall_time(); + // Solve the problems try { + // Problem bin 1 // The model is already set if (!(run_test(myMAiNGO, "Problem_bin1", 1, epsilonA, epsilonR, CPUofAllProcesses))) { - exceptionCounter ++; + exceptionCounter++; } // Problem int 1 @@ -257,6 +282,12 @@ main(int argc, char *argv[]) exceptionCounter++; } + // Problem nonsmooth + myMAiNGO->set_model(myModel_nonsmooth); + if (!(run_test(myMAiNGO, "Problem_nonsmooth", 0, epsilonA, epsilonR, CPUofAllProcesses))) { + exceptionCounter++; + } + // Problem case 1 lcoe myMAiNGO->set_model(myModel_case1_lcoe); if (!(run_test(myMAiNGO, "Problem_case1_lcoe", 50.2488287676, epsilonA, epsilonR, CPUofAllProcesses))) { @@ -287,42 +318,80 @@ main(int argc, char *argv[]) exceptionCounter++; } - // Problem NRTL RS Flash myMAiNGO->set_model(myModel_NRTL_RS_Flash); if (!(run_test(myMAiNGO, "Problem_NRTL_RS_Flash", 1064.34, epsilonA, epsilonR, CPUofAllProcesses))) { exceptionCounter++; } - // Problem OME RS Ideal Gas Flash myMAiNGO->set_model(myModel_OME_RS_IdealGasFlash); if (!(run_test(myMAiNGO, "Problem_OME_RS_IdealGasFlash", -0.385131174192, epsilonA, epsilonR, CPUofAllProcesses))) { exceptionCounter++; } - // Problem unused variables myMAiNGO->set_model(myModel_unusedVars); if (!(run_test(myMAiNGO, "Problem_unusedVars", 4.355812920567349, epsilonA, epsilonR, CPUofAllProcesses))) { exceptionCounter++; } - // Problem LP myMAiNGO->set_model(myModel_LP); if (!(run_test(myMAiNGO, "Problem_LP", 153.675, epsilonA, epsilonR, CPUofAllProcesses))) { exceptionCounter++; } + // Problem LP random + myMAiNGO->set_model(myModel_LP_random); + if (!(run_test(myMAiNGO, "Problem_LP_random", -2, epsilonA, epsilonR, CPUofAllProcesses))) { + exceptionCounter++; + } + // Problem MILP myMAiNGO->set_model(myModel_MILP); if (!(run_test(myMAiNGO, "Problem_MILP", -2, epsilonA, epsilonR, CPUofAllProcesses))) { exceptionCounter++; } + +#ifdef HAVE_MILP_SOLVER + // Problem MILP Sudoku - only run if dedicated MILP solver available, takes too long otherwise + myMAiNGO->set_model(myModel_MILP_sudoku); + if (!(run_test(myMAiNGO, "Problem_MILP_sudoku", 5, epsilonA, epsilonR, CPUofAllProcesses))) { + exceptionCounter++; + } +#else + std::cout << "Skipping Problem_MILP_sudoku to save time - no dedicated MILP solver available." << std::endl; +#endif + +#ifdef HAVE_QP_SOLVER + // Problem QP (extremly sparse) - only run if dedicated QP solver available, takes too long otherwise + myMAiNGO->set_model(myModel_QP); + if (!(run_test(myMAiNGO, "Problem_QP", 11.25, epsilonA, epsilonR, CPUofAllProcesses))) { + exceptionCounter++; + } +#else + std::cout << "Skipping Problem_QP to save time - no dedicated QP solver available." << std::endl; +#endif + + // Problem growing datasets - simple + myMAiNGO->set_model(myModel_growing_simple); + if (!(run_test(myMAiNGO, "Problem_growing_simple", 0.5066, epsilonA, epsilonR, CPUofAllProcesses))) { + exceptionCounter++; + } + + // Problem growing datasets - AVM + myMAiNGO->set_model(myModel_growing_AVM); + myMAiNGO->set_option("LBP_addAuxiliaryVars", 1); + if (!(run_test(myMAiNGO, "Problem_growing_AVM", 0.5066, epsilonA, epsilonR, CPUofAllProcesses))) { + exceptionCounter++; + } + + // For further test cases reset AVM: + // myMAiNGO->set_option("LBP_addAuxiliaryVars", 0); } catch (std::exception &e) { - exceptionCounter ++; + exceptionCounter++; MAiNGO_IF_BAB_MANAGER std::cerr << std::endl << e.what() << std::endl; @@ -337,7 +406,7 @@ main(int argc, char *argv[]) MAiNGO_END_IF MAiNGO_MPI_FINALIZE return -1; } - + if (exceptionCounter > 0) { throw std::exception(); return -1; diff --git a/tests/testProblems/problem_Henry_RS_IdealGasFlash.h b/tests/testProblems/problem_Henry_RS_IdealGasFlash.h index 47d9159..3583930 100644 --- a/tests/testProblems/problem_Henry_RS_IdealGasFlash.h +++ b/tests/testProblems/problem_Henry_RS_IdealGasFlash.h @@ -202,8 +202,13 @@ Model_Henry_RS_IdealGasFlash::evaluate(const std::vector<Var> &optVars) // Prepare output maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Objective: +#ifndef HAVE_GROWING_DATASETS result.objective = -L * x[3] / (F * z[3]); // result.objective = V*y[3]/(F*z[3]); +#else + result.objective_per_data.push_back(-L * x[3] / (F * z[3])); +#endif + // // Inequalities (<=0): result.ineq.push_back((x[1] - 0.01) / 1e-2); diff --git a/tests/testProblems/problem_LP.h b/tests/testProblems/problem_LP.h index 9900a72..a6ff9f4 100644 --- a/tests/testProblems/problem_LP.h +++ b/tests/testProblems/problem_LP.h @@ -125,7 +125,11 @@ Model_LP::evaluate(const std::vector<Var> &optVars) // Prepare output maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Objective function +#ifndef HAVE_GROWING_DATASETS result.objective = -(-0.225 * x1 - 0.153 * x2 - 0.162 * x3 - 0.225 * x4 - 0.162 * x5 - 0.126 * x6); +#else + result.objective_per_data.push_back(-(-0.225 * x1 - 0.153 * x2 - 0.162 * x3 - 0.225 * x4 - 0.162 * x5 - 0.126 * x6)); +#endif // Inequalities (<=0) result.ineq.push_back(x1 + x2 + x3 - (350)); diff --git a/tests/testProblems/problem_LP_random.h b/tests/testProblems/problem_LP_random.h new file mode 100644 index 0000000..a5d05a7 --- /dev/null +++ b/tests/testProblems/problem_LP_random.h @@ -0,0 +1,169 @@ +/********************************************************************************** + * Copyright (c) 2021 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + +#include "MAiNGOmodel.h" +#include <algorithm> +#include <numeric> +#include <random> +#include <vector> + +using Var = mc::FFVar; // This allows us to write Var instead of mc::FFVar + + +/** +* @class Model +* @brief Class defining the actual model implemented by the user +* +* This class is used by the user to implement the model +*/ +class Model_LP_random: public maingo::MAiNGOmodel { + + public: + /** + * @brief Default constructor + */ + Model_LP_random(unsigned problemSize = 100, double density = 0.5): + _problemSize(problemSize), _density(density) + { + //gives us the indices 0 to n^2+n-1. We shuffle them and choose density*100 percent of them from the front of the shuffled vector. + //The selected indices represent non zero elements of the problem. The first n correspond to the objective vector. The constraint matrix is assumed to be quadratic. + std::vector<unsigned int> indices(problemSize + problemSize * problemSize); + std::iota(indices.begin(), indices.end(), 0); + std::shuffle(indices.begin(), indices.end(), std::mt19937(std::random_device()())); + _indices = std::vector<unsigned>(indices.begin(), indices.begin() + indices.size() * density); + std::sort(_indices.begin(), _indices.end()); + std::random_device rd; //Will be used to obtain a seed for the random number engine + std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd() + std::uniform_real_distribution<> dis(-100.0, 100.0); // entries are randomly picked + _values = std::vector<double>(std::round(indices.size() * density)); + auto gen2 = [&dis, &gen]() { + return dis(gen); + }; + std::generate(_values.begin(), _values.end(), gen2); + } + + /** + * @brief Main function used to evaluate the model and construct a directed acyclic graph + * + * @param[in] optVars is the optimization variables vector + * @param[in] writeAdditionalOutput defines whether to write additional output + */ + maingo::EvaluationContainer evaluate(const std::vector<Var> &optVars); + + /** + * @brief Function for getting optimization variables data + */ + std::vector<maingo::OptimizationVariable> get_variables(); + + /** + * @brief Function for getting initial point data + */ + std::vector<double> get_initial_point(); + + private: + unsigned _problemSize; + double _density; + std::vector<unsigned> _indices; + std::vector<double> _values; +}; + + +////////////////////////////////////////////////////////////////////////// +// function for providing optimization variable data to the Branch-and-Bound solver +std::vector<maingo::OptimizationVariable> +Model_LP_random::get_variables() +{ + + std::vector<maingo::OptimizationVariable> variables; + // Required: Define optimization variables by specifying lower bound, upper bound (, optionally variable type, branching priority and a name) + // Continuous variables + + + for (unsigned i = 0; i < _problemSize; i++) { + variables.push_back(maingo::OptimizationVariable(maingo::Bounds(0, 10000000000), maingo::VT_CONTINUOUS, "none" + std::to_string(i))); + } + + // Binary variables + // Integer variables + + return variables; +} + + +////////////////////////////////////////////////////////////////////////// +// function for providing initial point data to the Branch-and-Bound solver +std::vector<double> +Model_LP_random::get_initial_point() +{ + + // Here you can provide an initial point for the local search + std::vector<double> initialPoint; + // Continuous variables + + for (unsigned i = 0; i < _problemSize; i++) { + initialPoint.push_back(0); + } + + // Binary variables + // Integer variables + + return initialPoint; +} + + +////////////////////////////////////////////////////////////////////////// +// Evaluate the model +maingo::EvaluationContainer +Model_LP_random::evaluate(const std::vector<Var> &optVars) +{ + + // Prepare output + maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Rename input + Var objective; + //indices is sorted, only the first n indices can be from objective + unsigned problemSize = _problemSize; + auto getRowColFromIndex = [problemSize](unsigned index) { + //i=row*ncolums+col + unsigned ncolums = problemSize; + unsigned col = index % ncolums; + unsigned row = (index - col) / ncolums; + return std::make_pair(row, col); + }; + unsigned j = 0; + while (_indices.at(j) < _problemSize && j < _indices.size()) //objective term + { + objective += optVars.at(_indices.at(j)) * _values.at(j) * 0; + j++; + } + objective += -2; + +#ifndef HAVE_GROWING_DATASETS + result.objective = objective; +#else + result.objective_per_data.push_back(objective); +#endif + for (unsigned row = 0; row < _problemSize; row++) { + Var currentRow; + bool rowUsed = false; + while (j < _indices.size() && getRowColFromIndex(_indices.at(j)).first == row) { + unsigned col = getRowColFromIndex(_indices.at(j)).second; + rowUsed = true; + currentRow += _values.at(j) * optVars.at(col); + j++; + } + if (rowUsed) + result.ineq.push_back(currentRow); + } + + return result; +} diff --git a/tests/testProblems/problem_MILP.h b/tests/testProblems/problem_MILP.h index 8f54d62..8e7c760 100644 --- a/tests/testProblems/problem_MILP.h +++ b/tests/testProblems/problem_MILP.h @@ -112,7 +112,11 @@ Model_MILP::evaluate(const std::vector<Var> &optVars) // Integer variables // Objective function +#ifndef HAVE_GROWING_DATASETS result.objective = -y; +#else + result.objective_per_data.push_back(-y); +#endif // Inequalities (<=0) result.ineq.push_back(-x + y - 1); diff --git a/tests/testProblems/problem_NRTL_RS_Flash.h b/tests/testProblems/problem_NRTL_RS_Flash.h index f39740b..0e3baac 100644 --- a/tests/testProblems/problem_NRTL_RS_Flash.h +++ b/tests/testProblems/problem_NRTL_RS_Flash.h @@ -204,7 +204,11 @@ Model_NRTL_RS_Flash::evaluate(const std::vector<Var> &optVars) // Prepare output maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Objective: +#ifndef HAVE_GROWING_DATASETS result.objective = -Q; +#else + result.objective_per_data.push_back(-Q); +#endif // Inequalities (<=0): result.ineq.push_back((0.99 - y[2]) / 1.0); // Equalities (=0): diff --git a/tests/testProblems/problem_OME_RS_IdealGasFlash.h b/tests/testProblems/problem_OME_RS_IdealGasFlash.h index 24a3c21..c40aa21 100644 --- a/tests/testProblems/problem_OME_RS_IdealGasFlash.h +++ b/tests/testProblems/problem_OME_RS_IdealGasFlash.h @@ -184,7 +184,11 @@ Model_OME_RS_IdealGasFlash::evaluate(const std::vector<Var> &optVars) // Prepare output maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Objective: +#ifndef HAVE_GROWING_DATASETS result.objective = -V * (y[0] + y[1] + y[2]); +#else + result.objective_per_data.push_back(-V * (y[0] + y[1] + y[2])); +#endif // Inequalities (<=0): result.ineq.push_back((0.98 - (y[0] + y[1] + y[2])) / 1.0); // Equalities (=0): diff --git a/tests/testProblems/problem_QP.h b/tests/testProblems/problem_QP.h new file mode 100644 index 0000000..e83e3e1 --- /dev/null +++ b/tests/testProblems/problem_QP.h @@ -0,0 +1,147 @@ +/********************************************************************************** + * Copyright (c) 2021 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + +#include "MAiNGOmodel.h" + + +using Var = mc::FFVar; // This allows us to write Var instead of mc::FFVar + + +/** +* @class Model +* @brief Class defining the actual model implemented by the user +* +* This class is used by the user to implement the model +*/ +class Model_QP: public maingo::MAiNGOmodel { + + public: + /** + * @brief Default constructor + */ + Model_QP(); + + /** + * @brief Main function used to evaluate the model and construct a directed acyclic graph + * + * @param[in] optVars is the optimization variables vector + * @param[in] writeAdditionalOutput defines whether to write additional output + */ + maingo::EvaluationContainer evaluate(const std::vector<Var> &optVars); + + /** + * @brief Function for getting optimization variables data + */ + std::vector<maingo::OptimizationVariable> get_variables(); + + /** + * @brief Function for getting initial point data + */ + std::vector<double> get_initial_point(); + + private: +}; + + +////////////////////////////////////////////////////////////////////////// +// function for providing optimization variable data to the Branch-and-Bound solver +std::vector<maingo::OptimizationVariable> +Model_QP::get_variables() +{ + + std::vector<maingo::OptimizationVariable> variables; + // Required: Define optimization variables by specifying lower bound, upper bound (, optionally variable type, branching priority and a name) + // Continuous variables + + variables.push_back(maingo::OptimizationVariable(maingo::Bounds(0, 10000000000), maingo::VT_CONTINUOUS, "x")); + for (int i = 0; i < 100; i++) { + variables.push_back(maingo::OptimizationVariable(maingo::Bounds(0, 10000000000), maingo::VT_CONTINUOUS, "none" + std::to_string(i))); + } + variables.push_back(maingo::OptimizationVariable(maingo::Bounds(0, 10000000000), maingo::VT_CONTINUOUS, "y")); + + // Binary variables + // Integer variables + + return variables; +} + + +////////////////////////////////////////////////////////////////////////// +// function for providing initial point data to the Branch-and-Bound solver +std::vector<double> +Model_QP::get_initial_point() +{ + + // Here you can provide an initial point for the local search + std::vector<double> initialPoint; + // Continuous variables + initialPoint.push_back(0); + for (int i = 0; i < 100; i++) { + initialPoint.push_back(0); + } + initialPoint.push_back(0); + // Binary variables + // Integer variables + + return initialPoint; +} + + +////////////////////////////////////////////////////////////////////////// +// constructor for the model +Model_QP::Model_QP() +{ + + // Initialize data if necessary: +} + + +////////////////////////////////////////////////////////////////////////// +// Evaluate the model +maingo::EvaluationContainer +Model_QP::evaluate(const std::vector<Var> &optVars) +{ + // Prepare output + maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Rename inputs + // Continuous variables + Var x = optVars.front(); + Var y = optVars.back(); + // Binary variables + // Integer variables + + // Objective function + + Var obj = 2 * x * x + pow(x + y, 2); + obj += x / 2.0 * 2.0; + obj -= 2; + obj += 6 * y + 4.0; +#ifndef HAVE_GROWING_DATASETS + result.objective = obj; // (((x*x * 2 + pow(x + y, 2)) + x * 0.5 * 2 - 2) + y * 6 + 4); +#else + result.objective_per_data.push_back(obj); +#endif + // Inequalities (<=0) + result.ineq.push_back(-2 * x + -3 * y + 4.0); + auto temp = x - x; + for (int i = 1; i < 100; i++) { + temp += optVars.at(i); + } + + // Equalities (=0) + result.eq.push_back(temp - 1.0); + // relaxation only inequalities (<=0): + + // relaxation only equalities (=0): + + return result; +} diff --git a/tests/testProblems/problem_bin1.h b/tests/testProblems/problem_bin1.h index 6e68edc..ff9bf9e 100644 --- a/tests/testProblems/problem_bin1.h +++ b/tests/testProblems/problem_bin1.h @@ -7,9 +7,6 @@ * * SPDX-License-Identifier: EPL-2.0 * - * @brief File containing a testcase for VT_BINARY. Correct binary solution: (0,0). - * Integer solution = continuous solution: (1,2). - * **********************************************************************************/ #pragma once @@ -97,7 +94,11 @@ Model_bin1::evaluate(const std::vector<Var> &optVars) // Prepare output maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Objective +#ifndef HAVE_GROWING_DATASETS result.objective = exp(4.5 * pow(x, 2) - 5 * x * y + pow(y, 2)); +#else + result.objective_per_data.push_back(exp(4.5 * pow(x, 2) - 5 * x * y + pow(y, 2))); +#endif return result; } diff --git a/tests/testProblems/problem_case1_lcoe.h b/tests/testProblems/problem_case1_lcoe.h index 8be01d5..8b96ca9 100644 --- a/tests/testProblems/problem_case1_lcoe.h +++ b/tests/testProblems/problem_case1_lcoe.h @@ -201,7 +201,11 @@ Model_case1_lcoe::evaluate(const std::vector<U>& currentPoint) // Prepare output maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Objective: +#ifndef HAVE_GROWING_DATASETS result.objective = LCOE; +#else + result.objective_per_data.push_back(LCOE); +#endif // Inequalities (<=0): result.ineq.push_back((S5.get_T() - Tmax) / 100); result.ineq.push_back((S4.get_Ts() + deltaTmin - TG3) / 100); diff --git a/tests/testProblems/problem_case2_lcoe.h b/tests/testProblems/problem_case2_lcoe.h index 20ab0f7..2211462 100644 --- a/tests/testProblems/problem_case2_lcoe.h +++ b/tests/testProblems/problem_case2_lcoe.h @@ -257,7 +257,11 @@ Model_case2_lcoe::evaluate(const std::vector<U>& currentPoint) maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Objective: +#ifndef HAVE_GROWING_DATASETS result.objective = LCOE; +#else + result.objective_per_data.push_back(LCOE); +#endif // HAVE_GROWING_DATASETS // Inequalities (<=0): result.ineq.push_back((S7.get_hSatVap() - S7.get_h()) / 1000); result.ineq.push_back((S5.get_Ts() + deltaTmin - TG3) / 100); diff --git a/tests/testProblems/problem_case3_wnet.h b/tests/testProblems/problem_case3_wnet.h index 7fb78e9..1e602e1 100644 --- a/tests/testProblems/problem_case3_wnet.h +++ b/tests/testProblems/problem_case3_wnet.h @@ -338,8 +338,12 @@ Model_case3_wnet::evaluate(const std::vector<U>& currentPoint) maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Objective: +#ifndef HAVE_GROWING_DATASETS // result.objective = LCOE; result.objective = -Work_net; +#else + result.objective_per_data.push_back(-Work_net); +#endif // Inequalities (<=0): result.ineq.push_back(((S7.get_hSatVap() - S7.get_h()) / 1000)); result.ineq.push_back(((S11.get_hSatVap() - S11.get_h()) / 1000)); diff --git a/tests/testProblems/problem_ex8_1_3.h b/tests/testProblems/problem_ex8_1_3.h index 53daaa3..cda813b 100644 --- a/tests/testProblems/problem_ex8_1_3.h +++ b/tests/testProblems/problem_ex8_1_3.h @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 * **********************************************************************************/ - + #pragma once #include "MAiNGOmodel.h" @@ -105,8 +105,11 @@ Model_ex8_1_3::evaluate(const std::vector<Var> &optVars) // Prepare output maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // objective function: +#ifndef HAVE_GROWING_DATASETS result.objective = -(-(1 + sqr(1 + x1 + x2) * (19 + 3 * sqr(x1) - 14 * x1 + 6 * x1 * x2 - 14 * x2 + 3 * sqr(x2))) * (30 + sqr(2 * x1 - 3 * x2) * (18 + 12 * sqr(x1) - 32 * x1 - 36 * x1 * x2 + 48 * x2 + 27 * sqr(x2))) - (0)); - +#else + result.objective_per_data.push_back(-(-(1 + sqr(1 + x1 + x2) * (19 + 3 * sqr(x1) - 14 * x1 + 6 * x1 * x2 - 14 * x2 + 3 * sqr(x2))) * (30 + sqr(2 * x1 - 3 * x2) * (18 + 12 * sqr(x1) - 32 * x1 - 36 * x1 * x2 + 48 * x2 + 27 * sqr(x2))) - (0))); +#endif // inequalities (<=0): // equalities (=0): diff --git a/tests/testProblems/problem_growingDatasets_AVM.h b/tests/testProblems/problem_growingDatasets_AVM.h new file mode 100644 index 0000000..c91789d --- /dev/null +++ b/tests/testProblems/problem_growingDatasets_AVM.h @@ -0,0 +1,139 @@ +/********************************************************************************** + * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + +#include "MAiNGOmodel.h" + +namespace dataAVM { +// Data pairs: inputValues and outputValues must have the same length +const std::vector<double> inputValues = { + 1.0, + 1.0, + 1.0}; +const std::vector<double> outputValues = { + 1.0, + 0.6, + 0.0}; +} // namespace dataAVM + +/** +* @class Model +* @brief Problem for testing the handling of additional auxiliary variables in MAiNGO with growing datasets +* +* To allow for adding auxiliary variables, we need a nonlinear dependency on at least 2 +* optimization variables which occurs at least 2 times. +* Thus, this class defines a parameter estimation problem for optimizing the slope and offset +* of an affine linear function +* output = slope1*slope2 * input + slope1*slope2 +* based on up to three data points, namely (1,1), (1,0.6), and (1,0). +* +*/ +class Model_growing_AVM: public maingo::MAiNGOmodel { + + public: + /** + * @brief Default constructor + */ + Model_growing_AVM(); + + /** + * @brief Main function used to evaluate the model and construct a directed acyclic graph + * + * @param[in] optVars is the optimization variables vector + */ + maingo::EvaluationContainer evaluate(const std::vector<Var> &optVars); + + /** + * @brief Function for getting optimization variables data + */ + std::vector<maingo::OptimizationVariable> get_variables(); + + /** + * @brief Function for getting initial point data + */ + std::vector<double> get_initial_point(); + + private: + size_t _noOfDataPoints; +}; + + +////////////////////////////////////////////////////////////////////////// +// function for providing optimization variable data to the Branch-and-Bound solver +std::vector<maingo::OptimizationVariable> +Model_growing_AVM::get_variables() +{ + + std::vector<maingo::OptimizationVariable> variables; + // Required: Define optimization variables by specifying lower bound, upper bound (, optionally variable type, branching priority and a name) + variables.push_back(maingo::OptimizationVariable(maingo::Bounds(0., 5.), maingo::VT_CONTINUOUS, "slope/offset Part I")); + variables.push_back(maingo::OptimizationVariable(maingo::Bounds(0., 5.), maingo::VT_CONTINUOUS, "slope/offset Part II")); + + return variables; +} + + +////////////////////////////////////////////////////////////////////////// +// function for providing initial point data to the Branch-and-Bound solver +std::vector<double> +Model_growing_AVM::get_initial_point() +{ + + // Here you can provide an initial point for the local search + std::vector<double> initialPoint; + + return initialPoint; +} + + +////////////////////////////////////////////////////////////////////////// +// constructor for the model +Model_growing_AVM::Model_growing_AVM() +{ + + _noOfDataPoints = dataAVM::inputValues.size(); +} + + +////////////////////////////////////////////////////////////////////////// +// evaluate the model +maingo::EvaluationContainer +Model_growing_AVM::evaluate(const std::vector<Var> &optVars) +{ + + // Rename inputs + Var slope1 = optVars.at(0); // We use .at() here to get a vector exception if a wrong access occurs + Var slope2 = optVars.at(1); // We use .at() here to get a vector exception if a wrong access occurs + + // Model prediction of linear function + std::vector<Var> predictedValues; + for (auto inputValue : dataAVM::inputValues) { + predictedValues.push_back(slope1 * slope2 * inputValue + slope1 * slope2); + } + + // Prepare output + maingo::EvaluationContainer result; + + // Objective given as the mean squared error: + Var se = 0; + Var se_per_data = 0; + for (auto i = 0; i < _noOfDataPoints; i++) { + se_per_data = sqr(predictedValues[i] - dataAVM::outputValues[i]); + se += se_per_data; + result.objective_per_data.push_back(se_per_data); + } + + result.objective = se; + result.output.push_back(maingo::OutputVariable("slope = offset", slope1 * slope2)); + + return result; +} \ No newline at end of file diff --git a/tests/testProblems/problem_growingDatasets_simple.h b/tests/testProblems/problem_growingDatasets_simple.h new file mode 100644 index 0000000..701f03b --- /dev/null +++ b/tests/testProblems/problem_growingDatasets_simple.h @@ -0,0 +1,127 @@ +/********************************************************************************** + * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + +#include "MAiNGOmodel.h" + +namespace data { +// Data pairs: inputValues and outputValues must have the same length +const std::vector<double> inputValues = { + 1.0, + 1.0, + 1.0}; +const std::vector<double> outputValues = { + 1.0, + 0.6, + 0.0}; +} // end namespace data + +/** +* @class Model +* @brief Simple test problem for MAiNGO with growing datasets +* +* This class defines a parameter estimation problem for optimizing the slope of a linear function +* output = slope * input through the origin and up to three data points, namely (1,1), (1,0.6), and (1,0). +* When using all data points, we expect optimal slope = 0.53 +* as we optimize min_slope [(slope*1-1)^2 + (slope*1-0.6)^2 + (slope*1-0)^2]. +* When using a single data point, we expect optimal slope = y value of the data point, and objective = 0. +* When using (1,0) and (1,0.6), we expect optimal slope = 0.3. +* When using (1,0) and (1,1.0), we expect optimal slope = 0.5. +* When using (1,0.6) and (1,1), we expect optimal slope = 0.8. +* To avoid MAiNGO passing this problem as a QP to CPLEX, we use sqroot(slope) as optimization variable. +* +* Note that augmentation rule SCALING can not trigger augmentation and, thus, does not give convergence +* for this model due to overfitting. In particular, 1 data point can be fitted perfectly, while there is +* a deviation between predictions and data when using at least 2 data points. +* +*/ +class Model_growing_simple: public maingo::MAiNGOmodel { + + public: + Model_growing_simple(); + + maingo::EvaluationContainer evaluate(const std::vector<Var> &optVars); + std::vector<maingo::OptimizationVariable> get_variables(); + std::vector<double> get_initial_point(); + + private: + size_t _noOfDataPoints; +}; + + +////////////////////////////////////////////////////////////////////////// +// function for providing optimization variable data to the Branch-and-Bound solver +std::vector<maingo::OptimizationVariable> +Model_growing_simple::get_variables() +{ + + std::vector<maingo::OptimizationVariable> variables; + + variables.push_back(maingo::OptimizationVariable(maingo::Bounds(0., 5.), maingo::VT_CONTINUOUS, "sqrt(slope)")); + + return variables; +} + + +////////////////////////////////////////////////////////////////////////// +// function for providing initial point data to the Branch-and-Bound solver +std::vector<double> +Model_growing_simple::get_initial_point() +{ + + std::vector<double> initialPoint; + + return initialPoint; +} + + +////////////////////////////////////////////////////////////////////////// +// constructor for the model +Model_growing_simple::Model_growing_simple() +{ + + _noOfDataPoints = data::inputValues.size(); +} + + +////////////////////////////////////////////////////////////////////////// +// evaluate the model +maingo::EvaluationContainer +Model_growing_simple::evaluate(const std::vector<Var> &optVars) +{ + + // Rename inputs + Var sqrt_slope = optVars.at(0); + + // Model prediction of linear function + std::vector<Var> predictedValues; + for (auto inputValue : data::inputValues) { + predictedValues.push_back(sqr(sqrt_slope) * inputValue); + } + + // Prepare output + maingo::EvaluationContainer result; + + // Objective given as the summed squared error: + Var se = 0; + Var se_per_data = 0; + for (auto i = 0; i < _noOfDataPoints; i++) { + se_per_data = sqr(predictedValues[i] - data::outputValues[i]); + se += se_per_data; + result.objective_per_data.push_back(se_per_data); + } + + result.objective = se; + result.output.push_back(maingo::OutputVariable("slope", sqr(sqrt_slope))); + + return result; +} \ No newline at end of file diff --git a/tests/testProblems/problem_int1.h b/tests/testProblems/problem_int1.h index 4fa6d34..91e9449 100644 --- a/tests/testProblems/problem_int1.h +++ b/tests/testProblems/problem_int1.h @@ -97,7 +97,11 @@ Model_int1::evaluate(const std::vector<Var> &optVars) // Prepare output maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Objective +#ifndef HAVE_GROWING_DATASETS result.objective = sin(x) + pow(y - 2.2, 2); +#else + result.objective_per_data.push_back(sin(x) + pow(y - 2.2, 2)); +#endif return result; } diff --git a/tests/testProblems/problem_nonsmooth.h b/tests/testProblems/problem_nonsmooth.h new file mode 100644 index 0000000..ecd409e --- /dev/null +++ b/tests/testProblems/problem_nonsmooth.h @@ -0,0 +1,67 @@ +/********************************************************************************** + * Copyright (c) 2023 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + +#include "MAiNGOmodel.h" + + +using Var = mc::FFVar; + + +class Model_nonsmooth: public maingo::MAiNGOmodel { + public: + maingo::EvaluationContainer evaluate(const std::vector<Var> &optVars); + + std::vector<maingo::OptimizationVariable> get_variables(); + + std::vector<double> get_initial_point(); +}; + + +////////////////////////////////////////////////////////////////////////// +std::vector<maingo::OptimizationVariable> +Model_nonsmooth::get_variables() +{ + std::vector<maingo::OptimizationVariable> variables; + variables.push_back(maingo::OptimizationVariable(maingo::Bounds(0, 1), "x")); + variables.push_back(maingo::OptimizationVariable(maingo::Bounds(0, 1), "y")); + + return variables; +} + + +////////////////////////////////////////////////////////////////////////// +std::vector<double> +Model_nonsmooth::get_initial_point() +{ + return {0., 0.}; +} + + +////////////////////////////////////////////////////////////////////////// +maingo::EvaluationContainer +Model_nonsmooth::evaluate(const std::vector<Var> &optVars) +{ + Var x = optVars[0]; + Var y = optVars[1]; + + maingo::EvaluationContainer result; + + result.objective = sqrt(x); + result.ineq.push_back(fabs(x)); + result.ineq.push_back(max(x, y)); + result.ineq.push_back(min(x, y)); + result.ineq.push_back(-fabs(x)); + result.ineq.push_back(-max(x, y)); + result.ineq.push_back(-min(x, y)); + return result; +} diff --git a/tests/testProblems/problem_sudoku.h b/tests/testProblems/problem_sudoku.h new file mode 100644 index 0000000..f8c171a --- /dev/null +++ b/tests/testProblems/problem_sudoku.h @@ -0,0 +1,207 @@ +/********************************************************************************** + * Copyright (c) 2021 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#pragma once + +#include "MAiNGOmodel.h" + +#include <map> +#include <tuple> + + +using Var = mc::FFVar; // This allows us to write Var instead of mc::FFVar + + +/** +* @class Model +* @brief Class defining the actual model implemented by the user +* +* This class is used by the user to implement the model +*/ +class Model_MILP_sudoku: public maingo::MAiNGOmodel { + + public: + /** + * @brief Default constructor + */ + Model_MILP_sudoku(); + + /** + * @brief Main function used to evaluate the model and construct a directed acyclic graph + * + * @param[in] optVars is the optimization variables vector + * @param[in] writeAdditionalOutput defines whether to write additional output + */ + maingo::EvaluationContainer evaluate(const std::vector<Var> &optVars); + + /** + * @brief Function for getting optimization variables data + */ + std::vector<maingo::OptimizationVariable> get_variables(); + + /** + * @brief Function for getting initial point data + */ + std::vector<double> get_initial_point(); + + private: + std::map<std::tuple<int, int, int>, int> pos; +}; + + +////////////////////////////////////////////////////////////////////////// +// function for providing optimization variable data to the Branch-and-Bound solver +std::vector<maingo::OptimizationVariable> +Model_MILP_sudoku::get_variables() +{ + + std::vector<maingo::OptimizationVariable> variables; + // Required: Define optimization variables by specifying lower bound, upper bound (, optionally variable type, branching priority and a name) + // Continuous variables + + // Binary variables + for (int i = 0; i < 9; i++) + for (int j = 0; j < 9; j++) + for (int k = 0; k < 9; k++) { + pos.insert({{i, j, k}, (int)variables.size()}); + variables.push_back(maingo::OptimizationVariable(maingo::Bounds(0.0, 2.0), maingo::VT_BINARY, "x" + std::to_string(i) + std::to_string(j) + std::to_string(k))); + } + + // Integer variables + + return variables; +} + + +////////////////////////////////////////////////////////////////////////// +// function for providing initial point data to the Branch-and-Bound solver +std::vector<double> +Model_MILP_sudoku::get_initial_point() +{ + + // Here you can provide an initial point for the local search + std::vector<double> initialPoint; + for (int i = 0; i < 9; i++) + for (int j = 0; j < 9; j++) + for (int k = 0; k < 9; k++) { + initialPoint.push_back(0); + } + return initialPoint; +} + + +////////////////////////////////////////////////////////////////////////// +// constructor for the model +Model_MILP_sudoku::Model_MILP_sudoku() +{ + + // Initialize data if necessary: +} + + +////////////////////////////////////////////////////////////////////////// +// Evaluate the model +maingo::EvaluationContainer +Model_MILP_sudoku::evaluate(const std::vector<Var> &optVars) +{ + // Prepare output + maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Rename inputs + // Continuous variables + // Objective function + + Var obj = optVars.front(); +#ifndef HAVE_GROWING_DATASETS + result.objective = obj + 5; +#else + result.objective_per_data.push_back(obj + 5); +#endif + // Equalities (=0) + // each cell can only contain one number + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + Var constraint = -1; + for (int k = 0; k < 9; k++) { + constraint += optVars.at(pos.at({i, j, k})); + } + result.eq.push_back(constraint); + } + } + // each column can only contain each number once + for (int i = 0; i < 9; i++) { + for (int k = 0; k < 9; k++) { + Var constraint = -1; + for (int j = 0; j < 9; j++) { + constraint += optVars.at(pos.at({i, j, k})); + } + result.eq.push_back(constraint); + } + } + // each row can only contain each number once + for (int j = 0; j < 9; j++) { + for (int k = 0; k < 9; k++) { + Var constraint = -1; + for (int i = 0; i < 9; i++) { + constraint += optVars.at(pos.at({i, j, k})); + } + result.eq.push_back(constraint); + } + } + + //each grid 3x3 cell can only contain each number once + for (int k = 0; k < 9; k++) { + for (int a = 0; a <= 2; a++) { + for (int b = 0; b <= 2; b++) { + Var constraint = -1; + for (int i = 0; i < 3; i++) { + for ( + int j = 0; + j < 3; j++) { + constraint += optVars.at(pos.at({3 * a + i, 3 * b + j, k})); + } + } + result.eq.push_back(constraint); + } + } + } + + //ALE input: + /* + definitions: + binary[9, 9, 9] x; + + objective: + x[1,1,1]; + + constraints: + forall i in {1 .. 9} : + forall j in {1 .. 9} : + sum(k in {1 .. 9} : x[i,j,k]) = 1; # each cell can only contain one number + + forall i in {1 .. 9} : + forall k in {1 .. 9} : + sum(j in {1 .. 9} : x[i,j,k]) = 1; # each column can only contain each number once + + forall j in {1 .. 9} : + forall k in {1 .. 9} : + sum(i in {1 .. 9} : x[i,j,k]) = 1; # each row can only contain each number once + + forall k in {1 .. 9} : + forall a in {0 .. 2} : + forall b in {0 .. 2} : + sum(i in {1 .. 3} : sum(j in {1 .. 3} : x[3*a + i,3*b + j,k])) = 1; # each grid-cell can only contain each number once + */ + + // relaxation only inequalities (<=0): + + // relaxation only equalities (=0): + + return result; +} diff --git a/tests/testProblems/problem_unusedVars.h b/tests/testProblems/problem_unusedVars.h index 96f4d28..a56a88c 100644 --- a/tests/testProblems/problem_unusedVars.h +++ b/tests/testProblems/problem_unusedVars.h @@ -120,7 +120,11 @@ Model_unusedVars::evaluate(const std::vector<Var> &optVars) // Prepare output maingo::EvaluationContainer result; /*!< variable holding the actual result consisting of an objective, inequalities, equalities, relaxation only inequalities and relaxation only equalities */ // Objective given as the Ackel Path function: +#ifndef HAVE_GROWING_DATASETS result.objective = -a * exp(temp1) - exp(temp2) + a + exp(1); +#else + result.objective_per_data.push_back(-a * exp(temp1) - exp(temp2) + a + exp(1)); +#endif // Inequalities (<=0): diff --git a/tests/unitTests/testDecayingProbability.cpp b/tests/unitTests/testDecayingProbability.cpp new file mode 100644 index 0000000..8858d65 --- /dev/null +++ b/tests/unitTests/testDecayingProbability.cpp @@ -0,0 +1,58 @@ +/********************************************************************************** + * Copyright (c) 2021 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#include "decayingProbability.h" + +#include <gtest/gtest.h> + + +/////////////////////////////////////////////////// +// testing if an optional step is always conducted if the decay coefficient is zero +TEST(TestDecayingProbability, TestZeroDecay) +{ + + const double decayCoefficient = 0.; + + bool alwaysDecidedToDoOptionalStep = true; + + const size_t nTrials = 100; + for (size_t i = 0; i < nTrials; ++i) { + const double babNodeDepth = i; + if (maingo::do_based_on_decaying_probability(decayCoefficient, babNodeDepth) == false) { + alwaysDecidedToDoOptionalStep = false; + break; + } + } + + EXPECT_TRUE(alwaysDecidedToDoOptionalStep); +} + + +/////////////////////////////////////////////////// +// testing if an optional step is always conducted at zero depth +TEST(TestDecayingProbability, TestZeroDepth) +{ + + const double babNodeDepth = 0.; + + bool alwaysDecidedToDoOptionalStep = true; + + const size_t nTrials = 100; + for (size_t i = 0; i < nTrials; ++i) { + const double decayCoefficient = ((double)i) / ((double)nTrials); + if (maingo::do_based_on_decaying_probability(decayCoefficient, babNodeDepth) == false) { + alwaysDecidedToDoOptionalStep = false; + break; + } + } + + EXPECT_TRUE(alwaysDecidedToDoOptionalStep); +} \ No newline at end of file diff --git a/tests/unitTests/testLogger.cpp b/tests/unitTests/testLogger.cpp new file mode 100644 index 0000000..226f975 --- /dev/null +++ b/tests/unitTests/testLogger.cpp @@ -0,0 +1,188 @@ +/********************************************************************************** + * Copyright (c) 2021-2023 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#include "logger.h" +#include "settings.h" + +#include <gtest/gtest.h> + + +/////////////////////////////////////////////////// +// struct on which the unit test will be preformed on +struct TestLogger: testing::Test { + std::shared_ptr<maingo::Settings> settings = std::make_shared<maingo::Settings>(); + std::shared_ptr<maingo::Logger> logger = std::make_shared<maingo::Logger>(settings); +}; + + +/////////////////////////////////////////////////// +// testing all three versions of _get_(max_)verb on different verbosities +TEST_F(TestLogger, TestVerb) +{ + settings->loggingDestination = maingo::LOGGING_OUTSTREAM; + + testing::internal::CaptureStdout(); + + + // test _get_verb() + + settings->LBP_verbosity = maingo::VERB_NONE; + settings->UBP_verbosity = maingo::VERB_NORMAL; + settings->BAB_verbosity = maingo::VERB_ALL; + + logger->print_message("1", maingo::VERB_NONE, maingo::LBP_VERBOSITY); + logger->print_message("2", maingo::VERB_NORMAL, maingo::LBP_VERBOSITY); + logger->print_message("3", maingo::VERB_ALL, maingo::LBP_VERBOSITY); + + logger->print_message("4", maingo::VERB_NONE, maingo::UBP_VERBOSITY); + logger->print_message("5", maingo::VERB_NORMAL, maingo::UBP_VERBOSITY); + logger->print_message("6", maingo::VERB_ALL, maingo::UBP_VERBOSITY); + + logger->print_message("7", maingo::VERB_NONE, maingo::BAB_VERBOSITY); + logger->print_message("8", maingo::VERB_NORMAL, maingo::BAB_VERBOSITY); + logger->print_message("9", maingo::VERB_ALL, maingo::BAB_VERBOSITY); + + + // test _get_max_verb() for two input verbosities + + settings->LBP_verbosity = maingo::VERB_NORMAL; + settings->UBP_verbosity = maingo::VERB_NONE; + + logger->print_message("A", maingo::VERB_NONE, maingo::LBP_VERBOSITY, maingo::UBP_VERBOSITY); + logger->print_message("B", maingo::VERB_NORMAL, maingo::LBP_VERBOSITY, maingo::UBP_VERBOSITY); + logger->print_message("C", maingo::VERB_ALL, maingo::LBP_VERBOSITY, maingo::UBP_VERBOSITY); + + logger->print_message("D", maingo::VERB_NONE, maingo::UBP_VERBOSITY, maingo::LBP_VERBOSITY); + logger->print_message("E", maingo::VERB_NORMAL, maingo::UBP_VERBOSITY, maingo::LBP_VERBOSITY); + logger->print_message("F", maingo::VERB_ALL, maingo::UBP_VERBOSITY, maingo::LBP_VERBOSITY); + + // test _get_max_verb() for three input verbosities + + settings->LBP_verbosity = maingo::VERB_ALL; + settings->UBP_verbosity = maingo::VERB_NORMAL; + settings->BAB_verbosity = maingo::VERB_NONE; + + logger->print_message("G", maingo::VERB_NONE, maingo::LBP_VERBOSITY, maingo::UBP_VERBOSITY, maingo::BAB_VERBOSITY); + logger->print_message("H", maingo::VERB_NORMAL, maingo::LBP_VERBOSITY, maingo::UBP_VERBOSITY, maingo::BAB_VERBOSITY); + logger->print_message("I", maingo::VERB_ALL, maingo::LBP_VERBOSITY, maingo::UBP_VERBOSITY, maingo::BAB_VERBOSITY); + + logger->print_message("J", maingo::VERB_NONE, maingo::BAB_VERBOSITY, maingo::BAB_VERBOSITY, maingo::BAB_VERBOSITY); + logger->print_message("K", maingo::VERB_NORMAL, maingo::BAB_VERBOSITY, maingo::BAB_VERBOSITY, maingo::BAB_VERBOSITY); + logger->print_message("L", maingo::VERB_ALL, maingo::BAB_VERBOSITY, maingo::BAB_VERBOSITY, maingo::BAB_VERBOSITY); + + + std::string output = testing::internal::GetCapturedStdout(); + + EXPECT_EQ("145789ABDEGHIJ", output); +} + + +/////////////////////////////////////////////////// +// testing output of logger to screen and log at different verbosities +TEST_F(TestLogger, TestOutputLogger) +{ + settings->LBP_verbosity = maingo::VERB_NONE; + settings->UBP_verbosity = maingo::VERB_NORMAL; + settings->BAB_verbosity = maingo::VERB_ALL; + + + // testing setting option LOGGING_OUTSTREAM by capturing output to console + settings->loggingDestination = maingo::LOGGING_OUTSTREAM; + + testing::internal::CaptureStdout(); + + logger->print_message("1", maingo::VERB_NONE, maingo::BAB_VERBOSITY); + logger->print_message("2", maingo::VERB_ALL, maingo::LBP_VERBOSITY); + logger->print_message("3", maingo::VERB_NORMAL, maingo::UBP_VERBOSITY); + + std::string output = testing::internal::GetCapturedStdout(); + + // second message should not be printed + EXPECT_EQ("13", output); + + logger->clear(); + + + // testing setting option LOGGING_FILE + settings->loggingDestination = maingo::LOGGING_FILE; + + logger->print_message("1", maingo::VERB_NONE, maingo::BAB_VERBOSITY); + logger->print_message("2", maingo::VERB_ALL, maingo::LBP_VERBOSITY); + logger->print_message("3", maingo::VERB_NORMAL, maingo::UBP_VERBOSITY); + + // second message should not be printed + EXPECT_EQ("1", logger->babLine.front()); + logger->babLine.pop(); + EXPECT_EQ("3", logger->babLine.front()); + logger->babLine.pop(); + + + logger->clear(); + + // testing clear() + EXPECT_EQ(true, logger->babLine.empty()); + + + // testing setting option LOGGING_FILE_AND_STREAM + settings->loggingDestination = maingo::LOGGING_FILE_AND_STREAM; + + testing::internal::CaptureStdout(); + + logger->print_message("1", maingo::VERB_NONE, maingo::BAB_VERBOSITY); + logger->print_message("2", maingo::VERB_ALL, maingo::LBP_VERBOSITY); + logger->print_message("3", maingo::VERB_NORMAL, maingo::UBP_VERBOSITY); + + // second message should not be printed + EXPECT_EQ("1", logger->babLine.front()); + logger->babLine.pop(); + EXPECT_EQ("3", logger->babLine.front()); + logger->babLine.pop(); + + output = testing::internal::GetCapturedStdout(); + + EXPECT_EQ("13", output); +} + + +/////////////////////////////////////////////////// +//testing the output of print_vector() by capturing the output to the consol and comparing it to the expected output +TEST_F(TestLogger, TestPrintVector) +{ + std::vector<double> testVector = {0.0, 1.0}; + + settings->LBP_verbosity = maingo::VERB_NONE; + settings->loggingDestination = maingo::LOGGING_OUTSTREAM; + + + // capturing output of print_vector second statement should not create output + testing::internal::CaptureStdout(); + + logger->print_vector(2, testVector, "TestString", maingo::VERB_NONE, maingo::LBP_VERBOSITY); + logger->print_vector(1, testVector, "TestString", maingo::VERB_NORMAL, maingo::LBP_VERBOSITY); + + + std::string output = testing::internal::GetCapturedStdout(); + + + // expected output + std::ostringstream compString; + + compString << "TestString" << std::endl; + for (unsigned int i = 0; i < 2; i++) { + compString << " x(" << i << "): " << testVector[i] << std::endl; + } + + + EXPECT_EQ(compString.str(), output); + + // expecting error if the numbers of values to be printed exceed the number of elements in the testVector + ASSERT_ANY_THROW(logger->print_vector(3, testVector, "TestString", maingo::VERB_NONE, maingo::LBP_VERBOSITY)); +} \ No newline at end of file diff --git a/tests/unitTests/testPointIsWithinNodeBounds.cpp b/tests/unitTests/testPointIsWithinNodeBounds.cpp new file mode 100644 index 0000000..33341d8 --- /dev/null +++ b/tests/unitTests/testPointIsWithinNodeBounds.cpp @@ -0,0 +1,75 @@ +/********************************************************************************** + * Copyright (c) 2023 Process Systems Engineering (AVT.SVT), RWTH Aachen University + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + **********************************************************************************/ + +#include "pointIsWithinNodeBounds.h" + +#include "babNode.h" + +#include <gtest/gtest.h> + + +using maingo::point_is_within_node_bounds; + + +/////////////////////////////////////////////////// +struct TestPointIsWithinNodeBounds: testing::Test { + const std::vector<double> lowerBounds = {0., -1., -2., 1.}; + const std::vector<double> upperBounds = {1., 1., -1., 1.}; + + const babBase::BabNode node{42. /*pruning score*/, lowerBounds, upperBounds, 0 /*index data set*/, 0 /*ID*/, 0 /*depth*/, false /*augment data*/}; +}; + + +/////////////////////////////////////////////////// +TEST_F(TestPointIsWithinNodeBounds, AcceptsPointsWithinBounds) +{ + std::vector<double> point = {0.5, 0., -1.5, 1.}; + EXPECT_EQ(point_is_within_node_bounds(point, lowerBounds, upperBounds), true); + EXPECT_EQ(point_is_within_node_bounds(point, node), true); + + point = {0., -1., -2, 1.}; + EXPECT_EQ(point_is_within_node_bounds(point, lowerBounds, upperBounds), true); + EXPECT_EQ(point_is_within_node_bounds(point, node), true); + + point = {1., 1., -1, 1.}; + EXPECT_EQ(point_is_within_node_bounds(point, lowerBounds, upperBounds), true); + EXPECT_EQ(point_is_within_node_bounds(point, node), true); +} + + +/////////////////////////////////////////////////// +TEST_F(TestPointIsWithinNodeBounds, DetectsPointOutsideBounds) +{ + + std::vector<double> point = {-1., 0., -1.5, 1.}; + EXPECT_EQ(point_is_within_node_bounds(point, lowerBounds, upperBounds), false); + EXPECT_EQ(point_is_within_node_bounds(point, node), false); + + point = {1.5, 0., -1.5, 1.}; + EXPECT_EQ(point_is_within_node_bounds(point, lowerBounds, upperBounds), false); + EXPECT_EQ(point_is_within_node_bounds(point, node), false); + + point = {0.5, 0., -2.5, 1.}; + EXPECT_EQ(point_is_within_node_bounds(point, lowerBounds, upperBounds), false); + EXPECT_EQ(point_is_within_node_bounds(point, node), false); + + point = {0.5, 0., -0.5, 1.}; + EXPECT_EQ(point_is_within_node_bounds(point, lowerBounds, upperBounds), false); + EXPECT_EQ(point_is_within_node_bounds(point, node), false); + + point = {0.5, 0., -1.5, 1. - 1e-6}; + EXPECT_EQ(point_is_within_node_bounds(point, lowerBounds, upperBounds), false); + EXPECT_EQ(point_is_within_node_bounds(point, node), false); + + point = {0.5, 0., -1.5, 1. + 1e-6}; + EXPECT_EQ(point_is_within_node_bounds(point, lowerBounds, upperBounds), false); + EXPECT_EQ(point_is_within_node_bounds(point, node), false); +} \ No newline at end of file diff --git a/utilities/MAiNGO_Reader_Writer/inc/MAiNGOReaderWriter.h b/utilities/MAiNGO_Reader_Writer/inc/MAiNGOReaderWriter.h index e3d2641..4211e28 100644 --- a/utilities/MAiNGO_Reader_Writer/inc/MAiNGOReaderWriter.h +++ b/utilities/MAiNGO_Reader_Writer/inc/MAiNGOReaderWriter.h @@ -297,25 +297,35 @@ class MAiNGOReaderWriter { std::vector<std::pair<std::string, double>> get_additional_output(); private: + /** + * @brief Function for reading in the given GAMS file and making sure (not rigorously!) it is a GAMS convert file + */ + void _read_file_and_check_it_is_gams_convert(); + /** * @brief Function for reading and saving general problem info from a GAMS convert file */ - void _read_problem_info(); + void _extract_problem_info(); + + /** + * @brief Function for reading and saving variable names from a GAMS convert file + */ + void _extract_variable_names(); /** * @brief Function for reading and saving variable bounds from a GAMS convert file */ - void _read_variable_bounds(); + void _extract_variable_bounds(); /** * @brief Function for reading and saving the initial point */ - void _read_initial_point(); + void _extract_initial_point(); /** * @brief Function for reading and saving constraints. It also sets the correct objective function. */ - void _read_constraints(); + void _extract_constraints(); /** * @brief Function for obtaining the correct vector index of variable with name varName. diff --git a/utilities/MAiNGO_Reader_Writer/main.cpp b/utilities/MAiNGO_Reader_Writer/main.cpp index b5a895e..462b859 100644 --- a/utilities/MAiNGO_Reader_Writer/main.cpp +++ b/utilities/MAiNGO_Reader_Writer/main.cpp @@ -23,7 +23,7 @@ main(int argc, char *argv[]) // Read GAMS file name std::string gamsFile = "../../example/gams.gms"; // You most likely need to adapt this path to your folder structure or specify it directly as a command line argument std::string dictFile = "../../example/dict.txt"; - if (argc >= 2) { + if (argc >= 2) { gamsFile = argv[1]; if (argc >= 3) { dictFile = argv[2]; @@ -34,32 +34,43 @@ main(int argc, char *argv[]) } } - // Create MAiNGOReaderWriter object - maingo::readerWriter::MAiNGOReaderWriter maingoRW; + try { + // Create MAiNGOReaderWriter object + maingo::readerWriter::MAiNGOReaderWriter maingoRW; - // Read GAMS file - maingoRW.read_GAMS_convert_file(gamsFile); + // Read GAMS file + maingoRW.read_GAMS_convert_file(gamsFile); - // Print problem information - // maingoRW.print_problem_info_GAMS_file(std::cout); + // Print problem information + // maingoRW.print_problem_info_GAMS_file(std::cout); - // Write MAiNGO problem file, default name is problem_gams.h - maingoRW.write_MAiNGO_problem_file("problem_gams", dictFile); + // Write MAiNGO problem file, default name is problem_gams.h + maingoRW.write_MAiNGO_problem_file("problem_gams", dictFile); - // Write ALE problem file, default name is problem_gams.txt - maingoRW.write_ALE_problem_file("problem_gams", dictFile); + // Write ALE problem file, default name is problem_gams.txt + maingoRW.write_ALE_problem_file("problem_gams", dictFile); - // Read MAiNGO log file - // maingoRW.read_MAiNGO_log_file("../../example/bab.log"); + // Read MAiNGO log file + // maingoRW.read_MAiNGO_log_file("../../example/bab.log"); - // Print info read in from log file - // maingoRW.print_problem_info_MAiNGO_log(std::cout); + // Print info read in from log file + // maingoRW.print_problem_info_MAiNGO_log(std::cout); - // Convert all GAMS files found in folder "input GAMS convert" and write them into the "output MAiNGO problem" folder - // maingoRW.convert_GAMS_folder_to_MAiNGO_problem("../../input_GAMS_convert", "../../output_MAiNGO_problem"); + // Convert all GAMS files found in folder "input GAMS convert" and write them into the "output MAiNGO problem" folder + // maingoRW.convert_GAMS_folder_to_MAiNGO_problem("../../input_GAMS_convert", "../../output_MAiNGO_problem"); - // Convert all GAMS files found in folder "input GAMS convert" and write them into the "output ALE problem" folder - // maingoRW.convert_GAMS_folder_to_ALE_problem("../../input_GAMS_convert", "../../output_ALE_problem"); + // Convert all GAMS files found in folder "input GAMS convert" and write them into the "output ALE problem" folder + // maingoRW.convert_GAMS_folder_to_ALE_problem("../../input_GAMS_convert", "../../output_ALE_problem"); + } + catch (const std::exception& e) { + std::cout << "Encountered a fatal error:" << std::endl; + std::cout << e.what() << std::endl; + return -1; + } + catch (...) { + std::cout << "Encountered an unknown fatal error." << std::endl; + return -1; + } return 0; } \ No newline at end of file diff --git a/utilities/MAiNGO_Reader_Writer/src/MAiNGOReaderWriter.cpp b/utilities/MAiNGO_Reader_Writer/src/MAiNGOReaderWriter.cpp index 01418dd..cfd932a 100644 --- a/utilities/MAiNGO_Reader_Writer/src/MAiNGOReaderWriter.cpp +++ b/utilities/MAiNGO_Reader_Writer/src/MAiNGOReaderWriter.cpp @@ -17,6 +17,7 @@ #include <ctime> #include <fstream> #include <iomanip> +#include <regex> #include <sstream> #ifdef GCC_FS_EXPERIMENTAL @@ -75,141 +76,188 @@ MAiNGOReaderWriter::read_GAMS_convert_file(const std::string &gamsFileName) _initialPointBin.clear(); _initialPointInt.clear(); + _read_file_and_check_it_is_gams_convert(); + + _extract_problem_info(); + + _extract_variable_names(); + + _extract_variable_bounds(); + + _extract_initial_point(); + + _extract_constraints(); + + std::cout << "GAMS convert file " << _gamsFileName << " read successfully." << std::endl; +} + + +///////////////////////////////////////////////////////////////////////////////////////////// +// function for getting total number of variables etc. +void +MAiNGOReaderWriter::_read_file_and_check_it_is_gams_convert() +{ std::ifstream inFile; inFile.open(_gamsFileName); - if (inFile.is_open()) { - std::string line; - while (std::getline(inFile, line)) { // Read file line by line - const auto strEnd = line.find_last_not_of(" "); - line = line.substr(0, strEnd + 1); - _file.push_back(line); - } + if (!inFile.is_open()) { + throw std::runtime_error("Error: Could not open file " + _gamsFileName + "."); } - else { // File not found - std::cout << "Error: Could not open file " << _gamsFileName << ". Terminating." << std::endl; - inFile.close(); - exit(-1); - } - inFile.close(); - _read_problem_info(); - - _read_variable_bounds(); + std::string line; + while (std::getline(inFile, line)) { + const auto strEnd = line.find_last_not_of(" "); + line = line.substr(0, strEnd + 1); + line = std::regex_replace(line, std::regex("\\r"), ""); + _file.push_back(line); + } - _read_initial_point(); + inFile.close(); - _read_constraints(); + if (_file.empty()) { + throw std::runtime_error("Error: File " + _gamsFileName + " is empty."); + } - std::cout << "GAMS convert file " << _gamsFileName << " read successfully." << std::endl; + const std::size_t positionOfConvertString = _file[0].find("written by GAMS Convert"); + if (positionOfConvertString == std::string::npos) { + throw std::runtime_error("Error: File " + _gamsFileName + " does not look like not a GAMS convert file."); + } } ///////////////////////////////////////////////////////////////////////////////////////////// -// function for getting total number of variables +// function for getting total number of variables etc. void -MAiNGOReaderWriter::_read_problem_info() +MAiNGOReaderWriter::_extract_problem_info() { + bool foundEqNumbers = false; + bool foundVarNumbers = false; + bool foundFixedVarNumber = false; + bool foundObjective = false; - std::string line = _file[0]; - unsigned counter = 0; // Line counter - std::size_t foundConvert = line.find("written by GAMS Convert"); // This check is not rigorous - if (foundConvert != std::string::npos) { - while (line[0] == '*') { - counter++; + for (size_t counter = 0; counter < _file.size(); counter++){ + std::string line = _file[counter]; + + // Consider only comment lines, since these contain the model info + if(line[0] != '*') { + continue; + } + + // Number of equations + if (line.find("Equation counts") != std::string::npos) { + counter++; // Line with Total,E,G,L,... + counter++; // Line holding the numbers line = _file[counter]; - // Equations - if (line.find("Equation counts") != std::string::npos) { - counter++; // Line with Total,E,G,L,... - counter++; // Line holding the numbers - line = _file[counter]; - std::istringstream iss(line); - std::string asterisk; - iss >> asterisk; - iss >> _ncons; - iss >> _neq; - iss >> _ngineq; - iss >> _nlineq; - // Check for N,X,C,B equations - unsigned int check; - for (unsigned int i = 0; i < 4; i++) { - iss >> check; - if (check != 0) { - std::cout << "ERROR: Provided file " << _gamsFileName << " contains non E,G,L equations. Terminating. " << std::endl; - exit(-1); - } + std::istringstream iss(line); + std::string asterisk; + iss >> asterisk; + iss >> _ncons; + iss >> _neq; + iss >> _ngineq; + iss >> _nlineq; + // Check for N,X,C,B equations + unsigned int check; + for (unsigned int i = 0; i < 4; i++) { + iss >> check; + if (check != 0) { + throw std::runtime_error("Error: File " + _gamsFileName + " contains non E,G,L equations."); } } - // Variables - if (line.find("Variable counts") != std::string::npos) { - counter++; // Line with x,b,i,... - counter++; // Line with Total,cont,binary,... - counter++; // Line holding the numbers - line = _file[counter]; - std::istringstream iss(line); - std::string asterisk; - iss >> asterisk; - iss >> _nvar; - iss >> _ncontVar; - iss >> _nbinVar; - iss >> _nintVar; - // Check for sos1,sos2,scont,sint variables - unsigned int check; - for (unsigned int i = 0; i < 4; i++) { - iss >> check; - if (check != 0) { - std::cout << "ERROR: Provided file " << _gamsFileName << " contains non cont,bin,int variables. Terminating. " << std::endl; - exit(-1); - } + foundEqNumbers = true; + continue; + } + + // Number of variables + if (line.find("Variable counts") != std::string::npos) { + counter++; // Line with x,b,i,... + counter++; // Line with Total,cont,binary,... + counter++; // Line holding the numbers + line = _file[counter]; + std::istringstream iss(line); + std::string asterisk; + iss >> asterisk; + iss >> _nvar; + iss >> _ncontVar; + iss >> _nbinVar; + iss >> _nintVar; + // Check for sos1,sos2,scont,sint variables + unsigned int check; + for (unsigned int i = 0; i < 4; i++) { + iss >> check; + if (check != 0) { + throw std::runtime_error("Error: File " + _gamsFileName + " contains non cont,bin,int variables."); } } - // Fixed variables - if (line.find("FX") != std::string::npos) { - std::istringstream iss(line); - std::string asterisk, FX; - iss >> asterisk; - iss >> FX; - iss >> _nfixedVar; - } - // Find whether we minimize or maximize and get the variable - if (line.find("Solve m") != std::string::npos) { - std::size_t optSense = line.find("minimizing"); - if (optSense != std::string::npos) { - _minimizing = true; - } - else { - optSense = line.find("maximizing"); - _minimizing = false; - } - _objName = line.substr(optSense + 11, line.length() - optSense - 12); - _objNr = std::stoi(_objName.substr(1, _objName.length())); + foundVarNumbers = true; + continue; + } + + // Number of fixed variables + if (line.find("FX") != std::string::npos) { + std::istringstream iss(line); + std::string asterisk, FX; + iss >> asterisk; + iss >> FX; + iss >> _nfixedVar; + foundFixedVarNumber = true; + continue; + } + + // Find whether we minimize or maximize and get the variable + if (line.find("Solve m") != std::string::npos) { + std::size_t optSense = line.find("minimizing"); + if (optSense != std::string::npos) { + _minimizing = true; } + else { + optSense = line.find("maximizing"); + _minimizing = false; + } + _objName = line.substr(optSense + 11, line.length() - optSense - 12); + _objNr = std::stoi(_objName.substr(1, _objName.length())); + foundObjective = true; + continue; } } - else { // The provided file is not a GAMS convert file - std::cout << "ERROR: Provided file " << _gamsFileName << " is not a GAMS convert file. Terminating. " << std::endl; - exit(-1); + + if (!foundEqNumbers) { + throw std::runtime_error("Error: Did not find equation counts in file " + _gamsFileName + "."); } + if (!foundVarNumbers) { + throw std::runtime_error("Error: Did not find variable counts in file " + _gamsFileName + "."); + } + if (!foundFixedVarNumber) { + throw std::runtime_error("Error: Did not find FX count in file " + _gamsFileName + "."); + } + if (!foundObjective) { + throw std::runtime_error("Error: Did not find solve statement in file " + _gamsFileName + "."); + } +} - unsigned int oldcounter = counter; +///////////////////////////////////////////////////////////////////////////////////////////// +// function for getting the variable names +void +MAiNGOReaderWriter::_extract_variable_names() +{ // Get string with all variables + size_t counter = 0; std::string variablesString = _file[counter]; - while (variablesString.find("Variables ") == std::string::npos) { + while ( (variablesString.find("Variables") == std::string::npos) || (variablesString[0] == '*') ) { counter++; variablesString = _file[counter]; } while (variablesString.find(";") == std::string::npos) { - std::string nextLine; counter++; - nextLine = _file[counter]; + std::string nextLine = _file[counter]; while (nextLine[0] == ' ') { nextLine.erase(nextLine.begin()); } variablesString = variablesString + nextLine; } - // Get rid of the word Variables and the colon ; - variablesString = variablesString.substr(10, variablesString.length() - 11); - // Save variables + // Get rid of the word Variables (at the beginning) and the semicolon (at the end) + variablesString = variablesString.substr(9, variablesString.length() - 10); + + // Split variables string and save variable names + default initial values _initialPointCont.clear(); _initialPointBin.clear(); _initialPointInt.clear(); @@ -266,13 +314,19 @@ MAiNGOReaderWriter::_read_problem_info() pos++; } - // Check if all binary and integer variables have been found + // Sanity checks: test if all variables have been found + + // Check if all continuous variables have been found + if (_ncontVar != _contVariables.size()) { + throw std::runtime_error("Error reading GAMS convert file: found " + std::to_string(_contVariables.size()) + " continuous variables but expected " + std::to_string(_ncontVar)); + } + // Check if all integer variables have been found if (_nbinVar != _binVariables.size()) { pos = 0; - counter = oldcounter; + counter = 0; // Get string with binary variables std::string variablesString = _file[counter]; - while (variablesString.find("Binary Variables ") == std::string::npos) { + while (variablesString.find("Binary Variables") == std::string::npos) { counter++; variablesString = _file[counter]; } @@ -316,13 +370,17 @@ MAiNGOReaderWriter::_read_problem_info() pos++; } } - // Check if all binary and integer variables have been found + // Check if all binaries were found now: + if (_nbinVar != _binVariables.size()) { + throw std::runtime_error("Error reading GAMS convert file: found " + std::to_string(_binVariables.size()) + " binary variables but expected " + std::to_string(_nbinVar)); + } + // Check if all integer variables have been found if (_nintVar != _intVariables.size()) { pos = 0; - counter = oldcounter; + counter = 0; // Get string with binary variables std::string variablesString = _file[counter]; - while (variablesString.find("Integer Variables ") == std::string::npos) { + while (variablesString.find("Integer Variables") == std::string::npos) { counter++; variablesString = _file[counter]; } @@ -366,15 +424,17 @@ MAiNGOReaderWriter::_read_problem_info() pos++; } } - // Save constraints - _constraints.resize(_ncons); + // Check if all integer variables were found now: + if (_nintVar != _intVariables.size()) { + throw std::runtime_error("Error reading GAMS convert file: found " + std::to_string(_intVariables.size()) + " integer variables but expected " + std::to_string(_nintVar)); + } } ///////////////////////////////////////////////////////////////////////////////////////////// // function for getting total number of variables void -MAiNGOReaderWriter::_read_variable_bounds() +MAiNGOReaderWriter::_extract_variable_bounds() { std::string line; @@ -525,7 +585,7 @@ MAiNGOReaderWriter::_read_variable_bounds() ///////////////////////////////////////////////////////////////////////////////////////////// // function for reading the initial point from a GAMS convert file void -MAiNGOReaderWriter::_read_initial_point() +MAiNGOReaderWriter::_extract_initial_point() { std::string line; @@ -565,9 +625,10 @@ MAiNGOReaderWriter::_read_initial_point() ///////////////////////////////////////////////////////////////////////////////////////////// // function for reading the constraints from a GAMS convert file void -MAiNGOReaderWriter::_read_constraints() +MAiNGOReaderWriter::_extract_constraints() { + _constraints.resize(_ncons); std::string line; for (unsigned int ifile = 0; ifile < _file.size(); ifile++) { // Read file line by line line = _file[ifile]; @@ -1266,7 +1327,7 @@ MAiNGOReaderWriter::_rename_variables_and_constraints() _replace_all_variables_in_constraint(_eqConstraints[k].rhs, _intVariables[i].name, newName); } } - _replace_all_variables_in_constraint(_objFunction, _binVariables[i].name, newName); + _replace_all_variables_in_constraint(_objFunction, _intVariables[i].name, newName); _intVariables[i].name = newName; } } @@ -1487,7 +1548,7 @@ MAiNGOReaderWriter::_write_MAiNGO_model(std::ofstream &outputFile, const std::st std::tm *now = std::localtime(&t); outputFile << "/**********************************************************************************\n"; - outputFile << " * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University\n"; + outputFile << " * Copyright (c) 2021 Process Systems Engineering (AVT.SVT), RWTH Aachen University\n"; outputFile << " *\n"; outputFile << " * MAiNGO and the accompanying materials are made available under the\n"; outputFile << " * terms of the Eclipse Public License 2.0 which is available at\n"; @@ -1514,9 +1575,7 @@ MAiNGOReaderWriter::_write_MAiNGO_model(std::ofstream &outputFile, const std::st outputFile << "\n"; outputFile << "/**\n"; outputFile << "* @class Model\n"; - outputFile << "* @brief Class defining the actual model implemented by the user \n"; - outputFile << "*\n"; - outputFile << "* This class is used by the user to implement the model \n"; + outputFile << "* @brief Class defining the actual model to be solved by MAiNGO \n"; outputFile << "*/\n"; outputFile << "class Model: public maingo::MAiNGOmodel {\n"; outputFile << "\n"; @@ -1530,7 +1589,6 @@ MAiNGOReaderWriter::_write_MAiNGO_model(std::ofstream &outputFile, const std::st outputFile << " * @brief Main function used to evaluate the model and construct a directed acyclic graph\n"; outputFile << " *\n"; outputFile << " * @param[in] optVars is the optimization variables vector\n"; - outputFile << " * @param[in] writeAdditionalOutput defines whether to write additional output \n"; outputFile << " */ \n"; outputFile << " maingo::EvaluationContainer evaluate(const std::vector<Var> &optVars);\n"; outputFile << "\n"; -- GitLab