Skip to content

Commit 1912bb2

Browse files
authored
Merge pull request #77 from StagPython/field-meta
snap.fields items come with their metadata
2 parents 8bab76c + 96f2014 commit 1912bb2

File tree

8 files changed

+77
-58
lines changed

8 files changed

+77
-58
lines changed

docs/sources/stagyydata.rst

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,14 @@ Vector and scalar fields are accessible through :attr:`Step.fields
189189
<stagpy._step.Step.fields>` using their name as key. For example, the
190190
temperature field of the 100th snapshot is obtained with
191191
``sdat.snaps[100].fields['T']``. Valid names of fields can be obtained by
192-
running ``% stagpy var``. Fields are four dimensional arrays, with indices in
193-
the order x, y, z and block.
192+
running ``% stagpy var``. Items are named tuples with two elements:
193+
194+
- :data:`values`: the field itself, a four dimensional array with indices in
195+
the order x, y, z and block;
196+
- :data:`meta`: metadata of the field, also a named tuple with:
197+
198+
- :data:`description`: explanation of what the field is;
199+
- :data:`dim`: the dimension of the field (if applicable) in SI units.
194200

195201
Tracers data
196202
------------

docs/sources/tuto.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Instantiating and using this class is rather simple::
9191

9292
# temperature field of the 10000th time step
9393
# (will be None if no snapshot is available at this timestep)
94-
temp_field = sdat.steps[10000].fields['T']
94+
temp_field = sdat.steps[10000].fields['T'].values
9595

9696
# iterate through snaps 100, 105, 110... up to the last one
9797
for snap in sdat.snaps[100::5]:

stagpy/_step.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,18 @@ def at_r(self, rval):
224224
return np.argmin(np.abs(self.r_centers - rval))
225225

226226

227+
Field = namedtuple('Field', ['values', 'meta'])
228+
229+
227230
class _Fields(Mapping):
228231
"""Fields data structure.
229232
230233
The :attr:`Step.fields` attribute is an instance of this class.
231234
232235
:class:`_Fields` inherits from :class:`collections.abc.Mapping`. Keys are
233-
fields names defined in :data:`stagpy.phyvars.[S]FIELD[_EXTRA]`.
236+
fields names defined in :data:`stagpy.phyvars.[S]FIELD[_EXTRA]`. Each item
237+
is a name tuple ('values', 'meta'), respectively the field itself, and a
238+
:class:`stagpy.phyvars.Varf` instance with relevant metadata.
234239
235240
Attributes:
236241
step (:class:`Step`): the step object owning the :class:`_Fields`
@@ -252,7 +257,10 @@ def __getitem__(self, name):
252257
if name in self._vars:
253258
fld_names, parsed_data = self._get_raw_data(name)
254259
elif name in self._extra:
255-
self._data[name] = self._extra[name].description(self.step)
260+
meta = self._extra[name]
261+
field = meta.description(self.step)
262+
meta = phyvars.Varf(_helpers.baredoc(meta.description), meta.dim)
263+
self._data[name] = Field(field, meta)
256264
return self._data[name]
257265
else:
258266
raise error.UnknownFieldVarError(name)
@@ -322,7 +330,7 @@ def _set(self, name, fld):
322330
while len(col_fld) > sdat.nfields_max:
323331
istep, fld_name = col_fld.pop(0)
324332
del sdat.steps[istep].fields[fld_name]
325-
self._data[name] = fld
333+
self._data[name] = Field(fld, self._vars[name])
326334

327335
def __delitem__(self, name):
328336
if name in self._data:

stagpy/field.py

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ def _threed_extract(step, var, walls=False):
4646
i_x = i_y = slice(None)
4747
varx, vary = var + '1', var + '2'
4848
if is_vector:
49-
data = (step.fields[varx][i_x, i_y, i_z, 0],
50-
step.fields[vary][i_x, i_y, i_z, 0])
49+
data = (step.fields[varx].values[i_x, i_y, i_z, 0],
50+
step.fields[vary].values[i_x, i_y, i_z, 0])
5151
else:
52-
data = step.fields[var][i_x, i_y, i_z, 0]
52+
data = step.fields[var].values[i_x, i_y, i_z, 0]
5353
return (xcoord, ycoord), data
5454

5555

@@ -77,11 +77,11 @@ def get_meshes_fld(step, var, walls=False):
7777
var (str): scalar field name.
7878
walls (bool): consider the walls as the relevant mesh.
7979
Returns:
80-
tuple of :class:`numpy.array`: xmesh, ymesh, fld
81-
2D arrays containing respectively the x position, y position, and
82-
the value of the requested field.
80+
tuple of :class:`numpy.array`: xmesh, ymesh, fld, meta
81+
2D arrays containing respectively the x position, y position, the
82+
values and the metadata of the requested field.
8383
"""
84-
fld = step.fields[var]
84+
fld, meta = step.fields[var]
8585
hwalls = (walls or fld.shape[0] != step.geom.nxtot or
8686
fld.shape[1] != step.geom.nytot)
8787
if step.geom.threed and step.geom.cartesian:
@@ -99,7 +99,7 @@ def get_meshes_fld(step, var, walls=False):
9999
fld = fld[0, :, :, 0]
100100
if step.geom.cartesian:
101101
xmesh, ymesh = np.meshgrid(xcoord, ycoord, indexing='ij')
102-
return xmesh, ymesh, fld
102+
return xmesh, ymesh, fld, meta
103103

104104

105105
def get_meshes_vec(step, var):
@@ -119,17 +119,17 @@ def get_meshes_vec(step, var):
119119
(xcoord, ycoord), (vec1, vec2) = _threed_extract(step, var)
120120
elif step.geom.twod_xz:
121121
xcoord, ycoord = step.geom.x_walls, step.geom.z_centers
122-
vec1 = step.fields[var + '1'][:, 0, :, 0]
123-
vec2 = step.fields[var + '3'][:, 0, :, 0]
122+
vec1 = step.fields[var + '1'].values[:, 0, :, 0]
123+
vec2 = step.fields[var + '3'].values[:, 0, :, 0]
124124
elif step.geom.cartesian and step.geom.twod_yz:
125125
xcoord, ycoord = step.geom.y_walls, step.geom.z_centers
126-
vec1 = step.fields[var + '2'][0, :, :, 0]
127-
vec2 = step.fields[var + '3'][0, :, :, 0]
126+
vec1 = step.fields[var + '2'].values[0, :, :, 0]
127+
vec2 = step.fields[var + '3'].values[0, :, :, 0]
128128
else: # spherical yz
129129
pcoord = step.geom.p_walls
130130
pmesh = np.outer(pcoord, np.ones(step.geom.nrtot))
131-
vec_phi = step.fields[var + '2'][0, :, :, 0]
132-
vec_r = step.fields[var + '3'][0, :, :, 0]
131+
vec_phi = step.fields[var + '2'].values[0, :, :, 0]
132+
vec_r = step.fields[var + '3'].values[0, :, :, 0]
133133
vec1 = vec_r * np.cos(pmesh) - vec_phi * np.sin(pmesh)
134134
vec2 = vec_phi * np.cos(pmesh) + vec_r * np.sin(pmesh)
135135
pcoord, rcoord = step.geom.p_walls, step.geom.r_centers
@@ -162,17 +162,12 @@ def plot_scalar(step, var, field=None, axis=None, **extra):
162162
:func:`~matplotlib.axes.Axes.pcolormesh`, and the colorbar returned
163163
by :func:`matplotlib.pyplot.colorbar`.
164164
"""
165-
if var in phyvars.FIELD:
166-
meta = phyvars.FIELD[var]
167-
else:
168-
meta = phyvars.FIELD_EXTRA[var]
169-
meta = phyvars.Varf(_helpers.baredoc(meta.description), meta.dim)
170165
if step.geom.threed and step.geom.spherical:
171166
raise NotAvailableError(
172167
'plot_scalar not implemented for 3D spherical geometry')
173168

174-
xmesh, ymesh, fld = get_meshes_fld(step, var,
175-
walls=not conf.field.interpolate)
169+
xmesh, ymesh, fld, meta = get_meshes_fld(step, var,
170+
walls=not conf.field.interpolate)
176171
if conf.field.interpolate and \
177172
step.geom.spherical and step.geom.twod_yz and \
178173
fld.shape[0] == step.geom.nytot:
@@ -254,7 +249,7 @@ def plot_iso(axis, step, var, **extra):
254249
extra (dict): options that will be passed on to
255250
:func:`matplotlib.axes.Axes.contour`.
256251
"""
257-
xmesh, ymesh, fld = get_meshes_fld(step, var)
252+
xmesh, ymesh, fld, _ = get_meshes_fld(step, var)
258253
if conf.field.shift:
259254
fld = np.roll(fld, conf.field.shift, axis=0)
260255
extra_opts = dict(linewidths=1)
@@ -297,11 +292,8 @@ def _findminmax(sdat, sovs):
297292
for step in sdat.walk.filter(snap=True):
298293
for var in sovs:
299294
if var in step.fields:
300-
if var in phyvars.FIELD:
301-
dim = phyvars.FIELD[var].dim
302-
else:
303-
dim = phyvars.FIELD_EXTRA[var].dim
304-
field, _ = sdat.scale(step.fields[var], dim)
295+
field, meta = step.fields[var]
296+
field, _ = sdat.scale(field, meta.dim)
305297
if var in minmax:
306298
minmax[var] = (min(minmax[var][0], np.nanmin(field)),
307299
max(minmax[var][1], np.nanmax(field)))

stagpy/plates.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313

1414
def detect_plates_vzcheck(step, vz_thres_ratio=0):
1515
"""Detect plates and check with vz and plate size."""
16-
v_z = step.fields['v3'][0, :-1, :, 0]
17-
v_x = step.fields['v2'][0, :, :, 0]
18-
tcell = step.fields['T'][0, :, :, 0]
16+
v_z = step.fields['v3'].values[0, :-1, :, 0]
17+
v_x = step.fields['v2'].values[0, :, :, 0]
18+
tcell = step.fields['T'].values[0, :, :, 0]
1919
n_z = step.geom.nztot
2020
nphi = step.geom.nptot
2121
r_c = step.geom.r_centers
@@ -68,7 +68,7 @@ def detect_plates_vzcheck(step, vz_thres_ratio=0):
6868

6969
def detect_plates(step, vrms_surface, fids, time):
7070
"""Detect plates using derivative of horizontal velocity."""
71-
vphi = step.fields['v2'][0, :, :, 0]
71+
vphi = step.fields['v2'].values[0, :, :, 0]
7272
ph_coord = step.geom.p_centers
7373

7474
if step.sdat.par['boundaries']['air_layer']:
@@ -123,7 +123,7 @@ def detect_plates(step, vrms_surface, fids, time):
123123

124124
dv_ridge = dvph2[arggreat_dv]
125125
if 'age' in conf.plates.plot:
126-
agefld = step.fields['age'][0, :, :, 0]
126+
agefld = step.fields['age'].values[0, :, :, 0]
127127
age_surface = np.ma.masked_where(agefld[:, indsurf] < 0.00001,
128128
agefld[:, indsurf])
129129
age_surface_dim = age_surface * vrms_surface *\
@@ -180,9 +180,9 @@ def plot_plate_limits_field(axis, rcmb, ridges, trenches):
180180
def plot_plates(step, time, vrms_surface, trench, ridge, agetrench,
181181
topo, fids):
182182
"""Handle plotting stuff."""
183-
vphi = step.fields['v2'][0, :, :, 0]
184-
tempfld = step.fields['T'][0, :, :, 0]
185-
concfld = step.fields['c'][0, :, :, 0]
183+
vphi = step.fields['v2'].values[0, :, :, 0]
184+
tempfld = step.fields['T'].values[0, :, :, 0]
185+
concfld = step.fields['c'].values[0, :, :, 0]
186186
timestep = step.isnap
187187

188188
if step.sdat.par['boundaries']['air_layer']:
@@ -296,7 +296,7 @@ def plot_plates(step, time, vrms_surface, trench, ridge, agetrench,
296296

297297
# plotting velocity and second invariant of stress
298298
if 'str' in conf.plates.plot:
299-
stressfld = step.fields['sII'][0, :, :, 0]
299+
stressfld = step.fields['sII'].values[0, :, :, 0]
300300
fig0, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(12, 8))
301301
ax1.plot(step.geom.p_walls, vphi[:, indsurf], label='Vel')
302302
ax1.axhline(y=0, xmin=0, xmax=2 * np.pi,
@@ -344,7 +344,7 @@ def plot_plates(step, time, vrms_surface, trench, ridge, agetrench,
344344

345345
# plotting velocity and age at surface
346346
if 'age' in conf.plates.plot:
347-
agefld = step.fields['age'][0, :, :, 0]
347+
agefld = step.fields['age'].values[0, :, :, 0]
348348
age_surface = np.ma.masked_where(
349349
agefld[:, indsurf] < 0.00001, agefld[:, indsurf])
350350
age_surface_dim = (age_surface * vrms_surface * conf.scaling.ttransit /
@@ -467,7 +467,7 @@ def lithospheric_stress(step, trench, ridge, time):
467467
timestep = step.isnap
468468
base_lith = step.geom.rcmb + 1 - 0.105
469469

470-
stressfld = step.fields['sII'][0, :, :, 0]
470+
stressfld = step.fields['sII'].values[0, :, :, 0]
471471
r_centers = np.outer(np.ones(stressfld.shape[0]), step.geom.r_centers)
472472
stressfld = np.ma.masked_where(r_centers < base_lith, stressfld)
473473

@@ -485,10 +485,10 @@ def lithospheric_stress(step, trench, ridge, time):
485485
saveplot(fig, 'lith', timestep)
486486

487487
# velocity
488-
vphi = step.fields['v2'][0, :, :, 0]
488+
vphi = step.fields['v2'].values[0, :, :, 0]
489489

490490
# position of continents
491-
concfld = step.fields['c'][0, :, :, 0]
491+
concfld = step.fields['c'].values[0, :, :, 0]
492492
if step.sdat.par['boundaries']['air_layer']:
493493
# we are a bit below the surface; delete "-some number"
494494
# to be just below
@@ -612,12 +612,12 @@ def main_plates(sdat):
612612
agetrenches, topo, fids)
613613

614614
# prepare for continent plotting
615-
concfld = step.fields['c'][0, :, :, 0]
615+
concfld = step.fields['c'].values[0, :, :, 0]
616616
continentsfld = np.ma.masked_where(
617617
concfld < 3, concfld) # plotting continents, to-do
618618
continentsfld = continentsfld / continentsfld
619619

620-
temp = step.fields['T'][0, :, :, 0]
620+
temp = step.fields['T'].values[0, :, :, 0]
621621
tgrad = (temp[:, isurf - 1] - temp[:, isurf]) /\
622622
(step.geom.r_centers[isurf] - step.geom.r_centers[isurf - 1])
623623

@@ -626,7 +626,7 @@ def main_plates(sdat):
626626
io_surface(timestep, time, fids[4], topo[:, 1])
627627
if 'age' in conf.plates.plot:
628628
io_surface(timestep, time, fids[5],
629-
step.fields['age'][0, :, isurf, 0])
629+
step.fields['age'].values[0, :, isurf, 0])
630630

631631
# plot viscosity field with position of trenches and ridges
632632
etamin, _ = sdat.scale(1e-2, 'Pa')

stagpy/processing.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,13 @@ def stream_function(step):
281281
"""
282282
if step.geom.twod_yz:
283283
x_coord = step.geom.y_walls
284-
v_x = step.fields['v2'][0, :, :, 0]
285-
v_z = step.fields['v3'][0, :, :, 0]
284+
v_x = step.fields['v2'].values[0, :, :, 0]
285+
v_z = step.fields['v3'].values[0, :, :, 0]
286286
shape = (1, v_x.shape[0], v_x.shape[1], 1)
287287
elif step.geom.twod_xz and step.geom.cartesian:
288288
x_coord = step.geom.x_walls
289-
v_x = step.fields['v1'][:, 0, :, 0]
290-
v_z = step.fields['v3'][:, 0, :, 0]
289+
v_x = step.fields['v1'].values[:, 0, :, 0]
290+
v_z = step.fields['v3'].values[:, 0, :, 0]
291291
shape = (v_x.shape[0], 1, v_x.shape[1], 1)
292292
else:
293293
raise NotAvailableError('Stream function only implemented in '

tests/test_field.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,26 @@ def test_valid_field_var_invalid():
2727
assert not stagpy.field.valid_field_var('dummyfieldvar')
2828

2929

30-
def test_get_meshes_fld(step):
31-
xmesh, ymesh, fld = stagpy.field.get_meshes_fld(step, 'T')
30+
def test_get_meshes_fld_no_walls(step):
31+
xmesh, ymesh, fld, meta = stagpy.field.get_meshes_fld(step, 'T',
32+
walls=False)
3233
assert len(fld.shape) == 2
33-
assert xmesh.shape == ymesh.shape == fld.shape
34+
assert xmesh.shape[0] == ymesh.shape[0] == fld.shape[0]
35+
assert xmesh.shape[1] == ymesh.shape[1] == fld.shape[1]
36+
assert meta.description == 'Temperature'
37+
38+
39+
def test_get_meshes_fld_walls(step):
40+
xmesh, ymesh, fld, meta = stagpy.field.get_meshes_fld(step, 'T',
41+
walls=True)
42+
assert len(fld.shape) == 2
43+
assert xmesh.shape[0] == ymesh.shape[0] == fld.shape[0] + 1
44+
assert xmesh.shape[1] == ymesh.shape[1] == fld.shape[1] + 1
45+
assert meta.description == 'Temperature'
3446

3547

3648
def test_get_meshes_vec(step):
3749
xmesh, ymesh, vec1, vec2 = stagpy.field.get_meshes_vec(step, 'v')
3850
assert len(vec1.shape) == 2
39-
assert xmesh.shape == ymesh.shape == vec1.shape == vec2.shape
51+
assert xmesh.shape[0] == ymesh.shape[0] == vec1.shape[0] == vec2.shape[0]
52+
assert xmesh.shape[1] == ymesh.shape[1] == vec1.shape[1] == vec2.shape[1]

tests/test_processing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,4 @@ def test_energy_prof(step):
5252

5353
def test_stream_function(step):
5454
psi = processing.stream_function(step)
55-
assert psi.shape[1:3] == step.fields['v3'].shape[1:3]
55+
assert psi.shape[1:3] == step.fields['v3'].values.shape[1:3]

0 commit comments

Comments
 (0)