Skip to content

Commit

Permalink
Better error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
sherzodr committed Oct 12, 2020
1 parent 1b4a2fa commit b2e12fe
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 41 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ Recording solar radiation gets us the most accurate ETo:

# TODO

* rounding consistency. Currently rounding of decimal points for floating point numbers
follow the [original paper][1]'s usage examples. I did so in order to be able
to test examples used in the paper. However this is far from ideal. This
issue of significant digits must be revisited in future revisions!

* *import_data()* must be supported by *penmon.eto.Station* class to import
bulk data into the station.

Expand Down
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

setup(
name="penmon",
version="0.2.0",
version="0.2.1",
description="Implementation of Penman-Monteith equation to calculate ET for a reference crop",
# long_description_content_type="text/markdown",
# long_description=long_description,
author="Sherzod RUZMETOV",
author_email="[email protected]",
license="MIT",
url="https://github.com/sherzodr/penmon",
download_url="https://github.com/sherzodr/penmon/archive/0.2.0.tar.gz",
download_url="https://github.com/sherzodr/penmon/archive/0.2.1.tar.gz",
py_modules=["penmon.eto"],
package_dir={'': 'src'},
packages=["penmon"],
Expand All @@ -23,4 +23,3 @@
"Topic :: Scientific/Engineering :: Atmospheric Science"
]
)

64 changes: 38 additions & 26 deletions src/penmon/eto.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@
CHECK_RADIATION_RANGE = True
CHECK_SUNSHINE_HOURS_RANGE = True


def is_number(s):
try:
float(s)
return True
except ValueError:
return False


class Station:
""" Class that implements a weather station at a known latitude and elevation."""

Expand All @@ -50,6 +48,12 @@ def __init__(self, latitude, altitude, anemometer_height=2):
* climate - set to default **Climate()** instance
* ref_crop - instance of **Crop** class, which sets default chracteristics
of the reference crop according to the paper.
Should you wish to change assumes Climate and Crop characteristics
you can do so after the object is innitialized, like so:
station=Station(41.42, 109)
station.ref_crop = Crop(albedo=0.25, height=0.35)
"""

if not type(latitude) is float:
Expand Down Expand Up @@ -81,6 +85,7 @@ def day(self, day_number):
def get_day(self, day_number, date_template="%Y-%m-%d",
temp_min=None,
temp_max=None,
temp_mean=None,
wind_speed=None,
humidity_mean=None,
radiation_s=None,
Expand Down Expand Up @@ -135,6 +140,7 @@ def get_day(self, day_number, date_template="%Y-%m-%d",
self.days[day_number] = day
day.temp_min = temp_min
day.temp_max = temp_max
day.temp_mean=temp_mean
day.humidity_mean = humidity_mean
day.wind_speed = wind_speed

Expand Down Expand Up @@ -206,7 +212,6 @@ def __init__(self, day_number, station):
- vapour_pressure
- wind_speed
- radiation_s
- radiation_a
- stephan_boltzman_constant
- climate - convenient reference to station.climate
- sunshine_hours
Expand All @@ -224,7 +229,6 @@ def __init__(self, day_number, station):
self.humidity_max = None
self.wind_speed = None
self.radiation_s = None
self.radiation_a = None
self.temp_dew = None
self.temp_dry = None
self.temp_wet = None
Expand Down Expand Up @@ -254,7 +258,7 @@ def wind_speed_2m(self):
# speed at 2m
if self.wind_speed and self.station.anemometer_height != 2:
return round(self.wind_speed * (4.87 /
math.log(67.8 * self.station.anemometer_height - 5.42)), 1)
math.log(67.8 * self.station.anemometer_height - 5.42)), 1)

# if we reach this far no wind information is available to work with. we
# consult if station has any climatic data, in which case we try to
Expand Down Expand Up @@ -551,6 +555,9 @@ def R_nl(self):
"""
Net longwave radiation. (Eq. 39)
"""

if not ( self.temp_max and self.temp_min ):
raise Exception("Net longwave radiation cannot be calculated without min/max temperature")

TmaxK = self.temp_max + 273.16
TminK = self.temp_min + 273.16
Expand All @@ -560,15 +567,19 @@ def R_nl(self):

sb_constant = self.stephan_boltzmann_constant
return round(sb_constant * ((TmaxK ** 4 + TminK ** 4) / 2) *
(0.34 - 0.14 * math.sqrt(ea)) *
(0.34 - 0.14 * math.sqrt(ea)) *
(1.35 * (rs / rso) - 0.35), 1)

def net_radiation(self):
"""
Net Radiation. (Eq. 40)
"""
ns = self.R_ns()
nl = self.R_nl()

try:
nl = self.R_nl()
except Exception as e:
raise(str(e))

if (not ns is None) and (not nl is None):
return round(ns - nl, 1)
Expand Down Expand Up @@ -613,31 +624,32 @@ def eto(self):
Eq. 6
"""

Tmax = self.temp_max
Tmin = self.temp_min

# if we cannot get wind speed data we revert to Hargreaves formula.
# Which is not ideal!
# Which is not ideal! This can happen only if user removed default 'climate'
# reference
if not self.wind_speed_2m():
return self.eto_hargreaves()

if Tmax and Tmin:
Tmean = (Tmax + Tmin) / 2

slope_of_vp = self.slope_of_saturation_vapour_pressure(Tmean)
net_radiation = self.net_radiation()
G = self.soil_heat_flux()
u2m = self.wind_speed_2m()

eto_nominator = (0.408 * slope_of_vp * (net_radiation - G) +
self.psychrometric_constant() * (900 / (Tmean + 273)) * u2m *
self.vapour_pressure_deficit())
if self.Tmean() == None:
raise Exception(
"Cannot calculate eto(): temp_mean (mean temperature) is missing")

eto_denominator = slope_of_vp + self.psychrometric_constant() * (1 + 0.34 * u2m)

return round(eto_nominator / eto_denominator, 2)

return None
try:
net_radiation = self.net_radiation()
except Exception as e:
raise(str(e))

Tmean=self.Tmean()
slope_of_vp = self.slope_of_saturation_vapour_pressure(Tmean)
G = self.soil_heat_flux()
u2m = self.wind_speed_2m()
eto_nominator = (0.408 * slope_of_vp * (net_radiation - G) +
self.psychrometric_constant() * (900 / (Tmean + 273)) * u2m *
self.vapour_pressure_deficit())

eto_denominator = slope_of_vp + self.psychrometric_constant() * (1 + 0.34 * u2m)
return round(eto_nominator / eto_denominator, 2)


class Climate:
Expand Down
27 changes: 15 additions & 12 deletions tests/error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import unittest
import penmon.eto as pm


class Test(unittest.TestCase):

def test_smoke(self):
Expand All @@ -15,23 +14,23 @@ def test_smoke(self):

def test_type_error(self):
try:
station = pm.Station(latitude=10, altitude=109)
pm.Station(latitude=10, altitude=109)
except TypeError:
self.assertTrue(True, "Exception was expected and raised")
else:
self.assertTrue(False, "Exception was expected but was NOT raised")

def test_latitude_range_error(self):
try:
station = pm.Station(latitude=100.0, altitude=100)
pm.Station(latitude=100.0, altitude=100)
except:
self.assertTrue(True, "Exception was expected and raised")
else:
self.assertTrue(False, "Exception was expected but was NOT raised")

def test_altitude_range_error(self):
try:
station = pm.Station(latitude=41.42, altitude=-1)
pm.Station(latitude=41.42, altitude=-1)
except:
self.assertTrue(True, "Exception was expected and raised")
else:
Expand All @@ -40,7 +39,7 @@ def test_altitude_range_error(self):
def test_day_number_type(self):
station = pm.Station(latitude=41.42, altitude=109)
try:
day = station.get_day(365.0)
station.get_day(365.0)
except TypeError:
self.assertTrue(True, "Exception was expected and raised")
else:
Expand All @@ -50,21 +49,25 @@ def test_day_number_range(self):
station = pm.Station(latitude=41.42, altitude=109)

try:
day = station.get_day(367)
station.get_day(367)
except:
self.assertTrue(True, "Exception was expected and raised")
else:
self.assertTrue(False, "Exception was expected but was NOT raised")


def test_immature_eto(self):
station = pm.Station(41.42, 109)
day = station.get_day(238)
day = station.get_day(238, temp_mean=25.00)
self.assertTrue(day.temp_mean != None, "temp_mean was set")
self.assertEqual(day.temp_mean, day.Tmean())

eto = day.eto()

self.assertEqual(eto, None)

# following code should raise an exception:
try:
day.eto()
except Exception as e:
self.assertTrue(True, str(e))
else:
self.assertTrue(False, "Exception was expected")

if __name__ == "__main__":
# import sys;sys.argv = ['', 'Test.testName']
Expand Down

0 comments on commit b2e12fe

Please sign in to comment.