KiCad PCB EDA Suite
Loading...
Searching...
No Matches
topo_match.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <cstdio>
21#include <cstdlib>
22#include <cmath>
23#include <string>
24#include <vector>
25#include <algorithm>
26#include <cassert>
27#include <future>
28#include <map>
29#include <set>
30#include <unordered_map>
31#include <unordered_set>
32
33#include <thread_pool.h>
34#include <cctype>
35
36#include <core/profile.h>
37#include <pad.h>
38#include <footprint.h>
39#include <refdes_utils.h>
40#include <board.h>
41#include <wx/string.h>
42#include <wx/log.h>
43
44#include "topo_match.h"
45
46
47static const wxString traceTopoMatch = wxT( "TOPO_MATCH" );
48static const wxString traceTopoMatchDetail = wxT( "TOPO_MATCH_DETAIL" );
49
50
51namespace TMATCH
52{
53
54bool PIN::IsIsomorphic( const PIN& b, TOPOLOGY_MISMATCH_REASON& aReason ) const
55{
56 if( m_conns.size() != b.m_conns.size() )
57 {
58 wxLogTrace( traceTopoMatch,
59 wxT( "[conns mismatch n1 %d n2 %d c-ref %d c-other %d thispin %s-%s "
60 "otherpin %s-%s" ),
62 b.m_netcode,
63 (int) m_conns.size(),
64 (int) b.m_conns.size(),
65 m_parent->m_reference,
66 m_ref,
68 b.m_ref );
69
70 aReason.m_reference = m_parent->GetParent()->GetReferenceAsString();
72 aReason.m_reason = wxString::Format(
73 _( "Pad %s of %s connects to %lu pads, but candidate pad %s of %s connects to %lu." ), m_ref,
74 aReason.m_reference, static_cast<unsigned long>( m_conns.size() ), b.m_ref, aReason.m_candidate,
75 static_cast<unsigned long>( b.m_conns.size() ) );
76
77 for( auto c : m_conns )
78 {
79 wxLogTrace( traceTopoMatch, wxT( "%s-%s " ), c->m_parent->m_reference, c->m_ref );
80 }
81
82 wxLogTrace( traceTopoMatch, wxT( "||" ) );
83
84 for( auto c : b.m_conns )
85 {
86 wxLogTrace( traceTopoMatch, wxT( "%s-%s " ), c->m_parent->m_reference, c->m_ref );
87 }
88
89
90 wxLogTrace( traceTopoMatch, wxT( "] " ) );
91 return false;
92 }
93
94 if( m_conns.empty() )
95 {
96 wxLogTrace( traceTopoMatch, wxT( "[conns empty]" ) );
97 return true;
98 }
99
100 std::vector<bool> matches( m_conns.size() );
101
102 for( size_t i = 0; i < m_conns.size(); i++ )
103 matches[i] = false;
104
105 size_t nref = 0;
106
107 for( auto& cref : m_conns )
108 {
109 for( size_t i = 0; i < m_conns.size(); i++ )
110 {
111 if( b.m_conns[i]->IsTopologicallySimilar( *cref ) )
112 {
113 matches[nref] = true;
114 break;
115 }
116 }
117
118 nref++;
119 }
120
121 for( size_t i = 0; i < m_conns.size(); i++ )
122 {
123 if( !matches[i] )
124 {
125 aReason.m_reference = m_parent->GetParent()->GetReferenceAsString();
127 aReason.m_reason = wxString::Format(
128 _( "Pad %s of %s cannot match candidate pad %s of %s due to differing connectivity." ), m_ref,
129 aReason.m_reference, b.m_ref, aReason.m_candidate );
130
131 return false;
132 }
133 }
134
135 return true;
136}
137
138
139std::unordered_map<int, int> buildBaseNetMapping( const BACKTRACK_STAGE& aMatches )
140{
141 std::unordered_map<int, int> mapping;
142 mapping.reserve( aMatches.GetMatchingComponentPairs().size() * 4 );
143
144 for( const auto& [tgtCmp, refCmp] : aMatches.GetMatchingComponentPairs() )
145 {
146 auto& refPins = refCmp->Pins();
147 auto& tgtPins = tgtCmp->Pins();
148
149 for( size_t i = 0; i < refPins.size() && i < tgtPins.size(); i++ )
150 mapping[refPins[i]->GetNetCode()] = tgtPins[i]->GetNetCode();
151 }
152
153 return mapping;
154}
155
156
157bool checkCandidateNetConsistency( const std::unordered_map<int, int>& aBaseMapping,
158 COMPONENT* aRef, COMPONENT* aTgt,
160 const std::unordered_set<int>& aExternalNets )
161{
162 if( aRef->Pins().size() != aTgt->Pins().size() )
163 {
164 aReason.m_reference = aRef->GetParent()->GetReferenceAsString();
165 aReason.m_candidate = aTgt->GetParent()->GetReferenceAsString();
166 aReason.m_reason =
167 wxString::Format( _( "Component %s expects %lu matching pads but candidate %s provides %lu." ),
168 aReason.m_reference, static_cast<unsigned long>( aRef->Pins().size() ),
169 aReason.m_candidate, static_cast<unsigned long>( aTgt->Pins().size() ) );
170 return false;
171 }
172
173 // Track net mappings introduced by this candidate's pins that aren't yet in the
174 // base mapping. Two pins sharing the same ref net must map to the same target net.
175 std::unordered_map<int, int> candidateAdditions;
176
177 for( size_t i = 0; i < aRef->Pins().size(); i++ )
178 {
179 int refNet = aRef->Pins()[i]->GetNetCode();
180 int tgtNet = aTgt->Pins()[i]->GetNetCode();
181
182 // Pads on external (global/power) nets are shared with the outside world and may be
183 // tied to different global nets in different channels (e.g. an address-select pin tied
184 // to +3V3 in one channel and GND in another). Skip them in the net-consistency check
185 // so such legitimate differences do not cause false topology-mismatch errors.
186 if( aExternalNets.count( refNet ) || aExternalNets.count( tgtNet ) )
187 continue;
188
189 auto baseIt = aBaseMapping.find( refNet );
190
191 if( baseIt != aBaseMapping.end() )
192 {
193 if( baseIt->second != tgtNet )
194 {
195 wxLogTrace( traceTopoMatch, wxT( "nets inconsistent\n" ) );
196
197 aReason.m_reference = aRef->GetParent()->GetReferenceAsString();
198 aReason.m_candidate = aTgt->GetParent()->GetReferenceAsString();
199
200 wxString refNetName;
201 wxString tgtNetName;
202
203 if( const BOARD* board = aRef->GetParent()->GetBoard() )
204 {
205 if( const NETINFO_ITEM* net = board->FindNet( refNet ) )
206 refNetName = net->GetNetname();
207 }
208
209 if( const BOARD* board = aTgt->GetParent()->GetBoard() )
210 {
211 if( const NETINFO_ITEM* net = board->FindNet( tgtNet ) )
212 tgtNetName = net->GetNetname();
213 }
214
215 if( refNetName.IsEmpty() )
216 refNetName = wxString::Format( _( "net %d" ), refNet );
217
218 if( tgtNetName.IsEmpty() )
219 tgtNetName = wxString::Format( _( "net %d" ), tgtNet );
220
221 aReason.m_reason = wxString::Format(
222 _( "Pad %s of %s is on net %s but its match in candidate %s is on net %s." ),
223 aRef->Pins()[i]->GetReference(), aReason.m_reference, refNetName,
224 aReason.m_candidate, tgtNetName );
225
226 return false;
227 }
228
229 continue;
230 }
231
232 auto localIt = candidateAdditions.find( refNet );
233
234 if( localIt != candidateAdditions.end() )
235 {
236 if( localIt->second != tgtNet )
237 {
238 wxLogTrace( traceTopoMatch, wxT( "nets inconsistent (candidate internal)\n" ) );
239
240 aReason.m_reference = aRef->GetParent()->GetReferenceAsString();
241 aReason.m_candidate = aTgt->GetParent()->GetReferenceAsString();
242 aReason.m_reason = wxString::Format(
243 _( "Pad %s of %s has inconsistent net mapping in candidate %s." ),
244 aRef->Pins()[i]->GetReference(), aReason.m_reference,
245 aReason.m_candidate );
246
247 return false;
248 }
249
250 continue;
251 }
252
253 candidateAdditions[refNet] = tgtNet;
254 }
255
256 return true;
257}
258
259
260std::vector<COMPONENT*>
262 const std::vector<COMPONENT*>& aStructuralMatches,
263 const TOPOLOGY_MISMATCH_REASON& aStructuralReason,
264 const BACKTRACK_STAGE& partialMatches,
265 std::vector<TOPOLOGY_MISMATCH_REASON>& aMismatchReasons,
266 const std::atomic<bool>* aCancelled )
267{
268 if( aCancelled && aCancelled->load( std::memory_order_relaxed ) )
269 return {};
270
271 PROF_TIMER timerFmc;
272
273 aMismatchReasons.clear();
274 std::vector<COMPONENT*> matches;
275 int candidatesChecked = 0;
276
277 // Build the net consistency map from locked pairs once for this entire evaluation
278 // pass, rather than rebuilding it from scratch for every candidate.
279 std::unordered_map<int, int> baseNetMapping = buildBaseNetMapping( partialMatches );
280
281 double netCheckMs = 0.0;
282
283 for( COMPONENT* cmpTarget : aStructuralMatches )
284 {
285 if( partialMatches.m_locked.find( cmpTarget ) != partialMatches.m_locked.end() )
286 continue;
287
288 candidatesChecked++;
289
290 wxLogTrace( traceTopoMatch, wxT( "Check '%s'/'%s' " ), aRef->m_reference,
291 cmpTarget->m_reference );
292
293 TOPOLOGY_MISMATCH_REASON localReason;
294 localReason.m_reference = aRef->GetParent()->GetReferenceAsString();
295 localReason.m_candidate = cmpTarget->GetParent()->GetReferenceAsString();
296
297 PROF_TIMER timerNet;
298 bool netResult = checkCandidateNetConsistency( baseNetMapping, aRef, cmpTarget, localReason,
300 timerNet.Stop();
301 netCheckMs += timerNet.msecs();
302
303 if( netResult )
304 {
305 wxLogTrace( traceTopoMatch, wxT( "match!\n" ) );
306 matches.push_back( cmpTarget );
307 }
308 else
309 {
310 wxLogTrace( traceTopoMatch, wxT( "Reject [net topo mismatch]\n" ) );
311 aMismatchReasons.push_back( localReason );
312 }
313 }
314
315 PROF_TIMER timerScore;
316
317 std::unordered_map<COMPONENT*, double> simScores;
318 simScores.reserve( matches.size() );
319
320 for( COMPONENT* match : matches )
321 {
322 int n = 0;
323
324 for( size_t i = 0; i < aRef->m_pins.size(); i++ )
325 {
326 if( aRef->m_pins[i]->GetNetCode() == match->m_pins[i]->GetNetCode() )
327 n++;
328 }
329
330 simScores[match] = static_cast<double>( n ) / static_cast<double>( aRef->m_pins.size() );
331 }
332
333 std::sort( matches.begin(), matches.end(),
334 [&]( COMPONENT* a, COMPONENT* b ) -> bool
335 {
336 double simA = simScores[a];
337 double simB = simScores[b];
338
339 if( simA != simB )
340 return simA > simB;
341
342 return a->GetParent()->GetReferenceAsString()
343 < b->GetParent()->GetReferenceAsString();
344 } );
345
346 timerScore.Stop();
347
348 if( matches.empty() && aMismatchReasons.empty() )
349 {
350 // No net-consistency reasons were recorded above, which means there were no structural
351 // candidates to test in the first place. Surface the structural reason captured during
352 // precomputation so the user sees the actual connectivity difference (e.g. a pad that
353 // connects to a different number of pads because of an external loop between channels)
354 // rather than a generic "no compatible component" message.
355 if( !aStructuralReason.m_reason.IsEmpty() )
356 {
357 aMismatchReasons.push_back( aStructuralReason );
358 }
359 else
360 {
362 reason.m_reference = aRef->GetParent()->GetReferenceAsString();
363 reason.m_reason = _( "No compatible component found in the target area." );
364 aMismatchReasons.push_back( reason );
365 }
366 }
367
368 timerFmc.Stop();
369
370 wxLogTrace( traceTopoMatchDetail,
371 wxT( " findMatch '%s' (%d pins): %s total, checked %d/%d structural, "
372 "netCheck %0.3f ms, score %0.3f ms, %d matches" ),
373 aRef->m_reference, aRef->GetPinCount(), timerFmc.to_string(),
374 candidatesChecked, (int) aStructuralMatches.size(),
375 netCheckMs, timerScore.msecs(),
376 (int) matches.size() );
377
378 return matches;
379}
380
381
382void CONNECTION_GRAPH::breakTie( COMPONENT* aRef, std::vector<COMPONENT*>& aMatches ) const
383{
384 if( aMatches.size() <= 1 )
385 return;
386
387 wxString candidateRefs;
388
389 for( size_t i = 0; i < aMatches.size(); i++ )
390 {
391 if( i > 0 )
392 candidateRefs += wxT( ", " );
393
394 candidateRefs += aMatches[i]->GetParent()->GetReferenceAsString();
395 }
396
397 wxLogTrace( traceTopoMatch, wxT( "Topology tie for %s: %s" ),
398 aRef->GetParent()->GetReferenceAsString(), candidateRefs );
399
400 if( breakTieBySymbolPath( aRef, aMatches ) )
401 {
402 wxLogTrace( traceTopoMatchDetail, wxT( "Broke tie with symbol path match for %s" ),
403 aRef->GetParent()->GetReferenceAsString() );
404 }
405 else if( breakTieByValue( aRef, aMatches ) )
406 {
407 wxLogTrace( traceTopoMatchDetail, wxT( "Broke tie with footprint value match for %s" ),
408 aRef->GetParent()->GetReferenceAsString() );
409 }
410 // TODO: other tie breakers can be added, e.g. based on position or reference designators,
411 // just waiting for actual user test cases
412 else
413 {
414 wxLogTrace( traceTopoMatchDetail, wxT( "No tie breakers worked for %s, leaving match order alone." ),
415 aRef->GetParent()->GetReferenceAsString() );
416 }
417}
418
419
420bool CONNECTION_GRAPH::breakTieByValue( COMPONENT* aRef, std::vector<COMPONENT*>& aMatches ) const
421{
422 FOOTPRINT* refFp = aRef ? aRef->GetParent() : nullptr;
423
424 if( !refFp )
425 return false;
426
427 const wxString refValue = refFp->GetValue();
428
429 if( refValue.IsEmpty() )
430 return false;
431
432 int valueHitCount = 0;
433 int uniqueMatchIdx = -1;
434
435 for( size_t i = 0; i < aMatches.size(); i++ )
436 {
437 if( aMatches[i]->GetParent()->GetValue() == refValue )
438 {
439 if( uniqueMatchIdx < 0 )
440 uniqueMatchIdx = static_cast<int>( i );
441
442 valueHitCount++;
443 }
444 }
445
446 // Only one candidate may share the value for it to disambiguate the tie. Several same-value
447 // candidates (e.g. a bank of identical decoupling caps) tell us nothing.
448 if( valueHitCount == 1 )
449 {
450 std::rotate( aMatches.begin(), aMatches.begin() + uniqueMatchIdx, aMatches.begin() + uniqueMatchIdx + 1 );
451 return true;
452 }
453
454 return false;
455}
456
457
458size_t CommonPathTailLength( const KIID_PATH& aA, const KIID_PATH& aB )
459{
460 // A nil symbol UUID is no instance link, so two of them must not count as a match
461 if( aA.empty() || aB.empty() || aA.back() == niluuid || aB.back() == niluuid )
462 return 0;
463
464 size_t n = 0;
465
466 while( n < aA.size() && n < aB.size() && aA[aA.size() - 1 - n] == aB[aB.size() - 1 - n] )
467 n++;
468
469 return n;
470}
471
472
473bool CONNECTION_GRAPH::breakTieBySymbolPath( COMPONENT* aRef, std::vector<COMPONENT*>& aMatches ) const
474{
475 FOOTPRINT* refFp = aRef ? aRef->GetParent() : nullptr;
476
477 if( !refFp || refFp->GetPath().empty() )
478 {
479 wxLogTrace( traceTopoMatchDetail, wxT( "Tie symbol path unavailable for %s" ),
480 refFp ? refFp->GetReferenceAsString() : wxString( wxT( "<null>" ) ) );
481 return false;
482 }
483
484 size_t bestLength = 0;
485 int bestCount = 0;
486 int bestIdx = -1;
487
488 for( size_t i = 0; i < aMatches.size(); i++ )
489 {
490 size_t length = CommonPathTailLength( refFp->GetPath(), aMatches[i]->GetParent()->GetPath() );
491
492 wxLogTrace( traceTopoMatchDetail, wxT( "Tie candidate %s shares %zu path elements with %s" ),
493 aMatches[i]->GetParent()->GetReferenceAsString(), length, refFp->GetReferenceAsString() );
494
495 if( length > bestLength )
496 {
497 bestLength = length;
498 bestCount = 1;
499 bestIdx = static_cast<int>( i );
500 }
501 else if( length == bestLength && length > 0 )
502 {
503 bestCount++;
504 }
505 }
506
507 // Copy and pasted footprints share a whole path, so only a unique longest tail is usable
508 if( bestLength == 0 || bestCount != 1 )
509 {
510 wxLogTrace( traceTopoMatchDetail, wxT( "No unique symbol path match for %s (tail %zu, hits %d)" ),
511 refFp->GetReferenceAsString(), bestLength, bestCount );
512 return false;
513 }
514
515 std::rotate( aMatches.begin(), aMatches.begin() + bestIdx, aMatches.begin() + bestIdx + 1 );
516
517 wxLogTrace( traceTopoMatchDetail, wxT( "Applied symbol path tie-break for %s: selected %s" ),
518 refFp->GetReferenceAsString(), aMatches.front()->GetParent()->GetReferenceAsString() );
519
520 return true;
521}
522
523
525{
526 std::sort( m_pins.begin(), m_pins.end(),
527 []( PIN* a, PIN* b )
528 {
529 return a->GetReference() < b->GetReference();
530 } );
531}
532
533
535{
536 std::sort( m_components.begin(), m_components.end(),
537 []( COMPONENT* a, COMPONENT* b )
538 {
539 if( a->GetPinCount() != b->GetPinCount() )
540 return a->GetPinCount() > b->GetPinCount();
541
542 return a->GetParent()->GetReferenceAsString() < b->GetParent()->GetReferenceAsString();
543 } );
544}
545
546
547void CONNECTION_GRAPH::BuildConnectivity( const std::unordered_set<int>& aExternalNets )
548{
549 m_externalNets = aExternalNets;
550
551 std::map<int, std::vector<PIN*>> nets;
552
554
555 for( auto c : m_components )
556 {
557 c->sortPinsByName();
558
559 for( auto p : c->Pins() )
560 {
561 if( p->GetNetCode() > 0 )
562 nets[p->GetNetCode()].push_back( p );
563 }
564 }
565
566 for( auto& [netcode, pins] : nets )
567 {
568 // Skip nets that extend beyond this channel's footprint set. Global power nets
569 // (GND, VCC, etc.) are shared across channels and can create spurious intra-channel
570 // connections that cause false topology mismatches when hierarchical pins are tied
571 // directly to those nets.
572 if( aExternalNets.count( netcode ) )
573 continue;
574
575 wxLogTrace( traceTopoMatch, wxT( "net %d: %d connections\n" ), netcode,
576 (int) pins.size() );
577
578 for( PIN* p : pins )
579 {
580 p->m_conns.reserve( pins.size() - 1 );
581
582 for( PIN* p2 : pins )
583 {
584 if( p != p2 )
585 p->m_conns.push_back( p2 );
586 }
587 }
588 }
589
590/* for( auto c : m_components )
591 for( auto p : c->Pins() )
592 {
593 printf("pin %s: \n", p->m_ref.c_str().AsChar() );
594
595 for( auto c : p->m_conns )
596 printf( "%s ", c->m_ref.c_str().AsChar() );
597 printf("\n");
598 }
599 */
600}
601
602
604{
605 std::map<wxString, int> counts;
606 std::map<wxString, wxString> footprintOf;
607 std::map<wxString, std::vector<wxString>> partsUsing;
608 std::set<wxString> repeatedRefs;
609};
610
611
612static SIDE_INVENTORY takeInventory( const std::vector<COMPONENT*>& aComponents )
613{
614 SIDE_INVENTORY inventory;
615
616 for( COMPONENT* cmp : aComponents )
617 {
618 const wxString reference = cmp->GetParent()->GetReferenceAsString();
619 wxString footprint = cmp->GetParent()->GetFPIDAsString();
620
621 if( footprint.IsEmpty() )
622 footprint = _( "(no library ID)" );
623
624 inventory.counts[footprint]++;
625 inventory.partsUsing[footprint].push_back( reference );
626
627 if( !inventory.footprintOf.emplace( reference, footprint ).second )
628 inventory.repeatedRefs.insert( reference );
629 }
630
631 for( auto& [footprint, parts] : inventory.partsUsing )
632 std::sort( parts.begin(), parts.end() );
633
634 return inventory;
635}
636
637
638static bool sameFootprintInventory( const std::vector<COMPONENT*>& aRefComponents,
639 const std::vector<COMPONENT*>& aTargetComponents,
640 std::vector<TOPOLOGY_MISMATCH_REASON>& aMismatchReasons )
641{
642 const SIDE_INVENTORY ref = takeInventory( aRefComponents );
643 const SIDE_INVENTORY target = takeInventory( aTargetComponents );
644
645 if( ref.counts == target.counts )
646 return true;
647
648 // Same name on both sides but a different footprint. That is the part someone changed.
649 for( const auto& [reference, footprint] : ref.footprintOf )
650 {
651 auto other = target.footprintOf.find( reference );
652
653 if( other == target.footprintOf.end() || other->second == footprint || ref.repeatedRefs.count( reference )
654 || target.repeatedRefs.count( reference ) )
655 continue;
656
658 reason.m_reference = reference;
659 reason.m_candidate = reference;
660 reason.m_reason = wxString::Format( _( "%s uses footprint '%s' in the reference area but "
661 "'%s' in the target area." ),
662 reference, footprint, other->second );
663 aMismatchReasons.push_back( reason );
664 }
665
666 // Lines that name parts go before bare counts. The caller shows the first line as the headline.
667 std::vector<TOPOLOGY_MISMATCH_REASON> namedParts;
668 std::vector<TOPOLOGY_MISMATCH_REASON> countsOnly;
669 std::vector<wxString> refOnly;
670 std::vector<wxString> targetOnly;
671
672 const size_t maxNamed = 12;
673
674 auto joinParts = []( const std::vector<wxString>& aParts )
675 {
676 wxString joined;
677
678 for( const wxString& part : aParts )
679 {
680 if( !joined.IsEmpty() )
681 joined += wxT( ", " );
682
683 joined += part;
684 }
685
686 return joined;
687 };
688
689 for( const auto& [footprint, count] : ref.counts )
690 {
691 if( !target.counts.count( footprint ) )
692 refOnly.push_back( footprint );
693 }
694
695 for( const auto& [footprint, count] : target.counts )
696 {
697 if( !ref.counts.count( footprint ) )
698 targetOnly.push_back( footprint );
699 }
700
701 // One footprint swapped for one other. Put both names in one line so a small spelling
702 // difference is easy to see.
703 const bool pairedSwap = refOnly.size() == 1 && targetOnly.size() == 1
704 && ref.partsUsing.at( refOnly.front() ).size() <= maxNamed
705 && target.partsUsing.at( targetOnly.front() ).size() <= maxNamed;
706
707 if( pairedSwap )
708 {
710 reason.m_reason =
711 wxString::Format( _( "Footprint '%s' in the reference area (%s) appears as "
712 "'%s' in the target area (%s)." ),
713 refOnly.front(), joinParts( ref.partsUsing.at( refOnly.front() ) ),
714 targetOnly.front(), joinParts( target.partsUsing.at( targetOnly.front() ) ) );
715 namedParts.push_back( reason );
716 }
717
718 auto reportUsage = [&]( const wxString& aFootprint, int aRefUses, int aTargetUses )
719 {
720 if( aRefUses == aTargetUses )
721 return;
722
723 if( pairedSwap && ( aFootprint == refOnly.front() || aFootprint == targetOnly.front() ) )
724 return;
725
726 // Only one side uses this footprint, so name the parts that do. Their numbering does not
727 // have to match the other side. A long list is left as counts.
728 const std::vector<wxString>& parts =
729 aRefUses == 0 ? target.partsUsing.at( aFootprint ) : ref.partsUsing.at( aFootprint );
731
732 if( ( aRefUses == 0 || aTargetUses == 0 ) && parts.size() <= maxNamed )
733 {
734 const wxString named = joinParts( parts );
735
736 if( aRefUses == 0 )
737 {
738 reason.m_reason = wxString::Format( _( "Footprint '%s' is used by %s in the target "
739 "area but by nothing in the reference area." ),
740 aFootprint, named );
741 }
742 else
743 {
744 reason.m_reason = wxString::Format( _( "Footprint '%s' is used by %s in the "
745 "reference area but by nothing in the "
746 "target area." ),
747 aFootprint, named );
748 }
749
750 namedParts.push_back( reason );
751 }
752 else
753 {
754 reason.m_reason = wxString::Format( _( "Footprint '%s': %d in the reference area, %d in "
755 "the target area." ),
756 aFootprint, aRefUses, aTargetUses );
757 countsOnly.push_back( reason );
758 }
759 };
760
761 for( const auto& [footprint, refUses] : ref.counts )
762 {
763 auto used = target.counts.find( footprint );
764
765 reportUsage( footprint, refUses, used == target.counts.end() ? 0 : used->second );
766 }
767
768 for( const auto& [footprint, targetUses] : target.counts )
769 {
770 if( !ref.counts.count( footprint ) )
771 reportUsage( footprint, 0, targetUses );
772 }
773
774 aMismatchReasons.insert( aMismatchReasons.end(), namedParts.begin(), namedParts.end() );
775 aMismatchReasons.insert( aMismatchReasons.end(), countsOnly.begin(), countsOnly.end() );
776
777 return false;
778}
779
780
782 std::vector<TOPOLOGY_MISMATCH_REASON>& aMismatchReasons,
783 const ISOMORPHISM_PARAMS& aParams )
784{
785 std::vector<BACKTRACK_STAGE> stack;
787
788 aMismatchReasons.clear();
789
790 if( aParams.m_totalComponents )
791 aParams.m_totalComponents->store( (int) m_components.size(), std::memory_order_relaxed );
792
793 PROF_TIMER timerTotal;
794 int backtrackCount = 0;
795 double mrvTotalMs = 0.0;
796
797 std::vector<TOPOLOGY_MISMATCH_REASON> localReasons;
798
799 if( m_components.empty()|| aTarget->m_components.empty() )
800 {
802
803 if( m_components.empty() && aTarget->m_components.empty() )
804 reason.m_reason = _( "Neither area has any footprints to match." );
805 else if( m_components.empty() )
806 reason.m_reason = _( "The reference area has no footprints to match." );
807 else
808 reason.m_reason = _( "The target area has no footprints to match." );
809
810 aMismatchReasons.push_back( reason );
811 return false;
812 }
813
814 if( m_components.size() != aTarget->m_components.size() )
815 {
817 reason.m_reason = wxString::Format( _( "The reference area has %d components and the "
818 "target area has %d." ),
819 (int) m_components.size(), (int) aTarget->m_components.size() );
820 aMismatchReasons.push_back( reason );
821 return false;
822 }
823
824 if( !sameFootprintInventory( m_components, aTarget->m_components, aMismatchReasons ) )
825 return false;
826
827 // Structural compatibility (MatchesWith) depends only on pin count, footprint ID, and
828 // pin connection topology -- all immutable graph properties. Precompute it once per
829 // source component so the backtracking loop never repeats these comparisons.
830 size_t numRef = m_components.size();
831 std::vector<std::vector<COMPONENT*>> structuralMatches( numRef );
832
833 // When a source component has no structural match at all, keep a representative reason so we
834 // can explain the actual connectivity difference rather than a generic "no compatible
835 // component" message.
836 std::vector<TOPOLOGY_MISMATCH_REASON> structuralReasons( numRef );
837
838 PROF_TIMER timerPrecompute;
839 {
841 std::vector<std::future<void>> futures;
842 futures.reserve( numRef );
843
844 const std::atomic<bool>* cancelled = aParams.m_cancelled;
845
846 for( size_t i = 0; i < numRef; i++ )
847 {
848 futures.emplace_back( tp.submit_task(
849 [this, i, aTarget, &structuralMatches, &structuralReasons, cancelled]()
850 {
851 if( cancelled && cancelled->load( std::memory_order_relaxed ) )
852 return;
853
854 COMPONENT* ref = m_components[i];
855 TOPOLOGY_MISMATCH_REASON reason;
856 TOPOLOGY_MISMATCH_REASON bestReason;
857 int bestRank = -1;
858
859 for( COMPONENT* tgt : aTarget->m_components )
860 {
861 if( ref->MatchesWith( tgt, reason ) )
862 {
863 structuralMatches[i].push_back( tgt );
864 continue;
865 }
866
867 // Report against the counterpart the user expects to match.
868 int rank = 0;
869
870 if( tgt->m_reference == ref->m_reference )
871 rank = 3;
872 else if( ref->IsSameKind( *tgt ) )
873 rank = 2;
874 else if( COMPONENT::prefixesShareCommonBase( ref->m_prefix, tgt->m_prefix ) )
875 rank = 1;
876
877 if( rank >= bestRank )
878 {
879 bestRank = rank;
880 bestReason = reason;
881 }
882 }
883
884 if( structuralMatches[i].empty() )
885 structuralReasons[i] = bestReason;
886 } ) );
887 }
888
889 for( auto& f : futures )
890 f.wait();
891 }
892 timerPrecompute.Stop();
893
894 wxLogTrace( traceTopoMatchDetail,
895 wxT( "Structural precomputation: %s (%d source x %d target)" ),
896 timerPrecompute.to_string(), (int) numRef,
897 (int) aTarget->m_components.size() );
898
899 if( aParams.m_cancelled && aParams.m_cancelled->load( std::memory_order_relaxed ) )
900 return false;
901
902 top.m_ref = m_components.front();
903 top.m_refIndex = 0;
904
905 stack.push_back( top );
906
907 int nloops = 0;
908
909 while( !stack.empty() )
910 {
911 if( aParams.m_cancelled && aParams.m_cancelled->load( std::memory_order_relaxed ) )
912 return false;
913
914 nloops++;
915 auto& current = stack.back();
916
917 for( auto it = current.m_locked.begin(); it != current.m_locked.end(); it++ )
918 {
919 if (it->second == current.m_ref)
920 {
921 wxLogTrace( traceTopoMatch, wxT( "stk: Remove %s from locked\n" ),
922 current.m_ref->m_reference );
923 current.m_locked.erase( it );
924 break;
925 }
926 }
927
928 if( nloops >= c_ITER_LIMIT )
929 {
930 wxLogTrace( traceTopoMatch, wxT( "stk: Iter cnt exceeded\n" ) );
931
933 reason.m_reason = wxString::Format( _( "Gave up after %d attempts to pair up the two "
934 "areas. Either their connections differ, or too "
935 "many components are interchangeable to tell "
936 "apart." ),
937 c_ITER_LIMIT );
938
939 if( aMismatchReasons.empty() )
940 aMismatchReasons.push_back( reason );
941 else
942 aMismatchReasons.insert( aMismatchReasons.begin(), reason );
943
944 return false;
945 }
946
947 if( current.m_currentMatch < 0 )
948 {
949 PROF_TIMER timerInitMatch;
950
951 localReasons.clear();
952 current.m_matches = aTarget->findMatchingComponents(
953 current.m_ref, structuralMatches[current.m_refIndex],
954 structuralReasons[current.m_refIndex], current, localReasons,
955 aParams.m_cancelled );
956
957 timerInitMatch.Stop();
958
959 wxLogTrace( traceTopoMatchDetail,
960 wxT( "iter %d: initial match for '%s' (%d pins): %s, %d candidates" ),
961 nloops, current.m_ref->m_reference, current.m_ref->GetPinCount(),
962 timerInitMatch.to_string(), (int) current.m_matches.size() );
963
964 if( current.m_matches.empty() && aMismatchReasons.empty() && !localReasons.empty() )
965 aMismatchReasons = localReasons;
966
967 current.m_currentMatch = 0;
968 }
969
970 wxLogTrace( traceTopoMatch, wxT( "stk: Current '%s' stack %d cm %d/%d locked %d/%d\n" ),
971 current.m_ref->m_reference, (int) stack.size(), current.m_currentMatch,
972 (int) current.m_matches.size(), (int) current.m_locked.size(),
973 (int) m_components.size() );
974
975 if( current.m_currentMatch == 0 && current.m_matches.size() > 1 )
976 breakTie( current.m_ref, current.m_matches );
977
978 if ( current.m_matches.empty() )
979 {
980 wxLogTrace( traceTopoMatch, wxT( "stk: No matches at all, going up [level=%d]\n" ),
981 (int) stack.size() );
982 stack.pop_back();
983 backtrackCount++;
984 continue;
985 }
986
987 if( current.m_currentMatch >= 0
988 && static_cast<size_t>( current.m_currentMatch ) >= current.m_matches.size() )
989 {
990 wxLogTrace( traceTopoMatch, wxT( "stk: No more matches, going up [level=%d]\n" ),
991 (int) stack.size() );
992 stack.pop_back();
993 backtrackCount++;
994 continue;
995 }
996
997 auto& match = current.m_matches[current.m_currentMatch];
998
999 wxLogTrace( traceTopoMatch, wxT( "stk: candidate '%s', match list : ( " ),
1000 current.m_matches[current.m_currentMatch]->m_reference, current.m_refIndex );
1001
1002 for( auto m : current.m_matches )
1003 wxLogTrace( traceTopoMatch, wxT( "%s " ), m->GetParent()->GetReferenceAsString() );
1004
1005 wxLogTrace( traceTopoMatch, wxT( "\n" ) );
1006
1007 current.m_currentMatch++;
1008 current.m_locked[match] = current.m_ref;
1009
1010 if( aParams.m_matchedComponents )
1011 {
1012 aParams.m_matchedComponents->store( (int) current.m_locked.size(),
1013 std::memory_order_relaxed );
1014 }
1015
1016 if( current.m_locked.size() == m_components.size() )
1017 {
1018 current.m_nloops = nloops;
1019
1020 aResult.clear();
1021 aMismatchReasons.clear();
1022
1023 for( auto iter : current.m_locked )
1024 aResult[ iter.second->GetParent() ] = iter.first->GetParent();
1025
1026 timerTotal.Stop();
1027 wxLogTrace( traceTopoMatch,
1028 wxT( "Isomorphism: %s, %d iterations, %d backtracks, "
1029 "MRV total %0.1f ms (%d candidates)" ),
1030 timerTotal.to_string(), nloops, backtrackCount, mrvTotalMs,
1031 (int) m_components.size() );
1032
1033 return true;
1034 }
1035
1036
1037 // MRV heuristic: find the unlocked component with the fewest candidate matches.
1038 // Collect unlocked components, then evaluate them in parallel since each
1039 // findMatchingComponents call is independent (read-only on graphs and current stage).
1040 struct MRV_CANDIDATE
1041 {
1042 COMPONENT* m_cmp;
1043 size_t m_index;
1044 std::vector<COMPONENT*> m_matches;
1045 std::vector<TOPOLOGY_MISMATCH_REASON> m_reasons;
1046 };
1047
1048 std::vector<MRV_CANDIDATE> mrvCandidates;
1049
1050 // Build a set of all ref-components already locked so we can skip them in O(1)
1051 // instead of scanning m_locked values for each component.
1052 std::unordered_set<COMPONENT*> lockedRefs;
1053 lockedRefs.reserve( current.m_locked.size() );
1054
1055 for( const auto& [tgt, ref] : current.m_locked )
1056 lockedRefs.insert( ref );
1057
1058 for( size_t i = 0; i < m_components.size(); i++ )
1059 {
1060 COMPONENT* cmp = m_components[i];
1061
1062 if( cmp != current.m_ref && lockedRefs.find( cmp ) == lockedRefs.end() )
1063 mrvCandidates.push_back( { cmp, i, {}, {} } );
1064 }
1065
1066 static const size_t MRV_PARALLEL_THRESHOLD = 4;
1067
1068 PROF_TIMER timerMrv;
1069
1070 if( mrvCandidates.size() >= MRV_PARALLEL_THRESHOLD )
1071 {
1073 std::vector<std::future<void>> futures;
1074 futures.reserve( mrvCandidates.size() );
1075
1076 const std::atomic<bool>* cancelled = aParams.m_cancelled;
1077
1078 for( MRV_CANDIDATE& c : mrvCandidates )
1079 {
1080 futures.emplace_back( tp.submit_task(
1081 [&c, aTarget, &current, &structuralMatches, &structuralReasons, cancelled]()
1082 {
1083 c.m_matches = aTarget->findMatchingComponents(
1084 c.m_cmp, structuralMatches[c.m_index],
1085 structuralReasons[c.m_index], current, c.m_reasons,
1086 cancelled );
1087 } ) );
1088 }
1089
1090 for( auto& f : futures )
1091 f.wait();
1092 }
1093 else
1094 {
1095 for( MRV_CANDIDATE& c : mrvCandidates )
1096 {
1097 c.m_matches = aTarget->findMatchingComponents(
1098 c.m_cmp, structuralMatches[c.m_index],
1099 structuralReasons[c.m_index], current, c.m_reasons,
1100 aParams.m_cancelled );
1101 }
1102 }
1103
1104 timerMrv.Stop();
1105 double mrvMs = timerMrv.msecs();
1106 mrvTotalMs += mrvMs;
1107
1108 wxLogTrace( traceTopoMatchDetail,
1109 wxT( "iter %d: MRV scan %0.3f ms, %d unlocked candidates" ),
1110 nloops, mrvMs, (int) mrvCandidates.size() );
1111
1112 if( aParams.m_cancelled && aParams.m_cancelled->load( std::memory_order_relaxed ) )
1113 return false;
1114
1115 int minMatches = std::numeric_limits<int>::max();
1116 COMPONENT* altNextRef = nullptr;
1117 COMPONENT* bestNextRef = nullptr;
1118 int bestRefIndex = 0;
1119 int altRefIndex = 0;
1120 std::vector<COMPONENT*> bestMatches;
1121
1122 for( MRV_CANDIDATE& c : mrvCandidates )
1123 {
1124 int nMatches = static_cast<int>( c.m_matches.size() );
1125
1126 if( nMatches == 1 )
1127 {
1128 bestNextRef = c.m_cmp;
1129 bestRefIndex = static_cast<int>( c.m_index );
1130 bestMatches = std::move( c.m_matches );
1131 break;
1132 }
1133 else if( nMatches == 0 )
1134 {
1135 altNextRef = c.m_cmp;
1136 altRefIndex = static_cast<int>( c.m_index );
1137
1138 if( aMismatchReasons.empty() && !c.m_reasons.empty() )
1139 aMismatchReasons = c.m_reasons;
1140 }
1141 else if( nMatches < minMatches )
1142 {
1143 minMatches = nMatches;
1144 bestNextRef = c.m_cmp;
1145 bestRefIndex = static_cast<int>( c.m_index );
1146 bestMatches = std::move( c.m_matches );
1147 }
1148 }
1149
1150 BACKTRACK_STAGE next( current );
1151
1152 if( bestNextRef )
1153 {
1154 wxLogTrace( traceTopoMatchDetail,
1155 wxT( "iter %d: MRV picked '%s' (%d matches, best of %d)" ),
1156 nloops, bestNextRef->m_reference,
1157 (int) bestMatches.size(), (int) mrvCandidates.size() );
1158
1159 next.m_ref = bestNextRef;
1160 next.m_refIndex = bestRefIndex;
1161 next.m_matches = std::move( bestMatches );
1162 next.m_currentMatch = 0;
1163 }
1164 else
1165 {
1166 wxLogTrace( traceTopoMatchDetail,
1167 wxT( "iter %d: MRV dead end, alt='%s'" ),
1168 nloops, altNextRef ? altNextRef->m_reference : wxString( "(none)" ) );
1169
1170 next.m_ref = altNextRef;
1171 next.m_refIndex = altRefIndex;
1172 next.m_currentMatch = -1;
1173 }
1174
1175 stack.push_back( next );
1176 };
1177
1178 timerTotal.Stop();
1179 wxLogTrace( traceTopoMatch,
1180 wxT( "Isomorphism: %s, %d iterations, %d backtracks, "
1181 "MRV total %0.1f ms (%d candidates)" ),
1182 timerTotal.to_string(), nloops, backtrackCount, mrvTotalMs,
1183 (int) m_components.size() );
1184
1185 return false;
1186}
1187
1188
1189#if 0
1190int main()
1191{
1192 FILE * f = fopen("connectivity.dump","rb" );
1193 auto cgRef = loadCGraph(f);
1194 auto cgTarget = loadCGraph(f);
1195
1196 cgRef->buildConnectivity();
1197 cgTarget->buildConnectivity();
1198
1199 int attempts = 0;
1200 int max_loops = 0;
1201
1202 for( ;; )
1203 {
1204 cgRef->shuffle();
1205 cgTarget->shuffle();
1206
1207 const BacktrackStage latest = cgRef->matchCGraphs( cgTarget );
1208
1209 if( !latest.locked.size() )
1210 {
1211 printf("MATCH FAIL\n");
1212 break;
1213 }
1214
1215 //printf("loops: %d\n", latest.nloops );
1216 //printf("Locked: %d\n", latest.locked.size() );
1217
1218 //if (matchFound)
1219 //{
1220 // for( auto& iter : latest.locked )
1221 //{
1222 // printf("%-10s : %-10s\n", iter.first->reference.c_str(), iter.second->reference.c_str() );
1223 //}
1224
1225 //}
1226
1227 if( latest.nloops > max_loops )
1228 {
1229 max_loops = latest.nloops;
1230 }
1231
1232 if (attempts % 10000 == 0)
1233 {
1234 printf("attempts: %d maxloops: %d\n", attempts, max_loops );
1235 }
1236
1237 attempts++;
1238
1239 }
1240
1241 fclose(f);
1242
1243 return 0;
1244}
1245
1246#endif
1247
1248
1249COMPONENT::COMPONENT( const wxString& aRef, FOOTPRINT* aParentFp,
1250 std::optional<VECTOR2I> aRaOffset ) :
1251 m_raOffset( aRaOffset ),
1252 m_reference( aRef ),
1253 m_parentFootprint( aParentFp )
1254{
1256}
1257
1258
1259bool COMPONENT::isChannelSuffix( const wxString& aSuffix )
1260{
1261 if( aSuffix.IsEmpty() )
1262 return true;
1263
1264 for( wxUniChar ch : aSuffix )
1265 {
1266 if( std::isalpha( static_cast<int>( ch ) ) )
1267 return false;
1268 }
1269
1270 return true;
1271}
1272
1273
1274bool COMPONENT::prefixesShareCommonBase( const wxString& aPrefixA, const wxString& aPrefixB )
1275{
1276 if( aPrefixA == aPrefixB )
1277 return true;
1278
1279 size_t commonLen = 0;
1280 size_t minLen = std::min( aPrefixA.length(), aPrefixB.length() );
1281
1282 while( commonLen < minLen && aPrefixA[commonLen] == aPrefixB[commonLen] )
1283 commonLen++;
1284
1285 if( commonLen == 0 )
1286 return false;
1287
1288 wxString suffixA = aPrefixA.Mid( commonLen );
1289 wxString suffixB = aPrefixB.Mid( commonLen );
1290
1291 return isChannelSuffix( suffixA ) && isChannelSuffix( suffixB );
1292}
1293
1294
1295bool COMPONENT::isUnannotatedRef( const wxString& aRef )
1296{
1297 // REF** placeholder prefix ends in wildcard glyph so shares no base with annotated dest
1298 // clean-prefix placeholders like SW? already match via prefixesShareCommonBase
1299 wxString prefix = UTIL::GetRefDesPrefix( aRef );
1300
1301 return !prefix.IsEmpty() && ( prefix.Last() == '*' || prefix.Last() == '?' );
1302}
1303
1304
1305bool COMPONENT::IsSameKind( const COMPONENT& b ) const
1306{
1309 {
1310 return false;
1311 }
1312
1313 return ( m_parentFootprint->GetFPID() == b.m_parentFootprint->GetFPID() )
1314 || ( m_parentFootprint->GetFPID().empty() && b.m_parentFootprint->GetFPID().empty() );
1315}
1316
1317
1319{
1320 m_pins.push_back( aPin );
1321 aPin->SetParent( this );
1322}
1323
1324
1326{
1327 if( GetPinCount() != b->GetPinCount() )
1328 {
1330 aReason.m_candidate = b->GetParent()->GetReferenceAsString();
1331 aReason.m_reason =
1332 wxString::Format( _( "Component %s has %d pads but candidate %s has %d." ), aReason.m_reference,
1333 GetPinCount(), aReason.m_candidate, b->GetPinCount() );
1334 return false;
1335 }
1336
1337 if( !IsSameKind( *b ) )
1338 {
1340 aReason.m_candidate = b->GetParent()->GetReferenceAsString();
1341
1344 {
1345 aReason.m_reason = wxString::Format(
1346 _( "Reference prefix mismatch: %s uses prefix '%s' but candidate %s uses '%s'." ),
1347 aReason.m_reference, m_prefix, aReason.m_candidate, b->m_prefix );
1348 }
1349 else
1350 {
1351 wxString refFootprint = GetParent()->GetFPIDAsString();
1352 wxString candFootprint = b->GetParent()->GetFPIDAsString();
1353
1354 if( refFootprint.IsEmpty() )
1355 refFootprint = _( "(no library ID)" );
1356
1357 if( candFootprint.IsEmpty() )
1358 candFootprint = _( "(no library ID)" );
1359
1360 aReason.m_reason =
1361 wxString::Format( _( "Library link mismatch: %s expects '%s' but candidate %s is '%s'." ),
1362 aReason.m_reference, refFootprint, aReason.m_candidate, candFootprint );
1363 }
1364
1365 return false;
1366 }
1367
1368 for( int pin = 0; pin < b->GetPinCount(); pin++ )
1369 {
1370 // Call with the reference pin as the subject so the reason's reference/candidate match
1371 // MatchesWith's own orientation (this == reference, b == candidate).
1372 if( !m_pins[pin]->IsIsomorphic( *b->m_pins[pin], aReason ) )
1373 {
1374 if( aReason.m_reason.IsEmpty() )
1375 {
1377 aReason.m_candidate = b->GetParent()->GetReferenceAsString();
1378 aReason.m_reason = wxString::Format( _( "Component pads differ between %s and %s." ),
1379 aReason.m_reference, aReason.m_candidate );
1380 }
1381
1382 return false;
1383 }
1384
1385 }
1386
1387 return true;
1388}
1389
1390
1392{
1393 auto cmp = new COMPONENT( aFp->GetReference(), aFp );
1394
1395 for( auto pad : aFp->Pads() )
1396 {
1397 auto pin = new PIN( );
1398 pin->m_netcode = pad->GetNetCode();
1399 pin->m_ref = pad->GetNumber();
1400 cmp->AddPin( pin );
1401 }
1402
1403 m_components.push_back( cmp );
1404}
1405
1406
1407std::unique_ptr<CONNECTION_GRAPH>
1408CONNECTION_GRAPH::BuildFromFootprintSet( const std::set<FOOTPRINT*>& aFps,
1409 const std::set<FOOTPRINT*>& aOtherChannelFps,
1410 const std::unordered_set<int>& aGlobalNets )
1411{
1412 auto cgraph = std::make_unique<CONNECTION_GRAPH>();
1413 VECTOR2I ref(0, 0);
1414
1415 if( aFps.size() > 0 )
1416 ref = (*aFps.begin())->GetPosition();
1417
1418 for( auto fp : aFps )
1419 cgraph->AddFootprint( fp, fp->GetPosition() - ref );
1420
1421 std::unordered_map<int, int> localNetPadCounts;
1422
1423 for( const FOOTPRINT* fp : aFps )
1424 {
1425 for( const PAD* pad : fp->Pads() )
1426 {
1427 if( pad->GetNetCode() > 0 )
1428 localNetPadCounts[pad->GetNetCode()]++;
1429 }
1430 }
1431
1432 std::unordered_map<int, int> otherChannelNetPadCounts;
1433
1434 for( const FOOTPRINT* fp : aOtherChannelFps )
1435 {
1436 for( const PAD* pad : fp->Pads() )
1437 {
1438 if( pad->GetNetCode() > 0 )
1439 otherChannelNetPadCounts[pad->GetNetCode()]++;
1440 }
1441 }
1442
1443 // Caller-supplied global rails, plus the pairwise fallback: nets with >=2 pads in both channels.
1444 // Single-pad boundary nets stay in the comparison; excluding them asymmetrically breaks the match.
1445 std::unordered_set<int> externalNets = aGlobalNets;
1446
1447 for( const auto& [netCode, localCount] : localNetPadCounts )
1448 {
1449 auto otherIt = otherChannelNetPadCounts.find( netCode );
1450
1451 if( localCount >= 2 && otherIt != otherChannelNetPadCounts.end() && otherIt->second >= 2 )
1452 externalNets.insert( netCode );
1453 }
1454
1455 cgraph->BuildConnectivity( externalNets );
1456
1457 return cgraph;
1458}
1459
1460
1465
1466
1468{
1469 for( COMPONENT* fp : m_components )
1470 {
1471 delete fp;
1472 }
1473}
1474
1475
1477{
1478 for( PIN* p : m_pins )
1479 {
1480 delete p;
1481 }
1482}
1483
1484
1485}; // namespace TMATCH
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
COMPONENT(const LIB_ID &aFPID, const wxString &aReference, const wxString &aValue, const KIID_PATH &aPath, const std::vector< KIID > &aKiids)
std::deque< PAD * > & Pads()
Definition footprint.h:355
wxString GetFPIDAsString() const
Definition footprint.h:419
const LIB_ID & GetFPID() const
Definition footprint.h:417
wxString GetReferenceAsString() const
Definition footprint.h:802
const wxString & GetValue() const
Definition footprint.h:817
const wxString & GetReference() const
Definition footprint.h:793
const KIID_PATH & GetPath() const
Definition footprint.h:455
bool empty() const
Definition lib_id.h:189
Handle the data for a net.
Definition netinfo.h:50
Definition pad.h:61
A small class to help profiling.
Definition profile.h:46
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:86
std::string to_string()
Definition profile.h:154
double msecs(bool aSinceLast=false)
Definition profile.h:148
const std::unordered_map< COMPONENT *, COMPONENT * > & GetMatchingComponentPairs() const
Definition topo_match.h:174
std::unordered_map< COMPONENT *, COMPONENT * > m_locked
Definition topo_match.h:184
static bool prefixesShareCommonBase(const wxString &aPrefixA, const wxString &aPrefixB)
Check if two prefixes share a common starting sequence.
static bool isUnannotatedRef(const wxString &aRef)
True for un-annotated placeholder refs like REF** that match any counterpart on FPID and topology alo...
bool IsSameKind(const COMPONENT &b) const
int GetPinCount() const
Definition topo_match.h:70
COMPONENT(const wxString &aRef, FOOTPRINT *aParentFp, std::optional< VECTOR2I > aRaOffset=std::optional< VECTOR2I >())
FOOTPRINT * m_parentFootprint
Definition topo_match.h:107
std::optional< VECTOR2I > m_raOffset
Definition topo_match.h:104
bool MatchesWith(COMPONENT *b, TOPOLOGY_MISMATCH_REASON &aDetail)
void AddPin(PIN *p)
std::vector< PIN * > & Pins()
Definition topo_match.h:72
friend class PIN
Definition topo_match.h:61
wxString m_reference
Definition topo_match.h:105
static bool isChannelSuffix(const wxString &aSuffix)
Check if a suffix looks like a channel identifier.
std::vector< PIN * > m_pins
Definition topo_match.h:108
FOOTPRINT * GetParent() const
Definition topo_match.h:73
std::vector< COMPONENT * > findMatchingComponents(COMPONENT *ref, const std::vector< COMPONENT * > &aStructuralMatches, const TOPOLOGY_MISMATCH_REASON &aStructuralReason, const BACKTRACK_STAGE &partialMatches, std::vector< TOPOLOGY_MISMATCH_REASON > &aFailureDetails, const std::atomic< bool > *aCancelled=nullptr)
bool FindIsomorphism(CONNECTION_GRAPH *target, COMPONENT_MATCHES &result, std::vector< TOPOLOGY_MISMATCH_REASON > &aFailureDetails, const ISOMORPHISM_PARAMS &aParams={})
void BuildConnectivity(const std::unordered_set< int > &aExternalNets={})
static std::unique_ptr< CONNECTION_GRAPH > BuildFromFootprintSet(const std::set< FOOTPRINT * > &aFps, const std::set< FOOTPRINT * > &aOtherChannelFps={}, const std::unordered_set< int > &aGlobalNets={})
bool breakTieByValue(COMPONENT *aRef, std::vector< COMPONENT * > &aMatches) const
Break a tie by footprint value when the symbol UUID can't, e.g.
std::vector< COMPONENT * > m_components
Definition topo_match.h:255
void AddFootprint(FOOTPRINT *aFp, const VECTOR2I &aOffset)
bool breakTieBySymbolPath(COMPONENT *aRef, std::vector< COMPONENT * > &aMatches) const
The most useful tie breaker is based on symbol/sheet instances, since multiple channels in a design a...
void breakTie(COMPONENT *aRef, std::vector< COMPONENT * > &aMatches) const
Many times components are electrically/topologically identical, e.g.
std::unordered_set< int > m_externalNets
Definition topo_match.h:256
std::vector< PIN * > m_conns
Definition topo_match.h:148
void SetParent(COMPONENT *parent)
Definition topo_match.h:119
COMPONENT * m_parent
Definition topo_match.h:147
bool IsIsomorphic(const PIN &b, TOPOLOGY_MISMATCH_REASON &aDetail) const
wxString m_ref
Definition topo_match.h:145
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
KIID niluuid(0)
std::unordered_map< int, int > buildBaseNetMapping(const BACKTRACK_STAGE &aMatches)
std::map< FOOTPRINT *, FOOTPRINT * > COMPONENT_MATCHES
Definition topo_match.h:188
static SIDE_INVENTORY takeInventory(const std::vector< COMPONENT * > &aComponents)
size_t CommonPathTailLength(const KIID_PATH &aA, const KIID_PATH &aB)
Count the trailing elements two symbol instance paths share.
static bool sameFootprintInventory(const std::vector< COMPONENT * > &aRefComponents, const std::vector< COMPONENT * > &aTargetComponents, std::vector< TOPOLOGY_MISMATCH_REASON > &aMismatchReasons)
bool checkCandidateNetConsistency(const std::unordered_map< int, int > &aBaseMapping, COMPONENT *aRef, COMPONENT *aTgt, TOPOLOGY_MISMATCH_REASON &aReason, const std::unordered_set< int > &aExternalNets)
wxString GetRefDesPrefix(const wxString &aRefDes)
Get the (non-numeric) prefix from a refdes - e.g.
CITER next(CITER it)
Definition ptree.cpp:120
Collection of utility functions for component reference designators (refdes)
std::atomic< bool > * m_cancelled
Definition topo_match.h:47
std::atomic< int > * m_totalComponents
Definition topo_match.h:49
std::map< wxString, std::vector< wxString > > partsUsing
std::map< wxString, int > counts
std::map< wxString, wxString > footprintOf
std::set< wxString > repeatedRefs
KIBIS top(path, &reporter)
KIBIS_PIN * pin
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:27
static const wxString traceTopoMatchDetail
static const wxString traceTopoMatch
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708