def test_optimization_model(self): num_cells = 20 model_type = pt_gs_k.PTGSKModel opt_model_type = pt_gs_k.PTGSKOptModel model = self.build_model(model_type, pt_gs_k.PTGSKParameter, num_cells) cal = api.Calendar() t0 = cal.time(2015, 1, 1, 0, 0, 0) dt = api.deltahours(1) n = 240 time_axis = api.TimeAxisFixedDeltaT(t0, dt, n) model_interpolation_parameter = api.InterpolationParameter() model.initialize_cell_environment( time_axis ) # just show how we can split the run_interpolation into two calls(second one optional) model.interpolate( model_interpolation_parameter, self.create_dummy_region_environment( time_axis, model.get_cells()[int(num_cells / 2)].geo.mid_point())) s0 = pt_gs_k.PTGSKStateVector() for i in range(num_cells): si = pt_gs_k.PTGSKState() si.kirchner.q = 40.0 s0.append(si) model.set_snow_sca_swe_collection(-1, True) model.set_states( s0 ) # at this point the intial state of model is established as well model.run_cells() cids = api.IntVector.from_numpy( [1]) # optional, we can add selective catchment_ids here sum_discharge = model.statistics.discharge(cids) sum_discharge_value = model.statistics.discharge_value( cids, 0) # at the first timestep self.assertGreaterEqual(sum_discharge_value, 130.0) # verify we can construct an optimizer opt_model = model.create_opt_model_clone() opt_model.set_snow_sca_swe_collection( -1, True) # ensure to fill in swe/sca opt_model.run_cells() opt_sum_discharge = opt_model.statistics.discharge(cids) opt_swe = opt_model.statistics.snow_swe(cids) # how to get out swe/sca opt_swe_v = opt_model.statistics.snow_swe_value(cids, 0) opt_sca = opt_model.statistics.snow_sca(cids) for i in range(len(opt_model.time_axis)): opt_sca_v = opt_model.statistics.snow_sca_value(cids, i) self.assertTrue(0.0 <= opt_sca_v <= 1.0) self.assertIsNotNone(opt_sum_discharge) self.assertIsNotNone(opt_swe) self.assertIsNotNone(opt_sca) optimizer = opt_model_type.optimizer_t( opt_model ) # notice that a model type know it's optimizer type, e.g. PTGSKOptimizer self.assertIsNotNone(optimizer) # # create target specification # opt_model.revert_to_initial_state( ) # set_states(s0) # remember to set the s0 again, so we have the same initial condition for our game tsa = api.TsTransform().to_average(t0, dt, n, sum_discharge) t_spec_1 = api.TargetSpecificationPts(tsa, cids, 1.0, api.KLING_GUPTA, 1.0, 0.0, 0.0, api.DISCHARGE, 'test_uid') target_spec = api.TargetSpecificationVector() target_spec.append(t_spec_1) upper_bound = model_type.parameter_t(model.get_region_parameter( )) # the model_type know it's parameter_t lower_bound = model_type.parameter_t(model.get_region_parameter()) upper_bound.kirchner.c1 = -1.9 lower_bound.kirchner.c1 = -3.0 upper_bound.kirchner.c2 = 0.99 lower_bound.kirchner.c2 = 0.80 optimizer.set_target_specification(target_spec, lower_bound, upper_bound) # Not needed, it will automatically get one. # optimizer.establish_initial_state_from_model() # s0_0 = optimizer.get_initial_state(0) # optimizer.set_verbose_level(1000) p0 = model_type.parameter_t(model.get_region_parameter()) orig_c1 = p0.kirchner.c1 orig_c2 = p0.kirchner.c2 # model.get_cells()[0].env_ts.precipitation.set(0, 5.1) # model.get_cells()[0].env_ts.precipitation.set(1, 4.9) goal_f0 = optimizer.calculate_goal_function(p0) p0.kirchner.c1 = -2.4 p0.kirchner.c2 = 0.91 opt_param = optimizer.optimize(p0, 1500, 0.1, 1e-5) goal_fx = optimizer.calculate_goal_function(opt_param) p0.kirchner.c1 = -2.4 p0.kirchner.c2 = 0.91 # goal_fx1 = optimizer.calculate_goal_function(p0) self.assertLessEqual(goal_fx, 10.0) self.assertAlmostEqual(orig_c1, opt_param.kirchner.c1, 4) self.assertAlmostEqual(orig_c2, opt_param.kirchner.c2, 4) # verify the interface to the new optimize_global function global_opt_param = optimizer.optimize_global(p0, max_n_evaluations=1500, max_seconds=3.0, solver_eps=1e-5) self.assertIsNotNone( global_opt_param ) # just to ensure signature and results are covered
def test_model_initialize_and_run(self): num_cells = 20 model_type = pt_gs_k.PTGSKModel model = self.build_model(model_type, pt_gs_k.PTGSKParameter, num_cells, num_catchments=1) self.assertEqual(model.size(), num_cells) self.verify_state_handler(model) # demo of feature for threads self.assertGreaterEqual(model.ncore, 1) # defaults to hardware concurrency model.ncore = 4 # set it to 4, and self.assertEqual(model.ncore, 4) # verify it works # now modify snow_cv forest_factor to 0.1 region_parameter = model.get_region_parameter() region_parameter.gs.snow_cv_forest_factor = 0.1 region_parameter.gs.snow_cv_altitude_factor = 0.0001 self.assertEqual(region_parameter.gs.snow_cv_forest_factor, 0.1) self.assertEqual(region_parameter.gs.snow_cv_altitude_factor, 0.0001) self.assertAlmostEqual(region_parameter.gs.effective_snow_cv(1.0, 0.0), region_parameter.gs.snow_cv + 0.1) self.assertAlmostEqual( region_parameter.gs.effective_snow_cv(1.0, 1000.0), region_parameter.gs.snow_cv + 0.1 + 0.1) cal = api.Calendar() time_axis = api.TimeAxisFixedDeltaT(cal.time(2015, 1, 1, 0, 0, 0), api.deltahours(1), 240) model_interpolation_parameter = api.InterpolationParameter() # degC/m, so -0.5 degC/100m model_interpolation_parameter.temperature_idw.default_temp_gradient = -0.005 # if possible use closest neighbor points and solve gradient using equation,(otherwise default min/max height) model_interpolation_parameter.temperature_idw.gradient_by_equation = True # Max number of temperature sources used for one interpolation model_interpolation_parameter.temperature_idw.max_members = 6 # 20 km is max distance model_interpolation_parameter.temperature_idw.max_distance = 20000 # zscale is used to discriminate neighbors at different elevation than target point self.assertAlmostEqual( model_interpolation_parameter.temperature_idw.zscale, 1.0) model_interpolation_parameter.temperature_idw.zscale = 0.5 self.assertAlmostEqual( model_interpolation_parameter.temperature_idw.zscale, 0.5) # Pure linear interpolation model_interpolation_parameter.temperature_idw.distance_measure_factor = 1.0 # This enables IDW with default temperature gradient. model_interpolation_parameter.use_idw_for_temperature = True self.assertAlmostEqual( model_interpolation_parameter.precipitation.scale_factor, 1.02) # just verify this one is as before change to scale_factor model.initialize_cell_environment( time_axis ) # just show how we can split the run_interpolation into two calls(second one optional) model.interpolate( model_interpolation_parameter, self.create_dummy_region_environment( time_axis, model.get_cells()[int(num_cells / 2)].geo.mid_point())) m_ip_parameter = model.interpolation_parameter # illustrate that we can get back the passed interpolation parameter as a property of the model self.assertEqual( m_ip_parameter.use_idw_for_temperature, True) # just to ensure we really did get back what we passed in self.assertAlmostEqual(m_ip_parameter.temperature_idw.zscale, 0.5) # # Section to demo that we can ensure that model.cells[].env_ts.xxx # have finite-values only # for env_ts_x in [ model.cells[0].env_ts.temperature, model.cells[0].env_ts.precipitation, model.cells[0].env_ts.rel_hum, model.cells[0].env_ts.radiation, model.cells[0].env_ts.wind_speed ]: self.assertTrue( model.is_cell_env_ts_ok() ) # demon how to verify that cell.env_ts can be checked prior to run vx = env_ts_x.value(0) # save value env_ts_x.set(0, float('nan')) # insert a nan self.assertFalse(model.is_cell_env_ts_ok() ) # demo it returns false if nan @cell.env_ts env_ts_x.set(0, vx) # insert back original value self.assertTrue(model.is_cell_env_ts_ok()) # ready to run again s0 = pt_gs_k.PTGSKStateVector() for i in range(num_cells): si = pt_gs_k.PTGSKState() si.kirchner.q = 40.0 s0.append(si) model.set_states(s0) model.set_state_collection( -1, True) # enable state collection for all cells model2 = model_type( model ) # make a copy, so that we in the stepwise run below get a clean copy with all values zero. opt_model = pt_gs_k.create_opt_model_clone( model) # this is how to make a model suitable for optimizer model.run_cells( ) # the default arguments applies: thread_cell_count=0,start_step=0,n_steps=0) cids = api.IntVector( ) # optional, we can add selective catchment_ids here sum_discharge = model.statistics.discharge(cids) sum_discharge_value = model.statistics.discharge_value( cids, 0) # at the first timestep sum_charge = model.statistics.charge(cids) sum_charge_value = model.statistics.charge_value(cids, 0) self.assertAlmostEqual(sum_charge_value, -110.6998, places=2) self.assertAlmostEqual(sum_charge.values[0], sum_charge_value, places=5) cell_charge = model.statistics.charge_value( api.IntVector([0, 1, 3]), 0, ix_type=api.stat_scope.cell) self.assertAlmostEqual(cell_charge, -16.7138, places=2) charge_sum_1_2_6 = model.statistics.charge( api.IntVector([1, 2, 6]), ix_type=api.stat_scope.cell).values.to_numpy().sum() self.assertAlmostEqual(charge_sum_1_2_6, 107.3981, places=2) ae_output = model.actual_evaptranspiration_response.output(cids) ae_pot_ratio = model.actual_evaptranspiration_response.pot_ratio(cids) self.assertIsNotNone(ae_output) self.assertAlmostEqual(ae_output.values.to_numpy().max(), 0.189214067680088) self.assertIsNotNone(ae_pot_ratio) self.assertAlmostEqual(ae_pot_ratio.values.to_numpy().min(), 0.9995599424191931) self.assertAlmostEqual(ae_pot_ratio.values.to_numpy().max(), 1.0) opt_model.run_cells( ) # starting out with the same state, same interpolated values, and region-parameters, we should get same results sum_discharge_opt_value = opt_model.statistics.discharge_value(cids, 0) self.assertAlmostEqual( sum_discharge_opt_value, sum_discharge_value, 3) # verify the opt_model clone gives same value self.assertGreaterEqual(sum_discharge_value, 130.0) opt_model.region_env.temperature[0].ts.set( 0, 23.2 ) # verify that region-env is different (no aliasing, a true copy is required) self.assertFalse( abs(model.region_env.temperature[0].ts.value(0) - opt_model.region_env.temperature[0].ts.value(0)) > 0.5) # # check values # self.assertIsNotNone(sum_discharge) # now, re-run the process in 24-hours steps x 10 model.set_states(s0) # restore state s0 self.assertEqual(s0.size(), model.initial_state.size()) for do_collect_state in [False, True]: model2.set_state_collection( -1, do_collect_state ) # issue reported by Yisak, prior to 21.3, this would crash model2.set_states(s0) # now after fix, it works Ok for section in range(10): model2.run_cells(use_ncore=0, start_step=section * 24, n_steps=24) section_discharge = model2.statistics.discharge(cids) self.assertEqual(section_discharge.size(), sum_discharge.size( )) # notice here that the values after current step are 0.0 stepwise_sum_discharge = model2.statistics.discharge(cids) # assert stepwise_sum_discharge == sum_discharge diff_ts = sum_discharge.values.to_numpy( ) - stepwise_sum_discharge.values.to_numpy() self.assertAlmostEqual((diff_ts * diff_ts).max(), 0.0, 4) # Verify that if we pass in illegal cids, then it raises exception(with first failing try: illegal_cids = api.IntVector([0, 4, 5]) model.statistics.discharge(illegal_cids) self.assertFalse( True, "Failed test, using illegal cids should raise exception") except RuntimeError as rte: pass avg_temperature = model.statistics.temperature(cids) avg_precipitation = model.statistics.precipitation(cids) self.assertIsNotNone(avg_precipitation) for time_step in range(time_axis.size()): precip_raster = model.statistics.precipitation( cids, time_step) # example raster output self.assertEqual(precip_raster.size(), num_cells) # example single value spatial aggregation (area-weighted) over cids for a specific timestep avg_gs_sc_value = model.gamma_snow_response.sca_value(cids, 1) self.assertGreaterEqual(avg_gs_sc_value, 0.0) avg_gs_sca = model.gamma_snow_response.sca(cids) # swe output self.assertIsNotNone(avg_gs_sca) # lwc surface_heat alpha melt_mean melt iso_pot_energy temp_sw avg_gs_albedo = model.gamma_snow_state.albedo(cids) self.assertIsNotNone(avg_gs_albedo) self.assertEqual(avg_temperature.size(), time_axis.size(), "expect results equal to time-axis size") copy_region_model = model.__class__(model) self.assertIsNotNone(copy_region_model) copy_region_model.run_cells( ) # just to verify we can copy and run the new model # # Play with routing and river-network # # 1st: add a river, with 36.000 meter hydro length, a UHGParameter with 1m/hour speed, alpha/beta suitable model.river_network.add( api.River(1, api.RoutingInfo(0, 3000.0), api.UHGParameter(1 / 3.60, 7.0, 0.0))) # river id =1 # 2nd: let cells route to the river model.connect_catchment_to_river( 1, 1) # now all cells in catchment 1 routes to river with id 1. self.assertTrue(model.has_routing()) # 3rd: now we can have a look at water coming in and out river_out_m3s = model.river_output_flow_m3s( 1) # should be delayed and reshaped river_local_m3s = model.river_local_inflow_m3s( 1 ) # should be equal to cell outputs (no routing stuff from cell to river) river_upstream_inflow_m3s = model.river_upstream_inflow_m3s( 1 ) # should be 0.0 in this case, since we do not have a routing network self.assertIsNotNone(river_out_m3s) self.assertAlmostEqual(river_out_m3s.value(8), 28.061248025828114, 0) self.assertIsNotNone(river_local_m3s) self.assertIsNotNone(river_upstream_inflow_m3s) model.connect_catchment_to_river(1, 0) self.assertFalse(model.has_routing()) # # Test the state-adjustments interfaces # q_0 = model.cells[0].state.kirchner.q model.adjust_q(2.0, cids) q_1 = model.cells[0].state.kirchner.q self.assertAlmostEqual(q_0 * 2.0, q_1) model.revert_to_initial_state() # ensure we have a known state model.run_cells(0, 10, 2) # just run step 10 and 11 q_avg = (model.statistics.discharge_value(cids, 10) + model.statistics.discharge_value(cids, 11) ) / 2.0 # get out the discharge for step 10 and 11 x = 0.7 # we want x*q_avg as target model.revert_to_initial_state( ) # important, need start state for the test here adjust_result = model.adjust_state_to_target_flow( x * q_avg, cids, start_step=10, scale_range=3.0, scale_eps=1e-3, max_iter=350, n_steps=2 ) # This is how to adjust state to observed average flow for cids for tstep 10 self.assertEqual(len(adjust_result.diagnostics), 0) # diag should be len(0) if ok. self.assertAlmostEqual(adjust_result.q_r, q_avg * x, 2) # verify we reached target self.assertAlmostEqual(adjust_result.q_0, q_avg, 2) # .q_0, # now verify what happens if we put in bad values for observed value adjust_result = model.adjust_state_to_target_flow(float('nan'), cids, start_step=10, scale_range=3.0, scale_eps=1e-3, max_iter=300, n_steps=2) assert len(adjust_result.diagnostics ) > 0, 'expect diagnostics length be larger than 0' # then verify what happens if we put in bad values on simulated result model.cells[0].env_ts.temperature.set(10, float('nan')) adjust_result = model.adjust_state_to_target_flow(30.0, cids, start_step=10, scale_range=3.0, scale_eps=1e-3, max_iter=300, n_steps=2) assert len(adjust_result.diagnostics ) > 0, 'expect diagnostics length be larger than 0'
def test_model_initialize_and_run(self): num_cells = 20 model_type = pt_gs_k.PTGSKModel model = self.build_model(model_type, pt_gs_k.PTGSKParameter, num_cells) self.assertEqual(model.size(), num_cells) # now modify snow_cv forest_factor to 0.1 region_parameter = model.get_region_parameter() region_parameter.gs.snow_cv_forest_factor = 0.1 region_parameter.gs.snow_cv_altitude_factor = 0.0001 self.assertEqual(region_parameter.gs.snow_cv_forest_factor, 0.1) self.assertEqual(region_parameter.gs.snow_cv_altitude_factor, 0.0001) self.assertAlmostEqual(region_parameter.gs.effective_snow_cv(1.0, 0.0), region_parameter.gs.snow_cv + 0.1) self.assertAlmostEqual( region_parameter.gs.effective_snow_cv(1.0, 1000.0), region_parameter.gs.snow_cv + 0.1 + 0.1) cal = api.Calendar() time_axis = api.Timeaxis(cal.time(api.YMDhms(2015, 1, 1, 0, 0, 0)), api.deltahours(1), 240) model_interpolation_parameter = api.InterpolationParameter() # degC/m, so -0.5 degC/100m model_interpolation_parameter.temperature_idw.default_temp_gradient = -0.005 # if possible use closest neighbor points and solve gradient using equation,(otherwise default min/max height) model_interpolation_parameter.temperature_idw.gradient_by_equation = True # Max number of temperature sources used for one interpolation model_interpolation_parameter.temperature_idw.max_members = 6 # 20 km is max distance model_interpolation_parameter.temperature_idw.max_distance = 20000 # Pure linear interpolation model_interpolation_parameter.temperature_idw.distance_measure_factor = 1.0 # This enables IDW with default temperature gradient. model_interpolation_parameter.use_idw_for_temperature = True self.assertAlmostEqual( model_interpolation_parameter.precipitation.scale_factor, 1.02) # just verify this one is as before change to scale_factor model.run_interpolation( model_interpolation_parameter, time_axis, self.create_dummy_region_environment( time_axis, model.get_cells()[int(num_cells / 2)].geo.mid_point())) s0 = pt_gs_k.PTGSKStateVector() for i in range(num_cells): si = pt_gs_k.PTGSKState() si.kirchner.q = 40.0 s0.append(si) model.set_states(s0) model.set_state_collection( -1, True) # enable state collection for all cells model.run_cells() cids = api.IntVector( ) # optional, we can add selective catchment_ids here sum_discharge = model.statistics.discharge(cids) self.assertIsNotNone(sum_discharge) avg_temperature = model.statistics.temperature(cids) avg_precipitation = model.statistics.precipitation(cids) self.assertIsNotNone(avg_precipitation) for time_step in range(time_axis.size()): precip_raster = model.statistics.precipitation( cids, time_step) # example raster output self.assertEqual(precip_raster.size(), num_cells) avg_gs_sca = model.gamma_snow_response.sca(cids) # swe output self.assertIsNotNone(avg_gs_sca) # lwc surface_heat alpha melt_mean melt iso_pot_energy temp_sw avg_gs_albedo = model.gamma_snow_state.albedo(cids) self.assertIsNotNone(avg_gs_albedo) self.assertEqual(avg_temperature.size(), time_axis.size(), "expect results equal to time-axis size") copy_region_model = model.__class__(model) self.assertIsNotNone(copy_region_model) copy_region_model.run_cells( ) #just to verify we can copy and run the new model
def test_model_initialize_and_run(self): num_cells = 20 model_type = pt_gs_k.PTGSKModel model = self.build_model(model_type, pt_gs_k.PTGSKParameter, num_cells) self.assertEqual(model.size(), num_cells) self.verify_state_handler(model) # demo of feature for threads self.assertGreaterEqual(model.ncore, 1) # defaults to hardware concurrency model.ncore = 4 # set it to 4, and self.assertEqual(model.ncore, 4) # verify it works # now modify snow_cv forest_factor to 0.1 region_parameter = model.get_region_parameter() region_parameter.gs.snow_cv_forest_factor = 0.1 region_parameter.gs.snow_cv_altitude_factor = 0.0001 self.assertEqual(region_parameter.gs.snow_cv_forest_factor, 0.1) self.assertEqual(region_parameter.gs.snow_cv_altitude_factor, 0.0001) self.assertAlmostEqual(region_parameter.gs.effective_snow_cv(1.0, 0.0), region_parameter.gs.snow_cv + 0.1) self.assertAlmostEqual( region_parameter.gs.effective_snow_cv(1.0, 1000.0), region_parameter.gs.snow_cv + 0.1 + 0.1) cal = api.Calendar() time_axis = api.TimeAxisFixedDeltaT(cal.time(2015, 1, 1, 0, 0, 0), api.deltahours(1), 240) model_interpolation_parameter = api.InterpolationParameter() # degC/m, so -0.5 degC/100m model_interpolation_parameter.temperature_idw.default_temp_gradient = -0.005 # if possible use closest neighbor points and solve gradient using equation,(otherwise default min/max height) model_interpolation_parameter.temperature_idw.gradient_by_equation = True # Max number of temperature sources used for one interpolation model_interpolation_parameter.temperature_idw.max_members = 6 # 20 km is max distance model_interpolation_parameter.temperature_idw.max_distance = 20000 # zscale is used to discriminate neighbors at different elevation than target point self.assertAlmostEqual( model_interpolation_parameter.temperature_idw.zscale, 1.0) model_interpolation_parameter.temperature_idw.zscale = 0.5 self.assertAlmostEqual( model_interpolation_parameter.temperature_idw.zscale, 0.5) # Pure linear interpolation model_interpolation_parameter.temperature_idw.distance_measure_factor = 1.0 # This enables IDW with default temperature gradient. model_interpolation_parameter.use_idw_for_temperature = True self.assertAlmostEqual( model_interpolation_parameter.precipitation.scale_factor, 1.02) # just verify this one is as before change to scale_factor model.initialize_cell_environment( time_axis ) # just show how we can split the run_interpolation into two calls(second one optional) model.interpolate( model_interpolation_parameter, self.create_dummy_region_environment( time_axis, model.get_cells()[int(num_cells / 2)].geo.mid_point())) m_ip_parameter = model.interpolation_parameter # illustrate that we can get back the passed interpolation parameter as a property of the model self.assertEqual( m_ip_parameter.use_idw_for_temperature, True) # just to ensure we really did get back what we passed in self.assertAlmostEqual(m_ip_parameter.temperature_idw.zscale, 0.5) s0 = pt_gs_k.PTGSKStateVector() for i in range(num_cells): si = pt_gs_k.PTGSKState() si.kirchner.q = 40.0 s0.append(si) model.set_states(s0) model.set_state_collection( -1, True) # enable state collection for all cells model2 = model_type( model ) # make a copy, so that we in the stepwise run below get a clean copy with all values zero. opt_model = pt_gs_k.create_opt_model_clone( model) # this is how to make a model suitable for optimizer model.run_cells( ) # the default arguments applies: thread_cell_count=0,start_step=0,n_steps=0) cids = api.IntVector( ) # optional, we can add selective catchment_ids here sum_discharge = model.statistics.discharge(cids) sum_discharge_value = model.statistics.discharge_value( cids, 0) # at the first timestep sum_charge = model.statistics.charge(cids) sum_charge_value = model.statistics.charge_value(cids, 0) ae_output = model.actual_evaptranspiration_response.output(cids) ae_pot_ratio = model.actual_evaptranspiration_response.pot_ratio(cids) self.assertIsNotNone(ae_output) self.assertAlmostEqual(ae_output.values.to_numpy().max(), 0.189214067680088) self.assertIsNotNone(ae_pot_ratio) self.assertAlmostEqual(ae_pot_ratio.values.to_numpy().min(), 0.9999330003895371) self.assertAlmostEqual(ae_pot_ratio.values.to_numpy().max(), 1.0) opt_model.run_cells( ) # starting out with the same state, same interpolated values, and region-parameters, we should get same results sum_discharge_opt_value = opt_model.statistics.discharge_value(cids, 0) self.assertAlmostEqual( sum_discharge_opt_value, sum_discharge_value, 3) # verify the opt_model clone gives same value self.assertGreaterEqual(sum_discharge_value, 130.0) opt_model.region_env.temperature[0].ts.set( 0, 23.2 ) # verify that region-env is different (no aliasing, a true copy is required) self.assertFalse( abs(model.region_env.temperature[0].ts.value(0) - opt_model.region_env.temperature[0].ts.value(0)) > 0.5) # # check values # self.assertIsNotNone(sum_discharge) # now, re-run the process in 24-hours steps x 10 model.set_states(s0) # restore state s0 self.assertEqual(s0.size(), model.initial_state.size()) for do_collect_state in [False, True]: model2.set_state_collection( -1, do_collect_state ) # issue reported by Yisak, prior to 21.3, this would crash model2.set_states(s0) # now after fix, it works Ok for section in range(10): model2.run_cells(use_ncore=0, start_step=section * 24, n_steps=24) section_discharge = model2.statistics.discharge(cids) self.assertEqual(section_discharge.size(), sum_discharge.size( )) # notice here that the values after current step are 0.0 stepwise_sum_discharge = model2.statistics.discharge(cids) # assert stepwise_sum_discharge == sum_discharge diff_ts = sum_discharge.values.to_numpy( ) - stepwise_sum_discharge.values.to_numpy() self.assertAlmostEqual((diff_ts * diff_ts).max(), 0.0, 4) # Verify that if we pass in illegal cids, then it raises exception(with first failing try: illegal_cids = api.IntVector([0, 4, 5]) model.statistics.discharge(illegal_cids) self.assertFalse( True, "Failed test, using illegal cids should raise exception") except RuntimeError as rte: pass avg_temperature = model.statistics.temperature(cids) avg_precipitation = model.statistics.precipitation(cids) self.assertIsNotNone(avg_precipitation) for time_step in range(time_axis.size()): precip_raster = model.statistics.precipitation( cids, time_step) # example raster output self.assertEqual(precip_raster.size(), num_cells) # example single value spatial aggregation (area-weighted) over cids for a specific timestep avg_gs_sc_value = model.gamma_snow_response.sca_value(cids, 1) self.assertGreaterEqual(avg_gs_sc_value, 0.0) avg_gs_sca = model.gamma_snow_response.sca(cids) # swe output self.assertIsNotNone(avg_gs_sca) # lwc surface_heat alpha melt_mean melt iso_pot_energy temp_sw avg_gs_albedo = model.gamma_snow_state.albedo(cids) self.assertIsNotNone(avg_gs_albedo) self.assertEqual(avg_temperature.size(), time_axis.size(), "expect results equal to time-axis size") copy_region_model = model.__class__(model) self.assertIsNotNone(copy_region_model) copy_region_model.run_cells( ) # just to verify we can copy and run the new model # # Play with routing and river-network # # 1st: add a river, with 36.000 meter hydro length, a UHGParameter with 1m/hour speed, alpha/beta suitable model.river_network.add( api.River(1, api.RoutingInfo(0, 3000.0), api.UHGParameter(1 / 3.60, 7.0, 0.0))) # river id =1 # 2nd: let cells route to the river model.connect_catchment_to_river( 0, 1) # now all cells in catchment 0 routes to river with id 1. self.assertTrue(model.has_routing()) # 3rd: now we can have a look at water coming in and out river_out_m3s = model.river_output_flow_m3s( 1) # should be delayed and reshaped river_local_m3s = model.river_local_inflow_m3s( 1 ) # should be equal to cell outputs (no routing stuff from cell to river) river_upstream_inflow_m3s = model.river_upstream_inflow_m3s( 1 ) # should be 0.0 in this case, since we do not have a routing network self.assertIsNotNone(river_out_m3s) self.assertAlmostEqual(river_out_m3s.value(8), 31.57297, 0) self.assertIsNotNone(river_local_m3s) self.assertIsNotNone(river_upstream_inflow_m3s) model.connect_catchment_to_river(0, 0) self.assertFalse(model.has_routing()) # # Test the state-adjustments interfaces # q_0 = model.cells[0].state.kirchner.q model.adjust_q(2.0, cids) q_1 = model.cells[0].state.kirchner.q self.assertAlmostEqual(q_0 * 2.0, q_1) model.revert_to_initial_state() # ensure we have a known state model.run_cells(0, 10, 1) # just run step 10 q_avg = model.statistics.discharge_value( cids, 10) # get out the discharge for step 10 x = 0.7 # we want x*q_avg as target model.revert_to_initial_state( ) # important, need start state for the test here adjust_result = model.adjust_state_to_target_flow( x * q_avg, cids, start_step=10, scale_range=3.0, scale_eps=1e-3, max_iter=350 ) # This is how to adjust state to observed average flow for cids for tstep 10 self.assertEqual(len(adjust_result.diagnostics), 0) # diag should be len(0) if ok. self.assertAlmostEqual(adjust_result.q_r, q_avg * x, 3) # verify we reached target self.assertAlmostEqual(adjust_result.q_0, q_avg, 3) # .q_0, def test_optimization_model(self): num_cells = 20 model_type = pt_gs_k.PTGSKOptModel model = self.build_model(model_type, pt_gs_k.PTGSKParameter, num_cells) cal = api.Calendar() t0 = cal.time(2015, 1, 1, 0, 0, 0) dt = api.deltahours(1) n = 240 time_axis = api.TimeAxisFixedDeltaT(t0, dt, n) model_interpolation_parameter = api.InterpolationParameter() model.initialize_cell_environment( time_axis ) # just show how we can split the run_interpolation into two calls(second one optional) model.interpolate( model_interpolation_parameter, self.create_dummy_region_environment( time_axis, model.get_cells()[int(num_cells / 2)].geo.mid_point())) s0 = pt_gs_k.PTGSKStateVector() for i in range(num_cells): si = pt_gs_k.PTGSKState() si.kirchner.q = 40.0 s0.append(si) model.set_states( s0 ) # at this point the intial state of model is established as well model.run_cells() cids = api.IntVector.from_numpy( [0]) # optional, we can add selective catchment_ids here sum_discharge = model.statistics.discharge(cids) sum_discharge_value = model.statistics.discharge_value( cids, 0) # at the first timestep self.assertGreaterEqual(sum_discharge_value, 130.0) # verify we can construct an optimizer optimizer = model_type.optimizer_t( model ) # notice that a model type know it's optimizer type, e.g. PTGSKOptimizer self.assertIsNotNone(optimizer) # # create target specification # model.revert_to_initial_state( ) # set_states(s0) # remember to set the s0 again, so we have the same initial condition for our game tsa = api.TsTransform().to_average(t0, dt, n, sum_discharge) t_spec_1 = api.TargetSpecificationPts(tsa, cids, 1.0, api.KLING_GUPTA, 1.0, 0.0, 0.0, api.DISCHARGE, 'test_uid') target_spec = api.TargetSpecificationVector() target_spec.append(t_spec_1) upper_bound = model_type.parameter_t(model.get_region_parameter( )) # the model_type know it's parameter_t lower_bound = model_type.parameter_t(model.get_region_parameter()) upper_bound.kirchner.c1 = -1.9 lower_bound.kirchner.c1 = -3.0 upper_bound.kirchner.c2 = 0.99 lower_bound.kirchner.c2 = 0.80 optimizer.set_target_specification(target_spec, lower_bound, upper_bound) # Not needed, it will automatically get one. # optimizer.establish_initial_state_from_model() # s0_0 = optimizer.get_initial_state(0) # optimizer.set_verbose_level(1000) p0 = model_type.parameter_t(model.get_region_parameter()) orig_c1 = p0.kirchner.c1 orig_c2 = p0.kirchner.c2 # model.get_cells()[0].env_ts.precipitation.set(0, 5.1) # model.get_cells()[0].env_ts.precipitation.set(1, 4.9) p0.kirchner.c1 = -2.4 p0.kirchner.c2 = 0.91 opt_param = optimizer.optimize(p0, 1500, 0.1, 1e-5) goal_fx = optimizer.calculate_goal_function(opt_param) p0.kirchner.c1 = -2.4 p0.kirchner.c2 = 0.91 # goal_fx1 = optimizer.calculate_goal_function(p0) self.assertLessEqual(goal_fx, 10.0) self.assertAlmostEqual(orig_c1, opt_param.kirchner.c1, 4) self.assertAlmostEqual(orig_c2, opt_param.kirchner.c2, 4)
def test_model_initialize_and_run(self): num_cells = 20 model_type = pt_gs_k.PTGSKModel model = self.build_model(model_type, pt_gs_k.PTGSKParameter, num_cells) self.assertEqual(model.size(), num_cells) self.verify_state_handler(model) # demo of feature for threads self.assertGreaterEqual(model.ncore, 1) # defaults to hardware concurrency model.ncore = 4 # set it to 4, and self.assertEqual(model.ncore, 4) # verify it works # now modify snow_cv forest_factor to 0.1 region_parameter = model.get_region_parameter() region_parameter.gs.snow_cv_forest_factor = 0.1 region_parameter.gs.snow_cv_altitude_factor = 0.0001 self.assertEqual(region_parameter.gs.snow_cv_forest_factor, 0.1) self.assertEqual(region_parameter.gs.snow_cv_altitude_factor, 0.0001) self.assertAlmostEqual(region_parameter.gs.effective_snow_cv(1.0, 0.0), region_parameter.gs.snow_cv + 0.1) self.assertAlmostEqual( region_parameter.gs.effective_snow_cv(1.0, 1000.0), region_parameter.gs.snow_cv + 0.1 + 0.1) cal = api.Calendar() time_axis = api.TimeAxisFixedDeltaT(cal.time(2015, 1, 1, 0, 0, 0), api.deltahours(1), 240) model_interpolation_parameter = api.InterpolationParameter() # degC/m, so -0.5 degC/100m model_interpolation_parameter.temperature_idw.default_temp_gradient = -0.005 # if possible use closest neighbor points and solve gradient using equation,(otherwise default min/max height) model_interpolation_parameter.temperature_idw.gradient_by_equation = True # Max number of temperature sources used for one interpolation model_interpolation_parameter.temperature_idw.max_members = 6 # 20 km is max distance model_interpolation_parameter.temperature_idw.max_distance = 20000 # zscale is used to discriminate neighbors at different elevation than target point self.assertAlmostEqual( model_interpolation_parameter.temperature_idw.zscale, 1.0) model_interpolation_parameter.temperature_idw.zscale = 0.5 self.assertAlmostEqual( model_interpolation_parameter.temperature_idw.zscale, 0.5) # Pure linear interpolation model_interpolation_parameter.temperature_idw.distance_measure_factor = 1.0 # This enables IDW with default temperature gradient. model_interpolation_parameter.use_idw_for_temperature = True self.assertAlmostEqual( model_interpolation_parameter.precipitation.scale_factor, 1.02) # just verify this one is as before change to scale_factor model.initialize_cell_environment( time_axis ) # just show how we can split the run_interpolation into two calls(second one optional) model.interpolate( model_interpolation_parameter, self.create_dummy_region_environment( time_axis, model.get_cells()[int(num_cells / 2)].geo.mid_point())) m_ip_parameter = model.interpolation_parameter # illustrate that we can get back the passed interpolation parameter as a property of the model self.assertEqual( m_ip_parameter.use_idw_for_temperature, True) # just to ensure we really did get back what we passed in self.assertAlmostEqual(m_ip_parameter.temperature_idw.zscale, 0.5) s0 = pt_gs_k.PTGSKStateVector() for i in range(num_cells): si = pt_gs_k.PTGSKState() si.kirchner.q = 40.0 s0.append(si) model.set_states(s0) model.set_state_collection( -1, True) # enable state collection for all cells model2 = model_type( model ) # make a copy, so that we in the stepwise run below get a clean copy with all values zero. opt_model = pt_gs_k.create_opt_model_clone( model) # this is how to make a model suitable for optimizer model.run_cells( ) # the default arguments applies: thread_cell_count=0,start_step=0,n_steps=0) cids = api.IntVector( ) # optional, we can add selective catchment_ids here sum_discharge = model.statistics.discharge(cids) sum_discharge_value = model.statistics.discharge_value( cids, 0) # at the first timestep sum_charge = model.statistics.charge(cids) sum_charge_value = model.statistics.charge_value(cids, 0) opt_model.run_cells( ) # starting out with the same state, same interpolated values, and region-parameters, we should get same results sum_discharge_opt_value = opt_model.statistics.discharge_value(cids, 0) self.assertAlmostEqual( sum_discharge_opt_value, sum_discharge_value, 3) # verify the opt_model clone gives same value self.assertGreaterEqual(sum_discharge_value, 130.0) opt_model.region_env.temperature[0].ts.set( 0, 23.2 ) # verify that region-env is different (no aliasing, a true copy is required) self.assertFalse( abs(model.region_env.temperature[0].ts.value(0) - opt_model.region_env.temperature[0].ts.value(0)) > 0.5) # # check values # self.assertIsNotNone(sum_discharge) # now, re-run the process in 24-hours steps x 10 model.set_states(s0) # restore state s0 self.assertEqual(s0.size(), model.initial_state.size()) for do_collect_state in [False, True]: model2.set_state_collection( -1, do_collect_state ) # issue reported by Yisak, prior to 21.3, this would crash model2.set_states(s0) # now after fix, it works Ok for section in range(10): model2.run_cells(use_ncore=0, start_step=section * 24, n_steps=24) section_discharge = model2.statistics.discharge(cids) self.assertEqual(section_discharge.size(), sum_discharge.size( )) # notice here that the values after current step are 0.0 stepwise_sum_discharge = model2.statistics.discharge(cids) # assert stepwise_sum_discharge == sum_discharge diff_ts = sum_discharge.values.to_numpy( ) - stepwise_sum_discharge.values.to_numpy() self.assertAlmostEqual((diff_ts * diff_ts).max(), 0.0, 4) # Verify that if we pass in illegal cids, then it raises exception(with first failing try: illegal_cids = api.IntVector([0, 4, 5]) model.statistics.discharge(illegal_cids) self.assertFalse( True, "Failed test, using illegal cids should raise exception") except RuntimeError as rte: pass avg_temperature = model.statistics.temperature(cids) avg_precipitation = model.statistics.precipitation(cids) self.assertIsNotNone(avg_precipitation) for time_step in range(time_axis.size()): precip_raster = model.statistics.precipitation( cids, time_step) # example raster output self.assertEqual(precip_raster.size(), num_cells) # example single value spatial aggregation (area-weighted) over cids for a specific timestep avg_gs_sc_value = model.gamma_snow_response.sca_value(cids, 1) self.assertGreaterEqual(avg_gs_sc_value, 0.0) avg_gs_sca = model.gamma_snow_response.sca(cids) # swe output self.assertIsNotNone(avg_gs_sca) # lwc surface_heat alpha melt_mean melt iso_pot_energy temp_sw avg_gs_albedo = model.gamma_snow_state.albedo(cids) self.assertIsNotNone(avg_gs_albedo) self.assertEqual(avg_temperature.size(), time_axis.size(), "expect results equal to time-axis size") copy_region_model = model.__class__(model) self.assertIsNotNone(copy_region_model) copy_region_model.run_cells( ) # just to verify we can copy and run the new model # # Play with routing and river-network # # 1st: add a river, with 36.000 meter hydro length, a UHGParameter with 1m/hour speed, alpha/beta suitable model.river_network.add( api.River(1, api.RoutingInfo(0, 3000.0), api.UHGParameter(1 / 3.60, 7.0, 0.0))) # river id =1 # 2nd: let cells route to the river model.connect_catchment_to_river( 0, 1) # now all cells in catchment 0 routes to river with id 1. self.assertTrue(model.has_routing()) # 3rd: now we can have a look at water coming in and out river_out_m3s = model.river_output_flow_m3s( 1) # should be delayed and reshaped river_local_m3s = model.river_local_inflow_m3s( 1 ) # should be equal to cell outputs (no routing stuff from cell to river) river_upstream_inflow_m3s = model.river_upstream_inflow_m3s( 1 ) # should be 0.0 in this case, since we do not have a routing network self.assertIsNotNone(river_out_m3s) self.assertAlmostEqual(river_out_m3s.value(8), 31.57297, 0) self.assertIsNotNone(river_local_m3s) self.assertIsNotNone(river_upstream_inflow_m3s) model.connect_catchment_to_river(0, 0) self.assertFalse(model.has_routing())