Skip to content

Commit 2478fe6

Browse files
authored
Merge pull request #217 from boutproject/document-workaround-load-conflicts
Document a workaround for conflicts in open_boutdataset(); some tidying
2 parents 22e1589 + 7a1093f commit 2478fe6

File tree

7 files changed

+90
-61
lines changed

7 files changed

+90
-61
lines changed

xbout/boutdataarray.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def from_field_aligned(self):
189189
return result
190190

191191
@property
192-
def regions(self):
192+
def _regions(self):
193193
if "regions" not in self.data.attrs:
194194
raise ValueError(
195195
"Called a method requiring regions, but these have not been created. "
@@ -279,7 +279,7 @@ def interpolate_parallel(
279279
self.interpolate_parallel(
280280
region, n=n, toroidal_points=toroidal_points, method=method
281281
).bout.to_dataset()
282-
for region in self.data.regions
282+
for region in self._regions
283283
]
284284

285285
# 'region' is not the same for all parts, and should not exist in the result,
@@ -301,7 +301,7 @@ def interpolate_parallel(
301301

302302
# Select a particular 'region' and interpolate to higher parallel resolution
303303
da = self.data
304-
region = da.regions[region]
304+
region = da.bout._regions[region]
305305
tcoord = da.metadata["bout_tdim"]
306306
xcoord = da.metadata["bout_xdim"]
307307
ycoord = da.metadata["bout_ydim"]
@@ -409,9 +409,9 @@ def remove_yboundaries(self, return_dataset=False, remove_extra_upper=False):
409409

410410
ycoord = self.data.metadata["bout_ydim"]
411411
parts = []
412-
for region in self.data.regions:
412+
for region in self._regions:
413413
part = self.data.bout.from_region(region, with_guards=0)
414-
part_region = list(part.regions.values())[0]
414+
part_region = list(part.bout._regions.values())[0]
415415
if part_region.connection_lower_y is None:
416416
part = part.isel({ycoord: slice(myg, None)})
417417
if part_region.connection_upper_y is None:
@@ -525,7 +525,7 @@ def ddy(self, region=None):
525525
if region is None:
526526
# Call the single-region version of this method for each region, and combine
527527
# the results together
528-
parts = [self.ddy(r).to_dataset() for r in self.data.regions]
528+
parts = [self.ddy(r).to_dataset() for r in self._regions]
529529

530530
# 'region' is not the same for all parts, and should not exist in the
531531
# result, so delete before merging
@@ -537,7 +537,7 @@ def ddy(self, region=None):
537537
result = xr.combine_by_coords(parts)[f"d({name})/dy"]
538538

539539
# regions get mixed up during the split and combine_by_coords, so reset them
540-
result.attrs["regions"] = self.data.regions
540+
result.attrs["regions"] = self._regions
541541

542542
return result
543543

@@ -568,7 +568,7 @@ def ddy(self, region=None):
568568
result = (da.shift({ycoord: -1}) - da.shift({ycoord: 1})) / (2.0 * dy)
569569

570570
# Remove any y-guard cells
571-
region_object = da.regions[region]
571+
region_object = da.bout._regions[region]
572572
if region_object.connection_lower_y is None:
573573
ylower = None
574574
else:
@@ -1000,13 +1000,26 @@ def interpolate_from_unstructured(
10001000

10011001
# BOUT-specific plotting functionality: methods that plot on a poloidal (R-Z) plane
10021002
def contour(self, ax=None, **kwargs):
1003+
"""
1004+
Contour-plot a radial-poloidal slice on the R-Z plane
1005+
"""
10031006
return plotfuncs.plot2d_wrapper(self.data, xr.plot.contour, ax=ax, **kwargs)
10041007

10051008
def contourf(self, ax=None, **kwargs):
1009+
"""
1010+
Filled-contour-plot a radial-poloidal slice on the R-Z plane
1011+
"""
10061012
return plotfuncs.plot2d_wrapper(self.data, xr.plot.contourf, ax=ax, **kwargs)
10071013

10081014
def pcolormesh(self, ax=None, **kwargs):
1015+
"""
1016+
Colour-plot a radial-poloidal slice on the R-Z plane
1017+
"""
10091018
return plotfuncs.plot2d_wrapper(self.data, xr.plot.pcolormesh, ax=ax, **kwargs)
10101019

1011-
def regions(self, ax=None, **kwargs):
1012-
return plotfuncs.regions(self.data, ax=ax, **kwargs)
1020+
def plot_regions(self, ax=None, **kwargs):
1021+
"""
1022+
Plot the regions into which xBOUT splits radial-poloidal arrays to handle
1023+
tokamak topology.
1024+
"""
1025+
return plotfuncs.plot_regions(self.data, ax=ax, **kwargs)

xbout/boutdataset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def from_region(self, name, with_guards=None):
143143
return _from_region(self.data, name, with_guards)
144144

145145
@property
146-
def regions(self):
146+
def _regions(self):
147147
if "regions" not in self.data.attrs:
148148
raise ValueError(
149149
"Called a method requiring regions, but these have not been created. "

xbout/load.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,21 @@ def open_boutdataset(
7878
`run_name` are ignored. `geometry` is treated specially, and can be passed when
7979
reloading a Dataset (along with `gridfilepath` if needed).
8080
81+
Troubleshooting
82+
---------------
83+
Variable conflicts: sometimes, for example when loading data from multiple restarts,
84+
some variables may have conflicts (e.g. a source term was changed between some of
85+
the restarts, but the source term is saved as time-independent, without a
86+
t-dimension). In this case one workaround is to pass a list of variable names to the
87+
keyword argument `drop_vars` to ignore the variables with conflicts, e.g. if `"S1"`
88+
and `"S2"` have conflicts
89+
```
90+
ds = open_boutdataset("data*/boutdata.nc", drop_vars=["S1", "S2"])
91+
```
92+
will open a Dataset which is missing `"S1"` and `"S2"`.\
93+
[`drop_vars` is an argument of `xarray.open_dataset()` that is passed down through
94+
`kwargs`.]
95+
8196
Parameters
8297
----------
8398
datapath : str or (list or tuple of xr.Dataset), optional
@@ -124,7 +139,7 @@ def open_boutdataset(
124139
info : bool or "terse", optional
125140
kwargs : optional
126141
Keyword arguments are passed down to `xarray.open_mfdataset`, which in
127-
turn extra kwargs down to `xarray.open_dataset`.
142+
turn passes extra kwargs down to `xarray.open_dataset`.
128143
129144
Returns
130145
-------
@@ -327,9 +342,6 @@ def collect(
327342
info=True,
328343
prefix="BOUT.dmp",
329344
):
330-
331-
from os.path import join
332-
333345
"""
334346
335347
Extract the data pertaining to a specified variable in a BOUT++ data set
@@ -369,6 +381,7 @@ def collect(
369381
ds : numpy.ndarray
370382
371383
"""
384+
from os.path import join
372385

373386
datapath = join(path, prefix + "*.nc")
374387

xbout/plotting/plotfuncs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
matplotlib.rcParams["pcolor.shading"] = "auto"
2525

2626

27-
def regions(da, ax=None, **kwargs):
27+
def plot_regions(da, ax=None, **kwargs):
2828
"""
2929
Plots each logical plotting region as a different color for debugging.
3030

xbout/plotting/utils.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ def plot_separatrix(da, sep_pos, ax, radial_coord="x"):
6464

6565
def _decompose_regions(da):
6666

67-
return {region: da.bout.from_region(region, with_guards=1) for region in da.regions}
67+
return {
68+
region: da.bout.from_region(region, with_guards=1)
69+
for region in da.bout._regions
70+
}
6871

6972

7073
def _is_core_only(da):
@@ -90,7 +93,7 @@ def plot_separatrices(da, ax, *, x="R", y="Z"):
9093
ycoord = da0.metadata["bout_ydim"]
9194

9295
for da_region in da_regions.values():
93-
inner = list(da_region.regions.values())[0].connection_inner_x
96+
inner = list(da_region.bout._regions.values())[0].connection_inner_x
9497
if inner in da_regions:
9598
da_inner = da_regions[inner]
9699

@@ -136,14 +139,14 @@ def plot_targets(da, ax, *, x="R", y="Z", hatching=True):
136139
y_boundary_guards = 0
137140

138141
for da_region in da_regions.values():
139-
if list(da_region.regions.values())[0].connection_lower_y is None:
142+
if list(da_region.bout._regions.values())[0].connection_lower_y is None:
140143
# lower target exists
141144
x_target = da_region.coords[x].isel(**{ycoord: y_boundary_guards})
142145
y_target = da_region.coords[y].isel(**{ycoord: y_boundary_guards})
143146
[line] = ax.plot(x_target, y_target, "k-", linewidth=2)
144147
if hatching:
145148
_add_hatching(line, ax)
146-
if list(da_region.regions.values())[0].connection_upper_y is None:
149+
if list(da_region.bout._regions.values())[0].connection_upper_y is None:
147150
# upper target exists
148151
x_target = da_region.coords[x].isel(**{ycoord: -y_boundary_guards - 1})
149152
y_target = da_region.coords[y].isel(**{ycoord: -y_boundary_guards - 1})

xbout/region.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,11 +1221,11 @@ def _concat_inner_guards(da, da_global, mxg):
12211221
if mxg <= 0:
12221222
return da
12231223

1224-
if len(da.regions) > 1:
1224+
if len(da.bout._regions) > 1:
12251225
raise ValueError("da passed should have only one region")
1226-
region = list(da.regions.values())[0]
1226+
region = list(da.bout._regions.values())[0]
12271227

1228-
if region.connection_inner_x not in da_global.regions:
1228+
if region.connection_inner_x not in da_global.bout._regions:
12291229
# No connection, or plotting restricted set of regions not including this
12301230
# connection
12311231
return da
@@ -1236,9 +1236,9 @@ def _concat_inner_guards(da, da_global, mxg):
12361236
ycoord = da_global.metadata["bout_ydim"]
12371237

12381238
da_inner = da_global.bout.from_region(region.connection_inner_x, with_guards=0)
1239-
if len(da_inner.regions) > 1:
1239+
if len(da_inner.bout._regions) > 1:
12401240
raise ValueError("da_inner should have only one region")
1241-
region_inner = list(da_inner.regions.values())[0]
1241+
region_inner = list(da_inner.bout._regions.values())[0]
12421242

12431243
if (
12441244
myg_da > 0
@@ -1276,7 +1276,7 @@ def _concat_inner_guards(da, da_global, mxg):
12761276
da_inner_lower = da_inner_lower.isel(
12771277
**{xcoord: slice(-mxg, None), ycoord: slice(-myg_da, None)}
12781278
)
1279-
save_regions = da_inner.regions
1279+
save_regions = da_inner.bout._regions
12801280
da_inner = xr.concat((da_inner_lower, da_inner), ycoord, join="exact")
12811281
# xr.concat takes attributes from the first variable, but we need da_inner's
12821282
# regions
@@ -1314,7 +1314,7 @@ def _concat_inner_guards(da, da_global, mxg):
13141314
da_inner[xcoord].data[...] = new_xcoord.data
13151315
da_inner[ycoord].data[...] = new_ycoord.data
13161316

1317-
save_regions = da.regions
1317+
save_regions = da.bout._regions
13181318
da = xr.concat((da_inner, da), xcoord, join="exact")
13191319
# xr.concat takes attributes from the first variable (for xarray>=0.15.0, keeps attrs
13201320
# that are the same in all objects for xarray<0.15.0)
@@ -1332,11 +1332,11 @@ def _concat_outer_guards(da, da_global, mxg):
13321332
if mxg <= 0:
13331333
return da
13341334

1335-
if len(da.regions) > 1:
1335+
if len(da.bout._regions) > 1:
13361336
raise ValueError("da passed should have only one region")
1337-
region = list(da.regions.values())[0]
1337+
region = list(da.bout._regions.values())[0]
13381338

1339-
if region.connection_outer_x not in da_global.regions:
1339+
if region.connection_outer_x not in da_global.bout._regions:
13401340
# No connection, or plotting restricted set of regions not including this
13411341
# connection
13421342
return da
@@ -1347,9 +1347,9 @@ def _concat_outer_guards(da, da_global, mxg):
13471347
ycoord = da_global.metadata["bout_ydim"]
13481348

13491349
da_outer = da_global.bout.from_region(region.connection_outer_x, with_guards=0)
1350-
if len(da_outer.regions) > 1:
1350+
if len(da_outer.bout._regions) > 1:
13511351
raise ValueError("da_outer should have only one region")
1352-
region_outer = list(da_outer.regions.values())[0]
1352+
region_outer = list(da_outer.bout._regions.values())[0]
13531353

13541354
if (
13551355
myg_da > 0
@@ -1387,7 +1387,7 @@ def _concat_outer_guards(da, da_global, mxg):
13871387
da_outer_lower = da_outer_lower.isel(
13881388
**{xcoord: slice(-mxg, None), ycoord: slice(-myg_da, None)}
13891389
)
1390-
save_regions = da_outer.regions
1390+
save_regions = da_outer.bout._regions
13911391
da_outer = xr.concat((da_outer_lower, da_outer), ycoord, join="exact")
13921392
# xr.concat takes attributes from the first variable, but we need da_outer's
13931393
# regions
@@ -1425,7 +1425,7 @@ def _concat_outer_guards(da, da_global, mxg):
14251425
da_outer[xcoord].data[...] = new_xcoord.data
14261426
da_outer[ycoord].data[...] = new_ycoord.data
14271427

1428-
save_regions = da.regions
1428+
save_regions = da.bout._regions
14291429
da = xr.concat((da, da_outer), xcoord, join="exact")
14301430
# xarray<0.15.0 only keeps attrs that are the same on all variables passed to concat
14311431
da.attrs["regions"] = save_regions
@@ -1442,11 +1442,11 @@ def _concat_lower_guards(da, da_global, mxg, myg):
14421442
if myg <= 0:
14431443
return da
14441444

1445-
if len(da.regions) > 1:
1445+
if len(da.bout._regions) > 1:
14461446
raise ValueError("da passed should have only one region")
1447-
region = list(da.regions.values())[0]
1447+
region = list(da.bout._regions.values())[0]
14481448

1449-
if region.connection_lower_y not in da_global.regions:
1449+
if region.connection_lower_y not in da_global.bout._regions:
14501450
# No connection, or plotting restricted set of regions not including this
14511451
# connection
14521452
return da
@@ -1525,7 +1525,7 @@ def _concat_lower_guards(da, da_global, mxg, myg):
15251525
da_lower[xcoord].data[...] = new_xcoord.data
15261526
da_lower[ycoord].data[...] = new_ycoord.data
15271527

1528-
save_regions = da.regions
1528+
save_regions = da.bout._regions
15291529
da = xr.concat((da_lower, da), ycoord, join="exact")
15301530
# xr.concat takes attributes from the first variable (for xarray>=0.15.0, keeps attrs
15311531
# that are the same in all objects for xarray<0.15.0)
@@ -1543,11 +1543,11 @@ def _concat_upper_guards(da, da_global, mxg, myg):
15431543
if myg <= 0:
15441544
return da
15451545

1546-
if len(da.regions) > 1:
1546+
if len(da.bout._regions) > 1:
15471547
raise ValueError("da passed should have only one region")
1548-
region = list(da.regions.values())[0]
1548+
region = list(da.bout._regions.values())[0]
15491549

1550-
if region.connection_upper_y not in da_global.regions:
1550+
if region.connection_upper_y not in da_global.bout._regions:
15511551
# No connection, or plotting restricted set of regions not including this
15521552
# connection
15531553
return da
@@ -1625,7 +1625,7 @@ def _concat_upper_guards(da, da_global, mxg, myg):
16251625
da_upper[xcoord].data[...] = new_xcoord.data
16261626
da_upper[ycoord].data[...] = new_ycoord.data
16271627

1628-
save_regions = da.regions
1628+
save_regions = da.bout._regions
16291629
da = xr.concat((da, da_upper), ycoord, join="exact")
16301630
# xarray<0.15.0 only keeps attrs that are the same on all variables passed to concat
16311631
da.attrs["regions"] = save_regions
@@ -1637,7 +1637,7 @@ def _from_region(ds_or_da, name, with_guards):
16371637
# ensure we do not modify the input
16381638
ds_or_da = ds_or_da.copy(deep=True)
16391639

1640-
region = ds_or_da.regions[name]
1640+
region = ds_or_da.bout._regions[name]
16411641
xcoord = ds_or_da.metadata["bout_xdim"]
16421642
ycoord = ds_or_da.metadata["bout_ydim"]
16431643

@@ -1669,7 +1669,7 @@ def _from_region(ds_or_da, name, with_guards):
16691669

16701670
# If the result (which only has a single region) is passed to from_region a
16711671
# second time, don't want to slice anything.
1672-
single_region = list(result.regions.values())[0]
1672+
single_region = list(result.bout._regions.values())[0]
16731673
single_region.xinner_ind = None
16741674
single_region.xouter_ind = None
16751675
single_region.ylower_ind = None

0 commit comments

Comments
 (0)