def test_run_scenario_timestep_followed(self, check_equal_pint):
        inp = self.tinp.copy()

        model = self.tmodel()

        res = model.run_scenarios(inp)
        check_equal_pint(model.delta_t, 1 * ur("yr"))

        inp_monthly = inp.resample("MS")
        res_monthly = model.run_scenarios(inp_monthly)
        check_equal_pint(model.delta_t, 1 * ur("month"))

        comp_filter = {
            "variable": "Surface Temperature",
            "year": int(res["year"].iloc[-1]
                        ),  # scmdata bug that you have to wrap this with int()
            "month": 1,
        }

        # running with two different timesteps should give approximately same results
        npt.assert_allclose(
            res.filter(**comp_filter).values.squeeze(),
            res_monthly.filter(**comp_filter).values.squeeze(),
            rtol=6 * 1e-3,
        )
        res.filter(variable="Surface Temperature")
    def _calculate_next_rndt(self, t1, t2, erf, efficacy):
        two_layer_paras = self.get_two_layer_parameters()
        lambda0 = two_layer_paras["lambda0"]

        if np.equal(efficacy, 1):
            efficacy_term = 0 * ur(self._erf_unit)
        else:
            gh = _calculate_geoffroy_helper_parameters(
                two_layer_paras["du"],
                two_layer_paras["dl"],
                two_layer_paras["lambda0"],
                two_layer_paras["efficacy"],
                two_layer_paras["eta"],
            )

            t1_h = t1 * ur(self._temp1_unit)
            t2_h = t2 * ur(self._temp2_unit)
            efficacy_term = (two_layer_paras["eta"] * (efficacy - 1) *
                             ((1 - gh["phi1"]) * t1_h +
                              (1 - gh["phi2"]) * t2_h))

            if str(efficacy_term.units) != "watt / meter ** 2":
                raise AssertionError("units should have come out as W/m^2")

        out = erf - lambda0.magnitude * (t1 + t2) - efficacy_term.magnitude

        return out
Esempio n. 3
0
    def test_calculate_next_temp_lower(self, check_same_unit):
        tdelta_t = 30 * 24 * 60 * 60
        ttemp_upper = 0.1
        ttemp_lower = 0.2
        teta = 0.78
        theat_capacity_lower = 10**8

        res = self.tmodel._calculate_next_temp_lower(tdelta_t, ttemp_lower,
                                                     ttemp_upper, teta,
                                                     theat_capacity_lower)

        expected = ttemp_lower + (tdelta_t * teta *
                                  (ttemp_upper - ttemp_lower) /
                                  theat_capacity_lower)

        npt.assert_equal(res, expected)

        # check internal units make sense
        check_same_unit(
            self.tmodel._temp_upper_unit,
            self.tmodel._temp_lower_unit,
        )
        check_same_unit(
            self.tmodel._temp_lower_unit,
            (1.0 * ur(self.tmodel._delta_t_unit) * 1.0 *
             ur(self.tmodel._eta_unit) * 1.0 *
             ur(self.tmodel._temp_upper_unit) /
             (1.0 * ur(self.tmodel._heat_capacity_upper_unit))).units,
        )
    def test_init_backwards_timescales_error(self):
        init_kwargs = dict(
            d1=250.0 * ur("yr"),
            d2=3 * ur("yr"),
        )

        error_msg = "The short-timescale must be d1"
        with pytest.raises(ValueError, match=error_msg):
            self.tmodel(**init_kwargs)
Esempio n. 5
0
    def test_calculate_next_temp_upper(self, check_same_unit):
        tdelta_t = 30 * 24 * 60 * 60
        ttemp_upper = 0.1
        ttemp_lower = 0.2
        terf = 1.1
        tlambda0 = 3.7 / 3
        ta = 0.02
        tefficacy = 0.9
        teta = 0.78
        theat_capacity_upper = 10**10

        res = self.tmodel._calculate_next_temp_upper(
            tdelta_t,
            ttemp_upper,
            ttemp_lower,
            terf,
            tlambda0,
            ta,
            tefficacy,
            teta,
            theat_capacity_upper,
        )

        expected = (ttemp_upper + tdelta_t *
                    (terf - (tlambda0 - ta * ttemp_upper) * ttemp_upper -
                     (tefficacy * teta *
                      (ttemp_upper - ttemp_lower))) / theat_capacity_upper)

        npt.assert_equal(res, expected)

        # check internal units make sense
        check_same_unit(
            self.tmodel._lambda0_unit,
            (1.0 * ur(self.tmodel._a_unit) * 1.0 *
             ur(self.tmodel._temp_upper_unit)).units,
        )

        check_same_unit(
            self.tmodel._erf_unit,
            (1.0 * ur(self.tmodel._lambda0_unit) * 1.0 *
             ur(self.tmodel._temp_upper_unit)).units,
        )

        check_same_unit(
            self.tmodel._erf_unit,
            (1.0 * ur(self.tmodel._efficacy_unit) * 1.0 *
             ur(self.tmodel._eta_unit) * 1.0 *
             ur(self.tmodel._temp_upper_unit)).units,
        )

        check_same_unit(
            self.tmodel._temp_upper_unit,
            (1.0 * ur(self.tmodel._delta_t_unit) * 1.0 *
             ur(self.tmodel._erf_unit) /
             (1.0 * ur(self.tmodel._heat_capacity_upper_unit))).units,
        )
Esempio n. 6
0
def test_calculate_geoffroy_helper_parameters(check_equal_pint):
    tdu = 35 * ur("m")
    tdl = 3200 * ur("m")
    tlambda0 = 4 / 3 * ur("W/m^2/delta_degC")
    tefficacy = 1.1 * ur("dimensionless")
    teta = 0.7 * ur("W/m^2/delta_degC")

    # for explanation of what is going on, see
    # impulse-response-equivalence.ipynb
    C = DENSITY_WATER * HEAT_CAPACITY_WATER * tdu
    C_D = DENSITY_WATER * HEAT_CAPACITY_WATER * tdl

    b = (tlambda0 + tefficacy * teta) / C + teta / C_D
    b_star = (tlambda0 + tefficacy * teta) / C - teta / C_D
    delta = b**2 - 4 * tlambda0 * teta / (C * C_D)

    tau1 = C * C_D / (2 * tlambda0 * teta) * (b - delta**0.5)
    tau2 = C * C_D / (2 * tlambda0 * teta) * (b + delta**0.5)
    phi1 = C / (2 * tefficacy * teta) * (b_star - delta**0.5)
    phi2 = C / (2 * tefficacy * teta) * (b_star + delta**0.5)

    a1 = phi2 * tau1 * tlambda0 / (C * (phi2 - phi1))
    a2 = -phi1 * tau2 * tlambda0 / (C * (phi2 - phi1))

    expected = {
        "C": C,
        "C_D": C_D,
        "b": b,
        "b_star": b_star,
        "delta": delta,
        "tau1": tau1,
        "tau2": tau2,
        "phi1": phi1,
        "phi2": phi2,
        "a1": a1,
        "a2": a2,
    }

    # check relationships hold
    check_equal_pint(a1 + a2, 1 * ur("dimensionless"))
    check_equal_pint(a1 / tau1 + a2 / tau2, tlambda0 / C)
    check_equal_pint(a1 * tau1 + a2 * tau2, (C + tefficacy * C_D) / tlambda0)
    check_equal_pint(a1 * tau2 + a2 * tau1, C_D / teta)
    check_equal_pint(phi1 * a1 / tau1 + phi2 * a2 / tau2,
                     0 * ur("1/s"),
                     atol=1e-10)
    check_equal_pint(tau1 * tau2, (C * C_D) / (tlambda0 * teta))
    check_equal_pint(C + phi1 * tefficacy * C_D, tlambda0 * tau1)
    check_equal_pint(C + phi2 * tefficacy * C_D, tlambda0 * tau2)
    check_equal_pint(phi1 * a1 + phi2 * a2, 1 * ur("dimensionless"))
    check_equal_pint(phi1 * phi2, -C / (tefficacy * C_D))

    res = _calculate_geoffroy_helper_parameters(du=tdu,
                                                dl=tdl,
                                                lambda0=tlambda0,
                                                efficacy=tefficacy,
                                                eta=teta)

    assert res == expected
    def test_get_two_layer_model_parameters(self, check_equal_pint):
        tq1 = 0.3 * ur("delta_degC/(W/m^2)")
        tq2 = 0.4 * ur("delta_degC/(W/m^2)")
        td1 = 3 * ur("yr")
        td2 = 300.0 * ur("yr")
        tefficacy = 1.2 * ur("dimensionless")

        start_paras = dict(
            d1=td1,
            d2=td2,
            q1=tq1,
            q2=tq2,
            efficacy=tefficacy,
        )

        mod_instance = self.tmodel(**start_paras)

        # for explanation of what is going on, see
        # impulse-response-equivalence.ipynb
        efficacy = tefficacy
        lambda0 = 1 / (tq1 + tq2)
        C = (td1 * td2) / (tq1 * td2 + tq2 * td1)

        a1 = lambda0 * tq1
        a2 = lambda0 * tq2
        tau1 = td1
        tau2 = td2

        C_D = (lambda0 * (tau1 * a1 + tau2 * a2) - C) / efficacy
        eta = C_D / (tau1 * a2 + tau2 * a1)

        expected = {
            "lambda0": lambda0,
            "du": C / (DENSITY_WATER * HEAT_CAPACITY_WATER),
            "dl": C_D / (DENSITY_WATER * HEAT_CAPACITY_WATER),
            "eta": eta,
            "efficacy": efficacy,
        }

        res = mod_instance.get_two_layer_parameters()

        assert res == expected

        # check circularity
        circular_params = TwoLayerModel(
            **res).get_impulse_response_parameters()
        for k, v in circular_params.items():
            check_equal_pint(v, start_paras[k])
    def test_set_erf(self, check_equal_pint):
        terf = np.array([0, 1, 2]) * ur("W/m^2")

        res = self.tmodel()
        res.erf = terf

        check_equal_pint(res.erf, terf)
Esempio n. 9
0
    def test_reset_run_reset(self):
        # move to integration tests
        terf = np.array([0, 1, 2, 3, 4, 5]) * ur("W/m^2")

        model = self.tmodel()
        model.set_drivers(terf)

        def assert_is_nan_and_erf_shape(inp):
            assert np.isnan(inp).all()
            assert inp.shape == terf.shape

        model.reset()
        assert_is_nan_and_erf_shape(model._temp_upper_mag)
        assert_is_nan_and_erf_shape(model._temp_lower_mag)
        assert_is_nan_and_erf_shape(model._rndt_mag)

        def assert_ge_zero_and_erf_shape(inp):
            assert not (inp < 0).any()
            assert inp.shape == terf.shape

        model.run()
        assert_ge_zero_and_erf_shape(model._temp_upper_mag)
        assert_ge_zero_and_erf_shape(model._temp_lower_mag)
        assert_ge_zero_and_erf_shape(model._rndt_mag)

        model.reset()
        assert_is_nan_and_erf_shape(model._temp_upper_mag)
        assert_is_nan_and_erf_shape(model._temp_lower_mag)
        assert_is_nan_and_erf_shape(model._rndt_mag)
    def test_run_scenarios_single(self):
        inp = self.tinp.copy()

        model = self.tmodel()

        res = model.run_scenarios(inp)

        model.set_drivers(inp.values.squeeze() *
                          ur(inp.get_unique_meta("unit", no_duplicates=True)))
        model.reset()
        model.run()

        npt.assert_allclose(
            res.filter(variable="Surface Temperature|Upper").values.squeeze(),
            model._temp_upper_mag,
        )
        assert (res.filter(
            variable="Surface Temperature|Upper").get_unique_meta(
                "unit", no_duplicates=True) == "delta_degC")

        npt.assert_allclose(
            res.filter(variable="Surface Temperature|Lower").values.squeeze(),
            model._temp_lower_mag,
        )
        assert (res.filter(
            variable="Surface Temperature|Lower").get_unique_meta(
                "unit", no_duplicates=True) == "delta_degC")

        npt.assert_allclose(
            res.filter(variable="Heat Uptake").values.squeeze(),
            model._rndt_mag)
        assert (res.filter(variable="Heat Uptake").get_unique_meta(
            "unit", no_duplicates=True) == "W/m^2")
Esempio n. 11
0
    def _binary_op(
        self,
        other,
        f: Callable[..., Any],
        reflexive=False,
        **kwargs,
    ) -> Callable[..., "TimeSeries"]:
        other_data = getattr(other, "_data", other)

        if isinstance(other, pint.Quantity):
            try:
                self_data = self._data * ur(self.meta["unit"])
            except KeyError:
                # let Pint assume dimensionless and raise an error as
                # necessary
                self_data = self._data
        else:
            self_data = self._data

        ts = f(self_data, other_data) if not reflexive else f(
            other_data, self_data)
        ts.attrs = self._data.attrs
        if isinstance(other, pint.Quantity):
            ts.attrs["unit"] = str(ts.data.units)
            ts.data = ts.data.magnitude

        return TimeSeries(ts)
Esempio n. 12
0
    def test_get_impulse_response_parameters_non_zero_a_raises(self):
        ta = 0.1 * ur("W/m^2/delta_degC^2")

        error_msg = re.escape(
            "Cannot calculate impulse response parameters with "
            "non-zero a={}".format(ta))
        with pytest.raises(ValueError, match=error_msg):
            self.tmodel(a=ta).get_impulse_response_parameters()
    def __init__(
            self,
            du=50 * ur("m"),
            dl=1200 * ur("m"),
            lambda0=3.74 / 3 * ur("W/m^2/delta_degC"),
            a=0.0 * ur("W/m^2/delta_degC^2"),
            efficacy=1.0 * ur("dimensionless"),
            eta=0.8 * ur("W/m^2/delta_degC"),
            delta_t=ur("yr").to("s"),
    ):  # pylint: disable=too-many-arguments
        """
        Initialise
        """
        self.du = du
        self.dl = dl
        self.lambda0 = lambda0
        self.a = a
        self.efficacy = efficacy
        self.eta = eta
        self.delta_t = delta_t

        self._erf = np.zeros(1) * np.nan
        self._temp_upper_mag = np.zeros(1) * np.nan
        self._temp_lower_mag = np.zeros(1) * np.nan
        self._rndt_mag = np.zeros(1) * np.nan
        self._timestep_idx = np.nan
    def test_run_scenarios_multiple(self):
        ts1_erf = np.linspace(0, 4, 101)
        ts2_erf = np.sin(np.linspace(0, 4, 101))

        inp = ScmRun(
            data=np.vstack([ts1_erf, ts2_erf]).T,
            index=np.linspace(1750, 1850, 101).astype(int),
            columns={
                "scenario": ["test_scenario_1", "test_scenario_2"],
                "model": "unspecified",
                "climate_model": "junk input",
                "variable": "Effective Radiative Forcing",
                "unit": "W/m^2",
                "region": "World",
            },
        )

        model = self.tmodel()

        res = model.run_scenarios(inp)

        for scenario_ts in inp.groupby("scenario"):
            scenario = scenario_ts.get_unique_meta("scenario",
                                                   no_duplicates=True)

            model.set_drivers(
                scenario_ts.values.squeeze() *
                ur(inp.get_unique_meta("unit", no_duplicates=True)))
            model.reset()
            model.run()

            res_scen = res.filter(scenario=scenario)

            npt.assert_allclose(
                res_scen.filter(
                    variable="Surface Temperature|Upper").values.squeeze(),
                model._temp_upper_mag,
            )
            assert (res.filter(
                variable="Surface Temperature|Upper").get_unique_meta(
                    "unit", no_duplicates=True) == "delta_degC")

            npt.assert_allclose(
                res_scen.filter(
                    variable="Surface Temperature|Lower").values.squeeze(),
                model._temp_lower_mag,
            )
            assert (res.filter(
                variable="Surface Temperature|Lower").get_unique_meta(
                    "unit", no_duplicates=True) == "delta_degC")

            npt.assert_allclose(
                res_scen.filter(variable="Heat Uptake").values.squeeze(),
                model._rndt_mag,
            )
            assert (res.filter(variable="Heat Uptake").get_unique_meta(
                "unit", no_duplicates=True) == "W/m^2")
Esempio n. 15
0
    def test_step(self):
        # move to integration tests
        terf = np.array([3, 4, 5, 6, 7]) * ur("W/m^2")

        model = self.tmodel()
        model.set_drivers(terf)
        model.reset()

        model.step()
        assert model._timestep_idx == 0
        npt.assert_equal(model._temp_upper_mag[model._timestep_idx], 0)
        npt.assert_equal(model._temp_lower_mag[model._timestep_idx], 0)
        npt.assert_equal(model._rndt_mag[model._timestep_idx], 0)

        model.step()
        model.step()
        model.step()
        assert model._timestep_idx == 3

        npt.assert_equal(
            model._temp_upper_mag[model._timestep_idx],
            model._calculate_next_temp_upper(
                model._delta_t_mag,
                model._temp_upper_mag[model._timestep_idx - 1],
                model._temp_lower_mag[model._timestep_idx - 1],
                model._erf_mag[model._timestep_idx - 1],
                model._lambda0_mag,
                model._a_mag,
                model._efficacy_mag,
                model._eta_mag,
                model._heat_capacity_upper_mag,
            ),
        )

        npt.assert_equal(
            model._temp_lower_mag[model._timestep_idx],
            model._calculate_next_temp_lower(
                model._delta_t_mag,
                model._temp_lower_mag[model._timestep_idx - 1],
                model._temp_upper_mag[model._timestep_idx - 1],
                model._eta_mag,
                model._heat_capacity_lower_mag,
            ),
        )

        npt.assert_equal(
            model._rndt_mag[model._timestep_idx],
            model._calculate_next_rndt(
                model._delta_t_mag,
                model._temp_lower_mag[model._timestep_idx],
                model._temp_lower_mag[model._timestep_idx - 1],
                model._heat_capacity_lower_mag,
                model._temp_upper_mag[model._timestep_idx],
                model._temp_upper_mag[model._timestep_idx - 1],
                model._heat_capacity_upper_mag,
            ),
        )
Esempio n. 16
0
def test_timeseries_add_pint_vector(ts_gtc_per_yr_units, inplace):
    to_add = np.arange(3) * ur("MtC / yr")
    if inplace:
        ts_gtc_per_yr_units += to_add
        ts2 = ts_gtc_per_yr_units
    else:
        ts2 = ts_gtc_per_yr_units + to_add

    npt.assert_allclose(ts2.values, [1, 2.001, 3.002])
    assert ts2.meta["unit"] == "gigatC / a"
Esempio n. 17
0
def test_timeseries_add_pint_scalar(ts_gtc_per_yr_units, inplace):
    to_add = 2 * ur("MtC / yr")
    if inplace:
        ts_gtc_per_yr_units += to_add
        ts2 = ts_gtc_per_yr_units
    else:
        ts2 = ts_gtc_per_yr_units + to_add

    npt.assert_allclose(ts2.values, [1.002, 2.002, 3.002])
    assert ts2.meta["unit"] == "gigatC / a"
Esempio n. 18
0
def test_timeseries_sub_pint_scalar(ts_gtc_per_yr_units, inplace):
    to_sub = 2 * ur("MtC / yr")
    if inplace:
        ts_gtc_per_yr_units -= to_sub
        ts2 = ts_gtc_per_yr_units
    else:
        ts2 = ts_gtc_per_yr_units - to_sub

    npt.assert_allclose(ts2.values, [0.998, 1.998, 2.998])
    assert ts2.meta["unit"] == "gigatC / a"
Esempio n. 19
0
def test_timeseries_mul_pint_scalar(ts_gtc_per_yr_units, inplace):
    to_mul = 2 * ur("yr")
    if inplace:
        ts_gtc_per_yr_units *= to_mul
        ts2 = ts_gtc_per_yr_units
    else:
        ts2 = ts_gtc_per_yr_units * to_mul

    npt.assert_allclose(ts2.values, [2, 4, 6])
    assert ts2.meta["unit"] == "gigatC"
Esempio n. 20
0
def test_timeseries_div_pint_scalar(ts_gtc_per_yr_units, inplace):
    to_div = 2 * ur("yr**-1")
    if inplace:
        ts_gtc_per_yr_units /= to_div
        ts2 = ts_gtc_per_yr_units
    else:
        ts2 = ts_gtc_per_yr_units / to_div

    npt.assert_allclose(ts2.values, [1 / 2, 2 / 2, 3 / 2])
    assert ts2.meta["unit"] == "gigatC"
Esempio n. 21
0
def test_timeseries_div_pint_vector(ts_gtc_per_yr_units, inplace):
    to_div = np.arange(3) * ur("yr**-1")
    if inplace:
        ts_gtc_per_yr_units /= to_div
        ts2 = ts_gtc_per_yr_units
    else:
        ts2 = ts_gtc_per_yr_units / to_div

    npt.assert_allclose(ts2.values, [np.inf, 2 / 1, 3 / 2])
    assert ts2.meta["unit"] == "gigatC"
Esempio n. 22
0
def test_timeseries_mul_pint_vector(ts_gtc_per_yr_units, inplace):
    to_mul = np.arange(3) * ur("yr")
    if inplace:
        ts_gtc_per_yr_units *= to_mul
        ts2 = ts_gtc_per_yr_units
    else:
        ts2 = ts_gtc_per_yr_units * to_mul

    npt.assert_allclose(ts2.values, [0, 2, 6])
    assert ts2.meta["unit"] == "gigatC"
Esempio n. 23
0
    def test_heat_capacity_lower(self, check_equal_pint):
        model = self.tmodel(dl=2.5 * ur("km"))

        expected = model.dl * DENSITY_WATER * HEAT_CAPACITY_WATER

        res = model.heat_capacity_lower

        check_equal_pint(res, expected)
        assert (model._heat_capacity_lower_mag == res.to(
            model._heat_capacity_lower_unit).magnitude)
Esempio n. 24
0
def test_timeseries_sub_pint_vector(ts_gtc_per_yr_units, inplace):
    to_sub = np.arange(3) * ur("MtC / yr")
    if inplace:
        ts_gtc_per_yr_units -= to_sub
        ts2 = ts_gtc_per_yr_units
    else:
        ts2 = ts_gtc_per_yr_units - to_sub

    npt.assert_allclose(ts2.values, [1, 1.999, 2.998])
    assert ts2.meta["unit"] == "gigatC / a"
Esempio n. 25
0
    def _select_timestep(driver):
        year_diff = driver["year"].diff().dropna()
        if (year_diff == 1).all():
            # assume yearly timesteps
            return 1 * ur("yr")

        time_diff = driver["time"].diff().dropna()
        if all(
                np.logical_and(
                    time_diff <= np.timedelta64(31, "D"),
                    time_diff >= np.timedelta64(28, "D"),
                )):
            # Assume constant monthly timesteps. This is clearly an approximation but
            # while we have constant internal timesteps it's the best we can do.
            return 1 * ur("month")

        raise NotImplementedError(
            "Could not decide on timestep for time axis: {}".format(
                driver["time"]))
 def test_init_wrong_units(self):
     """
     Test error thrown if the model is initiliased with wrong units
     for a quantity
     """
     # e.g.
     for parameter, value in self.parameters.items():
         error_msg = "{} units must be {}".format(parameter, value.units)
         with pytest.raises(TypeError, match=error_msg):
             self.tmodel(**{parameter: 34.3 * ur("kg")})
Esempio n. 27
0
def test_timeseries_mul_pint_vector_no_units(ts, inplace):
    vector = np.arange(3) * ur("GtC / yr")
    if inplace:
        ts *= vector
        ts2 = ts

    else:
        ts2 = ts * vector

    # operation works because units of base assumed to be dimensionless
    npt.assert_allclose(ts2.values, [0, 2, 6])
    assert ts2.meta["unit"] == "gigatC / a"
Esempio n. 28
0
def test_timeseries_mul_pint_scalar_no_units(ts, inplace):
    scalar = 2 * ur("GtC / yr")
    if inplace:
        ts *= scalar
        ts2 = ts

    else:
        ts2 = ts * scalar

    # operation works because units of base assumed to be dimensionless
    npt.assert_allclose(ts2.values, [2, 4, 6])
    assert ts2.meta["unit"] == "gigatC / a"
Esempio n. 29
0
def test_twolayer_plus_efficacy(
    update_expected_files,
    test_rcmip_forcings_scmrun,
    test_twolayer_output_dir,
    run_model_output_comparison,
):
    twolayer_plus_efficacy = TwoLayerModel(efficacy=1.2 * ur("dimensionless"))
    res = twolayer_plus_efficacy.run_scenarios(test_rcmip_forcings_scmrun)

    expected = os.path.join(test_twolayer_output_dir, "test_twolayer_plus_efficacy.csv")

    run_model_output_comparison(res, expected, update_expected_files)
    def __init__(
            self,
            q1=0.3 * ur("delta_degC/(W/m^2)"),
            q2=0.4 * ur("delta_degC/(W/m^2)"),
            d1=9.0 * ur("yr"),
            d2=400.0 * ur("yr"),
            efficacy=1.0 * ur("dimensionless"),
            delta_t=1 / 12 * ur("yr"),
    ):  # pylint: disable=too-many-arguments
        """
        Initialise

        Raises
        ------
        ValueError
            d1 >= d2, d1 must be the short-timescale
        """
        self.q1 = q1
        self.q2 = q2
        self.d1 = d1
        self.d2 = d2
        self.efficacy = efficacy
        self.delta_t = delta_t

        if d1 >= d2:
            raise ValueError("The short-timescale must be d1")

        self._erf = np.zeros(1) * np.nan
        self._temp1_mag = np.zeros(1) * np.nan
        self._temp2_mag = np.zeros(1) * np.nan
        self._rndt_mag = np.zeros(1) * np.nan
        self._timestep_idx = np.nan