Skip to content

Commit c800dab

Browse files
Version 1.2.0--Voxel geometry improvements, CPF refactoring
- Streamline capabilities for creating and visualizing voxel versions of 3D stochastic geometries - Enable particle simulations on voxel geometries - New features to compute spatial statistics quantities from voxel geometries - Refactor conditional probability function (CPF) functionality to enable new CPFs
1 parent d13bcdc commit c800dab

20 files changed

+1233
-592
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
**/*pdf
77
**/*npy
88
**/*csv
9+
**/*vtk
910
**/*~
1011

Core/3D/CPF_Basepy.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!usr/bin/env python
2+
import sys
3+
sys.path.append('/../../Classes/Tools')
4+
from ClassToolspy import ClassTools
5+
import numpy as np
6+
7+
## \brief Base class for conditional probability functions which return probability of material type at one point based on known material type at others
8+
# \author Aaron Olson, [email protected], [email protected]
9+
#
10+
class CPF_Base(ClassTools):
11+
def __init__(self):
12+
super(CPF_Base,self).__init__()
13+
14+
def __str__(self):
15+
return str(self.__dict__)
16+
17+
## \brief Defines mixing parameters to use in conditional probability function evaluations
18+
# To be provided by non-base class
19+
def defineMixingParams(self):
20+
assert 1==0
21+
22+
## \brief Computes conditional probabilities from governing points
23+
# To be provided by non-base class
24+
def returnConditionalProbabilities(self):
25+
assert 1==0
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!usr/bin/env python
2+
from CPF_Basepy import CPF_Base
3+
from MarkovianInputspy import MarkovianInputs
4+
import numpy as np
5+
6+
## \brief Conditional probability function evaluator for model where each point is assumed to contribute to the new point's material likelihoods independently
7+
# \author Aaron Olson, [email protected], [email protected]
8+
#
9+
class CPF_MarkovianIndependentContribs(CPF_Base,MarkovianInputs):
10+
def __init__(self):
11+
super(CPF_MarkovianIndependentContribs,self).__init__()
12+
13+
def __str__(self):
14+
return str(self.__dict__)
15+
16+
## \brief Defines mixing parameters (chord lengths and probabilities)
17+
#
18+
# \param[in] lam, list of floats, list of chord lengths
19+
# \returns sets chord length, probabilities, correlation length, seed locations
20+
def defineMixingParams(self,*args):
21+
# Assert material chord lengths and slab length and store in object
22+
lam = args[0][0]
23+
assert isinstance(lam,list)
24+
self.nummats = len(lam)
25+
for i in range(0,self.nummats): assert isinstance(lam[i],float) and lam[i]>0.0
26+
self.lam = lam
27+
28+
# Solve and store material probabilities and correlation length
29+
self.solveNaryMarkovianParamsBasedOnChordLengths(self.lam)
30+
31+
## \brief Computes conditional probabilities from governing points
32+
#
33+
# \returns sets condprob list of floats, probabilities of selecting each material type
34+
def returnConditionalProbabilities(self,locpoint,govpoints,govmatinds):
35+
govpoints = np.asarray(govpoints)
36+
self.govdists = np.power(np.subtract(govpoints[:,0],locpoint[0]),2)
37+
self.govdists = np.add( self.govdists, np.power(np.subtract(govpoints[:,1],locpoint[1]),2) )
38+
self.govdists = list( np.sqrt( np.add( self.govdists, np.power(np.subtract(govpoints[:,2],locpoint[2]),2) ) ) )
39+
40+
compliments = np.ones( self.nummats )
41+
#for loop through governing points, collect compliments of new point in same region as each material type
42+
for igovpt in range(0,len(govpoints)):
43+
imat = govmatinds[igovpt]
44+
compliments[imat] *= self.freqAtLeastOnePseudointerface_Markovian(igovpt)
45+
#compute fraction of combination space for which new point is not in the same cell as any governing points
46+
frac_newregion = np.prod( compliments )
47+
#compute fraction of combination space for which new point is in the same cell as governing points of each type
48+
frac_sameregion = []
49+
for imat in range(0,self.nummats):
50+
frac_sameregion.append( frac_newregion/compliments[imat] - frac_newregion )
51+
#compute probability of sharing cell with no governing point or governing point(s) of each type
52+
frac_valid = np.sum(frac_sameregion) + frac_newregion
53+
prob_newregion = frac_newregion / frac_valid
54+
prob_sameregion= np.divide(frac_sameregion,frac_valid)
55+
#compute probabilities of sampling each material type
56+
return np.add( prob_sameregion, np.multiply(prob_newregion,self.prob) )
57+
58+
## \brief Returns frequency of at least one psuedo-interface between two points in a Markovian-mixed medium
59+
#
60+
# \param[in] igovpt int, which governing point to compute the probability with regard to
61+
# \returns float, probability of having at least one pseudo-interface
62+
def freqAtLeastOnePseudointerface_Markovian(self,igovpt):
63+
return 1.0 - np.exp( - self.govdists[igovpt] / self.lamc )

Core/3D/Geometry_Basepy.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,17 @@ def defineGeometryBoundaries(self,xbounds=None,ybounds=None,zbounds=None):
2727
assert isinstance(xbounds,list) and isinstance(ybounds,list) and isinstance(zbounds,list)
2828
assert len(xbounds)==2 and len(ybounds)==2 and len(zbounds)==2
2929
assert isinstance(xbounds[0],float) and isinstance(xbounds[1],float) and xbounds[0]<=xbounds[1]
30-
if self.Part.numDims<2: assert xbounds[0]==-np.inf and xbounds[1]==np.inf
31-
assert isinstance(ybounds[0],float) and isinstance(ybounds[1],float) and ybounds[0]<=ybounds[1]
32-
if self.Part.numDims<3: assert ybounds[0]==-np.inf and ybounds[1]==np.inf
30+
if xbounds[0]==-np.inf and xbounds[1]==np.inf and ybounds[0]==-np.inf and ybounds[1]==np.inf:
31+
self.numDims = 1
32+
elif ybounds[0]==-np.inf and ybounds[1]==np.inf:
33+
self.numDims = 2
34+
else:
35+
self.numDims = 3
36+
37+
if hasattr(self, 'Part'):
38+
assert self.numDims == self.Part.numDims
39+
40+
assert isinstance(ybounds[0],float) and isinstance(ybounds[1],float) and ybounds[0]<=ybounds[1]
3341
assert isinstance(zbounds[0],float) and isinstance(zbounds[1],float) and zbounds[0]<=zbounds[1]
3442
if self.GeomType=='Markovian':
3543
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] ):
@@ -40,8 +48,9 @@ def defineGeometryBoundaries(self,xbounds=None,ybounds=None,zbounds=None):
4048
self.zbounds = zbounds
4149

4250
self.Volume = self.zbounds[1]-self.zbounds[0]
43-
if self.Part.numDims>=2: self.Volume *= (self.xbounds[1]-self.xbounds[0])
44-
if self.Part.numDims==3: self.Volume *= (self.ybounds[1]-self.ybounds[0])
51+
52+
if self.numDims>=2: self.Volume *= (self.xbounds[1]-self.xbounds[0])
53+
if self.numDims==3: self.Volume *= (self.ybounds[1]-self.ybounds[0])
4554

4655

4756
## \brief Define boundary conditions
@@ -52,7 +61,7 @@ def defineBoundaryConditions(self,xBCs,yBCs,zBCs):
5261
assert isinstance(xBCs,list) and isinstance(yBCs,list) and isinstance(zBCs,list)
5362
assert len(xBCs)==2 and len(yBCs)==2 and len(zBCs)==2
5463

55-
if self.Part.numDims<2: assert xBCs[0]==None and xBCs[1]==None
64+
if self.numDims<2: assert xBCs[0]==None and xBCs[1]==None
5665
else :
5766
assert xBCs[0]=='vacuum' or xBCs[0]=='reflective'
5867
assert xBCs[1]=='vacuum' or xBCs[1]=='reflective'
@@ -77,7 +86,8 @@ def defineBoundaryConditions(self,xBCs,yBCs,zBCs):
7786
# \returns sets cross sections
7887
def defineCrossSections(self,totxs=None,scatxs=None):
7988
assert isinstance(totxs,list)
80-
self.nummats = len(totxs)
89+
if not hasattr(self, "nummats"): self.nummats = len(totxs)
90+
assert self.nummats==len(totxs)
8191
for i in range(0,self.nummats):
8292
assert isinstance(totxs[i],float) and totxs[i]>=0.0
8393
self.totxs = totxs
@@ -107,6 +117,8 @@ def associateRng(self,Rng):
107117
# \returns initializes self.Part
108118
def associatePart(self,Part):
109119
assert isinstance(Part,Particle)
120+
if hasattr(self, "numDims"):
121+
assert self.numDims == Part.numDims
110122
self.Part = Part
111123

112124
## \brief Tests for a leakage or boundary reflection event, flags the first, evaluates the second
@@ -169,7 +181,7 @@ def calculateDistanceToBoundary(self):
169181
# pseudocode: if woodcock, sample whether or not it is accepted. If it's not woodcock,
170182
# we just want to sample absorb or scatter. Will also do that if collision is accepted for woodcock.
171183
def evaluateCollision(self):
172-
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
184+
if self.trackingType == "Woodcock":
173185
self.samplePoint() #define new point
174186
if self.Rng.rand()>self.totxs[self.CurrentMatInd]/self.Majorant: return 'reject' #accept or reject potential collision
175187
return 'absorb' if self.Rng.rand()>self.scatrat[self.CurrentMatInd] else 'scatter' #choose collision type
@@ -205,4 +217,4 @@ def defineMixingParams(self,laminf=None,prob=None):
205217
## \brief Samples a new point according to the selected rules
206218
# To be provided by non-base class
207219
def samplePoint(self):
208-
assert 1==0
220+
assert 1==0

Core/3D/Geometry_BoxPoissonpy.py

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!usr/bin/env python
2-
from Geometry_Basepy import Geometry_Base
2+
from Geometry_Voxelpy import Geometry_Voxel
33
import numpy as np
44
import bisect as bisect
55
try:
@@ -15,7 +15,7 @@
1515
#
1616
# Class for multi-D Box-Poisson geometry
1717
#
18-
class Geometry_BoxPoisson(Geometry_Base):
18+
class Geometry_BoxPoisson(Geometry_Voxel):
1919
def __init__(self):
2020
super(Geometry_BoxPoisson,self).__init__()
2121
self.flshowplot = False; self.flsaveplot = False
@@ -33,14 +33,14 @@ def __str__(self):
3333
def _initializeHistoryGeometryMemory(self):
3434
pass
3535

36-
## \brief Initializes list of material types and coordinate of location, initializes xyz boundary locations
36+
## \brief Samples hyperplanes to define geometry, voxelizes if selected
3737
#
38-
# 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
39-
#
40-
# \returns initializes self.MatInds and self.xBoundaries, self.yBoundaries, self.zBoundaries
38+
# \returns initializes self.MatInds, self.xBoundaries, self.yBoundaries, self.zBoundaries; sets self.samplePoint; and voxelizes if selected
4139
def _initializeSampleGeometryMemory(self):
4240
self._sampleNumberOfHyperplanes()
4341
self._sampleHyperplaneLocations()
42+
self.samplePoint = self.samplePoint_BoxPoisson
43+
if self.flVoxelize: self.voxelizeGeometry()
4444

4545
## \brief Defines mixing parameters (correlation length and material probabilities)
4646
# Note: laminf and rhoB notation and formula following LarmierJQSRT2017 different mixing types paper
@@ -51,6 +51,8 @@ def _initializeSampleGeometryMemory(self):
5151
def defineMixingParams(self,laminf=None,prob=None):
5252
# Assert material chord lengths and slab length and store in object
5353
assert isinstance(laminf,float) and laminf>0.0
54+
if not hasattr(self, "nummats"):
55+
self.nummats = len(prob)
5456
assert isinstance(prob,list) and len(prob)==self.nummats
5557
for i in range(0,self.nummats): assert isinstance(prob[i],float) and prob[i]>=0.0 and prob[i]<=1.0
5658
assert self.isclose( np.sum(prob), 1.0 )
@@ -71,7 +73,6 @@ def _sampleNumberOfHyperplanes(self):
7173
self.yPInterfaces = self.Rng.poisson(yAve)
7274
self.zPInterfaces = self.Rng.poisson(zAve)
7375
self.MatInds = np.full( ( self.xPInterfaces+1 , self.yPInterfaces+1 , self.zPInterfaces+1 ),None )
74-
#print(self.xPInterfaces,self.yPInterfaces,self.zPInterfaces, "planes sampled in x, y, and z")
7576

7677

7778
## \brief sample location of pseudo-interfaces and constructs Box-Poisson geometry
@@ -98,24 +99,20 @@ def _sampleHyperplaneLocations(self):
9899
self.yBoundaries.sort()
99100
self.zBoundaries.sort()
100101

101-
## \brief Samples a new point according to the selected rules
102+
## \brief Finds (or samples) material type at current location
102103
#
103-
# \returns scattering ratio of cell material type
104-
def samplePoint(self):
105-
#find index of nearest point
106-
x = self.Part.x
107-
y = self.Part.y
108-
z = self.Part.z
109-
110-
xIndex = bisect.bisect(self.xBoundaries,x) - 1
111-
yIndex = bisect.bisect(self.yBoundaries,y) - 1
112-
zIndex = bisect.bisect(self.zBoundaries,z) - 1
113-
#check if material has been sampled. If not sample material type.
104+
# \returns sets self.CurrentMatInd
105+
def samplePoint_BoxPoisson(self):
106+
#find indices of current box
107+
xIndex = bisect.bisect(self.xBoundaries,self.Part.x) - 1
108+
yIndex = bisect.bisect(self.yBoundaries,self.Part.y) - 1
109+
zIndex = bisect.bisect(self.zBoundaries,self.Part.z) - 1
110+
#check if material has been sampled in this box; if not, sample material type
111+
if self.MatInds[xIndex,yIndex,zIndex]==None:
112+
self.MatInds[xIndex,yIndex,zIndex] = int(self.Rng.choice(p=self.prob))
114113
self.CurrentMatInd = self.MatInds[xIndex,yIndex,zIndex]
115-
if self.CurrentMatInd==None:
116-
self.CurrentMatInd = int(self.Rng.choice(p=self.prob))
117-
self.MatInds[xIndex,yIndex,zIndex] = self.CurrentMatInd
118-
114+
115+
119116
def samplePointsFast(self, points):
120117
#find index of nearest point
121118
xinds = np.searchsorted(self.xBoundaries, points[:,0], side='right')-1
@@ -144,11 +141,7 @@ def samplePointsFast(self, points):
144141
xi = np.int16(xi)
145142
yi = np.int16(yi)
146143
zi = np.int16(zi)
147-
#uniques = np.array(list(uniques.keys()))
148-
#uniques = np.unique(unassigned_inds, axis=0)
149144
new_assignments = self.Rng.RngObj.choice(self.nummats, size=len(uniques), p=self.prob)
150-
#self.MatInds[uniques[:,0], uniques[:,1], uniques[:,2]] = new_assignments
151145
self.MatInds[xi, yi, zi] = new_assignments
152146
matTypes = self.MatInds[xinds, yinds, zinds]
153-
return matTypes.astype(np.int16)
154-
147+
return matTypes.astype(np.int16)

Core/3D/Geometry_CLSpy.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
## \brief Multi-D CLS geometry class
1111
# \author Dominic Lioce, [email protected], [email protected]
1212
#
13-
# A good chunk of this class is copied from the CoPS geometry class. Only a few things are different
14-
#
1513
class Geometry_CLS(Geometry_Base,ClassTools,MarkovianInputs):
1614
def __init__(self):
1715
super(Geometry_CLS,self).__init__()

0 commit comments

Comments
 (0)