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

Add support for latest versions of h5py, sympy, numpy, and pytest #115

Merged
merged 10 commits into from Jan 10, 2020
9 changes: 5 additions & 4 deletions .travis.yml
Expand Up @@ -29,10 +29,11 @@ before_install:
- export PYENV_ROOT="$HOME/.pyenv"
- export PATH="$HOME/.pyenv/bin:$PATH"
- eval "$(pyenv init -)"
- pyenv install -s 3.5.5
- pyenv install -s 3.6.5
- pyenv install -s 3.7.1
- pyenv local 3.5.5 3.6.5 3.7.1
- pyenv install -s 3.5.7
- pyenv install -s 3.6.9
- pyenv install -s 3.7.5
- pyenv install -s 3.8.0
- pyenv local 3.5.7 3.6.9 3.7.5 3.8.0
- pip install tox tox-pyenv codecov twine

# Command to run tests, e.g. python setup.py test
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.rst
Expand Up @@ -1057,7 +1057,7 @@ HDF5 Files

The :mod:`unyt` library provides a hook for writing data both to a new HDF5 file and an existing file and then subsequently reading that data back in to restore the array. This works via the :meth:`unyt_array.write_hdf5 <unyt.array.unyt_array.write_hdf5>` and :meth:`unyt_array.from_hdf5 <unyt.array.unyt_array.from_hdf5>` methods. The simplest way to use these functions is to write data to a file that does not exist yet:

>>> from unyt import cm, unyt_array
>>> from unyt import cm
>>> import os
>>> data = [1, 2, 3]*cm
>>> data.write_hdf5('my_data.h5')
Expand Down
22 changes: 12 additions & 10 deletions tox.ini
@@ -1,24 +1,26 @@
[tox]
envlist = py36-docs,begin,py35-dependencies,py35-versions,py{35,36,37},end
envlist = py36-docs,begin,py35-dependencies,py35-versions,py{35,36,37,38},end

[travis]
python =
3.8: py38
3.7: py37
3.6: py36, py36-docs
3.5: py35, py35-dependencies, py35-versions

[testenv]
setenv =
PYTHONPATH = {toxinidir}
recreate = true
depends = begin
deps =
pytest<5.1
pytest
sympy
numpy
h5py
pint
astropy
coverage
coverage<5.0
pytest-cov
pytest-doctestplus
flake8
Expand All @@ -30,13 +32,13 @@ commands =

[testenv:py35-versions]
deps =
pytest<5.1
pytest
sympy==1.2
numpy==1.13.3
h5py==2.6.0
pint==0.6
astropy==1.3.3
coverage
coverage<5.0
pytest-cov
pytest-doctestplus
commands =
Expand All @@ -46,10 +48,10 @@ commands =

[testenv:py35-dependencies]
deps =
pytest<5.1
pytest
sympy
numpy
coverage
coverage<5.0
pytest-cov
pytest-doctestplus
depends = begin
Expand Down Expand Up @@ -77,13 +79,13 @@ commands =
depends =
skip_install = true
deps =
coverage
coverage<5.0

[testenv:end]
commands =
coverage report --omit='.tox/*'
coverage html --omit='.tox/*'
skip_install = true
depends = py{35,36,37}
depends = py{35,36,37,38}
deps =
coverage
coverage<5.0
2 changes: 1 addition & 1 deletion unyt/_parsing.py
Expand Up @@ -76,7 +76,7 @@ def _auto_positive_symbol(tokens, local_dict, global_dict):
"sqrt": sqrt,
}

unit_text_transform = (_auto_positive_symbol, rationalize, auto_number)
unit_text_transform = (_auto_positive_symbol, auto_number, rationalize)


def parse_unyt_expr(unit_expr):
Expand Down
60 changes: 46 additions & 14 deletions unyt/array.py
Expand Up @@ -127,8 +127,13 @@
from unyt.equivalencies import equivalence_registry
from unyt._on_demand_imports import _astropy, _pint
from unyt._pint_conversions import convert_pint_units
from unyt._unit_lookup_table import default_unit_symbol_lut
from unyt.unit_object import _check_em_conversion, _em_conversion, Unit
from unyt.unit_registry import _sanitize_unit_system, UnitRegistry
from unyt.unit_registry import (
_sanitize_unit_system,
UnitRegistry,
default_unit_registry,
)

NULL_UNIT = Unit()
POWER_SIGN_MAPPING = {multiply: 1, divide: -1}
Expand Down Expand Up @@ -321,6 +326,8 @@ def _sanitize_units_convert(possible_units, registry):

trigonometric_operators = (sin, cos, tan)

multiple_output_operators = {modf: 2, frexp: 2, divmod_: 2}

LARGE_INPUT = {4: 16777217, 8: 9007199254740993}


Expand Down Expand Up @@ -1317,12 +1324,16 @@ def write_hdf5(self, filename, dataset_name=None, info=None, group_name=None):
info = {}

info["units"] = str(self.units)
info["unit_registry"] = np.void(pickle.dumps(self.units.registry.lut))
lut = {}
for k, v in self.units.registry.lut.items():
if k not in default_unit_registry.lut:
lut[k] = v
info["unit_registry"] = np.void(pickle.dumps(lut))

if dataset_name is None:
dataset_name = "array_data"

f = h5py.File(filename)
f = h5py.File(filename, "a")
if group_name is not None:
if group_name in f:
g = f[group_name]
Expand Down Expand Up @@ -1372,15 +1383,17 @@ def from_hdf5(cls, filename, dataset_name=None, group_name=None):
if dataset_name is None:
dataset_name = "array_data"

f = h5py.File(filename)
f = h5py.File(filename, "r")
if group_name is not None:
g = f[group_name]
else:
g = f
dataset = g[dataset_name]
data = dataset[:]
units = dataset.attrs.get("units", "")
unit_lut = pickle.loads(dataset.attrs["unit_registry"].tostring())
unit_lut = default_unit_symbol_lut.copy()
unit_lut_load = pickle.loads(dataset.attrs["unit_registry"].tostring())
unit_lut.update(unit_lut_load)
f.close()
registry = UnitRegistry(lut=unit_lut, add_default_symbols=False)
return cls(data, units, registry=registry)
Expand Down Expand Up @@ -1584,18 +1597,29 @@ def __getitem__(self, item):
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
func = getattr(ufunc, method)
if "out" not in kwargs:
out = None
out_func = None
if ufunc in multiple_output_operators:
out = (None,) * multiple_output_operators[ufunc]
out_func = out
else:
out = None
out_func = None
else:
# we need to get both the actual "out" object and a view onto it
# in case we need to do in-place operations
out = kwargs.pop("out")[0]
if out.dtype.kind in ("u", "i"):
new_dtype = "f" + str(out.dtype.itemsize)
float_values = out.astype(new_dtype)
out.dtype = new_dtype
np.copyto(out, float_values)
out_func = out.view(np.ndarray)
out = kwargs.pop("out")
if ufunc in multiple_output_operators:
out_func = []
for arr in out:
out_func.append(arr.view(np.ndarray))
out_func = tuple(out_func)
else:
out = out[0]
if out.dtype.kind in ("u", "i"):
new_dtype = "f" + str(out.dtype.itemsize)
float_values = out.astype(new_dtype)
out.dtype = new_dtype
np.copyto(out, float_values)
out_func = out.view(np.ndarray)
if len(inputs) == 1:
# Unary ufuncs
inp = inputs[0]
Expand Down Expand Up @@ -1775,6 +1799,14 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
except AttributeError:
# out_arr is an ndarray
out.units = Unit("", registry=self.units.registry)
elif isinstance(out, tuple):
for o, oa in zip(out, out_arr):
if o is None:
continue
try:
o.units = oa.units
except AttributeError:
o.units = Unit("", registry=self.units.registry)
if mul == 1:
return out_arr
return mul * out_arr
Expand Down
22 changes: 15 additions & 7 deletions unyt/tests/test_unyt_array.py
Expand Up @@ -1010,7 +1010,10 @@ def unary_ufunc_comparison(ufunc, a):


def binary_ufunc_comparison(ufunc, a, b):
out = b.copy()
if ufunc in [np.divmod]:
out = (b.copy(), b.copy())
else:
out = b.copy()
if ufunc in yield_np_ufuncs(
[
"add",
Expand Down Expand Up @@ -1073,7 +1076,10 @@ def binary_ufunc_comparison(ufunc, a, b):
):
assert not isinstance(ret, unyt_array) and isinstance(ret, np.ndarray)
if isinstance(ret, tuple):
assert_array_equal(ret[0], out)
assert isinstance(out, tuple)
assert len(out) == len(ret)
for o, r in zip(out, ret):
assert_array_equal(r, o)
else:
assert_array_equal(ret, out)
if ufunc in (np.divide, np.true_divide, np.arctan2) and (
Expand Down Expand Up @@ -1353,6 +1359,10 @@ def test_astropy():


def test_pint():
def assert_pint_array_equal(arr1, arr2):
assert_array_equal(arr1.magnitude, arr2.magnitude)
assert str(arr1.units) == str(arr2.units)

if isinstance(_pint.UnitRegistry, NotAModule):
return
ureg = _pint.UnitRegistry()
Expand All @@ -1365,13 +1375,11 @@ def test_pint():
yt_quan = unyt_quantity(10.0, "sqrt(g)/mm**3")
yt_quan2 = unyt_quantity.from_pint(p_quan)

assert_array_equal(p_arr, yt_arr.to_pint())
assert_equal(p_quan, yt_quan.to_pint())
assert_pint_array_equal(p_arr, yt_arr.to_pint())
assert_array_equal(yt_arr, unyt_array.from_pint(p_arr))
assert_array_equal(yt_arr, yt_arr2)

assert_equal(p_quan.magnitude, yt_quan.to_pint().magnitude)
assert_equal(p_quan, yt_quan.to_pint())
assert_pint_array_equal(p_quan, yt_quan.to_pint())
assert_equal(yt_quan, unyt_quantity.from_pint(p_quan))
assert_equal(yt_quan, yt_quan2)

Expand Down Expand Up @@ -1477,7 +1485,7 @@ def test_h5_io():

# write to a group that does exist

with _h5py.File("test.h5") as f:
with _h5py.File("test.h5", "a") as f:
f.create_group("/arrays/test_group")

warr.write_hdf5(
Expand Down
2 changes: 1 addition & 1 deletion unyt/unit_object.py
Expand Up @@ -87,7 +87,7 @@ def _get_latex_representation(expr, registry):
l_expr = expr
if isinstance(expr, Mul):
coeffs = expr.as_coeff_Mul()
if coeffs[0] == 1 or not isinstance(coeffs[0], Float):
if coeffs[0] == 1 or not isinstance(coeffs[0], Number):
l_expr = coeffs[1]
else:
l_expr = coeffs[1]
Expand Down