Skip to content

Commit

Permalink
radiation_s and sunshine_hours are checked for range errors now
Browse files Browse the repository at this point in the history
  • Loading branch information
sherzodr authored and sherzodr committed Oct 6, 2020
1 parent 02f5362 commit bbc7a4c
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 34 deletions.
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

setup(
name="penmon",
version="0.1.8",
version="0.1.9",
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="sherzodr@gmail.com",
license="MIT",
url="https://github.com/sherzodr/penmon",
download_url="https://github.com/sherzodr/penmon/archive/0.1.8.tar.gz",
download_url="https://github.com/sherzodr/penmon/archive/0.1.9.tar.gz",
py_modules=["penmon.eto"],
package_dir={'': 'src'},
packages=["penmon"],
Expand Down
96 changes: 64 additions & 32 deletions src/penmon/eto.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
"""
Penman-Monteith Equation implementation.
Penman-Monteith Equation implementation in Python.
Full implementation of Penman-Monteith ETo equation based on UAN-FAO
Irrigation and Drainage Paper 56. Document is available at http://www.fao.org/3/X0490E/x0490e00.htm
[Irrigation and Drainage Paper 56](http://www.fao.org/3/X0490E/x0490e00.htm)
Penman-Monteith equation is used to calculate reference crop evapotranspiration (ETo)
for a given location using available climate data. This method provides many ways of estimating
missing climate data using minimal data.
@author: sherzodr@gmail.com
"""

import math
import datetime as dt

Expand All @@ -24,22 +22,29 @@ def is_number(s):


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

def __init__(self, latitude, altitude, anemometer_height=2):
"""
Two arguments are required: latitude in decimal format, and
altitude in meters. Southern hemisphere must have negative latitude.
Public attributes are:
* latitude - user provided (float)
* altitude - user provided (int)
Required parameters:
:param latitude: latitude of the location in decimal format. For southern
hemisphere negative number must be used
:type latitude: float
:param altitude: altitude (elevation) of the location in meters
:type altitude: int
:param anemometer_height=2: height of the anemometer (wind-speed)
measuring device
:type anemometer_height: int
Following are additional attributes that you can get/set on this station
after instantiation:
* latitude_rad - latitude in radian, alculated based on latitude
* days - dictionary of days recorded (or calculated) by this station
* anemometer_height - defaults to 2m
* climate - set to default **Climate()**
* climate - set to default **Climate()** instance
* ref_crop - instance of **Crop** class, which sets default chracteristics
of the reference crop according to the paper.
"""
Expand Down Expand Up @@ -70,21 +75,39 @@ def day(self, day_number):
""" See get_day()"""
return self.get_day(day_number)

def get_day(self, day_number, date_template="%Y-%m-%d", temp_min=None, temp_max=None, wind_speed=None, humidity_mean=None, radiation_s=None, sunshine_hours=None):
def get_day(self, day_number, date_template="%Y-%m-%d",
temp_min=None,
temp_max=None,
wind_speed=None,
humidity_mean=None,
radiation_s=None,
sunshine_hours=None
):
"""
Given a day number (integer type from 1-366) returns a **StationDay*** instance for
that day. Logs the day in *days* attribute of the **Station()** class.
If it receives a string it expects it to be in "yyyy-mm-dd" format, in which case
it parses the string into **datetime** and calculates day number
If your date format is different than assumed, you can adjust *date_template*
as the second parameter. For example, following all three lines are identical
day = station.get_day(229)
day = station.get_day("2020-08-16")
day = station.get_day('08/16/2020', '%m/%d/%Y')
You can pass the following named-parameters to the method:
- temp_min
- temp_max
- wind_speed
- radiation_s
- sunshine_hours
If *radiation_s* and *sunshine_hours* is out of range for this location
for this date (based on solar declination, sun-distance and daylight hours)
raises ValueError exception.
"""

if type(day_number) is str:
Expand All @@ -111,8 +134,18 @@ def get_day(self, day_number, date_template="%Y-%m-%d", temp_min=None, temp_max=
day.temp_max = temp_max
day.humidity_mean = humidity_mean
day.wind_speed = wind_speed
day.radiation_s = radiation_s
day.sunshine_hours = sunshine_hours

if radiation_s:
if radiation_s <= day.R_so():
day.radiation_s = radiation_s
else:
raise ValueError("Raditaion out of range")

if sunshine_hours:
if sunshine_hours <= day.daylight_hours() :
day.sunshine_hours = sunshine_hours
else:
raise ValueError("Sunshine hours out of range")

return day

Expand Down Expand Up @@ -148,7 +181,7 @@ def __init__(self, day_number, station):
calculate solar radiation and humidity data.
Following attributes of the class are available. They can be both set
and read from.
and get.
- day_number
- station - references **Station** class.
Expand All @@ -170,6 +203,7 @@ def __init__(self, day_number, station):
- sunshine_hours
-
"""

self.day_number = day_number
self.station = station

Expand Down Expand Up @@ -232,8 +266,7 @@ def dew_point(self):
return self.temp_dew

if self.temp_min and self.climate:
self.temp_dew = (self.temp_min - self.climate.dew_point_difference)
return self.temp_dew
return self.temp_min - self.climate.dew_point_difference

def atmospheric_pressure(self):
"""
Expand Down Expand Up @@ -338,7 +371,7 @@ def actual_vapour_pressure(self):
return round((self.humidity_mean / 100) * ((vp_max + vp_min) / 2), 3)

if self.dew_point():
return round(self.saturation_vapour_pressure(self.temp_dew), 3)
return round(self.saturation_vapour_pressure(self.dew_point()), 3)

def vapour_pressure_deficit(self):
if self.temp_min and self.temp_max:
Expand Down Expand Up @@ -426,6 +459,10 @@ def solar_radiation(self):
"""

if self.radiation_s:
# We need to make sure that solar radiation if set, is not
# larger than clear-sky solar radiation
if self.radiation_s > self.R_so():
raise ValueError("Solar radiation out ot range. Rso="+str(self.R_so()))
return self.radiation_s

n = self.sunshine_hours
Expand Down Expand Up @@ -462,9 +499,7 @@ def solar_radiation(self):

# n cannot be more than N, which is available daylight hours
if n > self.daylight_hours():
raise ValueError(
"Observed daylight hours cannot be more than possible daylight hours for this date"
)
raise ValueError( "Daylight hours out of range" )

a_s = 0.25
b_s = 0.50
Expand Down Expand Up @@ -700,9 +735,7 @@ def coastal(self):
return self

def island(self):
"""
Sets *island_location*. Sets *k_rs* to 0.19
"""
""" Sets *island_location*. Sets *k_rs* to 0.19 """
self.interior_location = False
self.coastal_location = False
self.island_location = True
Expand Down Expand Up @@ -742,8 +775,7 @@ def describe(self):


class Crop:
"""
Represents reference crop as assumed by Penman-Monteith equation."""
""" Represents reference crop as assumed by Penman-Monteith equation."""

def __init__(self, resistance_a=208, albedo=0.23, height=0.12):
"""
Expand Down
36 changes: 36 additions & 0 deletions tests/test_solar_range.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'''
Created on Oct 6, 2020
@author: sherzodr
'''
import unittest
import penmon.eto as pm

class Test(unittest.TestCase):

def test_solar_radiation_range(self):
station = pm.Station(41.42, 109)

try:
day=station.get_day("2020-08-16", temp_min=19.8, temp_max=29.9, radiation_s=27.5)
self.assertTrue(day.eto())
except ValueError:
self.assertTrue(True, "Out of range ValueError caught")
else:
self.assertTrue(False, "Out of range ValueError NOT caught")

def test_daylight_hours(self):
station = pm.Station(41.42, 109)

try:
day = station.get_day("2020-08-16", temp_min=19.8, temp_max=29.9, radiation_s=35)
self.assertTrue(day.eto())
except ValueError:
self.assertTrue(True, "out of range ValueError caught")

else:
self.assertTrue(False, "out of range ValueError NOT caught")

if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.test_solar_radiation_range']
unittest.main()

0 comments on commit bbc7a4c

Please sign in to comment.