From a7eaeae4328fab9fdce15bc367613cca61551c44 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 29 Jan 2015 15:47:02 +0100 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFUI:=20GraphLayouting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented graph layouting, hybrid approach using spectral and force based layouting fortune cookie message = Giving credit where it's due ensures loyalty to you. --- CMakeLists.txt | 5 + README.md | 2 + bin/app/data/src/test/header.h | 137 +++++ src/app/CMakeLists.txt | 2 + src/app/main.cpp | 2 + src/app/qt/utility/QtGraphPostprocessor.cpp | 302 ++++++++++ src/app/qt/utility/QtGraphPostprocessor.h | 25 + src/app/qt/view/QtGraphView.cpp | 7 + src/app/qt/view/QtGraphView.h | 1 + src/external/CMakeLists.txt | 2 +- src/lib/CMakeLists.txt | 2 + .../component/controller/GraphController.cpp | 17 +- .../component/controller/GraphLayouter.cpp | 200 ++++++- src/lib/component/controller/GraphLayouter.h | 9 + src/lib/component/view/CodeView.cpp | 5 + .../component/view/graphElements/GraphNode.h | 55 +- src/lib/data/Storage.cpp | 25 + src/lib/utility/math/MatrixBase.h | 525 ++++++++++++++++++ src/lib/utility/math/MatrixDynamicBase.h | 134 +++++ src/lib/utility/math/VectorBase.h | 20 +- src/test/CMakeLists.txt | 1 + src/test/MatrixBaseTestSuite.h | 474 ++++++++++++++++ src/test/MatrixDynamicBaseTestSuite.h | 62 +++ 23 files changed, 2003 insertions(+), 11 deletions(-) create mode 100644 src/app/qt/utility/QtGraphPostprocessor.cpp create mode 100644 src/app/qt/utility/QtGraphPostprocessor.h create mode 100644 src/lib/utility/math/MatrixBase.h create mode 100644 src/lib/utility/math/MatrixDynamicBase.h create mode 100644 src/test/MatrixBaseTestSuite.h create mode 100644 src/test/MatrixDynamicBaseTestSuite.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e83370d..b25f18ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,10 @@ set(Boost_USE_STATIC_RUNTIME OFF) set(BOOST_LIBRARYDIR $ENV{BOOST_155_DIR}) find_package(Boost 1.55 COMPONENTS system filesystem REQUIRED) +# Eigen ------------------------------------------------------------------------- + +set(EIGEN_ROOT $ENV{EIGEN_DIR}) + # Lib -------------------------------------------------------------------------- set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin/lib) @@ -78,6 +82,7 @@ set_property( ${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} + ${EIGEN_ROOT} ) link_directories(${LLVM_LIBRARY_DIRS} ${CLANG_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) diff --git a/README.md b/README.md index 32d9e06a..2dbb9ec9 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ * Valgrind 3.9.0(linux, macOS) * Clang & LLVM (installation guide http://clang.llvm.org/docs/LibASTMatchersTutorial.html) * Boost 1.55 () +* Eigen 3.2.3 ##### Environment Variables @@ -16,6 +17,7 @@ * CXX_TEST_DIR - .../cxxtest-4.3/ * CLANG_DIR - .../clang-llvm/ * BOOST_155_DIR - .../boost_1_55_0 +* EIGEN_DIR - .../eigen For Win32: * VLD_DIR - .../Visual Leak Detector diff --git a/bin/app/data/src/test/header.h b/bin/app/data/src/test/header.h index 0f82665e..1c221730 100644 --- a/bin/app/data/src/test/header.h +++ b/bin/app/data/src/test/header.h @@ -132,4 +132,141 @@ public: private: int m_importantInt; +}; + +class AnotherClass + : public A +{ +public: + AnotherClass() + : A('x') + { + } + + int publicInt; +protected: + int protectedInt; +private: + int privateInt; +}; + + +class circleA +{ +public: + circleA() + { + + } + +private: + //circleB m_b; +}; + +class circleB +{ +public: + circleB() + { + m_a = circleA(); + } + + circleA foo(){return circleA();} + +private: + circleA m_a; +}; + +struct circleC +{ +public: + circleC() + { + m_b = circleB(); + } + + circleA foo0(){return circleA();} + circleB foo1(){return circleB();} + +private: + circleB m_b; +}; + +class circleD +{ +public: + circleD() + { + m_c = circleC(); + } + + circleC foo(){return circleC();} + + circleA foo2(){return circleA();} + circleB foo3(){return circleB();} + +private: + circleC m_c; +}; + +class circleE +{ +public: + circleE() + { + m_d = circleD(); + } + + circleC foo(){return circleC();} + circleB foo1(){return circleB();} + circleD foo2(){return circleD();} + circleA foo3(){return circleA();} + +private: + circleD m_d; +}; + +class circleF +{ +public: + circleF() + { + m_e = circleE(); + } + + circleC foo(){return circleC();} + circleB foo1(){return circleB();} + circleD foo2(){return circleD();} + circleA foo3(){return circleA();} + circleE foo4(){return circleE();} + +private: + circleE m_e; +}; + +class circleCenter +{ +public: + circleCenter() + { + m_a = circleA(); + m_b = circleB(); + m_c = circleC(); + m_d = circleD(); + } + + circleA foo0(){return circleA();} + circleB foo1(){return circleB();} + circleC foo2(){return circleC();} + circleD foo3(){return circleD();} + circleE foo4(){return circleE();} + circleF foo5(){return circleF();} + +private: + circleA m_a; + circleB m_b; + circleC m_c; + circleD m_d; + + //AnotherClass m_anotherClass; }; \ No newline at end of file diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index c1c35d0c..505fdf8f 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -26,6 +26,8 @@ add_files( qt/graphics/QtGraphicsRoundedRectItem.h qt/utility/QtDeviceScaledPixmap.h + qt/utility/QtGraphPostprocessor.cpp + qt/utility/QtGraphPostprocessor.h qt/utility/QtHighlighter.cpp qt/utility/QtHighlighter.h qt/utility/QtThreadedFunctor.h diff --git a/src/app/main.cpp b/src/app/main.cpp index 5fabce4f..6a61cfc0 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -7,11 +7,13 @@ #include "qt/utility/utilityQt.h" #include "qt/view/QtViewFactory.h" #include "utility/logging/ConsoleLogger.h" +#include "utility/logging/FileLogger.h" #include "utility/logging/LogManager.h" void init() { LogManager::getInstance()->addLogger(std::make_shared()); + LogManager::getInstance()->addLogger(std::make_shared()); utility::loadFontsFromDirectory("data/fonts", ".otf"); } diff --git a/src/app/qt/utility/QtGraphPostprocessor.cpp b/src/app/qt/utility/QtGraphPostprocessor.cpp new file mode 100644 index 00000000..7335777d --- /dev/null +++ b/src/app/qt/utility/QtGraphPostprocessor.cpp @@ -0,0 +1,302 @@ +#include "QtGraphPostprocessor.h" + +void QtGraphPostprocessor::doPostprocessing(std::list>& nodes) +{ + unsigned int atomarGridSize = 20; + + resizeNodes(nodes, atomarGridSize); + + if(nodes.size() < 2) + { + LOG_WARNING_STREAM(<< "Skipping postprocessing, need at least 2 nodes but got " << nodes.size()); + return; + } + + // determine center of mass (CoD) which is used to get outliers closer to the rest of the graph + int divisor = 999999; + int maxNodeSize = 0; + Vec2i centerOfMass(0, 0); + float totalMass = 0.0f; + std::list>::iterator it = nodes.begin(); + for(it; it != nodes.end(); it++) + { + if((*it)->getSize().x < divisor) + { + divisor = (*it)->getSize().x; + } + + if((*it)->getSize().y < divisor) + { + divisor = (*it)->getSize().y; + } + + if((*it)->getSize().x > maxNodeSize) + { + maxNodeSize = (*it)->getSize().x; + } + else if((*it)->getSize().y > maxNodeSize) + { + maxNodeSize = (*it)->getSize().y; + } + + float nodeMass = (*it)->getSize().getLengthSquared(); + centerOfMass += (*it)->getPosition() * nodeMass; + totalMass += nodeMass; + } + + centerOfMass /= totalMass; + + divisor = std::min(divisor, (int)atomarGridSize); + + resolveOutliers(nodes, centerOfMass); + + MatrixDynamicBase heatMap = buildHeatMap(nodes, divisor, maxNodeSize); + resolveOverlap(nodes, heatMap, divisor); +} + +void QtGraphPostprocessor::resolveOutliers(std::list>& nodes, const Vec2i& centerPoint) +{ + float maxDist = 0.0f; + std::list>::iterator it = nodes.begin(); + for(it; it != nodes.end(); it++) + { + Vec2i pos = (*it)->getPosition(); + Vec2i toCenterOfMass = centerPoint - pos; + if(toCenterOfMass.getLength() > maxDist) + { + maxDist = toCenterOfMass.getLength(); + } + } + + it = nodes.begin(); + for(it; it != nodes.end(); it++) + { + Vec2i pos = (*it)->getPosition(); + Vec2i toCenterOfMass = centerPoint - pos; + float dist = toCenterOfMass.getLength(); + + float distFactor = std::sqrt(dist/maxDist); // causes far away nodes to be effected stronger than nodes that are already close to the center + + (*it)->setPosition(pos + toCenterOfMass * distFactor); + } +} + +MatrixDynamicBase QtGraphPostprocessor::buildHeatMap(const std::list>& nodes, const int atomarNodeSize, const int maxNodeSize) +{ + int heatMapWidth = maxNodeSize * nodes.size() / atomarNodeSize; + int heatMapHeight = heatMapWidth; + + MatrixDynamicBase heatMap(heatMapWidth, heatMapHeight); + + std::list>::const_iterator it = nodes.cbegin(); + for(it; it != nodes.end(); it++) + { + int left = (*it)->getPosition().x / atomarNodeSize + heatMapWidth/2; + int up = (*it)->getPosition().y / atomarNodeSize + heatMapHeight/2; + int width = (*it)->getSize().x / atomarNodeSize; + int height = (*it)->getSize().y / atomarNodeSize; + + if(left + width > heatMapWidth || left < 0) + continue; + + if(up + height > heatMapHeight || up < 0) + continue; + + for(unsigned int i = 0; i < width; i++) + { + for(unsigned int j = 0; j < height; j++) + { + unsigned int x = left + i; + unsigned int y = up + j; + unsigned int value = heatMap.getValue(x, y); + heatMap.setValue(x, y, value+1); + } + } + } + + return heatMap; +} + +void QtGraphPostprocessor::resolveOverlap(std::list>& nodes, MatrixDynamicBase& heatMap, const int divisor) +{ + int heatMapWidth = heatMap.getColumnsCount(); + int heatMapHeight = heatMap.getRowsCount(); + + bool overlap = true; + int iterationCount = 0; + int maxIterations = 10; + + while(overlap && iterationCount < maxIterations) + { + overlap = false; + iterationCount++; + + std::list>::iterator it = nodes.begin(); + for(it; it != nodes.end(); it++) + { + Vec2i nodePos((*it)->getPosition().x / divisor + heatMapWidth/2, + (*it)->getPosition().y / divisor + heatMapHeight/2); + Vec2i nodeSize((*it)->getSize().x / divisor, + (*it)->getSize().y / divisor); + + if(nodePos.x + nodeSize.x > heatMapWidth || nodePos.x < 0) + continue; + + if(nodePos.y + nodeSize.y > heatMapHeight || nodePos.y < 0) + continue; + + Vec2f grad(0.0f, 0.0f); + if(getHeatmapGradient(grad, heatMap, nodePos, nodeSize)) + { + overlap = true; + } + + // handle overlap with no gradient + // e.g. when a node lies completely on top of another + float gradLength = grad.getLength(); + + if(grad.getLengthSquared() <= 0.000001f) + { + int val = heatMap.getValue(nodePos.x, nodePos.y); + if(val > 1) + { + grad = (*it)->getPosition(); + grad.normalize(); + grad *= -1.0f; + } + } + + // remove node temporarily from heat map, it will be re-added at the new position later on + modifyHeatmapArea(heatMap, nodePos, nodeSize, -1); + + // move node to new position + int xOffset = grad.x * divisor; + int yOffset = grad.y * divisor; + + // prevent the graph from "exploding" again... + if(xOffset > divisor) + xOffset = divisor; + else if(xOffset < -divisor) + xOffset = -divisor; + + if(yOffset > divisor) + yOffset = divisor; + else if(yOffset < -divisor) + yOffset = -divisor; + + Vec2i pos = (*it)->getPosition(); + pos += Vec2i(xOffset, yOffset); + // grid allignment + pos.x = (pos.x / divisor) * divisor; + pos.y = (pos.y / divisor) * divisor; + (*it)->setPosition(pos); + + // re-add node to heat map at new position + nodePos.x = (*it)->getPosition().x / divisor + heatMapWidth/2; + nodePos.y = (*it)->getPosition().y / divisor + heatMapHeight/2; + modifyHeatmapArea(heatMap, nodePos, nodeSize, 1); + } + } +} + +void QtGraphPostprocessor::modifyHeatmapArea(MatrixDynamicBase& heatMap, const Vec2i& leftUpperCorner, const Vec2i& size, const int modifier) +{ + bool wentOutOfRange = false; + + for(unsigned int i = 0; i < size.x; i++) + { + for(unsigned int j = 0; j < size.y; j++) + { + int x = leftUpperCorner.x + i; + int y = leftUpperCorner.y + j; + + if(x < 0 || x > heatMap.getColumnsCount()-1) + { + wentOutOfRange = true; + continue; + } + if(y < 0 || y > heatMap.getRowsCount()-1) + { + wentOutOfRange = true; + continue; + } + + unsigned int value = heatMap.getValue(x, y); + heatMap.setValue(x, y, value+modifier); + + if(wentOutOfRange == true) + { + LOG_WARNING("Left matrix range while trying to modify values."); + } + } + } +} + +bool QtGraphPostprocessor::getHeatmapGradient(Vec2f& outGradient, const MatrixDynamicBase& heatMap, const Vec2i& leftUpperCorner, const Vec2i& size) +{ + bool overlap = false; + + for(unsigned int i = 0; i < size.x; i++) + { + for(unsigned int j = 0; j < size.y; j++) + { + int x = leftUpperCorner.x + i; + int y = leftUpperCorner.y + j; + + // if x and y lie directly at the border not all 4 neighbours can be checked + if(x < 1 || x > heatMap.getColumnsCount()-2) + continue; + if(y < 1 || y > heatMap.getRowsCount()-2) + continue; + + float val = heatMap.getValue(x, y); + float xP1 = heatMap.getValue(x+1, y); + float xM1 = heatMap.getValue(x-1, y); + float yP1 = heatMap.getValue(x, y+1); + float yM1 = heatMap.getValue(x, y-1); + + xP1 = std::sqrtf(xP1); + xM1 = std::sqrtf(xM1); + yP1 = std::sqrtf(yP1); + yM1 = std::sqrtf(yM1); + + float xOffset = (xM1 - val) + (val - xP1); + float yOffset = (yM1 - val) + (val - yP1); + + outGradient += Vec2f(xOffset, yOffset); + + if(val > 1) + { + overlap = true; + } + } + } + + return overlap; +} + +void QtGraphPostprocessor::resizeNodes(std::list>& nodes, const unsigned int atomarSize) +{ + std::list>::iterator it = nodes.begin(); + for(it; it != nodes.end(); it++) + { + Vec2i size = (*it)->getSize(); + if(size.x % atomarSize != 0) + { + int multiplier = size.x / atomarSize; + ++multiplier; + + size.x = atomarSize * multiplier; + } + + if(size.y % atomarSize != 0) + { + int multiplier = size.y / atomarSize; + ++multiplier; + + size.y = atomarSize * multiplier; + } + + (*it)->setSize(size); + } +} diff --git a/src/app/qt/utility/QtGraphPostprocessor.h b/src/app/qt/utility/QtGraphPostprocessor.h new file mode 100644 index 00000000..55cad55f --- /dev/null +++ b/src/app/qt/utility/QtGraphPostprocessor.h @@ -0,0 +1,25 @@ +#ifndef QT_GRAPH_POSTPROCESSOR_H +#define QT_GRAPH_POSTPROCESSOR_H + +#include +#include + +#include "utility/math/MatrixDynamicBase.h" + +#include "qt/view/graphElements/QtGraphNode.h" + +class QtGraphPostprocessor +{ +public: + static void doPostprocessing(std::list>& nodes); + +private: + static MatrixDynamicBase buildHeatMap(const std::list>& nodes, const int atomarNodeSize, const int maxNodeSize); + static void resolveOutliers(std::list>& nodes, const Vec2i& centerPoint); + static void resolveOverlap(std::list>& nodes, MatrixDynamicBase& heatMap, const int divisor); + static void modifyHeatmapArea(MatrixDynamicBase& heatMap, const Vec2i& leftUpperCorner, const Vec2i& size, const int modifier); + static bool getHeatmapGradient(Vec2f& outGradient, const MatrixDynamicBase& heatMap, const Vec2i& leftUpperCorner, const Vec2i& size); + static void resizeNodes(std::list>& nodes, const unsigned int atomarSize); +}; + +#endif // QT_GRAPH_POSTPROCESSOR_H diff --git a/src/app/qt/view/QtGraphView.cpp b/src/app/qt/view/QtGraphView.cpp index 797f369b..200aa9d0 100644 --- a/src/app/qt/view/QtGraphView.cpp +++ b/src/app/qt/view/QtGraphView.cpp @@ -1,10 +1,13 @@ #include "QtGraphView.h" +#include + #include #include #include #include +#include "qt/utility/QtGraphPostprocessor.h" #include "qt/utility/utilityQt.h" #include "qt/view/QtViewWidgetWrapper.h" @@ -136,6 +139,8 @@ void QtGraphView::doRebuildGraph( m_graph = graph; } + QtGraphPostprocessor::doPostprocessing(m_nodes); + // Manually hover the items below the mouse cursor. view->scene()->setSceneRect(view->scene()->itemsBoundingRect()); QPointF point = view->mapToScene(view->mapFromGlobal(QCursor::pos())); @@ -148,6 +153,8 @@ void QtGraphView::doRebuildGraph( node->hoverEnter(); } } + + m_graph = graph; } void QtGraphView::doClear() diff --git a/src/app/qt/view/QtGraphView.h b/src/app/qt/view/QtGraphView.h index 0c971f04..6e76d48c 100644 --- a/src/app/qt/view/QtGraphView.h +++ b/src/app/qt/view/QtGraphView.h @@ -2,6 +2,7 @@ #define QT_GRAPH_VIEW_H #include "qt/utility/QtThreadedFunctor.h" +#include "utility/math/MatrixDynamicBase.h" #include "utility/math/Vector4.h" #include "utility/types.h" diff --git a/src/external/CMakeLists.txt b/src/external/CMakeLists.txt index c2bc510b..9e11505f 100644 --- a/src/external/CMakeLists.txt +++ b/src/external/CMakeLists.txt @@ -1,6 +1,6 @@ add_files( EXTERNAL_FILES - + tinyxml/tinystr.cpp tinyxml/tinystr.h tinyxml/tinyxml.cpp diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index d42308d2..44a61c7e 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -192,6 +192,8 @@ add_files( utility/logging/PlainFileLogger.h utility/math/Color.h + utility/math/MatrixBase.h + utility/math/MatrixDynamicBase.h utility/math/Vector2.h utility/math/Vector4.h utility/math/VectorBase.h diff --git a/src/lib/component/controller/GraphController.cpp b/src/lib/component/controller/GraphController.cpp index d9b0719e..7596d8ad 100644 --- a/src/lib/component/controller/GraphController.cpp +++ b/src/lib/component/controller/GraphController.cpp @@ -105,8 +105,6 @@ void GraphController::setActiveTokenIds(const std::vector& activeTokenIds) void GraphController::createDummyGraphForTokenIds(const std::vector& tokenIds) { - const GraphLayouter::LayoutFunction layoutFunction = &GraphLayouter::layoutSimpleRaster; - GraphView* view = getView(); if (!view) { @@ -142,7 +140,7 @@ void GraphController::createDummyGraphForTokenIds(const std::vector& tokenId setActiveAndVisibility(tokenIds); layoutNesting(); - layoutFunction(m_dummyNodes); + GraphLayouter::layoutSpectralPrototype(m_dummyNodes, m_dummyEdges); view->rebuildGraph(graph, m_dummyNodes, m_dummyEdges); } @@ -150,6 +148,19 @@ void GraphController::createDummyGraphForTokenIds(const std::vector& tokenId DummyNode GraphController::createDummyNodeTopDown(Node* node) { DummyNode result(node); + result.tokenId = node->getId(); + + // there is a global root node with id 0 afaik, so here we actually want the one node below this global root + Node* parent = node; + while(parent != NULL && parent->getParentNode() != NULL) + { + parent = parent->getParentNode(); + } + + if(parent != NULL) + { + result.topLevelAncestorId = parent->getId(); + } node->forEachChildNode( [node, &result, this](Node* child) diff --git a/src/lib/component/controller/GraphLayouter.cpp b/src/lib/component/controller/GraphLayouter.cpp index 971c933d..e55d72f0 100644 --- a/src/lib/component/controller/GraphLayouter.cpp +++ b/src/lib/component/controller/GraphLayouter.cpp @@ -1,9 +1,26 @@ #include "component/controller/GraphLayouter.h" -#include +#include +#include +#include +#include + +#include "component/view/graphElements/GraphEdge.h" #include "component/view/graphElements/GraphNode.h" +#include "utility/math/MatrixDynamicBase.h" + +// for prototyping, remove when done +#include "Eigen/Dense" +#include "Eigen/Eigenvalues" +#include "unsupported/Eigen/MatrixFunctions" + +bool compareEigenvaluePairs(const std::pair& p0, const std::pair& p1) +{ + return p0.second > p1.second; +} + void GraphLayouter::layoutSimpleRaster(std::vector& nodes) { int x = 0; @@ -48,3 +65,184 @@ void GraphLayouter::layoutSimpleRing(std::vector& nodes) } } } + +void GraphLayouter::layoutSpectralPrototype(std::vector& nodes, const std::vector& edges) +{ + if(nodes.size() < 2) + { + LOG_WARNING("Not enough nodes for layouting"); + return; + } + + MatrixDynamicBase laplacian = buildLaplacianMatrix(nodes, edges); + + Eigen::MatrixXd degreeMatrix(laplacian.getColumnsCount(), laplacian.getRowsCount()); + Eigen::MatrixXd eigenMatrix(laplacian.getColumnsCount(), laplacian.getRowsCount()); + + for(unsigned int x = 0; x < laplacian.getColumnsCount(); x++) + { + for(unsigned int y = 0; y < laplacian.getRowsCount(); y++) + { + eigenMatrix(x, y) = laplacian.getValue(x, y); + + if(x == y) + { + degreeMatrix(x, y) = laplacian.getValue(x, y); + } + } + } + + degreeMatrix = degreeMatrix.inverse(); + Eigen::MatrixPower dPow(degreeMatrix); + degreeMatrix = dPow(0.5); + + eigenMatrix = degreeMatrix * eigenMatrix * degreeMatrix; + + eigenMatrix.normalize(); + Eigen::EigenSolver solver(eigenMatrix); + + std::vector> eigenVectors; + for(unsigned int i = 0; i < solver.eigenvectors().cols(); i++) + { + eigenVectors.push_back(std::vector()); + + for(unsigned int j = 0; j < solver.eigenvectors().rows(); j++) + { + eigenVectors[i].push_back(solver.eigenvectors()(i*solver.eigenvectors().rows() + j).real()); + } + } + + std::vector> eigenValues; + for(unsigned int i = 0; i < solver.eigenvalues().size(); i++) + { + eigenValues.push_back(std::pair(i, solver.eigenvalues()(i).real())); + } + + std::sort(eigenValues.begin(), eigenValues.end(), compareEigenvaluePairs); + + if(eigenVectors.size() > 0 && eigenVectors[0].size() >= 3) + { + unsigned int xIdx = eigenValues[eigenValues.size()-2].first; + unsigned int yIdx = eigenValues[eigenValues.size()-3].first; + + /*double xEigenValue = std::sqrt(solver.eigenvalues()(xIdx).real()); + double yEigenValue = std::sqrt(solver.eigenvalues()(yIdx).real());*/ + + for(unsigned int i = 0; i < nodes.size(); i++) + { + float xPos = eigenVectors[xIdx][i]; + float yPos = eigenVectors[yIdx][i]; + + Vec2f newPos(xPos, yPos); + + newPos.normalize(); + newPos *= 600.0f; + + nodes[i].position.x = newPos.x; + nodes[i].position.y = newPos.y; + + //std::cout << newPos << std::endl; + } + //std::cout << "=================" << std::endl; + } +} + +MatrixDynamicBase GraphLayouter::buildLaplacianMatrix(const std::vector& nodes, const std::vector& edges) +{ + MatrixDynamicBase matrix(nodes.size(), nodes.size()); + + std::map nodesMap; + std::queue remainingNodes; + for(unsigned int i = 0; i < nodes.size(); i++) + { + remainingNodes.push(nodes[i]); + } + + while(remainingNodes.size() > 0) + { + if(remainingNodes.front().subNodes.size() > 0) + { + for(unsigned int i = 0; i < remainingNodes.front().subNodes.size(); i++) + { + remainingNodes.push(remainingNodes.front().subNodes[i]); + } + } + + nodesMap[remainingNodes.front().tokenId] = remainingNodes.front(); + remainingNodes.pop(); + } + + std::map, int> weightsMap; + for(unsigned int i = 0; i < edges.size(); i++) + { + DummyNode ownerNode = nodesMap[edges[i].ownerId]; + DummyNode targetNode = nodesMap[edges[i].targetId]; + + if(ownerNode.topLevelAncestorId != targetNode.topLevelAncestorId) + { + int weightIncrement = 1; + + Id ownerId = ownerNode.topLevelAncestorId; + Id targetId = targetNode.topLevelAncestorId; + std::pair key(ownerId, targetId); + std::pair inverseKey(targetId, ownerId); + std::pair keyOwner(ownerId, ownerId); + std::pair keyTarget(targetId, targetId); + + std::map, int>::iterator it = weightsMap.find(key); + if(it == weightsMap.end()) + { + weightsMap[key] = 0; + } + + weightsMap[key] += weightIncrement; + + it = weightsMap.find(inverseKey); + if(it == weightsMap.end()) + { + weightsMap[inverseKey] = 0; + } + + weightsMap[inverseKey] += weightIncrement; + + it = weightsMap.find(keyOwner); + if(it == weightsMap.end()) + { + weightsMap[keyOwner] = 0; + } + + weightsMap[keyOwner] += weightIncrement; + + it = weightsMap.find(keyTarget); + if(it == weightsMap.end()) + { + weightsMap[keyTarget] = 0; + } + + weightsMap[keyTarget] += weightIncrement; + } + } + + for(unsigned int x = 0; x < nodes.size(); x++) + { + for(unsigned int y = x; y < nodes.size(); y++) + { + unsigned int xNodeId = nodes[x].tokenId; + unsigned int yNodeId = nodes[y].tokenId; + + std::pair key(xNodeId, yNodeId); + + if(x == y) + { + matrix.setValue(x, y, weightsMap[key]); + } + else + { + matrix.setValue(x, y, -weightsMap[key]); + matrix.setValue(y, x, -weightsMap[key]); + } + } + } + + return matrix; +} diff --git a/src/lib/component/controller/GraphLayouter.h b/src/lib/component/controller/GraphLayouter.h index 7fe06526..2e3deeb9 100644 --- a/src/lib/component/controller/GraphLayouter.h +++ b/src/lib/component/controller/GraphLayouter.h @@ -3,6 +3,10 @@ #include +template +class MatrixDynamicBase; + +struct DummyEdge; struct DummyNode; class GraphLayouter @@ -12,6 +16,11 @@ public: static void layoutSimpleRaster(std::vector& nodes); static void layoutSimpleRing(std::vector& nodes); + + static void layoutSpectralPrototype(std::vector& nodes, const std::vector& edges); + +private: + static MatrixDynamicBase buildLaplacianMatrix(const std::vector& nodes, const std::vector& edges); }; #endif // GRAPH_LAYOUTER_H diff --git a/src/lib/component/view/CodeView.cpp b/src/lib/component/view/CodeView.cpp index 253a33c8..674c0c7c 100644 --- a/src/lib/component/view/CodeView.cpp +++ b/src/lib/component/view/CodeView.cpp @@ -15,6 +15,11 @@ CodeView::CodeSnippetParams::CodeSnippetParams() bool CodeView::CodeSnippetParams::sort(const CodeSnippetParams& a, const CodeSnippetParams& b) { + if(a.isActive && b.isActive) + { + return false; + } + // sort active snippet first if (a.isActive && !b.isActive) { diff --git a/src/lib/component/view/graphElements/GraphNode.h b/src/lib/component/view/graphElements/GraphNode.h index 7d11a250..c9276fd6 100644 --- a/src/lib/component/view/graphElements/GraphNode.h +++ b/src/lib/component/view/graphElements/GraphNode.h @@ -47,8 +47,9 @@ protected: struct DummyNode { - DummyNode(const Node* data) - : data(data) +public: + DummyNode() + : data(nullptr) , accessType(TokenComponentAccess::ACCESS_NONE) , active(false) , connected(false) @@ -60,6 +61,12 @@ struct DummyNode { } + DummyNode(const Node* data) + : data(data) + , accessType(TokenComponentAccess::ACCESS_NONE) + { + } + DummyNode(TokenComponentAccess::AccessType accessType) : data(nullptr) , accessType(accessType) @@ -78,6 +85,45 @@ struct DummyNode return expanded || autoExpanded; } + bool operator==(const DummyNode& other) const + { + if (data->getId() == other.data->getId() + && data->getName() == other.data->getName()) + { + return true; + } + return false; + } + + bool operator!=(const DummyNode& other) const + { + return !(*this == other); + } + + bool operator<(const DummyNode& other) const + { + if (data->getId() < other.data->getId()) + { + return true; + } + return false; + } + + bool operator>(const DummyNode& other) const + { + return !(*this < other); + } + + DummyNode& operator=(const DummyNode& other) + { + data = other.data; + subNodes = other.subNodes; + position = other.position; + topLevelAncestorId = other.topLevelAncestorId; + tokenId = other.tokenId; + return *this; + } + const Node* data; TokenComponentAccess::AccessType accessType; @@ -93,7 +139,10 @@ struct DummyNode size_t invisibleSubNodeCount; bool visible; - + + Id topLevelAncestorId; + Id tokenId; + std::vector subNodes; }; diff --git a/src/lib/data/Storage.cpp b/src/lib/data/Storage.cpp index 995576f6..3b94d3bd 100644 --- a/src/lib/data/Storage.cpp +++ b/src/lib/data/Storage.cpp @@ -1,5 +1,7 @@ #include "data/Storage.h" +#include + #include "utility/logging/logging.h" #include "utility/utilityString.h" @@ -553,6 +555,7 @@ std::shared_ptr Storage::getGraphForActiveTokenIds(const std::vector& else if (tokenIds.size() == 1) { Token* token = m_graph.getTokenById(tokenIds[0]); + if (!token) { LOG_ERROR_STREAM(<< "Token with id " << tokenIds[0] << " was not found"); @@ -585,6 +588,28 @@ std::shared_ptr Storage::getGraphForActiveTokenIds(const std::vector& } } + for(const std::pair> nodePair : graph->getNodes()) + { + Node* node = m_graph.getNodeById(nodePair.first); + + node->forEachEdge( + [graph](Edge* edge) + { + if(edge->getType() != Edge::EdgeType::EDGE_MEMBER) + { + Node* from = edge->getFrom(); + Node* to = edge->getTo(); + + if(graph->findNode([from](Node* node){return from->getId() == node->getId();}) != NULL + && graph->findNode([to](Node* node){return to->getId() == node->getId();}) != NULL) + { + graph->addEdge(edge); + } + } + } + ); + } + return graph; } diff --git a/src/lib/utility/math/MatrixBase.h b/src/lib/utility/math/MatrixBase.h new file mode 100644 index 00000000..4d022e74 --- /dev/null +++ b/src/lib/utility/math/MatrixBase.h @@ -0,0 +1,525 @@ +#ifndef MATRIX_BASE_H +#define MATRIX_BASE_H + +#include +#include +#include +#include + +#include "VectorBase.h" + +#define MATRIX_CHECK_INDEX(nIdx, mIdx) \ + do \ + { \ + unsigned int n((nIdx)); \ + unsigned int m((mIdx)); \ + checkIndexInRange(n, m, __FUNCTION__); \ + } \ + while (0) \ + +template +class MatrixBase +{ +public: + MatrixBase(); + MatrixBase(const T values[N][M]); + template + MatrixBase(const MatrixBase& matrix); + ~MatrixBase(); + + T getValue(const unsigned int columnIndex, const unsigned int rowIndex) const; + void setValue(const unsigned int columnIndex, const unsigned int rowIndex, const T& value); + + unsigned int getColumnsCount() const; + unsigned int getRowsCount() const; + + MatrixBase transposed() const; + + template + void assign(const MatrixBase& other); + + template + MatrixBase add(const MatrixBase& other); + template + MatrixBase subtract(const MatrixBase& other); + template + MatrixBase scalarMultiplication(const U& scalar); + template + MatrixBase matrixMultiplication(const MatrixBase& other) const; + + // Checks whether all values are the same. + template + bool isEqual(const MatrixBase& other) const; + // Checks whether it really is the same object (at one and the same memory address). + template + bool isSame(const MatrixBase& other) const; + + // jep, that's how you return an array... + T (&operator[](const unsigned int index))[M]; + + template + void operator=(const MatrixBase& other); + + template + MatrixBase operator+(const MatrixBase& other) const; + template + MatrixBase operator-(const MatrixBase& other) const; + template + MatrixBase operator*(const U& scalar) const; + template + MatrixBase operator/(const U& scalar) const; + + template + MatrixBase operator+=(const MatrixBase& other); + template + MatrixBase operator-=(const MatrixBase& other); + template + MatrixBase operator*=(const U& scalar); + template + MatrixBase operator/=(const U& scalar); + + // Checks whether all values are the same. + template + bool operator==(const MatrixBase& other) const; + // Checks whether at least one value is different. + template + bool operator!=(const MatrixBase& other) const; + + std::string toString() const; + +protected: + T m_values[N][M]; + +private: + inline void checkIndexInRange(unsigned int columnIndex, unsigned int rowIndex, const std::string& function) const + { + std::stringstream message; + + if (columnIndex >= N) + { + message << function << ": columnIndex " << columnIndex << " is out of range, maximum is " << N - 1; + } + + if (rowIndex >= M) + { + if(message.str().length() > 0) + { + message << "\n"; + } + + message << function << ": rowIndex " << rowIndex << " is out of range, maximum is " << M - 1; + } + + if(message.str().length() > 0) + { + throw std::range_error(message.str()); + } + } + + template + inline void setValues(const U values[N][M]) + { + for (unsigned int i = 0; i < N; i++) + { + for(unsigned int j = 0; j < M; j++) + { + m_values[i][j] = (T)values[i][j]; + } + } + } + + template + inline void setValues(const MatrixBase& matrix) + { + for (unsigned int i = 0; i < N; i++) + { + for(unsigned int j = 0; j < M; j++) + { + m_values[i][j] = (T)matrix.getValue(i, j); + } + } + } +}; + +template +MatrixBase::MatrixBase() +{} + +template +MatrixBase::MatrixBase(const T values[N][M]) +{ + setValues(values); +} + +template +template +MatrixBase::MatrixBase(const MatrixBase& matrix) +{ + setValues(matrix); +} + +template +MatrixBase::~MatrixBase() +{ +} + +template +T MatrixBase::getValue(const unsigned int columnIndex, const unsigned int rowIndex) const +{ + MATRIX_CHECK_INDEX(columnIndex, rowIndex); + + return m_values[columnIndex][rowIndex]; +} + +template +void MatrixBase::setValue(const unsigned int columnIndex, const unsigned int rowIndex, const T& value) +{ + MATRIX_CHECK_INDEX(columnIndex, rowIndex); + + m_values[columnIndex][rowIndex] = value; +} + +template +unsigned int MatrixBase::getColumnsCount() const +{ + return N; +} + +template +unsigned int MatrixBase::getRowsCount() const +{ + return M; +} + +template +MatrixBase MatrixBase::transposed() const +{ + T tmpValues[M][N]; + + for(unsigned int i = 0; i < N; i++) + { + for(unsigned int j = 0; j < M; j++) + { + tmpValues[j][i] = m_values[i][j]; + } + } + + return MatrixBase(tmpValues); +} + +template +template +void MatrixBase::assign(const MatrixBase& other) +{ + if(isSame(other)) + { + return; + } + + if(isEqual(other)) + { + return; + } + + setValues(other.m_values); +} + +template +template +MatrixBase MatrixBase::add(const MatrixBase& other) +{ + T tmpValues[N][M]; + for (unsigned int i = 0; i < N; i++) + { + for(unsigned int j = 0; j < M; j++) + { + tmpValues[i][j] = m_values[i][j] + other.m_values[i][j]; + } + } + + // The values of *this won't be changed until they are all in a valid state. + setValues(tmpValues); + return *this; +} + +template +template +MatrixBase MatrixBase::subtract(const MatrixBase& other) +{ + T tmpValues[N][M]; + for (unsigned int i = 0; i < N; i++) + { + for(unsigned int j = 0; j < M; j++) + { + tmpValues[i][j] = m_values[i][j] - other.m_values[i][j]; + } + } + + // The values of *this won't be changed until they are all in a valid state. + setValues(tmpValues); + return *this; +} + +template +template +MatrixBase MatrixBase::scalarMultiplication(const U& scalar) +{ + T tmpValues[N][M]; + for (unsigned int i = 0; i < N; i++) + { + for(unsigned int j = 0; j < M; j++) + { + tmpValues[i][j] = m_values[i][j] * scalar; + } + } + + // The values of *this won't be changed until they are all in a valid state. + setValues(tmpValues); + return *this; +} + +template +template +MatrixBase MatrixBase::matrixMultiplication(const MatrixBase& other) const +{ + MatrixBase result; + + for(unsigned int m = 0; m < M; m++) + { + for(unsigned int p = 0; p < P; p++) + { + int val = 0; + + for(unsigned int n = 0; n < N; n++) + { + val += m_values[n][m] * other.getValue(p, n); + } + + result.setValue(p, m, val); + } + } + + return result; +} + +template +template +bool MatrixBase::isEqual(const MatrixBase& other) const +{ + for(unsigned int i = 0; i < N; i++) + { + for(unsigned int j = 0; j < M; j++) + { + if(m_values[i][j] != other.m_values[i][j]) + { + return false; + } + } + } + + return true; +} + +template +template +bool MatrixBase::isSame(const MatrixBase& other) const +{ + return &other == this; +} + +template +T (&MatrixBase::operator[](const unsigned int index))[M] +{ + MATRIX_CHECK_INDEX(index, 0); + + return m_values[index]; +} + +template +template +void MatrixBase::operator=(const MatrixBase& other) +{ + assign(other); +} + +template +template +MatrixBase MatrixBase::operator+(const MatrixBase& other) const +{ + MatrixBase result(*this); + return result.add(other); +} + +template +template +MatrixBase MatrixBase::operator-(const MatrixBase& other) const +{ + MatrixBase result(*this); + return result.subtract(other); +} + +template +template +MatrixBase MatrixBase::operator*(const U& scalar) const +{ + MatrixBase result(*this); + return result.scalarMultiplication(scalar); +} + +template +template +MatrixBase MatrixBase::operator/(const U& scalar) const +{ + MatrixBase result(*this); + return result.scalarMultiplication(1.0f / scalar); +} + +template +template +MatrixBase MatrixBase::operator+=(const MatrixBase& other) +{ + return add(other); +} + +template +template +MatrixBase MatrixBase::operator-=(const MatrixBase& other) +{ + return subtract(other); +} + +template +template +MatrixBase MatrixBase::operator*=(const U& scalar) +{ + return scalarMultiplication(scalar); +} + +template +template +MatrixBase MatrixBase::operator/=(const U& scalar) +{ + return scalarMultiplication(1.0f / scalar); +} + +template +template +bool MatrixBase::operator==(const MatrixBase& other) const +{ + return isEqual(other); +} + +template +template +bool MatrixBase::operator!=(const MatrixBase& other) const +{ + return !isEqual(other); +} + +template +std::string MatrixBase::toString() const +{ + std::stringstream result; + + result << "\n"; + + for(unsigned int j = 0; j < M; j++) + { + for(unsigned int i = 0; i < N; i++) + { + if(i > 0) + { + result << ", "; + } + + result << m_values[i][j]; + } + + result << "\n"; + } + + return result.str(); +} + + +template +std::ostream& operator<<(std::ostream& ostream, const MatrixBase& matrix) +{ + ostream << matrix.toString(); + + return ostream; +} + +/** + * @note Vector will be treated as column vector + */ +template +VectorBase multiply(MatrixBase& matrix, const VectorBase& vector) +{ + // vector will be stored in a matrix instance to make use of MatrixBase matrix multiplication + MatrixBase vectorMatrix; + for(unsigned int i = 0; i < vector.getDimensions(); i++) + { + vectorMatrix.setValue(0, i, vector.getValue(i)); + } + + MatrixBase resultMatrix = matrix.matrixMultiplication(vectorMatrix); + + VectorBase result; + for(unsigned int i = 0; i < result.getDimensions(); i++) + { + result.setValue(i, resultMatrix.getValue(0, i)); + } + + return result; +} + +/** + * @note Vector will be treated as row vector + */ +template +VectorBase multiply(const VectorBase& vector, const MatrixBase& matrix) +{ + // vector will be stored in a matrix instance to make use of MatrixBase matrix multiplication + MatrixBase vectorMatrix; + for(unsigned int i = 0; i < vector.getDimensions(); i++) + { + vectorMatrix.setValue(i, 0, vector.getValue(i)); + } + + MatrixBase resultMatrix = vectorMatrix.matrixMultiplication(matrix); + + VectorBase result; + for(unsigned int i = 0; i < result.getDimensions(); i++) + { + result.setValue(i, resultMatrix.getValue(i, 0)); + } + + return result; +} + +/** + * @note Vector will be treated as row vector + */ +template +VectorBase operator*(const VectorBase& vector, const MatrixBase& matrix) +{ + // vector will be stored in a matrix instance to make use of MatrixBase matrix multiplication + MatrixBase vectorMatrix; + for(unsigned int i = 0; i < vector.getDimensions(); i++) + { + vectorMatrix.setValue(i, 1, vector.getValue(i)); + } + + MatrixBase resultMatrix = vectorMatrix.matrixMultiplication(matrix); + + VectorBase result; + for(unsigned int i = 0; i < vector.getDimensions(); i++) + { + result.setValue(i, resultMatrix.getValue(i, 1)); + } + + return result; +} + +#endif // MATRIX_BASE_H diff --git a/src/lib/utility/math/MatrixDynamicBase.h b/src/lib/utility/math/MatrixDynamicBase.h new file mode 100644 index 00000000..ff3ffa8e --- /dev/null +++ b/src/lib/utility/math/MatrixDynamicBase.h @@ -0,0 +1,134 @@ +#ifndef MATRIX_DYNAMIC_BASE_H +#define MATRIX_DYNAMIC_BASE_H + +#include + +/** + * @brief Matrix of variable size, needed for spectral graph layouting + * @note Use MatrixBase whenever possible because it's more efficient (e.g. it doesn't use stl containers) + */ +template +class MatrixDynamicBase +{ +public: + MatrixDynamicBase(); + MatrixDynamicBase(const unsigned int numColumns, const unsigned int numRows); + MatrixDynamicBase(const std::vector>& values); + ~MatrixDynamicBase(); + + T getValue(const unsigned int columnIndex, const unsigned int rowIndex) const; + void setValue(const unsigned int columnIndex, const unsigned int rowIndex, const T& value); + + unsigned int getColumnsCount() const; + unsigned int getRowsCount() const; + + std::string toString() const; + +private: + void initializeValues(const unsigned int numColumns, const unsigned int numRows); + + std::vector> m_values; +}; + +template +MatrixDynamicBase::MatrixDynamicBase() +{ +} + +template +MatrixDynamicBase::MatrixDynamicBase(const unsigned int numColumns, const unsigned int numRows) +{ + initializeValues(numColumns, numRows); +} + +template +MatrixDynamicBase::MatrixDynamicBase(const std::vector>& values) + : m_values(values) +{ +} + +template +MatrixDynamicBase::~MatrixDynamicBase() +{ +} + +template +T MatrixDynamicBase::getValue(const unsigned int columnIndex, const unsigned int rowIndex) const +{ + return m_values[columnIndex][rowIndex]; +} + +template +void MatrixDynamicBase::setValue(const unsigned int columnIndex, const unsigned int rowIndex, const T& value) +{ + m_values[columnIndex][rowIndex] = value; +} + +template +unsigned int MatrixDynamicBase::getColumnsCount() const +{ + return m_values.size(); +} + +template +unsigned int MatrixDynamicBase::getRowsCount() const +{ + if(m_values.size() > 0) + { + return m_values[0].size(); + } + + return 0; +} + +template +std::string MatrixDynamicBase::toString() const +{ + std::stringstream result; + + result << "\n"; + + unsigned int rowCount = getRowsCount(); + unsigned int columnCount = getColumnsCount(); + + for(unsigned int j = 0; j < rowCount; j++) + { + for(unsigned int i = 0; i < columnCount; i++) + { + if(i > 0) + { + result << ", "; + } + + result << m_values[i][j]; + } + + result << "\n"; + } + + return result.str(); +} + +template +void MatrixDynamicBase::initializeValues(const unsigned int numColumns, const unsigned int numRows) +{ + for(unsigned int x = 0; x < numColumns; x++) + { + std::vector row; + for(unsigned int y = 0; y < numRows; y++) + { + row.push_back(0); + } + m_values.push_back(row); + } +} + +template +std::ostream& operator<<(std::ostream& ostream, const MatrixDynamicBase& matrix) +{ + ostream << matrix.toString(); + + return ostream; +} + +#endif // MATRIX_DYNAMIC_BASE_H diff --git a/src/lib/utility/math/VectorBase.h b/src/lib/utility/math/VectorBase.h index 98e5dcfe..47077045 100644 --- a/src/lib/utility/math/VectorBase.h +++ b/src/lib/utility/math/VectorBase.h @@ -54,6 +54,8 @@ public: template bool isSame(const VectorBase& other) const; + bool isSame(const VectorBase& other) const; + T operator[](const unsigned int index); template @@ -233,7 +235,13 @@ void VectorBase::assign(const VectorBase& other) return; } - setValues(other.m_values); + T values[N]; + for(unsigned int i = 0; i < N; i++) + { + values[i] = other.getValue(i); + } + + setValues(values); } template @@ -299,7 +307,7 @@ bool VectorBase::isEqual(const VectorBase& other) const { for (unsigned int i = 0; i < N; i++) { - if (m_values[i] != other.m_values[i]) + if (m_values[i] != other.getValue(i)) { return false; } @@ -308,11 +316,17 @@ bool VectorBase::isEqual(const VectorBase& other) const return true; } +template +bool VectorBase::isSame(const VectorBase& other) const +{ + return &other == this; +} + template template bool VectorBase::isSame(const VectorBase& other) const { - return &other == this; + return false; } template diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 9329a30c..6dda2dce 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -16,6 +16,7 @@ add_files( GraphFilterTestSuite.h GraphFilterConductorTestSuite.h LogManagerTestSuite.h + MatrixBaseTestSuite.h MessageQueueTestSuite.h QueryTreeTestSuite.h SettingsTestSuite.h diff --git a/src/test/MatrixBaseTestSuite.h b/src/test/MatrixBaseTestSuite.h new file mode 100644 index 00000000..03847238 --- /dev/null +++ b/src/test/MatrixBaseTestSuite.h @@ -0,0 +1,474 @@ +#include "cxxtest/TestSuite.h" + +#include "utility/logging/logging.h" +#include "utility/math/MatrixBase.h" +#include "utility/math/VectorBase.h" + +class MatrixBaseTestSuite : public CxxTest::TestSuite +{ +public: + void test_matrixBase_constructors() + { + MatrixBase matrix0; + + TS_ASSERT_EQUALS(4, matrix0.getColumnsCount()); + TS_ASSERT_EQUALS(5, matrix0.getRowsCount()); + + Array3x5 testValues = getTestValues3x5(); + + MatrixBase matrix1(testValues.array); + + TS_ASSERT_EQUALS(3, matrix1.getColumnsCount()); + TS_ASSERT_EQUALS(5, matrix1.getRowsCount()); + + TS_ASSERT_EQUALS(0, matrix1.getValue(0, 0)); + TS_ASSERT_EQUALS(6, matrix1.getValue(2, 4)); + + MatrixBase matrix2(matrix1); + + TS_ASSERT_EQUALS(0, matrix2.getValue(0, 0)); + TS_ASSERT_EQUALS(6, matrix2.getValue(2, 4)); + } + + void test_matrixBase_getSetValue() + { + MatrixBase matrix0 = getTestMatrix3x5(); + + int value2_2 = matrix0.getValue(2, 2); + matrix0.setValue(2, 2, value2_2*2); + TS_ASSERT_EQUALS(value2_2*2, matrix0.getValue(2, 2)); + TS_ASSERT_EQUALS(3, matrix0.getValue(1, 2)); + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + } + + void test_matrixBase_getRowsColumnsCount() + { + MatrixBase matrix0 = getTestMatrix3x5(); + MatrixBase matrix1 = getTestMatrix5x3(); + + TS_ASSERT_EQUALS(3, matrix0.getColumnsCount()); + TS_ASSERT_EQUALS(5, matrix0.getRowsCount()); + + TS_ASSERT_EQUALS(5, matrix1.getColumnsCount()); + TS_ASSERT_EQUALS(3, matrix1.getRowsCount()); + } + + void test_matrixBase_transposed() + { + MatrixBase matrix0 = getTestMatrix3x5(); + TS_ASSERT_EQUALS(3, matrix0.getColumnsCount()); + TS_ASSERT_EQUALS(5, matrix0.getRowsCount()); + + MatrixBase matrix1 = matrix0.transposed(); + TS_ASSERT_EQUALS(5, matrix1.getColumnsCount()); + TS_ASSERT_EQUALS(3, matrix1.getRowsCount()); + + TS_ASSERT_EQUALS(matrix0.getValue(0, 0), matrix1.getValue(0, 0)); + TS_ASSERT_EQUALS(matrix0.getValue(0, 1), matrix1.getValue(1, 0)); + TS_ASSERT_EQUALS(matrix0.getValue(0, 4), matrix1.getValue(4, 0)); + TS_ASSERT_EQUALS(matrix0.getValue(1, 4), matrix1.getValue(4, 1)); + TS_ASSERT_EQUALS(matrix0.getValue(2, 4), matrix1.getValue(4, 2)); + TS_ASSERT_EQUALS(matrix0.getValue(0, 3), matrix1.getValue(3, 0)); + TS_ASSERT_EQUALS(matrix0.getValue(1, 3), matrix1.getValue(3, 1)); + TS_ASSERT_EQUALS(matrix0.getValue(2, 3), matrix1.getValue(3, 2)); + } + + void test_matrixBase_assign() + { + MatrixBase matrix0 = getTestMatrix3x5(); + MatrixBase matrix1 = getTestMatrix3x5_b(); + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(2, matrix0.getValue(1, 1)); + TS_ASSERT_EQUALS(4, matrix0.getValue(2, 2)); + + TS_ASSERT_EQUALS(0, matrix1.getValue(0, 0)); + TS_ASSERT_EQUALS(-2, matrix1.getValue(1, 1)); + TS_ASSERT_EQUALS(-4, matrix1.getValue(2, 2)); + + matrix0.assign(matrix1); + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(-2, matrix0.getValue(1, 1)); + TS_ASSERT_EQUALS(-4, matrix0.getValue(2, 2)); + } + + void test_matrixBase_add_subtract() + { + MatrixBase matrix0 = getTestMatrix3x5(); + MatrixBase matrix1 = getTestMatrix3x5_b(); + + matrix0.add(matrix1); + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(0, matrix0.getValue(1, 1)); + TS_ASSERT_EQUALS(0, matrix0.getValue(2, 2)); + + matrix0.subtract(matrix1); + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(2, matrix0.getValue(1, 1)); + TS_ASSERT_EQUALS(4, matrix0.getValue(2, 2)); + } + + void test_matrixBase_multiplyDivideScalar() + { + MatrixBase matrix0 = getTestMatrix3x5(); + + matrix0.scalarMultiplication(2.0f); // float is on porpoise (so is porpoise, womp womp) + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(4, matrix0.getValue(1, 1)); + TS_ASSERT_EQUALS(8, matrix0.getValue(2, 2)); + + matrix0.scalarMultiplication(0.5f); + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(2, matrix0.getValue(1, 1)); + TS_ASSERT_EQUALS(4, matrix0.getValue(2, 2)); + + matrix0.scalarMultiplication(0.5f); + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(1, matrix0.getValue(1, 1)); + TS_ASSERT_EQUALS(2, matrix0.getValue(2, 2)); + + matrix0.scalarMultiplication(0.5f); + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(0, matrix0.getValue(1, 1)); + TS_ASSERT_EQUALS(1, matrix0.getValue(2, 2)); + } + + void test_matrixBase_multiplyMatrix() + { + MatrixBase matrix0 = getTestMatrix3x5(); + MatrixBase matrix1 = getTestMatrix3x5_b(); + MatrixBase matrix1t = matrix1.transposed(); + + MatrixBase matrix2 = matrix0.matrixMultiplication(matrix1t); + MatrixBase matrix3 = matrix1t.matrixMultiplication(matrix0); + + // expected results + // matrix0 * matrix1t + /** + * -5, -8, -11, -14, -17 + * -8, -14, -20, -26, -32 + * -11, -20, -29, -38, -47 + * -14, -26, -38, -50, -62 + * -17, -32, -47, -62, -77 + */ + + // matrix1t * matrix0 + /** + * -30, -40, -50 + * -40, -55, -70 + * -50, -70, -90 + */ + + + TS_ASSERT_EQUALS(5, matrix2.getColumnsCount()); + TS_ASSERT_EQUALS(5, matrix2.getRowsCount()); + + TS_ASSERT_EQUALS(3, matrix3.getColumnsCount()); + TS_ASSERT_EQUALS(3, matrix3.getRowsCount()); + + TS_ASSERT_EQUALS(-5, matrix2.getValue(0, 0)); + TS_ASSERT_EQUALS(-77, matrix2.getValue(4, 4)); + TS_ASSERT_EQUALS(-29, matrix2.getValue(2, 2)); + TS_ASSERT_EQUALS(-11, matrix2.getValue(2, 0)); + TS_ASSERT_EQUALS(-11, matrix2.getValue(0, 2)); + TS_ASSERT_EQUALS(-38, matrix2.getValue(3, 2)); + + + TS_ASSERT_EQUALS(-30, matrix3.getValue(0, 0)); + TS_ASSERT_EQUALS(-90, matrix3.getValue(2, 2)); + TS_ASSERT_EQUALS(-50, matrix3.getValue(2, 0)); + TS_ASSERT_EQUALS(-50, matrix3.getValue(0, 2)); + TS_ASSERT_EQUALS(-55, matrix3.getValue(1, 1)); + } + + void test_matrixBase_isEqual() + { + MatrixBase matrix0 = getTestMatrix3x5(); + MatrixBase matrix0_b = getTestMatrix3x5(); + MatrixBase matrix1 = getTestMatrix3x5_b(); + + TS_ASSERT_EQUALS(true, matrix0.isEqual(matrix0_b)); + TS_ASSERT_EQUALS(false, matrix0.isEqual(matrix1)); + TS_ASSERT_EQUALS(true, matrix0.isEqual(matrix0)); + } + + void test_matrixBase_isSame() + { + MatrixBase matrix0 = getTestMatrix3x5(); + MatrixBase matrix0_b = getTestMatrix3x5(); + MatrixBase matrix1 = getTestMatrix3x5_b(); + + TS_ASSERT_EQUALS(false, matrix0.isSame(matrix0_b)); + TS_ASSERT_EQUALS(false, matrix0.isSame(matrix1)); + TS_ASSERT_EQUALS(true, matrix0.isSame(matrix0)); + } + + void test_matrixBase_accessOperator() + { + MatrixBase matrix0 = getTestMatrix3x5(); + + TS_ASSERT_EQUALS(0, matrix0[0][0]); + TS_ASSERT_EQUALS(4, matrix0[2][2]); + TS_ASSERT_EQUALS(6, matrix0[2][4]); + + matrix0[0][0] = 42; + + TS_ASSERT_EQUALS(42, matrix0[0][0]); + TS_ASSERT_EQUALS(4, matrix0[2][2]); + TS_ASSERT_EQUALS(6, matrix0[2][4]); + } + + void test_matrixBase_operators() + { + MatrixBase matrix0 = getTestMatrix3x5(); + MatrixBase matrix0_b = getTestMatrix3x5_b(); + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(4, matrix0.getValue(2, 2)); + TS_ASSERT_EQUALS(6, matrix0.getValue(2, 4)); + + TS_ASSERT_EQUALS(0, matrix0_b.getValue(0, 0)); + TS_ASSERT_EQUALS(-4, matrix0_b.getValue(2, 2)); + TS_ASSERT_EQUALS(-6, matrix0_b.getValue(2, 4)); + + MatrixBase matrix1 = matrix0 + matrix0_b; + MatrixBase matrix2 = matrix0 - matrix0_b; + + TS_ASSERT_EQUALS(0, matrix1.getValue(0, 0)); + TS_ASSERT_EQUALS(0, matrix1.getValue(2, 2)); + TS_ASSERT_EQUALS(0, matrix1.getValue(2, 4)); + + TS_ASSERT_EQUALS(0, matrix2.getValue(0, 0)); + TS_ASSERT_EQUALS(8, matrix2.getValue(2, 2)); + TS_ASSERT_EQUALS(12, matrix2.getValue(2, 4)); + + MatrixBase matrix3 = matrix0 * 3; + MatrixBase matrix4 = matrix0 / 2; + MatrixBase matrix5 = matrix0 * 3.3f; // float is on purpose + + TS_ASSERT_EQUALS(0, matrix3.getValue(0, 0)); + TS_ASSERT_EQUALS(12, matrix3.getValue(2, 2)); + TS_ASSERT_EQUALS(18, matrix3.getValue(2, 4)); + + TS_ASSERT_EQUALS(0, matrix4.getValue(0, 0)); + TS_ASSERT_EQUALS(2, matrix4.getValue(2, 2)); + TS_ASSERT_EQUALS(3, matrix4.getValue(2, 4)); + + TS_ASSERT_EQUALS(0, matrix5.getValue(0, 0)); + TS_ASSERT_EQUALS(13, matrix5.getValue(2, 2)); + TS_ASSERT_EQUALS(19, matrix5.getValue(2, 4)); + } + + void test_matrixBase_assignOperators() + { + MatrixBase matrix0 = getTestMatrix3x5(); + MatrixBase matrix0_b = getTestMatrix3x5_b(); + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(4, matrix0.getValue(2, 2)); + TS_ASSERT_EQUALS(6, matrix0.getValue(2, 4)); + + TS_ASSERT_EQUALS(0, matrix0_b.getValue(0, 0)); + TS_ASSERT_EQUALS(-4, matrix0_b.getValue(2, 2)); + TS_ASSERT_EQUALS(-6, matrix0_b.getValue(2, 4)); + + MatrixBase matrix1 = getTestMatrix3x5(); + matrix1 += matrix0; + + TS_ASSERT_EQUALS(0, matrix1.getValue(0, 0)); + TS_ASSERT_EQUALS(8, matrix1.getValue(2, 2)); + TS_ASSERT_EQUALS(12, matrix1.getValue(2, 4)); + + matrix1 += matrix0_b; + + TS_ASSERT_EQUALS(0, matrix1.getValue(0, 0)); + TS_ASSERT_EQUALS(4, matrix1.getValue(2, 2)); + TS_ASSERT_EQUALS(6, matrix1.getValue(2, 4)); + + matrix1 -= matrix0_b; + + TS_ASSERT_EQUALS(0, matrix1.getValue(0, 0)); + TS_ASSERT_EQUALS(8, matrix1.getValue(2, 2)); + TS_ASSERT_EQUALS(12, matrix1.getValue(2, 4)); + + matrix1 *= 3.3f; + + TS_ASSERT_EQUALS(0, matrix1.getValue(0, 0)); + TS_ASSERT_EQUALS(26, matrix1.getValue(2, 2)); + TS_ASSERT_EQUALS(39, matrix1.getValue(2, 4)); + + matrix1 /= 3; + + TS_ASSERT_EQUALS(0, matrix1.getValue(0, 0)); + TS_ASSERT_EQUALS(8, matrix1.getValue(2, 2)); + TS_ASSERT_EQUALS(13, matrix1.getValue(2, 4)); + } + + void test_matrixBase_comparisonOperators() + { + MatrixBase matrix0 = getTestMatrix3x5(); + MatrixBase matrix0_b = getTestMatrix3x5_b(); + MatrixBase matrix1 = getTestMatrix3x5(); + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(4, matrix0.getValue(2, 2)); + TS_ASSERT_EQUALS(6, matrix0.getValue(2, 4)); + + TS_ASSERT_EQUALS(0, matrix0_b.getValue(0, 0)); + TS_ASSERT_EQUALS(-4, matrix0_b.getValue(2, 2)); + TS_ASSERT_EQUALS(-6, matrix0_b.getValue(2, 4)); + + TS_ASSERT_EQUALS(0, matrix1.getValue(0, 0)); + TS_ASSERT_EQUALS(4, matrix1.getValue(2, 2)); + TS_ASSERT_EQUALS(6, matrix1.getValue(2, 4)); + + TS_ASSERT_EQUALS(true, matrix0 == matrix0); + TS_ASSERT_EQUALS(true, matrix0 == matrix1); + TS_ASSERT_EQUALS(true, matrix0 != matrix0_b); + + TS_ASSERT_EQUALS(false, matrix0 != matrix0); + TS_ASSERT_EQUALS(false, matrix0 != matrix1); + TS_ASSERT_EQUALS(false, matrix0 == matrix0_b); + } + + void test_matrixBase_vectorMultiplication() + { + MatrixBase matrix0 = getTestMatrix3x5(); + VectorBase vector0; + + for(unsigned int i = 0; i < vector0.getDimensions(); i++) + { + vector0.setValue(i, i+1); + } + + VectorBase vector0_r = multiply(matrix0, vector0); + + TS_ASSERT_EQUALS(8, vector0_r[0]); + TS_ASSERT_EQUALS(14, vector0_r[1]); + TS_ASSERT_EQUALS(20, vector0_r[2]); + TS_ASSERT_EQUALS(26, vector0_r[3]); + TS_ASSERT_EQUALS(32, vector0_r[4]); + + MatrixBase matrix1 = getTestMatrix5x3(); + VectorBase vector1; + + for(unsigned int i = 0; i < vector1.getDimensions(); i++) + { + vector1.setValue(i, i+1); + } + + VectorBase vector1_r = multiply(vector1, matrix1); + + TS_ASSERT_EQUALS(8, vector1_r[0]); + TS_ASSERT_EQUALS(14, vector1_r[1]); + TS_ASSERT_EQUALS(20, vector1_r[2]); + TS_ASSERT_EQUALS(26, vector1_r[3]); + TS_ASSERT_EQUALS(32, vector1_r[4]); + + MatrixBase matrix2 = getTestMatrix3x5(); + VectorBase vector2; + + for(unsigned int i = 0; i < vector2.getDimensions(); i++) + { + vector2.setValue(i, i+1); + } + + VectorBase vector2_r = multiply(vector2, matrix2); + + TS_ASSERT_EQUALS(40, vector2_r[0]); + TS_ASSERT_EQUALS(55, vector2_r[1]); + TS_ASSERT_EQUALS(70, vector2_r[2]); + } + +private: + /** + * C++ functions can't return statically allocated arrays. + * I don't want to use dynamically allocated arrays, so here's my work around for that... + * + * Update: acutally they can... see MatrixBase [] operator (in MatrixBase.cpp) + */ + template + struct Array3x5 + { + T array[3][5]; + }; + + template + struct Array5x3 + { + T array[5][3]; + }; + + Array3x5 getTestValues3x5() + { + Array3x5 result; + + for(unsigned int i = 0; i < 3; i++) + { + for(unsigned int j = 0; j < 5; j++) + { + result.array[i][j] = i + j; + } + } + + return result; + } + + Array3x5 getTestValues3x5_b() + { + Array3x5 result; + + for(unsigned int i = 0; i < 3; i++) + { + for(unsigned int j = 0; j < 5; j++) + { + result.array[i][j] = -i - j; + } + } + + return result; + } + + Array5x3 getTestValues5x3() + { + Array5x3 result; + + for(unsigned int i = 0; i < 5; i++) + { + for(unsigned int j = 0; j < 3; j++) + { + result.array[i][j] = i + j; + } + } + + return result; + } + + MatrixBase getTestMatrix3x5() + { + Array3x5 testValues = getTestValues3x5(); + + return MatrixBase(testValues.array); + } + + MatrixBase getTestMatrix3x5_b() + { + Array3x5 testValues = getTestValues3x5_b(); + + return MatrixBase(testValues.array); + } + + MatrixBase getTestMatrix5x3() + { + Array5x3 testValues = getTestValues5x3(); + + return MatrixBase(testValues.array); + } +}; \ No newline at end of file diff --git a/src/test/MatrixDynamicBaseTestSuite.h b/src/test/MatrixDynamicBaseTestSuite.h new file mode 100644 index 00000000..b884fcef --- /dev/null +++ b/src/test/MatrixDynamicBaseTestSuite.h @@ -0,0 +1,62 @@ +#include "cxxtest/TestSuite.h" + +#include "utility/logging/logging.h" +#include "utility/math/MatrixDynamicBase.h" + +class MatrixDynamicBaseTestSuite : public CxxTest::TestSuite +{ +public: + void test_matrixDynamicBase_constructors() + { + MatrixDynamicBase matrix0; + MatrixDynamicBase matrix1(3, 5); + + std::vector> testValues = getTestValues(3, 5); + MatrixDynamicBase matrix2(testValues); + + TS_ASSERT_EQUALS(0, matrix0.getColumnsCount()); + TS_ASSERT_EQUALS(0, matrix0.getRowsCount()); + TS_ASSERT_EQUALS(3, matrix1.getColumnsCount()); + TS_ASSERT_EQUALS(5, matrix1.getRowsCount()); + TS_ASSERT_EQUALS(3, matrix2.getColumnsCount()); + TS_ASSERT_EQUALS(5, matrix2.getRowsCount()); + } + + void test_matrixDynamicBase_getValue_setValue() + { + std::vector> testValues = getTestValues(3, 5); + MatrixDynamicBase matrix0(testValues); + + TS_ASSERT_EQUALS(0, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(4, matrix0.getValue(2, 2)); + TS_ASSERT_EQUALS(6, matrix0.getValue(2, 4)); + + matrix0.setValue(0, 0, 42); + matrix0.setValue(2, 2, 84); + matrix0.setValue(2, 4, 126); + + TS_ASSERT_EQUALS(42, matrix0.getValue(0, 0)); + TS_ASSERT_EQUALS(84, matrix0.getValue(2, 2)); + TS_ASSERT_EQUALS(126, matrix0.getValue(2, 4)); + } + +private: + std::vector> getTestValues(const unsigned int numColumns, const unsigned int numRows) + { + std::vector> testValues; + + for(unsigned int x = 0; x < numColumns; x++) + { + std::vector row; + + for(unsigned int y = 0; y < numRows; y++) + { + row.push_back(x + y); + } + + testValues.push_back(row); + } + + return testValues; + } +}; \ No newline at end of file