KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_creepage_engine_incremental.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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
30
33
34#include <board.h>
37#include <netinfo.h>
38#include <pcb_track.h>
40
43
44#include <limits>
45#include <optional>
46#include <queue>
47#include <set>
48#include <vector>
49
50
52{
54
56 {
57 if( m_board )
58 {
59 m_board->SetProject( nullptr );
60 m_board = nullptr;
61 }
62 }
63
65 std::unique_ptr<BOARD> m_board;
66};
67
68
69// Large enough that every nearby creepage path is reported regardless of the board's actual rule
70static constexpr int LARGE_CONSTRAINT = 50000000; // 50 mm in IU
71
72
74{
75 KI_TEST::LoadBoard( m_settingsManager, "creepage/creepage", m_board );
76 BOOST_REQUIRE_MESSAGE( m_board, "Failed to load board creepage" );
77
78 PCB_LAYER_ID layer = F_Cu;
79
80 // Find the net pair with the smallest finite creepage using the reference solver
81 std::vector<int> netcodes;
82
83 for( const auto& [code, net] : m_board->GetNetInfo().NetsByNetcode() )
84 {
85 if( net && code > 0 )
86 netcodes.push_back( code );
87 }
88
89 CREEPAGE_ENGINE reference( *m_board );
90
91 int bestA = -1;
92 int bestB = -1;
93 double bestDist = std::numeric_limits<double>::infinity();
94
95 for( size_t i = 0; i < netcodes.size(); ++i )
96 {
97 for( size_t j = i + 1; j < netcodes.size(); ++j )
98 {
99 std::optional<CREEPAGE_RESULT> r =
100 reference.SolveNetPairWholeBoard( netcodes[i], netcodes[j], layer,
102
103 if( r && r->m_distance < bestDist )
104 {
105 bestDist = r->m_distance;
106 bestA = netcodes[i];
107 bestB = netcodes[j];
108 }
109 }
110 }
111
112 BOOST_REQUIRE_MESSAGE( bestA > 0 && bestB > 0,
113 "No finite creepage net pair found on F_Cu" );
114 BOOST_TEST_MESSAGE( wxString::Format( "Tracking nets %d vs %d, base creepage %.0f nm", bestA,
115 bestB, bestDist ) );
116
117 std::set<const BOARD_ITEM*> movingItems;
118 std::vector<BOARD_ITEM*> movable;
119
120 for( PCB_TRACK* track : m_board->Tracks() )
121 {
122 if( track && track->GetNetCode() == bestA && track->IsOnLayer( layer ) )
123 {
124 movingItems.insert( track );
125 movable.push_back( track );
126 }
127 }
128
129 BOOST_REQUIRE_MESSAGE( !movable.empty(), "Dragged net has no movable items" );
130
131 auto constraintFn = []( int, int ) -> double { return LARGE_CONSTRAINT; };
132
133 CREEPAGE_ENGINE engine( *m_board );
134 engine.BeginInteractive( layer, { bestA }, movingItems, LARGE_CONSTRAINT, constraintFn );
135
136 const std::vector<VECTOR2I> deltas = { { 0, 0 },
137 { 500000, 0 },
138 { 0, 400000 },
139 { -300000, -200000 } };
140
141 VECTOR2I applied( 0, 0 );
142
143 for( const VECTOR2I& step : deltas )
144 {
145 // Move the items on the board itself so both solvers observe identical geometry
146 for( BOARD_ITEM* item : movable )
147 item->Move( step );
148
149 applied += step;
150
151 std::vector<CREEPAGE_RESULT> updated = engine.Update( LARGE_CONSTRAINT );
152
153 std::optional<CREEPAGE_RESULT> full =
154 reference.SolveNetPairWholeBoard( bestA, bestB, layer, LARGE_CONSTRAINT );
155
156 double updateDist = std::numeric_limits<double>::infinity();
157
158 for( const CREEPAGE_RESULT& r : updated )
159 {
160 if( ( r.m_netA == bestA && r.m_netB == bestB )
161 || ( r.m_netA == bestB && r.m_netB == bestA ) )
162 {
163 updateDist = r.m_distance;
164 break;
165 }
166 }
167
168 BOOST_TEST_MESSAGE( wxString::Format( " delta (%d,%d): update=%.0f full=%s", applied.x,
169 applied.y, updateDist,
170 full ? wxString::Format( "%.0f", full->m_distance )
171 : wxString( "none" ) ) );
172
173 BOOST_REQUIRE_MESSAGE( full.has_value(), "Reference solve found no path" );
174 BOOST_REQUIRE_MESSAGE( std::isfinite( updateDist ),
175 "Incremental Update found no path for the tracked pair" );
176
177 // Same geometry through both code paths must agree to within rounding
178 BOOST_CHECK_LE( std::abs( updateDist - full->m_distance ), 2000.0 );
179 }
180
181 for( BOARD_ITEM* item : movable )
182 item->Move( -applied );
183
184 engine.EndInteractive();
185}
186
187
188// The whole-board solver must return the same shortest creepage every time it is run on identical
189// geometry. A prior CREEPAGE_GRAPH::Solve drove its priority queue with a comparator that read the
190// live distance map, so a decrease-key reinsertion corrupted the heap and the early termination
191// could settle the target on a longer path. Each solve builds a fresh graph, so the heap layout (and
192// therefore the defect) varied per call; repeating the identical solve surfaces any reappearance of
193// the heisenbug as a divergence from the optimum.
194BOOST_FIXTURE_TEST_CASE( CreepageWholeBoardSolveDeterministic, CREEPAGE_INCREMENTAL_FIXTURE )
195{
196 KI_TEST::LoadBoard( m_settingsManager, "creepage/creepage", m_board );
197 BOOST_REQUIRE_MESSAGE( m_board, "Failed to load board creepage" );
198
199 PCB_LAYER_ID layer = F_Cu;
200
201 std::vector<int> netcodes;
202
203 for( const auto& [code, net] : m_board->GetNetInfo().NetsByNetcode() )
204 {
205 if( net && code > 0 )
206 netcodes.push_back( code );
207 }
208
209 CREEPAGE_ENGINE engine( *m_board );
210
211 // Find the net pair with the smallest finite creepage; that nearest pair is the one whose
212 // shortest path the solver is most likely to mis-route
213 int bestA = -1;
214 int bestB = -1;
215 double bestDist = std::numeric_limits<double>::infinity();
216
217 for( size_t i = 0; i < netcodes.size(); ++i )
218 {
219 for( size_t j = i + 1; j < netcodes.size(); ++j )
220 {
221 std::optional<CREEPAGE_RESULT> r =
222 engine.SolveNetPairWholeBoard( netcodes[i], netcodes[j], layer, LARGE_CONSTRAINT );
223
224 if( r && r->m_distance < bestDist )
225 {
226 bestDist = r->m_distance;
227 bestA = netcodes[i];
228 bestB = netcodes[j];
229 }
230 }
231 }
232
233 BOOST_REQUIRE_MESSAGE( bestA > 0 && bestB > 0, "No finite creepage net pair found on F_Cu" );
234
235 // Nudge the nets off their initial positions; the offset geometry is the one that exposed the
236 // heap defect on the reference board
237 for( PCB_TRACK* track : m_board->Tracks() )
238 {
239 if( track && track->GetNetCode() == bestA && track->IsOnLayer( layer ) )
240 track->Move( VECTOR2I( 500000, 0 ) );
241 }
242
243 // The shortest path is a property of the geometry, so every fresh-graph solve must agree
244 double lo = std::numeric_limits<double>::infinity();
245 double hi = 0.0;
246
247 for( int trial = 0; trial < 1000; ++trial )
248 {
249 std::optional<CREEPAGE_RESULT> r =
250 engine.SolveNetPairWholeBoard( bestA, bestB, layer, LARGE_CONSTRAINT );
251
252 BOOST_REQUIRE_MESSAGE( r.has_value(), "Reference solve found no path" );
253
254 lo = std::min( lo, r->m_distance );
255 hi = std::max( hi, r->m_distance );
256 }
257
258 BOOST_TEST_MESSAGE( wxString::Format( "spread lo=%.0f hi=%.0f", lo, hi ) );
259 BOOST_REQUIRE_MESSAGE( hi - lo <= 1.0,
260 wxString::Format( "Solve is non-deterministic: lo=%.0f hi=%.0f", lo, hi ) );
261}
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
Reusable creepage solver shared by the batch DRC provider and the realtime drag overlay.
std::vector< CREEPAGE_RESULT > Update(int aNearMargin=0)
Recompute creepage for the dragged nets at the items' current board positions.
void BeginInteractive(PCB_LAYER_ID aLayer, const std::set< int > &aAffectedNets, const std::set< const BOARD_ITEM * > &aMovingItems, int aMargin, std::function< double(int, int)> aConstraintFn)
Begin an interactive drag session.
std::optional< CREEPAGE_RESULT > SolveNetPairWholeBoard(int aNetA, int aNetB, PCB_LAYER_ID aLayer, double aConstraint)
Solve a single net pair against the whole board on one layer, building a fresh graph.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_Cu
Definition layer_ids.h:60
void LoadBoard(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< BOARD > &aBoard)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
Result of a single creepage query between two nets on one layer.
static constexpr int LARGE_CONSTRAINT
BOOST_FIXTURE_TEST_CASE(CreepageIncrementalEqualsFull, CREEPAGE_INCREMENTAL_FIXTURE)
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683