KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_kimath.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
23
25
26// Code under test
27#include <trigo.h>
28#include <hash_128.h>
29#include <geometry/shape_arc.h>
30
31#include <iomanip>
32
33
34/*
35CircleCenterFrom3Points calculate the center of a circle defined by 3 points
36It is similar to CalcArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd )
37but it was needed to debug CalcArcCenter, so I keep it available for other issues in CalcArcCenter
38
39The perpendicular bisector of the segment between two points is the
40set of all points equidistant from both. So if you take the
41perpendicular bisector of (x1,y1) and (x2,y2) and the perpendicular
42bisector of the segment from (x2,y2) to (x3,y3) and find the
43intersection of those lines, that point will be the center.
44
45To find the equation of the perpendicular bisector of (x1,y1) to (x2,y2),
46you know that it passes through the midpoint of the segment:
47((x1+x2)/2,(y1+y2)/2), and if the slope of the line
48connecting (x1,y1) to (x2,y2) is m, the slope of the perpendicular
49bisector is -1/m. Work out the equations for the two lines, find
50their intersection, and bingo! You've got the coordinates of the center.
51
52An error should occur if the three points lie on a line, and you'll
53need special code to check for the case where one of the slopes is zero.
54
55see https://web.archive.org/web/20171223103555/http://mathforum.org/library/drmath/view/54323.html
56*/
57static bool Ref0CircleCenterFrom3Points( const VECTOR2D& p1, const VECTOR2D& p2, const VECTOR2D& p3,
58 VECTOR2D* aCenter )
59{
60 // Move coordinate origin to p2, to simplify calculations
61 VECTOR2D b = p1 - p2;
62 VECTOR2D d = p3 - p2;
63 double bc = ( b.x * b.x + b.y * b.y ) / 2.0;
64 double cd = ( -d.x * d.x - d.y * d.y ) / 2.0;
65 double det = -b.x * d.y + d.x * b.y;
66
67 if( fabs( det ) < 1.0e-6 ) // arbitrary limit to avoid divide by 0
68 return false;
69
70 det = 1 / det;
71 aCenter->x = ( -bc * d.y - cd * b.y ) * det;
72 aCenter->y = ( b.x * cd + d.x * bc ) * det;
73 *aCenter += p2;
74
75 return true;
76}
77
78
79// Based on https://paulbourke.net/geometry/circlesphere/
80// Uses the Y'b equation too, depending on slope values
81static const VECTOR2D Ref1CalcArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd )
82{
84 double yDelta_21 = aMid.y - aStart.y;
85 double xDelta_21 = aMid.x - aStart.x;
86 double yDelta_32 = aEnd.y - aMid.y;
87 double xDelta_32 = aEnd.x - aMid.x;
88
89 // This is a special case for aMid as the half-way point when aSlope = 0 and bSlope = inf
90 // or the other way around. In that case, the center lies in a straight line between
91 // aStart and aEnd
92 if( ( ( xDelta_21 == 0.0 ) && ( yDelta_32 == 0.0 ) )
93 || ( ( yDelta_21 == 0.0 ) && ( xDelta_32 == 0.0 ) ) )
94 {
95 center.x = ( aStart.x + aEnd.x ) / 2.0;
96 center.y = ( aStart.y + aEnd.y ) / 2.0;
97 return center;
98 }
99
100 // Prevent div=0 errors
101 /*if( xDelta_21 == 0.0 )
102 xDelta_21 = std::numeric_limits<double>::epsilon();
103
104 if( xDelta_32 == 0.0 )
105 xDelta_32 = -std::numeric_limits<double>::epsilon();*/
106
107 double aSlope = yDelta_21 / xDelta_21;
108 double bSlope = yDelta_32 / xDelta_32;
109
110 if( aSlope == bSlope )
111 {
112 if( aStart == aEnd )
113 {
114 // This is a special case for a 360 degrees arc. In this case, the center is halfway between
115 // the midpoint and either newEnd point
116 center.x = ( aStart.x + aMid.x ) / 2.0;
117 center.y = ( aStart.y + aMid.y ) / 2.0;
118 return center;
119 }
120 else
121 {
122 // If the points are colinear, the center is at infinity, so offset
123 // the slope by a minimal amount
124 // Warning: This will induce a small error in the center location
125 /*aSlope += std::numeric_limits<double>::epsilon();
126 bSlope -= std::numeric_limits<double>::epsilon();*/
127 }
128 }
129
130 center.x = ( aSlope * bSlope * ( aStart.y - aEnd.y ) + bSlope * ( aStart.x + aMid.x )
131 - aSlope * ( aMid.x + aEnd.x ) )
132 / ( 2 * ( bSlope - aSlope ) );
133
134 if( std::abs( aSlope ) > std::abs( bSlope ) )
135 {
136 center.y = ( ( ( aStart.x + aMid.x ) / 2.0 - center.x ) / aSlope
137 + ( aStart.y + aMid.y ) / 2.0 );
138 }
139 else
140 {
141 center.y =
142 ( ( ( aMid.x + aEnd.x ) / 2.0 - center.x ) / bSlope + ( aMid.y + aEnd.y ) / 2.0 );
143 }
144
145 return center;
146}
147
148
155
156
160BOOST_AUTO_TEST_SUITE( KiMath )
161
162
163BOOST_AUTO_TEST_CASE( TestCalcArcCenter3Pts )
164{
165 // Order: start, mid, end
166 std::vector<TEST_CALC_ARC_CENTER_CASE> calc_center_cases = {
167 //{ { 183000000, 89000000 }, { 185333332, 89000000 }, { 185333333, 91333333 } } // Fails currently
168 };
169
170 const double tolerance = 1.0;
171
172 // Check random points (fails currently)
173 /*for( int i = 0; i < 100; i++ )
174 {
175 TEST_CALC_ARC_CENTER_CASE cas;
176
177 cas.istart.x = rand();
178 cas.istart.y = rand();
179
180 cas.imid.x = rand();
181 cas.imid.y = rand();
182
183 cas.iend.x = rand();
184 cas.iend.y = rand();
185
186 calc_center_cases.push_back( cas );
187 }*/
188
189 for( const TEST_CALC_ARC_CENTER_CASE& entry : calc_center_cases )
190 {
191 wxString msg;
192
193 VECTOR2D start( entry.istart );
194 VECTOR2D mid( entry.imid );
195 VECTOR2D end( entry.iend );
196
197 VECTOR2D calcCenter = CalcArcCenter( start, mid, end );
198
199 double crs = ( calcCenter - start ).EuclideanNorm();
200 double crm = ( calcCenter - mid ).EuclideanNorm();
201 double cre = ( calcCenter - end ).EuclideanNorm();
202
203 double cavg = ( crs + crm + cre ) / 3.0;
204
205 if( std::abs( crs - cavg ) > tolerance || std::abs( crm - cavg ) > tolerance
206 || std::abs( cre - cavg ) > tolerance )
207 {
208 msg << "CalcArcCenter failed.";
209
210 msg << "\nstart: " << entry.istart.Format();
211 msg << "\nmid: " << entry.imid.Format();
212 msg << "\nend: " << entry.iend.Format();
213
214 {
215 msg << "\nCalculated center: " << wxString::Format( "%.15f", calcCenter.x ) << ", "
216 << wxString::Format( "%.15f", calcCenter.y );
217
218 msg << "\n Avg radius: " << wxString::Format( "%.15f", cavg );
219 msg << "\nStart radius: " << wxString::Format( "%.15f", crs );
220 msg << "\n Mid radius: " << wxString::Format( "%.15f", crm );
221 msg << "\n End radius: " << wxString::Format( "%.15f", cre );
222 msg << "\n";
223
224 // Check mid/end points using the calculated center (like SHAPE_ARC)
225 EDA_ANGLE angStart( start - calcCenter );
226 EDA_ANGLE angMid( mid - calcCenter );
227 EDA_ANGLE angEnd( end - calcCenter );
228
229 EDA_ANGLE angCenter = angEnd - angStart;
230
231 VECTOR2D newMid = start;
232 VECTOR2D newEnd = start;
233
234 RotatePoint( newMid, calcCenter, -angCenter / 2.0 );
235 RotatePoint( newEnd, calcCenter, -angCenter );
236
237 msg << "\nNew mid: " << wxString::Format( "%.15f", newMid.x ) << ", "
238 << wxString::Format( "%.15f", newMid.y );
239
240 msg << "\nNew end: " << wxString::Format( "%.15f", newEnd.x ) << ", "
241 << wxString::Format( "%.15f", newEnd.y );
242 msg << "\n";
243
244 double endsDist = ( newEnd - end ).EuclideanNorm();
245
246 msg << "\nNew end is off by " << wxString::Format( "%.15f", endsDist );
247 msg << "\n";
248 }
249
250 {
251 VECTOR2D ref0Center;
252 Ref0CircleCenterFrom3Points( start, mid, end, &ref0Center );
253
254 double r0_rs = ( ref0Center - start ).EuclideanNorm();
255 double r0_rm = ( ref0Center - mid ).EuclideanNorm();
256 double r0_rre = ( ref0Center - end ).EuclideanNorm();
257
258 double r0_ravg = ( r0_rs + r0_rm + r0_rre ) / 3.0;
259
260 msg << "\nReference0 center: " << wxString::Format( "%.15f", ref0Center.x ) << ", "
261 << wxString::Format( "%.15f", ref0Center.y );
262
263 msg << "\nRef0 Avg radius: " << wxString::Format( "%.15f", r0_ravg );
264 msg << "\nRef0 Start radius: " << wxString::Format( "%.15f", r0_rs );
265 msg << "\nRef0 Mid radius: " << wxString::Format( "%.15f", r0_rm );
266 msg << "\nRef0 End radius: " << wxString::Format( "%.15f", r0_rre );
267 msg << "\n";
268 }
269
270 {
271 VECTOR2D ref1Center = Ref1CalcArcCenter( start, mid, end );
272
273 double r1_rs = ( ref1Center - start ).EuclideanNorm();
274 double r1_rm = ( ref1Center - mid ).EuclideanNorm();
275 double r1_rre = ( ref1Center - end ).EuclideanNorm();
276
277 double r1_ravg = ( r1_rs + r1_rm + r1_rre ) / 3.0;
278
279 msg << "\nReference1 center: " << wxString::Format( "%.15f", ref1Center.x ) << ", "
280 << wxString::Format( "%.15f", ref1Center.y );
281
282 msg << "\nRef1 Avg radius: " << wxString::Format( "%.15f", r1_ravg );
283 msg << "\nRef1 Start radius: " << wxString::Format( "%.15f", r1_rs );
284 msg << "\nRef1 Mid radius: " << wxString::Format( "%.15f", r1_rm );
285 msg << "\nRef1 End radius: " << wxString::Format( "%.15f", r1_rre );
286 msg << "\n";
287 }
288
289
290 BOOST_CHECK_MESSAGE( false, msg );
291 }
292 }
293}
294
295
296BOOST_AUTO_TEST_CASE( TestInterceptsPositiveX )
297{
298 BOOST_CHECK( !InterceptsPositiveX( 10.0, 20.0 ) );
299 BOOST_CHECK( !InterceptsPositiveX( 10.0, 120.0 ) );
300 BOOST_CHECK( !InterceptsPositiveX( 10.0, 220.0 ) );
301 BOOST_CHECK( !InterceptsPositiveX( 10.0, 320.0 ) );
302 BOOST_CHECK( InterceptsPositiveX( 20.0, 10.0 ) );
303 BOOST_CHECK( InterceptsPositiveX( 345.0, 15.0 ) );
304}
305
306
307BOOST_AUTO_TEST_CASE( TestInterceptsNegativeX )
308{
309 BOOST_CHECK( !InterceptsNegativeX( 10.0, 20.0 ) );
310 BOOST_CHECK( !InterceptsNegativeX( 10.0, 120.0 ) );
311 BOOST_CHECK( InterceptsNegativeX( 10.0, 220.0 ) );
312 BOOST_CHECK( InterceptsNegativeX( 10.0, 320.0 ) );
313 BOOST_CHECK( InterceptsNegativeX( 20.0, 10.0 ) );
314 BOOST_CHECK( !InterceptsNegativeX( 345.0, 15.0 ) );
315 BOOST_CHECK( InterceptsNegativeX( 145.0, 225.0 ) );
316}
317
318
320{
321 const HASH_128 zero{};
322
323 for( size_t i = 0; i < 16; i++ )
324 BOOST_CHECK( zero.Value8[i] == 0 );
325
326 HASH_128* zero_h = new HASH_128;
327
328 for( size_t i = 0; i < 16; i++ )
329 BOOST_CHECK( zero_h->Value8[i] == 0 );
330
331 delete zero_h;
332
333 HASH_128 h;
334 h.Value64[0] = 0x00CDEF0123456789ULL;
335 h.Value64[1] = 0x56789ABCDEF01234ULL;
336
337 BOOST_CHECK( h != zero );
338
339 HASH_128 b = h;
340 BOOST_CHECK( b != zero );
341
342 BOOST_CHECK( b == h );
343 BOOST_CHECK( h == b );
344 BOOST_CHECK_EQUAL( h.ToString(), std::string( "00CDEF012345678956789ABCDEF01234" ) );
345}
346
347
348//-----------------------------------------------------------------------------
349// Test MMH3_HASH against the original MurmurHash3_x64_128 implementation
350
351#include <mmh3_hash.h>
352
353void MurmurHash3_x64_128( const void* key, const int len, const uint32_t seed, void* out );
354
355BOOST_AUTO_TEST_CASE( TestMMH3Hash )
356{
357 std::vector<std::vector<int32_t>> data;
358
359 for( size_t i = 0; i < 10; i++ )
360 {
361 std::vector<int32_t>& vec = data.emplace_back();
362
363 size_t vecSize = rand() % 128;
364
365 for( size_t j = 0; j < vecSize; j++ )
366 vec.emplace_back( rand() );
367 }
368
369 int64_t seed = 0; // Test 0 seed first
370
371 for( const std::vector<int32_t>& vec : data )
372 {
373 MMH3_HASH mmh3( seed );
374
375 for( const int32_t val : vec )
376 mmh3.add( val );
377
378 HASH_128 h128 = mmh3.digest();
379
380 HASH_128 orig128;
381 MurmurHash3_x64_128( (void*) vec.data(), vec.size() * sizeof( vec[0] ), seed,
382 (void*) orig128.Value64 );
383
384 BOOST_CHECK( h128 == orig128 );
385
386 // Generate new random seed
387 seed = rand();
388 }
389}
390
391
392//-----------------------------------------------------------------------------
393// The original MurmurHash3_x64_128 implementation
394
395
396// Microsoft Visual Studio
397#if defined( _MSC_VER )
398 #define FORCE_INLINE __forceinline
399 #include <stdlib.h>
400 #define ROTL64( x, y ) _rotl64( x, y )
401 #define BIG_CONSTANT( x ) ( x )
402 // Other compilers
403#else // defined(_MSC_VER)
404 #define FORCE_INLINE inline __attribute__( ( always_inline ) )
405 inline uint64_t rotl64( uint64_t x, int8_t r )
406 {
407 return ( x << r ) | ( x >> ( 64 - r ) );
408 }
409 #define ROTL64( x, y ) rotl64( x, y )
410 #define BIG_CONSTANT( x ) ( x##LLU )
411#endif // !defined(_MSC_VER)
412
413
414FORCE_INLINE uint64_t getblock64 ( const uint64_t * p, int i )
415{
416 return p[i];
417}
418
419FORCE_INLINE uint64_t fmix64 ( uint64_t k )
420{
421 k ^= k >> 33;
422 k *= BIG_CONSTANT(0xff51afd7ed558ccd);
423 k ^= k >> 33;
424 k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);
425 k ^= k >> 33;
426
427 return k;
428}
429
430void MurmurHash3_x64_128 ( const void * key, const int len,
431 const uint32_t seed, void * out )
432{
433 const uint8_t * data = (const uint8_t*)key;
434 const int nblocks = len / 16;
435
436 uint64_t h1 = seed;
437 uint64_t h2 = seed;
438
439 const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5);
440 const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f);
441
442 //----------
443 // body
444
445 const uint64_t * blocks = (const uint64_t *)(data);
446
447 for(int i = 0; i < nblocks; i++)
448 {
449 uint64_t k1 = getblock64(blocks,i*2+0);
450 uint64_t k2 = getblock64(blocks,i*2+1);
451
452 k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
453
454 h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729;
455
456 k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
457
458 h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5;
459 }
460
461 //----------
462 // tail
463
464 const uint8_t * tail = (const uint8_t*)(data + nblocks*16);
465
466 uint64_t k1 = 0;
467 uint64_t k2 = 0;
468
469 switch(len & 15)
470 {
471 case 15: k2 ^= ((uint64_t)tail[14]) << 48;
472 case 14: k2 ^= ((uint64_t)tail[13]) << 40;
473 case 13: k2 ^= ((uint64_t)tail[12]) << 32;
474 case 12: k2 ^= ((uint64_t)tail[11]) << 24;
475 case 11: k2 ^= ((uint64_t)tail[10]) << 16;
476 case 10: k2 ^= ((uint64_t)tail[ 9]) << 8;
477 case 9: k2 ^= ((uint64_t)tail[ 8]) << 0;
478 k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
479
480 case 8: k1 ^= ((uint64_t)tail[ 7]) << 56;
481 case 7: k1 ^= ((uint64_t)tail[ 6]) << 48;
482 case 6: k1 ^= ((uint64_t)tail[ 5]) << 40;
483 case 5: k1 ^= ((uint64_t)tail[ 4]) << 32;
484 case 4: k1 ^= ((uint64_t)tail[ 3]) << 24;
485 case 3: k1 ^= ((uint64_t)tail[ 2]) << 16;
486 case 2: k1 ^= ((uint64_t)tail[ 1]) << 8;
487 case 1: k1 ^= ((uint64_t)tail[ 0]) << 0;
488 k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
489 };
490
491 //----------
492 // finalization
493
494 h1 ^= len; h2 ^= len;
495
496 h1 += h2;
497 h2 += h1;
498
499 h1 = fmix64(h1);
500 h2 = fmix64(h2);
501
502 h1 += h2;
503 h2 += h1;
504
505 ((uint64_t*)out)[0] = h1;
506 ((uint64_t*)out)[1] = h2;
507}
508
509//-----------------------------------------------------------------------------
510
511#undef FORCE_INLINE
512#undef ROTL64
513#undef BIG_CONSTANT
514
515
A streaming C++ equivalent for MurmurHash3_x64_128.
Definition mmh3_hash.h:56
FORCE_INLINE void add(const std::string &input)
Definition mmh3_hash.h:117
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:136
#define FORCE_INLINE
Definition mmh3_hash.h:42
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
A storage class for 128-bit hash value.
Definition hash_128.h:32
uint8_t Value8[16]
Definition hash_128.h:55
std::string ToString() const
Definition hash_128.h:43
uint64_t Value64[2]
Definition hash_128.h:57
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
FORCE_INLINE uint64_t fmix64(uint64_t k)
uint64_t rotl64(uint64_t x, int8_t r)
static const VECTOR2D Ref1CalcArcCenter(const VECTOR2D &aStart, const VECTOR2D &aMid, const VECTOR2D &aEnd)
FORCE_INLINE uint64_t getblock64(const uint64_t *p, int i)
#define ROTL64(x, y)
void MurmurHash3_x64_128(const void *key, const int len, const uint32_t seed, void *out)
static bool Ref0CircleCenterFrom3Points(const VECTOR2D &p1, const VECTOR2D &p2, const VECTOR2D &p3, VECTOR2D *aCenter)
Test suite for KiCad math code.
#define BIG_CONSTANT(x)
BOOST_AUTO_TEST_CASE(TestCalcArcCenter3Pts)
Declare the test suite.
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
const uint32_t seed
VECTOR2I center
VECTOR2I end
BOOST_CHECK_EQUAL(result, "25.4")
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:225
bool InterceptsPositiveX(double aStartAngle, double aEndAngle)
Test if an arc from aStartAngle to aEndAngle crosses the positive X axis (0 degrees).
Definition trigo.h:211
bool InterceptsNegativeX(double aStartAngle, double aEndAngle)
Test if an arc from aStartAngle to aEndAngle crosses the negative X axis (180 degrees).
Definition trigo.h:229
const VECTOR2I CalcArcCenter(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Determine the center of an arc or circle given three points on its circumference.
Definition trigo.cpp:530
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682