/* * $Revision: 2565 $ * * last checkin: * $Author: gutwenger $ * $Date: 2012-07-07 17:14:54 +0200 (Sa, 07. Jul 2012) $ ***************************************************************/ /** \file * \brief Implementation of Fast Multipole Multilevel Method (FM^3). * * \author Stefan Hachul * * \par License: * This file is part of the Open Graph Drawing Framework (OGDF). * * \par * Copyright (C)
* See README.txt in the root directory of the OGDF installation for details. * * \par * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * Version 2 or 3 as published by the Free Software Foundation; * see the file LICENSE.txt included in the packaging of this file * for details. * * \par * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * \par * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * \see http://www.gnu.org/copyleft/gpl.html ***************************************************************/ #include "FMMMLayout.h" #include "../basic/Math.h" #include "numexcept.h" #include "MAARPacking.h" #include "Multilevel.h" #include "Edge.h" #include "../basic/simple_graph_alg.h" #include "../basic/basic.h" #include "../internal/energybased/NodeAttributes.h" #include "../internal/energybased/EdgeAttributes.h" #include "Rectangle.h" #include #include #include namespace ogdf { FMMMLayout::FMMMLayout() { initialize_all_options(); } //--------------------------- most important functions -------------------------------- void FMMMLayout::call(GraphAttributes &GA) { const Graph &G = GA.constGraph(); EdgeArray edgelength(G,1.0); call(GA,edgelength); } void FMMMLayout::call(ClusterGraphAttributes &GA) { const Graph &G = GA.constGraph(); //compute depth of cluster tree, also sets cluster depth values const ClusterGraph &CG = GA.constClusterGraph(); int cdepth = CG.treeDepth(); EdgeArray edgeLength(G); //compute lca of end vertices for each edge edge e; forall_edges(e, G) { edgeLength[e] = cdepth - CG.clusterDepth(CG.commonCluster(e->source(),e->target())) + 1; OGDF_ASSERT(edgeLength[e] > 0) } call(GA,edgeLength); GA.updateClusterPositions(); } void FMMMLayout::call(GraphAttributes &GA, const EdgeArray &edgeLength) { const Graph &G = GA.constGraph(); NodeArray A(G); //stores the attributes of the nodes (given by L) EdgeArray E(G); //stores the edge attributes of G Graph G_reduced; //stores a undirected simple and loopfree copy //of G EdgeArray E_reduced; //stores the edge attributes of G_reduced NodeArray A_reduced; //stores the node attributes of G_reduced if(G.numberOfNodes() > 1) { GA.clearAllBends();//all edges are straight-line if(useHighLevelOptions()) update_low_level_options_due_to_high_level_options_settings(); import_NodeAttributes(G,GA,A); import_EdgeAttributes(G,edgeLength,E); double t_total; usedTime(t_total); max_integer_position = pow(2.0,maxIntPosExponent()); init_ind_ideal_edgelength(G,A,E); make_simple_loopfree(G,A,E,G_reduced,A_reduced,E_reduced); call_DIVIDE_ET_IMPERA_step(G_reduced,A_reduced,E_reduced); if(allowedPositions() != apAll) make_positions_integer(G_reduced,A_reduced); time_total = usedTime(t_total); export_NodeAttributes(G_reduced,A_reduced,GA); } else //trivial cases { if(G.numberOfNodes() == 1 ) { node v = G.firstNode(); GA.x(v) = 0; GA.y(v) = 0; } } } void FMMMLayout::call(GraphAttributes &AG, char* ps_file) { call(AG); create_postscript_drawing(AG,ps_file); } void FMMMLayout::call( GraphAttributes &AG, const EdgeArray &edgeLength, char* ps_file) { call(AG,edgeLength); create_postscript_drawing(AG,ps_file); } void FMMMLayout::call_DIVIDE_ET_IMPERA_step( Graph& G, NodeArray& A, EdgeArray& E) { NodeArray component(G); //holds for each node the index of its component number_of_components = connectedComponents(G,component);//calculate components of G Graph* G_sub = new Graph[number_of_components]; NodeArray* A_sub = new NodeArray[number_of_components]; EdgeArray* E_sub = new EdgeArray[number_of_components]; create_maximum_connected_subGraphs(G,A,E,G_sub,A_sub,E_sub,component); if(number_of_components == 1) call_MULTILEVEL_step_for_subGraph(G_sub[0],A_sub[0],E_sub[0],-1); else for(int i = 0; i < number_of_components;i++) call_MULTILEVEL_step_for_subGraph(G_sub[i],A_sub[i],E_sub[i],i); pack_subGraph_drawings (A,G_sub,A_sub); delete_all_subGraphs(G_sub,A_sub,E_sub); } void FMMMLayout::call_MULTILEVEL_step_for_subGraph( Graph& G, NodeArray& A, EdgeArray& E, int /*comp_index*/) { Multilevel Mult; int max_level = 30;//sufficient for all graphs with upto pow(2,30) nodes! //adapt mingraphsize such that no levels are created beyond input graph. if (m_singleLevel) m_minGraphSize = G.numberOfNodes(); Array G_mult_ptr(max_level+1); Array*> A_mult_ptr (max_level+1); Array*> E_mult_ptr (max_level+1); Mult.create_multilevel_representations(G,A,E,randSeed(), galaxyChoice(),minGraphSize(), randomTries(),G_mult_ptr,A_mult_ptr, E_mult_ptr,max_level); for(int i = max_level;i >= 0;i--) { if(i == max_level) create_initial_placement(*G_mult_ptr[i],*A_mult_ptr[i]); else { Mult.find_initial_placement_for_level(i,initialPlacementMult(),G_mult_ptr, A_mult_ptr,E_mult_ptr); update_boxlength_and_cornercoordinate(*G_mult_ptr[i],*A_mult_ptr[i]); } call_FORCE_CALCULATION_step(*G_mult_ptr[i],*A_mult_ptr[i],*E_mult_ptr[i], i,max_level); } Mult.delete_multilevel_representations(G_mult_ptr,A_mult_ptr,E_mult_ptr,max_level); } void FMMMLayout::call_FORCE_CALCULATION_step( Graph& G, NodeArray&A, EdgeArray& E, int act_level, int max_level) { const int ITERBOUND = 10000;//needed to guarantee termination if //stopCriterion() == scThreshold if(G.numberOfNodes() > 1) { int iter = 1; int max_mult_iter = get_max_mult_iter(act_level,max_level,G.numberOfNodes()); double actforcevectorlength = threshold() + 1; NodeArray F_rep(G); //stores rep. forces NodeArray F_attr(G); //stores attr. forces NodeArray F (G); //stores resulting forces NodeArray last_node_movement(G);//stores the force vectors F of the last //iterations (needed to avoid oscillations) set_average_ideal_edgelength(G,E);//needed for easy scaling of the forces make_initialisations_for_rep_calc_classes(G); while( ((stopCriterion() == scFixedIterations)&&(iter <= max_mult_iter)) || ((stopCriterion() == scThreshold)&&(actforcevectorlength >= threshold())&& (iter <= ITERBOUND)) || ((stopCriterion() == scFixedIterationsOrThreshold)&&(iter <= max_mult_iter) && (actforcevectorlength >= threshold())) ) {//while calculate_forces(G,A,E,F,F_attr,F_rep,last_node_movement,iter,0); if(stopCriterion() != scFixedIterations) actforcevectorlength = get_average_forcevector_length(G,F); iter++; }//while if(act_level == 0) { fixTwistedSplits(G, A); call_POSTPROCESSING_step(G,A,E,F,F_attr,F_rep,last_node_movement); } deallocate_memory_for_rep_calc_classes(); } } void FMMMLayout::call_POSTPROCESSING_step( Graph& G, NodeArray& A, EdgeArray& E, NodeArray& F, NodeArray& F_attr, NodeArray& F_rep, NodeArray& last_node_movement) { for(int i = 1; i<= 10; i++) calculate_forces(G,A,E,F,F_attr,F_rep,last_node_movement,i,1); if((resizeDrawing() == true)) { adapt_drawing_to_ideal_average_edgelength(G,A,E); update_boxlength_and_cornercoordinate(G,A); } for(int i = 1; i<= fineTuningIterations(); i++) calculate_forces(G,A,E,F,F_attr,F_rep,last_node_movement,i,2); if((resizeDrawing() == true)) adapt_drawing_to_ideal_average_edgelength(G,A,E); } //------------------------- functions for pre/post-processing ------------------------- void FMMMLayout::initialize_all_options() { //setting high level options useHighLevelOptions(false); pageFormat(pfSquare); unitEdgeLength(100); newInitialPlacement(false); qualityVersusSpeed(qvsBeautifulAndFast); //setting low level options //setting general options randSeed(100);edgeLengthMeasurement(elmBoundingCircle); allowedPositions(apInteger);maxIntPosExponent(40); //setting options for the divide et impera step pageRatio(1.0);stepsForRotatingComponents(10); tipOverCCs(toNone);minDistCC(100); presortCCs(psDecreasingArea); //setting options for the multilevel step minGraphSize(50);galaxyChoice(gcNonUniformProbLowerMass);randomTries(20); maxIterChange(micLinearlyDecreasing);maxIterFactor(10); initialPlacementMult(ipmAdvanced); m_singleLevel = false; //setting options for the force calculation step forceModel(fmNew);springStrength(1);repForcesStrength(1); repulsiveForcesCalculation(rfcNMM);stopCriterion(scFixedIterationsOrThreshold); threshold(0.01);fixedIterations(30);forceScalingFactor(0.05); coolTemperature(false);coolValue(0.99);initialPlacementForces(ipfRandomRandIterNr); //setting options for postprocessing resizeDrawing(true);resizingScalar(1);fineTuningIterations(20); fineTuneScalar(0.2);adjustPostRepStrengthDynamically(true); postSpringStrength(2.0);postStrengthOfRepForces(0.01); //setting options for different repulsive force calculation methods frGridQuotient(2); nmTreeConstruction(rtcSubtreeBySubtree);nmSmallCell(scfIteratively); nmParticlesInLeaves(25); nmPrecision(4); } void FMMMLayout::update_low_level_options_due_to_high_level_options_settings() { PageFormatType pf = pageFormat(); double uel = unitEdgeLength(); bool nip = newInitialPlacement(); QualityVsSpeed qvs = qualityVersusSpeed(); //update initialize_all_options(); useHighLevelOptions(true); pageFormat(pf); unitEdgeLength(uel); newInitialPlacement(nip); qualityVersusSpeed(qvs); if(pageFormat() == pfSquare) pageRatio(1.0); else if(pageFormat() ==pfLandscape) pageRatio(1.4142); else //pageFormat() == pfPortrait pageRatio(0.7071); if(newInitialPlacement()) initialPlacementForces(ipfRandomTime); else initialPlacementForces(ipfRandomRandIterNr); if(qualityVersusSpeed() == qvsGorgeousAndEfficient) { fixedIterations(60); fineTuningIterations(40); nmPrecision(6); } else if(qualityVersusSpeed() == qvsBeautifulAndFast) { fixedIterations(30); fineTuningIterations(20); nmPrecision(4); } else //qualityVersusSpeed() == qvsNiceAndIncredibleSpeed { fixedIterations(15); fineTuningIterations(10); nmPrecision(2); } } void FMMMLayout::import_NodeAttributes( const Graph& G, GraphAttributes& GA, NodeArray& A) { node v; DPoint position; forall_nodes(v,G) { position.m_x = GA.x(v); position.m_y = GA.y(v); A[v].set_NodeAttributes(GA.width(v),GA.height(v),position,NULL,NULL); } } void FMMMLayout::import_EdgeAttributes( const Graph& G, const EdgeArray& edgeLength, EdgeArray& E) { edge e; double length; forall_edges(e,G) { if(edgeLength[e] > 0) //no negative edgelength allowed length = edgeLength[e]; else length = 1; E[e].set_EdgeAttributes(length,NULL,NULL); } } void FMMMLayout::init_ind_ideal_edgelength( const Graph& G, NodeArray& A, EdgeArray& E) { edge e; if (edgeLengthMeasurement() == elmMidpoint) forall_edges(e,G) E[e].set_length(E[e].get_length() * unitEdgeLength()); else //(edgeLengthMeasurement() == elmBoundingCircle) { set_radii(G,A); forall_edges(e,G) E[e].set_length(E[e].get_length() * unitEdgeLength() + radius[e->source()] + radius[e->target()]); } } void FMMMLayout::set_radii(const Graph& G, NodeArray& A) { node v; radius.init(G); double w,h; forall_nodes(v,G) { w = A[v].get_width()/2; h = A[v].get_height()/2; radius[v] = sqrt(w*w+ h*h); } } void FMMMLayout::export_NodeAttributes( Graph& G_reduced, NodeArray& A_reduced, GraphAttributes& GA) { node v_copy; forall_nodes(v_copy,G_reduced) { GA.x(A_reduced[v_copy].get_original_node()) = A_reduced[v_copy].get_position().m_x; GA.y(A_reduced[v_copy].get_original_node()) = A_reduced[v_copy].get_position().m_y; } } void FMMMLayout::make_simple_loopfree( const Graph& G, NodeArray& A, EdgeArrayE, Graph& G_reduced, NodeArray& A_reduced, EdgeArray& E_reduced) { node u_orig,v_orig,v_reduced; edge e_reduced,e_orig; //create the reduced Graph G_reduced and save in A/E links to node/edges of G_reduced //create G_reduced as a copy of G without selfloops! G_reduced.clear(); forall_nodes(v_orig,G) A[v_orig].set_copy_node(G_reduced.newNode()); forall_edges(e_orig,G) { u_orig = e_orig->source(); v_orig = e_orig->target(); if(u_orig != v_orig) E[e_orig].set_copy_edge(G_reduced.newEdge (A[u_orig].get_copy_node(), A[v_orig].get_copy_node())); else E[e_orig].set_copy_edge(NULL);//mark this edge as deleted } //remove parallel (and reversed) edges from G_reduced EdgeArray new_edgelength(G_reduced); List S; S.clear(); delete_parallel_edges(G,E,G_reduced,S,new_edgelength); //make A_reduced, E_reduced valid for G_reduced A_reduced.init(G_reduced); E_reduced.init(G_reduced); //import information for A_reduced, E_reduced and links to the original nodes/edges //of the copy nodes/edges forall_nodes(v_orig,G) { v_reduced = A[v_orig].get_copy_node(); A_reduced[v_reduced].set_NodeAttributes(A[v_orig].get_width(), A[v_orig]. get_height(),A[v_orig].get_position(), v_orig,NULL); } forall_edges(e_orig,G) { e_reduced = E[e_orig].get_copy_edge(); if(e_reduced != NULL) E_reduced[e_reduced].set_EdgeAttributes(E[e_orig].get_length(),e_orig,NULL); } //update edgelength of copy edges in G_reduced associated with a set of parallel //edges in G update_edgelength(S,new_edgelength,E_reduced); } void FMMMLayout::delete_parallel_edges( const Graph& G, EdgeArray& E, Graph& G_reduced, List& S, EdgeArray& new_edgelength) { EdgeMaxBucketFunc MaxSort; EdgeMinBucketFunc MinSort; ListIterator EdgeIterator; edge e_act = 0, e_save = 0; Edge f_act; List sorted_edges; EdgeArray original_edge (G_reduced); //helping array int save_s_index = 0, save_t_index = 0, act_s_index, act_t_index; int counter = 1; Graph* Graph_ptr = &G_reduced; //save the original edges for each edge in G_reduced forall_edges(e_act,G) if(E[e_act].get_copy_edge() != NULL) //e_act is no self_loops original_edge[E[e_act].get_copy_edge()] = e_act; forall_edges(e_act,G_reduced) { f_act.set_Edge(e_act,Graph_ptr); sorted_edges.pushBack(f_act); } sorted_edges.bucketSort(0,G_reduced.numberOfNodes()-1,MaxSort); sorted_edges.bucketSort(0,G_reduced.numberOfNodes()-1,MinSort); //now parallel edges are consecutive in sorted_edges for(EdgeIterator = sorted_edges.begin();EdgeIterator.valid();++EdgeIterator) {//for e_act = (*EdgeIterator).get_edge(); act_s_index = e_act->source()->index(); act_t_index = e_act->target()->index(); if(EdgeIterator != sorted_edges.begin()) {//if if( (act_s_index == save_s_index && act_t_index == save_t_index) || (act_s_index == save_t_index && act_t_index == save_s_index) ) { if(counter == 1) //first parallel edge { S.pushBack(e_save); new_edgelength[e_save] = E[original_edge[e_save]].get_length() + E[original_edge[e_act]].get_length(); } else //more then two parallel edges new_edgelength[e_save] +=E[original_edge[e_act]].get_length(); E[original_edge[e_act]].set_copy_edge(NULL); //mark copy of edge as deleted G_reduced.delEdge(e_act); //delete copy edge in G_reduced counter++; } else { if (counter > 1) { new_edgelength[e_save]/=counter; counter = 1; } save_s_index = act_s_index; save_t_index = act_t_index; e_save = e_act; } }//if else //first edge { save_s_index = act_s_index; save_t_index = act_t_index; e_save = e_act; } }//for //treat special case (last edges were multiple edges) if(counter >1) new_edgelength[e_save]/=counter; } void FMMMLayout::update_edgelength( List& S, EdgeArray& new_edgelength, EdgeArray& E_reduced) { edge e; while (!S.empty()) { e = S.popFrontRet(); E_reduced[e].set_length(new_edgelength[e]); } } //inline double FMMMLayout::get_post_rep_force_strength(int n) //{ // return min(0.2,400.0/double(n)); //} void FMMMLayout::make_positions_integer(Graph& G, NodeArray& A) { node v; double new_x,new_y; if(allowedPositions() == apInteger) {//if //calculate value of max_integer_position max_integer_position = 100 * average_ideal_edgelength * G.numberOfNodes() * G.numberOfNodes(); }//if //restrict positions to lie in [-max_integer_position,max_integer_position] //X [-max_integer_position,max_integer_position] forall_nodes(v,G) if( (A[v].get_x() > max_integer_position) || (A[v].get_y() > max_integer_position) || (A[v].get_x() < max_integer_position * (-1.0)) || (A[v].get_y() < max_integer_position * (-1.0)) ) { DPoint cross_point; DPoint nullpoint (0,0); DPoint old_pos (A[v].get_x(),A[v].get_y()); DPoint lt ( max_integer_position * (-1.0),max_integer_position); DPoint rt ( max_integer_position,max_integer_position); DPoint lb ( max_integer_position * (-1.0),max_integer_position * (-1.0)); DPoint rb ( max_integer_position,max_integer_position * (-1.0)); DLine s (nullpoint,old_pos); DLine left_bound (lb,lt); DLine right_bound (rb,rt); DLine top_bound (lt,rt); DLine bottom_bound (lb,rb); if(s.intersection(left_bound,cross_point)) { A[v].set_x(cross_point.m_x); A[v].set_y(cross_point.m_y); } else if(s.intersection(right_bound,cross_point)) { A[v].set_x(cross_point.m_x); A[v].set_y(cross_point.m_y); } else if(s.intersection(top_bound,cross_point)) { A[v].set_x(cross_point.m_x); A[v].set_y(cross_point.m_y); } else if(s.intersection(bottom_bound,cross_point)) { A[v].set_x(cross_point.m_x); A[v].set_y(cross_point.m_y); } else cout<<"Error FMMMLayout:: make_positions_integer()"< x_max) x_max = AG.x(v); if(AG.y(v) < y_min) y_min = AG.y(v); else if(AG.y(v) > y_max) y_max = AG.y(v); } max_dist = max(x_max -x_min,y_max-y_min); scale_factor = 500.0/max_dist; out_fmmm<<"%!PS-Adobe-2.0 "<source())<<" "<source())<<" " <target())<<" "<target())<<" e"<& A, EdgeArray&E, Graph G_sub[], NodeArray A_sub[], EdgeArray E_sub[], NodeArray& component) { node u_orig,v_orig,v_sub; edge e_sub,e_orig; int i; //create the subgraphs and save links to subgraph nodes/edges in A forall_nodes(v_orig,G) A[v_orig].set_subgraph_node(G_sub[component[v_orig]].newNode()); forall_edges(e_orig,G) { u_orig = e_orig->source(); v_orig = e_orig->target(); E[e_orig].set_subgraph_edge( G_sub[component[u_orig]].newEdge (A[u_orig].get_subgraph_node(),A[v_orig].get_subgraph_node())); } //make A_sub,E_sub valid for the subgraphs for(i = 0; i< number_of_components;i++) { A_sub[i].init(G_sub[i]); E_sub[i].init(G_sub[i]); } //import information for A_sub,E_sub and links to the original nodes/edges //of the subGraph nodes/edges forall_nodes(v_orig,G) { v_sub = A[v_orig].get_subgraph_node(); A_sub[component[v_orig]][v_sub].set_NodeAttributes(A[v_orig].get_width(), A[v_orig].get_height(),A[v_orig].get_position(), v_orig,NULL); } forall_edges(e_orig,G) { e_sub = E[e_orig].get_subgraph_edge(); v_orig = e_orig->source(); E_sub[component[v_orig]][e_sub].set_EdgeAttributes(E[e_orig].get_length(), e_orig,NULL); } } void FMMMLayout::pack_subGraph_drawings( NodeArray& A, Graph G_sub[], NodeArray A_sub[]) { double aspect_ratio_area, bounding_rectangles_area; MAARPacking P; List R; if(stepsForRotatingComponents() == 0) //no rotation calculate_bounding_rectangles_of_components(R,G_sub,A_sub); else rotate_components_and_calculate_bounding_rectangles(R,G_sub,A_sub); P.pack_rectangles_using_Best_Fit_strategy(R,pageRatio(),presortCCs(), aspect_ratio_area,bounding_rectangles_area); export_node_positions(A,R,G_sub,A_sub); } void FMMMLayout::calculate_bounding_rectangles_of_components( List& R, Graph G_sub[], NodeArray A_sub[]) { int i; Rectangle r; R.clear(); for(i=0;i& A, int componenet_index) { Rectangle r; node v; double x_min = 0.0, x_max = 0.0, y_min = 0.0, y_max = 0.0; double act_x_min, act_x_max, act_y_min, act_y_max; double max_boundary;//the maximum of half of the width and half of the height of //each node; (needed to be able to tipp rectangles over without //having access to the height and width of each node) forall_nodes(v,G) { max_boundary = max(A[v].get_width()/2, A[v].get_height()/2); if(v == G.firstNode()) { x_min = A[v].get_x() - max_boundary; x_max = A[v].get_x() + max_boundary; y_min = A[v].get_y() - max_boundary; y_max = A[v].get_y() + max_boundary; } else { act_x_min = A[v].get_x() - max_boundary; act_x_max = A[v].get_x() + max_boundary; act_y_min = A[v].get_y() - max_boundary; act_y_max = A[v].get_y() + max_boundary; if(act_x_min < x_min) x_min = act_x_min; if(act_x_max > x_max) x_max = act_x_max; if(act_y_min < y_min) y_min = act_y_min; if(act_y_max > y_max) y_max = act_y_max; } } //add offset x_min -= minDistCC()/2; x_max += minDistCC()/2; y_min -= minDistCC()/2; y_max += minDistCC()/2; r.set_rectangle(x_max-x_min,y_max-y_min,x_min,y_min,componenet_index); return r; } void FMMMLayout::rotate_components_and_calculate_bounding_rectangles( List&R, Graph G_sub[], NodeArray A_sub[]) { int i,j; double sin_j,cos_j; double angle, act_area, best_area; double ratio,new_width,new_height; Array > best_coords(number_of_components); Array > old_coords(number_of_components); node v_sub; Rectangle r_act,r_best; DPoint new_pos,new_dlc; R.clear(); //make R empty for(i=0;i& A, List& R, Graph G_sub[], NodeArray A_sub[]) { ListIterator RectIterator; Rectangle r; int i; node v_sub; DPoint newpos,tipped_pos,tipped_dlc; for(RectIterator = R.begin();RectIterator.valid();++RectIterator) {//for r = *RectIterator; i = r.get_component_index(); if(r.is_tipped_over()) {//if //calculate tipped coordinates of the nodes forall_nodes(v_sub,G_sub[i]) { tipped_pos.m_x = A_sub[i][v_sub].get_y()*(-1); tipped_pos.m_y = A_sub[i][v_sub].get_x(); A_sub[i][v_sub].set_position(tipped_pos); } }//if forall_nodes(v_sub,G_sub[i]) { newpos = A_sub[i][v_sub].get_position() + r.get_new_dlc_position() - r.get_old_dlc_position(); A[A_sub[i][v_sub].get_original_node()].set_position(newpos); } }//for } //----------------------- functions for multilevel step ----------------------------- inline int FMMMLayout::get_max_mult_iter(int act_level, int max_level, int node_nr) { int iter; if(maxIterChange() == micConstant) //nothing to do iter = fixedIterations(); else if (maxIterChange() == micLinearlyDecreasing) //linearly decreasing values { if(max_level == 0) iter = fixedIterations() + ((maxIterFactor()-1) * fixedIterations()); else iter = fixedIterations() + int((double(act_level)/double(max_level) ) * ((maxIterFactor()-1)) * fixedIterations()); } else //maxIterChange == micRapidlyDecreasing (rapidly decreasing values) { if(act_level == max_level) iter = fixedIterations() + int( (maxIterFactor()-1) * fixedIterations()); else if(act_level == max_level - 1) iter = fixedIterations() + int(0.5 * (maxIterFactor()-1) * fixedIterations()); else if(act_level == max_level - 2) iter = fixedIterations() + int(0.25 * (maxIterFactor()-1) * fixedIterations()); else //act_level >= max_level - 3 iter = fixedIterations(); } //helps to get good drawings for small graphs and graphs with few multilevels if((node_nr <= 500) && (iter < 100)) return 100; else return iter; } //-------------------------- functions for force calculation --------------------------- inline void FMMMLayout::calculate_forces( Graph& G, NodeArray& A, EdgeArray& E, NodeArray& F, NodeArray& F_attr, NodeArray& F_rep, NodeArray& last_node_movement, int iter, int fine_tuning_step) { if(allowedPositions() != apAll) make_positions_integer(G,A); calculate_attractive_forces(G,A,E,F_attr); calculate_repulsive_forces(G,A,F_rep); add_attr_rep_forces(G,F_attr,F_rep,F,iter,fine_tuning_step); prevent_oscilations(G,F,last_node_movement,iter); move_nodes(G,A,F); update_boxlength_and_cornercoordinate(G,A); } void FMMMLayout::init_boxlength_and_cornercoordinate ( Graph& G, NodeArray& A) { //boxlength is set const double MIN_NODE_SIZE = 10; const double BOX_SCALING_FACTOR = 1.1; double w=0,h=0; //helping variables node v; forall_nodes(v,G) { w += max(A[v].get_width(),MIN_NODE_SIZE); h += max(A[v].get_height(),MIN_NODE_SIZE); } boxlength = ceil(max(w,h) * BOX_SCALING_FACTOR); //down left corner of comp. box is the origin down_left_corner.m_x = 0; down_left_corner.m_y = 0; } void FMMMLayout::create_initial_placement (Graph& G, NodeArray& A) { const int BILLION = 1000000000; int i,j,k; node v; if (initialPlacementForces() == ipfKeepPositions) // don't change anything { init_boxlength_and_cornercoordinate(G,A); } else if (initialPlacementForces() == ipfUniformGrid) //set nodes to the midpoints of a grid {//(uniform on a grid) init_boxlength_and_cornercoordinate(G,A); int level = static_cast( ceil(Math::log4(G.numberOfNodes()))); int m = static_cast(pow(2.0,level))-1; bool finished = false; double blall = boxlength/(m+1); //boxlength for boxes at the lowest level (depth) Array all_nodes(G.numberOfNodes()); k = 0; forall_nodes(v,G) { all_nodes[k] = v; k++; } v = all_nodes[0]; k = 0; i = 0; while ((!finished) && (i <= m)) {//while1 j = 0; while((!finished) && (j <= m)) {//while2 A[v].set_x(boxlength*i/(m+1) + blall/2); A[v].set_y(boxlength*j/(m+1) + blall/2); if(k == G.numberOfNodes()-1) finished = true; else { k++; v = all_nodes[k]; } j++; }//while2 i++; }//while1 }//(uniform on a grid) else //randomised distribution of the nodes; {//(random) init_boxlength_and_cornercoordinate(G,A); if(initialPlacementForces() == ipfRandomTime)//(RANDOM based on actual CPU-time) srand((unsigned int)time(0)); else if(initialPlacementForces() == ipfRandomRandIterNr)//(RANDOM based on seed) srand(clock()); forall_nodes(v,G) { DPoint rndp; rndp.m_x = double(randomNumber(0,BILLION))/BILLION;//rand_x in [0,1] rndp.m_y = double(randomNumber(0,BILLION))/BILLION;//rand_y in [0,1] A[v].set_x(rndp.m_x*(boxlength-2)+ 1); A[v].set_y(rndp.m_y*(boxlength-2)+ 1); } }//(random) update_boxlength_and_cornercoordinate(G,A); } inline void FMMMLayout::init_F(Graph& G, NodeArray& F) { DPoint nullpoint (0,0); node v; forall_nodes(v,G) F[v] = nullpoint; } inline void FMMMLayout::make_initialisations_for_rep_calc_classes(Graph& G) { if(repulsiveForcesCalculation() == rfcExact) FR.make_initialisations(boxlength,down_left_corner,frGridQuotient()); else if(repulsiveForcesCalculation() == rfcGridApproximation) FR.make_initialisations(boxlength,down_left_corner,frGridQuotient()); else //(repulsiveForcesCalculation() == rfcNMM NM.make_initialisations(G,boxlength,down_left_corner, nmParticlesInLeaves(),nmPrecision(), nmTreeConstruction(),nmSmallCell()); } void FMMMLayout::calculate_attractive_forces( Graph& G, NodeArray & A, EdgeArray & E, NodeArray& F_attr) { numexcept N; edge e; node u,v; double norm_v_minus_u,scalar; DPoint vector_v_minus_u,f_u; DPoint nullpoint (0,0); //initialisation init_F(G,F_attr); //calculation forall_edges (e,G) {//for u = e->source(); v = e->target(); vector_v_minus_u = A[v].get_position() - A[u].get_position(); norm_v_minus_u = vector_v_minus_u.norm(); if(vector_v_minus_u == nullpoint) f_u = nullpoint; else if(!N.f_near_machine_precision(norm_v_minus_u,f_u)) { scalar = f_attr_scalar(norm_v_minus_u,E[e].get_length())/norm_v_minus_u; f_u.m_x = scalar * vector_v_minus_u.m_x; f_u.m_y = scalar * vector_v_minus_u.m_y; } F_attr[v] = F_attr[v] - f_u; F_attr[u] = F_attr[u] + f_u; }//for } double FMMMLayout::f_attr_scalar(double d, double ind_ideal_edge_length) { double s; if(forceModel() == fmFruchtermanReingold) s = d*d/(ind_ideal_edge_length*ind_ideal_edge_length*ind_ideal_edge_length); else if (forceModel() == fmEades) { double c = 10; if (d == 0) s = -1e10; else s = c * Math::log2(d/ind_ideal_edge_length) /(ind_ideal_edge_length); } else if (forceModel() == fmNew) { double c = Math::log2(d/ind_ideal_edge_length); if (d > 0) s = c * d * d / (ind_ideal_edge_length * ind_ideal_edge_length * ind_ideal_edge_length); else s = -1e10; } else { s = -1e10; cout << " Error FMMMLayout:: f_attr_scalar" << endl; } return s; } void FMMMLayout::add_attr_rep_forces( Graph& G, NodeArray& F_attr, NodeArray& F_rep, NodeArray& F, int iter, int fine_tuning_step) { numexcept N; node v; DPoint f,force; DPoint nullpoint (0,0); double norm_f,scalar; double act_spring_strength,act_rep_force_strength; //set cool_factor if(coolTemperature() == false) cool_factor = 1.0; else if((coolTemperature() == true) && (fine_tuning_step == 0)) { if(iter == 1) cool_factor = coolValue(); else cool_factor *= coolValue(); } if(fine_tuning_step == 1) cool_factor /= 10.0; //decrease the temperature rapidly else if (fine_tuning_step == 2) { if(iter <= fineTuningIterations() -5) cool_factor = fineTuneScalar(); //decrease the temperature rapidly else cool_factor = (fineTuneScalar()/10.0); } //set the values for the spring strength and strength of the rep. force field if(fine_tuning_step <= 1)//usual case { act_spring_strength = springStrength(); act_rep_force_strength = repForcesStrength(); } else if(!adjustPostRepStrengthDynamically()) { act_spring_strength = postSpringStrength(); act_rep_force_strength = postStrengthOfRepForces(); } else //adjustPostRepStrengthDynamically()) { act_spring_strength = postSpringStrength(); act_rep_force_strength = get_post_rep_force_strength(G.numberOfNodes()); } forall_nodes(v,G) { f.m_x = act_spring_strength * F_attr[v].m_x + act_rep_force_strength * F_rep[v].m_x; f.m_y = act_spring_strength * F_attr[v].m_y + act_rep_force_strength * F_rep[v].m_y; f.m_x = average_ideal_edgelength * average_ideal_edgelength * f.m_x; f.m_y = average_ideal_edgelength * average_ideal_edgelength * f.m_y; norm_f = f.norm(); if(f == nullpoint) force = nullpoint; else if(N.f_near_machine_precision(norm_f,force)) restrict_force_to_comp_box(force); else { scalar = min (norm_f * cool_factor * forceScalingFactor(), max_radius(iter))/norm_f; force.m_x = scalar * f.m_x; force.m_y = scalar * f.m_y; } F[v] = force; } } void FMMMLayout::move_nodes( Graph& G, NodeArray& A, NodeArray& F) { node v; forall_nodes(v,G) A[v].set_position(A[v].get_position() + F[v]); } void FMMMLayout::update_boxlength_and_cornercoordinate( Graph& G, NodeArray&A) { node v; double xmin,xmax,ymin,ymax; DPoint midpoint; v = G.firstNode(); midpoint = A[v].get_position(); xmin = xmax = midpoint.m_x; ymin = ymax = midpoint.m_y; forall_nodes(v,G) { midpoint = A[v].get_position(); if (midpoint.m_x < xmin ) xmin = midpoint.m_x; if (midpoint.m_x > xmax ) xmax = midpoint.m_x; if (midpoint.m_y < ymin ) ymin = midpoint.m_y; if (midpoint.m_y > ymax ) ymax = midpoint.m_y; } //set down_left_corner and boxlength down_left_corner.m_x = floor(xmin - 1); down_left_corner.m_y = floor(ymin - 1); boxlength = ceil(max(ymax-ymin, xmax-xmin) *1.01 + 2); //exception handling: all nodes have same x and y coordinate if(boxlength <= 2 ) { boxlength = G.numberOfNodes()* 20; down_left_corner.m_x = floor(xmin) - (boxlength/2); down_left_corner.m_y = floor(ymin) - (boxlength/2); } //export the boxlength and down_left_corner values to the rep. calc. classes if(repulsiveForcesCalculation() == rfcExact || repulsiveForcesCalculation() == rfcGridApproximation) FR.update_boxlength_and_cornercoordinate(boxlength,down_left_corner); else //repulsiveForcesCalculation() == rfcNMM NM.update_boxlength_and_cornercoordinate(boxlength,down_left_corner); } void FMMMLayout::set_average_ideal_edgelength( Graph& G, EdgeArray& E) { double averagelength = 0; edge e; if(G.numberOfEdges() > 0) { forall_edges(e,G) averagelength += E[e].get_length(); average_ideal_edgelength = averagelength/G.numberOfEdges(); } else average_ideal_edgelength = 50; } double FMMMLayout::get_average_forcevector_length (Graph& G, NodeArray& F) { double lengthsum = 0; node v; forall_nodes(v,G) lengthsum += F[v].norm(); lengthsum /=G.numberOfNodes(); return lengthsum; } void FMMMLayout::prevent_oscilations( Graph& G, NodeArray& F, NodeArray& last_node_movement, int iter) { const double pi_times_1_over_6 = 0.52359878; const double pi_times_2_over_6 = 2 * pi_times_1_over_6; const double pi_times_3_over_6 = 3 * pi_times_1_over_6; const double pi_times_4_over_6 = 4 * pi_times_1_over_6; const double pi_times_5_over_6 = 5 * pi_times_1_over_6; const double pi_times_7_over_6 = 7 * pi_times_1_over_6; const double pi_times_8_over_6 = 8 * pi_times_1_over_6; const double pi_times_9_over_6 = 9 * pi_times_1_over_6; const double pi_times_10_over_6 = 10 * pi_times_1_over_6; const double pi_times_11_over_6 = 11 * pi_times_1_over_6; DPoint nullpoint (0,0); double fi; //angle in [0,2pi) measured counterclockwise double norm_old,norm_new,quot_old_new; if (iter > 1) //usual case {//if1 node v; forall_nodes(v,G) { DPoint force_new (F[v].m_x,F[v].m_y); DPoint force_old (last_node_movement[v].m_x,last_node_movement[v].m_y); norm_new = F[v].norm(); norm_old = last_node_movement[v].norm(); if ((norm_new > 0) && (norm_old > 0)) {//if2 quot_old_new = norm_old / norm_new; //prevent oszilations fi = angle(nullpoint,force_old,force_new); if(((fi <= pi_times_1_over_6)||(fi >= pi_times_11_over_6))&& ((norm_new > (norm_old*2.0))) ) { F[v].m_x = quot_old_new * 2.0 * F[v].m_x; F[v].m_y = quot_old_new * 2.0 * F[v].m_y; } else if ((fi >= pi_times_1_over_6)&&(fi <= pi_times_2_over_6)&& (norm_new > (norm_old*1.5) ) ) { F[v].m_x = quot_old_new * 1.5 * F[v].m_x; F[v].m_y = quot_old_new * 1.5 * F[v].m_y; } else if ((fi >= pi_times_2_over_6)&&(fi <= pi_times_3_over_6)&& (norm_new > (norm_old)) ) { F[v].m_x = quot_old_new * F[v].m_x; F[v].m_y = quot_old_new * F[v].m_y; } else if ((fi >= pi_times_3_over_6)&&(fi <= pi_times_4_over_6)&& (norm_new > (norm_old*0.66666666)) ) { F[v].m_x = quot_old_new * 0.66666666 * F[v].m_x; F[v].m_y = quot_old_new * 0.66666666 * F[v].m_y; } else if ((fi >= pi_times_4_over_6)&&(fi <= pi_times_5_over_6)&& (norm_new > (norm_old*0.5)) ) { F[v].m_x = quot_old_new * 0.5 * F[v].m_x; F[v].m_y = quot_old_new * 0.5 * F[v].m_y; } else if ((fi >= pi_times_5_over_6)&&(fi <= pi_times_7_over_6)&& (norm_new > (norm_old*0.33333333)) ) { F[v].m_x = quot_old_new * 0.33333333 * F[v].m_x; F[v].m_y = quot_old_new * 0.33333333 * F[v].m_y; } else if ((fi >= pi_times_7_over_6)&&(fi <= pi_times_8_over_6)&& (norm_new > (norm_old*0.5)) ) { F[v].m_x = quot_old_new * 0.5 * F[v].m_x; F[v].m_y = quot_old_new * 0.5 * F[v].m_y; } else if ((fi >= pi_times_8_over_6)&&(fi <= pi_times_9_over_6)&& (norm_new > (norm_old*0.66666666)) ) { F[v].m_x = quot_old_new * 0.66666666 * F[v].m_x; F[v].m_y = quot_old_new * 0.66666666 * F[v].m_y; } else if ((fi >= pi_times_9_over_6)&&(fi <= pi_times_10_over_6)&& (norm_new > (norm_old)) ) { F[v].m_x = quot_old_new * F[v].m_x; F[v].m_y = quot_old_new * F[v].m_y; } else if ((fi >= pi_times_10_over_6)&&(fi <= pi_times_11_over_6)&& (norm_new > (norm_old*1.5) ) ) { F[v].m_x = quot_old_new * 1.5 * F[v].m_x; F[v].m_y = quot_old_new * 1.5 * F[v].m_y; } }//if2 last_node_movement[v]= F[v]; } }//if1 else if (iter == 1) init_last_node_movement(G,F,last_node_movement); } double FMMMLayout::angle(DPoint& P, DPoint& Q, DPoint& R) { double dx1 = Q.m_x - P.m_x; double dy1 = Q.m_y - P.m_y; double dx2 = R.m_x - P.m_x; double dy2 = R.m_y - P.m_y; double fi;//the angle if ((dx1 == 0 && dy1 == 0) || (dx2 == 0 && dy2 == 0)) cout<<"Multilevel::angle()"<= 1.0 ) fi = 0; if (cosfi <= -1.0 ) fi = Math::pi; else { fi = acos(cosfi); if (dx1*dy2 < dy1*dx2) fi = -fi; if (fi < 0) fi += 2*Math::pi; } return fi; } void FMMMLayout::init_last_node_movement( Graph& G, NodeArray& F, NodeArray& last_node_movement) { node v; forall_nodes(v,G) last_node_movement[v]= F[v]; } void FMMMLayout::adapt_drawing_to_ideal_average_edgelength( Graph& G, NodeArray& A, EdgeArray& E) { edge e; node v; double sum_real_edgelength = 0; double sum_ideal_edgelength = 0; double area_scaling_factor; DPoint new_pos; forall_edges(e,G) { sum_ideal_edgelength += E[e].get_length(); sum_real_edgelength += (A[e->source()].get_position() - A[e->target()].get_position()).norm(); } if(sum_real_edgelength == 0) //very very unlike case area_scaling_factor = 1; else area_scaling_factor = sum_ideal_edgelength/sum_real_edgelength; forall_nodes(v,G) { new_pos.m_x = resizingScalar() * area_scaling_factor * A[v].get_position().m_x; new_pos.m_y = resizingScalar() * area_scaling_factor * A[v].get_position().m_y; A[v].set_position(new_pos); } } void FMMMLayout::fixTwistedSplits(Graph &G, NodeArray& A) { node v; forall_nodes(v, G) { // In order to be a simple two-way split, this node must lead to three others, two of which merge back // together in the same number of steps. std::vector adjacentNodes = getAdjacentNodes(v); if (adjacentNodes.size() == 3) { node direction1Finish; std::vector direction1Path; int direction1Steps; followNodesUntilBranch(v, adjacentNodes[0], &direction1Finish, &direction1Path, &direction1Steps); node direction2Finish; std::vector direction2Path; int direction2Steps; followNodesUntilBranch(v, adjacentNodes[1], &direction2Finish, &direction2Path, &direction2Steps); node direction3Finish; std::vector direction3Path; int direction3Steps; followNodesUntilBranch(v, adjacentNodes[2], &direction3Finish, &direction3Path, &direction3Steps); std::vector * path1 = 0; std::vector * path2 = 0; if (direction1Finish == direction2Finish && direction1Steps == direction2Steps && direction1Finish != direction3Finish) { path1 = &direction1Path; path2 = &direction1Path; } else if (direction1Finish == direction3Finish && direction1Steps == direction3Steps && direction1Finish != direction2Finish) { path1 = &direction1Path; path2 = &direction3Path; } else if (direction2Finish == direction3Finish && direction2Steps == direction3Steps && direction2Finish != direction1Finish) { path1 = &direction2Path; path2 = &direction3Path; } if (path1 != 0 && path2 != 0 && path1->size() > 1 && path2->size() > 1) { // If we got here, that means we've found a simple split! path1 and path2 store the nodes in order, so // we check if any of them cross, and if so, we swap their positions to uncross them. for (size_t i = 0; i < path1->size() - 1; ++i) { node path1Node1 = (*path1)[i]; node path1Node2 = (*path1)[i+1]; node path2Node1 = (*path2)[i]; node path2Node2 = (*path2)[i+1]; DPoint path1Node1Position = A[path1Node1].get_position(); DPoint path1Node2Position = A[path1Node2].get_position(); DPoint path2Node1Position = A[path2Node1].get_position(); DPoint path2Node2Position = A[path2Node2].get_position(); QPointF path1Node1Point(path1Node1Position.m_x, path1Node1Position.m_y); QPointF path1Node2Point(path1Node2Position.m_x, path1Node2Position.m_y); QPointF path2Node1Point(path2Node1Position.m_x, path2Node1Position.m_y); QPointF path2Node2Point(path2Node2Position.m_x, path2Node2Position.m_y); QLineF line1(path1Node1Point, path1Node2Point); QLineF line2(path2Node1Point, path2Node2Point); QPointF intersectionPoint; if (line1.intersects(line2, &intersectionPoint) == QLineF::BoundedIntersection) { A[path1Node2].set_position(path2Node2Position); A[path2Node2].set_position(path1Node2Position); } } } } } } std::vector FMMMLayout::getAdjacentNodes(node v) { std::vector adjacentNodes; ogdf::edge e; forall_adj_edges(e, v) { if (e->source() != v) adjacentNodes.push_back(e->source()); if (e->target() != v) adjacentNodes.push_back(e->target()); } return adjacentNodes; } std::vector FMMMLayout::getAdjacentNodesExcluding(node v, node ex) { std::vector adjacentNodes; ogdf::edge e; forall_adj_edges(e, v) { if (e->source() != v && e->source() != ex) adjacentNodes.push_back(e->source()); if (e->target() != v && e->source() != ex) adjacentNodes.push_back(e->target()); } return adjacentNodes; } void FMMMLayout::followNodesUntilBranch(node start, node first, node * finish, std::vector * path, int * steps) { node prev = start; node current = first; *steps = 0; while (true) { std::vector adjacentNodes = getAdjacentNodesExcluding(current, prev); if (adjacentNodes.size() != 1) break; prev = current; current = adjacentNodes[0]; *steps += 1; path->push_back(prev); } *finish = current; } } //end namespace ogdf