-
Notifications
You must be signed in to change notification settings - Fork 0
/
MC-GPU_kernel_v1.3_PCD.cu
1720 lines (1463 loc) · 94.2 KB
/
MC-GPU_kernel_v1.3_PCD.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 PCD ***
// ****************************
//
//! Definition of the CUDA GPU kernel for the simulation of x ray tracks in a voxelized geometry.
//! This kernel has been optimized to yield a good performance in the GPU but can still be
//! compiled in the CPU without problems. All the CUDA especific commands are enclosed in
//! pre-processor directives that are skipped if the parameter "USING_CUDA" is not defined
//! at compilation time.
//
// ** 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.3_PCD.cu
//! @author Preetham Rudrarju [Preetham.Rudraraju(at)fda.hhs.gov]
//! Andreu Badal [Andreu.Badal-Soler@fda.hhs.gov]
//! Bahaa Ghammraoui [Bahaa.Ghammraoui(at)fda.hhs.gov]
//! @date 02/01/2024
// -- Original code started on: 12/12/2012
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//! 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).
////////////////////////////////////////////////////////////////////////////////
/**********************************************************************************************************************
!!!!! v1.3_PCD modifications - Due to the modifications in the image structure and how we have to account for the !!!!!
!!!!! number of energy bins for each pixel, a for loop was required to traverse through evervy Nbin !!!!!
!!!!! copy for each pixel. !!!!!
***********************************************************************************************************************/
#ifdef USING_CUDA
__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) {
for (int x = 0; x < Nbin_CONST; x++) { //!!! Preetham's Pctk changes : Traversing through the pixel's copies in each Nbin
// -- 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
my_pixel += pixels_per_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
// }
// }
#endif
/********************************************************************************************************************
!!!!! v1.3_PCD modifications - Using the CUDA constants allocated during the simulation in tally_image function !!!!!
*********************************************************************************************************************/
////////////////////////////////////////////////////////////////////////////////
//! 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,
//! detector_data_CONST,
//! mfp_table_data_CONST,
//! Nbin_CONST,
//! Emin_CONST,
//! Emax_CONST,
//! bytes_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.
////////////////////////////////////////////////////////////////////////////////
#ifdef USING_CUDA
__global__ void track_particles(int histories_per_thread,
int num_p, // For a CT simulation: allocate space for up to MAX_NUM_PROJECTIONS projections.
int seed_input,
unsigned long long int* image,
ulonglong2* voxels_Edep,
float2* voxel_mat_dens,
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)
#else
void track_particles(int history_batch, // This variable is not required in the GPU, it uses the thread ID
int histories_per_thread,
int num_p,
int seed_input,
unsigned long long int* image,
ulonglong2* voxels_Edep,
float2* voxel_mat_dens,
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)
#endif
{
// -- 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.
#ifdef USING_CUDA
__shared__
#endif
struct compton_struct cgco_SHARED;
#ifdef USING_CUDA
__shared__
#endif
struct detector_struct detector_data_SHARED;
#ifdef USING_CUDA
__shared__
#endif
struct source_struct source_data_SHARED;
#ifdef USING_CUDA
if (0==threadIdx.x) // First GPU thread copies the variables to shared memory
{
#endif
// -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;
#ifdef USING_CUDA
}
__syncthreads(); // Make sure all threads will see the initialized shared variable
#endif
// -- Initialize the RANECU generator in a position far away from the previous history:
#ifdef USING_CUDA
init_PRNG((threadIdx.x + blockIdx.x*blockDim.x), histories_per_thread, seed_input, &seed); // Using a 1D block
#else
init_PRNG(history_batch, histories_per_thread, seed_input, &seed);
#endif
// -- 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.
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).
#ifdef USING_CUDA
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)
#else
index = (int)((energy-mfp_table_data_CONST.e0)*mfp_table_data_CONST.ide + 0.00001f); // Adding EPSILON to truncate to INT towards minus infinite. There may be a small error for energy<=mfp_table_data_CONST.e0 but this case is irrelevant (particles will always have more energy than e0).
#endif
// -- 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<0) // !!DeBuG!! MC-GPU_v1.3 ==> if I move this "if" above the code runs much slower!? Why???
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: // !!DeBuG!!
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<0)
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'.
// -- 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 * 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<0)
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:
#ifdef USING_CUDA
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)
#else
index = (int)((energy-mfp_table_data_CONST.e0)*mfp_table_data_CONST.ide + 0.00001f); // Adding EPSILON to truncate to INT
#endif
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
if (dose_ROI_x_max_CONST > -1)
tally_voxel_energy_deposition(&Edep, &voxel_coord, voxels_Edep);
}
// -- Break interaction loop for particles that have been absorved 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);
}
} // [Continue with a new history]
} // [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.
////////////////////////////////////////////////////////////////////////////////
#ifdef USING_CUDA
__device__
#endif
inline
void tally_voxel_energy_deposition(float* Edep, short3* voxel_coord, ulonglong2* voxels_Edep)
{
// !!DeBuG!! Maybe it would be faster to store a 6 element struct and save temp copy?? struct_short_int_x6_align16 dose_ROI_size = dose_ROI_size_CONST; // Get ROI coordinates from GPU constant memory and store temporal copy
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));
#ifdef USING_CUDA
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)
#else
voxels_Edep[num_voxel].x += (unsigned long long int)((*Edep)*SCALE_eV + 0.5f);
voxels_Edep[num_voxel].y += (unsigned long long int)((*Edep)*(*Edep) + 0.5f);
#endif
return;
}
////////////////////////////////////////////////////////////////////////////////
//! Tally a radiographic projection image.
//! This function is called whenever a particle escapes the voxelized volume.
//! The code checks if the particle would arrive at the detector if it kept
//! moving in a straight line after exiting the voxels (assuming vacuum enclosure).
//! An ideal image formation model is implemented: each pixel counts the total energy
//! of the x rays that enter the pixel (100% detection efficiency for any energy).
//! The image due to primaries and different kinds of scatter is tallied separately.
//!
//! In the GPU, and atomicAdd() function is used to make sure that multiple threads do
//! not update the same pixel at the same time, which would result in a lose of information.
//! Since the atomicAdd function is only available for 'unsigned long long int' data,
//! the float pixel values are scaled by a factor "SCALE_eV" defined in the header file
//! (eg, #define SCALE_eV 10000.0f) and stored as unsigned long long integers in main
//! memory.
//!
//! WARNING! If the total tallied signal (for all particles) is larger than "1.8e19/SCALE_eV",
//! there will be a bit overflow and the value will be reset to 0 giving bogus results.
//!
//! WARNING! The detector plane should be located outside the voxels bounding box. However, since
//! the particles are moved outside the bbox in the last step, they could cross the detector
//! plane anyway. If the particles are less than 2.0 cm behind the detector, they are moved
//! back and detected. Therefore the detector can be a few cm inside the bbox and still work.
//! If the Woodcock mean free path is larger than the distance from the bbox to the detector,
//! we may lose some particles behind the detector!
//!
//!
//! @param[in] energy X-ray energy
//! @param[in] position Particle position
//! @param[in] direction Particle direction (cosine vectors)
//! @param[in] scatter_state Flag marking primaries, single Compton, single Rayleigh or multiple scattered radiation
//! @param[out] image Integer array containing the image, ie, the pixel values (in tenths of meV)
////////////////////////////////////////////////////////////////////////////////
/*********************************************************************************************************************
!!!!! v1.3_PCD modifications - Added Photon Counting Detector Functionality and based on Nbin value, tally_image !!!!!
!!!!! can simulate both Energy Integrated Detector and Photon Counting Detector. !!!!!
**********************************************************************************************************************/
#ifdef USING_CUDA
__device__
#endif
inline void tally_image(float* energy, float3* position, float3* direction, signed char* scatter_state, unsigned long long int* image, struct source_struct* source_data_SHARED, struct detector_struct* detector_data_SHARED)
{
float dist_detector, rotated_position;
// !!! Preetham Pctk changes PctK :
// PctK add to image based on the EID or PctK mode
// In Pctk mode we count the number of photons instead
if (Nbin_CONST > 0)
{
if (detector_data_SHARED->rotation_flag == 1) // --> Initial source direction is not (0,1,0): detector has to be rotated to +Y to find the pixel number
{
// *** Skip particles not moving towards the detector.
// (a) Skip particles that were deflected more than 90 deg from the original source direction (backscatter).
// (b) Skip particles located more than 10 cm behind the detector
// (c) Skip particles for which the direction to the detector is way bigger than SDD (likely to intersect the plane outside the pixel region).
// !!DeBuG!! NOTE: This may give problems for big detectors very close to the source
// !!DeBuG!! Particles located after the detector will be moved back to the surface of the detector, but 10 cm maximum!!
// In this way the detector can intersect the voxels bbox or be located right on the surface of the bbox: the particles will be
// transported across the detector and until a little after the end of the bbox in the last step, but then moved back.
// This algorithm will give correct results ONLY when the detector intersects just slightly the air space around the phantom,
// so that the interactions after the detector are not significant (this happens sometimes using oblique beams).
// I could remove particles after the detector using "if (dist_detector<0.0f) return;".
// (a) Calculate the angle between the particle and the initial direction (dot product): reject particle if cos_angle < cos(89)==0 (angle>89deg):
// [Extra parenthesis are coded to suggest to the compiler the use of intrinsic multiply-add operations].
register float cos_angle = direction->x * source_data_SHARED->direction.x +
(direction->y * source_data_SHARED->direction.y +
(direction->z * source_data_SHARED->direction.z));
if (cos_angle < 0.025f)
return; // Reject particle: Angle larger than 89 deg --> particle moving parallel to the detector or backwards towards the source!
// (b) Find the distance from the current particle location (likely just after the surface of the voxel bbox) to the intersection with the detector plane:
dist_detector = ( source_data_SHARED->direction.x * (detector_data_SHARED->center.x - position->x) +
(source_data_SHARED->direction.y * (detector_data_SHARED->center.y - position->y) +
(source_data_SHARED->direction.z * (detector_data_SHARED->center.z - position->z))) ) / cos_angle;
// !!DeBuG!! IF's below (used in v1.2) are not needed when checking the x ray angle:
// if (dist_detector < -10.0f) // !!DeBuG!! Is 10 cm enough or too much? Should I use 0? or allow any distance?
// return; // !!DeBuG!! Reject particles located more than 10 cm behind the detector. 10 cm was selected arbitrarily. Woodcock MFP for x-rays in bone: MFP 200 keV photons in bone ==> 4 cm.
//
// if (fabsf(dist_detector)>(2.1f*detector_data_CONST.sdd))
// return; // Reject particle: distance to the detector plane too large, the particle is likely to travel almost parallel to the detector and will not be detected.
// *** Translate the particle to the detector plane (we assume the detector is completely absorbent: 100% detection efficiency):
position->x = position->x + dist_detector * direction->x;
position->y = position->y + dist_detector * direction->y;
position->z = position->z + dist_detector * direction->z;
// *** Rotate the particle position vector to the default reference system where the detector is perpendicular to the +Y axis, then find out if the particle is located inside a pixel:
#ifdef USING_CUDA
rotated_position = detector_data_SHARED->rot_inv[0]*position->x + detector_data_SHARED->rot_inv[1]*position->y + detector_data_SHARED->rot_inv[2]*position->z; // X coordinate
int pixel_coord_x = __float2int_rd((rotated_position - detector_data_SHARED->corner_min_rotated_to_Y.x) * detector_data_SHARED->inv_pixel_size_X); // Using CUDA intrinsic function to convert float to integer rounding down (towards minus infinite)
if ((pixel_coord_x>-1)&&(pixel_coord_x<detector_data_SHARED->num_pixels.x))
{
rotated_position = detector_data_SHARED->rot_inv[6]*position->x + detector_data_SHARED->rot_inv[7]*position->y + detector_data_SHARED->rot_inv[8]*position->z; // Z coordinate
int pixel_coord_z = __float2int_rd((rotated_position - detector_data_SHARED->corner_min_rotated_to_Y.z) * detector_data_SHARED->inv_pixel_size_Z);
if ((pixel_coord_z>-1)&&(pixel_coord_z<detector_data_SHARED->num_pixels.y) && ((*energy)*SCALE_eV >= (Emin_CONST)*100.0f ) && ((*energy)*SCALE_eV <= (Emax_CONST)*100.0f ) )
{
// -- Particle enters the detector! Tally the particle energy in the corresponding pixel (in tenths of meV):
// Using a CUDA atomic function (not available for global floats yet) to read and increase the pixel value in a single instruction, blocking interferences from other threads.
// The offset for the primaries or scatter images are calculated considering that:
// scatter_state=0 for non-scattered, =1 for Compton, =2 for Rayleigh, and =3 for multiple scatter.
atomicAdd( ( image + // Pointer to beginning of image array
(int)(*scatter_state) * detector_data_SHARED->total_num_pixels + // Offset to corresponding scatter image
((int)((*energy - Emin_CONST)/((Emax_CONST - Emin_CONST)/Nbin_CONST))) * (detector_data_SHARED->total_num_pixels * 4) + // Offset to corresponding Nbin
(pixel_coord_x + pixel_coord_z*(detector_data_SHARED->num_pixels.x)) // Offset to corresponding image pixel
),
__float2ull_rn(1.0)); // Energy arriving at the pixel, scaled by the factor SCALE_eV and rounded.
}
}
#else
// CPU version (not using CUDA intrinsics: atomicAdd, fast type casting)
rotated_position = detector_data_SHARED->rot_inv[0]*position->x + detector_data_SHARED->rot_inv[1]*position->y + detector_data_SHARED->rot_inv[2]*position->z; // X coordinate
float pixel_coord_x = floor((rotated_position - detector_data_SHARED->corner_min_rotated_to_Y.x)*detector_data_SHARED->inv_pixel_size_X); // Using float+floor instead of INT to avoid truncation errors for positive and negative values
if ( (pixel_coord_x>-0.1f) && (pixel_coord_x<(detector_data_SHARED->num_pixels.x-0.1f)) ) // Rejecting values negative or bigger than the image size
{
rotated_position = detector_data_SHARED->rot_inv[6]*position->x + detector_data_SHARED->rot_inv[7]*position->y + detector_data_SHARED->rot_inv[8]*position->z; // Z coordinate
float pixel_coord_z = floor((rotated_position - detector_data_SHARED->corner_min_rotated_to_Y.z)*detector_data_SHARED->inv_pixel_size_Z);
if ( (pixel_coord_z>-0.1f) && (pixel_coord_z<(detector_data_SHARED->num_pixels.y-0.1f)) && ((*energy)*SCALE_eV >= (Emin_CONST)*100.0f ) && ((*energy)*SCALE_eV <= (Emax_CONST)*100.0f ))
image[(int)(((float)*scatter_state)*detector_data_SHARED->total_num_pixels + pixel_coord_x + pixel_coord_z*detector_data_SHARED->num_pixels.x + ((int)((*energy - Emin_CONST)/((Emax_CONST - Emin_CONST)/Nbin_CONST))) * (detector_data_SHARED->total_num_pixels * 4) 0.0001f)]
+= (unsigned long long int)(1.0); // Tally the particle energy in the pixel. This instruction is not thread-safe, but it is ok in sequential CPU code.
}
#endif
}
else // (detector_data_SHARED->rotation_flag != 1) --> Initial source direction is (0,1,0): pixel number and distance can be found easily
{
if (direction->y < 0.0001f)
return; // *** Reject particles not moving towards the detector plane at +Y.
dist_detector = (detector_data_SHARED->center.y - position->y)/(direction->y); // Distance to the intersection with the detector at +Y.
// !!DeBuG!! IF below (v1.2) not needed when checking the angle
// if (dist_detector>(2.1f*detector_data_SHARED->sdd)) return;
#ifdef USING_CUDA
int pixel_coord_x = __float2int_rd((position->x + dist_detector*direction->x - detector_data_SHARED->corner_min_rotated_to_Y.x)*detector_data_SHARED->inv_pixel_size_X);
if ((pixel_coord_x>-1)&&(pixel_coord_x<detector_data_SHARED->num_pixels.x))
{
int pixel_coord_z = __float2int_rd((position->z + dist_detector*direction->z - detector_data_SHARED->corner_min_rotated_to_Y.z)*detector_data_SHARED->inv_pixel_size_Z);
if ((pixel_coord_z>-1)&&(pixel_coord_z<detector_data_SHARED->num_pixels.y) && ((*energy)*SCALE_eV >= (Emin_CONST)*100.0f ) && ((*energy)*SCALE_eV <= (Emax_CONST)*100.0f )) //check if within EMin and EMax levels
{
atomicAdd( ( image + // Pointer to beginning of image array
(int)(*scatter_state) * detector_data_SHARED->total_num_pixels + // Offset to corresponding scatter image
((int)((*energy - Emin_CONST)/((Emax_CONST - Emin_CONST)/Nbin_CONST))) * (detector_data_SHARED->total_num_pixels * 4) + // Offset to corresponding Nbin
(pixel_coord_x + pixel_coord_z*(detector_data_SHARED->num_pixels.x)) // Offset to corresponding image pixel
),
__float2ull_rn(1.0)); // Energy arriving at the pixel, scaled by the factor SCALE_eV and rounded.
}
}
#else
// --Calculate the pixel the xray enters, truncating towards minus infinite and making sure the conversion to int is safe:
float pixel_coord_x = floor((position->x + dist_detector*direction->x - detector_data_SHARED->corner_min_rotated_to_Y.x)*detector_data_SHARED->inv_pixel_size_X);
if ( (pixel_coord_x>-0.1f) && (pixel_coord_x<(detector_data_SHARED->num_pixels.x-0.1f)) )
{
float pixel_coord_z = floor((position->z + dist_detector*direction->z - detector_data_SHARED->corner_min_rotated_to_Y.z)*detector_data_SHARED->inv_pixel_size_Z);
if ( (pixel_coord_z>-0.1f) && (pixel_coord_z<(detector_data_SHARED->num_pixels.y-0.1f)) && ((*energy)*SCALE_eV >= (Emin_CONST)*100.0f ) && ((*energy)*SCALE_eV <= (Emax_CONST)*100.0f ))
image[(int)(((float)*scatter_sFEtate)*detector_data_SHARED->total_num_pixels + pixel_coord_x + pixel_coord_z*detector_data_SHARED->num_pixels.x + ((int)((*energy - Emin_CONST)/((Emax_CONST - Emin_CONST)/Nbin_CONST))) * (detector_data_SHARED->total_num_pixels * 4) + 0.0001f)]
+= (unsigned long long int)(1.0); // Truncate the pixel number to INT and round the energy value
}
#endif
}
}
// !!!!! Energy Integrated Detector !!!!!
else
{
if (detector_data_SHARED->rotation_flag == 1) // --> Initial source direction is not (0,1,0): detector has to be rotated to +Y to find the pixel number
{
// *** Skip particles not moving towards the detector.
// (a) Skip particles that were deflected more than 90 deg from the original source direction (backscatter).
// (b) Skip particles located more than 10 cm behind the detector
// (c) Skip particles for which the direction to the detector is way bigger than SDD (likely to intersect the plane outside the pixel region).
// !!DeBuG!! NOTE: This may give problems for big detectors very close to the source
// !!DeBuG!! Particles located after the detector will be moved back to the surface of the detector, but 10 cm maximum!!
// In this way the detector can intersect the voxels bbox or be located right on the surface of the bbox: the particles will be
// transported across the detector and until a little after the end of the bbox in the last step, but then moved back.
// This algorithm will give correct results ONLY when the detector intersects just slightly the air space around the phantom,
// so that the interactions after the detector are not significant (this happens sometimes using oblique beams).
// I could remove particles after the detector using "if (dist_detector<0.0f) return;".
// (a) Calculate the angle between the particle and the initial direction (dot product): reject particle if cos_angle < cos(89)==0 (angle>89deg):
// [Extra parenthesis are coded to suggest to the compiler the use of intrinsic multiply-add operations].
register float cos_angle = direction->x * source_data_SHARED->direction.x +
(direction->y * source_data_SHARED->direction.y +
(direction->z * source_data_SHARED->direction.z));
if (cos_angle < 0.025f)
return; // Reject particle: Angle larger than 89 deg --> particle moving parallel to the detector or backwards towards the source!
// (b) Find the distance from the current particle location (likely just after the surface of the voxel bbox) to the intersection with the detector plane:
dist_detector = ( source_data_SHARED->direction.x * (detector_data_SHARED->center.x - position->x) +
(source_data_SHARED->direction.y * (detector_data_SHARED->center.y - position->y) +
(source_data_SHARED->direction.z * (detector_data_SHARED->center.z - position->z))) ) / cos_angle;
// !!DeBuG!! IF's below (used in v1.2) are not needed when checking the x ray angle:
// if (dist_detector < -10.0f) // !!DeBuG!! Is 10 cm enough or too much? Should I use 0? or allow any distance?
// return; // !!DeBuG!! Reject particles located more than 10 cm behind the detector. 10 cm was selected arbitrarily. Woodcock MFP for x-rays in bone: MFP 200 keV photons in bone ==> 4 cm.
//
// if (fabsf(dist_detector)>(2.1f*detector_data_CONST.sdd))
// return; // Reject particle: distance to the detector plane too large, the particle is likely to travel almost parallel to the detector and will not be detected.
// *** Translate the particle to the detector plane (we assume the detector is completely absorbent: 100% detection efficiency):
position->x = position->x + dist_detector * direction->x;
position->y = position->y + dist_detector * direction->y;
position->z = position->z + dist_detector * direction->z;
// *** Rotate the particle position vector to the default reference system where the detector is perpendicular to the +Y axis, then find out if the particle is located inside a pixel:
#ifdef USING_CUDA
rotated_position = detector_data_SHARED->rot_inv[0]*position->x + detector_data_SHARED->rot_inv[1]*position->y + detector_data_SHARED->rot_inv[2]*position->z; // X coordinate
int pixel_coord_x = __float2int_rd((rotated_position - detector_data_SHARED->corner_min_rotated_to_Y.x) * detector_data_SHARED->inv_pixel_size_X); // Using CUDA intrinsic function to convert float to integer rounding down (towards minus infinite)
if ((pixel_coord_x>-1)&&(pixel_coord_x<detector_data_SHARED->num_pixels.x))
{
rotated_position = detector_data_SHARED->rot_inv[6]*position->x + detector_data_SHARED->rot_inv[7]*position->y + detector_data_SHARED->rot_inv[8]*position->z; // Z coordinate
int pixel_coord_z = __float2int_rd((rotated_position - detector_data_SHARED->corner_min_rotated_to_Y.z) * detector_data_SHARED->inv_pixel_size_Z);
if ((pixel_coord_z>-1)&&(pixel_coord_z<detector_data_SHARED->num_pixels.y))
{
// -- Particle enters the detector! Tally the particle energy in the corresponding pixel (in tenths of meV):
// Using a CUDA atomic function (not available for global floats yet) to read and increase the pixel value in a single instruction, blocking interferences from other threads.
// The offset for the primaries or scatter images are calculated considering that:
// scatter_state=0 for non-scattered, =1 for Compton, =2 for Rayleigh, and =3 for multiple scatter.
atomicAdd(( image + // Pointer to beginning of image array
(int)(*scatter_state) * detector_data_SHARED->total_num_pixels + // Offset to corresponding scatter image
(pixel_coord_x + pixel_coord_z*(detector_data_SHARED->num_pixels.x)) ), // Offset to the corresponding pixel
__float2ull_rn((*energy)*SCALE_eV) ); // Energy arriving at the pixel, scaled by the factor SCALE_eV and rounded.
// The maximum unsigned long long int value is ~1.8e19:
}
}
#else
// CPU version (not using CUDA intrinsics: atomicAdd, fast type casting)
rotated_position = detector_data_SHARED->rot_inv[0]*position->x + detector_data_SHARED->rot_inv[1]*position->y + detector_data_SHARED->rot_inv[2]*position->z; // X coordinate
float pixel_coord_x = floor((rotated_position - detector_data_SHARED->corner_min_rotated_to_Y.x)*detector_data_SHARED->inv_pixel_size_X); // Using float+floor instead of INT to avoid truncation errors for positive and negative values
if ( (pixel_coord_x>-0.1f) && (pixel_coord_x<(detector_data_SHARED->num_pixels.x-0.1f)) ) // Rejecting values negative or bigger than the image size
{
rotated_position = detector_data_SHARED->rot_inv[6]*position->x + detector_data_SHARED->rot_inv[7]*position->y + detector_data_SHARED->rot_inv[8]*position->z; // Z coordinate
float pixel_coord_z = floor((rotated_position - detector_data_SHARED->corner_min_rotated_to_Y.z)*detector_data_SHARED->inv_pixel_size_Z);
if ( (pixel_coord_z>-0.1f) && (pixel_coord_z<(detector_data_SHARED->num_pixels.y-0.1f)) )
image[(int)(((float)*scatter_state)*detector_data_SHARED->total_num_pixels + pixel_coord_x + pixel_coord_z*detector_data_SHARED->num_pixels.x + 0.0001f)]
+= (unsigned long long int)((*energy)*SCALE_eV + 0.5f); // Tally the particle energy in the pixel. This instruction is not thread-safe, but it is ok in sequential CPU code.
}
#endif
}
else // (detector_data_SHARED->rotation_flag != 1) --> Initial source direction is (0,1,0): pixel number and distance can be found easily
{
if (direction->y < 0.0001f)
return; // *** Reject particles not moving towards the detector plane at +Y.
dist_detector = (detector_data_SHARED->center.y - position->y)/(direction->y); // Distance to the intersection with the detector at +Y.
// !!DeBuG!! IF below (v1.2) not needed when checking the angle
// if (dist_detector>(2.1f*detector_data_SHARED->sdd)) return;
#ifdef USING_CUDA
int pixel_coord_x = __float2int_rd((position->x + dist_detector*direction->x - detector_data_SHARED->corner_min_rotated_to_Y.x)*detector_data_SHARED->inv_pixel_size_X);
if ((pixel_coord_x>-1)&&(pixel_coord_x<detector_data_SHARED->num_pixels.x))
{
int pixel_coord_z = __float2int_rd((position->z + dist_detector*direction->z - detector_data_SHARED->corner_min_rotated_to_Y.z)*detector_data_SHARED->inv_pixel_size_Z);
if ((pixel_coord_z>-1)&&(pixel_coord_z<detector_data_SHARED->num_pixels.y))
atomicAdd( ( image + // Pointer to beginning of image array
(int)(*scatter_state) * detector_data_SHARED->total_num_pixels + // Offset to corresponding scatter image
(pixel_coord_x + pixel_coord_z*(detector_data_SHARED->num_pixels.x)) ), // Offset to the corresponding pixel
__float2ull_rn((*energy)*SCALE_eV) ); // Energy arriving at the pixel, scaled by the factor SCALE_eV and rounded.
}
#else
// --Calculate the pixel the xray enters, truncating towards minus infinite and making sure the conversion to int is safe:
float pixel_coord_x = floor((position->x + dist_detector*direction->x - detector_data_SHARED->corner_min_rotated_to_Y.x)*detector_data_SHARED->inv_pixel_size_X);
if ( (pixel_coord_x>-0.1f) && (pixel_coord_x<(detector_data_SHARED->num_pixels.x-0.1f)) )
{
float pixel_coord_z = floor((position->z + dist_detector*direction->z - detector_data_SHARED->corner_min_rotated_to_Y.z)*detector_data_SHARED->inv_pixel_size_Z);
if ( (pixel_coord_z>-0.1f) && (pixel_coord_z<(detector_data_SHARED->num_pixels.y-0.1f)) )
image[(int)(((float)*scatter_state)*detector_data_SHARED->total_num_pixels + pixel_coord_x + pixel_coord_z*detector_data_SHARED->num_pixels.x + 0.0001f)]
+= (unsigned long long int)((*energy)*SCALE_eV + 0.5f); // Truncate the pixel number to INT and round the energy value
}
#endif
}
}
}
////////////////////////////////////////////////////////////////////////////////
//! 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).
////////////////////////////////////////////////////////////////////////////////
#ifdef USING_CUDA
__device__
#endif
inline void source(float3* position, float3* direction, float* energy, int2* seed, 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):
#ifdef USING_CUDA
int int_part = __float2int_rd(RN); // -- Integer part (round down)
#else
int int_part = (int)(RN);
#endif
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]);
// *** 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;
#ifdef USING_CUDA
sincos(phi_sampled, &sinphi_sampled,&cosphi_sampled); // Calculate the SIN and COS at the same time.
#else
sinphi_sampled = sin(phi_sampled); // Some CPU compilers will be able to use "sincos", but let's be safe.
cosphi_sampled = cos(phi_sampled);
#endif
direction->y = sin_theta_sampled * sinphi_sampled;
direction->x = sin_theta_sampled * cosphi_sampled;
}
while( fabsf(direction->z/(direction->y+1.0e-7f)) > source_data_SHARED->max_height_at_y1cm ); // !!DeBuG!! Force square field for any phi by rejection sampling!! Is it necessary to use the "+1.0e-7f" to prevent possible division by zero???
if (detector_data_SHARED->rotation_flag == 1)
{
// --Initial beam not pointing to (0,1,0), apply rotation:
register float direction_x_tmp = direction->x;
register float direction_y_tmp = direction->y;
direction->x = source_data_SHARED->rot_fan[0]*direction_x_tmp + source_data_SHARED->rot_fan[1]*direction_y_tmp + source_data_SHARED->rot_fan[2]*direction->z;
direction->y = source_data_SHARED->rot_fan[3]*direction_x_tmp + source_data_SHARED->rot_fan[4]*direction_y_tmp + source_data_SHARED->rot_fan[5]*direction->z;
direction->z = source_data_SHARED->rot_fan[6]*direction_x_tmp + source_data_SHARED->rot_fan[7]*direction_y_tmp + source_data_SHARED->rot_fan[8]*direction->z;
}
// Initialize x ray position at focal spot before translation into bbox. Particle stays in focal spot if no interaction found:
position->x = source_data_SHARED->position.x;
position->y = source_data_SHARED->position.y;
position->z = source_data_SHARED->position.z;
move_to_bbox(position, direction, voxel_data_CONST.size_bbox, absvox); // Move the particle inside the voxel bounding box.
}
////////////////////////////////////////////////////////////////////////////////
//! Functions that moves a particle inside 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 assumtions:
//! - 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[out] size_bbox Size of the bounding box.
////////////////////////////////////////////////////////////////////////////////
#ifdef USING_CUDA
__device__
#endif
inline void move_to_bbox(float3* position, float3* direction, float3 size_bbox, 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 > 0.0f) // The input position must correspond to the focal spot => position->y == source_data_CONST.position[*num_p].y
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 + (-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 < size_bbox.y)
dist_y = 0.0f; // No intersection with this plane
else
dist_y = EPS_SOURCE + (size_bbox.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 > 0.0f)
dist_x = 0.0f;
else
dist_x = EPS_SOURCE + (-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 < size_bbox.x)
dist_x = 0.0f;
else
dist_x = EPS_SOURCE + (size_bbox.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 > 0.0f)
dist_z = 0.0f;
else
dist_z = EPS_SOURCE + (-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 < size_bbox.z)
dist_z = 0.0f;
else
dist_z = EPS_SOURCE + (size_bbox.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):
position->x += dist_z * direction->x;
position->y += dist_z * direction->y;
position->z += dist_z * direction->z;
// Check if the new position is outside the bbox. If true, the particle must be moved back to the focal spot location:
if ( (position->x < 0.0f) || (position->x > size_bbox.x) ||
(position->y < 0.0f) || (position->y > size_bbox.y) ||
(position->z < 0.0f) || (position->z > size_bbox.z) )
{
position->x -= dist_z * direction->x; // Reject new position undoing the previous translation
position->y -= dist_z * direction->y;
position->z -= dist_z * direction->z;
(*intersection_flag) = -111; // Particle outside the bbox AND not pointing to the bbox: set absvox<0 to skip interaction sampling.
}
}
////////////////////////////////////////////////////////////////////////////////
//! Upper limit of the number of random values sampled in a single track.
#define LEAP_DISTANCE 256
//! 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.
//!
////////////////////////////////////////////////////////////////////////////////
#ifdef USING_CUDA
__device__
#endif
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: