//========================================================================= // FORCEDIRECTEDEMBEDDING.H - part of // OMNeT++/OMNEST // Discrete System Simulation in C++ // // Author: Levente Meszaros // //========================================================================= /*--------------------------------------------------------------* Copyright (C) 2006-2017 OpenSim Ltd. This file is distributed WITHOUT ANY WARRANTY. See the file `license' for details on this and other legal matters. *--------------------------------------------------------------*/ #ifndef __OMNETPP_LAYOUT_FORCEDIRECTEDEMBEDDING_H #define __OMNETPP_LAYOUT_FORCEDIRECTEDEMBEDDING_H #include <cstdint> #include <algorithm> #include <ctime> #include <iostream> #include "geometry.h" #include "forcedirectedparametersbase.h" namespace omnetpp { namespace layout { class LAYOUT_API ForceDirectedEmbedding { protected: /** * True means internal state for the layout has been initialized. * Initialization is not done during construction time, * but it is done not later than the first call to embed. */ bool initialized; /** * True indicates that the embedding is complete, this state may be reset by calling reinitialize. */ bool finished; /** * Total number of calculation cycles in the main loop since the last reinitialize call. */ int cycle; /** * Total number of calculation probe cycles in the inner loop since the last reinitialize call. */ int probCycle; /** * The total number of ticks spent on calculation since the last reinitialize call. */ long elapsedTicks; /** * Total virtual calculation time since the last reinitialize call. */ double elapsedTime; /** * Total kinetic energy seen in all cycles since the last reinitialize call. */ double kineticEnergySum; /** * Total mass of bodies. */ double totalMass; /** * Last calculated acceleration error. */ double lastAccelerationError; /** * Last calculated maximum velocity. */ double lastMaxVelocity; /** * Last calculated maximum acceleration. */ double lastMaxAcceleration; // positions std::vector<Pt> pn; // accelerations std::vector<Pt> an; // velocities std::vector<Pt> vn; // acceleration approximations std::vector<Pt> a1; std::vector<Pt> a2; std::vector<Pt> a3; std::vector<Pt> a4; // velocity deltas std::vector<Pt> dvn; std::vector<Pt> tvn; // position deltas std::vector<Pt> dpn; std::vector<Pt> tpn; /** * The calculation will find a solution for these variables. * Members are destructed. */ std::vector<Variable *> variables; /** * Used to generate forces in each cycle of the calculation. * Members are destructed. */ std::vector<IForceProvider *> forceProviders; /** * The various bodies which are part of the calculation. * Members are destructed. */ std::vector<IBody *> bodies; public: /** * Algorithm parameters. */ ForceDirectedParameters parameters; /** * Valid debug levels are: 0, 1, 2, 3, 4. * Higher debug level will print more debug messages to the standard output during embedding. * Debug level 0 means embedding will not print anything. */ int debugLevel; /** * When true embedding stops at every cycle to be able to inspect the state of the embedding. * Call embed again to continue. */ bool inspected; /** * A value between 0 and 1, where 0 means initialized state and 1 means finished state. * This will be updated according to the current internal state during the solution. * The value might decrease during the calculation although it is expected to increase most of the time. */ double relaxFactor; /** * The total time spent on calculation since the last reinitialize call. */ double elapsedCalculationTime; /** * Time step is automatically updated during the solution. It will always * have the highest value so that the acceleration error is less than the max * acceleration error. The time step is either multiplied or divided by the * time step multiplier according to the current acceleration error. */ double updatedTimeStep; public: ForceDirectedEmbedding(); virtual ~ForceDirectedEmbedding(); const std::vector<Variable *>& getVariables() const { return variables; } const std::vector<IForceProvider *>& getForceProviders() const { return forceProviders; } void addForceProvider(IForceProvider *forceProvider) { forceProviders.push_back(forceProvider); forceProvider->setForceDirectedEmbedding(this); } void removeForceProvider(IForceProvider *forceProvider) { std::vector<IForceProvider *>::iterator it = std::find(forceProviders.begin(), forceProviders.end(), forceProvider); if (it != forceProviders.end()) forceProviders.erase(it); } const std::vector<IBody *>& getBodies() const { return bodies; } void addBody(IBody *body) { bodies.push_back(body); body->setForceDirectedEmbedding(this); Variable *variable = body->getVariable(); if (std::find(variables.begin(), variables.end(), variable) == variables.end()) { variable->setForceDirectedEmbedding(this); variables.push_back(variable); } } bool getFinished() { return finished; } /** * Sets the default parameters. */ ForceDirectedParameters getParameters(int32_t seed = 1); /** * Clears all results from previous calculations and sets initial values. */ void reinitialize(); /** * Calculate the total current kinetic energy. */ double getKineticEnergy() { double sum = 0; for (int i = 0; i < (int)variables.size(); i++) sum += variables[i]->getKineticEnergy(); return sum; } /** * Calculate the total current potential energy. */ double getPotentialEnergy() { double sum = 0; for (int i = 0; i < (int)forceProviders.size(); i++) sum += forceProviders[i]->getPotentialEnergy(); return sum; } double getEnergyBasedFrictionCoefficient(double time) { return 3 * totalMass / time * log((getPotentialEnergy() + getKineticEnergy()) / parameters.velocityRelaxLimit); } Rc getBoundingRectangle(); /** * Find the solution for the differential equation using a * modified Runge-Kutta 4th order algorithm. * * a1 = a[pn, vn] * a2 = a[pn + h / 2 * vn + h * h / 8 * a1, vn + h / 2 * a1] * a3 = a[pn + h / 2 * vn + h * h / 8 * a2, vn + h / 2 * a2] * a4 = a[pn + h * vn + h * h / 2 * a3, vn + h * a3] * * pn+1 = pn + h * vn + h * h / 6 * [a1 + a2 + a3] * vn+1 = vn + h / 6 * (a1 + 2 * a2 + 2 * a3 + a4) * * This algorithm adaptively modifies timeStep and friction. */ void embed(); /** * Writes internal debug information into the given stream. */ void writeDebugInformation(std::ostream& ostream); protected: /** * Create a Pt array filled with zeros. */ std::vector<Pt> createPtArray() { std::vector<Pt> pts; for (int i = 0; i < (int)variables.size(); i++) pts.push_back(Pt::getZero()); return pts; } /** * Calculate the average relative error of any corresponding pair between a1, a2, a3 and a4 * relative to the absolute a values. */ double averageRelativeError(const std::vector<Pt>& a1, const std::vector<Pt>& a2, const std::vector<Pt>& a3, const std::vector<Pt>& a4) { double sum1 = 0; double sum2 = 0; Assert(a1.size() == a2.size() && a2.size() == a3.size() && a3.size() == a4.size()); for (int i = 0; i < (int)a1.size(); i++) { sum1 += a1[i].getDistance(a2[i]); sum1 += a2[i].getDistance(a3[i]); sum1 += a3[i].getDistance(a4[i]); sum2 += a1[i].getLength(); sum2 += a2[i].getLength(); sum2 += a3[i].getLength(); sum2 += a4[i].getLength(); } sum1 /= a1.size() * 3; sum2 /= a1.size() * 4; return sum2 == 0 ? 0 : sum1 / sum2; } /** * Calculate the maximum difference of any corresponding pair between a1, a2, a3 and a4. */ double maximumDifference(const std::vector<Pt>& a1, const std::vector<Pt>& a2, const std::vector<Pt>& a3, const std::vector<Pt>& a4) { double max = 0; Assert(a1.size() == a2.size() && a2.size() == a3.size() && a3.size() == a4.size()); for (int i = 0; i < (int)a1.size(); i++) { max = std::max(max, a1[i].getDistance(a2[i])); max = std::max(max, a2[i].getDistance(a3[i])); max = std::max(max, a3[i].getDistance(a4[i])); } return max; } /** * pts = a + b * c */ void addMultiplied(std::vector<Pt>& pts, const std::vector<Pt>& a, double b, const std::vector<Pt>& c) { Assert(a.size() == c.size()); Assert(pts.size() == c.size()); for (int i = 0; i < (int)pts.size(); i++) pts[i].assign(c[i]).multiply(b).add(a[i]); } /** * pts += a * b */ void incrementWithMultiplied(std::vector<Pt>& pts, double a, const std::vector<Pt>& b) { Pt pt; Assert(pts.size() == b.size()); for (int i = 0; i < (int)pts.size(); i++) { pt.assign(b[i]).multiply(a); pts[i].add(pt); } } /** * pts = a + b */ void add(std::vector<Pt>& pts, const std::vector<Pt>& a, const std::vector<Pt>& b) { Assert(a.size() == b.size()); for (int i = 0; i < (int)pts.size(); i++) pts[i].assign(a[i]).add(b[i]); } /** * pts += a */ void increment(std::vector<Pt>& pts, const std::vector<Pt>& a) { Assert(pts.size() == a.size()); for (int i = 0; i < (int)pts.size(); i++) pts[i].add(a[i]); } /** * pts *= a */ void multiply(std::vector<Pt>& pts, double a) { for (int i = 0; i < (int)pts.size(); i++) pts[i].multiply(a); } /** * an = b[pn, vn] */ void a(std::vector<Pt>& an, const std::vector<Pt>& pn, const std::vector<Pt>& vn); /** * Convert measured ticks to milliseconds. */ double ticksToMilliseconds(long ticks) { return (double)ticks / CLOCKS_PER_SEC * 1000; } }; } // namespace layout } // namespace omnetpp #endif