Skip to content

Commit 1f1a09f

Browse files
committed
Wrote tests for the particles class
1 parent 8ceec70 commit 1f1a09f

8 files changed

Lines changed: 300 additions & 81 deletions

File tree

_defaults

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ ny = 25 ; number of zones in the y-direction
4242
[particles]
4343
do_particles = 0 ; include particles? (1=yes, 0=no)
4444
n_particles = 100 ; number of particles
45+
particle_generator = random ; how do we generate particles? (random, grid)

advection/problems/inputs.smooth

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ limiter = 2
3636

3737
[particles]
3838
do_particles = 1
39+
particle_generator = grid

advection/problems/inputs.tophat

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ limiter = 2
3535

3636
[particles]
3737
do_particles = 1
38+
particle_generator = grid

advection/simulation.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import mesh.patch as patch
77
from simulation_null import NullSimulation, grid_setup, bc_setup
88
import particles.particles as particles
9-
from util import msg
109
import util.plot_tools as plot_tools
1110

11+
1212
class Simulation(NullSimulation):
1313

1414
def initialize(self):
@@ -28,12 +28,7 @@ def initialize(self):
2828
self.cc_data = my_data
2929

3030
if self.rp.get_param("particles.do_particles") == 1:
31-
n_particles = self.rp.get_param("particles.n_particles")
32-
if n_particles > 0:
33-
self.particles = particles.Particles(self.cc_data, bc, n_particles)
34-
else:
35-
msg.fail("ERROR: n_particles = %s < 0" % (n_particles))
36-
31+
self.particles = particles.Particles(self.cc_data, bc, self.rp)
3732

3833
# now set the initial conditions for the problem
3934
problem = importlib.import_module("advection.problems.{}".format(self.problem_name))

advection/tests/test_advection.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def setup_method(self):
1919

2020
self.rp.params["mesh.nx"] = 8
2121
self.rp.params["mesh.ny"] = 8
22+
self.rp.params["particles.do_particles"] = 0
2223

2324
self.sim = sn.Simulation("advection", "test", self.rp)
2425
self.sim.initialize()

particles/particles.py

Lines changed: 82 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99

1010

1111
class Particle(object):
12+
"""
13+
Class to hold properties of a single particle.
14+
15+
Not sure need velocity (or mass?), but will store it
16+
here for now.
17+
"""
1218

1319
def __init__(self, x, y, u=0, v=0, mass=1):
1420
self.x = x
@@ -41,7 +47,7 @@ def advect(self, u, v, dt):
4147

4248
class Particles(object):
4349

44-
def __init__(self, sim_data, bc, n_particles=100):
50+
def __init__(self, sim_data, bc, rp):
4551
"""
4652
Initialize the Particles object.
4753
@@ -53,11 +59,26 @@ def __init__(self, sim_data, bc, n_particles=100):
5359

5460
self.sim_data = sim_data
5561
self.bc = bc
62+
self.particles = set()
63+
self.rp = rp
5664

5765
# TODO: read something from rp here to determine how to
5866
# generate the particles - for now, we shall assume random.
5967

60-
self.randomly_generate_particles(n_particles)
68+
particle_generator = self.rp.get_param("particles.particle_generator")
69+
n_particles = self.rp.get_param("particles.n_particles")
70+
if n_particles <= 0:
71+
msg.fail("ERROR: n_particles = %s <= 0" % (n_particles))
72+
73+
if particle_generator == "random":
74+
self.randomly_generate_particles(n_particles)
75+
elif particle_generator == "grid":
76+
self.grid_generate_particles(n_particles)
77+
else:
78+
msg.fail("ERROR: do not recognise particle generator %s"
79+
% (particle_generator))
80+
81+
self.n_particles = len(self.particles)
6182

6283
def randomly_generate_particles(self, n_particles):
6384
"""
@@ -75,6 +96,31 @@ def randomly_generate_particles(self, n_particles):
7596

7697
self.particles = set([Particle(x, y) for (x, y) in positions])
7798

99+
def grid_generate_particles(self, n_particles):
100+
"""
101+
Generate particles equally spaced across the grid.
102+
103+
If necessary, shall increase/decrease n_particles
104+
in order to
105+
"""
106+
sq_n_particles = int(round(np.sqrt(n_particles)))
107+
108+
print(f'sq_n_particles = {sq_n_particles}')
109+
110+
if sq_n_particles**2 != n_particles:
111+
msg.warning("WARNING: Changing number of particles from {} to {}".format(n_particles, sq_n_particles**2))
112+
113+
myg = self.sim_data.grid
114+
115+
xs, step = np.linspace(myg.xmin, myg.xmax, num=sq_n_particles, endpoint=False, retstep=True)
116+
xs += 0.5 * step
117+
ys, step = np.linspace(myg.ymin, myg.ymax, num=sq_n_particles, endpoint=False, retstep=True)
118+
ys += 0.5 * step
119+
120+
print(f'xs = {xs}')
121+
122+
self.particles = set([Particle(x, y) for x in xs for y in ys])
123+
78124
def update_particles(self, u, v, dt, limiter=0):
79125
"""
80126
Update the particles on the grid. To do this, we need to
@@ -89,7 +135,7 @@ def update_particles(self, u, v, dt, limiter=0):
89135
differently in different problems.
90136
"""
91137
myg = self.sim_data.grid
92-
myd = self.sim_data.data
138+
# myd = self.sim_data.data
93139

94140
# limit the velocity
95141

@@ -120,7 +166,7 @@ def update_particles(self, u, v, dt, limiter=0):
120166
if x_idx >= myg.nx:
121167
x_frac += (x_idx - myg.nx) + 1
122168
x_idx = myg.nx - 1
123-
if y_idx >= myg.ny:
169+
if y_idx >= myg.ny:
124170
y_frac += (y_idx - myg.ny) + 1
125171
y_idx = myg.ny - 1
126172

@@ -167,47 +213,53 @@ def enforce_particle_boundaries(self):
167213
p = new_particles.pop()
168214

169215
# -x boundary
170-
if xlb == "outflow":
171-
if p.x < myg.xmin:
216+
if p.x < myg.xmin:
217+
if xlb in ["outflow", "neumann"]:
172218
continue
173-
elif xlb == "periodic":
174-
if p.x < myg.xmin:
219+
elif xlb == "periodic":
175220
p.x = myg.xmax + p.x - myg.xmin
176-
else:
177-
msg.fail("ERROR: xlb = %s invalid BC" % (xlb))
221+
elif xlb in ["reflect-even", "reflect-odd"]:
222+
p.x = 2 * myg.xmin - p.x
223+
else:
224+
msg.fail("ERROR: xlb = %s invalid BC" % (xlb))
178225

179226
# +x boundary
180-
if xrb == "outflow":
181-
if p.x > myg.xmax:
227+
if p.x > myg.xmax:
228+
if xrb in ["outflow", "neumann"]:
182229
continue
183-
elif xrb == "periodic":
184-
if p.x > myg.xmax:
230+
elif xrb == "periodic":
185231
p.x = myg.xmin + p.x - myg.xmax
186-
else:
187-
msg.fail("ERROR: xrb = %s invalid BC" % (xrb))
232+
elif xrb in ["reflect-even", "reflect-odd"]:
233+
p.x = 2 * myg.xmax - p.x
234+
else:
235+
msg.fail("ERROR: xrb = %s invalid BC" % (xrb))
188236

189237
# -y boundary
190-
if ylb == "outflow":
191-
if p.y < myg.ymin:
238+
if p.y < myg.ymin:
239+
if ylb in ["outflow", "neumann"]:
192240
continue
193-
elif ylb == "periodic":
194-
if p.y < myg.ymin:
241+
elif ylb == "periodic":
195242
p.y = myg.ymax + p.y - myg.ymin
196-
else:
197-
msg.fail("ERROR: ylb = %s invalid BC" % (ylb))
198-
199-
# +x boundary
200-
if yrb == "outflow":
201-
if p.y > myg.ymax:
243+
elif ylb in ["reflect-even", "reflect-odd"]:
244+
p.y = 2 * myg.ymin - p.y
245+
else:
246+
msg.fail("ERROR: ylb = %s invalid BC" % (ylb))
247+
248+
# +y boundary
249+
if p.y > myg.ymax:
250+
if yrb in ["outflow", "neumann"]:
202251
continue
203-
elif yrb == "periodic":
204-
if p.y > myg.ymax:
252+
elif yrb == "periodic":
205253
p.y = myg.ymin + p.y - myg.ymax
206-
else:
207-
msg.fail("ERROR: yrb = %s invalid BC" % (yrb))
254+
elif yrb in ["reflect-even", "reflect-odd"]:
255+
p.y = 2 * myg.ymax - p.y
256+
else:
257+
msg.fail("ERROR: yrb = %s invalid BC" % (yrb))
208258

209259
self.particles.add(p)
210260

261+
self.n_particles = len(self.particles)
262+
211263
def get_positions(self):
212264
"""
213265
Return an array of particle positions.

particles/tests/test_particles.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import particles.particles as particles
2+
import mesh.patch as patch
3+
from util import runparams
4+
import numpy as np
5+
from numpy.testing import assert_array_equal
6+
from simulation_null import NullSimulation, grid_setup, bc_setup
7+
8+
9+
def test_particle():
10+
n_particles = 5
11+
12+
for n in range(n_particles):
13+
x, y = np.random.rand(2)
14+
p = particles.Particle(x, y)
15+
16+
assert_array_equal(p.pos(), [x, y])
17+
assert_array_equal(p.velocity(), [0, 0])
18+
19+
20+
def test_particles_random_gen():
21+
rp = runparams.RuntimeParameters()
22+
23+
rp.params["mesh.nx"] = 8
24+
rp.params["mesh.ny"] = 8
25+
rp.params["particles.do_particles"] = 0
26+
rp.params["particles.n_particles"] = 50
27+
rp.params["particles.particle_generator"] = "random"
28+
29+
# set up sim
30+
sim = NullSimulation("", "", rp)
31+
32+
# set up grid
33+
my_grid = grid_setup(rp)
34+
my_data = patch.CellCenterData2d(my_grid)
35+
bc = bc_setup(rp)[0]
36+
my_data.create()
37+
sim.cc_data = my_data
38+
39+
# seed random number generator
40+
np.random.seed(3287469)
41+
42+
ps = particles.Particles(sim.cc_data, bc, rp)
43+
positions = set([(p.x, p.y) for p in ps.particles])
44+
45+
assert len(ps.particles) == 50, "There should be 50 particles"
46+
47+
# reseed random number generator
48+
np.random.seed(3287469)
49+
50+
correct_positions = np.random.rand(50, 2)
51+
correct_positions = set([(x, y) for (x, y) in correct_positions])
52+
53+
assert positions == correct_positions, "sets are not the same"
54+
55+
56+
def test_particles_grid_gen():
57+
rp = runparams.RuntimeParameters()
58+
59+
rp.params["mesh.nx"] = 8
60+
rp.params["mesh.ny"] = 8
61+
rp.params["particles.do_particles"] = 0
62+
rp.params["particles.n_particles"] = 50
63+
rp.params["particles.particle_generator"] = "grid"
64+
65+
# set up sim
66+
sim = NullSimulation("", "", rp)
67+
68+
# set up grid
69+
my_grid = grid_setup(rp)
70+
my_data = patch.CellCenterData2d(my_grid)
71+
bc = bc_setup(rp)[0]
72+
my_data.create()
73+
sim.cc_data = my_data
74+
75+
ps = particles.Particles(sim.cc_data, bc, rp)
76+
77+
assert len(ps.particles) == 49, "There should be 49 particles"
78+
79+
positions = set([(p.x, p.y) for p in ps.particles])
80+
81+
xs, step = np.linspace(0, 1, num=7, endpoint=False, retstep=True)
82+
xs += 0.5 * step
83+
84+
correct_positions = set([(x, y) for x in xs for y in xs])
85+
86+
assert positions == correct_positions, "sets are not the same"
87+
88+
89+
def test_particles_advect():
90+
rp = runparams.RuntimeParameters()
91+
92+
rp.params["mesh.nx"] = 8
93+
rp.params["mesh.ny"] = 8
94+
rp.params["particles.do_particles"] = 0
95+
rp.params["particles.n_particles"] = 50
96+
rp.params["particles.particle_generator"] = "grid"
97+
98+
# set up sim
99+
sim = NullSimulation("", "", rp)
100+
101+
# set up grid
102+
my_grid = grid_setup(rp, ng=4)
103+
my_data = patch.CellCenterData2d(my_grid)
104+
bc = bc_setup(rp)[0]
105+
my_data.create()
106+
sim.cc_data = my_data
107+
108+
ps = particles.Particles(sim.cc_data, bc, rp)
109+
110+
# advect with constant velocity
111+
112+
# first try 0 velocity
113+
u = my_grid.scratch_array()
114+
v = my_grid.scratch_array()
115+
116+
ps.update_particles(u, v, 1)
117+
118+
positions = set([(p.x, p.y) for p in ps.particles])
119+
120+
xs, step = np.linspace(0, 1, num=7, endpoint=False, retstep=True)
121+
xs += 0.5 * step
122+
123+
correct_positions = set([(x, y) for x in xs for y in xs])
124+
125+
assert positions == correct_positions, "sets are not the same"
126+
127+
# now move constant speed to the right
128+
u[:, :] = 1
129+
130+
ps.update_particles(u, v, 1)
131+
132+
positions = set([(p.x, p.y) for p in ps.particles])
133+
134+
correct_positions = set([(x+1, y) for x in xs for y in xs])
135+
136+
assert positions == correct_positions, "sets are not the same"
137+
138+
# constant speed up
139+
u[:, :] = 0
140+
v[:, :] = 1
141+
142+
ps = particles.Particles(sim.cc_data, bc, rp)
143+
ps.update_particles(u, v, 1)
144+
145+
positions = set([(p.x, p.y) for p in ps.particles])
146+
147+
correct_positions = set([(x, y+1) for x in xs for y in xs])
148+
149+
assert positions == correct_positions, "sets are not the same"
150+
151+
# constant speed right + up
152+
u[:, :] = 1
153+
154+
ps = particles.Particles(sim.cc_data, bc, rp)
155+
ps.update_particles(u, v, 1)
156+
157+
positions = set([(p.x, p.y) for p in ps.particles])
158+
159+
correct_positions = set([(x+1, y+1) for x in xs for y in xs])
160+
161+
assert positions == correct_positions, "sets are not the same"

0 commit comments

Comments
 (0)