├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bindings └── python │ ├── converters.cpp │ ├── module.cpp │ └── test_fgm.py ├── fgm.cpp ├── fgm.hpp ├── hungarian.cpp ├── hungarian.hpp ├── util.cpp └── util.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | *.py[cod] 31 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(fgm-cpp) 3 | 4 | if(CMAKE_COMPILER_IS_GNUCC) 5 | add_definitions("-fPIC -std=c++11") 6 | endif() 7 | 8 | if(WIN32 AND NOT CYGWIN) 9 | set(Boost_USE_STATIC_LIBS On) 10 | add_definitions(-DBOOST_PYTHON_STATIC_LIB) 11 | endif() 12 | 13 | find_package(PythonInterp REQUIRED) 14 | find_package(PythonLibs REQUIRED) 15 | find_package(Boost REQUIRED COMPONENTS python) 16 | 17 | execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "try: import numpy; print numpy.get_include()\nexcept: pass\n" 18 | OUTPUT_VARIABLE NUMPY_INCLUDE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) 19 | if (NOT EXISTS ${NUMPY_INCLUDE_DIR}) 20 | message(FATAL_ERROR "Please make sure you have NumPy installed.") 21 | endif() 22 | 23 | set(EIGEN_ROOT "$ENV{EIGEN_ROOT}") 24 | if(NOT EIGEN_ROOT) 25 | set(EIGEN_ROOT /usr/include/eigen3) 26 | endif() 27 | if (NOT EXISTS ${EIGEN_ROOT}) 28 | message(FATAL_ERROR "Please point EIGEN_ROOT to your Eigen3 directory.") 29 | endif() 30 | 31 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/bindings/python) 32 | include_directories($ENV{EIGEN_ROOT}) 33 | include_directories(${Boost_INCLUDE_DIRS}) 34 | include_directories(${PYTHON_INCLUDE_DIRS} ${NUMPY_INCLUDE_DIR}) 35 | 36 | list(FIND PYTHON_LIBRARIES optimized PYTHON_OPTIMIZED_INDEX) 37 | if (NOT ${PYTHON_OPTIMIZED_INDEX} EQUAL -1) 38 | math(EXPR PYTHON_OPTIMIZED_INDEX "${PYTHON_OPTIMIZED_INDEX}+1") 39 | list(GET PYTHON_LIBRARIES ${PYTHON_OPTIMIZED_INDEX} PYTHON_RELEASE_LIBRARY) 40 | link_libraries(${PYTHON_RELEASE_LIBRARY}) 41 | endif() 42 | 43 | link_libraries(${Boost_LIBRARIES}) 44 | 45 | add_library(fgm SHARED bindings/python/module.cpp util.cpp util.hpp fgm.cpp fgm.hpp hungarian.cpp hungarian.hpp) 46 | 47 | set_target_properties(fgm PROPERTIES PREFIX "") 48 | if(WIN32 AND NOT CYGWIN) 49 | set_target_properties(fgm PROPERTIES SUFFIX ".pyd") 50 | endif() 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Egbert Bouman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fgm-cpp 2 | C++ library for solving graph matching problems using Factorized Graph Matching 3 | 4 | ## Build instructions for Ubuntu 5 | You will need the GNU G++ compiler and cmake: 6 | 7 | ```apt-get install build-essential cmake``` 8 | 9 | Make sure you have libboost-python, numpy, and eigen3 installed: 10 | 11 | ```sudo apt-get install libboost-python1.55 python-numpy libeigen3-dev``` 12 | 13 | Next, you can generate the makefile using cmake and build fgm-cpp: 14 | 15 | ``` 16 | cd ~/git/fgm-cpp 17 | cmake . 18 | make 19 | ``` 20 | 21 | If you get the error like ```fatal error: Eigen/Dense: No such file or directory```, please try the following: 22 | 23 | ``` 24 | cd /usr/include 25 | sudo ln -sf eigen3/Eigen Eigen 26 | ``` 27 | -------------------------------------------------------------------------------- /bindings/python/converters.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 11 | #include 12 | 13 | namespace python = boost::python; 14 | 15 | 16 | template 17 | std::map dict2map(const python::dict &dict) 18 | { 19 | std::map map; 20 | python::list keys = dict.keys(); 21 | for (int i = 0; i < python::len(keys); i++) 22 | { 23 | python::extract key(keys[i]); 24 | python::extract value(dict[(std::string)key]); 25 | map[key] = value; 26 | } 27 | return map; 28 | } 29 | 30 | template 31 | python::dict map2dict(const std::map &map) 32 | { 33 | python::dict dict; 34 | for (const auto &it : map) 35 | dict[it.first] = it.second; 36 | return dict; 37 | } 38 | 39 | struct eigen2numpy { 40 | eigen2numpy() 41 | { 42 | python::to_python_converter(); 43 | } 44 | 45 | static PyObject* convert(const Eigen::MatrixXd& m) 46 | { 47 | npy_intp shape[] = {m.rows(), m.cols()}; 48 | PyArrayObject* array = (PyArrayObject*) PyArray_SimpleNew(2, shape, NPY_DOUBLE); 49 | Eigen::MatrixXd::Scalar* pyData = (Eigen::MatrixXd::Scalar*)PyArray_DATA(array); 50 | for (int i = 0; i < m.rows(); ++i) 51 | for (int j = 0; j < m.cols(); ++j) 52 | pyData[i * m.cols() + j] = m(i, j); 53 | return (PyObject*)array; 54 | } 55 | }; 56 | 57 | struct numpy2eigen { 58 | numpy2eigen() 59 | { 60 | python::converter::registry::push_back(&convertible, &construct, python::type_id()); 61 | } 62 | 63 | static void* convertible(PyObject* po) 64 | { 65 | return PyArray_Check(po) ? po : 0; 66 | } 67 | 68 | static void construct(PyObject* po, python::converter::rvalue_from_python_stage1_data* data) 69 | { 70 | PyArrayObject* pao = (PyArrayObject*) po; 71 | if (!PyArray_ISFLOAT(pao)) 72 | throw std::invalid_argument("PyObject is not an array of floats/doubles!"); 73 | if (PyArray_NDIM(pao) != 2) 74 | throw std::invalid_argument("PyObject is not a 2-dimensional array!"); 75 | 76 | void* storage = ((python::converter::rvalue_from_python_storage*)(data))->storage.bytes; 77 | 78 | int rows = PyArray_DIMS(pao)[0]; 79 | int cols = PyArray_DIMS(pao)[1]; 80 | 81 | int stride1 = PyArray_STRIDE(pao, 0); 82 | int stride2 = PyArray_STRIDE(pao, 1); 83 | 84 | Eigen::MatrixXd& m = *new (storage)Eigen::MatrixXd(rows, cols); 85 | double* pyData = (double*)PyArray_DATA(pao); 86 | int dsize = sizeof(double); 87 | for (int i = 0; i < rows; ++i) 88 | for (int j = 0; j < cols; ++j) 89 | m(i, j) = *(pyData + i * (stride1 / dsize) + j * (stride2 / dsize)); 90 | 91 | data->convertible = storage; 92 | } 93 | }; 94 | 95 | template 96 | struct pair2tuple 97 | { 98 | pair2tuple() 99 | { 100 | python::to_python_converter, pair2tuple>(); 101 | } 102 | 103 | static PyObject* convert(const std::pair& p) 104 | { 105 | return python::incref(python::make_tuple(p.first, p.second).ptr()); 106 | } 107 | }; 108 | 109 | template 110 | struct tuple2pair 111 | { 112 | tuple2pair() 113 | { 114 | python::converter::registry::push_back(&convertible, &construct, python::type_id >()); 115 | } 116 | 117 | static void* convertible(PyObject* po) 118 | { 119 | return PyTuple_Check(po) && PyTuple_GET_SIZE(po) == 2 ? po : 0; 120 | } 121 | 122 | static void construct(PyObject* po, python::converter::rvalue_from_python_stage1_data* data) 123 | { 124 | void* storage = ((python::converter::rvalue_from_python_storage >*)data)->storage.bytes; 125 | 126 | python::object o(python::borrowed(po)); 127 | std::pair p; 128 | p.first = python::extract(o[0]); 129 | p.second = python::extract(o[1]); 130 | new (storage)std::pair(p); 131 | 132 | data->convertible = storage; 133 | } 134 | }; 135 | 136 | void initializeConverters() 137 | { 138 | import_array(); 139 | eigen2numpy(); 140 | numpy2eigen(); 141 | 142 | pair2tuple(); 143 | tuple2pair(); 144 | } 145 | -------------------------------------------------------------------------------- /bindings/python/module.cpp: -------------------------------------------------------------------------------- 1 | #include "fgm.hpp" 2 | #include "converters.cpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace python = boost::python; 11 | 12 | 13 | python::object solve_python(PyObject* KP, PyObject* KQ, PyObject* Ct, PyObject* asgTX, 14 | const python::dict& gph1, const python::dict& gph2, 15 | int nAlp, int nItMa, int nHst) 16 | { 17 | Eigen::MatrixXd _KP = python::extract(KP); 18 | Eigen::MatrixXd _KQ = python::extract(KQ); 19 | Eigen::MatrixXd _Ct = python::extract(Ct); 20 | Eigen::MatrixXd _asgTX = python::extract(asgTX); 21 | auto _gph1 = dict2map(gph1); 22 | auto _gph2 = dict2map(gph2); 23 | return python::object(fgm(_KP, _KQ, _Ct, _asgTX, _gph1, _gph2, nAlp, nItMa, nHst)); 24 | } 25 | 26 | 27 | BOOST_PYTHON_MODULE(fgm) 28 | { 29 | initializeConverters(); 30 | python::def("solve", solve_python, (python::arg("nAlp")=101, python::arg("nItMa")=100, python::arg("nHst")=10)); 31 | python::scope().attr("version") = FGM_VERSION; 32 | } 33 | -------------------------------------------------------------------------------- /bindings/python/test_fgm.py: -------------------------------------------------------------------------------- 1 | r'''>>> import fgm 2 | ''' 3 | 4 | if __name__ == '__main__': 5 | import sys, os 6 | sys.path.append(os.getcwd()) 7 | import doctest, test_fgm 8 | doctest.testmod(test_fgm) 9 | -------------------------------------------------------------------------------- /fgm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "util.hpp" 6 | #include "hungarian.hpp" 7 | #include "fgm.hpp" 8 | 9 | using Eigen::MatrixXd; 10 | using Eigen::MatrixXi; 11 | using Eigen::VectorXd; 12 | using Eigen::VectorXi; 13 | using Eigen::SparseMatrix; 14 | 15 | 16 | MatrixXd gmPosDHun(MatrixXd& X) 17 | { 18 | double max_oeff = X.maxCoeff(); 19 | X *= -1; 20 | X.array() += max_oeff; 21 | 22 | int n1 = X.rows(); 23 | int n2 = X.cols(); 24 | 25 | VectorXi result_vector(n1); 26 | result_vector.fill(0); 27 | findMatching(X, result_vector); 28 | 29 | // index -> matrix 30 | VectorXi idx; 31 | if (n1 <= n2) 32 | { 33 | idx = sub2ind(n1, n2, VectorXi::LinSpaced(n1, 0, n1-1), result_vector.adjoint()); 34 | } 35 | else 36 | { 37 | VectorXi temp1 = find(result_vector); 38 | VectorXi temp2(temp1.size()); 39 | for (int i = 0; i < temp1.size(); i++) 40 | { 41 | temp2(i) = result_vector(temp1(i)); 42 | } 43 | idx = sub2ind(n1, n2, temp1.adjoint(), temp2.adjoint()); 44 | } 45 | 46 | MatrixXd result(n1, n2); 47 | result.fill(0); 48 | for (int i = 0; i < idx.size(); i++) 49 | { 50 | result(idx(i)) = 1; 51 | } 52 | return result; 53 | } 54 | 55 | double multGXHSQTr(const MatrixXd& indG, const MatrixXd& X, const MatrixXd& indH, const MatrixXd& IndS0, const MatrixXd& Q) 56 | { 57 | int n = X.cols(); 58 | 59 | MatrixXi IndS; 60 | int mS, nS; 61 | int lenS = IndS0.cols() - 1; 62 | 63 | if (lenS < 0) 64 | { 65 | mS = nS = n; 66 | IndS = MatrixXi(2*n, 1); 67 | for (int p = 0; p < n; ++p) 68 | { 69 | IndS(p * 2) = p; 70 | IndS(p * 2 + 1) = p; 71 | } 72 | } 73 | else 74 | { 75 | mS = (int) IndS0(lenS * 2); 76 | nS = (int) IndS0(lenS * 2 + 1); 77 | IndS = MatrixXi(2*lenS, 1); 78 | for (int p = 0; p < lenS; ++p) 79 | { 80 | IndS(p * 2) = (int) IndS0(p * 2) - 1; 81 | IndS(p * 2 + 1) = (int) IndS0(p * 2 + 1) - 1; 82 | } 83 | } 84 | 85 | // check the dimension 86 | if (mS != indG.rows() || nS != indH.cols()) 87 | throw std::invalid_argument("Incorrect dimension!"); 88 | 89 | double result = 0; 90 | int iS, jS, i, j, idxX, idxY; 91 | for (int pS = 0; pS < lenS; ++pS) 92 | { 93 | iS = IndS((pS << 1)); 94 | jS = IndS((pS << 1) + 1); 95 | 96 | i = (int) indG(iS); 97 | i--; 98 | j = (int) indH(jS); 99 | j--; 100 | 101 | if (i < 0 || j < 0) 102 | continue; 103 | 104 | idxY = jS * mS + iS; 105 | idxX = j * n + i; 106 | result += X(idxX) * Q(idxY); 107 | } 108 | 109 | return result; 110 | } 111 | 112 | std::pair fgm(MatrixXd& KP, MatrixXd& KQ, MatrixXd& Ct, MatrixXd& asgTX, 113 | std::map& gph1, 114 | std::map& gph2, 115 | int nAlp, int nItMa, int nHst) 116 | { 117 | // weight 118 | VectorXd alps = VectorXd::LinSpaced(nAlp, 0, 1); 119 | 120 | // graph elements 121 | MatrixXd G1 = gph1["G"]; 122 | MatrixXd H1 = gph1["H"]; 123 | MatrixXd G2 = gph2["G"]; 124 | MatrixXd H2 = gph2["H"]; 125 | 126 | // dimension 127 | int n1 = G1.rows(); 128 | int m1 = G1.cols(); 129 | int n2 = G2.rows(); 130 | int m2 = G2.cols(); 131 | int ns[] = { n1, n2 }; 132 | 133 | // add additional nodes to make sure n1 == n2 134 | if (n1 < n2) 135 | { 136 | auto mi = KP.minCoeff(); 137 | KP = resize(KP, n2, n2, mi); 138 | G1 = resize(G1, n2, m1, 0); 139 | H1 = resize(H1, n2, m1, 0); 140 | Ct = resize(Ct, n2, n2, 1); 141 | } 142 | else if (n1 > n2) 143 | { 144 | auto mi = KP.minCoeff(); 145 | KP = resize(KP, n1, n1, mi); 146 | G2 = resize(G2, n1, m2, 0); 147 | H2 = resize(H2, n1, m2, 0); 148 | Ct = resize(Ct, n1, n1, 1); 149 | } 150 | 151 | // auxiliary variables (for saving computational time) 152 | MatrixXd GG1 = G1.adjoint() * G1; 153 | MatrixXd GG2 = G2.adjoint() * G2; 154 | MatrixXd HH1 = H1.adjoint() * H1; 155 | MatrixXd HH2 = H2.adjoint() * H2; 156 | MatrixXd IndHH1 = mat2ind(HH1); 157 | MatrixXd IndHH2 = mat2ind(HH2); 158 | 159 | MatrixXd indG1 = mat2indC(G1); 160 | MatrixXd indG2 = mat2indC(G2); 161 | MatrixXd indH1 = mat2indC(H1); 162 | MatrixXd indH2 = mat2indC(H2); 163 | 164 | // sparse matrix 165 | SparseMatrix G1s = G1.sparseView(); 166 | SparseMatrix G2s = G2.sparseView(); 167 | SparseMatrix H1s = H1.sparseView(); 168 | SparseMatrix H2s = H2.sparseView(); 169 | 170 | // factorize KQ using SVD 171 | Eigen::JacobiSVD svd(KQ, Eigen::ComputeFullU | Eigen::ComputeFullV); 172 | 173 | MatrixXd U = svd.matrixU(); 174 | MatrixXd V = svd.matrixV(); 175 | 176 | VectorXd s = svd.singularValues(); 177 | int length = s.size(); 178 | 179 | MatrixXd Us = U.leftCols(length); 180 | MatrixXd Vs = V.leftCols(length); 181 | VectorXd s_sqrt = s.head(length).cwiseSqrt().real(); 182 | MatrixXd XQ1 = multDiag(Us, s_sqrt).adjoint(); 183 | MatrixXd XQ2 = multDiag(Vs, s_sqrt).adjoint(); 184 | 185 | // auxiliary variables for computing the derivative of the constant term 186 | MatrixXd QQ1 = XQ1.adjoint() * XQ1; 187 | MatrixXd QQ2 = XQ2.adjoint() * XQ2; 188 | MatrixXd GHHQQG = G1 * HH1.cwiseProduct(QQ1) * G1.adjoint() + G2 * HH2.cwiseProduct(QQ2) * G2.adjoint(); 189 | 190 | // initialize from a doubly stochastic matrix 191 | double eps = std::numeric_limits::epsilon(); 192 | MatrixXd X(Ct.rows(), Ct.cols()); 193 | double iv = 1 + eps; 194 | for (int i = 0; i < X.rows(); ++i) 195 | { 196 | for (int j = 0; j < X.cols(); ++j) 197 | X(i, j) = Ct(i, j) == 0 ? 0 : iv; 198 | } 199 | 200 | double tol = 1e-7; 201 | n1 = X.rows(); 202 | n2 = X.cols(); 203 | 204 | if (n1 != n2) 205 | { 206 | // non-square 207 | int n_max = (n1 > n2) ? n1 : n2; 208 | MatrixXd Xslack = resize(X, n_max, n_max, 1); 209 | 210 | Xslack = normalize_bistochastic(Xslack, tol, 1000); 211 | Xslack.conservativeResize(n1, n2); 212 | X = Xslack; 213 | } 214 | else 215 | { 216 | // square 217 | X = normalize_bistochastic(X, tol, 1000); 218 | } 219 | 220 | SparseMatrix Xs, GXGs, HXHs; 221 | SparseMatrix GHHQQGs = GHHQQG.sparseView(); 222 | SparseMatrix KPs = KP.sparseView(); 223 | double tmp1, tmp2; 224 | 225 | // path-following 226 | for (int iAlp = 0; iAlp < nAlp; ++iAlp) 227 | { 228 | // scale of alpha 229 | double alp = alps(iAlp); 230 | 231 | // MFW 232 | std::vector> Ys(nHst); 233 | Xs = X.sparseView(); 234 | 235 | // main iteration 236 | for (int nIt = 0; nIt < nItMa; ++nIt) 237 | { 238 | // gradient 239 | GXGs = G1s.adjoint() * Xs * G2s; 240 | HXHs = H1s.adjoint() * Xs * H2s; 241 | SparseMatrix GrGm = KPs + H1s * GXGs.cwiseProduct(KQ) * H2s.adjoint() + G1s * HXHs.cwiseProduct(KQ) * G2s.adjoint(); 242 | SparseMatrix GrCon = 2 * GHHQQGs * Xs; 243 | SparseMatrix Gr = GrGm + (alp - .5) * GrCon; 244 | 245 | // optimal direction 246 | MatrixXd Gr_temp = MatrixXd(Gr); 247 | SparseMatrix Y = gmPosDHun(Gr_temp).sparseView(); 248 | SparseMatrix V = Y - Xs; 249 | 250 | // save to history 251 | int pHst = nIt % nHst; 252 | Ys[pHst] = Y / nHst; 253 | 254 | // alternative direction 255 | if (nIt - 1 >= nHst) 256 | { 257 | SparseMatrix W = -Xs; 258 | for (int iHst = 0; iHst < nHst; ++iHst) 259 | W = W + Ys[iHst]; 260 | 261 | double vV = Gr.cwiseProduct(V).sum() / V.norm(); 262 | double vW = Gr.cwiseProduct(W).sum() / W.norm(); 263 | if (vW > vV) 264 | { 265 | V = W; 266 | Ys[pHst] = Y / nHst; 267 | } 268 | } 269 | 270 | // step size 271 | SparseMatrix GYGs = G1s.adjoint() * V * G2s; 272 | SparseMatrix HYHs = H1s.adjoint() * V * H2s; 273 | double aGm = GYGs.cwiseProduct(HYHs).cwiseProduct(KQ).sum(); 274 | double bGm = KPs.cwiseProduct(V).sum() + (GXGs.cwiseProduct(HYHs) + GYGs.cwiseProduct(HXHs)).cwiseProduct(KQ).sum(); 275 | 276 | MatrixXd YY = V * MatrixXd(V.adjoint()); 277 | MatrixXd XY = Xs * MatrixXd(V.adjoint()); 278 | 279 | tmp1 = multGXHSQTr(indG1.adjoint(), YY, indG1, IndHH1, QQ1); 280 | tmp2 = multGXHSQTr(indG2.adjoint(), YY, indG2, IndHH2, QQ2); 281 | double aCon = tmp1 + tmp2; 282 | 283 | tmp1 = multGXHSQTr(indG1.adjoint(), XY, indG1, IndHH1, QQ1); 284 | tmp2 = multGXHSQTr(indG2.adjoint(), XY, indG2, IndHH2, QQ2); 285 | double bCon = 2 * (tmp1 + tmp2); 286 | 287 | double a = aGm + (alp - .5) * aCon; 288 | double b = bGm + (alp - .5) * bCon; 289 | 290 | double t = -b / a / 2; 291 | 292 | if (t <= 0) 293 | { 294 | t = (a > 0) ? 1 : 0; 295 | 296 | } 297 | else if (t <= 0.5) 298 | { 299 | if (a > 0) 300 | t = 1; 301 | } 302 | else if (t <= 1) 303 | { 304 | if (a > 0) 305 | t = 1; 306 | } 307 | else 308 | { 309 | t = (a > 0) ? 0 : 1; 310 | } 311 | 312 | // update 313 | X = Xs + t * V; 314 | 315 | // stop condition 316 | if ((X.sparseView() - Xs).norm() < eps || t < eps) 317 | break; 318 | 319 | // store 320 | Xs = X.sparseView(); 321 | } 322 | } 323 | 324 | // re-size to the original size 325 | X.conservativeResize(ns[0], ns[1]); 326 | 327 | //accuracy 328 | double acc = 0; 329 | if (asgTX.size() > 0) 330 | { 331 | int co = 0; 332 | VectorXi idx = find(asgTX); 333 | for (int i = 0; i < idx.size(); ++i) 334 | { 335 | // correct correspondences found 336 | if (asgTX(idx(i)) == X(idx(i))) 337 | co += 1; 338 | } 339 | // percentage 340 | acc = co / (double)idx.size(); 341 | } 342 | 343 | std::pair result(X, acc); 344 | return result; 345 | } 346 | -------------------------------------------------------------------------------- /fgm.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FGM_HPP_INCLUDED 2 | #define FGM_HPP_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define FGM_VERSION "0.0.2" 10 | 11 | 12 | Eigen::MatrixXd gmPosDHun(Eigen::MatrixXd& X); 13 | 14 | double multGXHSQTr(const Eigen::MatrixXd& indG, const Eigen::MatrixXd& X, 15 | const Eigen::MatrixXd& indH, const Eigen::MatrixXd& IndS0, const Eigen::MatrixXd& Q); 16 | 17 | std::pair fgm(Eigen::MatrixXd& KP, Eigen::MatrixXd& KQ, 18 | Eigen::MatrixXd& Ct, Eigen::MatrixXd& asgTX, 19 | std::map& gph1, 20 | std::map& gph2, 21 | int nAlp = 101, int nItMa = 100, int nHst = 10); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /hungarian.cpp: -------------------------------------------------------------------------------- 1 | #include "hungarian.hpp" 2 | 3 | using Eigen::MatrixXd; 4 | using Eigen::VectorXi; 5 | 6 | 7 | void findMatching(MatrixXd& m, VectorXi& assignment) 8 | { 9 | int nOfRows = m.rows(); 10 | int nOfColumns = m.cols(); 11 | int minDim, row, col; 12 | 13 | double value, minValue; 14 | 15 | MatrixXb starMatrix(m.rows(), m.cols()); 16 | MatrixXb newStarMatrix(m.rows(), m.cols()); 17 | MatrixXb primeMatrix(m.rows(), m.cols()); 18 | VectorXi coveredColumns(m.rows()); 19 | VectorXi coveredRows(m.cols()); 20 | 21 | starMatrix.fill(0); 22 | newStarMatrix.fill(0); 23 | primeMatrix.fill(0); 24 | coveredColumns.fill(0); 25 | coveredRows.fill(0); 26 | 27 | assignment.fill(0); 28 | 29 | for (row = 0; row nOfColumns) */ 67 | { 68 | minDim = nOfColumns; 69 | 70 | for (col = 0; col::max(); 252 | for (row = 0; row 5 | 6 | typedef Eigen::Matrix MatrixXb; 7 | 8 | 9 | void findMatching(Eigen::MatrixXd& m, Eigen::VectorXi& assignment); 10 | 11 | void step2a(Eigen::VectorXi& assignment, Eigen::MatrixXd& m, MatrixXb& starMatrix, 12 | MatrixXb& newStarMatrix, MatrixXb& primeMatrix, Eigen::VectorXi& coveredColumns, 13 | Eigen::VectorXi& coveredRows, int nOfRows, int nOfColumns, int minDim); 14 | 15 | void step2b(Eigen::VectorXi& assignment, Eigen::MatrixXd& m, MatrixXb& starMatrix, 16 | MatrixXb& newStarMatrix, MatrixXb& primeMatrix, Eigen::VectorXi& coveredColumns, 17 | Eigen::VectorXi& coveredRows, int nOfRows, int nOfColumns, int minDim); 18 | 19 | void step3(Eigen::VectorXi& assignment, Eigen::MatrixXd& m, MatrixXb& starMatrix, 20 | MatrixXb& newStarMatrix, MatrixXb& primeMatrix, Eigen::VectorXi& coveredColumns, 21 | Eigen::VectorXi& coveredRows, int nOfRows, int nOfColumns, int minDim); 22 | 23 | void step4(Eigen::VectorXi& assignment, Eigen::MatrixXd& m, MatrixXb& starMatrix, 24 | MatrixXb& newStarMatrix, MatrixXb& primeMatrix, Eigen::VectorXi& coveredColumns, 25 | Eigen::VectorXi& coveredRows, int nOfRows, int nOfColumns, int minDim, int row, int col); 26 | 27 | void step5(Eigen::VectorXi& assignment, Eigen::MatrixXd& m, MatrixXb& starMatrix, 28 | MatrixXb& newStarMatrix, MatrixXb& primeMatrix, Eigen::VectorXi& coveredColumns, 29 | Eigen::VectorXi& coveredRows, int nOfRows, int nOfColumns, int minDim); 30 | 31 | void buildassignmentvector(Eigen::VectorXi& assignment, MatrixXb& starMatrix, int nOfRows, int nOfColumns); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "util.hpp" 3 | 4 | using Eigen::MatrixXd; 5 | using Eigen::VectorXd; 6 | using Eigen::VectorXi; 7 | 8 | 9 | MatrixXd resize(const MatrixXd& m, int rows, int cols, double default_value) 10 | { 11 | if (m.rows() > rows || m.cols() > cols) 12 | throw std::invalid_argument("Shrinking a matrix is currently not supported"); 13 | 14 | auto result = MatrixXd(rows, cols); 15 | result.fill(default_value); 16 | for (int i = 0; i < m.rows(); ++i) 17 | { 18 | for (int j = 0; j < m.cols(); ++j) 19 | result(i, j) = m(i, j); 20 | } 21 | return result; 22 | } 23 | 24 | MatrixXd mat2ind(const MatrixXd& m) 25 | { 26 | int k = 0; 27 | MatrixXd result(2, m.size() + 1); 28 | for (int i = 0; i < m.rows(); ++i) 29 | { 30 | for (int j = 0; j < m.cols(); ++j) 31 | { 32 | if (m(i, j) != 0) 33 | { 34 | result(1, k) = i+1; 35 | result(0, k) = j+1; 36 | ++k; 37 | } 38 | } 39 | } 40 | result(1, k) = m.rows(); 41 | result(0, k) = m.cols(); 42 | result.conservativeResize(2, k + 1); 43 | return result; 44 | } 45 | 46 | MatrixXd mat2indC(const MatrixXd& m) 47 | { 48 | MatrixXd result(1, m.cols()); 49 | result.fill(0); 50 | 51 | for (int j = 0; j < m.cols(); ++j) 52 | for (int i = 0; i < m.rows(); ++i) 53 | if (m(i, j) != 0) 54 | { 55 | result(j) = i+1; 56 | break; 57 | } 58 | return result; 59 | } 60 | 61 | MatrixXd multDiag(const MatrixXd& A, const VectorXd& v) 62 | { 63 | int m = A.rows(); 64 | MatrixXd V = v.adjoint().replicate(m, 1); 65 | return A.cwiseProduct(V); 66 | } 67 | 68 | int sub2ind(int dimrow, int dimcol, int row, int col) 69 | { 70 | return dimrow*col + row; 71 | } 72 | 73 | VectorXi sub2ind(int dimrow, int dimcol, const VectorXi setrow, const VectorXi setcol) 74 | { 75 | VectorXi genidx(setrow.rows()); 76 | for (int i = 0; i < setrow.rows(); i++) 77 | { 78 | genidx(i) = sub2ind(dimrow, dimcol, setrow(i), setcol(i)); 79 | } 80 | return genidx; 81 | } 82 | 83 | template 84 | VectorXi find(const Eigen::DenseBase& m) 85 | { 86 | std::vector res; 87 | for (int i=0; i < m.rows(); i++) 88 | { 89 | if (m(i) != 0) 90 | { 91 | res.push_back(i); 92 | } 93 | } 94 | Eigen::Map result(res.data(), res.size()); 95 | return result; 96 | } 97 | 98 | template VectorXi find(const Eigen::DenseBase& m); 99 | template VectorXi find(const Eigen::DenseBase& m); 100 | 101 | MatrixXd normalize_bistochastic(const MatrixXd& X, double tol, int max_iters) 102 | { 103 | int n = X.rows(); 104 | int m = X.cols(); 105 | MatrixXd X1 = X; 106 | MatrixXd X2; 107 | VectorXd rowsums, colsums; 108 | 109 | for (int i = 0; i < max_iters; i++) 110 | { 111 | X2 = X1; 112 | 113 | // normalize rows 114 | rowsums = X1.rowwise().sum().array().inverse(); 115 | for (int j = 0; j < m; j++) 116 | for (int i = 0; i < n; i++) 117 | X1(i,j) *= rowsums(i); 118 | 119 | // normalize columns 120 | colsums = X1.colwise().sum().array().inverse(); 121 | for (int j = 0; j < m; j++) 122 | for (int i = 0; i < n; i++) 123 | X1(i,j) *= colsums(j); 124 | 125 | // stop condition 126 | double score = 0; 127 | double temp = 0; 128 | for (int i = 0; i < X.size(); i++) { 129 | temp = X1(i) - X2(i); 130 | score += temp*temp; 131 | } 132 | score = sqrt(score); 133 | if (score < tol) 134 | break; 135 | } 136 | return X1; 137 | } 138 | -------------------------------------------------------------------------------- /util.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_HPP_INCLUDED 2 | #define UTIL_HPP_INCLUDED 3 | 4 | #include 5 | 6 | 7 | Eigen::MatrixXd resize(const Eigen::MatrixXd& m, int rows, int cols, double default_value); 8 | 9 | Eigen::MatrixXd mat2ind(const Eigen::MatrixXd& m); 10 | 11 | Eigen::MatrixXd mat2indC(const Eigen::MatrixXd& m); 12 | 13 | Eigen::MatrixXd multDiag(const Eigen::MatrixXd& A, const Eigen::VectorXd& v); 14 | 15 | int sub2ind(int dimrow, int dimcol, int row, int col); 16 | 17 | Eigen::VectorXi sub2ind(int dimrow, int dimcol, const Eigen::VectorXi setrow, const Eigen::VectorXi setcol); 18 | 19 | template 20 | Eigen::VectorXi find(const Eigen::DenseBase& m); 21 | 22 | Eigen::MatrixXd normalize_bistochastic(const Eigen::MatrixXd& X, double tol, int max_iters); 23 | 24 | #endif 25 | --------------------------------------------------------------------------------