def test_model_clone_with_catchment_parameters(self):
     """ Verify we can copy an opt-model from full model including catchment specific parameters"""
     m = self.build_model(pt_gs_k.PTGSKModel,
                          pt_gs_k.PTGSKParameter,
                          model_size=20,
                          num_catchments=2)
     p2 = pt_gs_k.PTGSKParameter()
     p2.kirchner.c1 = 2.3
     m.set_catchment_parameter(2, p2)
     self.assertTrue(m.has_catchment_parameter(2))
     self.assertFalse(m.has_catchment_parameter(1))
     o = pt_gs_k.create_opt_model_clone(
         m, True
     )  # this is how to create an opt-model, with catchm. spec params
     self.assertTrue(o.has_catchment_parameter(2))
     self.assertFalse(o.has_catchment_parameter(1))
     o = pt_gs_k.create_opt_model_clone(
         m, False)  # default, only region param is copied(for opt-purposes)
     self.assertFalse(o.has_catchment_parameter(2))
     self.assertFalse(o.has_catchment_parameter(1))
Exemplo n.º 2
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)
        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,
                                 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'
Exemplo n.º 4
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)
        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())