-
Notifications
You must be signed in to change notification settings - Fork 0
/
Simulation.py
163 lines (135 loc) · 5.88 KB
/
Simulation.py
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
import math, random, sys
dt = 0.01 # sec
v_det = 50 # arbitrary velocity inside detector; lower vel = more accuracy
random.seed()
def add(v1, v2):
"""Return tuple sum of tuples by element."""
return tuple(x + y for x, y in zip(v1, v2))
def scale(v, a):
"""Return tuple of v scaled by a."""
return tuple(x*a for x in v)
def dot(v1, v2):
"""Return scalar dot product of v1 and v2."""
return sum(tuple(x*y for x, y in zip(v1, v2)))
def write(fname, distr):
"""Writes count distribution (in the form of a list of (x, y, energy)) to fname."""
with open(fname, 'w') as f:
for i, res in enumerate(distr):
#f.write('{0}\t{1}\t{2}\n'.format(res[0], res[1], res[2]))
f.write('{0}\t{1}\n'.format(res[0], res[1]))
def isodir():
"""Return a tuple of isotropically randomly generated Cartesian coords."""
r1 = random.random()
r2 = random.random()
w = 1 - 2*r1
rho = math.sqrt(1 - w**2)
phi = 2*math.pi*r2
return (rho*math.cos(phi), rho*math.sin(phi), w)
def hemidir():
"""Return a tuple of randomly generated Cartesian coords uniformly distributed over negative hemisphere."""
r1 = random.random()
r2 = random.random()
w = r1 - 1
rho = math.sqrt(1 - w**2)
phi = 2*math.pi*r2
return (rho*math.cos(phi), rho*math.sin(phi), w)
class Particle:
"""Represents a particle with energy and vector position and direction."""
def __init__(self, pos, direc, energy):
self.pos = pos
self.dir = direc
self.en = energy
class Target:
"""Represents a scattering target.
Contains information on target geometry and composition.
"""
def __init__(self, mfp1, mfp2, x1, dist, descr=None):
"""Initialize the target composition and geometry."""
self.mfp1, self.mfp2 = mfp1, mfp2
self.x1 = x1
self.descr = descr
self.dist = dist
def in_target(self, p):
"""Returns true if particle is within target, false otherwise.
Modify this to reflect target geometry
"""
r = math.sqrt(p.pos[0]**2 + p.pos[1]**2)
return (self.dist <= p.pos[2] <= self.dist+5) and r <= 10
def get_mfp(self, p):
"""Return mean-free-path of target material at particle's current position."""
r = math.sqrt(p.pos[0]**2 + p.pos[1]**2)
rp = math.sqrt((p.pos[0] - self.x1)**2 + p.pos[1]**2)
if rp <= 1.5: return self.mfp1
if r <= 8: return self.mfp2
if r <= 10: return self.mfp1
def __str__(self):
"""Return descriptive string of this target."""
return self.descr
'''
def get_mass(self, p):
r = math.sqrt(p.pos[0]**2 + p.pos[1]**2)
if r <= self.r1: return self.m1
if r <= self.r2: return self.m2
else: return self.m1
'''
class Simulation:
def __init__(self, target, runs, det_len, bank_size, dist):
"""Run a simulation, creating a list of bubbles formed.
Keyword arguments:
target object, number of runs, detector length, side length of detector bank
"""
self.det_len = det_len
self.tgt = target
self.runs = int(runs)
self.in_target = target.in_target
self.d = bank_size
self.dist = dist
self.distr = []
self.queue = [] # initialize queue to hold fission neutrons
for i in range(int(runs)):
self.run()
def collimated_particle(self):
"""Returns a Particle at z=100 traveling in negative-z direction"""
return Particle(((self.d/2)-self.d*random.random(), self.d/2-self.d*random.random(), self.dist + 10), (0, 0, -1), 14100)
def run(self):
"""Simulate a single particle's path. If a bubble is formed in the detector,
add it to the list.
"""
if not self.queue: # if there are no fission particles to simulate
p = self.collimated_particle()
p.pos = add(p.pos, scale(p.dir, ((self.dist + 5)-p.pos[2])/p.dir[2]))
else: # simulate fission particle if there remain any
p = self.queue.pop()
if self.in_target(p):
step = scale(p.dir, -self.tgt.get_mfp(p)*math.log(1-random.random()))
p.pos = add(p.pos, step)
while self.in_bounds(p) and self.in_target(p):
ndir = isodir()
step = scale(ndir, -self.tgt.get_mfp(p)*math.log(1-random.random()))
#p.en *= (1+2*dot(p.dir, ndir)/self.tgt.get_mass(p))
p.dir = ndir
p.pos = add(p.pos, step)
p.pos = add(p.pos, scale(p.dir, -1.0*p.pos[2]/p.dir[2]))
if self.in_bounds(p) and self.in_detector(p):
self.detect(p)
def in_bounds(self, p):
"""Returns true if particle is within the simulated volume; else false."""
return math.fabs(p.pos[0]) <= (self.d/2) \
and math.fabs(p.pos[1]) <= (self.d/2) \
and (-self.det_len) <= p.pos[2] <= (self.dist + 20)
def in_detector(self, p):
"""Returns true if particle is within detector bank; else false."""
return (-self.det_len) <= p.pos[2] <= 0
def detect(self, p):
"""Simulates particle path through detector, adding to the distribution
if a bubble is formed.
"""
while self.in_detector(p) and self.in_bounds(p):
if random.random() <= -2*v_det*dt*p.dir[2]/self.det_len:
self.distr.append((p.pos[0], p.pos[1], p.en))
p.pos = add(p.pos, scale(p.dir, v_det*dt))
def __str__(self):
"""Return an identifying string for this simulation containing:
target description string, # runs, detector length, and detector bank side length
"""
return '{0}_runs{1}_detlen{2}_size{3}'.format(self.tgt.__str__(), self.runs, self.det_len, self.d)