Skip to content

Commit df37ad7

Browse files
committed
warnings
1 parent 741b92d commit df37ad7

9 files changed

Lines changed: 115 additions & 24 deletions

File tree

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def get_version():
5151
description='High Altitude Balloon Landing Prediction Software',
5252
long_description=long_description,
5353
test_suite='nose.collector',
54-
tests_require=['nose', 'mock'],
54+
tests_require=['nose', 'mock', 'Flask-Testing'],
5555
install_requires=[
5656
"magicmemoryview",
5757
"ruaumoko",

tawhiri/interpolate.pyx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ memory access.
3737

3838

3939
from magicmemoryview import MagicMemoryView
40+
from .warnings cimport WarningCounts
4041

4142

4243
# These need to match Dataset.axes.variable
@@ -64,7 +65,7 @@ class RangeError(ValueError):
6465
super(RangeError, self).__init__(s)
6566

6667

67-
def make_interpolator(dataset):
68+
def make_interpolator(dataset, WarningCounts warnings):
6869
"""
6970
Produce a function that can get wind data from `dataset`
7071
@@ -75,15 +76,18 @@ def make_interpolator(dataset):
7576

7677
cdef float[:, :, :, :, :] data
7778

79+
if warnings is None:
80+
raise TypeError("Warnings must not be None")
81+
7882
data = MagicMemoryView(dataset.array, (65, 47, 3, 361, 720), b"f")
7983

8084
def f(hour, lat, lng, alt):
81-
return get_wind(data, hour, lat, lng, alt)
85+
return get_wind(data, warnings, hour, lat, lng, alt)
8286

8387
return f
8488

8589

86-
cdef object get_wind(dataset ds,
90+
cdef object get_wind(dataset ds, WarningCounts warnings,
8791
double hour, double lat, double lng, double alt):
8892
"""
8993
Return [u, v] wind components for the given position.
@@ -111,12 +115,15 @@ cdef object get_wind(dataset ds,
111115
else:
112116
lerp = 0.5
113117

118+
if lerp < 0: warnings.altitude_too_high += 1
119+
if alt < 0: warnings.altitude_too_low += 1
120+
114121
cdef Lerp1 alt_lerp = Lerp1(altidx, lerp)
115122

116123
u = interp4(ds, lerps, alt_lerp, VAR_U)
117124
v = interp4(ds, lerps, alt_lerp, VAR_V)
118125

119-
return u, v
126+
return u, v,
120127

121128
cdef long pick(double left, double step, long n, double value,
122129
object variable_name, Lerp1[2] out) except -1:
@@ -171,6 +178,7 @@ cdef double interp3(dataset ds, Lerp3[8] lerps, long variable, long level):
171178

172179
return r
173180

181+
# Searches for the largest index lower than target, excluding the topmost level.
174182
cdef long search(dataset ds, Lerp3[8] lerps, double target):
175183
cdef long lower, upper, mid
176184
cdef double test

tawhiri/models.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ def drag_descent(t, lat, lng, alt):
7373
## Sideways Models ############################################################
7474

7575

76-
def make_wind_velocity(dataset):
76+
def make_wind_velocity(dataset, warningcounts):
7777
"""Return a wind-velocity model, which gives lateral movement at
7878
the wind velocity for the current time, latitude, longitude and
7979
altitude. The `dataset` argument is the wind dataset in use.
8080
"""
81-
get_wind = interpolate.make_interpolator(dataset)
81+
get_wind = interpolate.make_interpolator(dataset, warningcounts)
8282
dataset_epoch = calendar.timegm(dataset.ds_time.timetuple())
8383
def wind_velocity(t, lat, lng, alt):
8484
t -= dataset_epoch
@@ -159,7 +159,7 @@ def terminator(t, lat, lng, alt):
159159

160160

161161
def standard_profile(ascent_rate, burst_altitude, descent_rate,
162-
wind_dataset, elevation_dataset):
162+
wind_dataset, elevation_dataset, warningcounts):
163163
"""Make a model chain for the standard high altitude balloon situation of
164164
ascent at a constant rate followed by burst and subsequent descent
165165
at terminal velocity under parachute with a predetermined sea level
@@ -172,26 +172,26 @@ def standard_profile(ascent_rate, burst_altitude, descent_rate,
172172
"""
173173

174174
model_up = make_linear_model([make_constant_ascent(ascent_rate),
175-
make_wind_velocity(wind_dataset)])
175+
make_wind_velocity(wind_dataset, warningcounts)])
176176
term_up = make_burst_termination(burst_altitude)
177177

178178
model_down = make_linear_model([make_drag_descent(descent_rate),
179-
make_wind_velocity(wind_dataset)])
179+
make_wind_velocity(wind_dataset, warningcounts)])
180180
term_down = make_elevation_data_termination(elevation_dataset)
181181

182182
return ((model_up, term_up), (model_down, term_down))
183183

184184

185-
def float_profile(ascent_rate, float_altitude, stop_time, dataset):
185+
def float_profile(ascent_rate, float_altitude, stop_time, dataset, warningcounts):
186186
"""Make a model chain for the typical floating balloon situation of ascent
187187
at constant altitude to a float altitude which persists for some
188188
amount of time before stopping. Descent is in general not modelled.
189189
"""
190190

191191
model_up = make_linear_model([make_constant_ascent(ascent_rate),
192-
make_wind_velocity(dataset)])
192+
make_wind_velocity(dataset, warningcounts)])
193193
term_up = make_burst_termination(float_altitude)
194-
model_float = make_wind_velocity(dataset)
194+
model_float = make_wind_velocity(dataset, warningcounts)
195195
term_float = make_time_termination(stop_time)
196196

197197
return ((model_up, term_up), (model_float, term_float))

tawhiri/warnings.pxd

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright 2016 Daniel Richman
2+
#
3+
# This file is part of Tawhiri.
4+
#
5+
# Tawhiri is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# Tawhiri is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with Tawhiri. If not, see <http://www.gnu.org/licenses/>.
17+
18+
# Cython compiler directives:
19+
#
20+
# cython: language_level=3
21+
22+
cdef class WarningCounts:
23+
cdef public int altitude_too_low
24+
cdef public int altitude_too_high

tawhiri/warnings.pyx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright 2016 Daniel Richman
2+
#
3+
# This file is part of Tawhiri.
4+
#
5+
# Tawhiri is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# Tawhiri is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with Tawhiri. If not, see <http://www.gnu.org/licenses/>.
17+
18+
# Cython compiler directives:
19+
#
20+
# cython: language_level=3
21+
22+
"""
23+
A WarningCounts object is a set of flags that record which warnings have
24+
fired, and how many times they have fired.
25+
"""
26+
27+
cdef class WarningCounts:
28+
def __init__(self):
29+
self.altitude_too_low = 0
30+
self.altitude_too_high = 0
31+
32+
@property
33+
def any(self):
34+
return bool(self.altitude_too_low or self.altitude_too_high)
35+
36+
def to_dict(self):
37+
res = \
38+
{ "altitude_too_low": self.altitude_too_low
39+
, "altitude_too_high": self.altitude_too_high
40+
}
41+
42+
for key in list(res.keys()):
43+
if res[key] == 0:
44+
del res[key]
45+
46+
return res

testing/bench_prediction.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88

99
from tawhiri import solver, models
1010
from tawhiri.dataset import Dataset as WindDataset
11+
from tawhiri.warnings import WarningCounts
1112
from ruaumoko import Dataset as ElevationDataset
1213

1314
elevation = ElevationDataset()
15+
warningcounts = WarningCounts()
1416

1517
lat0 = 52.0
1618
lng0 = 0.0
@@ -23,7 +25,7 @@
2325
wind = WindDataset.open_latest(persistent=True)
2426
t0 = wind.ds_time + timedelta(hours=12)
2527
t0 = calendar.timegm(t0.timetuple())
26-
stages = models.standard_profile(5.0, 30000, 5.0, wind, elevation)
28+
stages = models.standard_profile(5.0, 30000, 5.0, wind, elevation, warningcounts)
2729
result = solver.solve(t0, lat0, lng0, alt0, stages)
2830
end_time = time.time()
2931

testing/test_prediction.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@
88

99
from tawhiri import solver, models, kml
1010
from tawhiri.dataset import Dataset as WindDataset
11+
from tawhiri.warnings import WarningCounts
1112
from ruaumoko import Dataset as ElevationDataset
1213

1314
lat0 = 52.5563
1415
lng0 = 360 - 3.1970
15-
alt0 = 0.0
16-
t0 = calendar.timegm(datetime(2014, 2, 19, 15).timetuple())
16+
alt0 = 100.0
17+
t0 = calendar.timegm(datetime(2016, 11, 28, 15).timetuple())
1718

1819
wind = WindDataset.open_latest()
1920
elevation = ElevationDataset()
21+
warningcounts = WarningCounts()
2022

21-
stages = models.standard_profile(5.0, 30000, 5.0, wind, elevation)
23+
stages = models.standard_profile(5.0, 30000, 5.0, wind, elevation, warningcounts)
2224
rise, fall = solver.solve(t0, lat0, lng0, alt0, stages)
2325

2426
assert rise[-1] == fall[0]
@@ -34,4 +36,6 @@
3436
{'name': 'burst', 'description': 'TODO', 'point': fall[0]}
3537
]
3638

39+
print("Warnings:", str(warningcounts.to_dict()))
40+
3741
kml.kml([rise, fall], markers, 'test_prediction.kml')

tests/test_models.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from nose.tools import assert_true, assert_false
2121
from mock import patch, Mock
2222

23-
from tawhiri import models
23+
from tawhiri import models, warnings
2424

2525

2626
class TestModels:
@@ -71,7 +71,8 @@ def test_wind_velocity(self, timegm, make_interpolator):
7171
get_wind.return_value = 3.0, -5.0
7272
make_interpolator.return_value = get_wind
7373

74-
f = models.make_wind_velocity(ds)
74+
w = warnings.WarningCounts()
75+
f = models.make_wind_velocity(ds, w)
7576
dlat, dlng, zero = f(10.0, 52.0, 0.5, 1000.0)
7677
assert_equal(zero, 0.0)
7778
get_wind.assert_called_with(0.0, 52.0, 0.5, 1000.0)
@@ -80,6 +81,8 @@ def test_wind_velocity(self, timegm, make_interpolator):
8081
assert_almost_equal(dlat, -4.495895997e-5)
8182
assert_almost_equal(dlng, 4.381527359e-5)
8283

84+
assert not w.any
85+
8386
def test_burst_termination(self):
8487
for alt in (0.0, 5000.0, 50000.0):
8588
f = models.make_burst_termination(alt)
@@ -152,6 +155,7 @@ def test_any_terminator(self):
152155
@patch('tawhiri.models.make_drag_descent')
153156
@patch('tawhiri.models.make_elevation_data_termination')
154157
def test_standard_profile(self, elev, drag, burst, wind, const, linear):
158+
warns = warnings.WarningCounts()
155159
wind_ds = Mock()
156160
elev_ds = Mock()
157161
const.return_value = 'const'
@@ -160,32 +164,35 @@ def test_standard_profile(self, elev, drag, burst, wind, const, linear):
160164
burst.return_value = 'burst'
161165
drag.return_value = 'drag'
162166
elev.return_value = 'elev'
163-
model = models.standard_profile(5.0, 30000.0, 6.0, wind_ds, elev_ds)
167+
model = models.standard_profile(5.0, 30000.0, 6.0, wind_ds, elev_ds, warns)
164168
const.assert_called_with(5.0)
165-
wind.assert_called_with(wind_ds)
169+
wind.assert_called_with(wind_ds, warns)
166170
linear.assert_any_call(['const', 'wind'])
167171
burst.assert_called_with(30000.0)
168172
drag.assert_called_with(6.0)
169173
linear.assert_called_with(['drag', 'wind'])
170174
elev.assert_called_with(elev_ds)
171175
assert_equal(model, (('linear', 'burst'), ('linear', 'elev')))
176+
assert not warns.any
172177

173178
@patch('tawhiri.models.make_linear_model')
174179
@patch('tawhiri.models.make_constant_ascent')
175180
@patch('tawhiri.models.make_wind_velocity')
176181
@patch('tawhiri.models.make_burst_termination')
177182
@patch('tawhiri.models.make_time_termination')
178183
def test_float_profile(self, time, burst, wind, const, linear):
184+
warns = warnings.WarningCounts()
179185
wind_ds = Mock()
180186
time.return_value = 'time'
181187
burst.return_value = 'burst'
182188
wind.return_value = 'wind'
183189
const.return_value = 'const'
184190
linear.return_value = 'linear'
185-
model = models.float_profile(5.0, 12000.0, 7200.0, wind_ds)
191+
model = models.float_profile(5.0, 12000.0, 7200.0, wind_ds, warns)
186192
const.assert_called_with(5.0)
187-
wind.assert_called_with(wind_ds)
193+
wind.assert_called_with(wind_ds, warns)
188194
linear.assert_called_with(['const', 'wind'])
189195
burst.assert_called_with(12000.0)
190196
time.assert_called_with(7200.0)
191197
assert_equal(model, (('linear', 'burst'), ('wind', 'time')))
198+
assert not warns.any

tests/testapi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import json
44

5-
from flask.ext.testing import TestCase
5+
from flask_testing import TestCase
66
from mock import patch, MagicMock
77
from urllib.parse import urlencode
88

0 commit comments

Comments
 (0)