Skip to content

Commit 9e85c6b

Browse files
committed
Implemented midpoint method for particle update, added figure to docs
1 parent 96473a6 commit 9e85c6b

5 files changed

Lines changed: 87 additions & 60 deletions

File tree

compressible/simulation.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,7 @@ def initialize(self, extra_vars=None, ng=4):
143143
self.cc_data = my_data
144144

145145
if self.rp.get_param("particles.do_particles") == 1:
146-
n_particles = self.rp.get_param("particles.n_particles")
147-
particle_generator = self.rp.get_param("particles.particle_generator")
148-
self.particles = particles.Particles(self.cc_data, bc, n_particles, particle_generator)
146+
self.particles = particles.Particles(self.cc_data, bc, self.rp)
149147

150148
# some auxillary data that we'll need to fill GC in, but isn't
151149
# really part of the main solution

docs/source/particles_basics.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,10 @@ x-position, we can plot them on the figure axis ``ax`` using the following code:
127127
128128
ax.set_xlim([myg.xmin, myg.xmax])
129129
ax.set_ylim([myg.ymin, myg.ymax])
130+
131+
Applying this to the Kelvin-Helmholtz problem with the ``compressible`` solver,
132+
we can produce a plot such as the one below, where the particles have been
133+
plotted on top of the fluid density.
134+
135+
.. image:: particles_kh_compressible.png
136+
:align: center
30 KB
Loading

particles/particles.py

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,48 @@ def update(self, u, v, dt):
4242
self.x += u * dt
4343
self.y += v * dt
4444

45+
def interpolate_velocity(self, myg, u, v):
46+
"""
47+
Interpolate the x- and y-velocities defined on grid myg to the
48+
particle's position.
49+
50+
Parameters
51+
----------
52+
myg : Grid2d
53+
grid which the velocities are defined on
54+
u : ArrayIndexer
55+
x-velocity
56+
v : ArrayIndexer
57+
y_velocity
58+
"""
59+
60+
# find what cell it lives in
61+
x_idx = (self.x - myg.xmin) / myg.dx - 0.5
62+
y_idx = (self.y - myg.ymin) / myg.dy - 0.5
63+
64+
x_frac = x_idx % 1
65+
y_frac = y_idx % 1
66+
67+
# get the index of the particle's closest bottom left cell
68+
# we'll add one as going to use buf'd grids -
69+
# this will catch the cases where the particle is on the edges
70+
# of the grid.
71+
x_idx = int(x_idx) + 1
72+
y_idx = int(y_idx) + 1
73+
74+
# interpolate velocity
75+
u_vel = (1-x_frac)*(1-y_frac)*u.v(buf=1)[x_idx, y_idx] + \
76+
x_frac*(1-y_frac)*u.v(buf=1)[x_idx+1, y_idx] + \
77+
(1-x_frac)*y_frac*u.v(buf=1)[x_idx, y_idx+1] + \
78+
x_frac*y_frac*u.v(buf=1)[x_idx+1, y_idx+1]
79+
80+
v_vel = (1-x_frac)*(1-y_frac)*v.v(buf=1)[x_idx, y_idx] + \
81+
x_frac*(1-y_frac)*v.v(buf=1)[x_idx+1, y_idx] + \
82+
(1-x_frac)*y_frac*v.v(buf=1)[x_idx, y_idx+1] + \
83+
x_frac*y_frac*v.v(buf=1)[x_idx+1, y_idx+1]
84+
85+
return u_vel, v_vel
86+
4587

4688
class Particles(object):
4789
"""
@@ -194,31 +236,20 @@ def update_particles(self, dt, u=None, v=None):
194236
elif v is None:
195237
v = self.sim_data.get_var("y-velocity")
196238

197-
for _, p in self.particles.items():
198-
# find what cell it lives in
199-
x_idx = (p.x - myg.xmin) / myg.dx - 0.5
200-
y_idx = (p.y - myg.ymin) / myg.dy - 0.5
239+
for k, p in self.particles.items():
240+
241+
# save old position and predict location at dt/2
242+
initial_position = p.pos()
201243

202-
x_frac = x_idx % 1
203-
y_frac = y_idx % 1
244+
u_vel, v_vel = p.interpolate_velocity(myg, u, v)
204245

205-
# get the index of the particle's closest bottom left cell
206-
# we'll add one as going to use buf'd grids -
207-
# this will catch the cases where the particle is on the edges
208-
# of the grid.
209-
x_idx = int(x_idx) + 1
210-
y_idx = int(y_idx) + 1
246+
p.update(u_vel, v_vel, 0.5*dt)
211247

212-
# interpolate velocity
213-
u_vel = (1-x_frac)*(1-y_frac)*u.v(buf=1)[x_idx, y_idx] + \
214-
x_frac*(1-y_frac)*u.v(buf=1)[x_idx+1, y_idx] + \
215-
(1-x_frac)*y_frac*u.v(buf=1)[x_idx, y_idx+1] + \
216-
x_frac*y_frac*u.v(buf=1)[x_idx+1, y_idx+1]
248+
# find velocity at dt/2
249+
u_vel, v_vel = p.interpolate_velocity(myg, u, v)
217250

218-
v_vel = (1-x_frac)*(1-y_frac)*v.v(buf=1)[x_idx, y_idx] + \
219-
x_frac*(1-y_frac)*v.v(buf=1)[x_idx+1, y_idx] + \
220-
(1-x_frac)*y_frac*v.v(buf=1)[x_idx, y_idx+1] + \
221-
x_frac*y_frac*v.v(buf=1)[x_idx+1, y_idx+1]
251+
# update to final time using original position and vel at dt/2
252+
p.x, p.y = initial_position
222253

223254
p.update(u_vel, v_vel, dt)
224255

@@ -234,6 +265,7 @@ def enforce_particle_boundaries(self):
234265
boundaries.
235266
"""
236267
old_particles = self.particles.copy()
268+
self.particles = dict()
237269

238270
myg = self.sim_data.grid
239271

@@ -248,7 +280,6 @@ def enforce_particle_boundaries(self):
248280
# -x boundary
249281
if p.x < myg.xmin:
250282
if xlb in ["outflow", "neumann"]:
251-
del(self.particles[k])
252283
continue
253284
elif xlb == "periodic":
254285
p.x = myg.xmax + p.x - myg.xmin
@@ -260,7 +291,6 @@ def enforce_particle_boundaries(self):
260291
# +x boundary
261292
if p.x > myg.xmax:
262293
if xrb in ["outflow", "neumann"]:
263-
del(self.particles[k])
264294
continue
265295
elif xrb == "periodic":
266296
p.x = myg.xmin + p.x - myg.xmax
@@ -272,7 +302,6 @@ def enforce_particle_boundaries(self):
272302
# -y boundary
273303
if p.y < myg.ymin:
274304
if ylb in ["outflow", "neumann"]:
275-
del(self.particles[k])
276305
continue
277306
elif ylb == "periodic":
278307
p.y = myg.ymax + p.y - myg.ymin
@@ -284,7 +313,6 @@ def enforce_particle_boundaries(self):
284313
# +y boundary
285314
if p.y > myg.ymax:
286315
if yrb in ["outflow", "neumann"]:
287-
del(self.particles[k])
288316
continue
289317
elif yrb == "periodic":
290318
p.y = myg.ymin + p.y - myg.ymax
@@ -293,6 +321,10 @@ def enforce_particle_boundaries(self):
293321
else:
294322
msg.fail("ERROR: yrb = %s invalid BC for particles" % (yrb))
295323

324+
self.particles[k] = p
325+
326+
self.n_particles = len(self.particles)
327+
296328
def get_positions(self):
297329
"""
298330
Return an array of current particle positions.

particles/tests/test_particles.py

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ def setup_test(n_particles=50, extra_rp_params=None):
4141

4242
rp.params["mesh.nx"] = 8
4343
rp.params["mesh.ny"] = 8
44+
rp.params["mesh.xmin"] = 0
45+
rp.params["mesh.xmax"] = 1
46+
rp.params["mesh.ymin"] = 0
47+
rp.params["mesh.ymax"] = 1
4448
rp.params["particles.do_particles"] = 1
4549
n_particles = n_particles
4650

@@ -134,11 +138,7 @@ def test_particles_advect():
134138
extra_rp_params = {"mesh.xlboundary": "periodic",
135139
"mesh.xrboundary": "periodic",
136140
"mesh.ylboundary": "periodic",
137-
"mesh.yrboundary": "periodic",
138-
"mesh.xmin": 0,
139-
"mesh.xmax": 1,
140-
"mesh.ymin": 0,
141-
"mesh.ymax": 1}
141+
"mesh.yrboundary": "periodic"}
142142

143143
myd, bc, n_particles = setup_test(extra_rp_params=extra_rp_params)
144144

@@ -164,11 +164,11 @@ def test_particles_advect():
164164
# now move constant speed to the right
165165
u[:, :] = 1
166166

167-
ps.update_particles(1, u, v)
167+
ps.update_particles(0.1, u, v)
168168

169169
positions = set([(p.x, p.y) for p in ps.particles.values()])
170170

171-
correct_positions = set([((x+1) % 1, y) for x in xs for y in xs])
171+
correct_positions = set([((x+0.1) % 1, y) for x in xs for y in xs])
172172

173173
assert positions == correct_positions, "sets are not the same"
174174

@@ -177,23 +177,23 @@ def test_particles_advect():
177177
v[:, :] = 1
178178

179179
ps = particles.Particles(myd, bc, n_particles, "grid")
180-
ps.update_particles(1, u, v)
180+
ps.update_particles(0.1, u, v)
181181

182182
positions = set([(p.x, p.y) for p in ps.particles.values()])
183183

184-
correct_positions = set([(x, (y+1) % 1) for x in xs for y in xs])
184+
correct_positions = set([(x, (y+0.1) % 1) for x in xs for y in xs])
185185

186186
assert positions == correct_positions, "sets are not the same"
187187

188188
# constant speed right + up
189189
u[:, :] = 1
190190

191191
ps = particles.Particles(myd, bc, n_particles, "grid")
192-
ps.update_particles(1, u, v)
192+
ps.update_particles(0.1, u, v)
193193

194194
positions = set([(p.x, p.y) for p in ps.particles.values()])
195195

196-
correct_positions = set([((x+1) % 1, (y+1) % 1) for x in xs for y in xs])
196+
correct_positions = set([((x+0.1) % 1, (y+0.1) % 1) for x in xs for y in xs])
197197

198198
assert positions == correct_positions, "sets are not the same"
199199

@@ -206,17 +206,13 @@ def test_reflect_bcs():
206206
extra_rp_params = {"mesh.xlboundary": "reflect-even",
207207
"mesh.xrboundary": "reflect-even",
208208
"mesh.ylboundary": "reflect-even",
209-
"mesh.yrboundary": "reflect-even",
210-
"mesh.xmin": 0,
211-
"mesh.xmax": 1,
212-
"mesh.ymin": 0,
213-
"mesh.ymax": 1}
209+
"mesh.yrboundary": "reflect-even"}
214210

215211
myd, bc, _ = setup_test(extra_rp_params=extra_rp_params)
216212

217213
# create an array of particles at the edge of the domain.
218-
init_particle_positions = [[0.5, 0.2], [0.5, 0.8],
219-
[0.2, 0.5], [0.8, 0.5]]
214+
init_particle_positions = [[0.5, 0.03], [0.5, 0.96],
215+
[0.04, 0.5], [0.97, 0.5]]
220216

221217
ps = particles.Particles(myd, bc, 4, "array", init_particle_positions)
222218

@@ -232,15 +228,15 @@ def test_reflect_bcs():
232228
u[(x > y) & (x > (1-y))] = 1
233229
v[(x > y) & (x < (1-y))] = -1
234230

235-
ps.update_particles(0.3, u, v)
231+
ps.update_particles(0.1, u, v)
236232

237233
# extract positions by their initial positions so they're in the right order
238234
# (as particles are stored in a dictionary they may not be returned in the
239235
# same order as we first inserted them.)
240236
positions = [ps.particles[(x, y)].pos() for (x, y) in init_particle_positions]
241237

242-
correct_positions = [[0.5, 0.1], [0.5, 0.9],
243-
[0.1, 0.5], [0.9, 0.5]]
238+
correct_positions = [[0.5, 0.07], [0.5, 0.94],
239+
[0.06, 0.5], [0.93, 0.5]]
244240

245241
np.testing.assert_array_almost_equal(positions, correct_positions)
246242

@@ -254,20 +250,14 @@ def test_outflow_bcs():
254250
extra_rp_params = {"mesh.xlboundary": "outflow",
255251
"mesh.xrboundary": "outflow",
256252
"mesh.ylboundary": "outflow",
257-
"mesh.yrboundary": "outflow",
258-
"mesh.xmin": 0,
259-
"mesh.xmax": 1,
260-
"mesh.ymin": 0,
261-
"mesh.ymax": 1,
262-
"mesh.nx": 100,
263-
"mesh.ny": 100}
253+
"mesh.yrboundary": "outflow"}
264254

265255
myd, bc, _ = setup_test(extra_rp_params=extra_rp_params)
266256

267257
# create an array of particles with some at the edge of the domain.
268-
init_particle_positions = [[0.5, 0.2], [0.5, 0.8],
269-
[0.2, 0.5], [0.8, 0.5],
270-
[0.5, 0.4], [0.6, 0.5]]
258+
init_particle_positions = [[0.5, 0.03], [0.5, 0.96],
259+
[0.04, 0.5], [0.97, 0.5],
260+
[0.5, 0.2], [0.8, 0.5]]
271261

272262
ps = particles.Particles(myd, bc, 4, "array", init_particle_positions)
273263

@@ -283,7 +273,7 @@ def test_outflow_bcs():
283273
u[(x > y) & (x > (1-y))] = 1
284274
v[(x > y) & (x < (1-y))] = -1
285275

286-
ps.update_particles(0.3, u, v)
276+
ps.update_particles(0.1, u, v)
287277

288278
assert len(ps.particles) == 2, "All but two of the particles should have flowed out of the domain"
289279

0 commit comments

Comments
 (0)