diff --git a/CMakeLists.txt b/CMakeLists.txt index a3177b2..8fc16d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,12 @@ project(DTWC++ VERSION "2.0.0" HOMEPAGE_URL https://battery-intelligence-lab.github.io/dtw-cpp/ LANGUAGES CXX) +option(CXX "enable C++ compilation" ON) + +if(CXX) + enable_language(CXX) +endif() + # Link this 'library' to use the warnings specified in CompilerWarnings.cmake add_library(project_warnings INTERFACE) @@ -15,17 +21,6 @@ add_library(project_warnings INTERFACE) add_library(project_options INTERFACE) target_compile_features(project_options INTERFACE cxx_std_20) -add_executable(dtwc++ - dtwc/main.cpp -) - -target_link_libraries(dtwc++ - PRIVATE - project_warnings - project_options -) - -target_compile_definitions(dtwc++ PRIVATE ROOT_FOLDER="${PROJECT_SOURCE_DIR}") if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") option(ENABLE_BUILD_WITH_TIME_TRACE "Enable -ftime-trace to generate time tracing .json files on clang" OFF) @@ -35,8 +30,13 @@ if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") endif() endif() +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + # # enable cache system # include(cmake/Cache.cmake) +# include(cmake/FindGUROBI.cmake) +find_package(GUROBI REQUIRED) +include_directories(${GUROBI_INCLUDE_DIRS}) # standard compiler warnings include(cmake/CompilerWarnings.cmake) @@ -52,3 +52,41 @@ set_project_warnings(project_warnings) # # allow for static analysis options # include(cmake/StaticAnalyzers.cmake) +message(STATUS "GUROBI CXX LIBRARY START") +message(STATUS ${GUROBI_CXX_LIBRARY}) +message(STATUS "GUROBI CXX LIBRARY END") + +message(STATUS "GUROBI CXX DEBUG LIBRARY START") +message(STATUS ${GUROBI_CXX_DEBUG_LIBRARY}) +message(STATUS "GUROBI CXX DEBUG LIBRARY END") + +message(STATUS "GUROBI LIBRARY START") +message(STATUS ${GUROBI_LIBRARY}) +message(STATUS "GUROBI LIBRARY END") + + +string(REPLACE "\\" "/" GRB_HOME $ENV{GUROBI_HOME}) + +file(GLOB GRB_SRC_FILES + ${GRB_HOME}/src/cpp/*.h + ${GRB_HOME}/src/cpp/*.cpp +) + +foreach(header ${GRB_SRC_FILES}) + # here replace / for linux and \ for windows + message(STATUS ${header}) +endforeach(header) + +add_executable(dtwc++ + dtwc/main.cpp + ${GRB_SRC_FILES} +) + +target_link_libraries(dtwc++ + PRIVATE + project_warnings + project_options + ${GUROBI_LIBRARY} +) + +target_compile_definitions(dtwc++ PRIVATE ROOT_FOLDER="${PROJECT_SOURCE_DIR}") diff --git a/cmake/CMakeLists_forGurobi.txt b/cmake/CMakeLists_forGurobi.txt new file mode 100644 index 0000000..2ca29c4 --- /dev/null +++ b/cmake/CMakeLists_forGurobi.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.3) + +project(gurobi-template C) + +option(CXX "enable C++ compilation" ON) + +if(CXX) + enable_language(CXX) +endif() + +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +# Visual Studio compiler with static runtime libraries +if(MSVC AND MT) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd") +endif() + +find_package(GUROBI REQUIRED) + +include_directories(${GUROBI_INCLUDE_DIRS}) + +# list source files here +set(sources mip1_c++.cpp) + +add_executable(${CMAKE_PROJECT_NAME} ${sources}) + +if(CXX) + set(CMAKE_CXX_STANDARD 11) + target_link_libraries(${CMAKE_PROJECT_NAME} optimized ${GUROBI_CXX_LIBRARY} + debug ${GUROBI_CXX_DEBUG_LIBRARY}) +endif() + +target_link_libraries(${CMAKE_PROJECT_NAME} ${GUROBI_LIBRARY}) + +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) + include(FeatureSummary) + feature_summary(WHAT ALL) +endif() diff --git a/cmake/FindGUROBI.cmake b/cmake/FindGUROBI.cmake new file mode 100644 index 0000000..0629fd5 --- /dev/null +++ b/cmake/FindGUROBI.cmake @@ -0,0 +1,55 @@ +find_path( + GUROBI_INCLUDE_DIRS + NAMES gurobi_c.h + HINTS ${GUROBI_DIR} $ENV{GUROBI_HOME} + PATH_SUFFIXES include) + +find_library( + GUROBI_LIBRARY + NAMES gurobi gurobi81 gurobi90 gurobi95 + HINTS ${GUROBI_DIR} $ENV{GUROBI_HOME} + PATH_SUFFIXES lib) + +if(CXX) + if(MSVC) + # determine Visual Studio year + if(MSVC_TOOLSET_VERSION EQUAL 142) + set(MSVC_YEAR "2019") + elseif(MSVC_TOOLSET_VERSION EQUAL 141) + set(MSVC_YEAR "2017") + elseif(MSVC_TOOLSET_VERSION EQUAL 140) + set(MSVC_YEAR "2015") + endif() + + if(MT) + set(M_FLAG "mt") + else() + set(M_FLAG "md") + endif() + + message(STATUS "CXX START") + message(STATUS gurobi_c++${M_FLAG}${MSVC_YEAR}) + + find_library( + GUROBI_CXX_LIBRARY + NAMES gurobi_c++${M_FLAG}${MSVC_YEAR} + HINTS ${GUROBI_DIR} $ENV{GUROBI_HOME} + PATH_SUFFIXES lib) + find_library( + GUROBI_CXX_DEBUG_LIBRARY + NAMES gurobi_c++${M_FLAG}d${MSVC_YEAR} + HINTS ${GUROBI_DIR} $ENV{GUROBI_HOME} + PATH_SUFFIXES lib) + else() + find_library( + GUROBI_CXX_LIBRARY + NAMES gurobi_c++ + HINTS ${GUROBI_DIR} $ENV{GUROBI_HOME} + PATH_SUFFIXES lib) + set(GUROBI_CXX_DEBUG_LIBRARY ${GUROBI_CXX_LIBRARY}) + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GUROBI DEFAULT_MSG GUROBI_LIBRARY + GUROBI_INCLUDE_DIRS) diff --git a/dtwc/dataTypes.hpp b/dtwc/dataTypes.hpp index f95e7a7..fa01e7c 100644 --- a/dtwc/dataTypes.hpp +++ b/dtwc/dataTypes.hpp @@ -22,6 +22,7 @@ class VecMatrix VecMatrix(VarType m_) : m(m_), n(m_), data(m_ * m_) {} // Sequare matrix VecMatrix(VarType m_, VarType n_) : m(m_), n(n_), data(m_ * n_) {} VecMatrix(VarType m_, VarType n_, Tdata x) : m(m_), n(n_), data(m_ * n_, x) {} + VecMatrix(VarType m_, VarType n_, std::vector &&vec) : m(m_), n(n_), data(std::move(vec)) {} inline void resize(VarType m_, VarType n_, Tdata x = 0) { diff --git a/dtwc/main.cpp b/dtwc/main.cpp index 6483a6d..8f22231 100644 --- a/dtwc/main.cpp +++ b/dtwc/main.cpp @@ -1,4 +1,5 @@ #include "dtwc.hpp" +#include "gurobi_c++.h" #include #include @@ -6,13 +7,15 @@ #include #include #include +#include + int main() { + using namespace dtwc; dtwc::Clock clk; - - int Ndata_max = 10; // Load 10 data maximum. + int Ndata_max = 100; // Load 10 data maximum. auto [p_vec, p_names] = load_data(settings::path, Ndata_max); @@ -38,6 +41,86 @@ int main() std::string DistMatrixName = "DTW_matrix.csv"; writeMatrix(DTWdist, DistMatrixName); // DTWdist.print(); - std::cout << "Finished all tasks in " << clk << "\n"; + std::cout << "Finished calculating distances " << clk << "\n"; std::cout << "Band used " << settings::band << "\n\n\n"; -} + + + auto Nb = p_vec.size(); + int Nc = 4; + + try { + GRBEnv env = GRBEnv(); + + GRBModel model = GRBModel(env); + + // Create variables + + GRBVar *isCluster = model.addVars(Nb, GRB_BINARY); + GRBVar *w = model.addVars(Nb * Nb, GRB_BINARY); + std::cout << "Finished creating w and is Cluster " << clk << "\n"; + + + for (size_t i{ 0 }; i < Nb; i++) { + GRBLinExpr lhs = 0; + for (size_t j{ 0 }; j < Nb; j++) { + lhs += w[j + i * Nb]; + } + model.addConstr(lhs, '=', 1.0); + } + std::cout << "Finished Only one cluster can be assigned " << clk << "\n"; + + + for (size_t j{ 0 }; j < Nb; j++) + for (size_t i{ 0 }; i < Nb; i++) + model.addConstr(w[i + j * Nb] <= isCluster[i]); + + std::cout << "Finished if w of ith data is activated then it is a cluster. " << clk << "\n"; + + { + GRBLinExpr lhs = 0; + for (size_t i{ 0 }; i < Nb; i++) + lhs += isCluster[i]; + + model.addConstr(lhs == Nc); // There should be Nc clusters. + } + std::cout << "Finished There should be Nc clusters. " << clk << "\n"; + + + // Set objective + + GRBLinExpr obj = 0; + for (size_t j{ 0 }; j < Nb; j++) + for (size_t i{ 0 }; i < Nb; i++) + obj += w[i + j * Nb] * DTWdistByInd(i, j); + + std::cout << "Finished OBJ. " << clk << "\n"; + + model.setObjective(obj, GRB_MINIMIZE); + + // First optimize() call will fail - need to set NonConvex to 2 + std::cout << "Finished setting up the MILP problem " << clk << "\n"; + model.optimize(); + + for (size_t i{ 0 }; i < Nb; i++) + std::cout << isCluster[i].get(GRB_StringAttr_VarName) << " " + << isCluster[i].get(GRB_DoubleAttr_X) << '\n'; + + + std::cout << "Obj: " << model.get(GRB_DoubleAttr_ObjVal) << std::endl; + + + delete[] isCluster; + delete[] w; + + + } catch (GRBException e) { + std::cout << "Error code = " << e.getErrorCode() << std::endl; + std::cout << e.getMessage() << std::endl; + } catch (...) { + std::cout << "Exception during optimization" << std::endl; + } + + std::cout << "Finished all tasks " << clk << "\n"; + + +} // diff --git a/matlab/findBestMedoids.m b/matlab/findBestMedoids.m index b109ca3..af7d37e 100644 --- a/matlab/findBestMedoids.m +++ b/matlab/findBestMedoids.m @@ -1,11 +1,15 @@ -function sol = findBestMedoids(distanceMat, Nc) +function sol = findBestMedoids(distanceMat, Nc, verbosity) + +if(nargin<3) + verbosity = 0; +end + Nb = length(distanceMat); % Number of batteries. w = binvar(Nb, Nb, 'full'); isCluster = binvar(Nb,1); F = []; % Constraints -%F = [F, 0 <= w <= 1]; F = [F, sum(w,1) == 1]; % Only one cluster can be assigned. F = [F, w <= repmat(isCluster,1,Nb)]; % if w of ith data is activated then it is a cluster. @@ -14,7 +18,7 @@ cost = sum(w.*distanceMat,'all'); -yalmipStr = optimize(F,cost,sdpsettings('verbose',0,'gurobi.MIPGap',1e-6)); +yalmipStr = optimize(F,cost,sdpsettings('verbose',verbosity,'gurobi.MIPGap',1e-6)); %% sol.yalmipStr = yalmipStr; diff --git a/matlab/test_MIP.m b/matlab/test_MIP.m new file mode 100644 index 0000000..e662485 --- /dev/null +++ b/matlab/test_MIP.m @@ -0,0 +1,14 @@ +% This is a test function. Please do not use it. + +clear variables; close all; clc; + + +distanceMat = readmatrix('../results/DTW_matrix.csv'); +assert(size(distanceMat,1) == size(distanceMat,2)); % See if it is square + +tic; +sol = findBestMedoids(distanceMat, 4, 2); + +toc + +fprintf('Cost: %4.6f\n',sol.cost); \ No newline at end of file