def test_metric_conversion(metric_name, species, conversion): base_str_formats = ["{}", "kg {} / yr", "kg {}", "{} / yr"] for base_str_format in base_str_formats: base = unit_registry(base_str_format.format(species)) dest = unit_registry(base_str_format.format("CO2")) with unit_registry.context(metric_name): np.testing.assert_allclose(base.to(dest).magnitude, conversion) np.testing.assert_allclose(dest.to(base).magnitude, 1 / conversion)
def test_context(): CO2 = unit_registry("CO2") N2ON = unit_registry("N2ON") N2O = unit_registry("N2O") with unit_registry.context("AR4GWP100"): np.testing.assert_allclose(CO2.to("N2ON").magnitude, 28 / (44 * 298)) np.testing.assert_allclose(N2ON.to("CO2").magnitude, 44 * 298 / 28) np.testing.assert_allclose(CO2.to("N2O").magnitude, 1 / 298) np.testing.assert_allclose(N2O.to("CO2").magnitude, 298)
def test_methane(): CH4 = unit_registry("CH4") with pytest.raises(DimensionalityError): CH4.to("C") C = unit_registry("C") with unit_registry.context("CH4_conversions"): np.testing.assert_allclose(CH4.to("C").magnitude, 12 / 16) np.testing.assert_allclose(C.to("CH4").magnitude, 16 / 12) # this also becomes allowed, unfortunately... np.testing.assert_allclose(CH4.to("CO2").magnitude, 44 / 16)
def test_pint_array_comparison(): a = np.array([0, 2]) * unit_registry("GtC") b = np.array([0, 2]) * unit_registry("MtC") # no error but does raise warning about stripping units with warnings.catch_warnings(record=True): np.testing.assert_allclose(a, b) # actually gives an error as we want with pytest.raises(AssertionError): assert_pint_equal(a, b)
def test_assert_same_unit(unit_1, unit_2, error, unit_1_type, unit_2_type): if unit_1_type == "pint_unit": unit_1 = unit_registry(unit_1) if unit_2_type == "pint_unit": unit_2 = unit_registry(unit_2) if error: with pytest.raises(AssertionError): assert_same_unit(unit_1, unit_2) else: assert_same_unit(unit_1, unit_2)
def test_nox(): NOx = unit_registry("NOx") # can only convert to N with right context with pytest.raises(DimensionalityError): NOx.to("N") N = unit_registry("N") NO2 = unit_registry("NO2") with unit_registry.context("NOx_conversions"): np.testing.assert_allclose(NOx.to("N").magnitude, 14 / 46) np.testing.assert_allclose(N.to("NOx").magnitude, 46 / 14) np.testing.assert_allclose(NO2.to("NOx").magnitude, 1) np.testing.assert_allclose(NOx.to("NO2").magnitude, 1) with pytest.raises(DimensionalityError): NOx.to("N2O")
def convert_lambda_to_ecs(lambda_val, f2x=3.74 * unit_registry("W/m^2")): """ Convert a lambda value to equilibrium climate sensitivity (ECS) Parameters ---------- lambda_val : :obj:`pint.Quantity` Value of lambda to convert to ECS f2x : :obj:`pint.Quantity` Value of the forcing due to a doubling of atmospheric |CO2| to assume during the conversion Returns ------- :obj:`pint.Quantity` ECS value Raises ------ TypeError ``lambda_val`` or ``f2x`` is not a :obj:`pint.Quantity`. """ if not isinstance(lambda_val, pint.Quantity): raise TypeError("lambda_val is not a pint.Quantity") if not isinstance(f2x, pint.Quantity): raise TypeError("f2x is not a pint.Quantity") return -f2x / lambda_val
def test_mixtures_constituents_no_gwp(metric_name, mixture, conversion): error_msg = re.escape( f"Cannot convert from '{mixture}' ([{mixture}]) to 'CO2' ([carbon])" ) with pytest.raises(DimensionalityError, match=error_msg): gwp = ( # noqa: F841 (1 * unit_registry(mixture)).to("CO2", metric_name).magnitude )
def test_split_invalid(): with pytest.raises(ValueError, match="Dimensions don't contain a gas mixture."): unit_registry.split_gas_mixture(1 * unit_registry("CO2")) with pytest.raises( NotImplementedError, match="More than one gas mixture in dimensions is not supported.", ): unit_registry.split_gas_mixture( 1 * unit_registry("CFC400") * unit_registry("HFC423a") ) with pytest.raises( NotImplementedError, match="Mixture has dimensionality 2 != 1, which is not supported.", ): unit_registry.split_gas_mixture(1 * unit_registry("CFC400") ** 2)
def test_convert_lambda_to_ecs_with_units(ecs, f2x, test_units): ecs = ecs * unit_registry("delta_degC") f2x = f2x * unit_registry("W/m^2") default_f2x = 3.74 * unit_registry("W/m^2") call_kwargs = {} if f2x is None: f2x_expected = default_f2x else: f2x_expected = f2x call_kwargs["f2x"] = f2x in_lambda = -f2x_expected / ecs npt.assert_allclose( convert_lambda_to_ecs(in_lambda, **call_kwargs).to(test_units).magnitude, ecs.to(test_units).magnitude, )
def test_ammonia(): NH3 = unit_registry("NH3") # can only convert to N with right context with pytest.raises(DimensionalityError): NH3.to("N") # can not convert to CO2 even in GWP contexts with pytest.raises(DimensionalityError): with unit_registry.context("AR5GWP100"): NH3.to("CO2") N = unit_registry("N") with unit_registry.context("NH3_conversions"): np.testing.assert_allclose(NH3.to("N").magnitude, 14 / 17) np.testing.assert_allclose(N.to("NH3").magnitude, 17 / 14) # can not convert to CO2 even in NH3 context with pytest.raises(DimensionalityError): NH3.to("CO2")
def test_nitrogen(): N = unit_registry("N") np.testing.assert_allclose(N.to("NO2").magnitude, 46 / 14) # can only convert to N with right context with pytest.raises(DimensionalityError): N.to("N2ON") with unit_registry.context("N2O_conversions"): np.testing.assert_allclose(N.to("N2ON").magnitude, 28 / 14) np.testing.assert_allclose(N.to("N2O").magnitude, 44 / 14)
def calc_dGSAT(var, ds, ds_out, scenario='scenario'): s_y = int(ds.isel(year=0)['year'].values) _erf_tmp = ds['ERF'].sel(variable=var).to_pandas() unit = "W/m^2" driver = ScmRun( data=_erf_tmp, index=s_y + np.arange(len(_erf_tmp)), columns={ "unit": unit, "model": "custom", "scenario": scenario, "region": "World", "variable": "Effective Radiative Forcing", }, ) impulse_res = ImpulseResponseModel( d1=d1 * unit_registry("yr"), d2=d2 * unit_registry("yr"), q1=q1 * unit_registry("delta_degC / (W / m^2)"), q2=q2 * unit_registry("delta_degC / (W / m^2)"), efficacy=eff * unit_registry("dimensionless"), ) dt_tmp = impulse_res.run_scenarios(driver) df_tmp = dt_tmp.filter( variable='Surface Temperature').timeseries() #.lineplot()#['Surface'] #_ds_dT[var] =df_tmp.transpose() #ds_out[var] = df_tmp = df_tmp.reset_index().iloc[:, 12:].transpose().rename( {0: var}, axis=1) #.to_xarray() year_index = pd.to_datetime(df_tmp.index).year df_tmp['year'] = year_index df_tmp = df_tmp.set_index('year') ds_out[var] = df_tmp.to_xarray()[var] return ds_out
def linear_regression(self): """ Calculate linear regression of each timeseries Note ---- Times in seconds since 1970-01-01 are used as the x-axis for the regressions. Such values can be accessed with ``self.time_points.values.astype("datetime64[s]").astype("int")``. This decision does not matter for the gradients, but is important for the intercept values. Returns ------- list of dict[str : Any] List of dictionaries. Each dictionary contains the metadata for the timeseries plus the gradient (with key ``"gradient"``) and intercept ( with key ``"intercept"``). The gradient and intercept are stored as :class:`pint.Quantity`. """ _, _, time_unit, gradients, intercepts, meta = _calculate_linear_regression( self) out = [] for row_meta, gradient, intercept in zip( meta.to_dict("records"), gradients, intercepts, ): unit = row_meta.pop("unit") row_meta["gradient"] = gradient * unit_registry("{} / {}".format( unit, time_unit)) row_meta["intercept"] = intercept * unit_registry(unit) out.append(row_meta) return out
def test_scalar_multiply_pint_by_run(): scalar = 1 * unit_registry("MtC / yr") start = get_multiple_ts(variable="Emissions|CO2", unit="GtC / yr", scenario=["scen_a", "scen_b"]) exp_ts = perform_pint_op(start, scalar, "multiply_inverse") exp = ScmRun(exp_ts) exp["unit"] = "megatC * gigatC / a**2" res = scalar * start assert_scmdf_almost_equal(res, exp, allow_unordered=True, check_ts_names=False)
def get_ecs_from_diagnosis_results(results_ecs_run): global_co2_concs = results_ecs_run.filter( variable='Atmospheric Concentrations|CO2', region='World') ecs_time, ecs_start_time = get_ecs_ecs_start_yr_from_CO2_concs( global_co2_concs) global_total_rf = results_ecs_run.filter(variable='Radiative Forcing', region='World') global_temp = results_ecs_run.filter(variable='Surface Temperature', region='World') ecs = float(global_temp.filter(time=ecs_time).values.squeeze()) ecs = abs(ecs) unit = global_temp.get_unique_meta('unit', no_duplicates=True) ecs = ecs * unit_registry(unit) return ecs
def test_vector_ops_pint_wrong_unit(op, start_unit): vector = np.arange(3) * unit_registry("Mt CH4 / yr") start = get_multiple_ts(variable="Emissions|Gas", unit=start_unit, scenario=["scen_a", "scen_b"]) error_msg = re.escape( "Cannot convert from 'gigatC / a' ([carbon] * [mass] / [time]) " "to 'CH4 * megametric_ton / a' ([mass] * [methane] / [time])") with pytest.raises(DimensionalityError, match=error_msg): if op == "add": start + vector elif op == "subtract": start - vector else: raise NotImplementedError(op)
def test_vector_ops_pint(op): vector = np.arange(3) * unit_registry("MtC / yr") start = get_multiple_ts(variable="Emissions|CO2", unit="GtC / yr", scenario=["scen_a", "scen_b"]) exp_ts = perform_pint_op(start, vector, op) exp = ScmRun(exp_ts) if op in ["add", "subtract"]: exp["unit"] = "gigatC / a" elif op == "multiply": exp["unit"] = "gigatC * megatC / a ** 2" elif op == "divide": exp["unit"] = "gigatC / megatC" if op == "add": res = start + vector elif op == "subtract": res = start - vector elif op == "divide": res = start / vector elif op == "multiply": res = start * vector else: raise NotImplementedError(op) assert_scmdf_almost_equal(res, exp, allow_unordered=True, check_ts_names=False)
def test_mixture_conversion(metric_name, mixture, conversion): gwp = (1 * unit_registry(mixture)).to("CO2", metric_name).magnitude # wikipedia values are rounded, therefore atol np.testing.assert_allclose(conversion, gwp, atol=0.5)
def delta_per_delta_time(self, out_var=None): """ Calculate change in timeseries values for each timestep, divided by the size of the timestep The output is placed on the middle of each timestep and is one timestep shorter than the input. Parameters ---------- out_var : str If provided, the variable column of the output is set equal to ``out_var``. Otherwise, the output variables are equal to the input variables, prefixed with "Delta " . Returns ------- :class:`scmdata.ScmRun <scmdata.run.ScmRun>` :class:`scmdata.ScmRun <scmdata.run.ScmRun>` containing the changes in values of ``self``, normalised by the change in time Warns ----- UserWarning The data contains nans. If this happens, the output data will also contain nans. """ time_unit = "s" times_numpy = self.time_points.values.astype( "datetime64[{}]".format(time_unit)) times_deltas_numpy = times_numpy[1:] - times_numpy[:-1] times_in_s = times_numpy.astype("int") time_deltas_in_s = times_in_s[1:] - times_in_s[:-1] ts = self.timeseries() if ts.isnull().sum().sum() > 0: warnings.warn( "You are calculating deltas of data which contains nans so your " "result will also contain nans. Perhaps you want to remove the " "nans before calculating the deltas using a combination of " ":meth:`filter` and :meth:`interpolate`?") out = ts.diff(periods=1, axis="columns") if not out.iloc[:, 0].isnull().all(): # pragma: no cover raise AssertionError( "Did pandas change their API? The first timestep is not all nan.") out = out.iloc[:, 1:] / time_deltas_in_s new_times = times_numpy[:-1] + times_deltas_numpy / 2 out.columns = TimePoints(new_times).to_index() out = type(self)(out) out /= unit_registry(time_unit) try: out_unit = out.get_unique_meta("unit", no_duplicates=True).replace(" ", "") out_unit = str(unit_registry(out_unit).to_reduced_units().units) out = out.convert_unit(out_unit) except ValueError: # more than one unit, don't try to clean up pass if out_var is None: out["variable"] = "Delta " + out["variable"] else: out["variable"] = out_var return out
def integrate(self, out_var=None): """ Integrate with respect to time Parameters ---------- out_var : str If provided, the variable column of the output is set equal to ``out_var``. Otherwise, the output variables are equal to the input variables, prefixed with "Cumulative " . Returns ------- :class:`scmdata.ScmRun <scmdata.run.ScmRun>` :class:`scmdata.ScmRun <scmdata.run.ScmRun>` containing the integral of ``self`` with respect to time Warns ----- UserWarning The data being integrated contains nans. If this happens, the output data will also contain nans. """ if not has_scipy: raise ImportError("scipy is not installed. Run 'pip install scipy'") time_unit = "s" times_in_s = self.time_points.values.astype( "datetime64[{}]".format(time_unit)).astype("int") ts = self.timeseries() if ts.isnull().sum().sum() > 0: warnings.warn( "You are integrating data which contains nans so your result will " "also contain nans. Perhaps you want to remove the nans before " "performing the integration using a combination of :meth:`filter` " "and :meth:`interpolate`?") # If required, we can remove the hard-coding of initial, it just requires # some thinking about unit handling _initial = 0.0 out = pd.DataFrame( scipy.integrate.cumtrapz(y=ts, x=times_in_s, axis=1, initial=_initial)) out.index = ts.index out.columns = ts.columns out = type(self)(out) out *= unit_registry(time_unit) try: out_unit = out.get_unique_meta("unit", no_duplicates=True).replace(" ", "") out_unit = str(unit_registry(out_unit).to_reduced_units().units) out = out.convert_unit(out_unit) except ValueError: # more than one unit, don't try to clean up pass if out_var is None: out["variable"] = "Cumulative " + out["variable"] else: out["variable"] = out_var return out
def test_short_definition(): tC = unit_registry("tC") np.testing.assert_allclose(tC.to("tCO2").magnitude, 44 / 12) np.testing.assert_allclose(tC.to("gC").magnitude, 10 ** 6)
def test_ppt(): ppt = unit_registry("ppt") np.testing.assert_allclose(ppt.to("ppb").magnitude, 1 / 1000)
def test_ppm(): ppm = unit_registry("ppm") np.testing.assert_allclose(ppm.to("ppb").magnitude, 1000)
def test_alias(): CO2 = unit_registry("carbon_dioxide") np.testing.assert_allclose(CO2.to("C").magnitude, 12 / 44)
def test_a(): a = unit_registry("a") np.testing.assert_allclose(a.to("day").magnitude, 365.25, rtol=1e-4)
def test_context_compound_unit(): CO2 = 1 * unit_registry("kg CO2 / yr") N2ON = 1 * unit_registry("kg N2ON / yr") with unit_registry.context("AR4GWP100"): np.testing.assert_allclose(CO2.to("kg N2ON / yr").magnitude, 28 / (44 * 298)) np.testing.assert_allclose(N2ON.to("kg CO2 / yr").magnitude, 44 * 298 / 28)
def test_context_dimensionality_error(): CO2 = unit_registry("CO2") with pytest.raises(DimensionalityError): CO2.to("N2O")
def test_mixture_constituent_sum_one(): for mixture in MIXTURES: constituents = unit_registry.split_gas_mixture(1 * unit_registry(mixture)) np.testing.assert_allclose(sum((c.magnitude for c in constituents)), 1)
def test_base_unit(): assert unit_registry("carbon") == unit_registry("C")