262 const std::vector<COMPONENT*>& aStructuralMatches,
265 std::vector<TOPOLOGY_MISMATCH_REASON>& aMismatchReasons,
266 const std::atomic<bool>* aCancelled )
268 if( aCancelled && aCancelled->load( std::memory_order_relaxed ) )
273 aMismatchReasons.clear();
274 std::vector<COMPONENT*> matches;
275 int candidatesChecked = 0;
281 double netCheckMs = 0.0;
283 for(
COMPONENT* cmpTarget : aStructuralMatches )
285 if( partialMatches.
m_locked.find( cmpTarget ) != partialMatches.
m_locked.end() )
291 cmpTarget->m_reference );
295 localReason.
m_candidate = cmpTarget->GetParent()->GetReferenceAsString();
301 netCheckMs += timerNet.
msecs();
306 matches.push_back( cmpTarget );
310 wxLogTrace(
traceTopoMatch, wxT(
"Reject [net topo mismatch]\n" ) );
311 aMismatchReasons.push_back( localReason );
317 std::unordered_map<COMPONENT*, double> simScores;
318 simScores.reserve( matches.size() );
324 for(
size_t i = 0; i < aRef->
m_pins.size(); i++ )
326 if( aRef->
m_pins[i]->GetNetCode() == match->m_pins[i]->GetNetCode() )
330 simScores[match] =
static_cast<double>( n ) /
static_cast<double>( aRef->
m_pins.size() );
333 std::sort( matches.begin(), matches.end(),
336 double simA = simScores[a];
337 double simB = simScores[b];
342 return a->GetParent()->GetReferenceAsString()
343 < b->GetParent()->GetReferenceAsString();
348 if( matches.empty() && aMismatchReasons.empty() )
355 if( !aStructuralReason.
m_reason.IsEmpty() )
357 aMismatchReasons.push_back( aStructuralReason );
363 reason.
m_reason =
_(
"No compatible component found in the target area." );
364 aMismatchReasons.push_back( reason );
371 wxT(
" findMatch '%s' (%d pins): %s total, checked %d/%d structural, "
372 "netCheck %0.3f ms, score %0.3f ms, %d matches" ),
374 candidatesChecked, (
int) aStructuralMatches.size(),
375 netCheckMs, timerScore.
msecs(),
376 (
int) matches.size() );
672 const std::vector<COMPONENT*>& aTargetComponents,
673 std::vector<TOPOLOGY_MISMATCH_REASON>& aMismatchReasons )
682 for(
const auto& [reference, footprint] : ref.
footprintOf )
693 reason.
m_reason = wxString::Format(
_(
"%s uses footprint '%s' in the reference area but "
694 "'%s' in the target area." ),
695 reference, footprint, other->second );
696 aMismatchReasons.push_back( reason );
700 std::vector<TOPOLOGY_MISMATCH_REASON> namedParts;
701 std::vector<TOPOLOGY_MISMATCH_REASON> countsOnly;
702 std::vector<wxString> refOnly;
703 std::vector<wxString> targetOnly;
705 const size_t maxNamed = 12;
707 auto joinParts = [](
const std::vector<wxString>& aParts )
711 for(
const wxString& part : aParts )
713 if( !joined.IsEmpty() )
714 joined += wxT(
", " );
722 for(
const auto& [footprint, count] : ref.
counts )
724 if( !target.
counts.count( footprint ) )
725 refOnly.push_back( footprint );
728 for(
const auto& [footprint, count] : target.
counts )
730 if( !ref.
counts.count( footprint ) )
731 targetOnly.push_back( footprint );
736 const bool pairedSwap = refOnly.size() == 1 && targetOnly.size() == 1
737 && ref.
partsUsing.at( refOnly.front() ).size() <= maxNamed
738 && target.
partsUsing.at( targetOnly.front() ).size() <= maxNamed;
744 wxString::Format(
_(
"Footprint '%s' in the reference area (%s) appears as "
745 "'%s' in the target area (%s)." ),
746 refOnly.front(), joinParts( ref.
partsUsing.at( refOnly.front() ) ),
747 targetOnly.front(), joinParts( target.
partsUsing.at( targetOnly.front() ) ) );
748 namedParts.push_back( reason );
751 auto reportUsage = [&](
const wxString& aFootprint,
int aRefUses,
int aTargetUses )
753 if( aRefUses == aTargetUses )
756 if( pairedSwap && ( aFootprint == refOnly.front() || aFootprint == targetOnly.front() ) )
761 const std::vector<wxString>& parts =
765 if( ( aRefUses == 0 || aTargetUses == 0 ) && parts.size() <= maxNamed )
767 const wxString named = joinParts( parts );
771 reason.
m_reason = wxString::Format(
_(
"Footprint '%s' is used by %s in the target "
772 "area but by nothing in the reference area." ),
777 reason.
m_reason = wxString::Format(
_(
"Footprint '%s' is used by %s in the "
778 "reference area but by nothing in the "
783 namedParts.push_back( reason );
787 reason.
m_reason = wxString::Format(
_(
"Footprint '%s': %d in the reference area, %d in "
788 "the target area." ),
789 aFootprint, aRefUses, aTargetUses );
790 countsOnly.push_back( reason );
794 for(
const auto& [footprint, refUses] : ref.
counts )
796 auto used = target.
counts.find( footprint );
798 reportUsage( footprint, refUses, used == target.
counts.end() ? 0 : used->second );
801 for(
const auto& [footprint, targetUses] : target.
counts )
803 if( !ref.
counts.count( footprint ) )
804 reportUsage( footprint, 0, targetUses );
807 aMismatchReasons.insert( aMismatchReasons.end(), namedParts.begin(), namedParts.end() );
808 aMismatchReasons.insert( aMismatchReasons.end(), countsOnly.begin(), countsOnly.end() );
815 std::vector<TOPOLOGY_MISMATCH_REASON>& aMismatchReasons,
818 std::vector<BACKTRACK_STAGE> stack;
821 aMismatchReasons.clear();
827 int backtrackCount = 0;
828 double mrvTotalMs = 0.0;
830 std::vector<TOPOLOGY_MISMATCH_REASON> localReasons;
837 reason.
m_reason =
_(
"Neither area has any footprints to match." );
839 reason.
m_reason =
_(
"The reference area has no footprints to match." );
841 reason.
m_reason =
_(
"The target area has no footprints to match." );
843 aMismatchReasons.push_back( reason );
850 reason.
m_reason = wxString::Format(
_(
"The reference area has %d components and the "
851 "target area has %d." ),
853 aMismatchReasons.push_back( reason );
864 std::vector<std::vector<COMPONENT*>> structuralMatches( numRef );
869 std::vector<TOPOLOGY_MISMATCH_REASON> structuralReasons( numRef );
874 std::vector<std::future<void>> futures;
875 futures.reserve( numRef );
877 const std::atomic<bool>* cancelled = aParams.
m_cancelled;
879 for(
size_t i = 0; i < numRef; i++ )
881 futures.emplace_back(
tp.submit_task(
882 [
this, i, aTarget, &structuralMatches, &structuralReasons, cancelled]()
884 if( cancelled && cancelled->load( std::memory_order_relaxed ) )
887 COMPONENT* ref = m_components[i];
888 TOPOLOGY_MISMATCH_REASON reason;
889 TOPOLOGY_MISMATCH_REASON bestReason;
892 for( COMPONENT* tgt : aTarget->m_components )
894 if( ref->MatchesWith( tgt, reason ) )
896 structuralMatches[i].push_back( tgt );
903 if( tgt->m_reference == ref->m_reference )
905 else if( ref->IsSameKind( *tgt ) )
907 else if( COMPONENT::prefixesShareCommonBase( ref->m_prefix, tgt->m_prefix ) )
910 if( rank >= bestRank )
917 if( structuralMatches[i].empty() )
918 structuralReasons[i] = bestReason;
922 for(
auto& f : futures )
925 timerPrecompute.
Stop();
928 wxT(
"Structural precomputation: %s (%d source x %d target)" ),
929 timerPrecompute.to_string(), (
int) numRef,
930 (
int) aTarget->m_components.size() );
932 if( aParams.m_cancelled && aParams.m_cancelled->load( std::memory_order_relaxed ) )
935 top.m_ref = m_components.front();
938 stack.push_back(
top );
942 while( !stack.empty() )
944 if( aParams.m_cancelled && aParams.m_cancelled->load( std::memory_order_relaxed ) )
948 auto& current = stack.back();
950 for(
auto it = current.m_locked.begin(); it != current.m_locked.end(); it++ )
952 if (it->second == current.m_ref)
954 wxLogTrace(
traceTopoMatch, wxT(
"stk: Remove %s from locked\n" ),
955 current.m_ref->m_reference );
956 current.m_locked.erase( it );
961 if( nloops >= c_ITER_LIMIT )
966 reason.
m_reason = wxString::Format(
_(
"Gave up after %d attempts to pair up the two "
967 "areas. Either their connections differ, or too "
968 "many components are interchangeable to tell "
972 if( aMismatchReasons.empty() )
973 aMismatchReasons.push_back( reason );
975 aMismatchReasons.insert( aMismatchReasons.begin(), reason );
980 if( current.m_currentMatch < 0 )
982 PROF_TIMER timerInitMatch;
984 localReasons.clear();
985 current.m_matches = aTarget->findMatchingComponents(
986 current.m_ref, structuralMatches[current.m_refIndex],
987 structuralReasons[current.m_refIndex], current, localReasons,
988 aParams.m_cancelled );
990 timerInitMatch.
Stop();
993 wxT(
"iter %d: initial match for '%s' (%d pins): %s, %d candidates" ),
994 nloops, current.m_ref->m_reference, current.m_ref->GetPinCount(),
995 timerInitMatch.
to_string(), (
int) current.m_matches.size() );
997 if( current.m_matches.empty() && aMismatchReasons.empty() && !localReasons.empty() )
998 aMismatchReasons = localReasons;
1000 current.m_currentMatch = 0;
1003 wxLogTrace(
traceTopoMatch, wxT(
"stk: Current '%s' stack %d cm %d/%d locked %d/%d\n" ),
1004 current.m_ref->m_reference, (
int) stack.size(), current.m_currentMatch,
1005 (
int) current.m_matches.size(), (
int) current.m_locked.size(),
1006 (
int) m_components.size() );
1008 if( current.m_currentMatch == 0 && current.m_matches.size() > 1 )
1009 breakTie( current.m_ref, current.m_matches );
1011 if ( current.m_matches.empty() )
1013 wxLogTrace(
traceTopoMatch, wxT(
"stk: No matches at all, going up [level=%d]\n" ),
1014 (
int) stack.size() );
1020 if( current.m_currentMatch >= 0
1021 &&
static_cast<size_t>( current.m_currentMatch ) >= current.m_matches.size() )
1023 wxLogTrace(
traceTopoMatch, wxT(
"stk: No more matches, going up [level=%d]\n" ),
1024 (
int) stack.size() );
1030 auto& match = current.m_matches[current.m_currentMatch];
1032 wxLogTrace(
traceTopoMatch, wxT(
"stk: candidate '%s', match list : ( " ),
1033 current.m_matches[current.m_currentMatch]->m_reference, current.m_refIndex );
1035 for(
auto m : current.m_matches )
1036 wxLogTrace(
traceTopoMatch, wxT(
"%s " ), m->GetParent()->GetReferenceAsString() );
1040 current.m_currentMatch++;
1041 current.m_locked[match] = current.m_ref;
1043 if( aParams.m_matchedComponents )
1045 aParams.m_matchedComponents->store( (
int) current.m_locked.size(),
1046 std::memory_order_relaxed );
1049 if( current.m_locked.size() == m_components.size() )
1051 current.m_nloops = nloops;
1054 aMismatchReasons.clear();
1056 for(
auto iter : current.m_locked )
1057 aResult[ iter.second->GetParent() ] = iter.first->GetParent();
1061 wxT(
"Isomorphism: %s, %d iterations, %d backtracks, "
1062 "MRV total %0.1f ms (%d candidates)" ),
1063 timerTotal.to_string(), nloops, backtrackCount, mrvTotalMs,
1064 (
int) m_components.size() );
1073 struct MRV_CANDIDATE
1077 std::vector<COMPONENT*> m_matches;
1078 std::vector<TOPOLOGY_MISMATCH_REASON> m_reasons;
1081 std::vector<MRV_CANDIDATE> mrvCandidates;
1085 std::unordered_set<COMPONENT*> lockedRefs;
1086 lockedRefs.reserve( current.m_locked.size() );
1088 for(
const auto& [tgt, ref] : current.m_locked )
1089 lockedRefs.insert( ref );
1091 for(
size_t i = 0; i < m_components.size(); i++ )
1095 if( cmp != current.m_ref && lockedRefs.find( cmp ) == lockedRefs.end() )
1096 mrvCandidates.push_back( { cmp, i, {}, {} } );
1099 static const size_t MRV_PARALLEL_THRESHOLD = 4;
1101 PROF_TIMER timerMrv;
1103 if( mrvCandidates.size() >= MRV_PARALLEL_THRESHOLD )
1106 std::vector<std::future<void>> futures;
1107 futures.reserve( mrvCandidates.size() );
1109 const std::atomic<bool>* cancelled = aParams.m_cancelled;
1111 for( MRV_CANDIDATE& c : mrvCandidates )
1113 futures.emplace_back(
tp.submit_task(
1114 [&c, aTarget, ¤t, &structuralMatches, &structuralReasons, cancelled]()
1116 c.m_matches = aTarget->findMatchingComponents(
1117 c.m_cmp, structuralMatches[c.m_index],
1118 structuralReasons[c.m_index], current, c.m_reasons,
1123 for(
auto& f : futures )
1128 for( MRV_CANDIDATE& c : mrvCandidates )
1130 c.m_matches = aTarget->findMatchingComponents(
1131 c.m_cmp, structuralMatches[c.m_index],
1132 structuralReasons[c.m_index], current, c.m_reasons,
1133 aParams.m_cancelled );
1138 double mrvMs = timerMrv.
msecs();
1139 mrvTotalMs += mrvMs;
1142 wxT(
"iter %d: MRV scan %0.3f ms, %d unlocked candidates" ),
1143 nloops, mrvMs, (
int) mrvCandidates.size() );
1145 if( aParams.m_cancelled && aParams.m_cancelled->load( std::memory_order_relaxed ) )
1148 int minMatches = std::numeric_limits<int>::max();
1151 int bestRefIndex = 0;
1152 int altRefIndex = 0;
1153 std::vector<COMPONENT*> bestMatches;
1155 for( MRV_CANDIDATE& c : mrvCandidates )
1157 int nMatches =
static_cast<int>( c.m_matches.size() );
1161 bestNextRef = c.m_cmp;
1162 bestRefIndex =
static_cast<int>( c.m_index );
1163 bestMatches = std::move( c.m_matches );
1166 else if( nMatches == 0 )
1168 altNextRef = c.m_cmp;
1169 altRefIndex =
static_cast<int>( c.m_index );
1171 if( aMismatchReasons.empty() && !c.m_reasons.empty() )
1172 aMismatchReasons = c.m_reasons;
1174 else if( nMatches < minMatches )
1176 minMatches = nMatches;
1177 bestNextRef = c.m_cmp;
1178 bestRefIndex =
static_cast<int>( c.m_index );
1179 bestMatches = std::move( c.m_matches );
1188 wxT(
"iter %d: MRV picked '%s' (%d matches, best of %d)" ),
1190 (
int) bestMatches.size(), (
int) mrvCandidates.size() );
1192 next.m_ref = bestNextRef;
1193 next.m_refIndex = bestRefIndex;
1194 next.m_matches = std::move( bestMatches );
1195 next.m_currentMatch = 0;
1200 wxT(
"iter %d: MRV dead end, alt='%s'" ),
1201 nloops, altNextRef ? altNextRef->
m_reference : wxString(
"(none)" ) );
1203 next.m_ref = altNextRef;
1204 next.m_refIndex = altRefIndex;
1205 next.m_currentMatch = -1;
1208 stack.push_back(
next );
1213 wxT(
"Isomorphism: %s, %d iterations, %d backtracks, "
1214 "MRV total %0.1f ms (%d candidates)" ),
1215 timerTotal.to_string(), nloops, backtrackCount, mrvTotalMs,
1216 (
int) m_components.size() );