Skip to content

Commit b201615

Browse files
authored
Merge pull request #3 from pelson/testing
Added testing and Travis.
2 parents d652fea + 726ec7b commit b201615

File tree

7 files changed

+339
-8
lines changed

7 files changed

+339
-8
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ __pycache__
44
build
55
*.swp
66
.ipynb_checkpoints
7-
*.pyc
7+
*.pyc
8+
*.c
9+
10+
dist/

.travis.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# The language in this case has no bearing - we are going to be making use of conda for a
2+
# python distribution for the scientific python stack.
3+
language: python
4+
5+
sudo: false
6+
7+
env:
8+
global:
9+
- CONDA_INSTALL_LOCN="${HOME}/conda"
10+
matrix:
11+
- PYTHON=2.7
12+
- PYTHON=3.5
13+
14+
install:
15+
- mkdir -p ${HOME}/cache/pkgs
16+
- "[ ! -f ${HOME}/cache/miniconda.sh ] && wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ${HOME}/cache/miniconda.sh || :"
17+
- bash ${HOME}/cache/miniconda.sh -b -p ${CONDA_INSTALL_LOCN} && export PATH=${CONDA_INSTALL_LOCN}/bin:$PATH
18+
19+
# Re-use the pacakges in the cache, and download any new ones into that location.
20+
- rm -rf ${CONDA_INSTALL_LOCN}/pkgs && ln -s ${HOME}/cache/pkgs ${CONDA_INSTALL_LOCN}/pkgs
21+
22+
# Now do the things we need to do to install it.
23+
- conda create -n test_env --file requirements.txt nose python=${PYTHON} ${EXTRA_DEPS} --yes --quiet
24+
- source activate test_env
25+
- python setup.py build_ext install
26+
- mkdir not_the_source_root && cd not_the_source_root
27+
28+
script:
29+
- nosetests stratify
30+
31+
32+
# We store the files that are downloaded from continuum.io, but not the environments that are created.
33+
cache:
34+
directories:
35+
- $HOME/cache
36+
before_cache:
37+
# Remove all untarred directories.
38+
- find $CONDA_INSTALL_LOCN/pkgs/ -mindepth 1 -maxdepth 1 -type d -exec rm -r {} \;

MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Include the license file
2+
include LICENSE
3+

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
setuptools
2+
cython
3+
numpy

setup.cfg

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[bdist_wheel]
2+
# This flag says that the code is written to work on both Python 2 and Python
3+
# 3. If at all possible, it is good practice to do this. If you cannot, you
4+
# will need to generate wheels for each Python version that you support.
5+
universal=1

setup.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
from __future__ import absolute_import, division, print_function
22

3-
from distutils.core import setup
3+
from setuptools import setup, find_packages, Extension
44
import os
55

66
import numpy as np
7-
import setuptools
87

98
from Cython.Build import cythonize
109

1110

12-
extensions = [setuptools.Extension('stratify._vinterp',
13-
['stratify/_vinterp.pyx'],
14-
include_dirs=[np.get_include()])]
11+
extensions = [Extension('stratify._vinterp',
12+
['stratify/_vinterp.pyx'],
13+
include_dirs=[np.get_include()])]
1514

1615
setup(
1716
name='stratify',
1817
description='Vectorized interpolators that are especially useful for Nd vertical interpolation/stratification of atmospheric and oceanographic datasets',
19-
version='0.2.0',
18+
version='0.3.0',
2019
ext_modules=cythonize(extensions),
21-
packages=['stratify', 'stratify.tests'],
20+
packages=find_packages(),
2221
classifiers=[
2322
'Development Status :: 3 - Alpha',
2423
('License :: OSI Approved :: '
@@ -37,4 +36,7 @@
3736
'Topic :: Scientific/Engineering',
3837
'Topic :: Scientific/Engineering :: GIS',
3938
],
39+
extras_require={'test': ['nose'],
40+
},
41+
zip_safe=False,
4042
)

stratify/tests/test_vinterp.py

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
from __future__ import division
2+
3+
import unittest
4+
5+
import numpy as np
6+
from numpy.testing import assert_array_equal, assert_array_almost_equal
7+
8+
import stratify
9+
import stratify._vinterp as vinterp
10+
11+
12+
class TestColumnInterpolation(unittest.TestCase):
13+
def interpolate(self, x_target, x_src):
14+
x_target = np.array(x_target)
15+
x_src = np.array(x_src)
16+
fx_src = np.empty(x_src.shape)
17+
18+
index_interp = vinterp._TestableIndexInterpKernel()
19+
extrap_direct = vinterp._TestableDirectionExtrapKernel()
20+
21+
r1 = stratify.interpolate(x_target, x_src, fx_src,
22+
interpolation=index_interp,
23+
extrapolation=extrap_direct)
24+
25+
r2 = stratify.interpolate(-1 * x_target, -1 * x_src, fx_src,
26+
rising=False, interpolation=index_interp,
27+
extrapolation=extrap_direct)
28+
assert_array_equal(r1, r2)
29+
30+
return r1
31+
32+
def test_interp_only(self):
33+
r = self.interpolate([1, 2, 3], [1, 3])
34+
assert_array_equal(r, [0, 1, 1])
35+
36+
def test_interp_multi_level_single_source(self):
37+
r = self.interpolate([1.5, 2, 2.5], [1, 3])
38+
assert_array_equal(r, [1, 1, 1])
39+
40+
def test_interp_single_level_multiple_source(self):
41+
r = self.interpolate([3.5], [1, 2, 3, 3, 4])
42+
assert_array_equal(r, [4])
43+
44+
def test_lower_extrap_only(self):
45+
r = self.interpolate([1, 2, 3], [4, 5])
46+
assert_array_equal(r, [-np.inf, -np.inf, -np.inf])
47+
48+
def test_upper_extrap_only(self):
49+
r = self.interpolate([1, 2, 3], [-4, -5])
50+
assert_array_equal(r, [np.inf, np.inf, np.inf])
51+
52+
def test_extrap_on_both_sides_only(self):
53+
r = self.interpolate([1, 2, 5, 6], [3, 4])
54+
assert_array_equal(r, [-np.inf, -np.inf, np.inf, np.inf])
55+
56+
def test_interp_and_extrap(self):
57+
r = self.interpolate([1, 2, 3, 5, 6], [2, 4, 5])
58+
assert_array_equal(r, [-np.inf, 0, 1, 2, np.inf])
59+
60+
def test_nan_in_target(self):
61+
msg = 'The target coordinate .* NaN'
62+
with self.assertRaisesRegexp(ValueError, msg):
63+
self.interpolate([1, np.nan], [2, 4, 5])
64+
65+
def test_nan_in_src(self):
66+
msg = 'The source coordinate .* NaN'
67+
with self.assertRaisesRegexp(ValueError, msg):
68+
self.interpolate([1], [0, np.nan])
69+
70+
def test_all_nan_in_src(self):
71+
r = self.interpolate([1, 2, 3, 4], [np.nan, np.nan, np.nan])
72+
assert_array_equal(r, [np.nan, np.nan, np.nan, np.nan])
73+
74+
def test_nan_in_src_not_a_problem(self):
75+
# If we pick levels low enough, we can get away with having NaNs
76+
# in the source.
77+
r = self.interpolate([1, 3], [2, 4, np.nan])
78+
assert_array_equal(r, [-np.inf, 1])
79+
80+
def test_no_levels(self):
81+
r = self.interpolate([], [2, 4, np.nan])
82+
assert_array_equal(r, [])
83+
84+
def test_wrong_rising_target(self):
85+
r = self.interpolate([2, 1], [1, 2])
86+
assert_array_equal(r, [1, np.inf])
87+
88+
def test_wrong_rising_source(self):
89+
r = self.interpolate([1, 2], [2, 1])
90+
assert_array_equal(r, [-np.inf, 0])
91+
92+
def test_wrong_rising_source_and_target(self):
93+
# If we overshoot the first level, there is no hope,
94+
# so we end up extrapolating.
95+
r = self.interpolate([3, 2, 1, 0], [2, 1])
96+
assert_array_equal(r, [np.inf, np.inf, np.inf, np.inf])
97+
98+
def test_non_monotonic_coordinate_interp(self):
99+
result = self.interpolate([15, 5, 15.], [10., 20, 0, 20])
100+
assert_array_equal(result, [1, 2, 3])
101+
102+
def test_non_monotonic_coordinate_extrap(self):
103+
result = self.interpolate([0, 15, 16, 17, 5, 15., 25], [10., 40, 0, 20])
104+
assert_array_equal(result, [-np.inf, 1, 1, 1, 2, 3, np.inf])
105+
106+
107+
class Test_INTERPOLATE_LINEAR(unittest.TestCase):
108+
def interpolate(self, x_target):
109+
interpolation = stratify.INTERPOLATE_LINEAR
110+
extrapolation = vinterp._TestableDirectionExtrapKernel()
111+
112+
x_src = np.arange(5)
113+
fx_src = 10 * x_src
114+
115+
# Use -2 to test negative number support.
116+
return stratify.interpolate(np.array(x_target) - 2, x_src - 2, fx_src,
117+
interpolation=interpolation,
118+
extrapolation=extrapolation)
119+
120+
def test_on_the_mark(self):
121+
assert_array_equal(self.interpolate([0, 1, 2, 3, 4]),
122+
[0, 10, 20, 30, 40])
123+
124+
def test_inbetween(self):
125+
assert_array_equal(self.interpolate([0.5, 1.25, 2.5, 3.75]),
126+
[5, 12.5, 25, 37.5])
127+
128+
def test_high_precision(self):
129+
assert_array_almost_equal(self.interpolate([1.123456789]),
130+
[11.23456789], decimal=6)
131+
132+
133+
class Test_INTERPOLATE_NEAREST(unittest.TestCase):
134+
def interpolate(self, x_target):
135+
interpolation = stratify.INTERPOLATE_NEAREST
136+
extrapolation = vinterp._TestableDirectionExtrapKernel()
137+
138+
x_src = np.arange(5)
139+
fx_src = 10 * x_src
140+
141+
# Use -2 to test negative number support.
142+
return stratify.interpolate(np.array(x_target) - 2, x_src - 2, fx_src,
143+
interpolation=interpolation,
144+
extrapolation=extrapolation)
145+
146+
def test_on_the_mark(self):
147+
assert_array_equal(self.interpolate([0, 1, 2, 3, 4]),
148+
[0, 10, 20, 30, 40])
149+
150+
def test_inbetween(self):
151+
# Nearest rounds down for exactly half way.
152+
assert_array_equal(self.interpolate([0.5, 1.25, 2.5, 3.75]),
153+
[0, 10, 20, 40])
154+
155+
def test_high_precision(self):
156+
assert_array_equal(self.interpolate([1.123456789]),
157+
[10])
158+
159+
160+
class Test_EXTRAPOLATE_NAN(unittest.TestCase):
161+
def interpolate(self, x_target):
162+
interpolation = vinterp._TestableIndexInterpKernel()
163+
extrapolation = stratify.EXTRAPOLATE_NAN
164+
165+
x_src = np.arange(5)
166+
fx_src = 10 * x_src
167+
168+
# Use -2 to test negative number support.
169+
return stratify.interpolate(np.array(x_target) - 2, x_src - 2, fx_src,
170+
interpolation=interpolation,
171+
extrapolation=extrapolation)
172+
173+
def test_below(self):
174+
assert_array_equal(self.interpolate([-1]), [np.nan])
175+
176+
def test_above(self):
177+
assert_array_equal(self.interpolate([5]), [np.nan])
178+
179+
180+
class Test_EXTRAPOLATE_NEAREST(unittest.TestCase):
181+
def interpolate(self, x_target):
182+
interpolation = vinterp._TestableIndexInterpKernel()
183+
extrapolation = stratify.EXTRAPOLATE_NEAREST
184+
185+
x_src = np.arange(5)
186+
fx_src = 10 * x_src
187+
188+
# Use -2 to test negative number support.
189+
return stratify.interpolate(np.array(x_target) - 2, x_src - 2, fx_src,
190+
interpolation=interpolation,
191+
extrapolation=extrapolation)
192+
193+
def test_below(self):
194+
assert_array_equal(self.interpolate([-1]), [0.])
195+
196+
def test_above(self):
197+
assert_array_equal(self.interpolate([5]), [40])
198+
199+
200+
class Test__Interpolator(unittest.TestCase):
201+
def test_axis_m1(self):
202+
data = np.empty([5, 4, 23, 7, 3])
203+
zdata = np.empty([5, 4, 23, 7, 3])
204+
i = vinterp._Interpolator([1, 3], zdata, data)
205+
# 1288 == 5 * 4 * 23 * 7
206+
self.assertEqual(i._result_working_shape, (1, 3220, 2, 1))
207+
self.assertEqual(i.result_shape, (5, 4, 23, 7, 2))
208+
self.assertEqual(i._zp_reshaped.shape, (3220, 3, 1))
209+
self.assertEqual(i._fp_reshaped.shape, (1, 3220, 3, 1))
210+
self.assertEqual(i.axis, -1)
211+
self.assertEqual(i.orig_shape, data.shape)
212+
self.assertIsInstance(i.z_target, np.ndarray)
213+
self.assertEqual(list(i.z_target), [1, 3])
214+
215+
def test_axis_0(self):
216+
data = zdata = np.empty([5, 4, 23, 7, 3])
217+
i = vinterp._Interpolator([1, 3], data, zdata, axis=0)
218+
# 1932 == 4 * 23 * 7 *3
219+
self.assertEqual(i._result_working_shape, (1, 1, 2, 1932))
220+
self.assertEqual(i.result_shape, (2, 4, 23, 7, 3))
221+
self.assertEqual(i._zp_reshaped.shape, (1, 5, 1932))
222+
223+
def test_axis_2(self):
224+
data = zdata = np.empty([5, 4, 23, 7, 3])
225+
i = vinterp._Interpolator([1, 3], data, zdata, axis=2)
226+
# 1932 == 4 * 23 * 7 *3
227+
self.assertEqual(i._result_working_shape, (1, 20, 2, 21))
228+
self.assertEqual(i.result_shape, (5, 4, 2, 7, 3))
229+
self.assertEqual(i._zp_reshaped.shape, (20, 23, 21))
230+
231+
def test_inconsistent_shape(self):
232+
data = np.empty([5, 4, 23, 7, 3])
233+
zdata = np.empty([5, 4, 3, 7, 3])
234+
with self.assertRaises(ValueError):
235+
vinterp._Interpolator([1, 3], data, zdata, axis=2)
236+
237+
def test_axis_out_of_bounds(self):
238+
data = np.empty([5, 4])
239+
zdata = np.empty([5, 4])
240+
with self.assertRaises(ValueError):
241+
vinterp._Interpolator([1, 3], data, zdata, axis=4)
242+
243+
def test_result_dtype_f4(self):
244+
interp = vinterp._Interpolator([17.5], np.arange(4) * 10,
245+
np.arange(4, dtype='f4'))
246+
result = interp.interpolate()
247+
248+
self.assertEqual(interp._target_dtype, np.dtype('f4'))
249+
self.assertEqual(result.dtype, np.dtype('f4'))
250+
251+
def test_result_dtype_f8(self):
252+
interp = vinterp._Interpolator([17.5], np.arange(4) * 10,
253+
np.arange(4, dtype='f8'))
254+
result = interp.interpolate()
255+
256+
self.assertEqual(interp._target_dtype, np.dtype('f8'))
257+
self.assertEqual(result.dtype, np.dtype('f8'))
258+
259+
260+
class Test__Interpolator_interpolate_z_target_nd(unittest.TestCase):
261+
def test_target_z_3d_axis_0(self):
262+
z_target = z_source = f_source = np.arange(3) * np.ones([4, 2, 3])
263+
interp = vinterp._Interpolator(z_target, z_source, f_source,
264+
axis=0, extrapolation=stratify.EXTRAPOLATE_NEAREST)
265+
result = interp.interpolate_z_target_nd()
266+
assert_array_equal(result, f_source)
267+
268+
def test_target_z_3d_axis_m1(self):
269+
z_target = z_source = f_source = np.arange(3) * np.ones([4, 2, 3])
270+
interp = vinterp._Interpolator(z_target, z_source, f_source,
271+
axis=-1, extrapolation=stratify.EXTRAPOLATE_NEAREST)
272+
result = interp.interpolate_z_target_nd()
273+
assert_array_equal(result, f_source)
274+
275+
276+
if __name__ == '__main__':
277+
unittest.main()

0 commit comments

Comments
 (0)