Skip to content

Commit 2292dd9

Browse files
Initial open-source commit
1 parent ee12f23 commit 2292dd9

File tree

59 files changed

+6357
-22
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+6357
-22
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
**/*pyc
2+
**/*txt
3+
**/*dat
4+
**/*h5
5+
**/*png
6+
**/*pdf
7+
**/*npy
8+
**/*csv
9+
**/*~
10+

Core/1D/MonteCarloParticleSolverpy.py

Lines changed: 358 additions & 0 deletions
Large diffs are not rendered by default.

Core/1D/OneDSlabpy.py

Lines changed: 562 additions & 0 deletions
Large diffs are not rendered by default.

Core/1D/SpecialMonteCarloDriverspy.py

Lines changed: 518 additions & 0 deletions
Large diffs are not rendered by default.

Core/3D/Geometry_Basepy.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
#!usr/bin/env python
2+
import sys
3+
sys.path.append('/../../Core/Tools')
4+
from RandomNumberspy import RandomNumbers
5+
from Particlepy import Particle
6+
from ClassToolspy import ClassTools
7+
import numpy as np
8+
import matplotlib.pyplot as plt
9+
10+
## \brief Multi-D base geometry class - useless by itself, but other multi-D geometry classes inherit from it
11+
# \author Aaron Olson, [email protected], [email protected]
12+
#
13+
# Base class for multi-D geometries
14+
#
15+
class Geometry_Base(ClassTools):
16+
def __init__(self):
17+
super(Geometry_Base,self).__init__()
18+
self.flshowplot = False; self.flsaveplot = False
19+
20+
def __str__(self):
21+
return str(self.__dict__)
22+
23+
## \brief Define outer boundaries of parallelapided geometry and solve volume
24+
#
25+
# \param[in] xbounds, ybounds, zbounds lists of two floats, boundaries of problem domain
26+
# \returns initializes self.xbounds, self.ybounds, self.zbounds, and calcs self.Volume
27+
def defineGeometryBoundaries(self,xbounds=None,ybounds=None,zbounds=None):
28+
assert isinstance(xbounds,list) and isinstance(ybounds,list) and isinstance(zbounds,list)
29+
assert len(xbounds)==2 and len(ybounds)==2 and len(zbounds)==2
30+
assert isinstance(xbounds[0],float) and isinstance(xbounds[1],float) and xbounds[0]<=xbounds[1]
31+
if self.Part.numDims<2: assert xbounds[0]==-np.inf and xbounds[1]==np.inf
32+
assert isinstance(ybounds[0],float) and isinstance(ybounds[1],float) and ybounds[0]<=ybounds[1]
33+
if self.Part.numDims<3: assert ybounds[0]==-np.inf and ybounds[1]==np.inf
34+
assert isinstance(zbounds[0],float) and isinstance(zbounds[1],float) and zbounds[0]<=zbounds[1]
35+
if self.GeomType=='Markovian':
36+
if not ( xbounds[0]==ybounds[0] and xbounds[0]==zbounds[0] and xbounds[1]==ybounds[1] and xbounds[1]==zbounds[1] and -xbounds[0]==xbounds[1] ):
37+
raise Exception("PlaybookMC's current 3D Markovian geometry implementation requires a cubic domain centered at the origin")
38+
39+
self.xbounds = xbounds
40+
self.ybounds = ybounds
41+
self.zbounds = zbounds
42+
43+
self.Volume = self.zbounds[1]-zbounds[0]
44+
if self.Part.numDims>=2: self.Volume *= (self.xbounds[1]-xbounds[0])
45+
if self.Part.numDims==3: self.Volume *= (self.ybounds[1]-ybounds[0])
46+
47+
48+
## \brief Define boundary conditions
49+
#
50+
# \param[in] xBCs, yBCs, zBCs lists of two strings, 'vacuum' or 'reflective'
51+
# \returns initializes self.xBCs, self.yBCs, self.zBCs
52+
def defineBoundaryConditions(self,xBCs,yBCs,zBCs):
53+
assert isinstance(xBCs,list) and isinstance(yBCs,list) and isinstance(zBCs,list)
54+
assert len(xBCs)==2 and len(yBCs)==2 and len(zBCs)==2
55+
56+
if self.Part.numDims<2: assert xBCs[0]==None and xBCs[1]==None
57+
else :
58+
assert xBCs[0]=='vacuum' or xBCs[0]=='reflective'
59+
assert xBCs[1]=='vacuum' or xBCs[1]=='reflective'
60+
61+
if self.Part.numDims<3: assert yBCs[0]==None and yBCs[1]==None
62+
else :
63+
assert yBCs[0]=='vacuum' or yBCs[0]=='reflective'
64+
assert yBCs[1]=='vacuum' or yBCs[1]=='reflective'
65+
66+
assert zBCs[0]=='vacuum' or zBCs[0]=='reflective'
67+
assert zBCs[1]=='vacuum' or zBCs[1]=='reflective'
68+
69+
self.xBCs = xBCs
70+
self.yBCs = yBCs
71+
self.zBCs = zBCs
72+
73+
74+
## \brief Defines Cross Sections
75+
#
76+
# \param[in] totxs, list of floats, list of total cross sections
77+
# \param scatxs, list of floats, list of scattering cross sections
78+
# \returns sets cross sections
79+
def defineCrossSections(self,totxs=None,scatxs=None):
80+
assert isinstance(totxs,list)
81+
self.nummats = len(totxs)
82+
for i in range(0,self.nummats):
83+
assert isinstance(totxs[i],float) and totxs[i]>=0.0
84+
self.totxs = totxs
85+
self.Majorant = max(totxs)
86+
if not scatxs==None:
87+
assert isinstance(scatxs,list) and len(scatxs)==self.nummats
88+
for i in range(0,self.nummats):
89+
assert isinstance(scatxs[i],float) and scatxs[i]>=0.0 and totxs[i]>=scatxs[i]
90+
self.scatxs = scatxs
91+
self.absxs = []
92+
self.scatrat = []
93+
for i in range(0,self.nummats):
94+
self.absxs.append( self.totxs[i]-self.scatxs[i] )
95+
self.scatrat.append( self.scatxs[i]/self.totxs[i] )
96+
97+
## \brief Associates random number object - must be instance of RandomNumbers class
98+
#
99+
# \param[in] Rng RandomNumbers object
100+
# \returns initializes self.Rng
101+
def associateRng(self,Rng):
102+
assert isinstance(Rng,RandomNumbers)
103+
self.Rng = Rng
104+
105+
## \brief Associates Particle object - must be instance of Particle class
106+
#
107+
# \param[in] Part Particle object
108+
# \returns initializes self.Part
109+
def associatePart(self,Part):
110+
assert isinstance(Part,Particle)
111+
self.Part = Part
112+
113+
## \brief Tests for a leakage or boundary reflection event, flags the first, evaluates the second
114+
#
115+
# \returns str 'reflect', 'transmit', 'sideleak', or 'noleak'
116+
def testForBoundaries(self):
117+
if self.isclose(self.Part.z,self.zbounds[0]):
118+
self.Part.z = self.zbounds[0]
119+
if self.zBCs[0]== 'vacuum': return 'reflect'
120+
elif self.zBCs[0]=='reflective': self.Part.reflect_z()
121+
if self.isclose(self.zbounds[1],self.Part.z):
122+
self.Part.z = self.zbounds[1]
123+
if self.zBCs[1]== 'vacuum': return 'transmit'
124+
elif self.zBCs[1]=='reflective': self.Part.reflect_z()
125+
126+
if self.isclose(self.Part.x,self.xbounds[0]):
127+
self.Part.x = self.xbounds[0]
128+
if self.xBCs[0]== 'vacuum': return 'sideleak'
129+
elif self.xBCs[0]=='reflective': self.Part.reflect_x()
130+
if self.isclose(self.xbounds[1],self.Part.x):
131+
self.Part.x = self.xbounds[1]
132+
if self.xBCs[1]== 'vacuum': return 'sideleak'
133+
elif self.xBCs[1]=='reflective': self.Part.reflect_x()
134+
135+
if self.isclose(self.Part.y,self.ybounds[0]):
136+
self.Part.y = self.ybounds[0]
137+
if self.yBCs[0]== 'vacuum': return 'sideleak'
138+
elif self.yBCs[0]=='reflective': self.Part.reflect_y()
139+
if self.isclose(self.ybounds[1],self.Part.y):
140+
self.Part.y = self.ybounds[1]
141+
if self.yBCs[1]== 'vacuum': return 'sideleak'
142+
elif self.yBCs[1]=='reflective': self.Part.reflect_y()
143+
144+
return 'noleak'
145+
146+
147+
## \brief Returns distance to geometry boundary, only boundary for WMC-based approach
148+
#
149+
# \returns positive float, distance along streaming path to boundary
150+
def calculateDistanceToBoundary(self):
151+
if self.isclose(self.Part.u,0.0): dbx = np.inf
152+
elif self.Part.u>0.0 : dbx = (self.xbounds[1]-self.Part.x)/self.Part.u
153+
else : dbx = (self.xbounds[0]-self.Part.x)/self.Part.u
154+
155+
if self.isclose(self.Part.v,0.0): dby = np.inf
156+
elif self.Part.v>0.0 : dby = (self.ybounds[1]-self.Part.y)/self.Part.v
157+
else : dby = (self.ybounds[0]-self.Part.y)/self.Part.v
158+
159+
if self.isclose(self.Part.w,0.0): dbz = np.inf
160+
elif self.Part.w>0.0 : dbz = (self.zbounds[1]-self.Part.z)/self.Part.w
161+
else : dbz = (self.zbounds[0]-self.Part.z)/self.Part.w
162+
163+
return min(dbx,dby,dbz)
164+
165+
166+
## \brief Samples new point, chooses to accept or reject, and chooses to absorb or scatter
167+
#
168+
# \returns drives sampling of new points and returns str 'reject', 'absorb', or 'scatter'
169+
# need to change to allow for standard tracking or Woodcock instead of just Woodcock
170+
# pseudocode: if woodcock, sample whether or not it is accepted. If it's not woodcock,
171+
# we just want to sample absorb or scatter. Will also do that if collision is accepted for woodcock.
172+
def evaluateCollision(self):
173+
if self.trackingType == "Woodcock": # is there a way to tell that we are using woodcock tracking at this point in the code??? If not, I think I need two functions
174+
self.samplePoint() #define new point
175+
if self.Rng.rand()>self.totxs[self.CurrentMatInd]/self.Majorant: return 'reject' #accept or reject potential collision
176+
return 'absorb' if self.Rng.rand()>self.scatrat[self.CurrentMatInd] else 'scatter' #choose collision type
177+
178+
179+
## \brief samples random point within defined geometry
180+
#
181+
# \returns list of floats that are x, y, z values
182+
def _generateRandomPoint(self):
183+
x = self.Rng.uniform(self.xbounds[0],self.xbounds[1])
184+
y = self.Rng.uniform(self.ybounds[0],self.ybounds[1])
185+
z = self.Rng.uniform(self.zbounds[0],self.zbounds[1])
186+
187+
return [x,y,z]
188+
189+
190+
191+
192+
193+
194+
195+
## \brief Define mixing parameters (correlation length and material probabilities)
196+
# To be provided by non-base class
197+
def defineMixingParams(self,laminf=None,prob=None):
198+
assert 1==0
199+
200+
## \brief Initializes list of material types and coordinate of location, initializes xyz boundary locations
201+
# To be provided by non-base class
202+
def initializeGeometryMemory(self):
203+
assert 1==0
204+
205+
## \brief Samples a new point according to the selected rules
206+
# To be provided by non-base class
207+
def samplePoint(self):
208+
assert 1==0

Core/3D/Geometry_BoxPoissonpy.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!usr/bin/env python
2+
from Geometry_Basepy import Geometry_Base
3+
import numpy as np
4+
import scipy.spatial
5+
import bisect as bisect
6+
import collections
7+
try:
8+
import pandas as pd
9+
except:
10+
print("Pandas not available, some functionality may run slower")
11+
pass
12+
13+
14+
## \brief Multi-D Box-Poisson geometry class
15+
16+
# \author Aaron Olson, [email protected], [email protected]
17+
#
18+
# Class for multi-D Box-Poisson geometry
19+
#
20+
class Geometry_BoxPoisson(Geometry_Base):
21+
def __init__(self):
22+
super(Geometry_BoxPoisson,self).__init__()
23+
self.flshowplot = False; self.flsaveplot = False
24+
self.kdtrees = False
25+
self.GeomType = 'BoxPoisson'
26+
27+
def __str__(self):
28+
return str(self.__dict__)
29+
30+
31+
## \brief Defines mixing parameters (correlation length and material probabilities)
32+
# Note: laminf and rhoB notation and formula following LarmierJQSRT2017 different mixing types paper
33+
#
34+
# \param[in] laminf, float, correlation length of Markovian mixing
35+
# \param[in] prob, list of floats, list of material abundances (probabilities)
36+
# \returns sets corr length, mat. probs, and hyperplane density rhoB
37+
def defineMixingParams(self,laminf=None,prob=None):
38+
# Assert material chord lengths and slab length and store in object
39+
assert isinstance(laminf,float) and laminf>0.0
40+
assert isinstance(prob,list) and len(prob)==self.nummats
41+
for i in range(0,self.nummats): assert isinstance(prob[i],float) and prob[i]>=0.0 and prob[i]<=1.0
42+
assert self.isclose( np.sum(prob), 1.0 )
43+
self.laminf = laminf
44+
self.prob = prob
45+
self.rhoB = 2.0 / 3.0 / self.laminf
46+
47+
48+
## \brief Initializes list of material types and coordinate of location, initializes xyz boundary locations
49+
#
50+
# Note: Numpy arrays don't start blank and concatenate, where lists do, therefore plan to use lists, but convert to arrays using np.asarray(l) if needed to use array format
51+
#
52+
# \returns initializes self.MatInds and self.xBoundaries, self.yBoundaries, self.zBoundaries
53+
def initializeGeometryMemory(self):
54+
self._sampleNumberOfHyperplanes()
55+
self._sampleHyperplaneLocations()
56+
57+
58+
## \brief compute number of pseudo-interfaces
59+
#
60+
# \computes number of pseudo-interfaces in x, y, and z direction for defined geometry
61+
def _sampleNumberOfHyperplanes(self):
62+
xLength = abs( self.xbounds[0]-self.xbounds[1] ); xAve = self.rhoB * xLength
63+
yLength = abs( self.ybounds[0]-self.ybounds[1] ); yAve = self.rhoB * yLength
64+
zLength = abs( self.zbounds[0]-self.zbounds[1] ); zAve = self.rhoB * zLength
65+
assert xLength==yLength and xLength==zLength #logic currently only for a cube
66+
self.xPInterfaces = self.Rng.poisson(xAve)
67+
self.yPInterfaces = self.Rng.poisson(yAve)
68+
self.zPInterfaces = self.Rng.poisson(zAve)
69+
self.MatInds = np.full( ( self.xPInterfaces+1 , self.yPInterfaces+1 , self.zPInterfaces+1 ),None )
70+
#print(self.xPInterfaces,self.yPInterfaces,self.zPInterfaces, "planes sampled in x, y, and z")
71+
72+
73+
## \brief sample location of pseudo-interfaces and constructs Box-Poisson geometry
74+
#
75+
# \returns boundary location vectors for each cartesianal direction
76+
def _sampleHyperplaneLocations(self):
77+
self.xBoundaries = [self.xbounds[0],self.xbounds[1]]
78+
self.yBoundaries = [self.ybounds[0],self.ybounds[1]]
79+
self.zBoundaries = [self.zbounds[0],self.zbounds[1]]
80+
81+
for x in range(0,self.xPInterfaces):
82+
location = self.Rng.uniform(self.xbounds[0],self.xbounds[1])
83+
self.xBoundaries.append(location)
84+
85+
for y in range(0,self.yPInterfaces):
86+
location = self.Rng.uniform(self.ybounds[0],self.ybounds[1])
87+
self.yBoundaries.append(location)
88+
89+
for z in range(0,self.zPInterfaces):
90+
location = self.Rng.uniform(self.zbounds[0],self.zbounds[1])
91+
self.zBoundaries.append(location)
92+
93+
self.xBoundaries.sort()
94+
self.yBoundaries.sort()
95+
self.zBoundaries.sort()
96+
97+
## \brief Samples a new point according to the selected rules
98+
#
99+
# \returns scattering ratio of cell material type
100+
def samplePoint(self):
101+
#find index of nearest point
102+
x = self.Part.x
103+
y = self.Part.y
104+
z = self.Part.z
105+
106+
xIndex = bisect.bisect(self.xBoundaries,x) - 1
107+
yIndex = bisect.bisect(self.yBoundaries,y) - 1
108+
zIndex = bisect.bisect(self.zBoundaries,z) - 1
109+
#check if material has been sampled. If not sample material type.
110+
self.CurrentMatInd = self.MatInds[xIndex,yIndex,zIndex]
111+
if self.CurrentMatInd==None:
112+
self.CurrentMatInd = int(self.Rng.choice(p=self.prob))
113+
self.MatInds[xIndex,yIndex,zIndex] = self.CurrentMatInd
114+
115+
def samplePointsFast(self, points):
116+
#find index of nearest point
117+
xinds = np.searchsorted(self.xBoundaries, points[:,0], side='right')-1
118+
yinds = np.searchsorted(self.yBoundaries, points[:,1], side='right')-1
119+
zinds = np.searchsorted(self.zBoundaries, points[:,2], side='right')-1
120+
121+
array_of_indices = np.vstack((xinds, yinds, zinds)).T
122+
#check if material has been sampled. If not, sample material type.
123+
matTypes = self.MatInds[xinds, yinds, zinds]
124+
unassigned_inds = array_of_indices[matTypes == None]
125+
if len(unassigned_inds) > 0:
126+
nbx = len(self.xBoundaries)
127+
nby = len(self.yBoundaries)
128+
nbz = len(self.zBoundaries)
129+
nbyz = nby*nbz
130+
single_ids = np.int64(xinds)*nbyz + np.int64(yinds)*nbz + np.int64(zinds)
131+
try:
132+
uniques = pd.unique(single_ids)
133+
except:
134+
uniques = np.unique(single_ids)
135+
ynb_plus_z = uniques % nbyz
136+
zi = ynb_plus_z % nbz
137+
yi = (ynb_plus_z - zi)/nbz
138+
xi = (uniques - ynb_plus_z)/nbyz
139+
140+
xi = np.int16(xi)
141+
yi = np.int16(yi)
142+
zi = np.int16(zi)
143+
#uniques = np.array(list(uniques.keys()))
144+
#uniques = np.unique(unassigned_inds, axis=0)
145+
new_assignments = self.Rng.RngObj.choice(self.nummats, size=len(uniques), p=self.prob)
146+
#self.MatInds[uniques[:,0], uniques[:,1], uniques[:,2]] = new_assignments
147+
self.MatInds[xi, yi, zi] = new_assignments
148+
matTypes = self.MatInds[xinds, yinds, zinds]
149+
return matTypes.astype(np.int16)
150+

0 commit comments

Comments
 (0)