Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
[submodule "ThirdParty/CppAD"]
path = ThirdParty/CppAD
url = https://github.com/coin-or/CppAD
[submodule "ThirdParty/pybind11"]
path = ThirdParty/pybind11
url = https://github.com/pybind/pybind11
branch = stable
23 changes: 22 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.9)
cmake_minimum_required(VERSION 3.20)
include(ExternalProject)

# For ccache
Expand Down Expand Up @@ -66,6 +66,9 @@ set(CBC_DIR "/opt/Cbc-2.10" CACHE STRING "The base directory where Cbc is locate
option(HAS_IPOPT "Is Ipopt available" OFF)
set(IPOPT_DIR "/opt/ipopt" CACHE STRING "The base directory where Ipopt is located (if available).")

# Python
option(HAS_PYTHON "Is Python available" OFF)

# Create also the executable
option(GENERATE_EXE "Should the SHOT executable be generated (requires at least that either OS or GAMS is available)"
ON)
Expand All @@ -77,6 +80,7 @@ set(BOOST_DIR "ThirdParty/boost")
set(CPPAD_DIR "ThirdParty/CppAD")
set(EIGEN_DIR "ThirdParty/eigen")
set(MCPP_DIR "ThirdParty/mc++")
set(PYBIND11_DIR "ThirdParty/pybind11")
set(SPDLOG_DIR "ThirdParty/spdlog")
set(TINYXML2_DIR "ThirdParty/tinyxml2")

Expand Down Expand Up @@ -339,6 +343,7 @@ include_directories(SYSTEM "${EIGEN_DIR}")
include_directories(SYSTEM "${MCPP_DIR}/include")
#include_directories(SYSTEM "${MCPP_DIR}/3rdparty/cpplapack/include")
#include_directories(SYSTEM "${MCPP_DIR}/3rdparty/fadbad++")
include_directories(SYSTEM "${PYBIND11_DIR}/include")
include_directories(SYSTEM "${SPDLOG_DIR}/include")

# Make sure the source file lists are in the correct format
Expand Down Expand Up @@ -426,6 +431,12 @@ add_library(
)
target_link_libraries(SHOTResults SHOTModel)

if(HAS_PYTHON)
add_subdirectory(${PYBIND11_DIR})
pybind11_add_module(shotpy src/Shotpy.cpp)
target_link_libraries(shotpy PRIVATE SHOTSolver)
endif(HAS_PYTHON)

# Creates the primal strategy library
add_library(
SHOTPrimalStrategy STATIC
Expand Down Expand Up @@ -628,6 +639,16 @@ if(GENERATE_EXE)
endif(HAS_GAMS)
endif(GENERATE_EXE)


if(HAS_PYTHON)
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
include_directories(SYSTEM ${Python3_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${Python3_LIBRARIES})
message("-- The following Python libraries will be used from: ${Python3_LIBRARIES}")
message(" ${Python3_INCLUDE_DIRS}")

endif(HAS_PYTHON)

# Extra flags for Visual Studio compilers
if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17")
Expand Down
1 change: 1 addition & 0 deletions ThirdParty/pybind11
Submodule pybind11 added at 58c382
Empty file added __init__.py
Empty file.
215 changes: 215 additions & 0 deletions src/Shotpy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/**
The Supporting Hyperplane Optimization Toolkit (SHOT).

@author Andreas Lundell, Åbo Akademi University

@section LICENSE
This software is licensed under the Eclipse Public License 2.0.
Please see the README and LICENSE files for more information.
*/

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include "Solver.h"

#include "DualSolver.h"
#include "PrimalSolver.h"
#include "Report.h"
#include "Results.h"
#include "Settings.h"
#include "TaskHandler.h"
#include "Timing.h"
#include "Utilities.h"

#ifdef HAS_GAMS
#include "ModelingSystem/ModelingSystemGAMS.h"
#endif
#ifdef HAS_AMPL
#include "ModelingSystem/ModelingSystemAMPL.h"
#endif
#include "ModelingSystem/ModelingSystemOSiL.h"

#include "SolutionStrategy/SolutionStrategySingleTree.h"
#include "SolutionStrategy/SolutionStrategyMultiTree.h"
#include "SolutionStrategy/SolutionStrategyMIQCQP.h"
#include "SolutionStrategy/SolutionStrategyNLP.h"

#include "../Tasks/TaskPerformBoundTightening.h"
#include "../Tasks/TaskReformulateProblem.h"

#include <map>

#ifdef HAS_STD_FILESYSTEM
#include <filesystem>
namespace fs = std;
#endif

#ifdef HAS_STD_EXPERIMENTAL_FILESYSTEM
#include <experimental/filesystem>
namespace fs = std::experimental;
#endif

#ifdef HAS_GUROBI
#include "gurobi_c++.h"
#endif

namespace SHOT
{
namespace py = pybind11;

PYBIND11_MODULE(shotpy, m) {
m.doc() = "shotpy";

py::class_<Solver>(m, "Solver")
.def(py::init())
.def("getAbsoluteObjectiveGap", &Solver::getAbsoluteObjectiveGap)
.def("getCurrentDualBound", &Solver::getCurrentDualBound)
.def("getModelReturnStatus", &Solver::getModelReturnStatus)
.def("getOptions", &Solver::getOptions)
.def("getOptionsOSoL", &Solver::getOptionsOSoL)
.def("getPrimalBound", &Solver::getPrimalBound)
.def("getPrimalSolution", &Solver::getPrimalSolution)
.def("getPrimalSolutions", &Solver::getPrimalSolutions)
.def("getRelativeObjectiveGap", &Solver::getRelativeObjectiveGap)
.def("getResultsOSrL", &Solver::getResultsOSrL)
.def("getResultsSol", &Solver::getResultsSol)
.def("getResultsTrace", &Solver::getResultsTrace)

.def("getSolutionStatistics", &Solver::getSolutionStatistics)
.def("getSettingsAsMarkup", &Solver::getSettingsAsMarkup)

.def("getBoolSetting", py::overload_cast<std::string, std::string>(&Solver::getSetting<bool>))
.def("getStringSetting", py::overload_cast<std::string, std::string>(&Solver::getSetting<std::string>))
.def("getIntSetting", py::overload_cast<std::string, std::string>(&Solver::getSetting<int>))
.def("getDoubleSetting", py::overload_cast<std::string, std::string>(&Solver::getSetting<double>))

.def("getTerminationReason", &Solver::getTerminationReason)
.def("hasPrimalSolution", &Solver::hasPrimalSolution)

.def("outputSolverHeader", &Solver::outputSolverHeader)
.def("outputOptionsReport", &Solver::outputOptionsReport)
.def("outputProblemInstanceReport", &Solver::outputProblemInstanceReport)
.def("outputSolutionReport", &Solver::outputSolutionReport)

.def("setLogFile", &Solver::setLogFile)
.def("setOptionsFromFile", &Solver::setOptionsFromFile)
.def("setOptionsFromOSoL", &Solver::setOptionsFromOSoL)
.def("setOptionsFromString", &Solver::setOptionsFromString)
.def("setProblem", py::overload_cast<std::string>(&Solver::setProblem))
.def("solveProblem", &Solver::solveProblem)
.def("updateLogLevels", &Solver::updateLogLevels)
.def("updateSetting", py::overload_cast<std::string, std::string, int>(&Solver::updateSetting))
.def("updateSetting", py::overload_cast<std::string, std::string, std::string>(&Solver::updateSetting))
.def("updateSetting", py::overload_cast<std::string, std::string, double>(&Solver::updateSetting))
.def("updateSetting", py::overload_cast<std::string, std::string, bool>(&Solver::updateSetting))
;

py::enum_<E_PrimalSolutionSource>(m, "PrimalSolutionSource", py::arithmetic())
.value("Rootsearch", E_PrimalSolutionSource::Rootsearch)
.value("RootsearchFixedIntegers", E_PrimalSolutionSource::RootsearchFixedIntegers)
.value("NLPFixedIntegers", E_PrimalSolutionSource::NLPFixedIntegers)
.value("NLPRelaxed", E_PrimalSolutionSource::NLPRelaxed)
.value("MIPSolutionPool", E_PrimalSolutionSource::MIPSolutionPool)
.value("LPFixedIntegers", E_PrimalSolutionSource::LPFixedIntegers)
.value("MIPCallback", E_PrimalSolutionSource::MIPCallback)
.value("InteriorPointSearch", E_PrimalSolutionSource::InteriorPointSearch)
;

py::enum_<E_ModelReturnStatus>(m, "ModelReturnStatus", py::arithmetic())
.value("None", E_ModelReturnStatus::None)
.value("OptimalGlobal", E_ModelReturnStatus::OptimalGlobal)
.value("Unbounded", E_ModelReturnStatus::Unbounded)
.value("UnboundedNoSolution", E_ModelReturnStatus::UnboundedNoSolution)
.value("InfeasibleGlobal", E_ModelReturnStatus::InfeasibleGlobal)
.value("InfeasibleLocal", E_ModelReturnStatus::InfeasibleLocal)
.value("FeasibleSolution", E_ModelReturnStatus::FeasibleSolution)
.value("NoSolutionReturned", E_ModelReturnStatus::NoSolutionReturned)
.value("ErrorUnknown", E_ModelReturnStatus::ErrorUnknown)
.value("ErrorNoSolution", E_ModelReturnStatus::ErrorNoSolution)
;

py::enum_<E_TerminationReason>(m, "TerminationReason", py::arithmetic())
.value("ConstraintTolerance", E_TerminationReason::ConstraintTolerance)
.value("ObjectiveStagnation", E_TerminationReason::ObjectiveStagnation)
.value("IterationLimit", E_TerminationReason::IterationLimit)
.value("TimeLimit", E_TerminationReason::TimeLimit)
.value("InfeasibleProblem", E_TerminationReason::InfeasibleProblem)
.value("UnboundedProblem", E_TerminationReason::UnboundedProblem)
.value("Error", E_TerminationReason::Error)
.value("AbsoluteGap", E_TerminationReason::AbsoluteGap)
.value("RelativeGap", E_TerminationReason::RelativeGap)
.value("UserAbort", E_TerminationReason::UserAbort)
.value("NoDualCutsAdded", E_TerminationReason::NoDualCutsAdded)
.value("None", E_TerminationReason::None)
.value("NumericIssues", E_TerminationReason::NumericIssues)
;

py::class_<PairIndexValue>(m, "PairIndexValue")
.def_readwrite("index", &PairIndexValue::index)
.def_readwrite("value", &PairIndexValue::value)
;

py::class_<PrimalSolution>(m, "PrimalSolution")
.def_readwrite("point", &PrimalSolution::point)
.def_readwrite("sourceType", &PrimalSolution::sourceType)
.def_readwrite("sourceDescription", &PrimalSolution::sourceDescription)
.def_readwrite("objValue", &PrimalSolution::objValue)
.def_readwrite("iterFound", &PrimalSolution::iterFound)
.def_readwrite("maxDevatingConstraintLinear", &PrimalSolution::maxDevatingConstraintLinear)
.def_readwrite("maxDevatingConstraintQuadratic", &PrimalSolution::maxDevatingConstraintQuadratic)
.def_readwrite("maxDevatingConstraintNonlinear", &PrimalSolution::maxDevatingConstraintNonlinear)
.def_readwrite("maxIntegerToleranceError", &PrimalSolution::maxIntegerToleranceError)
.def_readwrite("boundProjectionPerformed", &PrimalSolution::boundProjectionPerformed)
.def_readwrite("integerRoundingPerformed", &PrimalSolution::integerRoundingPerformed)
.def_readwrite("displayed", &PrimalSolution::displayed)
;

py::class_<SolutionStatistics>(m, "SolutionStatistics")
.def_readwrite("numberOfIterations", &SolutionStatistics::numberOfIterations)
.def_readwrite("numberOfProblemsLP", &SolutionStatistics::numberOfProblemsLP)
.def_readwrite("numberOfProblemsQP ", &SolutionStatistics::numberOfProblemsQP)
.def_readwrite("numberOfProblemsQCQP", &SolutionStatistics::numberOfProblemsQCQP)
.def_readwrite("numberOfProblemsFeasibleMILP", &SolutionStatistics::numberOfProblemsFeasibleMILP)
.def_readwrite("numberOfProblemsOptimalMILP", &SolutionStatistics::numberOfProblemsOptimalMILP)
.def_readwrite("numberOfProblemsFeasibleMIQP", &SolutionStatistics::numberOfProblemsFeasibleMIQP)
.def_readwrite("numberOfProblemsOptimalMIQP", &SolutionStatistics::numberOfProblemsOptimalMIQP)
.def_readwrite("numberOfProblemsFeasibleMIQCQP", &SolutionStatistics::numberOfProblemsFeasibleMIQCQP)
.def_readwrite("numberOfProblemsOptimalMIQCQP", &SolutionStatistics::numberOfProblemsOptimalMIQCQP)
.def_readwrite("numberOfFunctionEvalutions", &SolutionStatistics::numberOfFunctionEvalutions)
.def_readwrite("numberOfGradientEvaluations", &SolutionStatistics::numberOfGradientEvaluations)
.def_readwrite("numberOfProblemsMinimaxLP", &SolutionStatistics::numberOfProblemsMinimaxLP)
.def_readwrite("numberOfProblemsFixedNLP", &SolutionStatistics::numberOfProblemsFixedNLP)
.def_readwrite("numberOfConstraintsRemovedInPresolve", &SolutionStatistics::numberOfConstraintsRemovedInPresolve)
.def_readwrite("numberOfVariableBoundsTightenedInPresolve", &SolutionStatistics::numberOfVariableBoundsTightenedInPresolve)
.def_readwrite("numberOfHyperplanesWithConvexSource", &SolutionStatistics::numberOfHyperplanesWithConvexSource)
.def_readwrite("numberOfHyperplanesWithNonconvexSource", &SolutionStatistics::numberOfHyperplanesWithNonconvexSource)
.def_readwrite("numberOfIntegerCuts", &SolutionStatistics::numberOfIntegerCuts)
.def_readwrite("numberOfIterationsWithDualStagnation", &SolutionStatistics::numberOfIterationsWithDualStagnation)
.def_readwrite("lastIterationWithSignificantDualUpdate", &SolutionStatistics::lastIterationWithSignificantDualUpdate)
.def_readwrite("numberOfIterationsWithPrimalStagnation", &SolutionStatistics::numberOfIterationsWithPrimalStagnation)
.def_readwrite("lastIterationWithSignificantPrimalUpdate", &SolutionStatistics::lastIterationWithSignificantPrimalUpdate)
.def_readwrite("numberOfIterationsWithoutNLPCallMIP", &SolutionStatistics::numberOfIterationsWithoutNLPCallMIP)
.def_readwrite("iterationLastPrimalBoundUpdate", &SolutionStatistics::iterationLastPrimalBoundUpdate)
.def_readwrite("iterationLastDualBoundUpdate", &SolutionStatistics::iterationLastDualBoundUpdate)
.def_readwrite("iterationLastLazyAdded", &SolutionStatistics::iterationLastLazyAdded)
.def_readwrite("iterationLastDualCutAdded", &SolutionStatistics::iterationLastDualCutAdded)
.def_readwrite("timeLastDualBoundUpdate", &SolutionStatistics::timeLastDualBoundUpdate)
.def_readwrite("timeLastFixedNLPCall", &SolutionStatistics::timeLastFixedNLPCall)
.def_readwrite("numberOfOriginalInteriorPoints", &SolutionStatistics::numberOfOriginalInteriorPoints)
.def_readwrite("numberOfFoundPrimalSolutions", &SolutionStatistics::numberOfFoundPrimalSolutions)
.def_readwrite("numberOfExploredNodes", &SolutionStatistics::numberOfExploredNodes)
.def_readwrite("numberOfOpenNodes", &SolutionStatistics::numberOfOpenNodes)
.def_readwrite("numberOfPrimalReductionCutsUpdatesWithoutEffect", &SolutionStatistics::numberOfPrimalReductionCutsUpdatesWithoutEffect)
.def_readwrite("numberOfDualRepairsSinceLastPrimalUpdate", &SolutionStatistics::numberOfDualRepairsSinceLastPrimalUpdate)
.def_readwrite("numberOfPrimalReductionsPerformed", &SolutionStatistics::numberOfPrimalReductionsPerformed)
.def_readwrite("numberOfSuccessfulDualRepairsPerformed", &SolutionStatistics::numberOfSuccessfulDualRepairsPerformed)
.def_readwrite("numberOfUnsuccessfulDualRepairsPerformed", &SolutionStatistics::numberOfUnsuccessfulDualRepairsPerformed)
.def_readwrite("numberOfPrimalImprovementsAfterInfeasibilityRepair", &SolutionStatistics::numberOfPrimalImprovementsAfterInfeasibilityRepair)
.def_readwrite("numberOfPrimalImprovementsAfterReductionCut", &SolutionStatistics::numberOfPrimalImprovementsAfterReductionCut)
.def_readwrite("hasInfeasibilityRepairBeenPerformedSincePrimalImprovement", &SolutionStatistics::hasInfeasibilityRepairBeenPerformedSincePrimalImprovement)
.def_readwrite("hasReductionCutBeenAddedSincePrimalImprovement", &SolutionStatistics::hasReductionCutBeenAddedSincePrimalImprovement)
.def("getNumberOfTotalDualProblems", &SolutionStatistics::getNumberOfTotalDualProblems)
;
}
}
1 change: 1 addition & 0 deletions src/Solver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2053,4 +2053,5 @@ std::vector<PrimalSolution> Solver::getPrimalSolutions() { return (env->results-
E_TerminationReason Solver::getTerminationReason() { return (env->results->terminationReason); }

E_ModelReturnStatus Solver::getModelReturnStatus() { return (env->results->getModelReturnStatus()); }

} // namespace SHOT
2 changes: 1 addition & 1 deletion src/Solver.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class DllExport Solver
PrimalSolution getPrimalSolution();
std::vector<PrimalSolution> getPrimalSolutions();

SolutionStatistics getSetSolutionStatistics() { return env->solutionStatistics; };
SolutionStatistics getSolutionStatistics() { return env->solutionStatistics; };

E_TerminationReason getTerminationReason();
E_ModelReturnStatus getModelReturnStatus();
Expand Down
Empty file added test/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions test/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import os, sys
from pathlib import Path

# Establish parent folders and add build folder to sys.path
parent = str(Path(__file__).absolute().parent.parent)
build_folder = os.path.join(parent, 'build')
sys.path.append(build_folder)

# Import shotpy, which in this case is shotpy lib file from build folder
import shotpy


def solveProblem(problemFile, correctObjectiveValue):

solver = shotpy.Solver()
loglevel = 6
solver.updateSetting('Console.LogLevel', 'Output', loglevel)


if (solver.getIntSetting('Console.LogLevel', 'Output') != loglevel):
print("Failed to set log level")
return

if (not solver.setProblem(problemFile)):
print("Failed to read problem file from " + problemFile)
return
else:
print("Problem file read from " + problemFile)


solver.outputProblemInstanceReport()

if (not solver.solveProblem()):
print("Failed to solve problem in " + problemFile)
return

osrl = solver.getResultsOSrL()

solutions = solver.getPrimalSolutions()

if (len(solutions) == 0):
print("No solution found")
return
else :
print("Number of solution found", len(solutions))


for solution in solutions:
print("Solution " + solutions.index(solution).__str__())
print("\tObjective value " + str(solution.objValue))
print("\tSource " + str(solution.sourceType))
print("\tSource description: " + str(solution.sourceDescription))
print("\tPoint " + str(solution.point))
print("\tMax deviation " + str(solution.maxDevatingConstraintNonlinear.value))

objValue = solutions[0].objValue

if (abs(objValue - correctObjectiveValue) > 1e-4):
print("Objective value is incorrect")
return
else:
print("Objective value is correct")

stats = solver.getSolutionStatistics()
print("Number of iterations " + str(stats.numberOfIterations))

# print(solver.getResultsOSrL())

terminationreason = solver.getTerminationReason()
print("Termination reason is:", terminationreason)

if __name__ == '__main__':

solveProblem('data/alan.osil', 2.925)
solveProblem('data/meanvarxsc.osil', 14.36923211)
solveProblem('data/fo7_2.osil', 17.74934573)