From 19efaf46726d6b7e2c74364a8f74b390609f3ee7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 02:02:08 +0000 Subject: [PATCH] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_calibration.py | 20 +- xhydro/modelling/calibration.py | 68 ++--- xhydro/modelling/obj_funcs.py | 448 ++++++++++++++++---------------- 3 files changed, 269 insertions(+), 267 deletions(-) diff --git a/tests/test_calibration.py b/tests/test_calibration.py index eacbbb46..f77596b8 100644 --- a/tests/test_calibration.py +++ b/tests/test_calibration.py @@ -21,9 +21,9 @@ def test_spotpy_calibration(): "drainage_area": np.array([10]), "model_name": "Dummy", } - + mask = np.array([0, 0, 0, 0, 1, 1]) - + best_parameters, best_simulation, best_objfun = perform_calibration( model_config, "mae", @@ -38,16 +38,16 @@ def test_spotpy_calibration(): assert len(best_parameters) == len(bounds_high) # Test that the objective function is calculated correctly - objfun = get_objective_function( - model_config["Qobs"], - best_simulation, - obj_func="mae", - mask=mask, - ) - + objfun = get_objective_function( + model_config["Qobs"], + best_simulation, + obj_func="mae", + mask=mask, + ) + assert objfun == best_objfun # Test dummy model response model_config["parameters"] = [5, 5, 5] Qsim = dummy_model(model_config) - assert Qsim[3] == 3500.00 \ No newline at end of file + assert Qsim[3] == 3500.00 diff --git a/xhydro/modelling/calibration.py b/xhydro/modelling/calibration.py index 5c6feb9e..50fd9a2a 100644 --- a/xhydro/modelling/calibration.py +++ b/xhydro/modelling/calibration.py @@ -65,26 +65,27 @@ from spotpy.parameter import Uniform from xhydro.modelling.hydrological_modelling import hydrological_model_selector -from xhydro.modelling.obj_funcs import (get_objective_function, - get_objfun_minimize_or_maximize, - get_optimizer_minimize_or_maximize, - ) +from xhydro.modelling.obj_funcs import ( + get_objective_function, + get_objfun_minimize_or_maximize, + get_optimizer_minimize_or_maximize, +) class spot_setup: """Create the spotpy calibration system that is used for hydrological model calibration.""" - def __init__(self, - model_config, - bounds_high, - bounds_low, - obj_func=None, - take_negative=False, - mask=None, - transform=None, - epsilon=None, - ): - + def __init__( + self, + model_config, + bounds_high, + bounds_low, + obj_func=None, + take_negative=False, + mask=None, + transform=None, + epsilon=None, + ): """Initialize the spot_setup object. The initialization of the spot_setup object includes a generic @@ -115,7 +116,7 @@ def __init__(self, - "rsr" : Ratio of RMSE to standard deviation. - "volume_error": Total volume error over the period. """ - + # Gather the model_config dictionary and obj_func string, and other # optional arguments. self.model_config = model_config @@ -176,16 +177,17 @@ def objectivefunction( - simulation, observation : vectors of streamflow used to compute the objective function """ - - obj_fun_val = get_objective_function(evaluation, - simulation, - obj_func=self.obj_func, - take_negative=self.take_negative, - mask=self.mask, - transform=self.transform, - epsilon=self.epsilon, - ) - + + obj_fun_val = get_objective_function( + evaluation, + simulation, + obj_func=self.obj_func, + take_negative=self.take_negative, + mask=self.mask, + transform=self.transform, + epsilon=self.epsilon, + ) + return obj_fun_val @@ -215,7 +217,7 @@ def perform_calibration( It will be up to the user to provide the data that the model requires. obj_func : str The objective function used for calibrating. Can be any one of these: - + - "abs_bias" : Absolute value of the "bias" metric - "abs_pbias": Absolute value of the "pbias" metric - "abs_volume_error" : Absolute value of the volume_error metric @@ -231,7 +233,7 @@ def perform_calibration( - "rmse" : Root Mean Square Error - "rrmse" : Relative Root Mean Square Error (RMSE-to-mean ratio) - "rsr" : Ratio of RMSE to standard deviation. - + bounds_high : np.array High bounds for the model parameters to be calibrated. Spotpy will sample parameter sets from within these bounds. The size must be equal to the number of parameters to calibrate. @@ -259,13 +261,13 @@ def perform_calibration( # for the objective function and the working direction of the algorithm. of_maximize = get_objfun_minimize_or_maximize(obj_func) algo_maximize = get_optimizer_minimize_or_maximize(algorithm) - + # They are not working in the same direction. Take the negative of the OF. if of_maximize != algo_maximize: take_negative = True else: take_negative = False - + # Set up the spotpy object to prepare the calibration spotpy_setup = spot_setup( model_config, @@ -277,7 +279,7 @@ def perform_calibration( transform=transform, epsilon=epsilon, ) - + # Select an optimization algorithm and parameterize it, then run the # optimization process. if algorithm == "DDS": @@ -311,7 +313,7 @@ def perform_calibration( # Reconvert objective function if required. if take_negative: bestobjf = bestobjf * -1 - + # Update the parameter set to put the best parameters in model_config... model_config.update({"parameters": best_parameters}) @@ -319,4 +321,4 @@ def perform_calibration( Qsim = hydrological_model_selector(model_config) # Return the best parameters, Qsim and best objective function value. - return best_parameters, Qsim, bestobjf \ No newline at end of file + return best_parameters, Qsim, bestobjf diff --git a/xhydro/modelling/obj_funcs.py b/xhydro/modelling/obj_funcs.py index 766028ee..6b469b20 100644 --- a/xhydro/modelling/obj_funcs.py +++ b/xhydro/modelling/obj_funcs.py @@ -6,59 +6,62 @@ hydrological modelling and hydrological model calibration. The main function 'get_objective_function' returns the value of the desired objective function while allowing users to customize many aspects: - + 1- Select the objective function to run; 2- Allow providing a mask to remove certain elements from the objective function calculation (e.g. for odd/even year calibration, or calibration on high or low flows only, or any custom setup). - 3- Apply a transformation on the flows to modify the behaviour of the + 3- Apply a transformation on the flows to modify the behaviour of the objective function calculation (e.g taking the log, inverse or square root transform of the flows before computing the objective function). - + This function also contains some tools and inputs reserved for the calibration toolbox, such as the ability to take the negative of the objective function to maximize instead of minimize a metric according to the needs of the optimizing algorithm. """ +import sys + # Import packages import numpy as np -import sys -def get_objective_function(Qobs, - Qsim, - obj_func='rmse', - take_negative=False, - mask=None, - transform=None, - epsilon=None, - ): + +def get_objective_function( + Qobs, + Qsim, + obj_func="rmse", + take_negative=False, + mask=None, + transform=None, + epsilon=None, +): """ This function is the entrypoint for objective function calculation. More - can be added by adding the function to this file and adding the option in - this function. - + can be added by adding the function to this file and adding the option in + this function. + NOTE: All data corresponding to NaN values in the observation set are removed from the calculation. If a mask is passed, it must be the - same size as the Qsim and Qobs vectors. If any NaNs are present in - the Qobs dataset, all corresponding data in the Qobs, Qsim and mask + same size as the Qsim and Qobs vectors. If any NaNs are present in + the Qobs dataset, all corresponding data in the Qobs, Qsim and mask will be removed prior to passing to the processing function. - + Parameters ---------- Qobs: numpy array of size n Vector containing the Observed streamflow to be used in the objective function calculation. It is the target to attain. - + Qsim: numpy array of size n Vector containing the Simulated streamflow as generated by the hydro- - logical model. It is modified by changing parameters and resumulating + logical model. It is modified by changing parameters and resumulating the hydrological model. - + obj_func : string, optional - String representing the objective function to use in the calibration. + String representing the objective function to use in the calibration. Options must be one of the accepted objective functions: - + - "abs_bias" : Absolute value of the "bias" metric - "abs_pbias": Absolute value of the "pbias" metric - "abs_volume_error" : Absolute value of the volume_error metric @@ -77,45 +80,45 @@ def get_objective_function(Qobs, - "rrmse" : Relative Root Mean Square Error (RMSE-to-mean ratio) - "rsr" : Ratio of RMSE to standard deviation. - "volume_error": Total volume error over the period. - + The default is 'rmse'. - + take_negative : Boolean used to force the objective function to be multiplied by minus one (-1) such that it is possible to maximize it - if the optimizer is a minimizer and vice-versa. Should always be set + if the optimizer is a minimizer and vice-versa. Should always be set to False unless required by an optimization setup, which is handled internally and transparently to the user. - + The default is False. - + mask : numpy array (vector) of size n, optional - array of 0 or 1 on which the objective function should be applied. + array of 0 or 1 on which the objective function should be applied. Values of 1 indicate that the value is included in the calculation, and values of 0 indicate that the value is excluded and will have no impact on the objective function calculation. This can be useful for specific - optimization strategies such as odd/even year calibration, seasonal - calibration or calibration based on high/low flows. - + optimization strategies such as odd/even year calibration, seasonal + calibration or calibration based on high/low flows. + The default is None, and all data are preserved. - - transform : string indicating the type of transformation required. Can be + + transform : string indicating the type of transformation required. Can be one of the following values: - + - "sqrt" : Square root transformation of the flows [sqrt(Q)] - "log" : Logarithmic transformation of the flows [log(Q)] - "inv" : Inverse transformation of the flows [1/Q] - + The default value is "None", by which no transformation is performed. - - epsilon: scalar float indicating the perturbation to add to the flow - time series during a transformation to avoid division by zero and + + epsilon: scalar float indicating the perturbation to add to the flow + time series during a transformation to avoid division by zero and logarithmic transformation. The perturbation is equal to: - + perturbation = epsilon * mean(Qobs) - + The default value is 0.01. - - + + NOTE: All data corresponding to NaN values in the observation set are removed from the calculation @@ -124,65 +127,67 @@ def get_objective_function(Qobs, obj_fun_val: scalar float representing the value of the selected objective function (obj_fun). """ - + # List of available objective functions - obj_func_list = ["abs_bias", - "abs_pbias", - "abs_volume_error", - "agreement_index", - "bias", - "correlation_coeff", - "kge", - "kge_mod", - "mae", - "mare", - "mse", - "nse", - "pbias", - "r2", - "rmse", - "rrmse", - "rsr", - "volume_error", - ] - + obj_func_list = [ + "abs_bias", + "abs_pbias", + "abs_volume_error", + "agreement_index", + "bias", + "correlation_coeff", + "kge", + "kge_mod", + "mae", + "mare", + "mse", + "nse", + "pbias", + "r2", + "rmse", + "rrmse", + "rsr", + "volume_error", + ] + # Basic error checking if Qobs.shape[0] != Qsim.shape[0]: sys.exit("Observed and Simulated flows are not of the same size.") - + # Check mask size and contents if mask is not None: - # Size - if Qobs.shape[0]!= mask.shape[0]: + if Qobs.shape[0] != mask.shape[0]: sys.exit("Mask is not of the same size as the streamflow data.") - + # All zero or one? - if not np.setdiff1d(np.unique(mask),np.array([0, 1])).size == 0: + if not np.setdiff1d(np.unique(mask), np.array([0, 1])).size == 0: sys.exit("Mask contains values other 0 or 1. Please modify.") # Check that the objective function is in the list of available methods if obj_func not in obj_func_list: - sys.exit("Selected objective function is currently unavailable. " + - "Consider contributing to our project at: " + - "github.com/hydrologie/xhydro") - + sys.exit( + "Selected objective function is currently unavailable. " + + "Consider contributing to our project at: " + + "github.com/hydrologie/xhydro" + ) + # Ensure there are no NaNs in the dataset if mask is not None: mask = mask[~np.isnan(Qobs)] Qsim = Qsim[~np.isnan(Qobs)] Qobs = Qobs[~np.isnan(Qobs)] - + # Apply mask before trasform if mask is not None: - Qsim=Qsim[mask==1] - Qobs=Qobs[mask==1] - mask=mask[mask==1] - + Qsim = Qsim[mask == 1] + Qobs = Qobs[mask == 1] + mask = mask[mask == 1] + # Transform data if needed if transform is not None: Qsim, Qobs = transform_flows(Qsim, Qobs, transform, epsilon) - + # Compute objective function by switching to the correct algorithm match obj_func: case "abs_bias": @@ -221,90 +226,94 @@ def get_objective_function(Qobs, obj_fun_val = rsr(Qsim, Qobs) case "volume_error": obj_fun_val = volume_error(Qsim, Qobs) - - # Take the negative value of the Objective Function to return to the + + # Take the negative value of the Objective Function to return to the # optimizer. if take_negative: obj_fun_val = obj_fun_val * -1 - + print(obj_fun_val) - + return obj_fun_val def get_objfun_minimize_or_maximize(obj_func): """ This function checks the name of the objective function and returns whether - it should be maximized or minimized. Returns a boolean value, where True + it should be maximized or minimized. Returns a boolean value, where True means it should be maximized, and Flase means that it should be minimized. Objective functions other than those programmed here will raise an error. - + Inputs: - obj_func: string containing the label for the desired objective + obj_func: string containing the label for the desired objective function. """ - + # Define metrics that need to be maximized: - if obj_func in ["agreement_index", - "correlation_coeff", - "kge", - "kge_mod", - "nse", - "r2" - ]: + if obj_func in [ + "agreement_index", + "correlation_coeff", + "kge", + "kge_mod", + "nse", + "r2", + ]: maximize = True - + # Define the metrics that need to be minimized: - elif obj_func in ["abs_bias", - "abs_pbias", - "abs_volume_error", - "mae", - "mare", - "mse", - "rmse", - "rrmse", - "rsr", - ]: + elif obj_func in [ + "abs_bias", + "abs_pbias", + "abs_volume_error", + "mae", + "mare", + "mse", + "rmse", + "rrmse", + "rsr", + ]: maximize = False - + # Check for the metrics that exist but cannot be used for optimization - elif obj_func in ["bias","pbias","volume_error"]: - sys.exit("The bias, pbias and volume_error metrics cannot be minimized or maximized. \ - Please use the abs_bias, abs_pbias and abs_volume_error instead.") + elif obj_func in ["bias", "pbias", "volume_error"]: + sys.exit( + "The bias, pbias and volume_error metrics cannot be minimized or maximized. \ + Please use the abs_bias, abs_pbias and abs_volume_error instead." + ) else: sys.exit("The objective function is unknown.") - + return maximize - - + + def get_optimizer_minimize_or_maximize(algorithm): """ This function finds the direction in which the optimizer searches. Some optimizers try to maximize the objective function value, and others try to minimize it. Since our objective functions include some that need to be - maximized and others minimized, it is imperative to ensure that the + maximized and others minimized, it is imperative to ensure that the optimizer/objective-function pair work in tandem. - + Inputs: algorithm: string containing the direction of the optimizer search """ - + # Define metrics that need to be maximized: - if algorithm in ["DDS", - ]: - + if algorithm in [ + "DDS", + ]: maximize = True - + # Define the metrics that need to be minimized: - elif algorithm in ["SCEUA", - ]: - + elif algorithm in [ + "SCEUA", + ]: maximize = False - + # Any other optimizer at this date else: sys.exit("The optimization algorithm is unknown.") - + return maximize @@ -323,35 +332,35 @@ def transform_flows(Qsim, Qobs, transform=None, epsilon=0.01): Qobs : Observed streamflow vector (numpy array) - transform : string indicating the type of transformation required. Can be + transform : string indicating the type of transformation required. Can be one of the following values: - + - "sqrt" : Square root transformation of the flows [sqrt(Q)] - "log" : Logarithmic transformation of the flows [log(Q)] - "inv" : Inverse transformation of the flows [1/Q] - + The default value is "None", by which no transformation is performed. - - epsilon: scalar float indicating the perturbation to add to the flow - time series during a transformation to avoid division by zero and + + epsilon: scalar float indicating the perturbation to add to the flow + time series during a transformation to avoid division by zero and logarithmic transformation. The perturbation is equal to: - + perturbation = epsilon * mean(Qobs) - + The default value is 0.01. - + Returns ------- Qsim, Qobs transformed according to the transformation function requested by the user in "transform". Qsim and Qobs are numpy arrays. - + """ # Quick check if transform is not None: - if transform not in ['log','inv','sqrt']: + if transform not in ["log", "inv", "sqrt"]: sys.exit("Flow transformation method not recognized.") - + # Transform the flow series if required if transform == "log": # log transformation epsilon = epsilon * np.nanmean(Qobs) @@ -372,6 +381,7 @@ def transform_flows(Qsim, Qobs, transform=None, epsilon=0.01): BEGIN OBJECTIVE FUNCTIONS DEFINITIONS """ + def abs_bias(Qsim, Qobs): """ Parameters @@ -381,12 +391,12 @@ def abs_bias(Qsim, Qobs): Returns ------- - abs_bias: positive scalar float representing the absolute value of the - "bias" metric. This metric is useful when calibrating on the bias, because - bias should aim to be 0 but can take large positive or negative values. - Taking the absolute value of the bias will let the optimizer minimize + abs_bias: positive scalar float representing the absolute value of the + "bias" metric. This metric is useful when calibrating on the bias, because + bias should aim to be 0 but can take large positive or negative values. + Taking the absolute value of the bias will let the optimizer minimize the value to zero. - + The abs_bias should be MINIMIZED. """ return np.abs(bias(Qsim, Qobs)) @@ -401,12 +411,12 @@ def abs_pbias(Qsim, Qobs): Returns ------- - abs_pbias: positive scalar float representing the absolute value of the - "pbias" metric. This metric is useful when calibrating on the pbias, - because pbias should aim to be 0 but can take large positive or negative - values. Taking the absolute value of the pbias will let the optimizer + abs_pbias: positive scalar float representing the absolute value of the + "pbias" metric. This metric is useful when calibrating on the pbias, + because pbias should aim to be 0 but can take large positive or negative + values. Taking the absolute value of the pbias will let the optimizer minimize the value to zero. - + The abs_pbias should be MINIMIZED. """ return np.abs(bias(Qsim, Qobs)) @@ -421,12 +431,12 @@ def abs_volume_error(Qsim, Qobs): Returns ------- - abs_volume_error: positive scalar float representing the absolute value of - the "volume_error" metric. This metric is useful when calibrating on the - volume_error, because volume_error should aim to be 0 but can take large + abs_volume_error: positive scalar float representing the absolute value of + the "volume_error" metric. This metric is useful when calibrating on the + volume_error, because volume_error should aim to be 0 but can take large positive or negative values. Taking the absolute value of the volume_error will let the optimizer minimize the value to zero. - + The abs_volume_error should be MINIMIZED. """ return np.abs(volume_error(Qsim, Qobs)) @@ -441,18 +451,18 @@ def agreement_index(Qsim, Qobs): Returns ------- - agreement_index: scalar float representing the agreement index of Willmott + agreement_index: scalar float representing the agreement index of Willmott (1981). Varies between 0 and 1. - + The Agreement index should be MAXIMIZED. """ - - #Decompose into clearer chunks - a = np.sum((Qobs-Qsim)**2) - b = np.abs(Qsim - np.mean(Qobs)) + np.abs(Qobs-np.mean(Qobs)) + + # Decompose into clearer chunks + a = np.sum((Qobs - Qsim) ** 2) + b = np.abs(Qsim - np.mean(Qobs)) + np.abs(Qobs - np.mean(Qobs)) c = np.sum(b**2) - - return (1 - (a/c)) + + return 1 - (a / c) def bias(Qsim, Qobs): @@ -469,12 +479,12 @@ def bias(Qsim, Qobs): flows. This interpretation uses the definition that a positive bias value means that the simulation overestimates the true value (as opposed to many other sources on bias calculations that use the contrary interpretation). - + BIAS SHOULD AIM TO BE ZERO AND SHOULD NOT BE USED FOR CALIBRATION. FOR CALIBRATION, USE "abs_bias" TO TAKE THE ABSOLUTE VALUE. """ return np.mean(Qsim - Qobs) - + def correlation_coeff(Qsim, Qobs): """ @@ -485,12 +495,12 @@ def correlation_coeff(Qsim, Qobs): Returns ------- - correlation_coeff: scalar float representing the correlation coefficient. - + correlation_coeff: scalar float representing the correlation coefficient. + The correlation_coeff should be MAXIMIZED. """ - return np.corrcoef(Qobs, Qsim)[0,1] - + return np.corrcoef(Qobs, Qsim)[0, 1] + def kge(Qsim, Qobs): """ @@ -501,9 +511,9 @@ def kge(Qsim, Qobs): Returns ------- - kge: scalar float representing the Kling-Gupta Efficiency (KGE) metric of + kge: scalar float representing the Kling-Gupta Efficiency (KGE) metric of 2009. It can take values from -inf to 1 (best case). - + The KGE should be MAXIMIZED. """ # This pops up a lot, precalculate. @@ -512,13 +522,11 @@ def kge(Qsim, Qobs): # Calculate the components of KGE r_num = np.sum((Qsim - Qsim_mean) * (Qobs - Qobs_mean)) - r_den = np.sqrt( - np.sum((Qsim - Qsim_mean) ** 2) * np.sum((Qobs - Qobs_mean) ** 2) - ) + r_den = np.sqrt(np.sum((Qsim - Qsim_mean) ** 2) * np.sum((Qobs - Qobs_mean) ** 2)) r = r_num / r_den a = np.std(Qsim) / np.std(Qobs) b = np.sum(Qsim) / np.sum(Qobs) - + # Calculate the KGE kge = 1 - np.sqrt((r - 1) ** 2 + (a - 1) ** 2 + (b - 1) ** 2) @@ -534,25 +542,23 @@ def kge_mod(Qsim, Qobs): Returns ------- - kge_mod: scalar float representing the modified Kling-Gupta Efficiency + kge_mod: scalar float representing the modified Kling-Gupta Efficiency (KGE) metric of 2012. It can take values from -inf to 1 (best case). - + The kge_mod should be MAXIMIZED. """ - + # These pop up a lot, precalculate Qsim_mean = np.mean(Qsim) Qobs_mean = np.mean(Qobs) - + # Calc KGE components r_num = np.sum((Qsim - Qsim_mean) * (Qobs - Qobs_mean)) - r_den = np.sqrt( - np.sum((Qsim - Qsim_mean) ** 2) * np.sum((Qobs - Qobs_mean) ** 2) - ) + r_den = np.sqrt(np.sum((Qsim - Qsim_mean) ** 2) * np.sum((Qobs - Qobs_mean) ** 2)) r = r_num / r_den - g = ((np.std(Qsim) / Qsim_mean) / (np.std(Qobs) / Qobs_mean)) - b = (np.mean(Qsim) / np.mean(Qobs)) - + g = (np.std(Qsim) / Qsim_mean) / (np.std(Qobs) / Qobs_mean) + b = np.mean(Qsim) / np.mean(Qobs) + # Calc the modified KGE metric kge_mod = 1 - np.sqrt((r - 1) ** 2 + (g - 1) ** 2 + (b - 1) ** 2) @@ -569,14 +575,14 @@ def mae(Qsim, Qobs): Returns ------- mae: scalar float representing the Mean Absolute Error. It can be - interpreted as the average error (absolute) between observations and + interpreted as the average error (absolute) between observations and simulations for any time step. - + The mae should be MINIMIZED. """ - return np.mean(np.abs(Qsim-Qobs)) + return np.mean(np.abs(Qsim - Qobs)) + - def mare(Qsim, Qobs): """ Parameters @@ -586,14 +592,14 @@ def mare(Qsim, Qobs): Returns ------- - mare: scalar float representing the Mean Absolute Relative Error. For + mare: scalar float representing the Mean Absolute Relative Error. For streamflow, where Qobs is always zero or positive, the MARE is always positive. - + The mare should be MINIMIZED. """ - - return (np.sum(np.abs(Qobs - Qsim)) / np.sum(Qobs)) + + return np.sum(np.abs(Qobs - Qsim)) / np.sum(Qobs) def mse(Qsim, Qobs): @@ -608,12 +614,12 @@ def mse(Qsim, Qobs): mse: scalar float representing the Mean Square Error. It is the sum of squared errors for each day divided by the total number of days. Units are thus squared units, and the best possible value is 0. - + The mse should be MINIMIZED. """ - return np.mean((Qobs-Qsim)**2) - - + return np.mean((Qobs - Qsim) ** 2) + + def nse(Qsim, Qobs): """ Parameters @@ -625,15 +631,15 @@ def nse(Qsim, Qobs): ------- nse: scalar float representing the Nash-Sutcliffe Efficiency (NSE) metric. It can take values from -inf to 1, with 0 being as good as using the mean - observed flow as the estimator. - + observed flow as the estimator. + The nse should be MAXIMIZED. """ - + num = np.sum((Qobs - Qsim) ** 2) den = np.sum((Qobs - np.mean(Qobs)) ** 2) - - return (1 - (num / den)) + + return 1 - (num / den) def pbias(Qsim, Qobs): @@ -645,18 +651,18 @@ def pbias(Qsim, Qobs): Returns ------- - pbias: scalar float representing the Percent bias. Can be negative or - positive and gives the average relative error between the observed and - simulated flows. This interpretation uses the definition that a positive - bias value means that the simulation overestimates the true value (as - opposed to many other sources on bias calculations that use the contrary + pbias: scalar float representing the Percent bias. Can be negative or + positive and gives the average relative error between the observed and + simulated flows. This interpretation uses the definition that a positive + bias value means that the simulation overestimates the true value (as + opposed to many other sources on bias calculations that use the contrary interpretation). - + PBIAS SHOULD AIM TO BE ZERO AND SHOULD NOT BE USED FOR CALIBRATION. FOR CALIBRATION, USE "abs_pbias" TO TAKE THE ABSOLUTE VALUE. """ - - return (np.sum(Qsim - Qobs)/ np.sum(Qobs)) * 100 + + return (np.sum(Qsim - Qobs) / np.sum(Qobs)) * 100 def r2(Qsim, Qobs): @@ -669,12 +675,12 @@ def r2(Qsim, Qobs): Returns ------- r2: scalar float representing the r-squared (R2) metric equal to the square - of the correlation coefficient. - + of the correlation coefficient. + The r2 should be MAXIMIZED. """ return correlation_coeff(Qsim, Qobs) ** 2 - + def rmse(Qsim, Qobs): """ @@ -685,10 +691,10 @@ def rmse(Qsim, Qobs): Returns ------- - rmse: scalar float representing the Root Mean Square Error. Units are the - same as the timeseries data (ex. m3/s). It can take zero or positive + rmse: scalar float representing the Root Mean Square Error. Units are the + same as the timeseries data (ex. m3/s). It can take zero or positive values. - + The rmse should be MINIMIZED. """ @@ -704,11 +710,11 @@ def rrmse(Qsim, Qobs): Returns ------- - rrmse: scalar float representing the ratio of the RMSE to the mean of the - observations. It allows scaling RMSE values to compare results between + rrmse: scalar float representing the ratio of the RMSE to the mean of the + observations. It allows scaling RMSE values to compare results between time series of different magnitudes (ex. flows from small and large watersheds). Also known as the CVRMSE. - + The rrmse should be MINIMIZED. """ return rmse(Qsim, Qobs) / np.mean(Qobs) @@ -724,9 +730,9 @@ def rsr(Qsim, Qobs): Returns ------- rsr: scalar float representing the Root Mean Square Error (RMSE) divided by - the standard deviation of the observations. Also known as the "Ratio of the + the standard deviation of the observations. Also known as the "Ratio of the Root Mean Square Error to the Standard Deviation of Observations". - + The rsr should be MINIMIZED. """ return rmse(Qobs, Qsim) / np.std(Qobs) @@ -745,18 +751,12 @@ def volume_error(Qsim, Qobs): over the entire period. Expressed in terms of the same units as input data, so for flow rates it is important to multiply by the duration of the time- step to obtain actual volumes. - + The volume_error should be MINIMIZED. """ - return np.sum(Qsim-Qobs) / np.sum(Qobs) - + return np.sum(Qsim - Qobs) / np.sum(Qobs) """ ADD OBJECTIVE FUNCTIONS HERE """ - - - - - \ No newline at end of file