-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathMC-GPU_kernel_v1.5b.cu
1909 lines (1580 loc) · 98.5 KB
/
MC-GPU_kernel_v1.5b.cu
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
////////////////////////////////////////////////////////////////////////////////
//
// ****************************
// *** MC-GPU, version 1.5b ***
// ****************************
//
//! Definition of the CUDA GPU kernel for the simulation of x ray tracks in a voxelized geometry.
//! The physics models for Rayleigh and Compton scattering are translated from the Fortran
//! code in PENELOPE 2006.
//
// ** DISCLAIMER **
//
// This software and documentation (the "Software") were developed at the Food and
// Drug Administration (FDA) by employees of the Federal Government in the course
// of their official duties. Pursuant to Title 17, Section 105 of the United States
// Code, this work is not subject to copyright protection and is in the public
// domain. Permission is hereby granted, free of charge, to any person obtaining a
// copy of the Software, to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, or sell copies of the Software or derivatives, and to permit persons
// to whom the Software is furnished to do so. FDA assumes no responsibility
// whatsoever for use by other parties of the Software, its source code,
// documentation or compiled executables, and makes no guarantees, expressed or
// implied, about its quality, reliability, or any other characteristic. Further,
// use of this code in no way implies endorsement by the FDA or confers any
// advantage in regulatory decisions. Although this software can be redistributed
// and/or modified freely, we ask that any derivative works bear some notice that
// they are derived from it, and any modified versions bear some notice that they
// have been modified.
//
//
//! @file MC-GPU_kernel_v1.5b.cu
//! @author Andreu Badal (Andreu.Badal-Soler{at}fda.hhs.gov)
//! @date 2018/01/01
// -- Original code started on: 2009/04/14
//
////////////////////////////////////////////////////////////////////////////////
// ** This software is described in the following reference (please cite it in yuor papers):
// Andreu Badal, Diksha Sharma, Christian G.Graff, Rongping Zeng, and Aldo Badano, Mammography and breast
// tomosynthesis simulator for virtual clinical trials, Computer Physics Communications 261, p. 107779 (2021)
// https://doi.org/10.1016/j.cpc.2020.107779
// ** Update May 2021 ** !!BLOCKING_LAYER!!
// Enabling blocking (or dead) layers at the top and bottom of the detector slab.
// Interactions in these layers will not contribute to the pixel value, but their fluorescence will
// be tracked and might be detected somewhere else. The insensitive top layer causes a measurable
// drop in DQE(0), but it does not affect MTF as implemented. Pixel values will be reduced (less
// energy detected per history. [Reference: Zhou et al., Med. Phys. 34, 1098-1109 (2007)]
#define BLOCKING_LAYER_TOP 0.0000f // [cm] Thickness layer closer to source. Example: 0.0008f for a 8 micron layer (0.0 == no layer). !!BLOCKING_LAYER!!
#define BLOCKING_LAYER_BOTTOM 0.0000f // [cm] Thickness layer further from source. Example: 0.0008f for a 8 micron layer (0.0 == no layer). !!BLOCKING_LAYER!!
////////////////////////////////////////////////////////////////////////////////
//! Initialize the image array, ie, set all pixels to zero
//! Essentially, this function has the same effect as the command:
//! "cutilSafeCall(cudaMemcpy(image_device, image, image_bytes, cudaMemcpyHostToDevice))";
//!
//! CUDA performs some initialization work the first time a GPU kernel is called.
//! Therefore, calling a short kernel before the real particle tracking is performed
//! may improve the accuracy of the timing measurements in the relevant kernel.
//!
//! @param[in,out] image Pointer to the image array.
//! @param[in] pixels_per_image Number of pixels in the image (ie, elements in the array).
////////////////////////////////////////////////////////////////////////////////
__global__ void init_image_array_GPU(unsigned long long int* image, int pixels_per_image)
{
int my_pixel = threadIdx.x + blockIdx.x*blockDim.x;
if (my_pixel < pixels_per_image)
{
// -- Set the current pixel to 0 and return, avoiding overflow when more threads than pixels are used:
image[my_pixel] = (unsigned long long int)(0); // Initialize non-scatter image
my_pixel += pixels_per_image; // (advance to next image)
image[my_pixel] = (unsigned long long int)(0); // Initialize Compton image
my_pixel += pixels_per_image; // (advance to next image)
image[my_pixel] = (unsigned long long int)(0); // Initialize Rayleigh image
my_pixel += pixels_per_image; // (advance to next image)
image[my_pixel] = (unsigned long long int)(0); // Initialize multi-scatter image
}
}
// ////////////////////////////////////////////////////////////////////////////////
// //! Initialize the dose deposition array, ie, set all voxel doses to zero
// //!
// //! @param[in,out] dose Pointer to the dose mean and sigma arrays.
// //! @param[in] num_voxels_dose Number of voxels in the dose ROI (ie, elements in the arrays).
// ////////////////////////////////////////////////////////////////////////////////
// __global__
// void init_dose_array_GPU(ulonglong2* voxels_Edep, int num_voxels_dose)
// {
// int my_voxel = threadIdx.x + blockIdx.x*blockDim.x;
// register ulonglong2 ulonglong2_zero;
// ulonglong2_zero.x = ulonglong2_zero.y = (unsigned long long int) 0;
// if (my_voxel < num_voxels_dose)
// {
// dose[my_voxel] = ulonglong2_zero; // Set the current voxel to (0,0) and return, avoiding overflow
// }
// }
////////////////////////////////////////////////////////////////////////////////
//! Main function to simulate x-ray tracks inside a voxelized geometry.
//! Secondary electrons are not simulated (in photoelectric and Compton
//! events the energy is locally deposited).
//!
//! The following global variables, in the GPU __constant__ memory are used:
//! voxel_data_CONST,
//! source_energy_data_CONST
//! mfp_table_data_CONST.
//! density_LUT_CONST
//!
//! @param[in] history_batch Particle batch number (only used in the CPU version when CUDA is disabled!, the GPU uses the built-in variable threadIdx)
//! @param[in] num_p Projection number in the CT simulation. This variable defines a specific angle and the corresponding source and detector will be used.
//! @param[in] histories_per_thread Number of histories to simulate for each call to this function (ie, for GPU thread).
//! @param[in] seed_input Random number generator seed (the same seed is used to initialize the two MLCGs of RANECU).
//! @param[in] voxel_mat_dens Pointer to the voxel densities and material vector (the voxelized geometry), stored in GPU glbal memory.
//! @param[in] mfp_Woodcock_table Two parameter table for the linear interpolation of the Woodcock mean free path (MFP) (stored in GPU global memory).
//! @param[in] mfp_table_a First element for the linear interpolation of the interaction mean free paths (stored in GPU global memory).
//! @param[in] mfp_table_b Second element for the linear interpolation of the interaction mean free paths (stored in GPU global memory).
//! @param[in] rayleigh_table Pointer to the table with the data required by the Rayleigh interaction sampling, stored in GPU global memory.
//! @param[in] compton_table Pointer to the table with the data required by the Compton interaction sampling, stored in GPU global memory.
//! @param[in,out] image Pointer to the image vector in the GPU global memory.
//! @param[in,out] dose Pointer to the array containing the 3D voxel dose (and its uncertainty) in the GPU global memory.
////////////////////////////////////////////////////////////////////////////////
__global__ void track_particles(int histories_per_thread,
short int num_p, // For a CT simulation: allocate space for up to MAX_NUM_PROJECTIONS projections.
int* seed_input_device, // Random seed read from global memory; secuence continued for successive projections in same GPU. !!DBTv1.4!!
unsigned long long int* image,
ulonglong2* voxels_Edep,
int* voxel_mat_dens, //!!bitree!! Using "int" to be store the index to the bitree table //!!FixedDensity_DBT!! Allocating "voxel_mat_dens" as "char" instead of "float2"
char* bitree, //!!bitree!! Array with the bitrees for every non-uniform coarse voxel
float2* mfp_Woodcock_table,
float3* mfp_table_a,
float3* mfp_table_b,
struct rayleigh_struct* rayleigh_table,
struct compton_struct* compton_table,
struct detector_struct* detector_data_array,
struct source_struct* source_data_array,
ulonglong2* materials_dose)
{
// -- Declare the track state variables:
float3 position, direction;
float energy, step, prob, randno, mfp_density, mfp_Woodcock;
float3 mfp_table_read_a, mfp_table_read_b;
int2 seed;
int index;
int material0, // Current material, starting at 0 for 1st material
material_old; // Flag to mark a material or energy change
signed char scatter_state; // Flag for scatter images: scatter_state=0 for non-scattered, =1 for Compton, =2 for Rayleigh, and =3 for multiple scatter.
// -- Store the Compton table in shared memory from global memory:
// For Compton and Rayleigh the access to memory is not coherent and the caching capability do not speeds up the accesses, they actually slows down the acces to other data.
__shared__ struct compton_struct cgco_SHARED;
__shared__ struct detector_struct detector_data_SHARED;
__shared__ struct source_struct source_data_SHARED;
if (0==threadIdx.x) // First GPU thread copies the variables to shared memory
{
// -Copy the current source, detector data from global to shared memory for fast access:
source_data_SHARED = source_data_array[num_p];
detector_data_SHARED = detector_data_array[num_p]; // Copy the long array to a single instance in shared memory for the current projection
// -Copy the compton data to shared memory:
cgco_SHARED = *compton_table;
}
__syncthreads(); // Make sure all threads will see the initialized shared variable
// -- Initialize the RANECU generator in a position far away from the previous history:
init_PRNG((threadIdx.x + blockIdx.x*blockDim.x), histories_per_thread, *seed_input_device, &seed); // Using a 1D block. Random seed read from global memory. !!DBTv1.4!!
// -- Loop for the "histories_per_thread" particles in the current history_batch:
for( ; histories_per_thread>0; histories_per_thread--)
{
// printf("\n\n********* NEW HISTORY: %d [seeds: %d, %d]\n\n", histories_per_thread, seed.x, seed.y); // fflush(stdout); // !!Verbose!! calling printf from the GPU is possible but if multiple threads call it at the same time some output will be lost.
unsigned int absvox = 1;
// -- Call the source function to get a primary x ray:
source(&position, &direction, &energy, &seed, &absvox, &source_data_SHARED, &detector_data_SHARED);
scatter_state = (signed char)0; // Reset previous scatter state: new non-scattered particle loaded
// -- Find the current energy bin by truncation (this could be pre-calculated for a monoenergetic beam):
// The initialization host code made sure that the sampled energy will always be within the tabulated energies (index never negative or too large).
index = __float2int_rd((energy-mfp_table_data_CONST.e0)*mfp_table_data_CONST.ide); // Using CUDA function to convert float to integer rounding down (towards minus infinite)
// -- Get the minimum mfp at the current energy using linear interpolation (Woodcock tracking):
{
float2 mfp_Woodcock_read = mfp_Woodcock_table[index]; // Read the 2 parameters for the linear interpolation in a single read from global memory
mfp_Woodcock = mfp_Woodcock_read.x + energy * mfp_Woodcock_read.y; // Interpolated minimum MFP
}
// -- Reset previous material to force a recalculation of the MFPs (negative materials are not allowed in the voxels):
material_old = -1;
// *** X-ray interaction loop:
for(;;)
{
if (absvox==FLAG_OUTSIDE_VOXELS)
break; // -- Primary particle was not pointing to the voxel region! (but may still be detected after moving in vacuum in a straight line).
// *** Virtual interaction loop: // New loop structure in MC-GPU_v1.3: simulate all virtual events before sampling Compton & Rayleigh:
// float2 matdens;
short3 voxel_coord; // Variable used only by DOSE TALLY
do
{
step = -(mfp_Woodcock)*logf(ranecu(&seed)); // Using the minimum MFP in the geometry for the input energy (Woodcock tracking)
position.x += step*direction.x;
position.y += step*direction.y;
position.z += step*direction.z;
// -- Locate the new particle in the voxel geometry:
absvox = locate_voxel(position, &voxel_coord); // Get the voxel number at the current position and the voxel coordinates (used to check if inside the dose ROI in DOSE TALLY).
if (absvox==FLAG_OUTSIDE_VOXELS)
break; // -- Particle escaped the voxel region! ("index" is still >0 at this moment)
// matdens = voxel_mat_dens[absvox]; // Get the voxel material and density in a single read from global memory
// material0 = (int)(matdens.x - 1); // Set the current material by truncation, and set 1st material to value '0'.
//!!FixedDensity_DBT!! Allocating "voxel_mat_dens" as "char" instead of "float2". Density taken from function "density_LUT". First material number == 0
material0 = (int)voxel_mat_dens[absvox]; // Get the voxel material and density in a single read from global memory (first material==0)
if (material0<0)
{
// -- Non-uniform low resolution voxel: find material at current location searching the original high resolution geometry using the corresponding binary tree:
material0 = find_material_bitree(&position, bitree, -material0, &voxel_coord); // !!bitree!!
}
// -- Get the data for the linear interpolation of the interaction MFPs, in case the energy or material have changed:
if (material0 != material_old)
{
mfp_table_read_a = mfp_table_a[index*(MAX_MATERIALS)+material0];
mfp_table_read_b = mfp_table_b[index*(MAX_MATERIALS)+material0];
material_old = material0; // Store the new material
}
// *** Apply Woodcock tracking:
mfp_density = mfp_Woodcock * density_LUT_CONST[material0]; //!!FixedDensity_DBT!! Density taken from constant memory array "density_LUT_CONST"; Old: mfp_density=mfp_Woodcock*matdens.y;
// -- Calculate probability of delta scattering, using the total mean free path for the current material and energy (linear interpolation):
prob = 1.0f - mfp_density * (mfp_table_read_a.x + energy * mfp_table_read_b.x);
randno = ranecu(&seed); // Sample uniform PRN
}
while (randno<prob); // [Iterate if there is a delta scattering event]
if (absvox==FLAG_OUTSIDE_VOXELS)
break; // -- Particle escaped the voxel region! Break the interaction loop to call tally image.
// The GPU threads will be stopped and waiting here until ALL threads have a REAL event:
// -- Real event takes place! Check the kind of event and sample the effects of the interaction:
prob += mfp_density * (mfp_table_read_a.y + energy * mfp_table_read_b.y); // Interpolate total Compton MFP ('y' component)
if (randno<prob) // [Checking Compton scattering]
{
// *** Compton interaction:
// -- Sample new direction and energy:
double costh_Compton;
randno = energy; // Save temporal copy of the particle energy (variable randno not necessary until next sampling). DOSE TALLY
GCOa(&energy, &costh_Compton, &material0, &seed, &cgco_SHARED);
rotate_double(&direction, costh_Compton, /*phi=2*pi*PRN=*/ 6.28318530717958647693*ranecu_double(&seed));
randno = energy - randno; // Save temporal copy of the negative of the energy lost in the interaction. DOSE TALLY
// -- Find the new energy interval:
index = __float2int_rd((energy-mfp_table_data_CONST.e0)*mfp_table_data_CONST.ide); // Using CUDA function to convert float to integer rounding down (towards minus infinite)
if (index>-1) // 'index' will be negative only when the energy is below the tabulated minimum energy: particle will be then absorbed (rejected) after tallying the dose.
{
// -- Get the Woodcock MFP for the new energy (energy above minimum cutoff):
float2 mfp_Woodcock_read = mfp_Woodcock_table[index]; // Read the 2 parameters for the linear interpolation in a single read from global memory
mfp_Woodcock = mfp_Woodcock_read.x + energy * mfp_Woodcock_read.y; // Interpolated minimum MFP
material_old = -2; // Set an impossible material to force an update of the MFPs data for the nex energy interval
// -- Update scatter state:
if (scatter_state==(signed char)0)
scatter_state = (signed char)1; // Set scatter_state == 1: Compton scattered particle
else
scatter_state = (signed char)3; // Set scatter_state == 3: Multi-scattered particle
}
}
else
{
prob += mfp_density * (mfp_table_read_a.z + energy * mfp_table_read_b.z); // Interpolate total Rayleigh MFP ('z' component)
if (randno<prob) // [Checking Rayleigh scattering]
{
// *** Rayleigh interaction:
// -- Sample angular deflection:
double costh_Rayleigh;
float pmax_current = rayleigh_table->pmax[(index+1)*MAX_MATERIALS+material0]; // Get max (ie, value for next bin?) cumul prob square form factor for Rayleigh sampling
GRAa(&energy, &costh_Rayleigh, &material0, &pmax_current, &seed, rayleigh_table);
rotate_double(&direction, costh_Rayleigh, /*phi=2*pi*PRN=*/ 6.28318530717958647693*ranecu_double(&seed));
// -- Update scatter state:
if (scatter_state==(signed char)0)
scatter_state = (signed char)2; // Set scatter_state == 1: Rayleigh scattered particle
else
scatter_state = (signed char)3; // Set scatter_state == 3: Multi-scattered particle
}
else
{
// *** Photoelectric interaction (or pair production): mark particle for absorption after dose tally (ie, index<0)!
randno = -energy; // Save temporal copy of the (negative) energy deposited in the interaction (variable randno not necessary anymore).
index = -11; // A negative "index" marks that the particle was absorved and that it will never arrive at the detector.
}
}
// -- Tally the dose deposited in Compton and photoelectric interactions:
if (randno<-0.001f)
{
float Edep = -1.0f*randno; // If any energy was deposited, this variable will temporarily store the negative value of Edep.
// -- Tally the dose deposited in the current material, if enabled (ie, array allocated and not null):
if (materials_dose!=NULL)
tally_materials_dose(&Edep, &material0, materials_dose); // !!tally_materials_dose!!
// -- Tally the energy deposited in the current voxel, if enabled (tally disabled when dose_ROI_x_max_CONST is negative). DOSE TALLY
// Optional code to skip dose tally in air (material=0): if (dose_ROI_x_max_CONST > -1 && 0!=material0)
if (dose_ROI_x_max_CONST > -1)
tally_voxel_energy_deposition(&Edep, &voxel_coord, voxels_Edep);
}
// -- Break interaction loop for particles that have been absorbed or with energy below the tabulated cutoff: particle is "absorbed" (ie, track discontinued).
if (index<0)
break;
} // [Cycle the X-ray interaction loop]
if (index>-1)
{
// -- Particle escaped the voxels but was not absorbed, check if it will arrive at the detector and tally its energy:
tally_image(&energy, &position, &direction, &scatter_state, image, &source_data_SHARED, &detector_data_SHARED, &seed);
}
} // [Continue with a new history]
// -- Store the final random seed used by the last thread in the grid to global memory in order to continue the random secuence in successive projections in same GPU without overlapping. !!DBTv1.4!!
// Since I am only storing the 'x' component and using it to init both parts of the ranecu generator, the sequence will actually diverge, but I warranty that at least one MLCG will stay uncorrelated. !!DeBuG!!
if ( (blockIdx.x == (gridDim.x-1)) && (threadIdx.x == (blockDim.x-1)))
{
*seed_input_device = seed.x; // Store seed in GPU memory, but only for the thread with the largest id
}
} // [All tracks simulated for this kernel call: return to CPU]
////////////////////////////////////////////////////////////////////////////////
//! Tally the dose deposited in the voxels.
//! This function is called whenever a particle suffers a Compton or photoelectric
//! interaction. It is not necessary to call this function if the dose tally
//! was disabled in the input file (ie, dose_ROI_x_max_CONST < 0).
//! Electrons are not transported in MC-GPU and therefore we are approximating
//! that the dose is equal to the KERMA (energy released by the photons alone).
//! This approximation is acceptable when there is electronic equilibrium and when
//! the range of the secondary electrons is shorter than the voxel size. Usually the
//! doses will be acceptable for photon energies below 1 MeV. The dose estimates may
//! not be accurate at the interface of low density volumes.
//!
//! We need to use atomicAdd() in the GPU to prevent that multiple threads update the
//! same voxel at the same time, which would result in a lose of information.
//! This is very improbable when using a large number of voxels but gives troubles
//! with a simple geometries with few voxels (in this case the atomicAdd will slow
//! down the code because threads will update the voxel dose secuentially).
//!
//!
//! @param[in] Edep Energy deposited in the interaction
//! @param[in] voxel_coord Voxel coordinates, needed to check if particle located inside the input region of interest (ROI)
//! @param[out] voxels_Edep ulonglong2 array containing the 3D voxel dose and dose^2 (ie, uncertainty) as unsigned integers scaled by SCALE_eV.
////////////////////////////////////////////////////////////////////////////////
__device__ inline void tally_voxel_energy_deposition(float* Edep, short3* voxel_coord, ulonglong2* voxels_Edep)
{
if((voxel_coord->x < dose_ROI_x_min_CONST) || (voxel_coord->x > dose_ROI_x_max_CONST) ||
(voxel_coord->y < dose_ROI_y_min_CONST) || (voxel_coord->y > dose_ROI_y_max_CONST) ||
(voxel_coord->z < dose_ROI_z_min_CONST) || (voxel_coord->z > dose_ROI_z_max_CONST))
{
return; // -- Particle outside the ROI: return without tallying anything.
}
// -- Particle inside the ROI: tally Edep.
register int DX = 1 + (int)(dose_ROI_x_max_CONST - dose_ROI_x_min_CONST);
register int num_voxel = (int)(voxel_coord->x-dose_ROI_x_min_CONST) + ((int)(voxel_coord->y-dose_ROI_y_min_CONST))*DX + ((int)(voxel_coord->z-dose_ROI_z_min_CONST))*DX*(1 + (int)(dose_ROI_y_max_CONST-dose_ROI_y_min_CONST));
atomicAdd(&voxels_Edep[num_voxel].x, __float2ull_rn((*Edep)*SCALE_eV) ); // Energy deposited at the voxel, scaled by the factor SCALE_eV and rounded.
atomicAdd(&voxels_Edep[num_voxel].y, __float2ull_rn((*Edep)*(*Edep)) ); // (not using SCALE_eV for std_dev to prevent overflow)
return;
}
////////////////////////////////////////////////////////////////////////////////
//! Source that creates primary x rays, according to the defined source model.
//! The particles are automatically moved to the surface of the voxel bounding box,
//! to start the tracking inside a real material. If the sampled particle do not
//! enter the voxels, it is init in the focal spot and the main program will check
//! if it arrives at the detector or not.
//!
//! @param[in] source_data Structure describing the source.
//! @param[in] source_energy_data_CONST Global variable in constant memory space describing the source energy spectrum.
//! @param[out] position Initial particle position (particle transported inside the voxel bbox).
//! @param[out] direction Sampled particle direction (cosine vectors).
//! @param[out] energy Sampled energy of the new x ray.
//! @param[in] seed Current seed of the random number generator, requiered to sample the movement direction.
//! @param[out] absvox Set to <0 if primary particle will not cross the voxels, not changed otherwise (>0).
////////////////////////////////////////////////////////////////////////////////
__device__ inline void source(float3* position, float3* direction, float* energy, int2* seed, unsigned int* absvox, struct source_struct* source_data_SHARED, struct detector_struct* detector_data_SHARED)
{
// *** Sample the initial x-ray energy following the input energy spectrum using the Walker aliasing algorithm from PENELOPE:
// The following code is equivalent to calling the function "seeki_walker": int sampled_bin = seeki_walker(source_data_CONST.espc_cutoff, source_data_CONST.espc_alias, ranecu(seed), source_data_CONST.num_bins_espc);
int sampled_bin;
float RN = ranecu(seed) * source_energy_data_CONST.num_bins_espc; // Find initial interval (array starting at 0):
int int_part = __float2int_rd(RN); // -- Integer part (round down)
float fraction_part = RN - ((float)int_part); // -- Fractional part
if (fraction_part < source_energy_data_CONST.espc_cutoff[int_part]) // Check if we are in the aliased part
sampled_bin = int_part; // Below the cutoff: return current value
else
sampled_bin = (int)source_energy_data_CONST.espc_alias[int_part]; // Above the cutoff: return alias
// Linear interpolation of the final energy within the sampled energy bin:
*energy = source_energy_data_CONST.espc[sampled_bin] + ranecu(seed) * (source_energy_data_CONST.espc[sampled_bin+1] - source_energy_data_CONST.espc[sampled_bin]);
// *** If not a point source, sample the focal spot position using a uniformly-distributed angle on a sphere AND a Gaussian-distributed random radius: !!DBTv1.4!!
if (source_data_SHARED->focal_spot_FWHM > 5.0e-7f)
{
float g = sample_gausspdf_below2sigma(seed); // Return a Gaussian distributed random value located at less than 2 sigma from the center. !!DBTv1.4!!
// Cropping the Gaussian dist at 2 sigma to prevent generating photons unrealistically far from the focal spot center. The 2 sigma limit has been set arbitrary and will affect 4.55% of sampled locations.
// Experimental focal spot measurements show that the spot is quite sharp [A Burgess, "Focal spots: I. MTF separability", Invest Radiol 12, p. 36-43 (1977)]
//ALTERNATIVE METHOD: float g = sample_gausspdf(seed); // Return a Gaussian distributed random value. !!DBTv1.4!!
//ALTERNATIVE METHOD: gausspdf(&g1, &g2, seed); // Sample 2 independent Gaussian distributed random variables.
float cos_thetaFS = 2.0f*ranecu(seed)-1.0f; // Sample uniform points on a sphere
float sin_thetaFS = sqrtf(1.0f-cos_thetaFS*cos_thetaFS);
float phiFS = (PI*2.0f)*ranecu(seed);
float cos_phiFS, sin_phiFS;
sincos(phiFS, &sin_phiFS, &cos_phiFS);
// Full Width at Half Maximum for Gaussian curve: FWHM = [2*sqrt(2*ln(2))] * sigma = 2.3548 * sigma
// For a focal spot with FWHM = 0.0200 cm --> sigma = 0.0200/2.354820 = 0.0200*0.4246609 = 0.008493
float r = g * source_data_SHARED->focal_spot_FWHM * 0.424660900144f; // Use a Gaussian distribution for the radius
// Set current focal spot position with sampled focal spot shift (source_data_SHARED->position was already rotated to the appropriate angle):
position->x = source_data_SHARED->position.x + r*sin_thetaFS*cos_phiFS;
position->y = source_data_SHARED->position.y + r*sin_thetaFS*sin_phiFS;
position->z = source_data_SHARED->position.z + r*cos_thetaFS;
}
else
{
// Set default focal spot position for point source:
position->x = source_data_SHARED->position.x;
position->y = source_data_SHARED->position.y;
position->z = source_data_SHARED->position.z;
}
// *** Sample the initial direction:
do // Iterate sampling if the sampled direction is not acceptable to get a square field at the given phi (rejection sampling): force square field for any phi!!
{
// Using the algorithm used in PENMAIN.f, from penelope 2008 (by F. Salvat).
direction->z = source_data_SHARED->cos_theta_low + ranecu(seed)*source_data_SHARED->D_cos_theta; // direction->z = w = cos(theta_sampled)
register float phi_sampled = source_data_SHARED->phi_low + ranecu(seed)*source_data_SHARED->D_phi;
register float sin_theta_sampled = sqrtf(1.0f - direction->z*direction->z);
float sinphi_sampled, cosphi_sampled;
sincos(phi_sampled, &sinphi_sampled,&cosphi_sampled); // Calculate the SIN and COS at the same time.
direction->y = sin_theta_sampled * sinphi_sampled;
direction->x = sin_theta_sampled * cosphi_sampled;
}
while( (fabsf(direction->z/(direction->y+1.0e-8f)) > source_data_SHARED->max_height_at_y1cm) || // Force square field for any phi by rejection sampling. (The "+1e-8" prevents division by zero)
(fabsf(direction->x/(direction->y+1.0e-8f)) > source_data_SHARED->max_width_at_y1cm) ); //!!DBTv1.4!!
// -- Apply the rotation that moves the emission direction from the default direction pointing to (0,1,0), to the required acquistion orientation:
apply_rotation(direction, source_data_SHARED->rot_fan); //!!DBTv1.4!!
// *** Simulate motion blur (if needed): Rotate focal spot position and emission direction according to a uniformly-sampled angular motion blur !!DBTv1.4!!
if (source_data_SHARED->rotation_blur>EPS)
{
position->x -= source_data_SHARED->rotation_point.x; // Move to the coordinate system where rotation point is at the origin to apply the rotation
position->y -= source_data_SHARED->rotation_point.y;
position->z -= source_data_SHARED->rotation_point.z;
float blur_angle = source_data_SHARED->rotation_blur*(ranecu(seed)-0.5f); // Uniform sampling of angular motion blur before and after the nominal acquisition angle
// rotate_around_axis_Rodrigues(&blur_angle, &source_data_SHARED->axis_of_rotation, position); // Rotate position around rotation angle using Rodrigues' formula (http://mathworld.wolfram.com/RodriguesRotationFormula.html)
rotate_2vectors_around_axis_Rodrigues(&blur_angle, &source_data_SHARED->axis_of_rotation, position, direction); // Rotate position and direction around rotation angle using Rodrigues' formula (http://mathworld.wolfram.com/RodriguesRotationFormula.html)
position->x += source_data_SHARED->rotation_point.x; // Move back to the real-world coordinate system where rotation point is not at the origin
position->y += source_data_SHARED->rotation_point.y;
position->z += source_data_SHARED->rotation_point.z;
}
// To be safe, renormalize the direction vector to 1 (should not be necessary but single precision math might accumulate errors)
double NORM = rsqrt(direction->x*direction->x + direction->y*direction->y + direction->z*direction->z); // !!DeBuG!! Check if it is really necessary to renormalize in a real simulation!!
direction->x = NORM*direction->x;
direction->y = NORM*direction->y;
direction->z = NORM*direction->z;
// printf("%.20lf %.20lf %.20lf\n", NORM, rsqrt(direction->x*direction->x + direction->y*direction->y + direction->z*direction->z), diff); //!!VERBOSE!! !!DeBuG!!
// *** Move the particle to the inside of the voxel bounding box:
move_to_bbox(position, direction, absvox);
}
////////////////////////////////////////////////////////////////////////////////
//! Functions to moves a particle towards the inside of the voxelized geometry bounding box.
//! An EPSILON distance is added to make sure the particles will be clearly inside the bbox,
//! not exactly on the surface.
//!
//! This algorithm makes the following assumptions:
//! - The back lower vertex of the voxel bounding box is always located at the origin: (x0,y0,z0)=(0,0,0).
//! - The initial value of "position" corresponds to the focal spot location.
//! - When a ray is not pointing towards the bbox plane that it should cross according to the sign of the direction,
//! I assign a distance to the intersection =0 instead of the real negative distance. The wall that will be
//! crossed to enter the bbox is always the furthest and therefore a 0 distance will never be used except
//! in the case of a ray starting inside the bbox or outside the bbox and not pointing to any of the 3 planes.
//! In this situation the ray will be transported a 0 distance, meaning that it will stay at the focal spot.
//!
//! (Interesting information on ray-box intersection: http://tog.acm.org/resources/GraphicsGems/gems/RayBox.c)
//!
//! @param[in,out] position Particle position: initially set to the focal spot, returned transported inside the voxel bbox.
//! @param[out] direction Sampled particle direction (cosine vectors).
//! @param[out] intersection_flag Set to <0 if particle outside bbox and will not cross the voxels, not changed otherwise.
//! @param[in] size_bbox Global variable from structure voxel_data_CONST: size of the bounding box.
//! @param[in] offset Global variable from structure voxel_data_CONST: offset of the geometry in x, y, and z.
////////////////////////////////////////////////////////////////////////////////
__device__ inline void move_to_bbox(float3* position, float3* direction, unsigned int* intersection_flag)
{
float dist_y, dist_x, dist_z;
// -Distance to the nearest Y plane:
if ((direction->y) > EPS_SOURCE) // Moving to +Y: check distance to y=0 plane
{
// Check Y=0 (bbox wall):
if (position->y > voxel_data_CONST.offset.y) //!!DBTv1.4!! Allowing a 3D offset of the voxelized geometry (default origin at lower back corner).
dist_y = 0.0f; // No intersection with this plane: particle inside or past the box
// The actual distance would be negative but we set it to 0 bc we will not move the particle if no intersection exist.
else
dist_y = EPS_SOURCE + (voxel_data_CONST.offset.y-position->y)/(direction->y); // dist_y > 0 for sure in this case
}
else if ((direction->y) < NEG_EPS_SOURCE)
{
// Check Y=voxel_data_CONST.size_bbox.y:
if (position->y < (voxel_data_CONST.size_bbox.y + voxel_data_CONST.offset.y))
dist_y = 0.0f; // No intersection with this plane
else
dist_y = EPS_SOURCE + (voxel_data_CONST.size_bbox.y + voxel_data_CONST.offset.y - position->y)/(direction->y); // dist_y > 0 for sure in this case
}
else // (direction->y)~0
dist_y = NEG_INF; // Particle moving parallel to the plane: no interaction possible (set impossible negative dist = -INFINITE)
// -Distance to the nearest X plane:
if ((direction->x) > EPS_SOURCE)
{
// Check X=0:
if (position->x > voxel_data_CONST.offset.x)
dist_x = 0.0f;
else
dist_x = EPS_SOURCE + (voxel_data_CONST.offset.x-position->x)/(direction->x); // dist_x > 0 for sure in this case
}
else if ((direction->x) < NEG_EPS_SOURCE)
{
// Check X=voxel_data_CONST.size_bbox.x:
if (position->x < (voxel_data_CONST.size_bbox.x+voxel_data_CONST.offset.x))
dist_x = 0.0f;
else
dist_x = EPS_SOURCE + (voxel_data_CONST.size_bbox.x + voxel_data_CONST.offset.x - position->x)/(direction->x); // dist_x > 0 for sure in this case
}
else
dist_x = NEG_INF;
// -Distance to the nearest Z plane:
if ((direction->z) > EPS_SOURCE)
{
// Check Z=0:
if (position->z > voxel_data_CONST.offset.z)
dist_z = 0.0f;
else
dist_z = EPS_SOURCE + (voxel_data_CONST.offset.z - position->z)/(direction->z); // dist_z > 0 for sure in this case
}
else if ((direction->z) < NEG_EPS_SOURCE)
{
// Check Z=voxel_data_CONST.size_bbox.z:
if (position->z < (voxel_data_CONST.size_bbox.z+voxel_data_CONST.offset.z))
dist_z = 0.0f;
else
dist_z = EPS_SOURCE + (voxel_data_CONST.size_bbox.z + voxel_data_CONST.offset.z - position->z)/(direction->z); // dist_z > 0 for sure in this case
}
else
dist_z = NEG_INF;
// -- Find the longest distance plane, which is the one that has to be crossed to enter the bbox.
// Storing the maximum distance in variable "dist_z". Distance will be =0 if no intersection exists or
// if the x ray is already inside the bbox.
if ( (dist_y>dist_x) && (dist_y>dist_z) )
dist_z = dist_y; // dist_z == dist_max
else if (dist_x>dist_z)
dist_z = dist_x;
// else
// dist_max = dist_z;
// -- Move particle from the focal spot (current location) to the bbox wall surface (slightly inside):
float x = position->x + dist_z * direction->x;
float y = position->y + dist_z * direction->y;
float z = position->z + dist_z * direction->z;
// Check if the new position is outside the bbox. If not, return the moved location:
if ( (x < voxel_data_CONST.offset.x) || (x > (voxel_data_CONST.size_bbox.x+voxel_data_CONST.offset.x)) ||
(y < voxel_data_CONST.offset.y) || (y > (voxel_data_CONST.size_bbox.y+voxel_data_CONST.offset.y)) ||
(z < voxel_data_CONST.offset.z) || (z > (voxel_data_CONST.size_bbox.z+voxel_data_CONST.offset.z)) )
{
(*intersection_flag) = FLAG_OUTSIDE_VOXELS; // OLD: -111; // Particle outside the bbox AND not pointing to the bbox: set absvox<0 to skip interaction sampling. Leave particle position at focal spot.
}
else
{
position->x = x;
position->y = y;
position->z = z;
}
}
////////////////////////////////////////////////////////////////////////////////
//! Upper limit of the number of random values sampled in a single track.
//! I need a large leap for simulations containing a heavy element that causes a lot of delta scattering (eg, for a 15 keV simulation with bone and water I might have 10 delta scatterings; adding Tungsten I might have >650 deltas, and each delta iteration consumes two PRN).
#define LEAP_DISTANCE 2048
// #define LEAP_DISTANCE 256 //!!DeBuG!! !!DBTv1.4!! 256 is too low when using Tungsten!!!
//! Multipliers and moduli for the two MLCG in RANECU.
#define a1_RANECU 40014
#define m1_RANECU 2147483563
#define a2_RANECU 40692
#define m2_RANECU 2147483399
////////////////////////////////////////////////////////////////////////////////
//! Initialize the pseudo-random number generator (PRNG) RANECU to a position
//! far away from the previous history (leap frog technique).
//!
//! Each calculated seed initiates a consecutive and disjoint sequence of
//! pseudo-random numbers with length LEAP_DISTANCE, that can be used to
//! in a parallel simulation (Sequence Splitting parallelization method).
//! The basic equation behind the algorithm is:
//! S(i+j) = (a**j * S(i)) MOD m = [(a**j MOD m)*S(i)] MOD m ,
//! which is described in:
//! P L'Ecuyer, Commun. ACM 31 (1988) p.742
//!
//! This function has been adapted from "seedsMLCG.f", see:
//! A Badal and J Sempau, Computer Physics Communications 175 (2006) p. 440-450
//!
//! @param[in] history Particle bach number.
//! @param[in] seed_input Initial PRNG seed input (used to initiate both MLCGs in RANECU).
//! @param[out] seed Initial PRNG seeds for the present history.
//!
////////////////////////////////////////////////////////////////////////////////
__device__ inline void init_PRNG(int history_batch, int histories_per_thread, int seed_input, int2* seed)
{
// -- Move the RANECU generator to a unique position for the current batch of histories:
// I have to use an "unsigned long long int" value to represent all the simulated histories in all previous batches
// The maximum unsigned long long int value is ~1.8e19: if history >1.8e16 and LEAP_DISTANCE==1000, 'leap' will overflow.
// **** 1st MLCG:
unsigned long long int leap = ((unsigned long long int)(history_batch+1))*(histories_per_thread*LEAP_DISTANCE);
int y = 1;
int z = a1_RANECU;
// -- Calculate the modulo power '(a^leap)MOD(m)' using a divide-and-conquer algorithm adapted to modulo arithmetic
for(;;)
{
// (A2) Halve n, and store the integer part and the residue
if (0!=(leap&01)) // (bit-wise operation for MOD(leap,2), or leap%2 ==> proceed if leap is an odd number) Equivalent: t=(short)(leap%2);
{
leap >>= 1; // Halve n moving the bits 1 position right. Equivalent to: leap=(leap/2);
y = abMODm(m1_RANECU,z,y); // (A3) Multiply y by z: y = [z*y] MOD m
if (0==leap) break; // (A4) leap==0? ==> finish
}
else // (leap is even)
{
leap>>= 1; // Halve leap moving the bits 1 position right. Equivalent to: leap=(leap/2);
}
z = abMODm(m1_RANECU,z,z); // (A5) Square z: z = [z*z] MOD m
}
// AjMODm1 = y; // Exponentiation finished: AjMODm = expMOD = y = a^j
// -- Compute and display the seeds S(i+j), from the present seed S(i), using the previously calculated value of (a^j)MOD(m):
// S(i+j) = [(a**j MOD m)*S(i)] MOD m
// S_i = abMODm(m,S_i,AjMODm)
seed->x = abMODm(m1_RANECU, seed_input, y); // Using the input seed as the starting seed
// **** 2nd MLCG (repeating the previous calculation for the 2nd MLCG parameters):
leap = ((unsigned long long int)(history_batch+1))*(histories_per_thread*LEAP_DISTANCE);
y = 1;
z = a2_RANECU;
for(;;)
{
// (A2) Halve n, and store the integer part and the residue
if (0!=(leap&01)) // (bit-wise operation for MOD(leap,2), or leap%2 ==> proceed if leap is an odd number) Equivalent: t=(short)(leap%2);
{
leap >>= 1; // Halve n moving the bits 1 position right. Equivalent to: leap=(leap/2);
y = abMODm(m2_RANECU,z,y); // (A3) Multiply y by z: y = [z*y] MOD m
if (0==leap) break; // (A4) leap==0? ==> finish
}
else // (leap is even)
{
leap>>= 1; // Halve leap moving the bits 1 position right. Equivalent to: leap=(leap/2);
}
z = abMODm(m2_RANECU,z,z); // (A5) Square z: z = [z*z] MOD m
}
// AjMODm2 = y;
seed->y = abMODm(m2_RANECU, seed_input, y); // Using the input seed as the starting seed
}
/////////////////////////////////////////////////////////////////////
//! Calculate "(a1*a2) MOD m" with 32-bit integers and avoiding
//! the possible overflow, using the Russian Peasant approach
//! modulo m and the approximate factoring method, as described
//! in: L'Ecuyer and Cote, ACM Trans. Math. Soft. 17 (1991).
//!
//! This function has been adapted from "seedsMLCG.f", see:
//! Badal and Sempau, Computer Physics Communications 175 (2006)
//!
//! @param[in] m,a,s MLCG parameters
//! @return (a1*a2) MOD m
//
// Input: 0 < a1 < m
// 0 < a2 < m
//
// Return value: (a1*a2) MOD m
//
/////////////////////////////////////////////////////////////////////
__device__ __host__ inline int abMODm(int m, int a, int s)
{
// CAUTION: the input parameters are modified in the function but should not be returned to the calling function! (pass by value!)
int q, k;
int p = -m; // p is always negative to avoid overflow when adding
// ** Apply the Russian peasant method until "a =< 32768":
while (a>32768) // We assume '32' bit integers (4 bytes): 2^(('32'-2)/2) = 32768
{
if (0!=(a&1)) // Store 's' when 'a' is odd Equivalent code: if (1==(a%2))
{
p += s;
if (p>0) p -= m;
}
a >>= 1; // Half a (move bits 1 position right) Equivalent code: a = a/2;
s = (s-m) + s; // Double s (MOD m)
if (s<0) s += m; // (s is always positive)
}
// ** Employ the approximate factoring method (a is small enough to avoid overflow):
q = (int) m / a;
k = (int) s / q;
s = a*(s-k*q)-k*(m-q*a);
while (s<0)
s += m;
// ** Compute the final result:
p += s;
if (p<0) p += m;
return p;
}
////////////////////////////////////////////////////////////////////////////////
//! Pseudo-random number generator (PRNG) RANECU returning a float value
//! (single precision version).
//!
//! @param[in,out] seed PRNG seed (seed kept in the calling function and updated here).
//! @return PRN double value in the open interval (0,1)
//!
////////////////////////////////////////////////////////////////////////////////
__device__ inline float ranecu(int2* seed)
{
int i1 = (int)(seed->x/53668);
seed->x = 40014*(seed->x-i1*53668)-i1*12211;
int i2 = (int)(seed->y/52774);
seed->y = 40692*(seed->y-i2*52774)-i2*3791;
if (seed->x < 0) seed->x += 2147483563;
if (seed->y < 0) seed->y += 2147483399;
i2 = seed->x-seed->y;
if (i2 < 1) i2 += 2147483562;
return (__int2float_rn(i2)*4.65661305739e-10f); // 4.65661305739e-10 == 1/2147483563
}
////////////////////////////////////////////////////////////////////////////////
//! Pseudo-random number generator (PRNG) RANECU returning a double value.
////////////////////////////////////////////////////////////////////////////////
__device__ inline double ranecu_double(int2* seed)
{
int i1 = (int)(seed->x/53668);
seed->x = 40014*(seed->x-i1*53668)-i1*12211;
int i2 = (int)(seed->y/52774);
seed->y = 40692*(seed->y-i2*52774)-i2*3791;
if (seed->x < 0) seed->x += 2147483563;
if (seed->y < 0) seed->y += 2147483399;
i2 = seed->x-seed->y;
if (i2 < 1) i2 += 2147483562;
return (__int2double_rn(i2)*4.6566130573917692e-10);
}
////////////////////////////////////////////////////////////////////////////////
__host__ inline double ranecu_double_CPU(int2* seed)
{
int i1 = (int)(seed->x/53668);
seed->x = 40014*(seed->x-i1*53668)-i1*12211;
int i2 = (int)(seed->y/52774);
seed->y = 40692*(seed->y-i2*52774)-i2*3791;
if (seed->x < 0) seed->x += 2147483563;
if (seed->y < 0) seed->y += 2147483399;
i2 = seed->x-seed->y;
if (i2 < 1) i2 += 2147483562;
return ((double)(i2)*4.6566130573917692e-10);
}
////////////////////////////////////////////////////////////////////////////////
//! Find the voxel that contains the current position.
//! Report the voxel absolute index and the x,y,z indices.
//! The structure containing the voxel number and size is read from CONSTANT memory.
//!
//! @param[in] position Particle position
//! @param[out] voxel_coord Pointer to three integer values (short3*) that will store the x,y and z voxel indices.
//! @return Returns "absvox", the voxel number where the particle is
//! located (negative if position outside the voxel bbox).
//!
////////////////////////////////////////////////////////////////////////////////
__device__ inline unsigned int locate_voxel(float3 p, short3* voxel_coord)
{
p.x -= voxel_data_CONST.offset.x; // Translate the coordinate system to a reference where the voxel's lower back corner is at the origin
p.y -= voxel_data_CONST.offset.y;
p.z -= voxel_data_CONST.offset.z;
if ( (p.y < EPS) || (p.y > (voxel_data_CONST.size_bbox.y-EPS)) ||
(p.x < EPS) || (p.x > (voxel_data_CONST.size_bbox.x-EPS)) ||
(p.z < EPS) || (p.z > (voxel_data_CONST.size_bbox.z-EPS)) )
{
// -- Particle escaped the voxelized geometry:
return FLAG_OUTSIDE_VOXELS; // OLD CODE: return -1; !!DBTv1.4!!
}
// -- Particle inside the voxelized geometry, find current voxel:
// The truncation from float to integer could give troubles for negative coordinates but this will never happen thanks to the IF at the begining of this function.
// (no need to use the CUDA function to convert float to integer rounding down (towards minus infinite): __float2int_rd)
register int voxel_coord_x, voxel_coord_y, voxel_coord_z;
voxel_coord_x = __float2int_rd(p.x * voxel_data_CONST.inv_voxel_size.x);
voxel_coord_y = __float2int_rd(p.y * voxel_data_CONST.inv_voxel_size.y);
voxel_coord_z = __float2int_rd(p.z * voxel_data_CONST.inv_voxel_size.z);
voxel_coord->x = (short int) voxel_coord_x; // Output the voxel coordinates as short int (2 bytes) instead of int (4 bytes) to save registers; avoid type castings in the calculation of the return value.
voxel_coord->y = (short int) voxel_coord_y;
voxel_coord->z = (short int) voxel_coord_z;
return ((unsigned int)(voxel_coord_x + voxel_coord_y*(voxel_data_CONST.num_voxels.x)) + ((unsigned int)voxel_coord_z)*(voxel_data_CONST.num_voxels.x)*(voxel_data_CONST.num_voxels.y));
}
//////////////////////////////////////////////////////////////////////
//! Rotates a vector; the rotation is specified by giving
//! the polar and azimuthal angles in the "self-frame", as
//! determined by the vector to be rotated.
//! This function is a literal translation from Fortran to C of
//! PENELOPE (v. 2006) subroutine "DIRECT".
//!
//! @param[in,out] (u,v,w) input vector (=d) in the lab. frame; returns the rotated vector components in the lab. frame
//! @param[in] costh cos(theta), angle between d before and after turn
//! @param[in] phi azimuthal angle (rad) turned by d in its self-frame
//
// Output:
// (u,v,w) -> rotated vector components in the lab. frame
//
// Comments:
// -> (u,v,w) should have norm=1 on input; if not, it is
// renormalized on output, provided norm>0.
// -> The algorithm is based on considering the turned vector
// d' expressed in the self-frame S',
// d' = (sin(th)cos(ph), sin(th)sin(ph), cos(th))
// and then apply a change of frame from S' to the lab
// frame. S' is defined as having its z' axis coincident
// with d, its y' axis perpendicular to z and z' and its
// x' axis equal to y'*z'. The matrix of the change is then
// / uv/rho -v/rho u \
// S ->lab: | vw/rho u/rho v | , rho=(u^2+v^2)^0.5
// \ -rho 0 w /
// -> When rho=0 (w=1 or -1) z and z' are parallel and the y'
// axis cannot be defined in this way. Instead y' is set to
// y and therefore either x'=x (if w=1) or x'=-x (w=-1)
//////////////////////////////////////////////////////////////////////
__device__ inline void rotate_double(float3* direction, double costh, double phi) // The direction vector is single precision but the rotation is performed in double precision for increased accuracy.
{
double DXY, NORM, cosphi, sinphi, SDT;
DXY = direction->x*direction->x + direction->y*direction->y;
sincos(phi, &sinphi,&cosphi); // Calculate the SIN and COS at the same time. sinphi = sin(phi); cosphi = cos(phi);
// **** Ensure normalisation
NORM = DXY + direction->z*direction->z; // !!DeBuG!! Check if it is really necessary to renormalize in a real simulation!!
if (fabs(NORM-1.0)>1.0e-14)
{
NORM = rsqrt(NORM);
direction->x = NORM*direction->x;
direction->y = NORM*direction->y;
direction->z = NORM*direction->z;
DXY = direction->x*direction->x + direction->y*direction->y;
}
if (DXY>1.0e-28)
{
SDT = sqrt((1.0-costh*costh)/DXY);
float direction_x_in = direction->x;
direction->x = direction->x*costh + SDT*(direction_x_in*direction->z*cosphi-direction->y*sinphi);
direction->y = direction->y*costh+SDT*(direction->y*direction->z*cosphi+direction_x_in*sinphi);
direction->z = direction->z*costh-DXY*SDT*cosphi;
}
else
{
SDT = sqrt(1.0-costh*costh);
direction->y = SDT*sinphi;
if (direction->z>0.0)
{
direction->x = SDT*cosphi;
direction->z = costh;
}
else
{
direction->x =-SDT*cosphi;
direction->z =-costh;
}
}
}
//////////////////////////////////////////////////////////////////////
// ***********************************************************************
// * Translation of PENELOPE's "SUBROUTINE GRAa" from FORTRAN77 to C *
// ***********************************************************************
//! Sample a Rayleigh interaction using the sampling algorithm
//! used in PENELOPE 2006.
//!
//! @param[in] energy Particle energy (not modified with Rayleigh)