def test_all_parameters_fixed_predict_no_new_data(model_name, fitted_model): """Do not predict, sans new data, when all passed parameters were fixed. This only works when the model object had fit run, and predictions can be from the fitted data. """ all_parameters = fitted_model.get_params() new_model = utils.load_model(model_name)(parameters=all_parameters) with pytest.raises(TypeError): new_model.predict()
def test_no_nan_in_observations_predict(): """the observation data.frame, either in fitting or prediction, should not have any NA """ single_model = utils.load_model('ThermalTime')() obs_with_nan = obs.copy(deep=True) obs_with_nan['doy'][1] = np.nan with pytest.raises(ValueError): single_model.fit(obs_with_nan, predictors, optimizer_params='testing')
def test_predict_with_nan(): """model.predict() method should work with nan in obs.doy column. see https://github.com/sdtaylor/pyPhenology/issues/105 """ single_model = utils.load_model('ThermalTime')() single_model.fit(obs, predictors, optimizer_params='testing') obs_with_nan = obs.copy(deep=True) obs_with_nan['doy'] = np.nan p_with_nan = single_model.predict(obs_with_nan, predictors) p = single_model.predict(obs, predictors) assert np.all(p == p_with_nan)
def test_estimate_all_but_one_parameter(model_name, fitted_model): """Estimate only a single parameter. The end result should be the same as what as passed """ all_parameters = fitted_model.get_params() fixed_param, fixed_value = all_parameters.popitem() new_model = utils.load_model(model_name)(parameters={ fixed_param: fixed_value }) new_model.fit(obs, predictors, optimizer_params='testing', debug=True) new_value = new_model.get_params()[fixed_param] assert new_value == fixed_value
def test_brute_force(model_name, fitted_model): """Test brute force optimization""" new_model = utils.load_model(model_name)() new_model.fit(obs, predictors, method='BF', optimizer_params='testing') assert len(new_model.predict()) == len(obs)
def test_do_not_predict_wihout_fit(model_name, fitted_model): """Do not predict on unfitted model""" model = utils.load_model(model_name)() with pytest.raises(RuntimeError): model.predict(obs, predictors)
def test_error_on_bad_parameter_names(model_name, fitted_model): """Expect error when unknown parameter name is used""" with pytest.raises(RuntimeError): utils.load_model(model_name)(parameters={'not_a_parameter': 0})
'initial_params': { 'num_bootstraps': 10, 'core_model': models.ThermalTime } }) # The rest can all be tested the same way model_names = [ 'Uniforc', 'Unichill', 'ThermalTime', 'Alternating', 'MSB', 'Linear', 'Sequential' ] for name in model_names: model_test_cases.append({ 'model_name': name, 'model_func': utils.load_model(name), 'fit_params': { 'optimizer_params': 'testing', 'debug': True }, 'initial_params': {} }) divider = '#' * 90 print(divider) print('Model test cases') print(divider) for test_case in model_test_cases: model_name = test_case['model_name']
def test_all_parameters_fixed_fit(model_name, fitted_model): """Do not attempt to fit a model when all passed parameters are fixed""" all_parameters = fitted_model.get_params() new_model = utils.load_model(model_name)(parameters=all_parameters) with pytest.raises(RuntimeError): new_model.fit(obs, predictors, optimizer_params='testing', debug=True)
from pyPhenology import utils import pytest import numpy as np obs, predictors = utils.load_test_data() core_model_names = [ 'Uniforc', 'Unichill', 'ThermalTime', 'Alternating', 'MSB', 'Linear', 'Sequential', 'M1', 'Naive', 'FallCooling' ] # Setup a list of (model_name, fitted_model object) fitted_models = [ utils.load_model(model_name)() for model_name in core_model_names ] [ model.fit(obs, predictors, optimizer_params='testing') for model in fitted_models ] model_test_cases = list(zip(core_model_names, fitted_models)) ######################################################### @pytest.mark.parametrize('model_name, fitted_model', model_test_cases) def test_predict_output_length(model_name, fitted_model): """Predict output length should equal input length""" assert len(fitted_model.predict()) == len(obs)
def test_save_and_load_model(model_name, fitted_model): """Load a saved model by passing file as parameters arg""" fitted_model.save_params('model_params.json', overwrite=True) loaded_model = utils.load_model(model_name)(parameters='model_params.json') assert fitted_model.get_params() == loaded_model.get_params()
from pyPhenology import utils observations, temp = utils.load_test_data(name='vaccinium') import pandas as pd import time models_to_use = ['ThermalTime', 'Alternating', 'Uniforc', 'MSB', 'Unichill'] methods_to_use = ['DE', 'BF'] sensible_defaults = ['testing', 'practical', 'intensive'] timings = [] for m in models_to_use: for optimizer_method in methods_to_use: for s_default in sensible_defaults: model = utils.load_model(m)() start_time = time.time() model.fit(observations, temp, method=optimizer_method, optimizer_params=s_default) fitting_time = round(time.time() - start_time, 2) num_parameters = len(model.get_params()) timings.append({ 'model': m, 'num_parameters': num_parameters, 'method': optimizer_method, 'sensible_default': s_default, 'time': fitting_time
observations_test = observations[0:10] observations_train = observations[10:] # AIC based off mean sum of squares def aic(obs, pred, n_param): return len(obs) * np.log(np.mean((obs - pred)**2)) + 2 * (n_param + 1) best_aic = np.inf best_base_model = None best_base_model_name = None for model_name in models_to_test: Model = utils.load_model(model_name) model = Model() model.fit(observations_train, predictors, optimizer_params='practical') model_aic = aic(obs=observations_test.doy.values, pred=model.predict(observations_test, predictors), n_param=len(model.get_params())) if model_aic < best_aic: best_model = model best_model_name = model_name best_aic = model_aic print('model {m} got an aic of {a}'.format(m=model_name, a=model_aic)) print('Best model: {m}'.format(m=best_model_name))
from pyPhenology import utils, models import pytest import numpy as np obs, predictors = utils.load_test_data() Model = utils.load_model('ThermalTime') model = Model() ##################################################### # Tests for other things besides the core model fitting/prediction def test_basinhopping_method(): model.fit(obs, predictors, method='BH', optimizer_params='testing') assert len(model.predict()) == len(obs) def test_bruteforce_method(): model.fit(obs, predictors, method='BF', optimizer_params='testing') assert len(model.predict()) == len(obs) def test_daylength_util(): """daylength equation from julian day & latitude. Numbers confirmed via http://www.solartopo.com/daylength.htm """ d = models.utils.transforms.daylength(np.array([30, 90, 180]), np.array([20, 30, 40])) np assert np.all(np.round(d, 1) == np.array([11.1, 12.3, 14.8]))
def test_invalid_model_name(): with pytest.raises(TypeError): utils.load_model(123)
def test_all_parameters_fixed_predict_new_data(model_name, fitted_model): """Predict , with new data, when all passed parameters were fixed""" all_parameters = fitted_model.get_params() new_model = utils.load_model(model_name)(parameters=all_parameters) assert len(new_model.predict(obs, predictors)) == len(obs)
def test_unknown_model_name(): with pytest.raises(ValueError): utils.load_model('asdf')
def run(): divider='#'*90 config = tools.load_config() today = datetime.datetime.today().date() land_mask = xr.open_dataset(config['mask_file']) print(divider) print('Applying phenocam phenology models - ' + str(today)) doy_0 = np.datetime64('2018-01-01') current_climate_forecast_files = glob.glob(config['current_forecast_folder']+'*.nc') print(str(len(current_climate_forecast_files)) + ' current climate forecast files: \n' + str(current_climate_forecast_files)) print(divider) # Load the climate forecasts current_climate_forecasts = [xr.open_dataset(f) for f in current_climate_forecast_files] for i, forecast_info in enumerate(phenocam_models): model_nickname = forecast_info['nickname'] model_parameters = forecast_info['parameters'] base_model_name = forecast_info['base_model'] Model = utils.load_model(base_model_name) model = Model(parameters=model_parameters) print('attempting phenocam model ' + model_nickname) #TODO: use tools.phenology_tools stuff here ensemble = [] for climate in current_climate_forecasts: doy_series = pd.TimedeltaIndex(climate.time.values - doy_0, freq='D').days.values ensemble.append(model.predict(to_predict = climate.tmean.values, doy_series=doy_series)) ensemble = np.array(ensemble).astype(np.float) # apply nan to non predictions ensemble[ensemble==999]=np.nan prediction = np.mean(ensemble, axis=0) prediction_sd = np.std(ensemble, axis=0) # extend the axis by 1 to match the xarray creation prediction = np.expand_dims(prediction, axis=0) prediction_sd = np.expand_dims(prediction_sd, axis=0) forecast = xr.Dataset(data_vars = {'doy_prediction':(('model', 'lat','lon'), prediction), 'doy_sd':(('model', 'lat','lon'), prediction_sd)}, coords = {'model':[model_nickname], 'lat':land_mask.lat, 'lon':land_mask.lon}) forecast = forecast.chunk({'lat':50,'lon':50}) if i==0: phenocam_forecasts = forecast else: phenocam_forecasts = xr.merge([phenocam_forecasts,forecast]) print(divider) print('phenocam phenology forecast final processing') current_season = tools.current_growing_season(config) provenance_note = \ """Forecasts for plant phenology of select species flowering and/or leaf out times for the {s} season. Made on {t} from NOAA CFSv2 forecasts downscaled using PRISM climate data. Phenocam models built by Eli Melaas. """.format(s=current_season, t=today) phenocam_forecasts.attrs['note']=provenance_note phenocam_forecasts.attrs['issue_date']=str(today) phenocam_forecasts.attrs['crs']='+init=epsg:4269' forecast_filename = config['phenology_forecast_folder']+'phenocam_phenology_forecast_'+str(today)+'.nc' phenocam_forecasts = phenocam_forecasts.chunk({'lat':50,'lon':50}) phenocam_forecasts.to_netcdf(forecast_filename, encoding={'doy_prediction':{'zlib':True, 'complevel':4, 'dtype':'int32', 'scale_factor':0.001, '_FillValue': -9999}, 'doy_sd':{'zlib':True, 'complevel':4, 'dtype':'int32', 'scale_factor':0.001, '_FillValue': -9999}}) # Return filename of final forecast file for use by primary script return forecast_filename