-
Notifications
You must be signed in to change notification settings - Fork 5
/
chap2.tex
2344 lines (2147 loc) · 118 KB
/
chap2.tex
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
\chapter{The OpenMC Monte Carlo Code}
\label{chap:openmc}
\section{Background}
As part of the present work, a general Monte Carlo neutron transport code was
written from scratch with an emphasis on high performance algorithms, accurate
physics capabilities, and modern software design practices. The development of
the code started as a simplistic platform, first mentioned in
\cite{lanl-romano-2009}, within which algorithms could be easily tested. At that
stage, the geometry was limited to a simple structured mesh and all physics were
based on predefined fictitious multigroup cross sections. However, as further
studies of domain and data decomposition schemes, described in
\autoref{chap:domain-decomp} and \autoref{chap:data-decomp}, demanded that
realistic geometry and physics be present, a decision was made to greatly expand
the capabilities of the code.
In early 2011, the geometry and physics from the original structured-mesh,
multigroup code was gutted and replaced with constructive solid geometry
routines and physics based on continuous-energy cross sections. In Fall 2011,
the input file format was changed to an XML format and a basic outline of the
current tally system was introduced. Since then, improvements have been made in
physics methods, geometry capabilities, and input/output options.
OpenMC has been described in a recent paper in \emph{Annals of Nuclear Energy}
\cite{ane-romano-2013}. Further developments will be reported in a paper at the
M\&C 2013 conference \cite{mc-romano-2013}.
\section{Overview of Program Flow}
OpenMC performs a Monte Carlo simulation one particle at a time --- at no point
is more than one particle being tracked on a single program
instance\footnote{When executed in parallel, each program instance tracks one
particle at a time.}. Before any particles are tracked, the problem must be
initialized. This involves the following steps:
\begin{itemize}
\item Read input files and build data structures for the geometry, materials,
tallies, and other associated variables.
\item Initialize the pseudorandom number generator.
\item Read ACE format cross sections specified in the problem.
\item If using a special energy grid treatment such as a union energy grid with
pointers, that must be initialized as well.
\item In a fixed source problem, source sites are sampled from the specified
source. In an eigenvalue problem, source sites are sampled from some initial
source distribution or from a source file. The source sites consist of
coordinates, a direction, and an energy.
\end{itemize}
Once initialization is complete, the actual transport simulation can
proceed. The life of a single particle will proceed as follows:
\begin{enumerate}
\item The particle's properties are initialized from a source site previously
sampled.
\item Based on the particle's coordinates, the current cell in which the
particle resides is determined.
\item The energy-dependent cross sections for the material that the particle is
currently in are determined. Note that this includes the total cross section,
which is not pre-calculated.
\item The distance to the nearest boundary of the particle's cell is determined
based on the bounding surfaces to the cell.
\item The distance to the next collision is sampled. If the total material
cross section is $\Sigma_t$, this can be shown to be
\begin{equation}
d = -\frac{\ln \xi}{\Sigma_t}
\end{equation}
where $\xi$ is a pseudorandom number sampled from a uniform distribution on
$[0,1)$.
\item If the distance to the nearest boundary is less than the distance to the
next collision, the particle is moved forward to this boundary. Then, the
process is repeated from step 2. If the distance to collision is closer than
the distance to the nearest boundary, then the particle will undergo a
collision. In either case, track-length estimate tallies are scored to based
on the closest distance.
\item The material at the collision site may consist of multiple
nuclides. First, the nuclide with which the collision will happen is sampled
based on the total cross sections. If the total cross section of material $i$
is $\Sigma_{t,i}$, then the probability that any nuclide is sampled is
\begin{equation}
P(i) = \frac{\Sigma_{t,i}}{\Sigma_t}.
\end{equation}
\item Once the specific nuclide is sampled, a reaction for that nuclide is
randomly sampled based on the microscopic cross sections. If the microscopic
cross section for some reaction $x$ is $\sigma_x$ and the total microscopic
cross section for the nuclide is $\sigma_t$, then the probability that
reaction $x$ will occur is
\begin{equation}
P(x) = \frac{\sigma_x}{\sigma_t}.
\end{equation}
\item If the sampled reaction is elastic or inelastic scattering, the outgoing
energy and angle is sampled from the appropriate distribution. Reactions of
type $(n,xn)$ are treated as scattering and the weight of the particle is
increased by the multiplicity of the reaction. The particle then continues
from step 3. If the reaction is absorption or fission, the particle dies and
if necessary, fission sites are created and stored in the fission bank.
\end{enumerate}
After all particles have been simulated, there are a few final tasks that must
be performed before the run is finished. This include the following:
\begin{itemize}
\item With the accumulated sum and sum of squares for each tally, the sample
mean and its variance is calculated.
\item All tallies and other results are written to disk.
\item If requested, a source file is written to disk.
\item All allocatable arrays are deallocated.
\end{itemize}
\section{Geometry}
\subsection{Constructive Solid Geometry}
OpenMC uses a technique known as constructive solid geometry (CSG) to build
arbitrarily complex three-dimensional models in Euclidean space. In a CSG model,
every unique object is described as the union, intersection, or difference of
\emph{half-spaces} created by bounding surfaces. Every surface divides all of
space into exactly two half-spaces. We can mathematically define a surface as a
collection of points that satisfy an equation of the form $f(x,y,z) = 0$ where
$f(x,y,z)$ is a given function. All coordinates for which $f(x,y,z) < 0$ are
referred to as the negative half-space (or simply the \emph{negative side}) and
coordinates for which $f(x,y,z) > 0$ are referred to as the positive half-space.
Let us take the example of a sphere centered at the point $(x_0,y_0,z_0)$
with radius $R$. One would normally write the equation of the sphere as
\begin{equation}
\label{eq:sphere-equation}
(x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = R^2.
\end{equation}
By subtracting the right-hand term from both sides of
\eqref{eq:sphere-equation}, we can then write the surface equation for the
sphere:
\begin{equation}
\label{eq:surface-equation-sphere}
f(x,y,z) = (x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 - R^2 = 0
\end{equation}
One can confirm that any point inside this sphere will correspond to
$f(x,y,z) < 0$ and any point outside the sphere will correspond to
$f(x,y,z) > 0$.
In OpenMC, every surface defined by the user is assigned an integer to uniquely
identify it. We can then refer to either of the two half-spaces created by a
surface by a combination of the unique ID of the surface and a positive/negative
sign. \autoref{fig:halfspace} shows an example of an ellipse with unique ID 1
dividing space into two half-spaces.
\begin{figure}[htb]
\centering
\includegraphics[width=3.0in]{figures/ch2/halfspace.pdf}
\caption{Example of an ellipse and its associated half-spaces.}
\label{fig:halfspace}
\end{figure}
References to half-spaces created by surfaces are used to define regions of
space of uniform composition, known as \emph{cells}. While some codes allow
regions to be defined by intersections, unions, and differences or half-spaces,
OpenMC is currently limited to cells defined only as intersections of
half-spaces. Thus, the specification of the cell must include a list of
half-space references whose intersection defines the region. The region is then
assigned a material defined elsewhere. \autoref{fig:union} shows an example of a
cell defined as the intersection of an ellipse and two planes.
\begin{figure}[htb]
\centering
\includegraphics[width=4.0in]{figures/ch2/union.pdf}
\caption{The shaded region represents a cell bounded by three surfaces.}
\label{fig:union}
\end{figure}
The ability to form regions based on bounding quadratic surfaces enables OpenMC
to model arbitrarily complex three-dimensional objects. In practice, one is
limited only by the different surface types available in
OpenMC. \autoref{tab:surfaces} lists the available surface types, the identifier
used to specify them in input files, the corresponding surface equation, and the
input parameters needed to fully define the surface.
\begin{table}[htb]
\caption{Surface types available in OpenMC.}
\label{tab:surfaces}
\footnotesize{
\begin{tabular}{c c c c}
\toprule
Surface & Identifier & Equation & Parameters \\
\midrule
Plane perpendicular to $x$-axis & x-plane & $x - x_0 = 0$ & $x_0$ \\
Plane perpendicular to $y$-axis & y-plane & $y - y_0 = 0$ & $y_0$ \\
Plane perpendicular to $z$-axis & z-plane & $z - z_0 = 0$ & $z_0$ \\
Arbitrary plane & plane & $Ax + By + Cy = D$ & $A \; B \; C \; D$ \\
Infinite cylinder parallel to $x$-axis & x-cylinder & $(y - y_0)^2 + (z -
z_0)^2 = R^2$ & $y_0 \; z_0 \; R$ \\
Infinite cylinder parallel to $y$-axis & y-cylinder & $(x - x_0)^2 + (z -
z_0)^2 = R^2$ & $x_0 \; z_0 \; R$ \\
Infinite cylinder parallel to $z$-axis & z-cylinder & $(x - x_0)^2 + (y -
y_0)^2 = R^2$ & $x_0 \; y_0 \; R$ \\
Sphere & sphere & $(x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = R^2$ & $x_0 \;
y_0 \; z_0 \; R$ \\
Cone parallel to the $x$-axis & x-cone & $(y - y_0)^2 + (z - z_0)^2 = R^2(x
- x_0)^2 $ & $x_0 \; y_0 \; z_0 \; R^2$ \\
Cone parallel to the $y$-axis & y-cone & $(x - x_0)^2 + (z - z_0)^2 = R^2(y
- y_0)^2 $ & $x_0 \; y_0 \; z_0 \; R^2$ \\
Cone parallel to the $z$-axis & z-cone & $(x - x_0)^2 + (y - y_0)^2 = R^2(z
- z_0)^2 $ & $x_0 \; y_0 \; z_0 \; R^2$ \\
\bottomrule
\end{tabular}
}
\end{table}
\subsubsection{Universes}
OpenMC supports universe-based geometry similar to the likes of MCNP
\cite{lanl-x5-2008} and Serpent \cite{vtt-leppanen-2007}. This capability
enables users to model any identical repeated structures once and then fill them
in various spots in the geometry. A prototypical example of a repeated structure
would be a fuel pin within a fuel assembly or a fuel assembly within a core.
Each cell in OpenMC can either be filled with a normal material or with a
universe. If the cell is filled with a universe, only the region of the universe
that is within the defined boundaries of the parent cell will be present in the
geometry. That is to say, even though a collection of cells in a universe may
extend to infinity, not all of the universe will be ``visible'' in the geometry
since it will be truncated by the boundaries of the cell that contains it.
When a cell is filled with a universe, it is possible to specify that the
universe filling the cell should be rotated and translated. This is done through
the \texttt{rotation} and \texttt{translation} attributes on a cell (note though
that these can only be specified on a cell that is filled with a universe, not a
material).
It is not necessary to use or assign universes in a geometry if there are no
repeated structures. Any cell in the geometry that is not assigned to a
specified universe is automatically part of the \emph{base universe} whose
coordinates are just the normal coordinates in Euclidean space.
\subsubsection{Lattices}
Often times, repeated structures in a geometry occur in a regular pattern such
as a rectangular or hexagonal lattice. In such a case, it would be cumbersome
for a user to have to define the boundaries of each of the cells to be filled
with a universe. Thus, OpenMC provides a lattice capability similar to that used
in MCNP and Serpent.
The implementation of lattices is similar in principle to universes --- instead
of a cell being filled with a universe, the user can specify that it is filled
with a finite lattice. The lattice is then defined by a two-dimensional array of
universes that are to fill each position in the lattice. A good example of the
use of lattices and universes can be seen in the OpenMC model for the Monte
Carlo Performance benchmark \cite{romano-2012}.
\subsection{Computing the Distance to Nearest Boundary}
One of the most basic algorithms in any Monte Carlo code is determining the
distance to the nearest surface within a cell. Since each cell is defined by
the surfaces that bound it, if we compute the distance to all surfaces bounding
a cell, we can determine the nearest one.
With the possibility of a particle having coordinates on multiple levels
(universes) in a geometry, we must exercise care when calculating the distance
to the nearest surface. Each different level of geometry has a set of boundaries
with which the particle's direction of travel may intersect. Thus, it is
necessary to check the distance to the surfaces bounding the cell in each
level. This should be done starting at the highest (most global) level going
down to the lowest (most local) level. That ensures that if two surfaces on
different levels are coincident, by default the one on the higher level will be
selected as the nearest surface. Although they are not explicitly defined, it is
also necessary to check the distance to surfaces representing lattice boundaries
if a lattice exists on a given level.
The following procedure is used to calculate the distance to each bounding
surface. Suppose we have a particle at $(x_0,y_0,z_0)$ traveling in the
direction $u_0,v_0,w_0$. To find the distance $d$ to a surface $f(x,y,z) = 0$,
we need to solve the equation:
\begin{equation}
\label{eq:dist-to-boundary-1}
f(x_0 + du_0, y + dv_0, z + dw_0) = 0.
\end{equation}
Since $f(x,y,z)$ in general is quadratic in $x$, $y$, and $z$, this implies that
$f(x_0 + du_0, y + dv_0, z + dw_0)$ is quadratic in $d$. Thus we expect at most
two real solutions to \eqref{eq:dist-to-boundary-1}. If no solutions to
\eqref{eq:dist-to-boundary-1} exist or the only solutions are complex, then the
particle's direction of travel will not intersect the surface. If the solution
to \eqref{eq:dist-to-boundary-1} is negative, this means that the surface is
``behind'' the particle, i.e. if the particle continues traveling in its current
direction, it will not hit the surface.
Once a distance has been computed to a surface, we need to check if it is closer
than previously-computed distances to surfaces. Unfortunately, we cannot just
use the minimum function because some of the calculated distances, which should
be the same in theory (e.g. coincident surfaces), may be slightly different due
to the use of floating-point arithmetic. Consequently, we should first check for
floating-point equality of the current distance calculated and the minimum found
thus far. This is done by checking if
\begin{equation}
\label{eq:fp-distance}
\frac{| d - d_{min} |}{d_{min}} < \epsilon
\end{equation}
where $d$ is the distance to a surface just calculated, $d_{min}$ is
the minimum distance found thus far, and $\epsilon$ is a small number. In
OpenMC, this parameter is set to $\epsilon = 10^{-14}$ since all floating
calculations are done on 8-byte floating point numbers.
\subsection{Finding a Cell Given a Point}
\label{sec:find-cell}
Another basic algorithm is to determine which cell contains a given point in the
global coordinate system, i.e. if the particle's position is $(x,y,z)$, what
cell is it currently in. This is done in the following manner in OpenMC. With
the possibility of multiple levels of coordinates, we must perform a recursive
search for the cell. First, we start in the highest (most global) universe,
which we call the base universe, and loop over each cell within that
universe. For each cell, we check whether the specified point is inside the cell
using the algorithm described in \autoref{sec:cell-contains}. If the cell is
filled with a normal material, the search is done and we have identified the
cell containing the point. If the cell is filled with another universe, we then
search all cells within that universe to see if any of them contain the
specified point. If the cell is filled with a lattice, the position within the
lattice is determined, and then whatever universe fills that lattice position is
recursively searched. The search ends once a cell containing a normal material
is found that contains the specified point.
\subsection{Determining if a Coordinate is in a Cell}
\label{sec:cell-contains}
To determine which cell a particle is in given its coordinates, we need to be
able to check whether a given cell contains a point. The algorithm for
determining if a cell contains a point is as follows. For each surface that
bounds a cell, we determine the particle's sense with respect to the surface. As
explained earlier, if we have a point $(x_0,y_0,z_0)$ and a surface $f(x,y,z) =
0$, the point is said to have negative sense if $f(x_0,y_0,z_0) < 0$ and
positive sense if $f(x_0,y_0,z_0) > 0$. If for all surfaces, the sense of the
particle with respect to the surface matches the specified sense that defines
the half-space within the cell, then the point is inside the cell. Note that
this algorithm works only for \emph{simple cells} defined as intersections of
half-spaces.
It may help to illustrate this algorithm using a simple example. Let's say we
have a cell defined as\footnote{The input file syntax is defined in detail in
the OpenMC User's Guide \cite{romano-2012-doc}.}
\begin{lstlisting}[language=xml,frame=none]
<surface id="1" type="sphere" coeffs="0 0 0 10" />
<surface id="2" type="x-plane" coeffs="-3" />
<surface id="3" type="y-plane" coeffs="2" />
<cell id="1" surfaces="-1 2 -3" />
\end{lstlisting}
This means that the cell is defined as the intersection of the negative half
space of a sphere, the positive half-space of an x-plane, and the negative
half-space of a y-plane. Said another way, any point inside this cell must
satisfy the following equations
\begin{equation}
\label{eq:cell-contains-example}
\begin{split}
x^2 + y^2 + z^2 - 10^2 &< 0 \\
x - (-3) &> 0 \\
x - 2 &< 0
\end{split}
\end{equation}
In order to determine if a point is inside the cell, we would substitute its
coordinates into \eqref{eq:cell-contains-example}. If the resulting inequalities
are satisfied, than the point is indeed inside the cell.
\subsection{Handling Surface Crossings}
A particle will cross a surface if the distance to the nearest surface is closer
than the distance sampled to the next collision. A number of things happen when
a particle hits a surface. First, we need to check if a non-transmissive
boundary condition has been applied to the surface. If a vacuum boundary
condition has been applied, the particle is killed and any surface current
tallies are scored to as needed. If a reflective boundary condition has been
applied to the surface, surface current tallies are scored to and then the
particle's direction is changed according to the procedure in
\autoref{sec:reflection}.
Next, we need to determine what cell is beyond the surface in the direction of
travel of the particle so that we can evaluate cross sections based on its
material properties. At initialization, a list of neighboring cells is created
for each surface in the problem as described in
\autoref{sec:neighbor-lists}. The algorithm outlined in \autoref{sec:find-cell}
is used to find a cell containing the particle with one minor modification;
rather than searching all cells in the base universe, only the list of
neighboring cells is searched. If this search is unsuccessful, then a search is
done over every cell in the base universe.
\subsection{Building Neighbor Lists}
\label{sec:neighbor-lists}
After the geometry has been loaded and stored in memory from an input file,
OpenMC builds a list for each surface containing any cells that are bounded by
that surface in order to speed up processing of surface crossings. The algorithm
to build these lists is as follows. First, we loop over all cells in the
geometry and count up how many times each surface appears in a specification as
bounding a negative half-space and bounding a positive half-space. Two arrays
are then allocated for each surface, one that lists each cell that contains the
negative half-space of the surface and one that lists each cell that contains
the positive half-space of the surface. Another loop is performed over all cells
and the neighbor lists are populated for each surface.
\subsection{Reflective Boundary Conditions}
\label{sec:reflection}
If the velocity of a particle is $\mathbf{v}$ and it crosses a surface of
the form $f(x,y,z) = 0$ with a reflective boundary condition, it can be
shown based on geometric arguments that the velocity vector will then become
\begin{equation}
\label{eq:reflection-v}
\mathbf{v'} = \mathbf{v} - 2 (\mathbf{v} \cdot \hat{\mathbf{n}})
\hat{\mathbf{n}}
\end{equation}
where $\hat{\mathbf{n}}$ is a unit vector normal to the surface at the point of
the surface crossing. The rationale for this can be understood by noting that
$(\mathbf{v} \cdot \hat{\mathbf{n}}) \hat{\mathbf{n}}$ is the projection of the
velocity vector onto the normal vector. By subtracting two times this
projection, the velocity is reflected with respect to the surface normal. Since
the magnitude of the velocity will not change as it undergoes reflection, we can
work with the direction of the particle instead, simplifying
\eqref{eq:reflection-v} to
\begin{equation}
\label{eq:reflection-omega}
\mathbf{\Omega'} = \mathbf{\Omega} - 2 (\mathbf{\Omega} \cdot
\hat{\mathbf{n}}) \hat{\mathbf{n}}
\end{equation}
where $\mathbf{v} = || \mathbf{v} || \mathbf{\Omega}$. The direction of the
surface normal will be the gradient of the surface at the point of crossing,
i.e. $\mathbf{n} = \nabla f(x,y,z)$. Substituting this into
\eqref{eq:reflection-omega}, we get
\begin{equation}
\label{eq:reflection-omega-2}
\mathbf{\Omega'} = \mathbf{\Omega} - \frac{2 ( \mathbf{\Omega} \cdot \nabla
f )}{|| \nabla f ||^2} \nabla f
\end{equation}
If we write the initial and final directions in terms of their vector
components, $\mathbf{\Omega} = (u,v,w)$ and $\mathbf{\Omega'} = (u', v', w')$,
this allows us to represent \eqref{eq:reflection-omega-2} as a series of
equations:
\begin{equation}
\label{eq:reflection-system}
\begin{split}
u' &= u - \frac{2 ( \mathbf{\Omega} \cdot \nabla f )}{|| \nabla f ||^2}
\frac{\partial f}{\partial x} \\
v' &= v - \frac{2 ( \mathbf{\Omega} \cdot \nabla f )}{|| \nabla f ||^2}
\frac{\partial f}{\partial y} \\
w' &= w - \frac{2 ( \mathbf{\Omega} \cdot \nabla f )}{|| \nabla f ||^2}
\frac{\partial f}{\partial z}.
\end{split}
\end{equation}
One can then use \eqref{eq:reflection-system} to develop equations for
transforming a particle's direction given the equation of the surface.
\section{Cross Section Representation}
The data governing the interaction of neutrons with various nuclei are
represented using the ACE format \cite{lanl-x5-2008-ace} which is used by MCNP
\cite{lanl-x5-2008} and Serpent \cite{vtt-leppanen-2007}. ACE-format data can be
generated with the NJOY nuclear data processing system
\cite{nds-macfarlane-2010} which converts raw ENDF/B data
\cite{nds-chadwick-2011} into linearly-interpolable data as required by most
Monte Carlo codes. The use of a standard cross section format allows for a
direct comparison of OpenMC with other codes since the same cross section
libraries can be used.
The ACE format contains continuous-energy cross sections for the following types
of reactions: elastic scattering, fission (or first-chance fission,
second-chance fission, etc.), inelastic scattering, $(n,xn)$, $(n,\gamma)$, and
various other absorption reactions. For those reactions with one or more
neutrons in the exit channel, secondary angle and energy distributions may be
provided. In addition, fissionable nuclides have total, prompt, and/or delayed
$\nu$ as a function of energy and neutron precursor distributions. Many nuclides
also have probability tables to be used for accurate treatment of self-shielding
in the unresolved resonance range. For bound scatterers, separate tables with
$S(\alpha,\beta,T)$ scattering law data can be used.
\subsection{Energy Grid Methods}
The method by which continuous energy cross sections for each nuclide in a
problem are stored as a function of energy can have a substantial effect on the
performance of a Monte Carlo simulation. Since the ACE format is based on
linearly-interpolable cross sections, each nuclide has cross sections tabulated
over a wide range of energies. Some nuclides may only have a few points
tabulated (e.g. $^1$H) whereas other nuclides may have hundreds or thousands of
points tabulated (e.g. $^{238}$U).
At each collision, it is necessary to sample the probability of having a
particular type of interaction whether it be elastic scattering, $(n,2n)$, level
inelastic scattering, etc. This requires looking up the microscopic cross
sections for these reactions for each nuclide within the target material. Since
each nuclide has a unique energy grid, it would be necessary to search for the
appropriate index for each nuclide at every collision. This can become a very
time-consuming process, especially if there are many nuclides in a problem as
there would be for burnup calculations. Thus, there is a strong motive to
implement a method of reducing the number of energy grid searches in order to
speed up the calculation.
\subsubsection{Unionized Energy Grid}
The most naïve method to reduce the number of energy grid searches is to
construct a new energy grid that consists of the union of the energy points of
each nuclide and use this energy grid for all nuclides. This method is
computationally very efficient as it only requires one energy grid search at
each collision as well as one interpolation between cross section values since
the interpolation factor can be used for all nuclides. However, it requires
redundant storage of cross section values at points which were added to each
nuclide grid. This additional burden on memory storage can become quite
prohibitive. To lessen that burden, the unionized energy grid can be thinned
with cross sections reconstructed on the thinned energy grid. This method is
currently used by default in the Serpent Monte Carlo code
\cite{vtt-leppanen-2012}.
\subsubsection{Unionized Energy Grid with Nuclide Pointers}
While having a unionized grid that is used for all nuclides allows for very fast
lookup of cross sections, the burden on memory is in many circumstances
unacceptable. The OpenMC Monte Carlo code utilizes a method that allows for a
single energy grid search to be performed at every collision while avoiding the
redundant storage of cross section values. Instead of using the unionized grid
for every nuclide, the original energy grid of each nuclide is kept and a list
of pointers (of the same length as the unionized energy grid) is constructed for
each nuclide that gives the corresponding grid index on the nuclide grid for a
given grid index on the unionized grid. One must still interpolate on cross
section values for each nuclide since the interpolation factors will generally
be different. \autoref{fig:uniongrid} illustrates this method. All values within
the dashed box would need to be stored on a per-nuclide basis, and the union
grid would need to be stored once. This method is also referred to as
\emph{double indexing} \cite{ane-leppanen-2009} and is available as an option in
Serpent.
\begin{figure}[htb]
\centering
\includegraphics[width=6.0in]{figures/ch2/uniongrid.pdf}
\caption{Mapping of union energy grid to nuclide energy grid through
pointers.}
\label{fig:uniongrid}
\end{figure}
\section{Random Number Generation}
In order to sample probability distributions, one must be able to produce random
numbers. The standard technique to do this is to generate numbers on the
interval $[0,1)$ from a deterministic sequence that has properties that make it
appear to be random, e.g. being uniformly distributed and not exhibiting
correlation between successive terms. Since the numbers produced this way are
not truly ``random'' in a strict sense, they are typically referred to as
pseudorandom numbers, and the techniques used to generate them are
pseudorandom number generators (PRNGs). Numbers sampled on the unit interval
can then be transformed for the purpose of sampling other continuous or
discrete probability distributions.
There are a great number of algorithms for generating random numbers. One of the
simplest and commonly used algorithms is called a linear congruential generator
(LCG). We start with a random number \emph{seed} $\xi_0$ and a sequence of
random numbers can then be generated using the following recurrence relation:
\begin{equation}
\label{eq:lcg}
\xi_{i+1} = g \xi_i + c \mod M
\end{equation}
where $g$, $c$, and $M$ are constants. The choice of these constants will have a
profound effect on the quality and performance of the generator, so they should
not be chosen arbitrarily. As Donald Knuth stated in his seminal work \emph{The
Art of Computer Programming} \cite{knuth-2006}, ``random numbers should not be
generated with a method chosen at random. Some theory should be used.''
Typically, $M$ is chosen to be a power of two as this enables $x \mod M$ to be
performed using the bitwise AND operator with a bit mask. The constants for the
linear congruential generator used by default in OpenMC are $g =
2806196910506780709$, $c = 1$, and $M = 2^{63}$ \cite{mathcomp-lecuyer-1999}.
One of the important capabilities for a random number generator is to be able to
skip ahead in the sequence of random numbers. Without this capability, it would
be very difficult to maintain reproducibility in a parallel calculation. If we
want to skip ahead $N$ random numbers and $N$ is large, the cost of sampling $N$
random numbers to get to that position may be prohibitively
expensive. Fortunately, algorithms have been developed that allow us to skip
ahead in $O(\log_2 N)$ operations instead of $O(N)$. One algorithm to do so is
described in a paper by Brown \cite{trans-brown-1994}. This algorithm relies on
the following relationship:
\begin{equation}
\label{eq:lcg-skipahead}
\xi_{i+k} = g^k \xi_i + c \frac{g^k - 1}{g - 1} \mod M
\end{equation}
Note that \eqref{eq:lcg-skipahead} has the same general form as \eqref{eq:lcg},
so the idea is to determine the new multiplicative and additive constants in
$O(\log_2 N)$ operations.
\section{Physics}
\subsection{Sampling Distance to Next Collision}
As a particle travels through a homogeneous material, the probability
distribution function for the distance to its next collision $\ell$ is
\begin{equation}
\label{eq:distance-pdf}
p(\ell) d\ell = \Sigma_t e^{-\Sigma_t \ell} d\ell
\end{equation}
where $\Sigma_t$ is the total macroscopic cross section of the
material. Equation \eqref{eq:distance-pdf} tells us that the further the
distance is to the next collision, the less likely the particle will travel that
distance. In order to sample the probability distribution function, we first
need to convert it to a cumulative distribution function
\begin{equation}
\label{eq:distance-cdf}
\int_0^{\ell} d\ell' p(\ell') = \int_0^{\ell} d\ell' \Sigma_t e^{-\Sigma_t
\ell'} = 1 - e^{-\Sigma_t \ell}.
\end{equation}
By setting the cumulative distribution function equal to $\xi$, a random number
on the unit interval, and solving for the distance $\ell$, we obtain a formula
for sampling the distance to next collision:
\begin{equation}
\label{eq:sample-distance-1}
\ell = -\frac{\ln (1 - \xi)}{\Sigma_t}
\end{equation}
Since $\xi$ is uniformly distributed on $[0,1)$, this implies that $1 - \xi$ is
also uniformly distributed on $[0,1)$ as well. Thus, the formula used to
calculate the distance to next collision in OpenMC is
\begin{equation}
\label{eq:sample-distance-2}
\ell = -\frac{\ln \xi}{\Sigma_t}.
\end{equation}
\subsection{\texorpdfstring{$(n,\gamma)$}{(n,gamma)} and Other Disappearance Reactions}
All absorption reactions other than fission do not produce any secondary
neutrons. As a result, these are the easiest type of reactions to handle. When a
collision occurs, the first step is to sample a nuclide within a material. Once
the nuclide has been sampled, then a specific reaction for that nuclide is
sampled. Since the total absorption cross section is pre-calculated at the
beginning of a simulation, the first step in sampling a reaction is to determine
whether a ``disappearance'' reaction occurs where no secondary neutrons are
produced. This is done by sampling a random number $\xi$ on the interval $[0,1)$
and checking whether
\begin{equation}
\label{eq:disappearance}
\xi \sigma_t (E) < \sigma_a (E) - \sigma_f (E)
\end{equation}
where $\sigma_t$ is the total cross section, $\sigma_a$ is the absorption cross
section (this includes fission), and $\sigma_f$ is the total fission cross
section. If this condition is met, then the neutron is killed and we proceed to
simulate the next neutron from the source bank.
No secondary particles from disappearance reactions such as photons or
alpha-particles are produced or tracked in OpenMC. To truly capture the affects
of gamma heating, it would be necessary to explicitly track photons originating
from $(n,\gamma)$ and other reactions.
\subsection{Elastic Scattering}
Elastic scattering refers to the process by which a neutron scatters off a
nucleus and does not leave it in an excited state. It is referred to as
``elastic'' because in the center-of-mass system, the neutron does not actually
lose energy. However, in lab coordinates, the neutron does indeed lose
energy. Elastic scattering can be treated exactly in a Monte Carlo code thanks
to its simplicity.
Let us discuss how OpenMC handles two-body elastic scattering kinematics. The
first step is to determine whether the target nucleus has any associated
motion. Above a certain energy threshold (400 kT by default), all scattering is
assumed to take place with the target at rest. Below this threshold though, we
must account for the thermal motion of the target nucleus. Methods to sample the
velocity of the target nucleus are described later in \autoref{sec:freegas}. For
the time being, let us assume that we have sampled the target velocity
$\mathbf{v}_t$. The velocity of the center-of-mass system is calculated as
\begin{equation}
\label{eq:velocity-com}
\mathbf{v}_{cm} = \frac{\mathbf{v}_n + A \mathbf{v}_t}{A + 1}
\end{equation}
where $\mathbf{v}_n$ is the velocity of the neutron and $A$ is the atomic mass
of the target nucleus measured in neutron masses (commonly referred to as the
\emph{atomic weight ratio}). With the velocity of the center-of-mass calculated,
we can then determine the neutron's velocity in the center-of-mass system:
\begin{equation}
\label{eq:velocity-neutron-com}
\mathbf{V}_n = \mathbf{v}_n - \mathbf{v}_{cm}
\end{equation}
where we have used uppercase $\mathbf{V}$ to denote the center-of-mass
system. The direction of the neutron in the center-of-mass system is
\begin{equation}
\label{eq:angle-neutron-com}
\mathbf{\Omega}_n = \frac{\mathbf{V}_n}{|| \mathbf{V}_n ||}.
\end{equation}
At low energies, elastic scattering will be isotropic in the center-of-mass
system, but for higher energies, there may be p-wave and higher order scattering
that leads to anisotropic scattering. Thus, in general, we need to sample a
cosine of the scattering angle which we will refer to as $\mu$. For elastic
scattering, the secondary angle distribution is always given in the
center-of-mass system and is sampled according to the procedure outlined in
\autoref{sec:sample-angle}. After the cosine of the angle of scattering has been
sampled, we need to determine the neutron's new direction $\mathbf{\Omega}'_n$
in the center-of-mass system. This is done with the procedure in
\autoref{sec:transform-coordinates}. The new direction is multiplied by the
speed of the neutron in the center-of-mass system to obtain the new velocity
vector in the center-of-mass:
\begin{equation}
\label{eq:velocity-neutron-com-2}
\mathbf{V}'_n = || \mathbf{V}_n || \mathbf{\Omega}'_n.
\end{equation}
Finally, we transform the velocity in the center-of-mass system back to lab
coordinates:
\begin{equation}
\label{eq:velocity-neutron-lab}
\mathbf{v}'_n = \mathbf{V}'_n + \mathbf{v}_{cm}.
\end{equation}
In OpenMC, the angle and energy of the neutron are stored rather than the
velocity vector itself, so the post-collision angle and energy can be inferred
from the post-collision velocity of the neutron in the lab system.
For tallies that require the scattering cosine, it is important to store the
scattering cosine in the lab system. If we know the scattering cosine in the
center-of-mass, the scattering cosine in the lab system can be calculated as
\begin{equation}
\label{eq:cosine-lab}
\mu_{lab} = \frac{1 + A\mu}{\sqrt{A^2 + 2A\mu + 1}}.
\end{equation}
However, \eqref{eq:cosine-lab} is only valid if the target was at rest. When the
target nucleus does have thermal motion, the cosine of the scattering angle can
be determined by simply taking the dot product of the neutron's initial and
final direction in the lab system.
\subsection{Inelastic Scattering}
\label{sec:inelastic-scatter}
The major algorithms for inelastic scattering are described in the following
subsections. First, a scattering cosine is sampled using the algorithms in
\autoref{sec:sample-angle}. Then an outgoing energy is sampled using the
algorithms in \autoref{sec:sample-energy}. If the outgoing energy and scattering
cosine were given in the center-of-mass system, they are transformed to
laboratory coordinates using the algorithm described in
\autoref{sec:transform-coordinates}. Finally, the direction of the particle is
changed also using the procedure in \autoref{sec:transform-coordinates}.
Although inelastic scattering leaves the target nucleus in an excited state, no
secondary photons from nuclear de-excitation are tracked in OpenMC.
\subsection{$(n,xn)$ Reactions}
These types of reactions are just treated as inelastic scattering and as such
are subject to the same procedure as described in
\autoref{sec:inelastic-scatter}. Rather than tracking multiple secondary
neutrons, the weight of the outgoing neutron is multiplied by the number of
secondary neutrons, e.g. for $(n,2n)$, only one outgoing neutron is tracked but
its weight is doubled.
\subsection{Fission}
\label{sec:fission}
While fission is normally considered an absorption reaction, from the
perspective of a Monte Carlo simulation it actually bears more similarities to
inelastic scattering since fission results in secondary neutrons in the exit
channel. Other absorption reactions like $(n,\gamma)$ or $(n,\alpha)$, on the
contrary, produce no neutrons. There are a few other idiosyncrasies in treating
fission. In an eigenvalue calculation, secondary neutrons from fission are only
``banked'' for use in the next generation rather than being tracked as secondary
neutrons from elastic and inelastic scattering would be. On top of this, fission
is sometimes broken into first-chance fission, second-chance fission, etc. An
ACE table either lists the partial fission reactions with secondary energy
distributions for each one, or a total fission reaction with a single secondary
energy distribution.
When a fission reaction is sampled in OpenMC (either total fission or, if data
exists, first- or second-chance fission), the following algorithm is used to
create and store fission sites for the following generation. First, the average
number of prompt and delayed neutrons must be determined to decide whether the
secondary neutrons will be prompt or delayed. This is important because delayed
neutrons have a markedly different spectrum from prompt neutrons, one that has a
lower average energy of emission. The total number of neutrons emitted $\nu_t$
is given as a function of incident energy in the ACE format. Two representations
exist for $\nu_t$. The first is a polynomial of order $N$ with coefficients
$c_0,c_1,\dots,c_N$. If $\nu_t$ has this format, we can evaluate it at incoming
energy $E$ by using the equation
\begin{equation}
\label{eq:nu-polynomial}
\nu_t (E) = \sum_{i = 0}^N c_i E^i.
\end{equation}
The other representation is just a tabulated function with a specified
interpolation law. The number of prompt neutrons released per fission event
$\nu_p$ is also given as a function of incident energy and can be specified in a
polynomial or tabular format. The number of delayed neutrons released per
fission event $\nu_d$ can only be specified in a tabular format. In practice, we
only need to determine $\nu_t$ and $\nu_d$. Once these have been determined, we
can calculate the delayed neutron fraction
\begin{equation}
\label{eq:beta}
\beta = \frac{\nu_d}{\nu_t}.
\end{equation}
We then need to determine how many total neutrons should be emitted from
fission. If survival biasing is not being used, then the number of neutrons
emitted is
\begin{equation}
\label{eq:fission-neutrons}
\nu = \frac{w \nu_t}{k_{eff}}
\end{equation}
where $w$ is the statistical weight and $k_{eff}$ is the effective
multiplication factor from the previous generation. The number of neutrons
produced is biased in this manner so that the expected number of fission
neutrons produced is the number of source particles that we started with in the
generation. Since $\nu$ is not an integer, we use the following procedure to
obtain an integral number of fission neutrons to produce. If $\xi > \nu -
\lfloor \nu \rfloor$, then we produce $\lfloor \nu \rfloor$ neutrons. Otherwise,
we produce $\lfloor \nu \rfloor + 1$ neutrons. Then, for each fission site
produced, we sample the outgoing angle and energy according to the algorithms
given in \autoref{sec:sample-angle} and \autoref{sec:sample-energy}
respectively. If the neutron is to be born delayed, then there is an extra step
of sampling a delayed neutron precursor group since they each have an associated
secondary energy distribution.
The sampled outgoing angle and energy of fission neutrons along with the
position of the collision site are stored in an array called the fission
bank. In a subsequent generation, these fission bank sites are used as starting
source sites.
\subsection{Sampling Secondary Angle Distributions}
\label{sec:sample-angle}
For any reactions with secondary neutrons, it is necessary to sample secondary
angle and energy distributions. This includes elastic and inelastic scattering,
fission, and $(n,xn)$ reactions. In some cases, the angle and energy
distributions may be specified separately, and in other cases, they may be
specified as a correlated angle-energy distribution. In the following sections,
we will outline the methods used to sample secondary distributions as well as
how they are used to modify the state of a particle.
For elastic scattering, it is only necessary to specify a secondary angle
distribution since the outgoing energy can be determined analytically. Other
reactions may also have separate secondary angle and secondary energy
distributions that are uncorrelated. In these cases, the secondary angle
distribution is represented as either
\begin{itemize}
\item An isotropic angular distribution,
\item An equiprobable distribution with 32 bins, or
\item A tabular distribution.
\end{itemize}
\subsubsection{Isotropic Angular Distribution}
In the first case, no data needs to be stored on the ACE table, and the cosine
of the scattering angle is simply calculated as
\begin{equation}
\label{eq:isotropic-angle}
\mu = 2\xi - 1
\end{equation}
where $\mu$ is the cosine of the scattering angle and $\xi$ is a random number
sampled uniformly on $[0,1)$.
\subsubsection{Equiprobable Angle Bin Distribution}
For a 32 equiprobable bin distribution, we select a random number $\xi$ to
sample a cosine bin $i$ such that
\begin{equation}
\label{eq:equiprobable-bin}
i = 1 + \lfloor 32\xi \rfloor.
\end{equation}
The same random number can then also be used to interpolate between neighboring
$\mu$ values to get the final scattering cosine:
\begin{equation}
\label{eq:equiprobable-cosine}
\mu = \mu_i + (32\xi - i) (\mu_{i+1} - \mu_i)
\end{equation}
where $\mu_i$ is the $i$th scattering cosine.
\subsubsection{Tabular Angular Distribution}
\label{sec:angle-tabular}
As the MCNP manual points out \cite{lanl-x5-2008}, using an equiprobable bin
distribution works well for high-probability regions of the scattering cosine,
but for low-probability regions it is not very accurate. Thus, a more accurate
method is to represent the scattering cosine with a tabular distribution. In
this case, we have a table of cosines and their corresponding values for a
probability distribution function and cumulative distribution function. For each
incoming neutron energy $E_i$, let us call $p_{i,j}$ the $j$th value in the
probability distribution function and $c_{i,j}$ the $j$th value in the
cumulative distribution function. We first find the interpolation factor on the
incoming energy grid:
\begin{equation}
\label{eq:interpolation-factor}
f = \frac{E - E_i}{E_{i+1} - E_i}
\end{equation}
where $E$ is the incoming energy of the particle. Then, statistical
interpolation is performed to choose between using the cosines and distribution
functions corresponding to energy $E_i$ and $E_{i+1}$. Let $\ell$ be the chosen
table where $\ell = i$ if $\xi_1 > f$ and $\ell = i + 1$ otherwise, and $\xi_1$
is a random number. Another random number $\xi_2$ is used to sample a scattering
cosine bin $j$ using the cumulative distribution function:
\begin{equation}
\label{eq:sample-cdf}
c_{\ell,j} < \xi_2 < c_{\ell,j+1}.
\end{equation}
The final scattering cosine will depend on whether histogram or linear-linear
interpolation is used. In general, we can write the cumulative distribution
function as
\begin{equation}
\label{eq:cdf}
c(\mu) = \int_{-1}^\mu p(\mu') d\mu'
\end{equation}
where $c(\mu)$ is the cumulative distribution function and $p(\mu)$
is the probability distribution function. Since we know that
$c(\mu_{\ell,j}) = c_{\ell,j}$, this implies that for $\mu >
\mu_{\ell,j}$,
\begin{equation}
\label{eq:cdf-2}
c(\mu) = c_{\ell,j} + \int_{\mu_{\ell,j}}^{\mu} p(\mu') d\mu'
\end{equation}
For histogram interpolation, we have that $p(\mu') = p_{\ell,j}$ for
$\mu_{\ell,j} \le \mu' < \mu_{\ell,j+1}$. Thus, after integrating
\eqref{eq:cdf-2} we have that
\begin{equation}
\label{eq:cumulative-dist-histogram}
c(\mu) = c_{\ell,j} + (\mu - \mu_{\ell,j}) p_{\ell,j} = \xi_2.
\end{equation}
Solving for the scattering cosine, we obtain the final form for histogram
interpolation:
\begin{equation}
\label{eq:cosine-histogram}
\mu = \mu_{\ell,j} + \frac{\xi_2 - c_{\ell,j}}{p_{\ell,j}}.
\end{equation}
For linear-linear interpolation, we represent the function $p(\mu')$ as a
first-order polynomial in $\mu'$. If we interpolate between successive values on
the probability distribution function, we have that
\begin{equation}
\label{eq:pdf-interpolation}
p(\mu') - p_{\ell,j} = \frac{p_{\ell,j+1} - p_{\ell,j}}{\mu_{\ell,j+1} -
\mu_{\ell,j}} (\mu' - \mu_{\ell,j}).
\end{equation}
Solving for $p(\mu')$ in \eqref{eq:pdf-interpolation} and inserting it into
\eqref{eq:cdf-2}, we obtain
\begin{equation}
\label{eq:cdf-linlin}
c(\mu) = c_{\ell,j} + \int_{\mu_{\ell,j}}^{\mu} \left [ \frac{p_{\ell,j+1} -
p_{\ell,j}}{\mu_{\ell,j+1} - \mu_{\ell,j}} (\mu' - \mu_{\ell,j}) +
p_{\ell,j} \right ] d\mu'.
\end{equation}
Let us now make a change of variables using
\begin{equation}
\label{eq:introduce-eta}
\eta = \frac{p_{\ell,j+1} - p_{\ell,j}}{\mu_{\ell,j+1} - \mu_{\ell,j}}
(\mu' - \mu_{\ell,j}) + p_{\ell,j}.
\end{equation}
Equation \eqref{eq:cdf-linlin} then becomes
\begin{equation}
\label{eq:cdf-linlin-eta}
c(\mu) = c_{\ell,j} + \frac{1}{m} \int_{p_{\ell,j}}^{m(\mu - \mu_{\ell,j}) +
p_{\ell,j}} \eta \, d\eta
\end{equation}
where we have used
\begin{equation}
\label{eq:slope}
m = \frac{p_{\ell,j+1} - p_{\ell,j}}{\mu_{\ell,j+1} - \mu_{\ell,j}}.
\end{equation}
Integrating \eqref{eq:cdf-linlin-eta}, we have
\begin{equation}
\label{eq:cdf-linlin-integrated}
c(\mu) = c_{\ell,j} + \frac{1}{2m} \left ( \left [ m (\mu - \mu_{\ell,j} ) +
p_{\ell,j} \right ]^2 - p_{\ell,j}^2 \right ) = \xi_2.
\end{equation}
Solving for $\mu$, we have the final form for the scattering cosine using a
tabular distribution and linear-linear interpolation:
\begin{equation}
\label{eq:cosine-linlin}
\mu = \mu_{\ell,j} + \frac{1}{m} \left ( \sqrt{p_{\ell,j}^2 + 2 m (\xi_2 -
c_{\ell,j} )} - p_{\ell,j} \right ).
\end{equation}
\subsection{Sampling Secondary Energy and Correlated Angle/Energy Distributions}
\label{sec:sample-energy}
For a reaction with secondary neutrons, it is necessary to determine the
outgoing energy of the neutrons. For any reaction other than elastic scattering,
the outgoing energy must be determined based on tabulated or parameterized
data. The ENDF-6 Format \cite{bnl-herman-2009} specifies a variety of ways that
the secondary energy distribution can be represented. ENDF File 5 contains
uncorrelated energy distribution whereas ENDF File 6 contains correlated
energy-angle distributions. The ACE format specifies its own representations
based loosely on the formats given in ENDF-6. In this section, we will describe
how the outgoing energy of secondary particles is determined based on each ACE
law.
One of the subtleties in the ACE format is the fact that a single reaction can
have multiple secondary energy distributions. This is mainly useful for
reactions with multiple neutrons in the exit channel such as $(n,2n)$ or
$(n,3n)$. In these types of reactions, each neutron is emitted corresponding to
a different excitation level of the compound nucleus, and thus in general the
neutrons will originate from different energy distributions. If multiple energy
distributions are present, they are assigned probabilities that can then be used
to randomly select one.
Once a secondary energy distribution has been sampled, the procedure for
determining the outgoing energy will depend on which ACE law has been specified
for the data.
\subsubsection{ACE Law 1 - Tabular Equiprobable Energy Bins}
\label{sec:ace-law-1}
In the tabular equiprobable bin representation, an array of equiprobable
outgoing energy bins is given for a number of incident energies. While the
representation itself is simple, the complexity lies in how one interpolates
between incident as well as outgoing energies on such a table. If one performs
simple interpolation between tables for neighboring incident energies, it is
possible that the resulting energies would violate laws governing the
kinematics, i.e. the outgoing energy may be outside the range of available
energy in the reaction.
To avoid this situation, the accepted practice is to use a process known as
scaled interpolation \cite{nse-doyas-1972}. First, we find the tabulated