Example #1
0
    def test_resume(self):
        model_ab = GR4JCN()
        model_ab.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )
        kwargs = dict(params=(0.529, -3.396, 407.29, 1.072, 16.9, 0.947), )
        # Reference run
        model_ab(
            TS,
            run_name="run_ab",
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2001, 1, 1),
            **kwargs,
        )

        model_a = GR4JCN()

        model_a.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )
        model_a(
            TS,
            run_name="run_a",
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2000, 7, 1),
            **kwargs,
        )

        # Path to solution file from run A
        rvc = model_a.outputs["solution"]

        # Resume with final state from live model
        model_a.resume()

        model_a(
            TS,
            run_name="run_2",
            start_date=dt.datetime(2000, 7, 1),
            end_date=dt.datetime(2001, 1, 1),
            **kwargs,
        )

        for key in ["Soil Water[0]", "Soil Water[1]"]:
            np.testing.assert_array_almost_equal(
                model_a.storage[key] - model_ab.storage[key], 0, 5)

        # Resume with final state from saved solution file
        model_b = GR4JCN()
        model_b.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )
        model_b.resume(
            rvc
        )  # <--------- And this is how you feed it to a brand new model.
        model_b(
            TS,
            run_name="run_2",
            start_date=dt.datetime(2000, 7, 1),
            end_date=dt.datetime(2001, 1, 1),
            **kwargs,
        )

        for key in ["Soil Water[0]", "Soil Water[1]"]:
            np.testing.assert_array_almost_equal(
                model_b.storage[key] - model_ab.storage[key], 0, 5)
Example #2
0
    def test_evaluation(self):
        model = GR4JCN()

        model.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )

        model(
            TS,
            area=4250.6,
            elevation=843.0,
            latitude=54.4848,
            longitude=-123.3659,
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2002, 1, 1),
            params=(0.529, -3.396, 407.29, 1.072, 16.9, 0.947),
            suppress_output=False,
            evaluation_metrics=["RMSE", "KLING_GUPTA"],
            evaluation_periods=[
                EvaluationPeriod("period1", "2000-01-01", "2000-12-31"),
                EvaluationPeriod("period2", "2001-01-01", "2001-12-31"),
            ],
        )
        d = model.diagnostics
        assert "DIAG_RMSE" in d
        assert "DIAG_KLING_GUPTA" in d
        assert len(d["DIAG_RMSE"]) == 3  # ALL, period1, period2
Example #3
0
    def test_update_soil_water(self):
        params = (0.529, -3.396, 407.29, 1.072, 16.9, 0.947)
        # Reference run
        model = GR4JCN()
        model.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )
        model(
            TS,
            run_name="run_a",
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2000, 2, 1),
            params=params,
        )

        s_0 = float(model.storage["Soil Water[0]"].isel(time=-1).values)
        s_1 = float(model.storage["Soil Water[1]"].isel(time=-1).values)

        # hru_state = replace(model.rvc.hru_state, soil0=s_0, soil1=s_1)
        model.config.rvc.hru_states[1] = replace(
            model.config.rvc.hru_states[1], soil0=s_0, soil1=s_1)

        model(
            TS,
            run_name="run_b",
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2000, 2, 1),
            # hru_state=hru_state,
            params=params,
        )

        assert s_0 != model.storage["Soil Water[0]"].isel(time=-1)
        assert s_1 != model.storage["Soil Water[1]"].isel(time=-1)
Example #4
0
    def test_hindcasting_GEPS(self):

        # Prepare a RAVEN model run using historical data, GR4JCN in this case.
        # This is a dummy run to get initial states. In a real forecast situation,
        # this run would end on the day before the forecast, but process is the same.
        ts = get_local_testdata(
            "raven-gr4j-cemaneige/Salmon-River-Near-Prince-George_meteo_daily.nc"
        )
        hrus = (GR4JCN.LandHRU(area=4250.6,
                               elevation=843.0,
                               latitude=54.4848,
                               longitude=-123.3659), )
        model = GR4JCN()
        model(
            ts,
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2002, 6, 1),
            hrus=hrus,
            params=(0.529, -3.396, 407.29, 1.072, 16.9, 0.947),
        )

        # Extract the final states that will be used as the next initial states
        rvc = model.outputs["solution"]

        ts20 = get_local_testdata("caspar_eccc_hindcasts/geps_watershed.nc")
        nm = 20

        # It is necessary to clean the model state because the input variables of the previous
        # model are not the same as the ones provided in the forecast model. therefore, if we
        # do not clean, the model will simply add the hindcast file to the list of available
        # data provided in the testdata above. Then the dates will not work, and the model errors.
        model = GR4JCN()

        model.config.rvc.parse_solution(rvc.read_text())

        # And run the model with the forecast data.
        model(
            ts=ts20,
            start_date=dt.datetime(2018, 6, 1),
            end_date=dt.datetime(2018, 6, 10),
            hrus=hrus,
            params=(0.529, -3.396, 407.29, 1.072, 16.9, 0.947),
            overwrite=True,
            pr={
                "scale": 1.0,
                "offset": 0.0,
                "time_shift": -0.25,
                "deaccumulate": True,
            },
            tas={"time_shift": -0.25},
            parallel=dict(nc_index=range(nm)),
        )

        # The model now has the forecast data generated and it has 10 days of forecasts.
        assert len(model.q_sim.values) == 10

        # Also see if GEPS has 20 members produced.
        assert model.q_sim.values.shape[1] == nm
Example #5
0
    def test_overwrite(self):
        model = GR4JCN()

        model.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )

        model(
            TS,
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2002, 1, 1),
            params=(0.529, -3.396, 407.29, 1.072, 16.9, 0.947),
        )
        assert model.config.rvi.suppress_output == ""

        qsim1 = model.q_sim.copy(deep=True)
        m1 = qsim1.mean()

        # This is only needed temporarily while we fix this: https://github.com/CSHS-CWRA/RavenPy/issues/4
        # Please remove when fixed!
        model.hydrograph.close()  # Needed with xarray 0.16.1

        model(TS,
              params=(0.5289, -3.397, 407.3, 1.071, 16.89, 0.948),
              overwrite=True)

        qsim2 = model.q_sim.copy(deep=True)
        m2 = qsim2.mean()

        # This is only needed temporarily while we fix this: https://github.com/CSHS-CWRA/RavenPy/issues/4
        # Please remove when fixed!
        model.hydrograph.close()  # Needed with xarray 0.16.1

        assert m1 != m2

        np.testing.assert_almost_equal(m1, m2, 1)

        d = model.diagnostics

        np.testing.assert_almost_equal(d["DIAG_NASH_SUTCLIFFE"], -0.117315, 4)

        model.config.rvc.hru_states[1] = HRUStateVariableTableCommand.Record(
            soil0=0)

        # Set initial conditions explicitly
        model(
            TS,
            end_date=dt.datetime(2001, 2, 1),
            # hru_state=HRUStateVariableTableCommand.Record(soil0=0),
            overwrite=True,
        )
        assert model.q_sim.isel(time=1).values[0] < qsim2.isel(
            time=1).values[0]
Example #6
0
    def test_run_new_hrus_param(self):
        model = GR4JCN()

        model(
            TS,
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2002, 1, 1),
            params=(0.529, -3.396, 407.29, 1.072, 16.9, 0.947),
            suppress_output=False,
            hrus=(GR4JCN.LandHRU(**salmon_land_hru_1), ),
        )
        d = model.diagnostics

        np.testing.assert_almost_equal(d["DIAG_NASH_SUTCLIFFE"], -0.117301, 4)
Example #7
0
    def test_dap(self):
        """Test Raven with DAP link instead of local netCDF file."""
        model = GR4JCN()
        config = dict(
            start_date=dt.datetime(2000, 6, 1),
            end_date=dt.datetime(2000, 6, 10),
            run_name="test",
            hrus=(GR4JCN.LandHRU(**salmon_land_hru_1), ),
            params=model.Params(0.529, -3.396, 407.29, 1.072, 16.9, 0.947),
        )

        ts = (
            f"{TDS}/raven-gr4j-cemaneige/Salmon-River-Near-Prince-George_meteo_daily.nc"
        )
        model(ts, **config)
Example #8
0
    def test_run(self):
        model = GR4JCN()

        model.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )

        model(
            TS,
            area=4250.6,
            elevation=843.0,
            latitude=54.4848,
            longitude=-123.3659,
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2002, 1, 1),
            params=(0.529, -3.396, 407.29, 1.072, 16.9, 0.947),
            suppress_output=False,
        )
        d = model.diagnostics

        np.testing.assert_almost_equal(d["DIAG_NASH_SUTCLIFFE"], -0.117301, 4)
Example #9
0
    def test_parallel_basins(self, input2d):
        ts = input2d
        model = GR4JCN()
        model.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )

        model(
            ts,
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2002, 1, 1),
            params=[0.529, -3.396, 407.29, 1.072, 16.9, 0.947],
            # name=["basin1", "basin2"],  # Not sure about this..
            suppress_output=False,
            parallel={"nc_index": [0, 0]},
        )

        assert len(model.diagnostics) == 2
        assert len(model.hydrograph.nbasins) == 2
        np.testing.assert_array_equal(model.hydrograph.basin_name[:],
                                      ["sub_001", "sub_001"])
        z = zipfile.ZipFile(model.outputs["rv_config"])
        assert len(z.filelist) == 10
Example #10
0
    def test_parallel_params(self):
        model = GR4JCN()
        model.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )

        model(
            TS,
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2002, 1, 1),
            suppress_output=False,
            parallel={
                "params": [
                    (0.529, -3.396, 407.29, 1.072, 16.9, 0.947),
                    (0.528, -3.4, 407.3, 1.07, 17, 0.95),
                ]
            },
        )

        assert len(model.diagnostics) == 2
        assert model.hydrograph.dims["params"] == 2
        z = zipfile.ZipFile(model.outputs["rv_config"])
        assert len(z.filelist) == 10
Example #11
0
    def test_full_example(self):
        ts = get_local_testdata(
            "raven-gr4j-cemaneige/Salmon-River-Near-Prince-George_meteo_daily.nc"
        )
        model = "GR4JCN"
        nash, params = reg.read_gauged_params(model)
        variables = ["latitude", "longitude", "area", "forest"]
        props = reg.read_gauged_properties(variables)
        ungauged_props = {
            "latitude": 40.4848,
            "longitude": -103.3659,
            "area": 4250.6,
            "forest": 0.4,
        }

        hrus = (GR4JCN.LandHRU(area=4250.6,
                               elevation=843.0,
                               latitude=40.4848,
                               longitude=-103.3659), )

        qsim, ens = reg.regionalize(
            "SP_IDW",
            model,
            nash,
            params,
            props,
            ungauged_props,
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2002, 1, 1),
            hrus=hrus,
            longitude=-103.3659,
            min_NSE=0.6,
            size=2,
            ts=ts,
        )

        assert qsim.max() > 1
        assert len(ens) == 2
        assert "realization" in ens.dims
        assert "param" in ens.dims
Example #12
0
    def test_resume_earlier(self):
        """Check that we can resume a run with the start date set at another date than the time stamp in the
        solution."""
        params = (0.529, -3.396, 407.29, 1.072, 16.9, 0.947)
        # Reference run
        model = GR4JCN()
        model.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )
        model(
            TS,
            run_name="run_a",
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2000, 2, 1),
            params=params,
        )

        s_a = model.storage["Soil Water[0]"].isel(time=-1)

        # Path to solution file from run A
        rvc = model.outputs["solution"]

        # Resume with final state from live model
        # We have two options to do this:
        # 1. Replace model template by solution file as is: model.resume()
        # 2. Replace variable in RVC class by parsed values: model.rvc.parse(rvc.read_text())
        # I think in many cases option 2 will prove simpler.

        model.config.rvc.parse_solution(rvc.read_text())

        model(
            TS,
            run_name="run_b",
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2000, 2, 1),
            params=params,
        )

        s_b = model.storage["Soil Water[0]"].isel(time=-1)
        assert s_a != s_b
Example #13
0
import datetime as dt

from ravenpy.models import GR4JCN
from ravenpy.utilities.testdata import get_local_testdata

"""
Test to perform a hindcast using auto-queried ECCC data aggregated on THREDDS.
Currently only runs GEPS, eventually will run GEPS, GDPS, REPS and RDPS.
To do so will need to add the actual data from ECCC but this is a proof of concept.
"""

hru = GR4JCN.LandHRU(
    area=44250.6, elevation=843.0, latitude=54.4848, longitude=-123.3659
)


class TestECCCForecast:
    def test_forecasting_GEPS(self):

        # Prepare a RAVEN model run using historical data, GR4JCN in this case.
        # This is a dummy run to get initial states. In a real forecast situation,
        # this run would end on the day before the forecast, but process is the same.
        ts = get_local_testdata(
            "raven-gr4j-cemaneige/Salmon-River-Near-Prince-George_meteo_daily.nc"
        )
        model = GR4JCN()
        model(
            ts,
            start_date=dt.datetime(2000, 1, 1),
            end_date=dt.datetime(2002, 6, 1),
            params=(0.529, -3.396, 407.29, 1.072, 16.9, 0.947),
Example #14
0
    def test_simple(self):
        model = GR4JCN_OST()
        model.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )

        # Parameter bounds
        low = (0.01, -15.0, 10.0, 0.0, 1.0, 0.0)
        high = (2.5, 10.0, 700.0, 7.0, 30.0, 1.0)

        model.configure(
            get_local_testdata("ostrich-gr4j-cemaneige/OstRandomNumbers.txt"))

        model(
            TS,
            start_date=dt.datetime(1954, 1, 1),
            duration=208,
            lowerBounds=low,
            upperBounds=high,
            algorithm="DDS",
            random_seed=0,
            max_iterations=10,
        )

        d = model.diagnostics

        np.testing.assert_almost_equal(d["DIAG_NASH_SUTCLIFFE"], 0.50717, 4)

        # Random number seed: 123
        # Budget:             10
        # Algorithm:          DDS
        # :StartDate          1954-01-01 00:00:00
        # :Duration           208
        opt_para = astuple(model.calibrated_params)
        opt_func = model.obj_func

        np.testing.assert_almost_equal(
            opt_para,
            [2.424726, 3.758972, 204.3856, 5.866946, 16.60408, 0.3728098],
            4,
            err_msg="calibrated parameter set is not matching expected value",
        )

        np.testing.assert_almost_equal(
            opt_func,
            -0.50717,
            4,
            err_msg="calibrated NSE is not matching expected value",
        )

        # # Random number seed: 123
        # # Budget:             50
        # # Algorithm:          DDS
        # # :StartDate          1954-01-01 00:00:00
        # # :Duration           20819
        # np.testing.assert_almost_equal( opt_para, [0.3243268,3.034247,407.2890,2.722774,12.18124,0.9468769], 4,
        #                                 err_msg='calibrated parameter set is not matching expected value')
        # np.testing.assert_almost_equal( opt_func, -0.5779910, 4,
        #                                 err_msg='calibrated NSE is not matching expected value')
        gr4j = GR4JCN()
        gr4j.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )

        gr4j(
            TS,
            start_date=dt.datetime(1954, 1, 1),
            duration=208,
            params=model.calibrated_params,
        )

        np.testing.assert_almost_equal(gr4j.diagnostics["DIAG_NASH_SUTCLIFFE"],
                                       d["DIAG_NASH_SUTCLIFFE"])
Example #15
0
    def test_simple_alternate_hrus_param(self):
        client = client_for(
            Service(processes=[RavenGR4JCemaNeigeProcess()],
                    cfgfiles=CFG_FILE))

        params = "0.529, -3.396, 407.29, 1.072, 16.9, 0.947"

        # some params in Raven input files are derived from those 21 parameters
        # pdefaults.update({'GR4J_X1_hlf':            pdefaults['GR4J_X1']*1000./2.0})    --> x1 * 1000. / 2.0
        # pdefaults.update({'one_minus_CEMANEIGE_X2': 1.0 - pdefaults['CEMANEIGE_X2']})   --> 1.0 - x6

        salmon_land_hru_1 = dict(area=4250.6,
                                 elevation=843.0,
                                 latitude=54.4848,
                                 longitude=-123.3659)
        hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )
        hrus = json.dumps(list(map(asdict, hrus)))

        datainputs = (
            "ts=files@xlink:href=file://{ts};"
            "params={params};"
            "start_date={start_date};"
            "end_date={end_date};"
            "name={name};"
            "run_name={run_name};"
            "hrus={hrus};".format(
                ts=get_local_testdata(
                    "raven-gr4j-cemaneige/Salmon-River-Near-Prince-George_meteo_daily.nc",
                ),
                params=params,
                start_date=dt.datetime(2000, 1, 1),
                end_date=dt.datetime(2002, 1, 1),
                name="Salmon",
                run_name="test",
                hrus=hrus,
            ))

        resp = client.get(
            service="WPS",
            request="Execute",
            version="1.0.0",
            identifier="raven-gr4j-cemaneige",
            datainputs=datainputs,
        )

        assert_response_success(resp)

        out = get_output(resp.xml)
        assert "diagnostics" in out
        tmp_file, _ = urlretrieve(out["diagnostics"])
        tmp_content = open(tmp_file).readlines()

        # checking correctness of NSE (full period 1954-2010 would be NSE=0.511214)
        assert "DIAG_NASH_SUTCLIFFE" in tmp_content[0]
        idx_diag = tmp_content[0].split(",").index("DIAG_NASH_SUTCLIFFE")
        diag = float(tmp_content[1].split(",")[idx_diag])
        np.testing.assert_almost_equal(
            diag, -0.117301, 4, err_msg="NSE is not matching expected value")

        # checking correctness of RMSE (full period 1954-2010 would be RMSE=32.8827)
        assert "DIAG_RMSE" in tmp_content[0]
        idx_diag = tmp_content[0].split(",").index("DIAG_RMSE")
        diag = float(tmp_content[1].split(",")[idx_diag])
        np.testing.assert_almost_equal(
            diag, 37.9493, 4, err_msg="RMSE is not matching expected value")

        assert "rv_config" in out
        rv_config, _ = urlretrieve(out["rv_config"])
        z = zipfile.ZipFile(rv_config)
        assert len(z.filelist) == 5
Example #16
0
    def test_simple(self):
        model = GR4JCN()
        print(model.workdir)
        model.config.rvi.start_date = dt.datetime(2000, 1, 1)
        model.config.rvi.end_date = dt.datetime(2002, 1, 1)
        model.config.rvi.run_name = "test"

        model.config.rvh.hrus = (GR4JCN.LandHRU(**salmon_land_hru_1), )

        model.config.rvp.params = model.Params(0.529, -3.396, 407.29, 1.072,
                                               16.9, 0.947)

        total_area_in_m2 = model.config.rvh.hrus[0].area * 1000 * 1000
        model.config.rvp.avg_annual_runoff = get_average_annual_runoff(
            TS, total_area_in_m2)

        np.testing.assert_almost_equal(model.config.rvp.avg_annual_runoff,
                                       208.4805694844741)

        assert model.config.rvi.suppress_output == ""

        model(TS)

        # ------------
        # Check quality (diagnostic) of simulated streamflow values
        # ------------
        d = model.diagnostics
        np.testing.assert_almost_equal(d["DIAG_NASH_SUTCLIFFE"], -0.117301, 4)

        # ------------
        # Check simulated streamflow values q_sim
        # ------------
        hds = model.q_sim

        assert hds.attrs["long_name"] == "Simulated outflows"

        assert len(hds.nbasins) == 1  # number of "gauged" basins is 1

        # We only have one SB with gauged=True, so the output has a single column.
        # The number of time steps simulated between (2000, 1, 1) and
        # (2002, 1, 1) is 732.
        assert hds.shape == (732, 1)

        # Check simulated streamflow at first three timesteps and three simulated
        # timesteps in the middle of the simulation period.
        dates = (
            "2000-01-01",
            "2000-01-02",
            "2000-01-03",
            "2001-01-30",
            "2001-01-31",
            "2001-02-01",
        )

        target_q_sim = [
            0.0, 0.165788, 0.559366, 12.374606, 12.33398, 12.293458
        ]

        for t in range(6):
            np.testing.assert_almost_equal(hds.sel(nbasins=0, time=dates[t]),
                                           target_q_sim[t], 4)

        # ------------
        # Check parser
        # ------------

        assert model.config.rvi.calendar == RVI.CalendarOptions.GREGORIAN.value

        # ------------
        # Check saved HRU states saved in RVC
        # ------------
        assert 1 in model.solution.hru_states

        # ------------
        # Check attributes
        # ------------
        assert model.hydrograph.attrs["model_id"] == "gr4jcn"
Example #17
0
    def test_routing(self):
        """We need at least 2 subbasins to activate routing."""
        model = GR4JCN()

        ts_2d = get_local_testdata(
            "raven-gr4j-cemaneige/Salmon-River-Near-Prince-George_meteo_daily_2d.nc"
        )

        #########
        # R V I #
        #########

        model.config.rvi.start_date = dt.datetime(2000, 1, 1)
        model.config.rvi.end_date = dt.datetime(2002, 1, 1)
        model.config.rvi.run_name = "test_gr4jcn_routing"
        model.config.rvi.routing = "ROUTE_DIFFUSIVE_WAVE"

        #########
        # R V H #
        #########

        # Here we assume that we have two subbasins. The first one (subbasin_id=10)
        # has a lake (hru_id=2; area-100km2) and the rest is covered by land (hru_id=1;
        # area=4250.6km2). The second subbasin (subbasin_id=20) does not contain a
        # lake and is hence only land (hru_id=3; area=2000km2).
        #
        # Later the routing product will tell us which basin flows into which. Here
        # we assume that the first subbasin (subbasin_id=10) drains into the second
        # (subbasin_id=20). At the outlet of this second one we have an observation
        # station (see :ObservationData in RVT). We will compare these observations
        # with the simulated streamflow. That is the reason why "gauged=True" for
        # the second basin.

        # HRU IDs are 1 to 3
        model.config.rvh.hrus = (
            GR4JCN.LandHRU(hru_id=1, subbasin_id=10, **salmon_land_hru_1),
            GR4JCN.LakeHRU(hru_id=2, subbasin_id=10, **salmon_lake_hru_1),
            GR4JCN.LandHRU(hru_id=3, subbasin_id=20, **salmon_land_hru_2),
        )

        # Sub-basin IDs are 10 and 20 (not 1 and 2), to help disambiguate
        model.config.rvh.subbasins = (
            # gauged = False:
            # Usually this output would only be written for user's convenience.
            # There is usually no observation of streamflow available within
            # catchments; only at the outlet. That's most commonly the reason
            # why a catchment is defined as it is defined.
            Sub(
                name="upstream",
                subbasin_id=10,
                downstream_id=20,
                profile="chn_10",
                gauged=False,
            ),
            # gauged = True:
            # Since this is the outlet, this would usually be what we calibrate
            # against (i.e. we try to match this to Qobs).
            Sub(
                name="downstream",
                subbasin_id=20,
                downstream_id=-1,
                profile="chn_20",
                gauged=True,
            ),
        )

        model.config.rvh.land_subbasin_property_multiplier = (
            SBGroupPropertyMultiplierCommand("Land", "MANNINGS_N", 1.0))
        model.config.rvh.lake_subbasin_property_multiplier = (
            SBGroupPropertyMultiplierCommand("Lakes", "RESERVOIR_CREST_WIDTH",
                                             1.0))

        #########
        # R V T #
        #########

        gws = GridWeightsCommand(
            number_hrus=3,
            number_grid_cells=1,
            # Here we have a special case: station is 0 for every row because the example NC
            # has only one region/station (which is column 0)
            data=((1, 0, 1.0), (2, 0, 1.0), (3, 0, 1.0)),
        )
        # These will be shared (inline) to all the StationForcing commands in the RVT
        model.config.rvt.grid_weights = gws

        #########
        # R V P #
        #########

        model.config.rvp.params = model.Params(0.529, -3.396, 407.29, 1.072,
                                               16.9, 0.947)

        total_area_in_km2 = sum(hru.area for hru in model.config.rvh.hrus)
        total_area_in_m2 = total_area_in_km2 * 1000 * 1000
        model.config.rvp.avg_annual_runoff = get_average_annual_runoff(
            ts_2d, total_area_in_m2)

        np.testing.assert_almost_equal(model.config.rvp.avg_annual_runoff,
                                       139.5407534171111)

        # These channel profiles describe the geometry of the actual river crossection.
        # The eight points (x) to describe the following geometry are given in each
        # profile:
        #
        # ----x                                     x---
        #      \           FLOODPLAIN             /
        #       x----x                     x----x
        #             \                  /
        #               \   RIVERBED   /
        #                 x----------x
        #
        model.config.rvp.channel_profiles = [
            ChannelProfileCommand(
                name="chn_10",
                bed_slope=7.62066e-05,
                survey_points=[
                    (0, 463.647),
                    (16.0, 459.647),
                    (90.9828, 459.647),
                    (92.9828, 458.647),
                    (126.4742, 458.647),
                    (128.4742, 459.647),
                    (203.457, 459.647),
                    (219.457, 463.647),
                ],
                roughness_zones=[
                    (0, 0.0909167),
                    (90.9828, 0.035),
                    (128.4742, 0.0909167),
                ],
            ),
            ChannelProfileCommand(
                name="chn_20",
                bed_slope=9.95895e-05,
                survey_points=[
                    (0, 450.657),
                    (16.0, 446.657),
                    (85.0166, 446.657),
                    (87.0166, 445.657),
                    (117.5249, 445.657),
                    (119.5249, 446.657),
                    (188.54149999999998, 446.657),
                    (204.54149999999998, 450.657),
                ],
                roughness_zones=[
                    (0, 0.0915769),
                    (85.0166, 0.035),
                    (119.5249, 0.0915769),
                ],
            ),
        ]

        #############
        # Run model #
        #############

        model(ts_2d)

        ###########
        # Verify  #
        ###########

        hds = model.q_sim

        assert len(hds.nbasins) == 1  # number of "gauged" basins is 1

        # We only have one SB with gauged=True, so the output has a single column.
        # The number of time steps simulated between (2000, 1, 1) and
        # (2002, 1, 1) is 732.
        assert hds.shape == (732, 1)

        # Check simulated streamflow at first three timesteps and three simulated
        # timesteps in the middle of the simulation period.
        dates = (
            "2000-01-01",
            "2000-01-02",
            "2000-01-03",
            "2001-01-30",
            "2001-01-31",
            "2001-02-01",
        )

        target_q_sim = [
            0.0, 0.304073, 0.980807, 17.54049, 17.409493, 17.437954
        ]

        for t in range(6):
            np.testing.assert_almost_equal(hds.sel(nbasins=0, time=dates[t]),
                                           target_q_sim[t], 4)

        # For lumped GR4J model we have 1 subbasin and 1 HRU as well as no routing, no
        # channel profiles, and the area of the entire basin is 4250.6 [km2]. Comparison
        # of simulated and observed streamflow at outlet yielded:
        # np.testing.assert_almost_equal(d["DIAG_NASH_SUTCLIFFE"], -0.116971, 4)
        #
        # This is now a different value due to:
        # - basin we have here is larger (4250.6 [km2] + 100 [km2] + 2000.0 [km2])
        # - we do routing: so water from subbasin 1 needs some time to arrive at the
        #   outlet of subbasin 2
        d = model.diagnostics
        np.testing.assert_almost_equal(d["DIAG_NASH_SUTCLIFFE"], -0.0141168, 4)