3131
3232#ifdef _OPENMP
3333#include <omp.h>
34+ #else
35+ static int omp_get_thread_num (void ) { return 0 ; }
36+ static int omp_get_max_threads (void ) { return 1 ; }
3437#endif
3538
3639#include "ctlgeom.h"
@@ -117,59 +120,73 @@ static vector3 make_vector3(double x, double y, double z) {
117120/************************************************************************/
118121/* return a uniform random number in [a,b] */
119122/************************************************************************/
120- static double urand (double a , double b ) { return a + (b - a ) * (rand () / ((double )RAND_MAX )); }
123+ /* Thread-safe: each thread passes a pointer to its own seed (see rand_r),
124+ * so results are reproducible and independent of parallel execution order. */
125+ static double urand (double a , double b , unsigned int * seed ) {
126+ return a + (b - a ) * (rand_r (seed ) / ((double )RAND_MAX ));
127+ }
128+
129+ static double drand (unsigned int * seed ) { return urand (0.0 , 1.0 , seed ); }
121130
122- static double drand () { return urand (0.0 , 1.0 ); }
131+ /* Allocate one PRNG seed per OpenMP thread, seed[tid] = tid (caller frees).
132+ * Inside a parallel loop, pass &seed[omp_get_thread_num()] to the random
133+ * helpers so each thread draws from its own reproducible, thread-safe stream. */
134+ static unsigned int * make_thread_seeds (void ) {
135+ int nthreads = omp_get_max_threads (), t ;
136+ unsigned int * seed = (unsigned int * )malloc (nthreads * sizeof (unsigned int ));
137+ for (t = 0 ; t < nthreads ; t ++ ) seed [t ] = (unsigned int )t ;
138+ return seed ;
139+ }
123140
124141/************************************************************************/
125142/* random point uniformly distributed over a parallelepiped */
126143/************************************************************************/
127- vector3 random_point_in_box (vector3 min_corner , vector3 max_corner ) {
128- return make_vector3 (urand (min_corner .x , max_corner .x ), urand (min_corner .y , max_corner .y ),
129- urand (min_corner .z , max_corner .z ));
144+ vector3 random_point_in_box (vector3 min_corner , vector3 max_corner , unsigned int * seed ) {
145+ return make_vector3 (urand (min_corner .x , max_corner .x , seed ), urand (min_corner .y , max_corner .y , seed ),
146+ urand (min_corner .z , max_corner .z , seed ));
130147}
131148
132149/************************************************************************/
133150/* random point uniformly distributed over a planar polygon */
134151/* (all z coordinates are 0) */
135152/************************************************************************/
136- vector3 random_point_in_polygon (prism * prsm ) {
153+ vector3 random_point_in_polygon (prism * prsm , unsigned int * seed ) {
137154 // randomly choose a vertex and generate random point within the triangle
138155 // formed by that vertex, the next vertex, and the centroid
139156 vector3 * vertices = prsm -> vertices .items ;
140157 int num_vertices = prsm -> vertices .num_items ;
141- int which_vertex = rand ( ) % num_vertices ;
158+ int which_vertex = rand_r ( seed ) % num_vertices ;
142159 vector3 v0 = {0 , 0 , 0 };
143160 vector3 v1 = vertices [which_vertex ];
144161 vector3 v2 = vertices [(which_vertex + 1 ) % num_vertices ];
145- double xi = urand (0.0 , 1.0 ), eta = urand (0.0 , 1.0 - xi );
162+ double xi = urand (0.0 , 1.0 , seed ), eta = urand (0.0 , 1.0 - xi , seed );
146163 return vector3_plus (vector3_scale (xi , vector3_minus (v1 , v0 )),
147164 vector3_scale (eta , vector3_minus (v2 , v0 )));
148165}
149166
150167/************************************************************************/
151168/* random point uniformly distributed over the surface of a prism */
152169/************************************************************************/
153- vector3 random_point_on_prism (geometric_object o ) {
170+ vector3 random_point_on_prism (geometric_object o , unsigned int * seed ) {
154171 prism * prsm = o .subclass .prism_data ;
155172 vector3 * vertices = prsm -> vertices_p .items ;
156173 int num_vertices = prsm -> vertices_p .num_items ;
157174 double height = prsm -> height ;
158175
159176 // choose a face
160177 int num_faces = num_vertices + 2 ;
161- int which_face = rand ( ) % num_faces ;
178+ int which_face = rand_r ( seed ) % num_faces ;
162179 if (which_face < num_vertices ) // side face
163180 {
164181 vector3 min_corner = vertices [which_face ];
165182 vector3 max_corner = vertices [(which_face + 1 ) % num_vertices ];
166183 max_corner .z = height ;
167184 return random_point_in_box (prism_coordinate_p2c (prsm , min_corner ),
168- prism_coordinate_p2c (prsm , max_corner ));
185+ prism_coordinate_p2c (prsm , max_corner ), seed );
169186 }
170187 else // floor or ceiling
171188 {
172- vector3 p = random_point_in_polygon (prsm );
189+ vector3 p = random_point_in_polygon (prsm , seed );
173190 if (which_face == num_faces - 1 ) p .z = height ;
174191 return prism_coordinate_p2c (prsm , p );
175192 }
@@ -178,9 +195,9 @@ vector3 random_point_on_prism(geometric_object o) {
178195/************************************************************************/
179196/* random unit vector with direction uniformly distributed over unit sphere*/
180197/************************************************************************/
181- vector3 random_unit_vector3 () {
182- double cos_theta = urand (0.0 , 1.0 ), sin_theta = sqrt (1.0 - cos_theta * cos_theta );
183- double phi = urand (0.0 , 2.0 * K_PI );
198+ vector3 random_unit_vector3 (unsigned int * seed ) {
199+ double cos_theta = urand (0.0 , 1.0 , seed ), sin_theta = sqrt (1.0 - cos_theta * cos_theta );
200+ double phi = urand (0.0 , 2.0 * K_PI , seed );
184201 return make_vector3 (sin_theta * cos (phi ), sin_theta * sin (phi ), cos_theta );
185202}
186203
@@ -284,9 +301,13 @@ int test_point_inclusion(geometric_object the_block, geometric_object the_prism,
284301 // latter calls geom_fix_object_ptr internally and is not thread-safe.
285302 // the_block and the_prism were fixed up in run_unit_tests above
286303 // before this parallel loop runs.
287- #pragma omp parallel for schedule(dynamic) reduction(+ : num_failed, num_adjusted)
304+ // schedule(static): fixed iteration->thread mapping so per-thread seeds give
305+ // reproducible results (dynamic scheduling would reintroduce nondeterminism).
306+ unsigned int * seed = make_thread_seeds ();
307+ #pragma omp parallel for schedule(static) reduction(+ : num_failed, num_adjusted)
288308 for (n = 0 ; n < num_tests ; n ++ ) {
289- vector3 p = random_point_in_box (min_corner , max_corner );
309+ unsigned int * s = & seed [omp_get_thread_num ()];
310+ vector3 p = random_point_in_box (min_corner , max_corner , s );
290311 boolean in_block = point_in_fixed_objectp (p , the_block );
291312 boolean in_prism = point_in_fixed_objectp (p , the_prism );
292313
@@ -304,6 +325,7 @@ int test_point_inclusion(geometric_object the_block, geometric_object the_prism,
304325 }
305326 }
306327 if (f ) fclose (f );
328+ free (seed );
307329
308330 printf ("point inclusion: %i/%i points failed (%i adjusted)\n" , num_failed , num_tests ,
309331 num_adjusted );
@@ -325,14 +347,16 @@ int test_normal_to_object(geometric_object the_block, geometric_object the_prism
325347 double tolerance = 1.0e-6 ;
326348
327349 int n ;
328- #pragma omp parallel for schedule(dynamic) reduction(+ : num_failed)
350+ unsigned int * seed = make_thread_seeds ();
351+ #pragma omp parallel for schedule(static) reduction(+ : num_failed)
329352 for (n = 0 ; n < num_tests ; n ++ ) {
353+ unsigned int * s = & seed [omp_get_thread_num ()];
330354 // with probability PFACE, generate random base point lying on one
331355 // of the 6 faces of the prism.
332356 // with probability 1-PFACE, generate random base point lying in the
333357 // extended volume (2x volume of block)
334- vector3 p = (urand (0.0 , 1.0 ) < PFACE ) ? random_point_on_prism (the_prism )
335- : random_point_in_box (min_corner , max_corner );
358+ vector3 p = (urand (0.0 , 1.0 , s ) < PFACE ) ? random_point_on_prism (the_prism , s )
359+ : random_point_in_box (min_corner , max_corner , s );
336360
337361 // normal_to_fixed_object instead of normal_to_object: the latter is
338362 // not thread-safe (calls geom_fix_object_ptr internally).
@@ -348,6 +372,7 @@ int test_normal_to_object(geometric_object the_block, geometric_object the_prism
348372 }
349373 }
350374 if (f ) fclose (f );
375+ free (seed );
351376
352377 printf ("%i/%i normals failed\n" , num_failed , num_tests );
353378 return num_failed ;
@@ -365,13 +390,15 @@ int test_line_segment_intersection(geometric_object the_block, geometric_object
365390
366391 int num_failed = 0 ;
367392 int n ;
368- #pragma omp parallel for schedule(dynamic) reduction(+ : num_failed)
393+ unsigned int * seed = make_thread_seeds ();
394+ #pragma omp parallel for schedule(static) reduction(+ : num_failed)
369395 for (n = 0 ; n < num_tests ; n ++ ) {
396+ unsigned int * s = & seed [omp_get_thread_num ()];
370397 // randomly generated base point within enlarged bounding box
371- vector3 p = random_point_in_box (min_corner , max_corner );
372- vector3 d = random_unit_vector3 ();
373- double a = urand (0.0 , 1.0 );
374- double b = urand (0.0 , 1.0 );
398+ vector3 p = random_point_in_box (min_corner , max_corner , s );
399+ vector3 d = random_unit_vector3 (s );
400+ double a = urand (0.0 , 1.0 , s );
401+ double b = urand (0.0 , 1.0 , s );
375402
376403 double sblock = intersect_line_segment_with_object (p , d , the_block , a , b );
377404 double sprism = intersect_line_segment_with_object (p , d , the_prism , a , b );
@@ -394,6 +421,7 @@ int test_line_segment_intersection(geometric_object the_block, geometric_object
394421 }
395422 }
396423 if (f ) fclose (f );
424+ free (seed );
397425
398426 printf ("%i/%i segments failed\n" , num_failed , num_tests );
399427 return num_failed ;
@@ -1154,8 +1182,9 @@ int run_unit_tests() {
11541182 /* and prism by a random displacement vector */
11551183 /***************************************************************/
11561184#define P_SHIFT 0.75
1157- if (urand (0.0 , 1.0 ) < P_SHIFT ) {
1158- vector3 shift = vector3_scale (urand (0.0 , 1.0 ), random_unit_vector3 ());
1185+ unsigned int shift_seed = 0 ; // single-threaded setup, own seed
1186+ if (urand (0.0 , 1.0 , & shift_seed ) < P_SHIFT ) {
1187+ vector3 shift = vector3_scale (urand (0.0 , 1.0 , & shift_seed ), random_unit_vector3 (& shift_seed ));
11591188 the_block .center = vector3_plus (the_block .center , shift );
11601189 the_prism .center = vector3_plus (the_prism .center , shift );
11611190 // Sync each object's internal cache (block projection_matrix, prism
@@ -1211,7 +1240,8 @@ void usage(char *msg) { print_usage(msg, 1); }
12111240/************************************************************************/
12121241/************************************************************************/
12131242int main (int argc , char * argv []) {
1214- srand (time (NULL ));
1243+ // No global srand(): the randomized tests use rand_r with fixed per-thread
1244+ // seeds (see make_thread_seeds), so runs are deterministic and reproducible.
12151245 geom_initialize ();
12161246
12171247 if (argc <= 1 ) // if no arguments, run unit tests
0 commit comments