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
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)
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, )
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)
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")
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)
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")
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, ), )
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"
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"
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"
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"
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"
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"
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"
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)
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"
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")})
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"
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"
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