# -------------------------------------------------------------------
# - NAME: judgingclass.py
# - AUTHOR: Reto Stauffer
# - DATE: 2014-09-21
# -------------------------------------------------------------------
# - DESCRIPTION: This is the judgingclass with the rules used
# after 2002-12-06.
# Note: you can simply make a copy of this
# class, adapt the scoring rules. Furthermore you
# have to define/change/adapt the rule in the
# ComputePoints.py script.
# -------------------------------------------------------------------
# - EDITORIAL: 2014-09-21, RS: Created file on thinkreto.
# -------------------------------------------------------------------
# - L@ST MODIFIED: 2018-01-22 12:04 on marvin
# -------------------------------------------------------------------
# - Need numpy everywhere
import numpy as np
[docs]class judging(object):
"""This is a judgingclass - a class used to compute the points
a user gets on a specific weekend. Please note that it is possible
that the rules change somewhen and that there is a second judgingclass.
The class contains public attributes tdate_min and tdate_max as a
safety-instrument. As soon as you would like to compute points
for a specific tournament which falls outside this limits the script
will stop in the operational mode not to re-compute old bets with
a wrong judgingclass.
Args:
quiet (:obj:`bool`): Default False, can be set to True to procude
some more output.
"""
# ----------------------------------------------------------------
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# Additional safety thing. If set (not NONE)
# These settings will be checked when method get_points is called
# with tdate input. In this case:
# - if tdate is smaller than tdate_min --> ERROR
# - if tdate bigger or equal to tdate_max --> ERROR
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# If you create a new judging class please modify not only
# tdate_min of the new class, be sure that you also set the
# tdate_max in the old judgingclass to avoid that older tournament
# dates can be based on the wrong judgingclass.
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# ----------------------------------------------------------------
## Do not use this judgingclass in the operational mode for tournaments
# smaller or equal to this date (days since 1970-01-01).
tdate_min = 12027
## If set - do not use this judgingclass in the operational mode
# for tournaments > this date (days sicne 1970-01-01).
tdate_max = None
def __init__(self,quiet=False):
"""Initialize new juding object.
Args:
quiet (:obj:`bool`): Default is False. Redudes the output
to a minimum if set to True. Used for some applications.
"""
if not quiet:
print ' Initializing judging class 2002-12-06 py'
self.quiet = quiet
# ----------------------------------------------------------------
# - Prepares data to insert them into the database.
# Creates list tuple out of two lists.
# ----------------------------------------------------------------
[docs] def _prepare_for_database_(self,userID,cityID,paramID,tdate,betdate,values):
"""Prepares ID/value pairs for database.
Creates a tuple list which can be used with
database.database.executemany. Used later with
the method 'points_to_database' within this class to update
the bets table.
Args:
userID (:obj:`list`): List containing :obj:`int`, numeric user ID.
cityID (:obj:`list`): List containing :obj:`int`, numeric city ID.
paramID (:obj:`list`): List containing :obj:`int`, numeric parameter ID.
tdate (:obj:`list`): List containing :obj:`int`, numeric tournament date, days since 1970-01-01.
betdate (:obj:`list`): List containing :obj:`int`, numeric betdate, days since 1970-01-01.
values (:obj:`list`): List containing the values (points).
Returns:
list of tuples: Re-structures the inputs to a list. The list has the
same length as the input lists but each list element is a tuple containing
the inputs. This object is then used for the executemany statement (write
data to database).
"""
if not len(userID) == len(values):
utils.exit('In judging.prepare_for_database got different lengths of userIDs and values!')
# - Create result
res = []
for i in range(len(values)):
res.append( (values[i],userID[i],cityID[i],paramID[i],tdate[i],betdate[i]) )
return res
# ----------------------------------------------------------------
# - Inserts data into database. They have to be prepared
# ----------------------------------------------------------------
[docs] def points_to_database(self,db,userID,cityID,paramID,tdate,betdate,values):
"""Updates the bets database. All inputs are lists and have to be of the
same length. (userID, cityID, paramID, tdate, betdate) define the unique
ID in the wetterturnier_bets database. Argument values is a list as well
containing the compute points.
Args:
userID (:obj:`list`): List containing :obj:`int`, numeric user ID.
cityID (:obj:`list`): List containing :obj:`int`, numeric city ID.
paramID (:obj:`list`): List containing :obj:`int`, numeric parameter ID.
tdate (:obj:`list`): List containing :obj:`int`, numeric tournament date, days since 1970-01-01.
betdate (:obj:`list`): List containing :obj:`int`, numeric betdate, days since 1970-01-01.
values (:obj:`list`): List containing the values (points).
"""
# - Prepare tuple list object
data = self._prepare_for_database_(userID,cityID,paramID,tdate,betdate,values)
sql = 'UPDATE '+db.prefix+'wetterturnier_bets SET points = %s ' + \
'WHERE userID=%s and cityID=%s and paramID=%s and tdate=%s and betdate=%s'
cur = db.cursor()
cur.executemany( sql, data )
# ----------------------------------------------------------------
# - Handling all different point computation methods.
# Stops if there is an undefined class.
# ----------------------------------------------------------------
[docs] def get_points(self,obs,what,data,special=None,tdate=None):
"""Compute the points. This is a generic function which is used
to compute the points for all required parameters. The inputs
define what has to be computed.
Args:
obs (:obj:`...`): Observations
what (:obj:`str`): Which parameter, this defines the method to be called (internally).
special (:obj:`float`): Some rules have 'special' sub-rules. This
speical is a float observation required to compute the points.
tdate (obj:`int`): tournament date, days since 1970-01-01 or None.
Used to test whether or not it is allowed to compute the
points for this specific tournament date with this judginclass.
Second level securety check not to use the wrong judgingclass
for a specific date.
Returns:
np.ndarray with floats: Returns a np.ndarray with the points rounded
to one digit.
"""
import utils
import numpy as np
# - If obs is none at all: return None
if obs is None: return(None)
# - Filter non-None obs
tmp = []
for rec in obs:
if not rec is None: tmp.append( rec )
if len(tmp) == 0:
return [0.] * len(data)
obs = tmp
# - If tdate is set: check if this special date is
# allowed in this judgingclass or not. Please note
# that this is not used in the TestPoints.py case.
if not tdate == None:
if not self.tdate_min == None:
if self.tdate_min > tdate:
import sys
utils.exit("judgingclass %s not allowed for tournament date %d" % \
(__name__,tdate))
if not self.tdate_max == None:
if self.tdate_max <= tdate:
import sys
utils.exit("judgingclass %s not allowed for tournament date %d" % \
(__name__,tdate))
# - What to plot?
method_to_use = '__points_%s__' % what
# - Not available?
if not method_to_use in dir(self):
utils.exit('Method %s does not exist in judgingclass' % method_to_use)
# - Loading method dynamical and call it.
if not self.quiet:
print ' - Using method: %s' % method_to_use
call = getattr(self,method_to_use)
# Round to one digit after the comma
return np.round( call(obs,data,special), 1 )
# -----------------------------------------------------------------
# - Compute residuals
# -----------------------------------------------------------------
def __residuals__(self,obs,data):
"""Helper function to compute the residuals.
If input 'data' lies in between minimum and maximum of 'obs' this
was a perfect hit (and 0.0 will be returned). Else the minimal
absolute difference to the closest 'obs' will be returned.
Args:
obs (): Observations.
data (): Forecasted values.
Returns:
np.ndarray of type float: np.ndarray containing the residuals.
"""
# - Observations min/max. If it is only one value min is
# equal to max.
obs = np.asarray(obs)
min = np.min(obs)
max = np.max(obs)
# - Compute residuals
resid = np.ndarray(len(data),dtype='float'); resid[:] = -999.
resid[ np.where(np.logical_and(data >= min, data <= max)) ] = 0.
resid[ np.where(data < min) ] = np.abs(min - data[ np.where(data < min) ])
resid[ np.where(data > max) ] = np.abs(max - data[ np.where(data > max) ])
return resid
# ----------------------------------------------------------------
# - Compute TTm (maximum temperature) points
# Compute TTn (minimum temperature) points
# Compute TTd (dewpoint temperature) points
# - They are using the same method (forwarding)
# ----------------------------------------------------------------
def __points_TTm__(self,obs,data,special):
"""Function to compute points for TTm (maximum temperature).
This is only an alias to __points_TTmTTnTTd__ as they all use
the same rule.
"""
return self.__points_TTmTTnTTd__(obs,data,special,0.3)
def __points_TTn__(self,obs,data,special):
"""Function to compute points for TTn (minimum temperature)
This is only an alias to __points_TTmTTnTTd__ as they all use
the same rule.
"""
return self.__points_TTmTTnTTd__(obs,data,special,0.3)
def __points_TTd__(self,obs,data,special):
"""Function to compute points for TTd (dew-point temperature)
This is only an alias to __points_TTmTTnTTd__ as they all use
the same rule.
"""
return self.__points_TTmTTnTTd__(obs,data,special,0.2)
# - Here TTm and TTn are getting computed
def __points_TTmTTnTTd__(self,obs,data,special,factor):
"""Rule function to compute points for minimum/maximum temperature
and dew-point temperature.
Args:
obs (): Observations.
data (): Forecasted values.
special (): Special observations for additional rules.
These are not used to compute the points in this function.
factor (): Factor for the loss of points for non-perfect forecasts.
As this factor differs for TTm/TTn and TTd it is specified
via input argument.
Returns:
Returns the corresponding points.
"""
if not self.quiet:
print ' - Called TTm/TTn/TTd point computation method'
data = np.asarray(data)
resid = self.__residuals__(obs,data)
# - Full points
points = np.ndarray(len(data),dtype='float'); points[:] = 10.
# - Deduction for the first 10 tenth
# Minus 0.1 Points for each difference unit
points = points - np.minimum( resid, 10. )*0.1
# - Deduction for the rest
# Minus 0.3 points for each difference unit
points = points - np.maximum( resid - 10., 0. )*factor
# - Show data (development stuff)
#for i in range(len(data)):
# print '%5d %5d %6.2f' % (data[i], resid[i], points[i])
return points
# ----------------------------------------------------------------
# - Compute N (cloud cover) points
# ----------------------------------------------------------------
def __points_N__(self,obs,data,special):
"""Rule function to compute points for clouod cover.
Args:
obs (): Observations.
data (): Forecasted values.
special (): Special observations for additional rules.
Returns:
Returns the corresponding points.
"""
if not self.quiet:
print ' - Called N point computation method'
data = np.asarray(data)
resid = self.__residuals__(obs,data)
# - Full points
points = np.ndarray(len(data),dtype='float'); points[:] = 6.
# - For a difference of 1-3 (10 - 30 in difference units)
# deduction of 1 per difference unit.
idx = np.where(np.logical_and(resid > 0, resid < 30))
points[idx] = points[idx] - resid[idx]/10.
# Minus 4 points if residual is 3 (30)
idx = np.where(resid == 30)
points[idx] = points[idx] - 4.
# Minus 6 points if residual is 4 (40)
idx = np.where(resid >= 40)
points[idx] = points[idx] - 6.
# - Special: if observation was 0 or 8 and the
# residual is not equal to 0: subtract one
# more point.
obs = np.asarray(obs); min = np.min(obs); max = np.max(obs)
if min == 80. or max == 0.:
idx = np.where(resid > 0)
points[idx] = points[idx] - 1.
# - Points cannot be negative
points = np.maximum( points, 0)
# - Show data (development stuff)
#for i in range(len(data)):
# print '%2d|%2d: %2d %2d %6.2f' % (min/10., max/10., data[i]/10., resid[i]/10., points[i])
return points
# ----------------------------------------------------------------
# - Compute Sd (sunshine duration) points
# ----------------------------------------------------------------
def __points_Sd__(self,obs,data,special):
"""Rule function to compute points for relative sunshine duration.
Args:
obs (): Observations.
data (): Forecasted values.
special (): Special observations for additional rules.
Returns:
Returns the corresponding points.
"""
if not self.quiet:
print ' - Called Sd point computation method'
data = np.asarray(data)
resid = self.__residuals__(obs,data)
# - Full points
points = np.ndarray(len(data),dtype='float'); points[:] = 5.
# - Deduction for each percent.
# Minus 0.01 Points for each difference unit
# Note: i store 1/10 percent in the database.
points = points - resid*0.01
# - If user bet was wrong (resid > 0) and one of the
# observations was 0 (0%) or 10 (1%): subtract additional 1.5 points.
obs = np.asarray(obs); min = np.min(obs); max = np.max(obs)
# - Additinoal 1.5 points less between observed 0% and bet 1% or
# higher the other way around.
# - minus 1.5 points. Why + 0.1? I allready subtracted
# 0.1 points because 0/10 makes 10 difference units times
# 0.01 above makes 0.1 points deduction. Therefore I
# have to subtract only 1.5 additional points here.
idx = np.where( np.logical_and(max == 0., data > 0.) )
points[idx] = points[idx] - 1.5 + 0.1
# - The same the other way around
idx = np.where( np.logical_and(min > 0., data == 0.) )
points[idx] = points[idx] - 1.5 + 0.1
# - Cannot be negative
points = np.maximum( points, 0)
# - Show data (development stuff)
#for i in range(len(data)):
# print '%3d|%3d: %3d %3d %6.2f' % (min/10., max/10., data[i]/10., resid[i]/10., points[i])
return points
# ----------------------------------------------------------------
# - Compute dd (wind direction)
# ----------------------------------------------------------------
def __points_dd__(self,obs,data,special):
"""Rule function to compute points for wind direction parameter.
Args:
obs (): Observations.
data (): Forecasted values.
special (): Special observations for additional rules.
Returns:
Returns the corresponding points.
"""
if not self.quiet:
print ' - Called dd point computation method'
data = np.asarray(data)
obs = np.asarray(obs);
try:
min = np.min(obs[np.where( np.logical_and(obs > 0., obs <= 3600. ))])
except:
min = None
try:
max = np.max(obs[np.where( np.logical_and(obs > 0., obs <= 3600. ))])
except:
max = None
# - Change minimum if angle (difference) is bigger than 180 degrees.
if not max == None and not min == None:
if max - min > 1800.:
tmp = max
max = min + 3600.
min = tmp
# - If min or max is none, min and max are equal
if not min == None and max == None: max = min
if min == None and not max == None: min = max
# - Lowest observed wind speed. Has to be on speical!
# If nothing is observed, assume ffmin == 0 (gives
# less negative points, however, that is not the players
# fault if there is no ff observation).
if len(special) == 0:
ffmin = 0
else:
ffmin = np.min( special )
# - Max points
maxpoints = 9.
# Give everyone max points at the beginning
p_normal = np.ndarray( len(data), dtype='float'); p_normal[:] = -999.
p_special = np.ndarray( len(data), dtype='float'); p_special[:] = -999.
all_resid = np.zeros( len(data), dtype='float')
# - Checking if we have had calm and/or variable conditions
calm = False; variable = False; normal = False
#falsch#if len( np.where(obs == 0.)[0] ) > 0: calm = True
#falsch#if len( np.where(obs == 9900.)[0] ) > 0: variable = True
if len( np.where(obs == 0.)[0] ) == len(obs): calm = True
if len( np.where(obs == 9900.)[0] ) == len(obs): variable = True
if len( np.where( np.logical_and(obs > 0., obs <= 3600.) )[0] ) > 0: normal = True
# -------------------------------------------------------------
# - Compute the normal residuals for dd if and only if
# there were "normal" dd observations.
# -------------------------------------------------------------
if normal:
idx = np.where( np.logical_and( data > 0., data <= 3600. ) )
# - If we do have two 'normal' observations and the difference
# is exactely 180 degrees all degree-bets are in between the
# two observations (there is no 'good' or 'bad' side - clock
# or counter clock wise).
dd_min = np.min( obs[np.where(np.logical_and(obs > 0.,obs<=3600.))] )
dd_max = np.max( obs[np.where(np.logical_and(obs > 0.,obs<=3600.))] )
dd_diff = np.abs( dd_min-dd_max )
if len(idx[0]) > 0:
# - Normal penalty
# If minimum wind was less than 6kt: 1.0 points per 10 deg
# If minimum wind was >= 6kt: 0.5 points per 10 deg
# Please note that the 'data' (bets) are in 1/10th of degrees
# and therefore 1.0/100. = 0.01, 0.5/100. = 0.005
if dd_diff == 1800.:
p_normal[idx] = maxpoints
all_resid[idx] = 0.
else:
the_obs = np.asarray([min,max])
residA = self.__residuals__(the_obs-3600.,data[idx])
residB = self.__residuals__(the_obs, data[idx])
residC = self.__residuals__(the_obs+3600.,data[idx])
resid = np.minimum(residA,residB)
resid = np.minimum(resid, residC)
if ffmin < 60.:
p_normal[idx] = maxpoints - resid*0.005
else:
p_normal[idx] = maxpoints - resid*0.01
all_resid[idx] = resid
# -------------------------------------------------------------
# - Deduction for people forecasted 0/990 (if wrong)
# forecast observed deduction
# ----------------------------------
# (a) 0. 9900. 5
# (b) 9900. 0. 5
# (c) 0/9900. >0/<3600. 7
# (d) 0/9900. >0/<3600. 7
# ----------------------------------
# -------------------------------------------------------------
if calm or variable:
if calm:
# - Full points for people obs 0 and forecast 0
p_special[np.where( data == 0. )] = maxpoints
# - If calm observed but forecast was in between >0/<=3600: -7
idx = np.where( np.logical_and( data > 0., data <= 3600. ) )
p_special[idx] = maxpoints - 7
# - If calm observed but 9900 forecasted: -5
idx = np.where( data == 9900. )
p_special[idx] = maxpoints - 5
if variable:
# - Full points for people obs 9900 and forecast 9900
p_special[np.where( data == 9900. )] = maxpoints
# - If variable observed but forecast was in between >0/<=3600: -7
idx = np.where( np.logical_and( data > 0., data <= 3600. ) )
p_special[idx] = maxpoints - 7
# - If variable observed but 0 forecasted: -5
idx = np.where( data == 0. )
p_special[idx] = maxpoints - 5
# - If 0 forecasted but not 'normal' wind direction observed
if not calm:
idx = np.where( data == 0. )
p_special[idx] = maxpoints - 7
# - If 9900 forecasted but not 'normal' wind direction observed
if not variable:
idx = np.where( data >= 9900. )
p_special[idx] = maxpoints - 7
# - Now take maximum points
points = np.maximum( p_special, p_normal )
points = np.maximum( points, 0 )
# - Show data (development stuff)
#print " FFMIN: %7.2f" % ffmin
#if calm: print ' CALM CONDITION TRUE'
#if variable: print ' VARIABLE CONDITION TRUE'
#print obs
#print min, max
#if min == None and max == None:
# for i in range(len(data)):
# print '--- | --- bet %3d %6.2f | n: %7.2f s: %7.2f | %7.2f' \
# % (data[i]/10., all_resid[i]/100, p_normal[i], p_special[i], points[i])
#else:
# for i in range(len(data)):
# print '%3d|%3d: bet %3d %6.2f | n: %7.2f s: %7.2f | %7.2f' \
# % (min/10., max/10., data[i]/10., all_resid[i]/100, p_normal[i], p_special[i], points[i])
return points
# ----------------------------------------------------------------
# - Compute ff (mean wind speed) points
# ----------------------------------------------------------------
def __points_ff__(self,obs,data,special):
"""Rule function to compute points for wind speed.
Args:
obs (): Observations.
data (): Forecasted values.
special (): Special observations for additional rules.
Returns:
Returns the corresponding points.
"""
if not self.quiet:
print ' - Called ff point computation method'
data = np.asarray(data)
resid = self.__residuals__(obs,data)
# - Full points
points = np.ndarray(len(data),dtype='float'); points[:] = 6.
# - Deduction
# Minus 0.1 Points for each difference unit
points = points - resid*0.1
# - Show data (development stuff)
#for i in range(len(data)):
# print '%5d %5d | %5d %5d %6.2f' % (np.min(obs), np.max(obs), data[i], resid[i], points[i])
return points
# ----------------------------------------------------------------
# - Compute fx (wind gusts) points
# ----------------------------------------------------------------
def __points_fx__(self,obs,data,special):
"""Rule function to compute points for gust speed.
Args:
obs (): Observations.
data (): Forecasted values.
special (): Special observations for additional rules.
Returns:
Returns the corresponding points.
"""
# - Getting min and max from the obs
data = np.asarray(data)
obs = np.asarray(obs);
min = np.min(obs)
max = np.max(obs)
# - To avoid wrong inputs: knots below 25 (250.)
# are set to 0!
data[np.where( data < 250. )] = 0
if not self.quiet:
print ' - Called fx point computation method'
data = np.asarray(data)
resid = self.__residuals__(obs,data)
# - Full points
maxpoints = 4.
points = np.ndarray(len(data),dtype='float'); points[:] = maxpoints
# - Default mode is minus 0.25 Points
# for the first 0-15 knots difference, afterwards
# minus 0.5 points for all above 15 knots difference.
def normal_penalty( resid ):
return np.minimum( resid, 150. )*0.025 + np.maximum( resid-150., 0)*0.05
# - Non-special penalty is if fx >= 250 and forecast >= 250 (250=25kt)
if max >= 250.:
idx = np.where( data >= 250. )
points[idx] = points[idx] - normal_penalty( resid[idx] )
# - For these where forecast (data) was 0. but obs was >= 250:
# Special rule. First: -3 points and then normal penalty
# for residuals - 250.
idx = np.where( np.logical_and( data == 0, min >= 250. ) )
points[idx] = maxpoints - 3 - normal_penalty( np.maximum(resid[idx]-250.,0) )
# - For these where forecast (data) was >= 250. but obs was == 0:
# Special rule. First: -3 points and then normal penalty
# for residuals - 250.
idx = np.where( np.logical_and( data >= 250, max == 0. ) )
points[idx] = maxpoints - 3 - normal_penalty( np.maximum(resid[idx]-250.,0) )
# - Now correcting:
# For all resid == 0: set points to maxpoints.
idx = np.where( resid == 0. )
points[idx] = maxpoints
# - Show data (development stuff)
#for i in range(len(data)):
# print '%5d %5d | %5d %5d %6.2f' % (min, max, data[i], resid[i], points[i])
return points
# ----------------------------------------------------------------
# - Compute Wv (maximum temperature) points
# Compute Wv (minimum temperature) points
# - They are using the same method (forwarding)
# ----------------------------------------------------------------
def __points_Wv__(self,obs,data,special):
"""Function to compute points for Wv (weather type noon)
This is only an alias to __points_WvWn__ as they all use
the same rule.
"""
return self.__points_WvWn__(obs,data,special)
def __points_Wn__(self,obs,data,special):
"""Function to compute points for Wn (weather type afternoon)
This is only an alias to __points_WvWn__ as they all use
the same rule.
"""
return self.__points_WvWn__(obs,data,special)
# - Here Wn and WvWn are getting computed
def __points_WvWn__(self,obs,data,special):
"""Rule function to compute points for weather types.
Args:
obs (): Observations.
data (): Forecasted values.
special (): Special observations for additional rules.
Returns:
Returns the corresponding points.
"""
if not self.quiet:
print ' - Called WvWn point computation method'
data = np.asarray(data)
# Deduction matrix list. Note that 1/2/3 will never be
# ranked (and cannot be forecasted). If something is wrong
# with my judgment class this leads to -89 points. If this
# occures we have to check this stuff.
# bet was 0 1 2 3 4 5 6 7 8 9
point_matrix = [[ 0.,99.,99.,99., 5., 7., 7., 7., 6., 6.,], # observed 0
[99.,99.,99.,99.,99.,99.,99.,99.,99.,99.,], # observed 1
[99.,99.,99.,99.,99.,99.,99.,99.,99.,99.,], # observed 2
[99.,99.,99.,99.,99.,99.,99.,99.,99.,99.,], # observed 3
[ 8.,99.,99.,99., 0., 3., 5., 5., 9., 9.,], # observed 4
[10.,99.,99.,99., 3., 0., 2., 4., 6., 8.,], # observed 5
[10.,99.,99.,99., 6., 1., 0., 3., 3., 4.,], # observed 6
[10.,99.,99.,99., 6., 4., 4., 0., 3., 4.,], # observed 7
[ 7.,99.,99.,99., 8., 5., 2., 2., 0., 2.,], # observed 8
[ 8.,99.,99.,99., 9., 7., 5., 6., 3., 0.,]] # observed 9
# - Start with -999 Points (should never stay negative - else we do
# have a but.
points = np.ndarray(len(data),dtype='float'); points[:] = -999
# - Compute points for all observations and always take
# the maximum of these.
for o in obs:
for i in range(len(data)):
# maxpoints - observed bet value
tmp = 10 - point_matrix[int(o/10)][int(data[i])/10]
# Minimum points: 0!
if tmp < 0:
points[i] = 0.
if tmp > points[i]:
points[i] = tmp
# - Show data (development stuff)
#for i in range(len(data)):
# omin = np.min( np.asarray(obs) )
# omax = np.max( np.asarray(obs) )
# print 'obs: %5d %5d bet: %5d %6.2f' % (omin, omax, data[i], points[i])
return points
# ----------------------------------------------------------------
# - Compute PPP (station pressure) points
# ----------------------------------------------------------------
def __points_PPP__(self,obs,data,special):
"""Rule function to compute points for reduced surface air pressure.
Args:
obs (): Observations.
data (): Forecasted values.
special (): Special observations for additional rules.
Returns:
Returns the corresponding points.
"""
if not self.quiet:
print ' - Called PPP point computation method'
data = np.asarray(data)
resid = self.__residuals__(obs,data)
# - Full points
points = np.ndarray(len(data),dtype='float'); points[:] = 10.
# - Deduction
# Minus 0.1 points for each unit difference (bets are stored in hPa*10)
points = points - resid*0.1
# - Show data (development stuff)
#for i in range(len(data)):
# print '%5d %5d %6.2f' % (data[i], resid[i], points[i])
return points
# ----------------------------------------------------------------
# - Compute RR (precipitation) points
# ----------------------------------------------------------------
def __points_RR__(self,obs,data,special):
"""Rule function to compute points for precipitation sum.
Args:
obs (): Observations.
data (): Forecasted values.
special (): Special observations for additional rules.
Returns:
Returns the corresponding points.
"""
# - Getting min and max from the obs
data = np.asarray(data)
obs = np.asarray(obs);
min = np.min(obs)
max = np.max(obs)
if not self.quiet:
print ' - Called RR point computation method'
data = np.asarray(data)
# - WARNING: compute residuals only to 0mm observation!
# The penalty for observed -3.0 will be added later on.
resid = self.__residuals__(np.maximum(obs,0),np.maximum(data,0))
# - Maximum number of points to reach
maxpoints = 10.
deduction = np.zeros(len(data), dtype='float'); deduction[:] = 0.
# - Precipitation scoring is quiet fancy. I am doing this
# with a vector defining the penalty for different
# amounts of OBSERVED prcipitation and its penalties.
# If observation is 0.0: start from zero element (giving the 1.0 points penalty)
# If observation is 0.1: start from first element (giving 0.1 points penalty)
# .. up to 5.0. Afterwards the user gets 0.05 points penalty for each difference.
full_penalty = np.zeros( (50), dtype='float' );
full_penalty[0] = 1.
full_penalty[1:] = 0.1
# -------------------------------------------------------------
# - For players ABOVE the maximum, compute points:
# -------------------------------------------------------------
# - Now take the penalty vector if max is in that range.
if max <= 0:
penalty = full_penalty
elif max < len(full_penalty):
penalty = full_penalty[max:]
else:
penalty = []
idx = np.where( data > max )[0]
# - For the first len(penalty) deviances
if len(penalty) > 0:
for i in idx:
#bugfix? needs 2 be tested!
#imax0 = np.minimum( resid[i], len(penalty) )
imax = int(np.minimum( resid[i], len(penalty) ) )
#print(imax0, imax)
deduction[i] = deduction[i] + np.sum(penalty[0:imax])
# - For these with more difference as len(penalty)-1
deduction[idx] = deduction[idx] + np.maximum(0,resid[idx]-len(penalty)) * 0.05
# - Only half points deduction for all forecasted values >= 0.1mm
# if and only if the forecast was bigger than the observed values.
idx = np.where( np.logical_and( deduction > 0., data > max, data > 0 ) )
deduction[idx] = deduction[idx] * 0.5
# - PROBLEM: if data == 0 and max > 0 the user gets
# 1.0 points deduction between 0.0 and 0.1 mm. BUT
# I devided the points by 2. This does not yield
# for the first point 1.0 between 0.0 and 0.1. Therefore
# we have to add 0.5 points (half of 1.0) again if
# - User forecast was == 0
# - Observed maximum was > 0 (bigger than forecast)
# - Deduction is not equal to 0.
if len(penalty) > 0:
if penalty[0] == 1.:
idx = np.where( np.logical_and( deduction > 0., data > max ) )
deduction[idx] = deduction[idx] + 0.5
# -------------------------------------------------------------
# - For players BELOW the minimum, compute points:
# -------------------------------------------------------------
# - Now take the penalty vector from user tip
# up to minimum observed value BUT tip was not -3.0mm
# same her with the int() bugfix...
idx = np.where( data < min )[0]
imax = np.minimum( min, len(full_penalty) )
for i in idx:
imin = np.maximum( data[i], 0 )
#possible 0.0 bugfix needs to be tested:
#if imin == imax == 0:
# slc = 0
#else:
# slc = range(imin+1,imax+1)
deduction[i] = deduction[i] + np.sum( full_penalty[imin:imax] )
if min > 50.:
tmp = self.__residuals__( min, np.maximum(50, data[idx]) )
deduction[idx] = deduction[idx] + tmp * 0.05
# - Special case: min(obs) was >= 0 but forecast was -3.0
# remove 3 more points.
if min >= 0:
idx = np.where( data < 0)
deduction[idx] = deduction[idx] + 3
# - Same for case: max(obs) was < 0 (-3.0) but forecast was >=0
if max < 0:
idx = np.where( data >= 0)
deduction[idx] = deduction[idx] + 3
# - Compute points and round to one tenth
####deduction = np.round( deduction, 1 )
points = maxpoints - deduction
# - Show data (development stuff)
if min >=0: print ' WET CONDITIONS'
if max < 0: print ' DRY CONDITIONS'
for i in range(len(data)):
print '%5d %5d | bet %5d | resid: %5d | %6.2f (ded: %6.2f)' % (min,max,data[i], resid[i], points[i], deduction[i])
return points