Revise Kruskal implementation
This updates the Kruskal algorithm to a faster variant utilizing a compressed disjoint set and heap
This commit is contained in:
parent
2ab9ceaf02
commit
a2ad84f84d
|
@ -2,6 +2,7 @@
|
||||||
* This program source code file is part of KICAD, a free EDA CAD application.
|
* This program source code file is part of KICAD, a free EDA CAD application.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2013-2017 CERN
|
* Copyright (C) 2013-2017 CERN
|
||||||
|
* Copyright (C) 2019-2020 KiCad Developers, see AUTHORS.txt for contributors.
|
||||||
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
||||||
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
||||||
*
|
*
|
||||||
|
@ -58,19 +59,32 @@ class PROGRESS_REPORTER;
|
||||||
class CN_EDGE
|
class CN_EDGE
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CN_EDGE() {};
|
CN_EDGE()
|
||||||
CN_EDGE( CN_ANCHOR_PTR aSource, CN_ANCHOR_PTR aTarget, int aWeight = 0 ) :
|
: m_weight( 0 ), m_visible( true )
|
||||||
m_source( aSource ),
|
{}
|
||||||
m_target( aTarget ),
|
|
||||||
m_weight( aWeight ) {}
|
CN_EDGE( CN_ANCHOR_PTR aSource, CN_ANCHOR_PTR aTarget, unsigned aWeight = 0 )
|
||||||
|
: m_source( aSource ), m_target( aTarget ), m_weight( aWeight ), m_visible( true )
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This sort operator implements the reverse sort such that the smallest weight will be placed first
|
||||||
|
* in a priority queue
|
||||||
|
* @param aOther Other edge to compare
|
||||||
|
* @return true if our weight is larger than the other weight
|
||||||
|
*/
|
||||||
|
bool operator<( CN_EDGE aOther ) const
|
||||||
|
{
|
||||||
|
return m_weight > aOther.m_weight;
|
||||||
|
}
|
||||||
|
|
||||||
CN_ANCHOR_PTR GetSourceNode() const { return m_source; }
|
CN_ANCHOR_PTR GetSourceNode() const { return m_source; }
|
||||||
CN_ANCHOR_PTR GetTargetNode() const { return m_target; }
|
CN_ANCHOR_PTR GetTargetNode() const { return m_target; }
|
||||||
int GetWeight() const { return m_weight; }
|
unsigned GetWeight() const { return m_weight; }
|
||||||
|
|
||||||
void SetSourceNode( const CN_ANCHOR_PTR& aNode ) { m_source = aNode; }
|
void SetSourceNode( const CN_ANCHOR_PTR& aNode ) { m_source = aNode; }
|
||||||
void SetTargetNode( const CN_ANCHOR_PTR& aNode ) { m_target = aNode; }
|
void SetTargetNode( const CN_ANCHOR_PTR& aNode ) { m_target = aNode; }
|
||||||
void SetWeight( unsigned int weight ) { m_weight = weight; }
|
void SetWeight( unsigned weight ) { m_weight = weight; }
|
||||||
|
|
||||||
void SetVisible( bool aVisible )
|
void SetVisible( bool aVisible )
|
||||||
{
|
{
|
||||||
|
@ -95,8 +109,8 @@ public:
|
||||||
private:
|
private:
|
||||||
CN_ANCHOR_PTR m_source;
|
CN_ANCHOR_PTR m_source;
|
||||||
CN_ANCHOR_PTR m_target;
|
CN_ANCHOR_PTR m_target;
|
||||||
unsigned int m_weight = 0;
|
unsigned m_weight;
|
||||||
bool m_visible = true;
|
bool m_visible;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CN_CONNECTIVITY_ALGO
|
class CN_CONNECTIVITY_ALGO
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* This program source code file is part of KICAD, a free EDA CAD application.
|
* This program source code file is part of KICAD, a free EDA CAD application.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2013-2017 CERN
|
* Copyright (C) 2013-2017 CERN
|
||||||
* Copyright (C) 2018 KiCad Developers, see AUTHORS.txt for contributors.
|
* Copyright (C) 2018-2020 KiCad Developers, see AUTHORS.txt for contributors.
|
||||||
*
|
*
|
||||||
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
||||||
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
||||||
|
@ -81,6 +81,11 @@ public:
|
||||||
return m_pos;
|
return m_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unsigned int Dist( const CN_ANCHOR& aSecond )
|
||||||
|
{
|
||||||
|
return ( m_pos - aSecond.Pos() ).EuclideanNorm();
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns tag, common identifier for connected nodes
|
/// Returns tag, common identifier for connected nodes
|
||||||
inline int GetTag() const
|
inline int GetTag() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
* This program source code file is part of KICAD, a free EDA CAD application.
|
* This program source code file is part of KICAD, a free EDA CAD application.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2013-2017 CERN
|
* Copyright (C) 2013-2017 CERN
|
||||||
|
* Copyright (C) 2019-2020 KiCad Developers, see AUTHORS.txt for contributors.
|
||||||
|
*
|
||||||
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
||||||
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
||||||
*
|
*
|
||||||
|
@ -36,117 +38,97 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
static uint64_t getDistance( const CN_ANCHOR_PTR& aNode1, const CN_ANCHOR_PTR& aNode2 )
|
class disjoint_set
|
||||||
{
|
{
|
||||||
double dx = ( aNode1->Pos().x - aNode2->Pos().x );
|
|
||||||
double dy = ( aNode1->Pos().y - aNode2->Pos().y );
|
|
||||||
|
|
||||||
return sqrt( dx * dx + dy * dy );
|
public:
|
||||||
|
disjoint_set( size_t size )
|
||||||
|
{
|
||||||
|
m_data.resize( size );
|
||||||
|
m_depth.resize( size, 0 );
|
||||||
|
|
||||||
|
for( size_t i = 0; i < size; i++ )
|
||||||
|
m_data[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
int find( int aVal )
|
||||||
|
{
|
||||||
|
int root = aVal;
|
||||||
|
|
||||||
|
while( m_data[root] != root )
|
||||||
|
root = m_data[root];
|
||||||
|
|
||||||
|
// Compress the path
|
||||||
|
while( m_data[aVal] != aVal )
|
||||||
|
{
|
||||||
|
auto& tmp = m_data[aVal];
|
||||||
|
aVal = tmp;
|
||||||
|
tmp = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool sortWeight( const CN_EDGE& aEdge1, const CN_EDGE& aEdge2 )
|
bool unite( int aVal1, int aVal2 )
|
||||||
{
|
{
|
||||||
return aEdge1.GetWeight() < aEdge2.GetWeight();
|
aVal1 = find( aVal1 );
|
||||||
}
|
aVal2 = find( aVal2 );
|
||||||
|
|
||||||
|
if( aVal1 != aVal2 )
|
||||||
static const std::vector<CN_EDGE> kruskalMST( std::list<CN_EDGE>& aEdges,
|
|
||||||
std::vector<CN_ANCHOR_PTR>& aNodes )
|
|
||||||
{
|
{
|
||||||
unsigned int nodeNumber = aNodes.size();
|
if( m_depth[aVal1] < m_depth[aVal2] )
|
||||||
unsigned int mstExpectedSize = nodeNumber - 1;
|
|
||||||
unsigned int mstSize = 0;
|
|
||||||
bool ratsnestLines = false;
|
|
||||||
|
|
||||||
// The output
|
|
||||||
std::vector<CN_EDGE> mst;
|
|
||||||
|
|
||||||
// Set tags for marking cycles
|
|
||||||
std::unordered_map<CN_ANCHOR_PTR, int> tags;
|
|
||||||
unsigned int tag = 0;
|
|
||||||
|
|
||||||
for( auto& node : aNodes )
|
|
||||||
{
|
{
|
||||||
node->SetTag( tag );
|
m_data[aVal1] = aVal2;
|
||||||
tags[node] = tag++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lists of nodes connected together (subtrees) to detect cycles in the graph
|
|
||||||
std::vector<std::list<int> > cycles( nodeNumber );
|
|
||||||
|
|
||||||
for( unsigned int i = 0; i < nodeNumber; ++i )
|
|
||||||
cycles[i].push_back( i );
|
|
||||||
|
|
||||||
// Kruskal algorithm requires edges to be sorted by their weight
|
|
||||||
aEdges.sort( sortWeight );
|
|
||||||
|
|
||||||
while( mstSize < mstExpectedSize && !aEdges.empty() )
|
|
||||||
{
|
|
||||||
//printf("mstSize %d %d\n", mstSize, mstExpectedSize);
|
|
||||||
auto& dt = aEdges.front();
|
|
||||||
|
|
||||||
int srcTag = tags[dt.GetSourceNode()];
|
|
||||||
int trgTag = tags[dt.GetTargetNode()];
|
|
||||||
|
|
||||||
// Check if by adding this edge we are going to join two different forests
|
|
||||||
if( srcTag != trgTag )
|
|
||||||
{
|
|
||||||
// Because edges are sorted by their weight, first we always process connected
|
|
||||||
// items (weight == 0). Once we stumble upon an edge with non-zero weight,
|
|
||||||
// it means that the rest of the lines are ratsnest.
|
|
||||||
if( !ratsnestLines && dt.GetWeight() != 0 )
|
|
||||||
ratsnestLines = true;
|
|
||||||
|
|
||||||
// Update tags
|
|
||||||
if( ratsnestLines )
|
|
||||||
{
|
|
||||||
for( auto it = cycles[trgTag].begin(); it != cycles[trgTag].end(); ++it )
|
|
||||||
{
|
|
||||||
tags[aNodes[*it]] = srcTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do a copy of edge, but make it RN_EDGE_MST. In contrary to RN_EDGE,
|
|
||||||
// RN_EDGE_MST saves both source and target node and does not require any other
|
|
||||||
// edges to exist for getting source/target nodes
|
|
||||||
CN_EDGE newEdge ( dt.GetSourceNode(), dt.GetTargetNode(), dt.GetWeight() );
|
|
||||||
|
|
||||||
assert( newEdge.GetSourceNode()->GetTag() != newEdge.GetTargetNode()->GetTag() );
|
|
||||||
assert( newEdge.GetWeight() > 0 );
|
|
||||||
|
|
||||||
mst.push_back( newEdge );
|
|
||||||
++mstSize;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// for( it = cycles[trgTag].begin(), itEnd = cycles[trgTag].end(); it != itEnd; ++it )
|
m_data[aVal2] = aVal1;
|
||||||
// for( auto it : cycles[trgTag] )
|
|
||||||
for( auto it = cycles[trgTag].begin(); it != cycles[trgTag].end(); ++it )
|
if( m_depth[aVal1] == m_depth[aVal2] )
|
||||||
|
m_depth[aVal1]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<int> m_data;
|
||||||
|
std::vector<int> m_depth;
|
||||||
|
};
|
||||||
|
|
||||||
|
void RN_NET::kruskalMST( std::priority_queue<CN_EDGE> &aEdges )
|
||||||
{
|
{
|
||||||
tags[aNodes[*it]] = srcTag;
|
disjoint_set dset( m_nodes.size() );
|
||||||
aNodes[*it]->SetTag( srcTag );
|
|
||||||
|
m_rnEdges.clear();
|
||||||
|
|
||||||
|
for( size_t i = 0; i < m_nodes.size(); i++ )
|
||||||
|
m_nodes[i]->SetTag( i );
|
||||||
|
|
||||||
|
while( !aEdges.empty() )
|
||||||
|
{
|
||||||
|
auto& tmp = aEdges.top();
|
||||||
|
|
||||||
|
int u = tmp.GetSourceNode()->GetTag();
|
||||||
|
int v = tmp.GetTargetNode()->GetTag();
|
||||||
|
|
||||||
|
if( dset.unite( u, v ) )
|
||||||
|
{
|
||||||
|
if( tmp.GetWeight() > 0 )
|
||||||
|
m_rnEdges.push_back( tmp );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processing a connection, decrease the expected size of the ratsnest MST
|
aEdges.pop();
|
||||||
--mstExpectedSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move nodes that were marked with old tag to the list marked with the new tag
|
|
||||||
cycles[srcTag].splice( cycles[srcTag].end(), cycles[trgTag] );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the edge that was just processed
|
|
||||||
aEdges.erase( aEdges.begin() );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probably we have discarded some of edges, so reduce the size
|
|
||||||
mst.resize( mstSize );
|
|
||||||
|
|
||||||
return mst;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -201,9 +183,9 @@ public:
|
||||||
m_allNodes.push_back( aNode );
|
m_allNodes.push_back( aNode );
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::list<CN_EDGE> Triangulate()
|
const std::priority_queue<CN_EDGE> Triangulate()
|
||||||
{
|
{
|
||||||
std::list<CN_EDGE> mstEdges;
|
std::priority_queue<CN_EDGE> mstEdges;
|
||||||
std::list<hed::EDGE_PTR> triangEdges;
|
std::list<hed::EDGE_PTR> triangEdges;
|
||||||
std::vector<hed::NODE_PTR> triNodes;
|
std::vector<hed::NODE_PTR> triNodes;
|
||||||
|
|
||||||
|
@ -270,7 +252,7 @@ public:
|
||||||
{
|
{
|
||||||
auto src = m_allNodes[ triNodes[i]->Id() ];
|
auto src = m_allNodes[ triNodes[i]->Id() ];
|
||||||
auto dst = m_allNodes[ triNodes[i + 1]->Id() ];
|
auto dst = m_allNodes[ triNodes[i + 1]->Id() ];
|
||||||
mstEdges.emplace_back( src, dst, getDistance( src, dst ) );
|
mstEdges.emplace( src, dst, src->Dist( *dst ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -284,7 +266,7 @@ public:
|
||||||
auto src = m_allNodes[ e->GetSourceNode()->Id() ];
|
auto src = m_allNodes[ e->GetSourceNode()->Id() ];
|
||||||
auto dst = m_allNodes[ e->GetTargetNode()->Id() ];
|
auto dst = m_allNodes[ e->GetTargetNode()->Id() ];
|
||||||
|
|
||||||
mstEdges.emplace_back( src, dst, getDistance( src, dst ) );
|
mstEdges.emplace( src, dst, src->Dist( *dst ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,7 +287,7 @@ public:
|
||||||
const auto& prevNode = chain[j - 1];
|
const auto& prevNode = chain[j - 1];
|
||||||
const auto& curNode = chain[j];
|
const auto& curNode = chain[j];
|
||||||
int weight = prevNode->GetCluster() != curNode->GetCluster() ? 1 : 0;
|
int weight = prevNode->GetCluster() != curNode->GetCluster() ? 1 : 0;
|
||||||
mstEdges.emplace_back( prevNode, curNode, weight );
|
mstEdges.emplace( prevNode, curNode, weight );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,13 +349,13 @@ void RN_NET::compute()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for( const auto& e : m_boardEdges )
|
for( const auto& e : m_boardEdges )
|
||||||
triangEdges.push_back( e );
|
triangEdges.push( e );
|
||||||
|
|
||||||
// Get the minimal spanning tree
|
// Get the minimal spanning tree
|
||||||
#ifdef PROFILE
|
#ifdef PROFILE
|
||||||
PROF_COUNTER cnt2("mst");
|
PROF_COUNTER cnt2("mst");
|
||||||
#endif
|
#endif
|
||||||
m_rnEdges = kruskalMST( triangEdges, m_nodes );
|
kruskalMST( triangEdges );
|
||||||
#ifdef PROFILE
|
#ifdef PROFILE
|
||||||
cnt2.Show();
|
cnt2.Show();
|
||||||
#endif
|
#endif
|
||||||
|
@ -442,10 +424,14 @@ bool RN_NET::NearestBicoloredPair( const RN_NET& aOtherNet, CN_ANCHOR_PTR& aNode
|
||||||
|
|
||||||
for( const auto& nodeA : m_nodes )
|
for( const auto& nodeA : m_nodes )
|
||||||
{
|
{
|
||||||
|
if( nodeA->GetNoLine() )
|
||||||
|
continue;
|
||||||
|
|
||||||
for( const auto& nodeB : aOtherNet.m_nodes )
|
for( const auto& nodeB : aOtherNet.m_nodes )
|
||||||
{
|
{
|
||||||
if( !nodeA->GetNoLine() )
|
if( nodeB->GetNoLine() )
|
||||||
{
|
continue;
|
||||||
|
|
||||||
auto squaredDist = ( nodeA->Pos() - nodeB->Pos() ).SquaredEuclideanNorm();
|
auto squaredDist = ( nodeA->Pos() - nodeB->Pos() ).SquaredEuclideanNorm();
|
||||||
|
|
||||||
if( squaredDist < distMax )
|
if( squaredDist < distMax )
|
||||||
|
@ -457,7 +443,6 @@ bool RN_NET::NearestBicoloredPair( const RN_NET& aOtherNet, CN_ANCHOR_PTR& aNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,6 +148,9 @@ protected:
|
||||||
///> Recomputes ratsnest from scratch.
|
///> Recomputes ratsnest from scratch.
|
||||||
void compute();
|
void compute();
|
||||||
|
|
||||||
|
///> Compute the minimum spanning tree using Kruskal's algorithm
|
||||||
|
void kruskalMST( std::priority_queue<CN_EDGE> &aEdges );
|
||||||
|
|
||||||
///> Vector of nodes
|
///> Vector of nodes
|
||||||
std::vector<CN_ANCHOR_PTR> m_nodes;
|
std::vector<CN_ANCHOR_PTR> m_nodes;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue