Skip to content

Commit fd53ae3

Browse files
authored
Merge pull request #73 from StagPython/field-coords
* Geometry objects are now aware that cell centers and cell walls are different locations, `r_walls` and `r_centers` replace `r_coord` and similar for other directions. * `r_mesh` and similar are dropped as they are memory consuming and not general (they were only defined for centers, and defining them for any combination of wall and center locations would lead to even more memory consumption). * An extra layer was previously added to cell-centered fields in all geometry for the sole purpose to close the spherical annulus on plots; the used interpolation was only valid for wrapping boundary condition, and forced scripts to actively ignore that extra point. This PR removes that extra point. * Interpolation when plotting scalar fields is disabled by default (but can be enabled through the `+interpolate` switch and the `conf.field.interpolate` configuration key) to avoid giving a sense that the run is more refined than it actually is.
2 parents a054632 + cdb620c commit fd53ae3

File tree

6 files changed

+221
-167
lines changed

6 files changed

+221
-167
lines changed

stagpy/_step.py

Lines changed: 133 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from collections.abc import Mapping
1010
from collections import namedtuple
1111
from itertools import chain
12-
import re
1312

1413
import numpy as np
1514

@@ -22,97 +21,146 @@ class _Geometry:
2221
2322
It is deduced from the information in the header of binary field files
2423
output by StagYY.
25-
26-
Attributes:
27-
nxtot, nytot, nztot, nttot, nptot, nrtot, nbtot (int): number of grid
28-
point in the various directions. Note that nxtot==nttot,
29-
nytot==nptot, nztot==nrtot.
30-
x_coord, y_coord, z_coord, t_coord, p_coord, r_coord (numpy.array):
31-
positions of cell centers in the various directions. Note that
32-
x_coord==t_coord, y_coord==p_coord, z_coord==r_coord.
33-
x_mesh, y_mesh, z_mesh, t_mesh, p_mesh, r_mesh (numpy.array):
34-
mesh in cartesian and curvilinear frames. The last three are
35-
not defined if the geometry is cartesian.
3624
"""
3725

38-
_regexes = (re.compile(r'^n([xyztprb])tot$'), # ntot
39-
re.compile(r'^([xyztpr])_coord$'), # coord
40-
re.compile(r'^([xyz])_mesh$'), # cartesian mesh
41-
re.compile(r'^([tpr])_mesh$')) # curvilinear mesh
42-
43-
def __init__(self, header, par):
26+
def __init__(self, header, step):
4427
self._header = header
45-
self._par = par
46-
self._coords = None
47-
self._cart_meshes = None
48-
self._curv_meshes = None
28+
self._step = step
4929
self._shape = {'sph': False, 'cyl': False, 'axi': False,
5030
'ntot': list(header['nts']) + [header['ntb']]}
5131
self._init_shape()
5232

53-
self._coords = [header['e1_coord'],
54-
header['e2_coord'],
55-
header['e3_coord']]
56-
57-
# instead of adding horizontal rows, should construct two grids:
58-
# - center of cells coordinates (the current one);
59-
# - vertices coordinates on which vector fields are determined,
60-
# which geometrically contains one more row.
61-
62-
# add theta, phi / x, y row to have a continuous field
63-
if not self.twod_yz:
64-
self._coords[0] = np.append(
65-
self.t_coord,
66-
self.t_coord[-1] + self.t_coord[1] - self.t_coord[0])
67-
if not self.twod_xz:
68-
self._coords[1] = np.append(
69-
self.p_coord,
70-
self.p_coord[-1] + self.p_coord[1] - self.p_coord[0])
71-
72-
if self.cartesian:
73-
self._cart_meshes = np.meshgrid(self.x_coord, self.y_coord,
74-
self.z_coord, indexing='ij')
75-
self._curv_meshes = (None, None, None)
33+
def _scale_radius_mo(self, radius):
34+
"""Rescale radius for MO runs."""
35+
if self._step.sdat.par['magma_oceans_in']['magma_oceans_mode']:
36+
return self._header['mo_thick_sol'] * (
37+
radius + self._header['mo_lambda'])
38+
return radius
39+
40+
@crop
41+
def nttot(self):
42+
"""Number of grid point along the x/theta direction."""
43+
return self._shape['ntot'][0]
44+
45+
@crop
46+
def nptot(self):
47+
"""Number of grid point along the y/phi direction."""
48+
return self._shape['ntot'][1]
49+
50+
@crop
51+
def nrtot(self):
52+
"""Number of grid point along the z/r direction."""
53+
return self._shape['ntot'][2]
54+
55+
@crop
56+
def nbtot(self):
57+
"""Number of blocks."""
58+
return self._shape['ntot'][3]
59+
60+
nxtot = nttot
61+
nytot = nptot
62+
nztot = nrtot
63+
64+
@crop
65+
def r_walls(self):
66+
"""Position of FV walls along the z/r direction."""
67+
rgeom = self._header.get("rgeom")
68+
if rgeom is not None:
69+
walls = rgeom[:, 0] + self.rcmb
70+
else:
71+
walls = self._header["e3_coord"] + self.rcmb
72+
walls.append(self._step.rprofs.bounds[1])
73+
return self._scale_radius_mo(walls)
74+
75+
@crop
76+
def r_centers(self):
77+
"""Position of FV centers along the z/r direction."""
78+
rgeom = self._header.get("rgeom")
79+
if rgeom is not None:
80+
walls = rgeom[:-1, 1] + self.rcmb
7681
else:
77-
if self.twod_yz:
78-
self._coords[0] = np.array(np.pi / 2)
79-
elif self.twod_xz:
80-
self._coords[1] = np.array(0)
81-
self._coords[2] += self.rcmb
82-
if par['magma_oceans_in']['magma_oceans_mode']:
83-
self._coords[2] += header['mo_lambda']
84-
self._coords[2] *= header['mo_thick_sol']
85-
t_mesh, p_mesh, r_mesh = np.meshgrid(
86-
self.t_coord, self.p_coord, self.r_coord, indexing='ij')
87-
# compute cartesian coordinates
88-
# z along rotation axis at theta=0
89-
# x at th=90, phi=0
90-
# y at th=90, phi=90
91-
x_mesh = r_mesh * np.cos(p_mesh) * np.sin(t_mesh)
92-
y_mesh = r_mesh * np.sin(p_mesh) * np.sin(t_mesh)
93-
z_mesh = r_mesh * np.cos(t_mesh)
94-
self._cart_meshes = (x_mesh, y_mesh, z_mesh)
95-
self._curv_meshes = (t_mesh, p_mesh, r_mesh)
82+
walls = self._step.rprofs.centers
83+
return self._scale_radius_mo(walls)
84+
85+
@crop
86+
def t_walls(self):
87+
"""Position of FV walls along x/theta."""
88+
if self.threed or self.twod_xz:
89+
if self.yinyang:
90+
tmin, tmax = -np.pi / 4, np.pi / 4
91+
elif self.curvilinear:
92+
# should take theta_position/theta_center into account
93+
tmin = 0
94+
tmax = min(np.pi,
95+
self._step.sdat.par['geometry']['aspect_ratio'][0])
96+
else:
97+
tmin = 0
98+
tmax = self._step.sdat.par['geometry']['aspect_ratio'][0]
99+
return np.linspace(tmin, tmax, self.nttot + 1)
100+
# twoD YZ
101+
center = np.pi / 2 if self.curvilinear else 0
102+
d_t = (self.p_walls[1] - self.p_walls[0]) / 2
103+
return np.array([center - d_t, center + d_t])
104+
105+
@crop
106+
def t_centers(self):
107+
"""Position of FV centers along x/theta."""
108+
return (self.t_walls[:-1] + self.t_walls[1:]) / 2
109+
110+
@crop
111+
def p_walls(self):
112+
"""Position of FV walls along y/phi."""
113+
if self.threed or self.twod_yz:
114+
if self.yinyang:
115+
pmin, pmax = -3 * np.pi / 4, 3 * np.pi / 4
116+
elif self.curvilinear:
117+
pmin = 0
118+
pmax = min(2 * np.pi,
119+
self._step.sdat.par['geometry']['aspect_ratio'][1])
120+
else:
121+
pmin = 0
122+
pmax = self._step.sdat.par['geometry']['aspect_ratio'][1]
123+
return np.linspace(pmin, pmax, self.nptot + 1)
124+
# twoD YZ
125+
d_p = (self.t_walls[1] - self.t_walls[0]) / 2
126+
return np.array([-d_p, d_p])
127+
128+
@crop
129+
def p_centers(self):
130+
"""Position of FV centers along y/phi."""
131+
return (self.p_walls[:-1] + self.p_walls[1:]) / 2
132+
133+
z_walls = r_walls
134+
z_centers = r_centers
135+
x_walls = t_walls
136+
x_centers = t_centers
137+
y_walls = p_walls
138+
y_centers = p_centers
96139

97140
def _init_shape(self):
98141
"""Determine shape of geometry."""
99-
shape = self._par['geometry']['shape'].lower()
142+
shape = self._step.sdat.par['geometry']['shape'].lower()
100143
aspect = self._header['aspect']
101-
if self.rcmb is not None and self.rcmb >= 0:
144+
if self._header['rcmb'] is not None and self._header['rcmb'] >= 0:
102145
# curvilinear
103146
self._shape['cyl'] = self.twod_xz and (shape == 'cylindrical' or
104147
aspect[0] >= np.pi)
105148
self._shape['sph'] = not self._shape['cyl']
106-
elif self.rcmb is None:
107-
self._header['rcmb'] = self._par['geometry']['r_cmb']
108-
if self.rcmb >= 0:
149+
elif self._header['rcmb'] is None:
150+
self._header['rcmb'] = self._step.sdat.par['geometry']['r_cmb']
151+
if self._header['rcmb'] >= 0:
109152
if self.twod_xz and shape == 'cylindrical':
110153
self._shape['cyl'] = True
111154
elif shape == 'spherical':
112155
self._shape['sph'] = True
113156
self._shape['axi'] = self.cartesian and self.twod_xz and \
114157
shape == 'axisymmetric'
115158

159+
@crop
160+
def rcmb(self):
161+
"""Radius of CMB, 0 in cartesian geometry."""
162+
return max(self._header["rcmb"], 0)
163+
116164
@property
117165
def cartesian(self):
118166
"""Whether the grid is in cartesian geometry."""
@@ -166,26 +214,14 @@ def at_z(self, zval):
166214
"""
167215
if self.curvilinear:
168216
zval += self.rcmb
169-
return np.argmin(np.abs(self.z_coord - zval))
217+
return np.argmin(np.abs(self.z_centers - zval))
170218

171219
def at_r(self, rval):
172220
"""Return ir closest to given rval position.
173221
174222
If called in cartesian geometry, this is equivalent to :meth:`at_z`.
175223
"""
176-
return np.argmin(np.abs(self.r_coord - rval))
177-
178-
def __getattr__(self, attr):
179-
# provide nDtot, D_coord, D_mesh and nbtot
180-
# with D = x, y, z or t, p, r
181-
for reg, dat in zip(self._regexes, (self._shape['ntot'],
182-
self._coords,
183-
self._cart_meshes,
184-
self._curv_meshes)):
185-
match = reg.match(attr)
186-
if match is not None:
187-
return dat['xtypzrb'.index(match.group(1)) // 2]
188-
return self._header[attr]
224+
return np.argmin(np.abs(self.r_centers - rval))
189225

190226

191227
class _Fields(Mapping):
@@ -226,13 +262,6 @@ def __getitem__(self, name):
226262
header, fields = parsed_data
227263
self._cropped__header = header
228264
for fld_name, fld in zip(fld_names, fields):
229-
if self._header['xyp'] == 0:
230-
if not self.geom.twod_yz:
231-
newline = (fld[:1, ...] + fld[-1:, ...]) / 2
232-
fld = np.concatenate((fld, newline), axis=0)
233-
if not self.geom.twod_xz:
234-
newline = (fld[:, :1, ...] + fld[:, -1:, ...]) / 2
235-
fld = np.concatenate((fld, newline), axis=1)
236265
self._set(fld_name, fld)
237266
return self._data[name]
238267

@@ -317,7 +346,7 @@ def geom(self):
317346
None if not available for this time step.
318347
"""
319348
if self._header is not None:
320-
return _Geometry(self._header, self.step.sdat.par)
349+
return _Geometry(self._header, self.step)
321350

322351

323352
class _Tracers:
@@ -430,12 +459,15 @@ def centers(self):
430459
def walls(self):
431460
"""Radial position of cell walls."""
432461
rbot, rtop = self.bounds
433-
centers = self.centers
434-
# assume walls are mid-way between T-nodes
435-
# could be T-nodes at center between walls
436-
walls = (centers[:-1] + centers[1:]) / 2
437-
walls = np.insert(walls, 0, rbot)
438-
walls = np.append(walls, rtop)
462+
try:
463+
walls = self.step.fields.geom.r_walls
464+
except error.StagpyError:
465+
# assume walls are mid-way between T-nodes
466+
# could be T-nodes at center between walls
467+
centers = self.centers
468+
walls = (centers[:-1] + centers[1:]) / 2
469+
walls = np.insert(walls, 0, rbot)
470+
walls = np.append(walls, rtop)
439471
return walls
440472

441473
@crop
@@ -451,8 +483,10 @@ def bounds(self):
451483
rcmb = step.sdat.par['geometry']['r_cmb']
452484
if step.sdat.par['geometry']['shape'].lower() == 'cartesian':
453485
rcmb = 0
454-
rcmb = max(rcmb, 0)
455-
return rcmb, rcmb + 1
486+
rbot = max(rcmb, 0)
487+
thickness = (step.sdat.scales.length
488+
if step.sdat.par['switches']['dimensional_units'] else 1)
489+
return rbot, rbot + thickness
456490

457491

458492
class Step:

stagpy/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def _index_collection(arg):
104104
('shift', Conf(None, True, None, {'type': int},
105105
False, 'shift plot horizontally')),
106106
('timelabel', switch_opt(False, None, 'add label with time')),
107-
('interpolate', switch_opt(True, None, 'apply Gouraud shading')),
107+
('interpolate', switch_opt(False, None, 'apply Gouraud shading')),
108108
('colorbar', switch_opt(True, None, 'add color bar to plot')),
109109
('ix', Conf(None, True, None, {'type': int},
110110
False, 'x-index of slice for 3D fields')),

0 commit comments

Comments
 (0)