Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7491906
Finish implementing test_against_collect
johnomotani Oct 21, 2019
896459b
pip-install boutdata for Travis tests
johnomotani Oct 23, 2019
d7b5971
Include BOUT_VERSION in create_bout_ds
johnomotani Oct 23, 2019
ba95d92
Remove MYPE, PE_XIND and PE_YIND from METADATA_VARS
johnomotani Oct 23, 2019
1c97746
Drop _BOUT_PER_PROC_VARIABLES in test_load
johnomotani Oct 23, 2019
5fdf62b
Fix load._get_limit() for case when number of guard cells is 0
johnomotani Oct 23, 2019
d3278e5
Define custom xBOUTWarning
johnomotani Oct 21, 2019
93f6f91
Suppress warning from xarray
johnomotani Oct 23, 2019
740c822
pip-install vtk in .travis.yml, workaround for Python-3.6 install
johnomotani Oct 23, 2019
212f913
PEP8 fixes
johnomotani Oct 23, 2019
b970c1a
Merge branch 'toroidal-coordinates-fix' into fix-test-against-collect
johnomotani Oct 24, 2019
807c8e3
Add tests opening 'toroidal' and 's-alpha' geometries
johnomotani Oct 24, 2019
f8fa440
Add option to pass grid file
johnomotani Oct 24, 2019
4fad704
Add 'hthe' from grid in s-alpha geometry
johnomotani Oct 24, 2019
181a691
Travis workaround to skip xarray-0.14.0, which breaks the tests
johnomotani Oct 25, 2019
60b9d9a
Fix PEP8 issuses, remove debugging print statements
johnomotani Oct 25, 2019
cfc039c
Don't store _grid in dataset attrs
johnomotani Oct 26, 2019
7ae0f18
Test saving BoutDataset with geometry
johnomotani Oct 26, 2019
3fc4f02
Do not save hthe in dataset for s-alpha geometry
johnomotani Oct 26, 2019
75f3072
Remove workaround for mayavi dependency of boutdata
johnomotani Oct 26, 2019
7c05112
Remove workaround for mayavi dependency of boutdata
johnomotani Oct 26, 2019
4902a74
Travis workaround to skip xarray-0.14.0, which breaks the tests
johnomotani Oct 25, 2019
4a86947
Revert addition of xBOUTWarning
johnomotani Oct 28, 2019
5047941
Merge pull request #58 from boutproject/test-geometries
johnomotani Dec 3, 2019
bf9de54
Don't try to store grid dataset after reading coordinates
johnomotani Dec 3, 2019
9922188
Remove skipped grid_merge test
johnomotani Dec 3, 2019
d7f8163
Fix passing MXG to _open_grid
johnomotani Dec 3, 2019
c099242
Merge branch 'master' into fix-test-against-collect
johnomotani Dec 3, 2019
0583eac
Docstring for 'grid' argument to applyGeometry
johnomotani Dec 3, 2019
dcda0d3
Skip test_against_collect if boutdata.collect is not available
johnomotani Dec 3, 2019
5370f7d
Fix PEP8 issues
johnomotani Dec 3, 2019
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
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ python:
- "3.6"
- "3.7"
install:
- pip install --upgrade setuptools pip pytest pytest-cov coverage codecov
- pip install --upgrade setuptools pip pytest pytest-cov coverage codecov boutdata "xarray!=0.14.0"
- pip install -r requirements.txt
- pip install -e .
script:
Expand Down
4 changes: 4 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]
filterwarnings =
ignore:No geometry type found, no coordinates will be added:UserWarning
ignore:deallocating CachingFileManager.*, but file is not already closed. This may indicate a bug\.:RuntimeWarning

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should actually fix whatever is causing this warning...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree in principle, but this warning also seems to be thrown in xarray's test suite (pydata/xarray#3266), so I guess it's an xarray issue. The warnings seem to be thrown during garbage-collection (I think I saw a comment somewhere that it might be related to some sort of reference-cycle, but I forget where now), and I didn't want to go chasing into the guts of xarray.

3 changes: 0 additions & 3 deletions xbout/boutdataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ def __init__(self, da):
self.data = da
self.metadata = da.attrs.get('metadata') # None if just grid file
self.options = da.attrs.get('options') # None if no inp file
self.grid = da.attrs.get('grid') # None if no grid file

def __str__(self):
"""
Expand All @@ -35,8 +34,6 @@ def __str__(self):
"Metadata:\n{}\n".format(styled(self.metadata))
if self.options:
text += "Options:\n{}".format(styled(self.options))
if self.grid:
text += "Grid:\n{}".format(styled(self.grid))
return text

def animate2D(self, animate_over='t', x='x', y='y', animate=True,
Expand Down
3 changes: 0 additions & 3 deletions xbout/boutdataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ def __init__(self, ds):
self.data = ds
self.metadata = ds.attrs.get('metadata') # None if just grid file
self.options = ds.attrs.get('options') # None if no inp file
self.grid = ds.attrs.get('grid') # None if no grid file

def __str__(self):
"""
Expand All @@ -34,8 +33,6 @@ def __str__(self):
"Metadata:\n{}\n".format(styled(self.metadata))
if self.options:
text += "Options:\n{}".format(styled(self.options))
if self.grid:
text += "Grid:\n{}".format(styled(self.grid))
return text

#def __repr__(self):
Expand Down
54 changes: 47 additions & 7 deletions xbout/geometries.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class UnregisteredGeometryError(Exception):
# desired


def apply_geometry(ds, geometry_name):
def apply_geometry(ds, geometry_name, *, coordinates=None, grid=None):
"""

Parameters
Expand All @@ -25,6 +25,15 @@ def apply_geometry(ds, geometry_name):
Dataset (from
geometry_name : str
Name under which the desired geometry function was registered
coordinates : dict of str, optional
Names to give the physical coordinates corresponding to 'x', 'y' and 'z'; values
corresponding to 'x', 'y' and 'z' keys in the passed dict are used as the names
of the dimensions. Any not passed are given default values. If not specified,
default names are chosen.
grid : Dataset, optional
Dataset containing extra geometrical information not stored in the dump files
that is needed to add coordinates for the geometry being applied. For example,
should contain 2d arrays Rxy, Zxy and psixy for toroidal geometry.

Returns
-------
Expand All @@ -43,7 +52,16 @@ def apply_geometry(ds, geometry_name):
have been registered.""".format(geometry_name))
raise UnregisteredGeometryError(message)

updated_ds = add_geometry_coords(ds)
# User-registered functions may accept 'coordinates' and 'grid' arguments, but do not
# have to as long as they are not used
if coordinates is not None and grid is not None:
updated_ds = add_geometry_coords(ds, coordinates=coordinates, grid=grid)
elif coordinates is not None:
updated_ds = add_geometry_coords(ds, coordinates=coordinates)
elif grid is not None:
updated_ds = add_geometry_coords(ds, grid=grid)
else:
updated_ds = add_geometry_coords(ds)
return updated_ds


Expand Down Expand Up @@ -92,7 +110,7 @@ def _set_default_toroidal_coordinates(coordinates):


@register_geometry('toroidal')
def add_toroidal_geometry_coords(ds, coordinates=None):
def add_toroidal_geometry_coords(ds, *, coordinates=None, grid=None):

coordinates = _set_default_toroidal_coordinates(coordinates)

Expand All @@ -104,6 +122,16 @@ def add_toroidal_geometry_coords(ds, coordinates=None):
"It may be useful to use the 'coordinates' argument to "
"add_toroidal_geometry_coords() for this.".format(bad_names))

# Get extra geometry information from grid file if it's not in the dump files
needed_variables = ['psixy', 'Rxy', 'Zxy']
for v in needed_variables:
if v not in ds:
if grid is None:
raise ValueError("Grid file is required to provide %s. Pass the grid "
"file name as the 'gridfilepath' argument to "
"open_boutdataset().")
ds[v] = grid[v]

# Change names of dimensions to Orthogonal Toroidal ones
ds = ds.rename(y=coordinates['y'])

Expand Down Expand Up @@ -137,11 +165,22 @@ def add_toroidal_geometry_coords(ds, coordinates=None):


@register_geometry('s-alpha')
def add_s_alpha_geometry_coords(ds, coordinates=None):
def add_s_alpha_geometry_coords(ds, *, coordinates=None, grid=None):

coordinates = _set_default_toroidal_coordinates(coordinates)

ds = add_toroidal_geometry_coords(ds, coordinates=coordinates)
# Add 'hthe' from grid file, needed below for radial coordinate
if 'hthe' not in ds:
hthe_from_grid = True
if grid is None:
raise ValueError("Grid file is required to provide %s. Pass the grid "
"file name as the 'gridfilepath' argument to "
"open_boutdataset().")
ds['hthe'] = grid['hthe']
else:
hthe_from_grid = False

ds = add_toroidal_geometry_coords(ds, coordinates=coordinates, grid=grid)

# Add 1D radial coordinate
if 'r' in ds:
Expand All @@ -152,7 +191,8 @@ def add_s_alpha_geometry_coords(ds, coordinates=None):
ds = ds.set_coords('r')
ds = ds.rename(x='r')

# Simplify psi to be radially-varying only
ds['r'] = ds['r'].isel({coordinates['y']: 0}).squeeze(drop=True)
if hthe_from_grid:
# remove hthe because it does not have correct metadata
del ds['hthe']

return ds
28 changes: 21 additions & 7 deletions xbout/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@


def open_boutdataset(datapath='./BOUT.dmp.*.nc', inputfilepath=None,
geometry=None, chunks={},
geometry=None, gridfilepath=None, chunks={},
keep_xboundaries=True, keep_yboundaries=False,
run_name=None, info=True):
"""
Expand Down Expand Up @@ -67,6 +67,9 @@ def open_boutdataset(datapath='./BOUT.dmp.*.nc', inputfilepath=None,
To define a new type of geometry you need to use the
`register_geometry` decorator. You are encouraged to do this for your
own BOUT++ physics module, to apply relevant normalisations.
gridfilepath : str, optional
The path to a grid file, containing any variables needed to apply the geometry
specified by the 'geometry' option, which are not contained in the dump files.
keep_xboundaries : bool, optional
If true, keep x-direction boundary cells (the cells past the physical
edges of the grid, where boundary conditions are set); increases the
Expand Down Expand Up @@ -125,8 +128,17 @@ def open_boutdataset(datapath='./BOUT.dmp.*.nc', inputfilepath=None,
if geometry:
if info:
print("Applying {} geometry conventions".format(geometry))

if gridfilepath is not None:
grid = _open_grid(gridfilepath, chunks=chunks,
keep_xboundaries=keep_xboundaries,
keep_yboundaries=keep_yboundaries,
mxg=ds.metadata['MXG'])
else:
grid = None

# Update coordinates to match particular geometry of grid
ds = geometries.apply_geometry(ds, geometry)
ds = geometries.apply_geometry(ds, geometry, grid=grid)
else:
if info:
warn("No geometry type found, no coordinates will be added")
Expand Down Expand Up @@ -199,7 +211,7 @@ def _expand_filepaths(datapath):
" `file_cache_maxsize` global option to {} to accommodate this. "
"Recommend using `xr.set_options(file_cache_maxsize=NUM)`"
" to explicitly set this to a large enough value."
.format(str(len(filepaths))), UserWarning)
.format(str(len(filepaths))))
xr.set_options(file_cache_maxsize=len(filepaths))

return filepaths, filetype
Expand Down Expand Up @@ -393,10 +405,13 @@ def _get_limit(side, dim, keep_boundaries, boundaries, guards):
else:
limit = None

if limit == 0:
# 0 would give incorrect result as an upper limit
limit = None
return limit


def _open_grid(datapath, chunks, keep_xboundaries, keep_yboundaries):
def _open_grid(datapath, chunks, keep_xboundaries, keep_yboundaries, mxg=2):
"""
Opens a single grid file. Implements slightly different logic for
boundaries to deal with different conventions in a BOUT grid file.
Expand All @@ -415,12 +430,11 @@ def _open_grid(datapath, chunks, keep_xboundaries, keep_yboundaries):
# pytest warnings capture - doesn't match strings containing brackets
warn(
"Will drop all variables containing the dimensions {} because "
"they are not recognised".format(str(unrecognised_dims)[1:-1]),
UserWarning)
"they are not recognised".format(str(unrecognised_dims)[1:-1]))
grid = grid.drop_dims(unrecognised_dims)

if not keep_xboundaries:
xboundaries = int(grid.metadata['MXG'])
xboundaries = mxg
if xboundaries > 0:
grid = grid.isel(x=slice(xboundaries, -xboundaries, None))
if not keep_yboundaries:
Expand Down
103 changes: 83 additions & 20 deletions xbout/tests/test_against_collect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,108 @@

import numpy.testing as npt

from xbout.load import _auto_open_mfboutdataset
from xbout.load import open_boutdataset
from .test_load import create_bout_ds, create_bout_ds_list, METADATA_VARS

boutdata = pytest.importorskip("boutdata", reason="boutdata is not available")
collect = boutdata.collect

class TestAccuracyAgainstOldCollect:
@pytest.mark.skip
def test_single_file(self):
from boutdata import collect
def test_single_file(self, tmpdir_factory):

# Create temp directory for files
test_dir = tmpdir_factory.mktemp("test_data")

# Generate some test data
generated_ds = create_bout_ds(syn_data_type="linear")
generated_ds.to_netcdf(str(test_dir.join("BOUT.dmp.0.nc")))

var = 'n'
expected = collect(var, path='./tests/data/dump_files/single',
prefix='equilibrium', xguards=False)
expected = collect(var, path=test_dir, xguards=True, yguards=False)

ds, metadata = _auto_open_mfboutdataset('./tests/data/dump_files/single/equilibrium.nc')
print(ds)
ds = open_boutdataset(test_dir.join("BOUT.dmp.0.nc"))
actual = ds[var].values

assert expected.shape == actual.shape
npt.assert_equal(actual, expected)

@pytest.mark.skip
def test_multiple_files_along_x(self):
from boutdata import collect
def test_multiple_files_along_x(self, tmpdir_factory):

# Create temp directory for files
test_dir = tmpdir_factory.mktemp("test_data")

# Generate some test data
ds_list, file_list = create_bout_ds_list("BOUT.dmp", nxpe=3, nype=1,
syn_data_type="linear")
for temp_ds, file_name in zip(ds_list, file_list):
temp_ds.to_netcdf(str(test_dir.join(str(file_name))))

var = 'n'
expected = collect(var, path='./tests/data/dump_files/',
prefix='BOUT.dmp', xguards=False)
expected = collect(var, path=test_dir,
prefix='BOUT.dmp', xguards=True)

ds, metadata = _auto_open_mfboutdataset('./tests/data/dump_files/BOUT.dmp.*.nc')
ds = open_boutdataset(test_dir.join('BOUT.dmp.*.nc'))
actual = ds[var].values

assert expected.shape == actual.shape
npt.assert_equal(actual, expected)

def test_multiple_files_along_y(self, tmpdir_factory):

# Create temp directory for files
test_dir = tmpdir_factory.mktemp("test_data")

# Generate some test data
ds_list, file_list = create_bout_ds_list("BOUT.dmp", nxpe=1, nype=3,
syn_data_type="linear")
for temp_ds, file_name in zip(ds_list, file_list):
temp_ds.to_netcdf(str(test_dir.join(str(file_name))))

var = 'n'
expected = collect(var, path=test_dir,
prefix='BOUT.dmp', xguards=True)

ds = open_boutdataset(test_dir.join('BOUT.dmp.*.nc'))
actual = ds[var].values

assert expected.shape == actual.shape
npt.assert_equal(actual, expected)

def test_multiple_files_along_xy(self, tmpdir_factory):

# Create temp directory for files
test_dir = tmpdir_factory.mktemp("test_data")

# Generate some test data
ds_list, file_list = create_bout_ds_list("BOUT.dmp", nxpe=3, nype=3,
syn_data_type="linear")
for temp_ds, file_name in zip(ds_list, file_list):
temp_ds.to_netcdf(str(test_dir.join(str(file_name))))

var = 'n'
expected = collect(var, path=test_dir,
prefix='BOUT.dmp', xguards=True)

ds = open_boutdataset(test_dir.join('BOUT.dmp.*.nc'))
actual = ds[var].values

assert expected.shape == actual.shape
npt.assert_equal(actual, expected)

def test_metadata(self, tmpdir_factory):
# Create temp directory for files
test_dir = tmpdir_factory.mktemp("test_data")

# Generate some test data
generated_ds = create_bout_ds(syn_data_type="linear")
generated_ds.to_netcdf(str(test_dir.join("BOUT.dmp.0.nc")))

@pytest.mark.skip
def test_multiple_files_along_x(self):
...
ds = open_boutdataset(test_dir.join('BOUT.dmp.*.nc'))

@pytest.mark.skip
def test_metadata(self):
...
for v in METADATA_VARS:
expected = collect(v, path=test_dir)
actual = ds.bout.metadata[v]
npt.assert_equal(actual, expected)


@pytest.mark.skip
Expand Down
2 changes: 1 addition & 1 deletion xbout/tests/test_animate.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def create_test_file(tmpdir_factory):
for ds, file_name in zip(ds_list, file_list):
ds.to_netcdf(str(save_dir.join(str(file_name))))

ds = open_boutdataset(save_dir.join("BOUT.dmp.*.nc")) # Open test data
ds = open_boutdataset(save_dir.join("BOUT.dmp.*.nc")) # Open test data

return save_dir, ds

Expand Down
4 changes: 0 additions & 4 deletions xbout/tests/test_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ def test_open_grid_extra_dims(self, create_example_grid_file):
assert_equal(result, example_grid)
result.close()

@pytest.mark.skip
def test_open_grid_merge_ds(self):
...

def test_open_grid_apply_geometry(self, create_example_grid_file):
@register_geometry(name="Schwarzschild")
def add_schwarzschild_coords(ds, coordinates=None):
Expand Down
Loading