Skip to content

Commit a44b53a

Browse files
authored
Doc: Simple Compute (Helpers) (#273)
Highlight in the manual how to use NumPy/CuPy/Pandas helpers on whole data containers for simple compute operations.
1 parent dbfd49c commit a44b53a

File tree

3 files changed

+134
-44
lines changed

3 files changed

+134
-44
lines changed

docs/source/usage/compute.rst

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,24 @@ The field data can have more than one component (in the slowest varying index),
3030

3131
This is how to iterate and potentially compute for all blocks assigned to a local process in pyAMReX:
3232

33-
.. literalinclude:: ../../../tests/test_multifab.py
34-
:language: python3
35-
:dedent: 4
36-
:start-after: # Manual: Compute Mfab START
37-
:end-before: # Manual: Compute Mfab END
33+
.. tab-set::
34+
35+
.. tab-item:: Simple
36+
37+
.. literalinclude:: ../../../tests/test_multifab.py
38+
:language: python3
39+
:dedent: 4
40+
:start-after: # Manual: Compute Mfab Simple START
41+
:end-before: # Manual: Compute Mfab Simple END
42+
43+
.. tab-item:: Detailed
44+
45+
.. literalinclude:: ../../../tests/test_multifab.py
46+
:language: python3
47+
:dedent: 4
48+
:start-after: # Manual: Compute Mfab Detailed START
49+
:end-before: # Manual: Compute Mfab Detailed END
50+
3851

3952
For a complete physics example that uses CPU/GPU agnostic Python code for computation on fields, see:
4053

@@ -69,19 +82,31 @@ Here is the general structure for computing on particles:
6982

7083
.. tab-item:: Modern (pure SoA) Layout
7184

72-
.. literalinclude:: ../../../tests/test_particleContainer.py
73-
:language: python3
74-
:dedent: 4
75-
:start-after: # Manual: Pure SoA Compute PC START
76-
:end-before: # Manual: Pure SoA Compute PC END
85+
.. tab-set::
86+
87+
.. tab-item:: Simple: Pandas (read-only)
88+
89+
.. literalinclude:: ../../../tests/test_particleContainer.py
90+
:language: python3
91+
:dedent: 4
92+
:start-after: # Manual: Pure SoA Compute PC Pandas START
93+
:end-before: # Manual: Pure SoA Compute PC Pandas END
94+
95+
.. tab-item:: Detailed (read and write)
96+
97+
.. literalinclude:: ../../../tests/test_particleContainer.py
98+
:language: python3
99+
:dedent: 4
100+
:start-after: # Manual: Pure SoA Compute PC Detailed START
101+
:end-before: # Manual: Pure SoA Compute PC Detailed END
77102

78103
.. tab-item:: Legacy (AoS + SoA) Layout
79104

80105
.. literalinclude:: ../../../tests/test_particleContainer.py
81106
:language: python3
82107
:dedent: 4
83-
:start-after: # Manual: Legacy Compute PC START
84-
:end-before: # Manual: Legacy Compute PC END
108+
:start-after: # Manual: Legacy Compute PC Detailed START
109+
:end-before: # Manual: Legacy Compute PC Detailed END
85110

86111
For many small CPU and GPU examples on how to compute on particles, see the following test cases:
87112

tests/test_multifab.py

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,64 @@
1111
def test_mfab_numpy(mfab):
1212
"""Used in docs/source/usage/compute.rst"""
1313

14-
# Manual: Compute Mfab START
15-
# finest active MR level, get from a simulation's AmrMesh object, e.g.:
14+
class Config:
15+
have_gpu = False
16+
17+
# Manual: Compute Mfab Detailed START
18+
# finest active MR level, get from a
19+
# simulation's AmrMesh object, e.g.:
1620
# finest_level = sim.finest_level
1721
finest_level = 0 # no MR
1822

19-
# iterate over every mesh-refinement levels
23+
# iterate over mesh-refinement levels
2024
for lev in range(finest_level + 1):
21-
# get an existing MultiFab, e.g., from a simulation:
22-
# mfab = sim.get_field(lev=lev) # code-specific getter function
25+
# get an existing MultiFab, e.g.,
26+
# from a simulation:
27+
# mfab = sim.get_field(lev=lev)
28+
# Config = sim.extension.Config
2329

2430
# grow (aka guard/ghost/halo) regions
2531
ngv = mfab.n_grow_vect
2632

2733
# get every local block of the field
2834
for mfi in mfab:
29-
# global index space box, including guards
35+
# global index box w/ guards
3036
bx = mfi.tilebox().grow(ngv)
31-
print(bx) # note: global index space of this block
32-
33-
# numpy representation: non-copying view, including the
34-
# guard/ghost region
35-
field_np = mfab.array(mfi).to_numpy()
37+
print(bx)
38+
39+
# numpy representation: non-
40+
# copying view, w/ guard/ghost
41+
field = (
42+
mfab.array(mfi).to_cupy()
43+
if Config.have_gpu
44+
else mfab.array(mfi).to_numpy()
45+
)
3646

37-
# notes on indexing in field_np:
47+
# notes on indexing in field:
3848
# - numpy uses locally zero-based indexing
3949
# - layout is F_CONTIGUOUS by default, just like AMReX
4050

41-
# notes:
42-
# Only the next lines are the "HOT LOOP" of the computation.
43-
# For efficiency, use numpy array operation for speed on CPUs.
44-
# For GPUs use .to_cupy() above and compute with cupy or numba.
45-
field_np[()] = 42.0
46-
# Manual: Compute Mfab END
51+
field[()] = 42.0
52+
# Manual: Compute Mfab Detailed END
53+
54+
# Manual: Compute Mfab Simple START
55+
# finest active MR level, get from a
56+
# simulation's AmrMesh object, e.g.:
57+
# finest_level = sim.finest_level
58+
finest_level = 0 # no MR
59+
60+
# iterate over mesh-refinement levels
61+
for lev in range(finest_level + 1):
62+
# get an existing MultiFab, e.g.,
63+
# from a simulation:
64+
# mfab = sim.get_field(lev=lev)
65+
# Config = sim.extension.Config
66+
67+
field_list = mfab.to_cupy() if Config.have_gpu else mfab.to_numpy()
68+
69+
for field in field_list:
70+
field[()] = 42.0
71+
# Manual: Compute Mfab Simple END
4772

4873

4974
def test_mfab_loop(mfab):

tests/test_particleContainer.py

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -319,24 +319,23 @@ def test_soa_pc_numpy(soa_particle_container, Npart):
319319
class Config:
320320
have_gpu = False
321321

322-
# Manual: Pure SoA Compute PC START
322+
# Manual: Pure SoA Compute PC Detailed START
323323
# code-specific getter function, e.g.:
324324
# pc = sim.get_particles()
325325
# Config = sim.extension.Config
326326

327-
# iterate over mesh-refinement level (no MR: lev=0)
327+
# iterate over mesh-refinement levels
328328
for lvl in range(pc.finest_level + 1):
329-
# get every local chunk of particles
329+
# loop local tiles of particles
330330
for pti in pc.iterator(pc, level=lvl):
331-
# compile-time and runtime attributes in SoA format
331+
# compile-time and runtime attributes
332332
soa = pti.soa().to_cupy() if Config.have_gpu else pti.soa().to_numpy()
333333

334334
# print all particle ids in the chunk
335335
print("idcpu =", soa.idcpu)
336336

337-
# notes:
338-
# Only the next lines are the "HOT LOOP" of the computation.
339-
# For speed, use array operations.
337+
x = soa.real["x"]
338+
y = soa.real["y"]
340339

341340
# write to all particles in the chunk
342341
# note: careful, if you change particle positions, you might need to
@@ -345,15 +344,15 @@ class Config:
345344
soa.real["y"][:] = 0.35
346345
soa.real["z"][:] = 0.40
347346

348-
soa.real["a"][:] = 0.50
349-
soa.real["b"][:] = 0.50
347+
soa.real["a"][:] = x[:] ** 2
348+
soa.real["b"][:] = x[:] + y[:]
350349
soa.real["c"][:] = 0.50
351350
# ...
352351

353352
# all int attributes
354353
for soa_int in soa.int.values():
355354
soa_int[:] = 12
356-
# Manual: Pure SoA Compute PC END
355+
# Manual: Pure SoA Compute PC Detailed END
357356

358357

359358
def test_pc_numpy(particle_container, Npart):
@@ -363,14 +362,14 @@ def test_pc_numpy(particle_container, Npart):
363362
class Config:
364363
have_gpu = False
365364

366-
# Manual: Legacy Compute PC START
365+
# Manual: Legacy Compute PC Detailed START
367366
# code-specific getter function, e.g.:
368367
# pc = sim.get_particles()
369368
# Config = sim.extension.Config
370369

371-
# iterate over mesh-refinement level (no MR: lev=0)
370+
# iterate over mesh-refinement levels
372371
for lvl in range(pc.finest_level + 1):
373-
# get every local chunk of particles
372+
# loop local tiles of particles
374373
for pti in pc.iterator(pc, level=lvl):
375374
# default layout: AoS with positions and idcpu
376375
# note: not part of the new PureSoA particle container layout
@@ -402,7 +401,7 @@ class Config:
402401

403402
for soa_int in soa.int.values():
404403
soa_int[:] = 12
405-
# Manual: Legacy Compute PC END
404+
# Manual: Legacy Compute PC Detailed END
406405

407406

408407
@pytest.mark.skipif(
@@ -440,6 +439,47 @@ def test_soa_pc_df_mpi(soa_particle_container, Npart):
440439
print(df)
441440

442441

442+
@pytest.mark.skipif(
443+
importlib.util.find_spec("pandas") is None, reason="pandas is not available"
444+
)
445+
def test_soa_pc_df(soa_particle_container, Npart):
446+
"""Used in docs/source/usage/compute.rst"""
447+
pc = soa_particle_container
448+
449+
class Config:
450+
have_gpu = False
451+
452+
# Manual: Pure SoA Compute PC Pandas START
453+
# code-specific getter function, e.g.:
454+
# pc = sim.get_particles()
455+
# Config = sim.extension.Config
456+
457+
# local particles on all levels
458+
df = pc.to_df() # this is a copy!
459+
print(df)
460+
461+
# read
462+
print(df["x"])
463+
464+
# write (into copy!)
465+
df["x"] = 0.30
466+
df["y"] = 0.35
467+
df["z"] = 0.40
468+
469+
df["a"] = df["x"] ** 2
470+
df["b"] = df["x"] + df["y"]
471+
df["c"] = 0.50
472+
473+
# int attributes
474+
# df["i1"] = 12
475+
# df["i2"] = 12
476+
# ...
477+
478+
print(df)
479+
480+
# Manual: Pure SoA Compute PC Pandas END
481+
482+
443483
@pytest.mark.skipif(
444484
importlib.util.find_spec("pandas") is None, reason="pandas is not available"
445485
)

0 commit comments

Comments
 (0)