Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define Regions and add method to get BoutDataArrays in a region #107

Merged
merged 72 commits into from
Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
e31ed50
Simplify check that 'n_aligned' does not exist yet
johnomotani Mar 18, 2020
cf86982
Create topological regions
johnomotani Mar 15, 2020
3969e0b
Convert plotting to use fromRegion
johnomotani Mar 15, 2020
d9f873f
Fix coordinates of guard cells added to a region to make them continuous
johnomotani Mar 15, 2020
4e563c1
Copy regions to DataArray members
johnomotani Mar 16, 2020
e648514
Region constructor requires keyword arguments, rename arguments
johnomotani Mar 16, 2020
7f45229
Calculate x- and y-coordinate ranges in Region constructor
johnomotani Mar 16, 2020
65093df
Add calculation of local nx and ny to Region
johnomotani Mar 16, 2020
cbdeb8d
Rename regions->da_regions in plotfuncs.py and animate.py
johnomotani Mar 16, 2020
871ac65
Fix gridlines option of plot2d_wrapper
johnomotani Mar 16, 2020
8fa290d
Save region in DataArrays for regions
johnomotani Mar 16, 2020
81757da
Plotting utils use a dict of regions
johnomotani Mar 16, 2020
8b4a134
Simplify plotting utilities using generic regions
johnomotani Mar 16, 2020
2a835c4
Add docstring for Region.__init__() parameters
johnomotani Mar 16, 2020
6b6de8b
Move region creation into add_toroidal_geometry_coords
johnomotani Mar 16, 2020
3756669
Better validation of topology inputs
johnomotani Mar 16, 2020
2e30729
Create correct topology in grid for unit tests
johnomotani Mar 16, 2020
c198c9e
Remove regions from variables and coordinates before saving
johnomotani Mar 16, 2020
2773d09
Add 'x' as a global coordinate
johnomotani Mar 16, 2020
d967383
Remove 'x' coordinate for s-alpha geometry
johnomotani Mar 16, 2020
f4614de
__repr__ method for Region class
johnomotani Mar 17, 2020
ee41260
Pass explicit transpose_coords argument to transpose to silence warnings
johnomotani Mar 17, 2020
797426e
Option to create test datasets with different topologies
johnomotani Mar 18, 2020
e435514
Check for possible out-of-bounds slices in fromRegion
johnomotani Mar 18, 2020
417a615
Exclude 'boundary' cells from core region of core and limiter topologies
johnomotani Mar 18, 2020
9887863
Fix _create_regions_toroidal for case with no upper targets
johnomotani Mar 18, 2020
79f5d8c
Test of regions in core topology
johnomotani Mar 18, 2020
baad2ac
Make capitalisation of SOL consistent when creating regions
johnomotani Mar 18, 2020
1797157
Fix boundary indices for SOL region in sol and limiter topologies
johnomotani Mar 18, 2020
7d9f73d
Test for regions in sol topology
johnomotani Mar 18, 2020
b3626d1
Fix some whitespace in fromRegion
johnomotani Mar 18, 2020
81f9025
Add optional mxg, myg arguments to Region.get*GuardsSlices()
johnomotani Mar 19, 2020
dbc70aa
Remove unnecessary print from test_region_sol
johnomotani Mar 19, 2020
21bfe93
Test for fromRegion using limiter topology
johnomotani Mar 19, 2020
a4fef89
Use join='exact' argument to xr.concat()
johnomotani Mar 19, 2020
de2abd4
xr.concat takes attrs from first variable - ensure region is correct
johnomotani Mar 19, 2020
0f3441f
Ensure coordinates assigned to guard-cell da's have correct size
johnomotani Mar 19, 2020
123d6cb
Work-arounds for limiter in BoutDataArray.fromRegion()
johnomotani Mar 19, 2020
1308cca
Fix region cases to handle -ybndry=0 at upper boundary
johnomotani Mar 19, 2020
4edea60
Test of regions in single-null topology
johnomotani Mar 19, 2020
7c677a7
Fix typo in single-null setup of _create_regions_toroidal
johnomotani Mar 19, 2020
3d22cc0
Define MYSUB variable for setting up topology in create_bout_ds
johnomotani Mar 19, 2020
d723161
Fix test for connected-double-null in _get_topology()
johnomotani Mar 19, 2020
247e185
Add ny_inner argument to create_bout_grid_ds()
johnomotani Mar 19, 2020
2c620c2
Fix error comments in create_bout_ds
johnomotani Mar 19, 2020
a4c820a
Test of regions in connected-double-null topology
johnomotani Mar 19, 2020
735ff80
Test of regions in disconnected-double-null topology
johnomotani Mar 19, 2020
0a2b1eb
Fix setting ixseps2 in create_bout_ds
johnomotani Mar 19, 2020
c6d84ce
Test for with_guards argument of BoutDataArray.fromRegion()
johnomotani Mar 20, 2020
f429eb2
Simplify with_guards arguments in BoutDataArray.fromRegion()
johnomotani Mar 20, 2020
f5aff90
PEP8 fixes
johnomotani Mar 20, 2020
1bf436b
Use join='exact' for stricter checking in load.py
johnomotani Mar 20, 2020
b04ceb6
Workaround for old attr propagation behaviour in xarray.concat()
johnomotani Mar 20, 2020
230bf1b
Test for regions in xpoint topology
johnomotani Mar 20, 2020
9e53543
Reduce grid sizes to speed up region tests
johnomotani Mar 21, 2020
bf8a5e4
Change version of numpy for minimum versions test on Travis
johnomotani Mar 21, 2020
4c5eb9d
Tidy up reading with_guards using dict.get()
johnomotani Mar 23, 2020
1836bbe
Rename argument region->name for BoutDataArray.fromRegion()
johnomotani Mar 23, 2020
44cd48b
Rename fromRegion to from_region
johnomotani Mar 23, 2020
b89df77
Don't use self.data.bout.* in BoutDataArray
johnomotani Mar 23, 2020
e143d02
Rename getSlices() to get_slices()
johnomotani Mar 23, 2020
5a86fa6
Rename get*GuardsSlices() to get_*_guards_slices()
johnomotani Mar 23, 2020
0546921
Use f-string for Region.__repr__
johnomotani Mar 23, 2020
d738c64
Region.get*slices() methods return dict of slices instead of tuple
johnomotani Mar 23, 2020
ae4ff96
Move guards logic from from_region() to utility functions in region.py
johnomotani Mar 23, 2020
71c0160
Merge branch 'master' into regions
johnomotani Mar 23, 2020
2fe05ef
Move mxg and myg checks inside _concat_*_guards functions
johnomotani Mar 24, 2020
5255fde
Better names for connection variables and arguments
johnomotani Mar 24, 2020
c4004e9
Update Region.connection_* variables in plotting/utils.py
johnomotani Mar 25, 2020
0b7e04f
Merge branch 'master' into regions
johnomotani Jun 18, 2020
3fb735a
Fixes for re-loading a Dataset with Regions
johnomotani Jun 18, 2020
ab4f52a
__eq__() method for Region
johnomotani Jun 18, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion xbout/boutdataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from .plotting.animate import animate_poloidal, animate_pcolormesh, animate_line
from .plotting import plotfuncs
from .plotting.utils import _create_norm
from .region import (Region, _concat_inner_guards, _concat_outer_guards,
_concat_lower_guards, _concat_upper_guards)


@register_dataarray_accessor('bout')
Expand Down Expand Up @@ -97,7 +99,7 @@ def _shiftZ(self, zShift):
for dim in phase.dims:
extra_dims.remove(dim)
phase = phase.expand_dims(extra_dims)
phase = phase.transpose(*fft_dims)
phase = phase.transpose(*fft_dims, transpose_coords=True)

data_shifted_fft = data_fft * np.exp(phase.data)

Expand Down Expand Up @@ -131,6 +133,56 @@ def fromFieldAligned(self):
result["direction_y"] = "Standard"
return result

def from_region(self, name, with_guards=None):
"""
Get a logically-rectangular section of data from a certain region.
Includes guard cells from neighbouring regions.

Parameters
----------
name : str
Region to get data for
with_guards : int or dict of int, optional
Number of guard cells to include, by default use MXG and MYG from BOUT++.
Pass a dict to set different numbers for different coordinates.
"""

region = self.data.regions[name]
xcoord = self.data.metadata['bout_xdim']
ycoord = self.data.metadata['bout_ydim']

if with_guards is None:
mxg = self.data.metadata['MXG']
myg = self.data.metadata['MYG']
else:
try:
mxg = with_guards.get(xcoord, self.data.metadata['MXG'])
myg = with_guards.get(ycoord, self.data.metadata['MYG'])
except AttributeError:
mxg = with_guards
myg = with_guards

da = self.data.isel(region.get_slices())
da.attrs['region'] = region

if mxg > 0:
johnomotani marked this conversation as resolved.
Show resolved Hide resolved
if region.connection_inner is not None:
# get inner x-guard cells for da from the global array
da = _concat_inner_guards(da, self.data, mxg)
if region.connection_outer is not None:
# get outer x-guard cells for da from the global array
da = _concat_outer_guards(da, self.data, mxg)

if myg > 0:
if region.connection_lower is not None:
# get lower y-guard cells from the global array
da = _concat_lower_guards(da, self.data, mxg, myg)
if region.connection_upper is not None:
# get upper y-guard cells from the global array
da = _concat_upper_guards(da, self.data, mxg, myg)

return da

def animate2D(self, animate_over='t', x=None, y=None, animate=True, fps=10,
save_as=None, ax=None, poloidal_plot=False, logscale=None, **kwargs):
"""
Expand Down
8 changes: 8 additions & 0 deletions xbout/boutdataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ def dict_to_attrs(obj, key):
except KeyError:
pass

# Do not need to save regions as these can be reconstructed from the metadata
del to_save.attrs['regions']
for var in chain(to_save.data_vars, to_save.coords):
try:
del to_save[var].attrs['regions']
except KeyError:
pass

if separate_vars:
# Save each major variable to a different netCDF file

Expand Down
17 changes: 15 additions & 2 deletions xbout/geometries.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import xarray as xr
import numpy as np

from .region import Region, _create_regions_toroidal
from .utils import _set_attrs_on_all_vars

REGISTERED_GEOMETRIES = {}


Expand Down Expand Up @@ -65,6 +67,7 @@ def apply_geometry(ds, geometry_name, *, coordinates=None, grid=None):
updated_ds = add_geometry_coords(ds, grid=grid)
else:
updated_ds = add_geometry_coords(ds)

return updated_ds


Expand Down Expand Up @@ -143,15 +146,21 @@ def add_toroidal_geometry_coords(ds, *, coordinates=None, grid=None):
ds = ds.rename(y=coordinates['y'])

# Add 1D Orthogonal Toroidal coordinates
# Make index 'x' a coordinate, useful for handling global indexing
nx = ds.dims['x']
ds = ds.assign_coords(x=np.arange(nx))
ny = ds.dims[coordinates['y']]
# dy should always be constant in x, so it is safe to slice to x=0
# dy should always be constant in x, so it is safe to slice to x=0.
# [The y-coordinate has to be a 1d coordinate that labels x-z slices of the grid
# (similarly x-coordinate is 1d coordinate that labels y-z slices and z-coordinate is
# a 1d coordinate that labels x-y slices). A coordinate might have different values
# in disconnected regions, but there are no branch-cuts allowed in the x-direction in
# BOUT++ (at least for the momement), so the y-coordinate has to be 1d and
# single-valued. Therefore similarly dy has to be 1d and single-valued.]
dy = ds['dy'].isel(x=0)
# Need drop=True so that the result does not have an x-coordinate value which
# prevents it being added as a coordinate.
dy = ds['dy'].isel(x=0, drop=True)

# calculate theta at the centre of each cell
theta = dy.cumsum(keep_attrs=True) - dy/2.
ds = ds.assign_coords(**{coordinates['y']: theta})
Expand Down Expand Up @@ -197,6 +206,8 @@ def add_toroidal_geometry_coords(ds, *, coordinates=None, grid=None):
except KeyError:
pass

ds = _create_regions_toroidal(ds)

return ds


Expand Down Expand Up @@ -224,6 +235,8 @@ def add_s_alpha_geometry_coords(ds, *, coordinates=None, grid=None):
"geometry='s-alpha'")
ds['r'] = ds['hthe'].isel({coordinates['y']: 0}).squeeze(drop=True)
ds['r'].attrs['units'] = 'm'
# remove x-index coordinate, don't need when we have 'r' as a radial coordinate
ds = ds.drop('x')
ds = ds.set_coords('r')
ds = ds.rename(x='r')

Expand Down
4 changes: 2 additions & 2 deletions xbout/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ def _auto_open_mfboutdataset(datapath, chunks={}, info=True,
ds = xr.open_mfdataset(paths_grid, concat_dim=concat_dims, combine='nested',
data_vars=_BOUT_TIME_DEPENDENT_META_VARS,
preprocess=_preprocess, engine=filetype,
chunks=chunks, **kwargs)
chunks=chunks, join='exact', **kwargs)

# Remove any duplicate time values from concatenation
_, unique_indices = unique(ds['t_array'], return_index=True)
Expand Down Expand Up @@ -621,5 +621,5 @@ def _open_grid(datapath, chunks, keep_xboundaries, keep_yboundaries, mxg=2):
y=slice(nin + 2 * yboundaries, None, None))
grid = xr.concat((grid_lower, grid_upper), dim='y',
data_vars='minimal',
compat='identical')
compat='identical', join='exact')
return grid
16 changes: 8 additions & 8 deletions xbout/plotting/animate.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,16 @@ def animate_poloidal(da, *, ax=None, cax=None, animate_over='t', separatrix=True

ax.set_aspect(aspect)

regions = _decompose_regions(da)
da_regions = _decompose_regions(da)

# Plot all regions on same axis
blocks = []
for region in regions:
for da_region in da_regions.values():
# Load values eagerly otherwise for some reason the plotting takes
# 100's of times longer - for some reason animatplot does not deal
# well with dask arrays!
blocks.append(amp.blocks.Pcolormesh(region.coords[x].values,
region.coords[y].values, region.values, ax=ax, cmap=cmap,
blocks.append(amp.blocks.Pcolormesh(da_region.coords[x].values,
da_region.coords[y].values, da_region.values, ax=ax, cmap=cmap,
**kwargs))

ax.set_title(da.name)
Expand All @@ -121,10 +121,10 @@ def animate_poloidal(da, *, ax=None, cax=None, animate_over='t', separatrix=True
targets = False

if separatrix:
plot_separatrices(da, ax)
plot_separatrices(da_regions, ax)

if targets:
plot_targets(da, ax, hatching=add_limiter_hatching)
plot_targets(da_regions, ax, hatching=add_limiter_hatching)

if animate:
timeline = amp.Timeline(np.arange(da.sizes[animate_over]), fps=fps)
Expand Down Expand Up @@ -220,7 +220,7 @@ def animate_pcolormesh(data, animate_over='t', x=None, y=None, animate=True,
raise ValueError("Dimension {} is not present in the data" .format(x))
y = spatial_dims[0]

data = data.transpose(animate_over, y, x)
data = data.transpose(animate_over, y, x, transpose_coords=True)

# Load values eagerly otherwise for some reason the plotting takes
# 100's of times longer - for some reason animatplot does not deal
Expand Down Expand Up @@ -317,7 +317,7 @@ def animate_line(data, animate_over='t', animate=True,
if (t_read is animate_over):
pass
else:
data = data.transpose(animate_over, t_read)
data = data.transpose(animate_over, t_read, transpose_coords=True)

# Load values eagerly otherwise for some reason the plotting takes
# 100's of times longer - for some reason animatplot does not deal
Expand Down
40 changes: 19 additions & 21 deletions xbout/plotting/plotfuncs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import collections

import matplotlib
import matplotlib.pyplot as plt
import numpy as np
Expand All @@ -26,10 +24,10 @@ def regions(da, ax=None, **kwargs):
if ax is None:
fig, ax = plt.subplots()

regions = _decompose_regions(da)
da_regions = _decompose_regions(da)

colored_regions = [xr.full_like(region, fill_value=num / len(regions))
for num, region in enumerate(regions)]
colored_regions = [xr.full_like(da_region, fill_value=num / len(regions))
for num, da_region in enumerate(da_regions.values())]

return [region.plot.pcolormesh(x=x, y=y, vmin=0, vmax=1, cmap='tab20',
infer_intervals=False, add_colorbar=False, ax=ax,
Expand Down Expand Up @@ -181,12 +179,13 @@ def plot2d_wrapper(da, method, *, ax=None, separatrix=True, targets=True,
if 'infer_intervals' not in kwargs:
kwargs['infer_intervals'] = False

regions = _decompose_regions(da)
da_regions = _decompose_regions(da)

# Plot all regions on same axis
add_labels = [True] + [False] * (len(regions) - 1)
add_labels = [True] + [False] * (len(da_regions) - 1)
artists = [method(region, x=x, y=y, ax=ax, add_colorbar=False, add_labels=add_label,
cmap=cmap, **kwargs) for region, add_label in zip(regions, add_labels)]
cmap=cmap, **kwargs)
for region, add_label in zip(da_regions.values(), add_labels)]

if method is xr.plot.contour:
# using extend='neither' guarantees that the ends of the colorbar will be
Expand All @@ -213,35 +212,34 @@ def plot2d_wrapper(da, method, *, ax=None, separatrix=True, targets=True,
if not isinstance(value, slice):
raise ValueError('Argument passed to gridlines must be bool, int or '
'slice. Got a ' + type(value) + ', ' + str(value))
R_global = da['R']
R_global.attrs['metadata'] = da.metadata

Z_global = da['Z']
Z_global.attrs['metadata'] = da.metadata

R_regions = _decompose_regions(da['R'])
Z_regions = _decompose_regions(da['Z'])
R_regions = [da_region['R'] for da_region in da_regions.values()]
Z_regions = [da_region['Z'] for da_region in da_regions.values()]

for R, Z in zip(R_regions, Z_regions):
if (not da.metadata['bout_xdim'] in R.dims
and not da.metadata['bout_ydim'] in R.dims):
# Small regions around X-point do not have segments in x- or y-directions,
# so skip
# Currently this region does not exist, but there is a small white gap at
# the X-point, so we might add it back in future
continue
if gridlines.get('x') is not None:
# transpose in case Dataset or DataArray has been transposed away from the usual
# form
dim_order = (da.metadata['bout_xdim'], da.metadata['bout_ydim'])
yarg = {da.metadata['bout_ydim']: gridlines['x']}
plt.plot(R.isel(**yarg).transpose(*dim_order),
Z.isel(**yarg).transpose(*dim_order), color='k', lw=0.1)
plt.plot(R.isel(**yarg).transpose(*dim_order, transpose_coords=True),
Z.isel(**yarg).transpose(*dim_order, transpose_coords=True),
color='k', lw=0.1)
if gridlines.get('y') is not None:
xarg = {da.metadata['bout_xdim']: gridlines['y']}
# Need to plot transposed arrays to make gridlines that go in the
# y-direction
dim_order = (da.metadata['bout_ydim'], da.metadata['bout_xdim'])
plt.plot(R.isel(**xarg).transpose(*dim_order),
Z.isel(**yarg).transpose(*dim_order), color='k', lw=0.1)
plt.plot(R.isel(**xarg).transpose(*dim_order, transpose_coords=True),
Z.isel(**yarg).transpose(*dim_order, transpose_coords=True),
color='k', lw=0.1)

ax.set_title(da.name)

Expand All @@ -250,9 +248,9 @@ def plot2d_wrapper(da, method, *, ax=None, separatrix=True, targets=True,
targets = False

if separatrix:
plot_separatrices(da, ax)
plot_separatrices(da_regions, ax)

if targets:
plot_targets(da, ax, hatching=add_limiter_hatching)
plot_targets(da_regions, ax, hatching=add_limiter_hatching)

return artists
Loading