From 6d1ea2f11345c3cb8ea4b8c733cad8d5e70e6592 Mon Sep 17 00:00:00 2001 From: Clara Witte <clara.witte@avt.rwth-aachen.de> Date: Wed, 26 Jul 2023 11:33:59 +0200 Subject: [PATCH] Release version v0.7.0 --- .gitattributes | 1 + .gitlab-ci.yml | 2 + .gitmodules | 32 ++-- AUTHORS | 1 + CMakeLists.txt | 1 + LICENSE | 38 ++++- Readme.md | 4 +- ReleaseNotes.txt | 8 + cmake/MAiNGOversion.cmake | 2 +- dep/libale | 2 +- dep/melon | 2 +- doc/manual.dox | 10 +- .../01_BasicExample/examplePythonInterface.py | 12 +- inc/MAiNGO.h | 5 +- inc/lbp.h | 18 ++- inc/lbpClp.h | 5 +- inc/lbpCplex.h | 5 +- inc/lbpInterval.h | 5 +- inc/pointIsWithinNodeBounds.h | 88 +++++------ maingopy/Readme.md | 11 +- maingopy/_maingopy.cpp | 62 ++++++++ .../testErrorHandling.py | 31 ++++ maingopy/tests/testMaingopy.py | 1 + setup.py | 5 +- src/MAiNGO.cpp | 33 ++-- src/MAiNGOtoOtherLanguage.cpp | 4 +- src/lbp.cpp | 13 +- src/lbpClp.cpp | 6 +- src/lbpCplex.cpp | 6 +- src/lbpFactory.cpp | 12 +- src/lbpInterval.cpp | 4 +- src/pointIsWithinNodeBounds.cpp | 94 +++++------ src/ubp.cpp | 11 +- .../unitTests/testPointIsWithinNodeBounds.cpp | 148 +++++++++--------- .../src/MAiNGOReaderWriter.cpp | 119 +++++++++----- 35 files changed, 509 insertions(+), 292 deletions(-) create mode 100644 .gitattributes create mode 100644 maingopy/tests/individualPythonTests/testErrorHandling.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fcc0fb7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto* text=auto diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 14b7d90..289f60a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,6 +56,7 @@ build_linux: variables: GIT_STRATEGY: clone before_script: + - module load /opt/intel/oneapi/modulefiles/mpi/latest - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts - git submodule init @@ -89,6 +90,7 @@ test_linux: tags: - linux script: + - module load /opt/intel/oneapi/modulefiles/mpi/latest - cd build - ./unit-test-maingo - ./maingo-test-problems-sequential diff --git a/.gitmodules b/.gitmodules index d8b069c..6fad4ae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,39 +1,39 @@ [submodule "dep/filib"] path = dep/filib - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/filib.git + url = ../../public/thirdparty/filib.git [submodule "dep/nlopt"] path = dep/nlopt - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/nlopt.git + url = ../../public/thirdparty/nlopt.git [submodule "dep/fadbad"] path = dep/fadbad - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/fadbad.git + url = ../../public/thirdparty/fadbad.git [submodule "dep/cpplapack"] path = dep/cpplapack - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/cpplapack.git + url = ../../public/thirdparty/cpplapack.git [submodule "dep/ipopt"] path = dep/ipopt - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/ipopt.git + url = ../../public/thirdparty/ipopt.git [submodule "dep/mumps"] path = dep/mumps - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/mumps.git + url = ../../public/thirdparty/mumps.git [submodule "dep/blas"] path = dep/blas - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/blas.git + url = ../../public/thirdparty/blas.git [submodule "dep/lapack"] path = dep/lapack - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/lapack.git + url = ../../public/thirdparty/lapack.git [submodule "dep/knitro"] path = dep/knitro - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/knitro.git + url = ../../public/thirdparty/knitro.git [submodule "dep/cplex"] path = dep/cplex - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/cplex.git + url = ../../public/thirdparty/cplex.git [submodule "dep/clp"] path = dep/clp - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/clp.git + url = ../../public/thirdparty/clp.git [submodule "dep/json"] path = dep/json - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/json.git + url = ../../public/thirdparty/json.git [submodule "dep/pybind11"] path = dep/pybind11 url = https://github.com/pybind/pybind11 @@ -42,13 +42,13 @@ url = https://github.com/google/googletest [submodule "dep/babbase"] path = dep/babbase - url = https://git.rwth-aachen.de/avt-svt/public/babbase.git + url = ../../public/babbase.git [submodule "dep/libale"] path = dep/libale - url = https://git.rwth-aachen.de/avt-svt/public/libale.git + url = ../../public/libale.git [submodule "dep/mcpp"] path = dep/mcpp - url = https://git.rwth-aachen.de/avt-svt/public/thirdparty/mcpp.git + url = ../../public/thirdparty/mcpp.git [submodule "dep/melon"] path = dep/melon - url = https://git.rwth-aachen.de/avt-svt/public/melon.git + url = ../../public/melon.git diff --git a/AUTHORS b/AUTHORS index db5a821..6013fd2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -30,6 +30,7 @@ Contributors: - Jana Hauer (AVT.SVT, RWTH Aachen University) CI/CD & unit testing Refactoring + Python parallelization - Jannik Burre (AVT.SVT, RWTH Aachen University) Wolfgang R. Huster (AVT.SVT, RWTH Aachen University) diff --git a/CMakeLists.txt b/CMakeLists.txt index 58de650..b6c000c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -431,6 +431,7 @@ if(MAiNGO_build_test) COMMAND ${CMAKE_COMMAND} -E make_directory $<$<CONFIG:Debug>:${RUNTIME_OUTPUT_DIRECTORY_DEBUG}>$<$<NOT:$<CONFIG:Debug>>:${RUNTIME_OUTPUT_DIRECTORY_RELEASE}>/individualPythonTests COMMAND ${CMAKE_COMMAND} -E copy + ${PYTHON_TEST_DIR}/individualPythonTests/testErrorHandling.py ${PYTHON_TEST_DIR}/individualPythonTests/testEvaluationContainer.py ${PYTHON_TEST_DIR}/individualPythonTests/testIntrinsicFunctions.py ${PYTHON_TEST_DIR}/individualPythonTests/testMAiNGOmodel.py diff --git a/LICENSE b/LICENSE index e9cbe49..52a0fd3 100644 --- a/LICENSE +++ b/LICENSE @@ -13,7 +13,7 @@ source or binary distributions of MAiNGO are licensed under the following licens - Filib++, NLopt, cpplapack: LGPL - json: MIT - MUMPS: CeCILL-C -- pybind11, netlib LAPACK: Modified BSD +- pybind11, netlib LAPACK, googletest: Modified BSD - netlib BLAS, fadbad++: own licenses The coyprights and license terms are given below. @@ -1554,4 +1554,38 @@ reasonable handling fee. *************************************************************** ANY USE OF THIS CODE CONSTITUTES ACCEPTANCE OF THE TERMS OF THE COPYRIGHT NOTICE -*************************************************************** \ No newline at end of file +*************************************************************** + + +------------------------------------------------------------------------------------------------------ + +googletest: + +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Readme.md b/Readme.md index 85a08a0..75c58f5 100644 --- a/Readme.md +++ b/Readme.md @@ -5,7 +5,8 @@ functionality in GitLab](https://git.rwth-aachen.de/avt-svt/public/maingo/-/issu ## About -General information about the capabilities and main algorithmic features of MAiNGO can be found in our technical report available on [our website](http://permalink.avt.rwth-aachen.de/?id=729717). +MAiNGO is a deterministic global optimization solver for nonconvex mixed-integer nonlinear programs (MINLPs). It is jointly being developed at [RWTH Aachen University](http://permalink.avt.rwth-aachen.de/?id=729717) and [KU Leuven](https://cit.kuleuven.be/creas/researchthemes). +General information about the capabilities and main algorithmic features of MAiNGO can be found in our technical report available [here](http://permalink.avt.rwth-aachen.de/?id=729717). For a detailed installation guide, description of the algorithm, and a complete list of available options, please refer to the [MAiNGO documentation](https://avt-svt.pages.rwth-aachen.de/public/maingo). ## Obtaining MAiNGO @@ -44,7 +45,6 @@ Note that the following features are *not* available in the maingopy package ava If you want to use these features, you should obtain the MAiNGO code via git as described above. This also enables you to build a version of the <tt>maingopy</tt> package that can use CPLEX and KNITRO if you have them installed on your system. -The MPI parallelization is currently not available via the Python interface of MAiNGO even when building it from source. ## First steps diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index ec999c9..365cc2f 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,3 +1,11 @@ +Release version 0.7.0 (July 26th, 2023): + - New features & interfaces: + - MAiNGO can also be used in parallel in python (requires package mpi4py) + - MAiNGO is now also available in JuMP + - Bugfixes: + - in Utility MAiNGOReaderWriter + - in writing into ALE syntax + 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. diff --git a/cmake/MAiNGOversion.cmake b/cmake/MAiNGOversion.cmake index 7528a74..6238904 100644 --- a/cmake/MAiNGOversion.cmake +++ b/cmake/MAiNGOversion.cmake @@ -1,3 +1,3 @@ set(MAiNGO_VERSION -0.6.0 +0.7.0 ) diff --git a/dep/libale b/dep/libale index 69e1c4c..49bacde 160000 --- a/dep/libale +++ b/dep/libale @@ -1 +1 @@ -Subproject commit 69e1c4ce4bb003ffda30283a60d07094dd5585a9 +Subproject commit 49bacdeaa0ae4c2236d01a65c8b31f87a8576926 diff --git a/dep/melon b/dep/melon index cc3783b..7485651 160000 --- a/dep/melon +++ b/dep/melon @@ -1 +1 @@ -Subproject commit cc3783b457a7d209d079303e1d5b67e570fea6e5 +Subproject commit 74856516f95e94fc0b517c9ebba985f4f1c92b09 diff --git a/doc/manual.dox b/doc/manual.dox index 0a378b9..8644b33 100644 --- a/doc/manual.dox +++ b/doc/manual.dox @@ -133,7 +133,7 @@ Note that the following features are *not* available in the maingopy package ava - the C++ API and text-based model parser of MAiNGO - the subsolvers CPLEX and KNITRO -If you want to use these features, you should obtain the MAiNGO code via git as described above. This also enables you to build a version of the <tt>maingopy</tt> package that can use CPLEX and KNITRO if you have them installed on your system. The MPI parallelization is currently not available via the Python interface of MAiNGO even when building it from source. +If you want to use these features, you should obtain the MAiNGO code via git as described above. This also enables you to build a version of the <tt>maingopy</tt> package that can use CPLEX and KNITRO if you have them installed on your system. If you obtain the binary distribution of <tt>maingopy</tt> from PyPI, there is no need to compile anything and you can skip the following steps and directly proceed with \ref executing_maingo_python "executing MAiNGO to solve an example problem" or \ref modeling_cpp_python "writing you own optimization problems". @@ -910,7 +910,6 @@ different algorithms. There is also a parallelized version of MAiNGO, which allows the use of multiple processors via MPI. Its main use is for large problems where the B&B algorithm needs a long time to converge. The parallel version can be accessed and compiled the same way as the sequential version. In order to generate a project of the parallel MAiNGO version please set the CMake variable <tt>MAiNGO_use_mpi=true</tt> and make sure you have an MPI library installed. -Unfortunately, the parallel version is not yet supported when using MAiNGO from Python. To run MAiNGO using MPI, type \code{.sh} @@ -918,6 +917,13 @@ To run MAiNGO using MPI, type \endcode where <tt>\<mpiexec\></tt> is the correct mpiexec executable of your MPI library. +To use the parallel version of MAiNGO from Python the package mpi4py is required. It can easily be installed through PyPI (https://pypi.org/project/mpi4py/). An example of how to use the parallel version from Python can be found in <tt>examples/01_BasicExample/examplePythonInterface.py</tt>. + +To run MAiNGO from Python using MPI, type +\code{.sh} +<mpiexec> -n <numberOfProcessors> python <pathToYourPythonScript> +\endcode + It may happen that MPI asks you for an username and a password. If you type in a wrong password or a wrong username resulting in MPI not allowing you to proceed. In this case you can reset it via <tt>\<mpiexec\> -remove</tt>. diff --git a/examples/01_BasicExample/examplePythonInterface.py b/examples/01_BasicExample/examplePythonInterface.py index 8b7eb6a..f9865d2 100644 --- a/examples/01_BasicExample/examplePythonInterface.py +++ b/examples/01_BasicExample/examplePythonInterface.py @@ -1,6 +1,11 @@ from maingopy import * from math import pi - +#only for the parallel version +if HAVE_MAiNGO_MPI(): + from mpi4py import MPI + MPI.COMM_WORLD + #mute multiple outputs + buffer = muteWorker() # Auxiliary class, just to highlight we can use other classes, functions, etc. in our models class SomeExternalClass(): @@ -129,6 +134,11 @@ myMAiNGO.read_settings(fileName) # If fileName is empty, MAiNGO will attempt to # myMAiNGO.set_iterations_csv_file_name("iterations.csv") # myMAiNGO.set_solution_and_statistics_csv_file_name("solution_and_statistics.csv") +#only for the parallel version +if HAVE_MAiNGO_MPI(): + #wokers must be unmuted before solving model + unmuteWorker(buffer) + # We can have MAiNGO write the current model to a file in a given modeling language. # (As an alternative, this could also be done within the solve function of MAiNGO # through the settings modelWritingLanguage, but with less options for customization) diff --git a/inc/MAiNGO.h b/inc/MAiNGO.h index 7c9ec18..e3229b2 100644 --- a/inc/MAiNGO.h +++ b/inc/MAiNGO.h @@ -420,9 +420,9 @@ class MAiNGO { void _recognize_structure(); /** - * @brief Uses mc::FFDep properties and the DAG to obtain information on the properties of constraints + * @brief Uses mc::FFDep properties and the DAG to obtain information on the properties of constraints and variables */ - void _set_constraint_properties(); + void _set_constraint_and_variable_properties(); /** * @name MAiNGO printing functions @@ -630,6 +630,7 @@ class MAiNGO { std::vector<OptimizationVariable> _originalVariables; /*!< vector holding the original user-defined optimization variables (initial bounds, variable type, name, branching priority) */ std::vector<OptimizationVariable *> _infeasibleVariables; /*!< vector containing pointers to variables in _originalVariables with empty host set */ std::vector<OptimizationVariable> _variables; /*!< vector holding the optimization variables participating in the problem (initial bounds, variable type, name, branching priority) */ + std::vector<bool> _variableIsLinear; /*!< vector storing which variables occur only linearly in the problem */ std::vector<bool> _removedVariables; /*!< vector holding the information on which variable has been removed from the problem */ std::vector<std::string> _uniqueNamesOriginal; /*!< auxiliary needed for parsing MAiNGO to a different modeling language since in most cases unique variable names are required */ std::vector<std::string> _uniqueNames; /*!< auxiliary needed for parsing MAiNGO to a different modeling language since in most cases unique variable names are required. It is holding the not removed variables */ diff --git a/inc/lbp.h b/inc/lbp.h index 8d68218..0ccabe3 100644 --- a/inc/lbp.h +++ b/inc/lbp.h @@ -70,7 +70,8 @@ class LowerBoundingSolver { * @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver * @param[in] DAGvars are the variables corresponding to the DAG * @param[in] DAGfunctions are the functions corresponding to the DAG - * @param[in] variables is a vector containing the initial optimization variables defined in problem.h + * @param[in] variables is a vector containing the optimization variables + * @param[in] variableIsLinear is a vector containing information about which variables occur only linearly * @param[in] nineqIn is the number of inequality constraints * @param[in] neqIn is the number of equality * @param[in] nineqRelaxationOnlyIn is the number of inequality for use only in the relaxed problem @@ -81,7 +82,8 @@ class LowerBoundingSolver { * @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO */ LowerBoundingSolver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions, - const std::vector<babBase::OptimizationVariable> &variables, const unsigned nineqIn, const unsigned neqIn, + const std::vector<babBase::OptimizationVariable> &variables, const std::vector<bool>& variableIsLinear, + const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn); @@ -107,9 +109,10 @@ class LowerBoundingSolver { * @param[in,out] currentNode is the B&B node for which the lower bounding problem should be solved; if OBBT is successful in tightening bounds, currentNode will be modified accordingly * @param[in] currentUBD is the current upper bounds (i.e., incumbent objective value); It is used for the objective function cut if reductionType==OBBT_FEASOPT * @param[in] reductionType determines whether OBBT should include only feasibility or also optimality (i.e., an objective function cut f_cv<=currentUBD) + * @param[in] includeLinearVars determines whether OBBT runs should also be conducted for variables occurring only linearly * @return Return code, see enum TIGHTENING_RETCODE */ - TIGHTENING_RETCODE solve_OBBT(babBase::BabNode ¤tNode, const double currentUBD, const OBBT reductionType); + TIGHTENING_RETCODE solve_OBBT(babBase::BabNode ¤tNode, const double currentUBD, const OBBT reductionType, const bool includeLinearVars = false); /** * @brief Function called by B&B solver for constraint propagation. This function is virtual as it may be overwritten for certain LBD solvers. @@ -751,7 +754,8 @@ class LowerBoundingSolver { const unsigned _neqRelaxationOnly; /*!< number of non-constant equalities for use only in the relaxed problem */ const unsigned _nineqSquash; /*!< number of non-constant squash inequalities in the original (not relaxed) problem*/ bool _onlyBoxConstraints; /*!< flag indicating whether the relaxed problem has only box constraints, i.e., all constraint counters are 0 */ - std::vector<babBase::OptimizationVariable> _originalVariables; /*!< original variables (i.e., original upper and lower bounds, info on which variables are binary etc., cf. structs.h) */ + std::vector<babBase::OptimizationVariable> _originalVariables; /*!< original variables (i.e., original upper and lower bounds, info on which variables are binary etc.) */ + std::vector<bool> _variableIsLinear; /*!< information about which variables occur only linearly in the problem */ double _objectiveValue; /*!< variable holding the objective value of the linear program for MAiNGO solver and Interval solver */ std::vector<double> _solutionPoint; /*!< vector storing the solution point for MAiNGO solver and Interval solver */ std::vector<double> _multipliers; /*!< vector storing the multipliers for MAiNGO solver and Interval solver */ @@ -795,7 +799,8 @@ class LowerBoundingSolver { * @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver * @param[in] DAGvars are the variables corresponding to the DAG * @param[in] DAGfunctions are the functions corresponding to the DAG -* @param[in] variables is a vector containing the initial optimization variables defined in problem.h +* @param[in] variables is a vector containing the optimization variables +* @param[in] variableIsLinear is a vector containing information about which variables occur only linearly * @param[in] nineqIn is the number of inequality constraints * @param[in] neqIn is the number of equality * @param[in] nineqRelaxationOnlyIn is the number of inequality for use only in the relaxed problem @@ -806,7 +811,8 @@ class LowerBoundingSolver { * @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO */ std::shared_ptr<LowerBoundingSolver> make_lbp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions, - const std::vector<babBase::OptimizationVariable> &variables, const unsigned nineqIn, const unsigned neqIn, + const std::vector<babBase::OptimizationVariable> &variables, const std::vector<bool>& variableIsLinear, + const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn); diff --git a/inc/lbpClp.h b/inc/lbpClp.h index d72ab91..3655179 100644 --- a/inc/lbpClp.h +++ b/inc/lbpClp.h @@ -40,7 +40,8 @@ class LbpClp: public LowerBoundingSolver { * @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver * @param[in] DAGvars are the variables corresponding to the DAG * @param[in] DAGfunctions are the functions corresponding to the DAG - * @param[in] variables is a vector containing the initial optimization variables defined in problem.h + * @param[in] variables is a vector containing the optimization variables + * @param[in] variableIsLinear is a vector containing information about which variables occur only linearly * @param[in] nineqIn is the number of inequality constraints * @param[in] neqIn is the number of equality * @param[in] nineqRelaxationOnlyIn is the number of inequality for use only in the relaxed problem @@ -51,7 +52,7 @@ class LbpClp: public LowerBoundingSolver { * @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO */ LbpClp(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions, - const std::vector<babBase::OptimizationVariable> &variables, const unsigned nineqIn, const unsigned neqIn, + const std::vector<babBase::OptimizationVariable> &variables, const std::vector<bool>& variableIsLinear, const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn); diff --git a/inc/lbpCplex.h b/inc/lbpCplex.h index 22d0900..907870c 100644 --- a/inc/lbpCplex.h +++ b/inc/lbpCplex.h @@ -38,7 +38,8 @@ class LbpCplex: public LowerBoundingSolver { * @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver * @param[in] DAGvars are the variables corresponding to the DAG * @param[in] DAGfunctions are the functions corresponding to the DAG - * @param[in] variables is a vector containing the initial optimization variables defined in problem.h + * @param[in] variables is a vector containing the optimization variables + * @param[in] variableIsLinear is a vector containing information about which variables occur only linearly * @param[in] nineqIn is the number of inequality constraints * @param[in] neqIn is the number of equality * @param[in] nineqRelaxationOnlyIn is the number of inequality for use only in the relaxed problem @@ -49,7 +50,7 @@ class LbpCplex: public LowerBoundingSolver { * @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO */ LbpCplex(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions, - const std::vector<babBase::OptimizationVariable> &variables, const unsigned nineqIn, const unsigned neqIn, + const std::vector<babBase::OptimizationVariable> &variables, const std::vector<bool>& variableIsLinear, const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn); diff --git a/inc/lbpInterval.h b/inc/lbpInterval.h index 7dc1366..4656972 100644 --- a/inc/lbpInterval.h +++ b/inc/lbpInterval.h @@ -34,7 +34,8 @@ class LbpInterval: public LowerBoundingSolver { * @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver * @param[in] DAGvars are the variables corresponding to the DAG * @param[in] DAGfunctions are the functions corresponding to the DAG - * @param[in] variables is a vector containing the initial optimization variables defined in problem.h + * @param[in] variables is a vector containing the optimization variables + * @param[in] variableIsLinear is a vector containing information about which variables occur only linearly * @param[in] nineqIn is the number of inequality constraints * @param[in] neqIn is the number of equality * @param[in] nineqRelaxationOnlyIn is the number of inequality for use only in the relaxed problem @@ -45,7 +46,7 @@ class LbpInterval: public LowerBoundingSolver { * @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO */ LbpInterval(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions, - const std::vector<babBase::OptimizationVariable> &variables, const unsigned nineqIn, const unsigned neqIn, + const std::vector<babBase::OptimizationVariable> &variables, const std::vector<bool>& variableIsLinear, const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn); diff --git a/inc/pointIsWithinNodeBounds.h b/inc/pointIsWithinNodeBounds.h index 72d141e..969f0a9 100644 --- a/inc/pointIsWithinNodeBounds.h +++ b/inc/pointIsWithinNodeBounds.h @@ -1,45 +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); - - +/********************************************************************************** + * 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/maingopy/Readme.md b/maingopy/Readme.md index d1bd06a..c4684c7 100644 --- a/maingopy/Readme.md +++ b/maingopy/Readme.md @@ -14,12 +14,13 @@ To obtain it via PyPI, run $ pip install maingopy -This will typically get you the binary distribution of the maingopy package that contains a pre-compiled version of MAiNGO along with its Python bindings, -as well as an extension module for [MeLOn](https://git.rwth-aachen.de/avt-svt/public/melon), which contains machine learning models for use in optimization problems to be solved by MAiNGO. -Note that the pre-compiled version of MAiNGO contained in this package does not allow the use of the optional closed-source subsolvers CPLEX or KNITRO, even if they are installed on your system. +This will typically get you the binary distribution of the maingopy package that contains a pre-compiled version of MAiNGO along with its Python bindings, as well as an extension module for [MeLOn](https://git.rwth-aachen.de/avt-svt/public/melon), which contains machine learning models for use in optimization problems to be solved by MAiNGO. -To build maingopy (and melonpy) from source along with MAiNGO, obtain the code from our [GitLab page](https://git.rwth-aachen.de/avt-svt/public/maingo) and follow the instructions provided there. -This also allows using CPLEX or KNITRO if you have them installed. +Note that the pre-compiled version of MAiNGO contained in this package does not allow the use of +1. the optional closed-source subsolvers CPLEX or KNITRO, even if they are installed on your system, +2. the MPI parallelization of MAiNGO. + +To use these features, you will need to build maingopy from source. In this case, please obtain the code from our [GitLab page](https://git.rwth-aachen.de/avt-svt/public/maingo) and follow the instructions provided there. ## Using maingopy diff --git a/maingopy/_maingopy.cpp b/maingopy/_maingopy.cpp index efe172f..f614b0e 100644 --- a/maingopy/_maingopy.cpp +++ b/maingopy/_maingopy.cpp @@ -12,7 +12,9 @@ #include "MAiNGO.h" #include "MAiNGOmodel.h" #include "MAiNGOmodelEpsCon.h" +#include "MAiNGOException.h" #include "functionWrapper.h" +#include "mpiUtilities.h" #include "babOptVar.h" @@ -24,6 +26,50 @@ namespace py = pybind11; +// make pre-processor variable HAVE_MAiNGO_MPI available for Python API +bool haveMPI() { + +#ifdef HAVE_MAiNGO_MPI + return true; +#else + return false; +#endif +} + +struct BufferPair +{ + std::streambuf* coutBuf; + std::streambuf* cerrBuf; +}; + + +BufferPair muteWorkerOutput(){ + +#ifdef HAVE_MAiNGO_MPI + + int _rank; + MPI_Comm_rank(MPI_COMM_WORLD, &_rank); + + // Mute cout for workers to avoid multiple outputs + std::ostringstream mutestream; + + MAiNGO_IF_BAB_WORKER + std::cout.rdbuf(mutestream.rdbuf()); + std::cerr.rdbuf(mutestream.rdbuf()); + MAiNGO_END_IF +#endif + BufferPair curBuffer; + curBuffer.coutBuf = std::cout.rdbuf(); + curBuffer.cerrBuf = std::cerr.rdbuf(); + return curBuffer; +} + + +void unmuteWorkerOutput(BufferPair originalBuffer) { + std::cout.rdbuf(originalBuffer.coutBuf); + std::cerr.rdbuf(originalBuffer.cerrBuf); + return; +} // First, create a dummy classes that is needed later to expose the MAiNGOmodel base class // so that it can be used and inherited from in Python @@ -411,4 +457,20 @@ PYBIND11_MODULE(_maingopy, m) .def_readwrite("description", &maingo::OutputVariable::description) .def_readwrite("value", &maingo::OutputVariable::value) .def(py::self == py::self); + + // MPI + py::class_<BufferPair>(m, "BufferPair"); + m.def("HAVE_MAiNGO_MPI", &haveMPI, "make pre-processor variable HAVE_MAiNGO_MPI available for Python API"); + m.def("muteWorker", &muteWorkerOutput, "Mute Output for workers to avoid multiple outputs"); + m.def("unmuteWorker", &unmuteWorkerOutput, "Unmute Output for workers"); + //Expose MAiNGOException + static py::exception<maingo::MAiNGOException> exc(m, "MAiNGOException"); + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) std::rethrow_exception(p); + } + catch (const maingo::MAiNGOException& e) { + exc(e.what()); + } + }); } \ No newline at end of file diff --git a/maingopy/tests/individualPythonTests/testErrorHandling.py b/maingopy/tests/individualPythonTests/testErrorHandling.py new file mode 100644 index 0000000..61a395f --- /dev/null +++ b/maingopy/tests/individualPythonTests/testErrorHandling.py @@ -0,0 +1,31 @@ +from maingopy import * +import unittest + +class ModelEmptyVariables(MAiNGOmodel): + def __init__(self): + MAiNGOmodel.__init__(self) + + def get_variables(self): + variables = [] + return variables + + def get_initial_point(self): + return [0.5, + 2.5] + + def evaluate(self, vars): + result = EvaluationContainer() + result.obj = vars[0] * vars[1] + result.ineq = [0.5 - vars[0], + 2.5 - vars[1]] + return result + +class TestMAiNGOmodel(unittest.TestCase): + def test_model_empty_variables(self): + model = ModelEmptyVariables() + with self.assertRaises(MAiNGOException): + myModel = ModelEmptyVariables() + myMAiNGO = MAiNGO(myModel) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/maingopy/tests/testMaingopy.py b/maingopy/tests/testMaingopy.py index 28aa82f..949a94b 100644 --- a/maingopy/tests/testMaingopy.py +++ b/maingopy/tests/testMaingopy.py @@ -3,6 +3,7 @@ from individualPythonTests.testIntrinsicFunctions import * from individualPythonTests.testEvaluationContainer import * from individualPythonTests.testMAiNGOmodel import * from individualPythonTests.testSolver import * +from individualPythonTests.testErrorHandling import * import unittest if __name__ == '__main__': diff --git a/setup.py b/setup.py index 351946c..12c0271 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def exclude_static_libraries(cmake_manifest): setup( name='maingopy', version=MAiNGOversion, - author='Dominik Bongartz, Jaromil Najman, Susanne Sass, Alexander Mitsos', + author='Dominik Bongartz, Jaromil Najman, Susanne Sass, Clara Witte, Alexander Mitsos', author_email='MAiNGO@avt.rwth-aachen.de', description='A Python package for using MAiNGO - McCormick-based Algorithm for mixed-integer Nonlinear Global Optimization', long_description=long_description, @@ -47,10 +47,11 @@ setup( 'Topic :: Scientific/Engineering', 'License :: OSI Approved :: Eclipse Public License 2.0 (EPL-2.0)', 'Programming Language :: C++', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', 'Operating System :: MacOS :: MacOS X', diff --git a/src/MAiNGO.cpp b/src/MAiNGO.cpp index a583863..c5aab75 100644 --- a/src/MAiNGO.cpp +++ b/src/MAiNGO.cpp @@ -534,7 +534,7 @@ MAiNGO::_analyze_and_solve_problem() try { _recognize_structure(); - _set_constraint_properties(); + _set_constraint_and_variable_properties(); MAiNGO_MPI_BARRIER } catch (const std::exception& e) { @@ -629,7 +629,7 @@ MAiNGO::_analyze_and_solve_problem() } } else { - _set_constraint_properties(); + _set_constraint_and_variable_properties(); _initialize_solve(); // Needed to properly clear everything _preprocessTime = get_cpu_time() - _preprocessTime; @@ -1302,11 +1302,11 @@ MAiNGO::_initialize_solve() if (!_maingoSettings->PRE_pureMultistart) { // For a pure multistart, lower bounding solver and the B&B tree are not needed _myUBSBab = ubp::make_ubp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _nineq, _neq, _nineqSquash, _maingoSettings, _logger, _nonconstantConstraintsUBP, ubp::UpperBoundingSolver::USE_BAB); if (_maingoSettings->LBP_addAuxiliaryVars) { - _myLBS = lbp::make_lbp_solver(_DAGlbd, _DAGvarsLbd, _DAGfunctionsLbd, _variablesLbd, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly + _nauxiliaryRelOnlyEqs, + _myLBS = lbp::make_lbp_solver(_DAGlbd, _DAGvarsLbd, _DAGfunctionsLbd, _variablesLbd, _variableIsLinear, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly + _nauxiliaryRelOnlyEqs, _nineqSquash, _maingoSettings, _logger, _nonconstantConstraints); } else { - _myLBS = lbp::make_lbp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly, + _myLBS = lbp::make_lbp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _variableIsLinear, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly, _nineqSquash, _maingoSettings, _logger, _nonconstantConstraints); } #ifdef HAVE_GROWING_DATASETS @@ -1320,11 +1320,11 @@ MAiNGO::_initialize_solve() #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, + _myLBSFull = lbp::make_lbp_solver(_DAGlbd, _DAGvarsLbd, _DAGfunctionsLbd, _variablesLbd, _variableIsLinear, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly + _nauxiliaryRelOnlyEqs, _nineqSquash, _maingoSettings, _logger, _nonconstantConstraints); } else { - _myLBSFull = lbp::make_lbp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly, + _myLBSFull = lbp::make_lbp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _variableIsLinear, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly, _nineqSquash, _maingoSettings, _logger, _nonconstantConstraints); } _myBaB->pass_LBSFull_to_bab(_myLBSFull); @@ -1405,7 +1405,7 @@ MAiNGO::_root_obbt_feasibility() _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); + _rootObbtStatus = _myLBS->solve_OBBT(_rootNode, _maingoSettings->infinity, lbp::OBBT_FEAS, true /*include linear variables*/); } catch (std::exception& e) { #ifdef HAVE_MAiNGO_MPI @@ -1474,7 +1474,7 @@ MAiNGO::_root_obbt_feasibility_optimality() babBase::BabNode tmpNode(_rootNode); try { - _rootObbtStatus = _myLBS->solve_OBBT(tmpNode, _solutionValue, lbp::OBBT_FEASOPT); + _rootObbtStatus = _myLBS->solve_OBBT(tmpNode, _solutionValue, lbp::OBBT_FEASOPT, true /*include linear variables*/); } catch (std::exception& e) { #ifdef HAVE_MAiNGO_MPI @@ -2585,7 +2585,7 @@ MAiNGO::_add_auxiliary_variables_to_lbd_dag() ///////////////////////////////////////////////////////////////////////// // sets function properties, number of variables and type (linear, bilinear...) void -MAiNGO::_set_constraint_properties() +MAiNGO::_set_constraint_and_variable_properties() { // Get dependency sets of all functions @@ -2601,12 +2601,15 @@ MAiNGO::_set_constraint_properties() } } + // Prepare for determining which variable occurs only linearly (except in output functions) + _variableIsLinear = std::vector<bool>(_nvarLbd, true); + // Loop over all functions unsigned indexLinear = 0, indexNonlinear = 0; for (unsigned int i = 0; i < size; i++) { mc::FFDep::TYPE functionStructure = mc::FFDep::L; std::vector<unsigned> participatingVars; - for (unsigned int j = 0; j < _nvar; j++) { + for (unsigned int j = 0; j < _nvarLbd; j++) { auto ito = func_dep[i].find(j); // Count all participating variables if (ito != func_dep[i].end()) { @@ -2616,6 +2619,9 @@ MAiNGO::_set_constraint_properties() if (functionStructure < variableDep) { functionStructure = variableDep; } + if (variableDep > mc::FFDep::TYPE::L) { + _variableIsLinear[j] = false; + } } } (*_nonconstantConstraints)[i].nparticipatingVariables = participatingVars.size(); @@ -2705,4 +2711,11 @@ MAiNGO::_set_constraint_properties() break; // We don't use relaxation only constraints in the ubp } } + + // Set branching priorities on linear variables to zero to avoid branching on them + for (size_t i = 0; i < _nvarLbd; i++) { + if (_variableIsLinear[i]) { + _variablesLbd[i].set_branching_priority(0.); + } + } } diff --git a/src/MAiNGOtoOtherLanguage.cpp b/src/MAiNGOtoOtherLanguage.cpp index f90c437..1cb5c96 100644 --- a/src/MAiNGOtoOtherLanguage.cpp +++ b/src/MAiNGOtoOtherLanguage.cpp @@ -208,7 +208,7 @@ MAiNGO::_write_gams_variables(std::ofstream &gamsFile) } } else if (_originalVariables[i].get_variable_type() == VT_BINARY) { - if (binVariables.length() > 200 + lengthCounterBin * 200) { + if (binVariables.length() + currentName.length() > 200 + lengthCounterBin * 200) { binVariables = binVariables + currentName + ",\n "; lengthCounterBin++; } @@ -217,7 +217,7 @@ MAiNGO::_write_gams_variables(std::ofstream &gamsFile) } } else if (_originalVariables[i].get_variable_type() == VT_INTEGER) { - if (intVariables.length() > 200 + lengthCounterInt * 200) { + if (intVariables.length() + currentName.length() > 200 + lengthCounterInt * 200) { intVariables = intVariables + currentName + ",\n "; lengthCounterInt++; } diff --git a/src/lbp.cpp b/src/lbp.cpp index 9bc12f8..c929e71 100644 --- a/src/lbp.cpp +++ b/src/lbp.cpp @@ -25,9 +25,10 @@ using namespace lbp; ///////////////////////////////////////////////////////////////////////////////////////////// // constructor for the lower bounding solver LowerBoundingSolver::LowerBoundingSolver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions, - const std::vector<babBase::OptimizationVariable> &variables, const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, + const std::vector<babBase::OptimizationVariable> &variables, const std::vector<bool>& variableIsLinear, + const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn): - _originalVariables(variables), + _originalVariables(variables), _variableIsLinear(variableIsLinear), _nvar(variables.size()), _nineq(nineqIn), _neq(neqIn), _nineqRelaxationOnly(nineqRelaxationOnlyIn), _neqRelaxationOnly(neqRelaxationOnlyIn), _nineqSquash(nineqSquashIn), _maingoSettings(settingsIn), _logger(loggerIn), _constraintProperties(constraintPropertiesIn) { @@ -527,7 +528,7 @@ LowerBoundingSolver::solve_LBP(const babBase::BabNode ¤tNode, double &lowe ///////////////////////////////////////////////////////////////////////////////////////////// // solve LP for optimization-based range reduction TIGHTENING_RETCODE -LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double currentUBD, const OBBT reductionType) +LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double currentUBD, const OBBT reductionType, const bool includeLinearVars) { if ((reductionType == OBBT_FEAS) && _onlyBoxConstraints) { @@ -591,8 +592,10 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode ¤tNode, const double curr std::list<unsigned> toTreatMax, toTreatMin; std::vector<double> lastPoint(_nvar); for (unsigned ivar = 0; ivar < _nvar; ivar++) { // Note that we also treat auxiliaries (if any are added) - toTreatMax.push_back(ivar); - toTreatMin.push_back(ivar); + if (!_variableIsLinear[ivar] || includeLinearVars) { + toTreatMax.push_back(ivar); + toTreatMin.push_back(ivar); + } lastPoint[ivar] = 0.5 * (lowerVarBounds[ivar] + upperVarBounds[ivar]); } // Modify treatment of objective function diff --git a/src/lbpClp.cpp b/src/lbpClp.cpp index 7b267f5..d490342 100644 --- a/src/lbpClp.cpp +++ b/src/lbpClp.cpp @@ -23,9 +23,9 @@ using namespace lbp; ///////////////////////////////////////////////////////////////////////////////////////////// // constructor for the lower bounding solver LbpClp::LbpClp(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions, const std::vector<babBase::OptimizationVariable> &variables, - const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, - std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn): - LowerBoundingSolver(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn) + const std::vector<bool>& variableIsLinear, const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, + std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn): + LowerBoundingSolver(DAG, DAGvars, DAGfunctions, variables, variableIsLinear, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn) { try { diff --git a/src/lbpCplex.cpp b/src/lbpCplex.cpp index ef23fff..6245882 100644 --- a/src/lbpCplex.cpp +++ b/src/lbpCplex.cpp @@ -23,9 +23,9 @@ using namespace lbp; ///////////////////////////////////////////////////////////////////////////////////////////// // constructor for the lower bounding solver LbpCplex::LbpCplex(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions, const std::vector<babBase::OptimizationVariable> &variables, - const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, - std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn): - LowerBoundingSolver(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn) + const std::vector<bool>& variableIsLinear, const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, + std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn): + LowerBoundingSolver(DAG, DAGvars, DAGfunctions, variables, variableIsLinear, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn) { try { diff --git a/src/lbpFactory.cpp b/src/lbpFactory.cpp index 7e583a4..6650f81 100644 --- a/src/lbpFactory.cpp +++ b/src/lbpFactory.cpp @@ -28,8 +28,8 @@ using namespace lbp; // function for initializing the different lower bounding solver wrappers std::shared_ptr<LowerBoundingSolver> lbp::make_lbp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions, - const std::vector<babBase::OptimizationVariable> &variables, const unsigned nineqIn, const unsigned neqIn, - const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, + const std::vector<babBase::OptimizationVariable> &variables, const std::vector<bool>& variableIsLinear, + const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn) { @@ -37,18 +37,18 @@ lbp::make_lbp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, co case LBP_SOLVER_MAiNGO: { loggerIn->print_message(" Lower bounding: MAiNGO internal solver (McCormick relaxations for objective, intervals for constraints)\n", VERB_NORMAL, BAB_VERBOSITY); - return std::make_shared<LowerBoundingSolver>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, + return std::make_shared<LowerBoundingSolver>(DAG, DAGvars, DAGfunctions, variables, variableIsLinear, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn); } case LBP_SOLVER_INTERVAL: { loggerIn->print_message(" Lower bounding: Interval extensions\n", VERB_NORMAL, BAB_VERBOSITY); - return std::make_shared<LbpInterval>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, + return std::make_shared<LbpInterval>(DAG, DAGvars, DAGfunctions, variables, variableIsLinear, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn); } case LBP_SOLVER_CPLEX: { #ifdef HAVE_CPLEX loggerIn->print_message(" Lower bounding: CPLEX\n", VERB_NORMAL, BAB_VERBOSITY); - return std::make_shared<LbpCplex>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, + return std::make_shared<LbpCplex>(DAG, DAGvars, DAGfunctions, variables, variableIsLinear, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn); #else throw MAiNGOException(" Error in LbpFactory: Cannot use lower bounding strategy LBP_SOLVER_CPLEX: Your MAiNGO build does not contain CPLEX."); @@ -56,7 +56,7 @@ lbp::make_lbp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, co } case LBP_SOLVER_CLP: { loggerIn->print_message(" Lower bounding: CLP\n", VERB_NORMAL, BAB_VERBOSITY); - return std::make_shared<LbpClp>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, + return std::make_shared<LbpClp>(DAG, DAGvars, DAGfunctions, variables, variableIsLinear, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn); } default: { diff --git a/src/lbpInterval.cpp b/src/lbpInterval.cpp index ac29ff7..0868e1b 100644 --- a/src/lbpInterval.cpp +++ b/src/lbpInterval.cpp @@ -21,9 +21,9 @@ using namespace lbp; ///////////////////////////////////////////////////////////////////////// // constructor for the lower bounding solver LbpInterval::LbpInterval(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions, const std::vector<babBase::OptimizationVariable> &variables, - const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, + const std::vector<bool>& variableIsLinear, const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn, std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn): - LowerBoundingSolver(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn) + LowerBoundingSolver(DAG, DAGvars, DAGfunctions, variables, variableIsLinear, nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn) { // We only need the Interval stuff if we want to use natural interval extensions diff --git a/src/pointIsWithinNodeBounds.cpp b/src/pointIsWithinNodeBounds.cpp index bbf3d05..2fa9c86 100644 --- a/src/pointIsWithinNodeBounds.cpp +++ b/src/pointIsWithinNodeBounds.cpp @@ -1,48 +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()); -} - - +/********************************************************************************** + * 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/ubp.cpp b/src/ubp.cpp index b685bf9..4e6adf3 100644 --- a/src/ubp.cpp +++ b/src/ubp.cpp @@ -774,7 +774,7 @@ 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 ( (std::fabs(modelOutput[i + 1 + _nineq + _nineqSquash]) > _maingoSettings->deltaEq) || (std::isnan(modelOutput[i + 1 + _nineq + _nineqSquash])) ) { 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); @@ -791,7 +791,7 @@ SUBSOLVER_RETCODE UpperBoundingSolver::_check_ineq(const std::vector<double> &modelOutput) const { for (unsigned int i = 0; i < _nineq; i++) { - if (modelOutput[i + 1] > _maingoSettings->deltaIneq) { + if ( (modelOutput[i + 1] > _maingoSettings->deltaIneq) || (std::isnan(modelOutput[i + 1])) ) { 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); @@ -809,8 +809,7 @@ 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 ( (modelOutput[i + 1 + _nineq] > 0) || (std::isnan(modelOutput[i + 1 + _nineq])) ) { 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); @@ -923,9 +922,7 @@ UpperBoundingSolver::check_feasibility(const std::vector<double> ¤tPoint, objectiveValue = _DAGobj->resultDouble[0]; - if (std::isgreaterequal(objectiveValue, objectiveValue)) { - // Ok, the objective is non NaN (isgreaterequal returns false for NaN) - + if (! std::isnan(objectiveValue)) { // Return the objective value and print solution if desired std::ostringstream outstr; diff --git a/tests/unitTests/testPointIsWithinNodeBounds.cpp b/tests/unitTests/testPointIsWithinNodeBounds.cpp index 33341d8..877bf05 100644 --- a/tests/unitTests/testPointIsWithinNodeBounds.cpp +++ b/tests/unitTests/testPointIsWithinNodeBounds.cpp @@ -1,75 +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); +/********************************************************************************** + * 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/src/MAiNGOReaderWriter.cpp b/utilities/MAiNGO_Reader_Writer/src/MAiNGOReaderWriter.cpp index cfd932a..2a579ba 100644 --- a/utilities/MAiNGO_Reader_Writer/src/MAiNGOReaderWriter.cpp +++ b/utilities/MAiNGO_Reader_Writer/src/MAiNGOReaderWriter.cpp @@ -687,46 +687,70 @@ MAiNGOReaderWriter::_extract_constraints() // Get objective function unsigned int nObj = 0; unsigned int objCon; - for (unsigned int i = 0; i < _ncons; i++) { - if (_constraints[i].lhs.find(" " + _objName + " ") != std::string::npos || _constraints[i].lhs.find(" " + _objName) != std::string::npos || _constraints[i].lhs.find(_objName + " ") != std::string::npos) { - if (_constraints[i].constraintType == CT_EQ) { - nObj++; - } - else { - nObj += 2; - } - // Check whether the objective is part of some operation, e.g., c*objective - std::size_t pos = _constraints[i].lhs.find(_objName + " "); - if (pos != std::string::npos && pos > 0 /*It's not the beginning of the line*/) { - if (_constraints[i].lhs[pos] != ' ') { // If it is not a whitespace, it's most likely a multiplication or something else so just leave it as it is - nObj += 2; - } - } - objCon = i; - } - if (_constraints[i].rhs.find(" " + _objName + " ") != std::string::npos || _constraints[i].rhs.find(" " + _objName) != std::string::npos || _constraints[i].rhs.find(_objName + " ") != std::string::npos) { - if (_constraints[i].constraintType == CT_EQ) { - nObj++; - } - else { - nObj += 2; - } - // Check whether the objective is part of some operation, e.g., c*objective - std::size_t pos = _constraints[i].rhs.find(_objName + " "); - if (pos != std::string::npos && pos > 0 /*It's not the beginning of the line*/) { - if (_constraints[i].rhs[pos] != ' ') { // If it is not a whitespace, it's most likely a multiplication or something else so just leave it as it is - nObj += 2; - } - } - objCon = i; - } - } + for (unsigned int i = 0; i < _ncons; i++) { + if (_constraints[i].lhs.find(" " + _objName + " ") != std::string::npos || _constraints[i].lhs.find(" " + _objName) != std::string::npos || _constraints[i].lhs.find(_objName + " ") != std::string::npos) { + if (_constraints[i].constraintType == CT_EQ) { + nObj++; + } + else { + nObj += 2; + } + // Check whether the objective is part of some operation, e.g., c*objective + if (_constraints[i].lhs.find(" " + _objName + " ") != std::string::npos) { + std::size_t pos = _constraints[i].lhs.find(" " + _objName + " "); + if ((pos + _objName.length() + 2) < _constraints[i].lhs.length() && _constraints[i].lhs[pos + _objName.length() + 2] != '+' && _constraints[i].lhs[pos + _objName.length() + 2] != '-' || pos > 0 && _constraints[i].lhs[pos - 1] != '+' && _constraints[i].lhs[pos - 1] != '-') { + nObj += 2; + } + } + else if (_constraints[i].lhs.find(" " + _objName) != std::string::npos) { + std::size_t pos = _constraints[i].lhs.find(" " + _objName); + if ((pos + _objName.length() + 1) < _constraints[i].lhs.length() && _constraints[i].lhs[pos + _objName.length() + 1] != '+' && _constraints[i].lhs[pos + _objName.length() + 1] != '-' || _constraints[i].lhs[pos - 1] != '+' && _constraints[i].lhs[pos - 1] != '-') { + nObj += 2; + } + } + else { + std::size_t pos = _constraints[i].lhs.find(_objName + " "); + if (_constraints[i].lhs[pos + _objName.length() + 1] != '+' && _constraints[i].lhs[pos + _objName.length() + 1] != '-' || pos > 0 && _constraints[i].lhs[pos - 1] != '+' && _constraints[i].lhs[pos - 1] != '-') { + nObj += 2; + } + } + objCon = i; + } + if (_constraints[i].rhs.find(" " + _objName + " ") != std::string::npos || _constraints[i].rhs.find(" " + _objName) != std::string::npos || _constraints[i].rhs.find(_objName + " ") != std::string::npos) { + if (_constraints[i].constraintType == CT_EQ) { + nObj++; + } + else { + nObj += 2; + } + // Check whether the objective is part of some operation, e.g., c*objective + if (_constraints[i].rhs.find(" " + _objName + " ") != std::string::npos) { + std::size_t pos = _constraints[i].rhs.find(" " + _objName + " "); + if ((pos + _objName.length() + 2) < _constraints[i].rhs.length() && _constraints[i].rhs[pos + _objName.length() + 2] != '+' && _constraints[i].rhs[pos + _objName.length() + 2] != '-' || pos > 0 && _constraints[i].rhs[pos - 1] != '+' && _constraints[i].rhs[pos - 1] != '-') { // check if before and after obj is + or - + nObj += 2; + } + } + else if (_constraints[i].rhs.find(" " + _objName) != std::string::npos) { + std::size_t pos = _constraints[i].rhs.find(" " + _objName); + if ((pos + _objName.length() + 1) < _constraints[i].rhs.length() && _constraints[i].rhs[pos + _objName.length() + 1] != '+' && _constraints[i].rhs[pos + _objName.length() + 1] != '-' || _constraints[i].rhs[pos - 1] != '+' && _constraints[i].rhs[pos - 1] != '-') { // check if before and after obj is + or - + nObj += 2; + } + } + else { + std::size_t pos = _constraints[i].rhs.find(_objName + " "); + if (_constraints[i].rhs[pos + _objName.length() + 1] != '+' && _constraints[i].rhs[pos + _objName.length() + 1] != '-' || pos > 0 && _constraints[i].rhs[pos - 1] != '+' && _constraints[i].rhs[pos - 1] != '-') { // check if before and after obj is + or - + nObj += 2; + } + } + objCon = i; + } + } _objSingle = (nObj > 1) ? false : true; if (_objSingle) { unsigned int posObj; unsigned int additionalLength = 0; if (_constraints[objCon].lhs.find(" " + _objName + " ") != std::string::npos || _constraints[objCon].lhs.find(" " + _objName) != std::string::npos || _constraints[objCon].lhs.find(_objName + " ") != std::string::npos) { - if (_constraints[objCon].lhs.find(" " + _objName + " ") != std::string::npos) { + if (_constraints[objCon].lhs.find(" " + _objName + " ") != std::string::npos) { posObj = static_cast<unsigned int>(_constraints[objCon].lhs.find(" " + _objName + " ")); additionalLength = 2; } @@ -735,7 +759,7 @@ MAiNGOReaderWriter::_extract_constraints() additionalLength = 1; } else { - posObj = static_cast<unsigned int>(_constraints[objCon].lhs.find(_objName + " ")); + posObj = static_cast<unsigned int>(_constraints[objCon].lhs.find(_objName + " ")); additionalLength = 1; } posObj = posObj == 0 ? posObj + 1 : posObj; @@ -787,10 +811,10 @@ MAiNGOReaderWriter::_extract_constraints() _objFunction = "-(" + _constraints[objCon].rhs + ")"; } else { - _objFunction = "-(" + _constraints[objCon].rhs + ") - (" + _constraints[objCon].lhs + ")"; + _objFunction = "-(" + _constraints[objCon].rhs + ") + (" + _constraints[objCon].lhs + ")"; } } - if (_constraints[objCon].rhs[posObj - 1] == '-') { + else if (_constraints[objCon].rhs[posObj - 1] == '-') { _constraints[objCon].rhs.erase(posObj - 1, 1 + additionalLength + _objName.length()); if (_constraints[objCon].lhs.compare("0") == 0) { _objFunction = _constraints[objCon].rhs; @@ -799,6 +823,15 @@ MAiNGOReaderWriter::_extract_constraints() _objFunction = _constraints[objCon].rhs + " - (" + _constraints[objCon].lhs + ")"; } } + else { + _constraints[objCon].rhs.erase(posObj - 1, 1 + additionalLength + _objName.length()); + if (_constraints[objCon].lhs.compare("0") == 0) { + _objFunction = "-(" + _constraints[objCon].rhs + ")"; + } + else { + _objFunction = "-(" + _constraints[objCon].rhs + ") + (" + _constraints[objCon].lhs + ")"; + } + } } _constraints.erase(_constraints.begin() + objCon); _ncons--; @@ -1090,9 +1123,11 @@ MAiNGOReaderWriter::_rename_double_asterisk(std::string &str) } } // We can recognize exp, log, pow, sin, max, min, tan, cos, abs - if (str[pos - 1] == 'p' || str[pos - 1] == 'g' || str[pos - 1] == 'w' || str[pos - 1] == 'n' || str[pos - 1] == 'x' || str[pos - 1] == 's') { - pos -= 3; - } + if (pos != 0) { + if (str[pos - 1] == 'p' || str[pos - 1] == 'g' || str[pos - 1] == 'w' || str[pos - 1] == 'n' || str[pos - 1] == 'x' || str[pos - 1] == 's') { + pos -= 3; + } + } str.insert(pos, "pow("); } else { @@ -1100,7 +1135,7 @@ MAiNGOReaderWriter::_rename_double_asterisk(std::string &str) while (str[pos] != ' ' && str[pos] != '*' && str[pos] != '/' && str[pos] != '+' && str[pos] != '-' && str[pos] != '(' && pos > 0) { pos--; } - if (pos != 0) { + if (pos != 0 || (pos==0 && str[pos] == '-')) { str.insert(pos + 1, "pow("); } else { -- GitLab