dynamic-characterization 0.0.1.dev1__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {dynamic_characterization-0.0.1.dev1/dynamic_characterization.egg-info → dynamic_characterization-0.0.3}/PKG-INFO +14 -6
  2. {dynamic_characterization-0.0.1.dev1 → dynamic_characterization-0.0.3}/README.md +10 -5
  3. {dynamic_characterization-0.0.1.dev1 → dynamic_characterization-0.0.3}/dynamic_characterization/__init__.py +3 -1
  4. dynamic_characterization-0.0.3/dynamic_characterization/classes.py +5 -0
  5. dynamic_characterization-0.0.3/dynamic_characterization/dynamic_characterization.py +335 -0
  6. dynamic_characterization-0.0.3/dynamic_characterization/temporalis/__init__.py +15 -0
  7. dynamic_characterization-0.0.3/dynamic_characterization/temporalis/radiative_forcing.py +137 -0
  8. dynamic_characterization-0.0.3/dynamic_characterization/timex/__init__.py +23 -0
  9. dynamic_characterization-0.0.3/dynamic_characterization/timex/radiative_forcing.py +471 -0
  10. {dynamic_characterization-0.0.1.dev1 → dynamic_characterization-0.0.3/dynamic_characterization.egg-info}/PKG-INFO +14 -6
  11. dynamic_characterization-0.0.3/dynamic_characterization.egg-info/SOURCES.txt +16 -0
  12. {dynamic_characterization-0.0.1.dev1 → dynamic_characterization-0.0.3}/dynamic_characterization.egg-info/requires.txt +3 -0
  13. {dynamic_characterization-0.0.1.dev1 → dynamic_characterization-0.0.3}/pyproject.toml +8 -1
  14. dynamic_characterization-0.0.1.dev1/dynamic_characterization.egg-info/SOURCES.txt +0 -10
  15. {dynamic_characterization-0.0.1.dev1 → dynamic_characterization-0.0.3}/LICENSE +0 -0
  16. {dynamic_characterization-0.0.1.dev1 → dynamic_characterization-0.0.3}/MANIFEST.in +0 -0
  17. {dynamic_characterization-0.0.1.dev1 → dynamic_characterization-0.0.3}/dynamic_characterization.egg-info/dependency_links.txt +0 -0
  18. {dynamic_characterization-0.0.1.dev1 → dynamic_characterization-0.0.3}/dynamic_characterization.egg-info/top_level.txt +0 -0
  19. {dynamic_characterization-0.0.1.dev1 → dynamic_characterization-0.0.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dynamic_characterization
3
- Version: 0.0.1.dev1
3
+ Version: 0.0.3
4
4
  Summary: Collection of dynamic characterization functions for life cycle inventories with temporal information
5
5
  Author-email: Timo Diepers <timo.diepers@ltt.rwth-aachen.de>
6
6
  Maintainer-email: Timo Diepers <timo.diepers@ltt.rwth-aachen.de>
@@ -20,6 +20,9 @@ Classifier: Topic :: Scientific/Engineering
20
20
  Requires-Python: >=3.9
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
+ Requires-Dist: pandas
24
+ Requires-Dist: numpy
25
+ Requires-Dist: bw2data
23
26
  Provides-Extra: testing
24
27
  Requires-Dist: dynamic_characterization; extra == "testing"
25
28
  Requires-Dist: pytest; extra == "testing"
@@ -76,11 +79,12 @@ Here's an example of what such a function could look like:
76
79
 
77
80
  ```python
78
81
  def characterize_something(series, period: int = 100, cumulative=False) -> pd.DataFrame:
79
- date_beginning: np.datetime64 = series["date"].to_numpy()
80
- date_characterized: np.ndarray = date_beginning + np.arange(
82
+ date_beginning: np.datetime64 = series.date.to_numpy()
83
+ dates_characterized: np.ndarray = date_beginning + np.arange(
81
84
  start=0, stop=period, dtype="timedelta64[Y]"
82
85
  ).astype("timedelta64[s]")
83
86
 
87
+ # let's assume some simple decay function
84
88
  decay_multipliers: list = np.array(
85
89
  [
86
90
  1.234 * (1 - np.exp(-year / 56.789))
@@ -88,13 +92,14 @@ def characterize_something(series, period: int = 100, cumulative=False) -> pd.Da
88
92
  ]
89
93
  )
90
94
 
91
- forcing = pd.Series(data=series.amount * decay_multipliers, dtype="float64")
95
+ forcing = np.array(series.amount * decay_multipliers, dtype="float64")
96
+
92
97
  if not cumulative:
93
- forcing = forcing.diff(periods=1).fillna(0)
98
+ forcing = np.diff(forcing, prepend=0)
94
99
 
95
100
  return pd.DataFrame(
96
101
  {
97
- "date": pd.Series(data=date_characterized, dtype="datetime64[s]"),
102
+ "date": np.array(dates_characterized, dtype="datetime64[s]"),
98
103
  "amount": forcing,
99
104
  "flow": series.flow,
100
105
  "activity": series.activity,
@@ -131,6 +136,9 @@ _dynamic_characterization_ is free and open source software.
131
136
  If you encounter any problems,
132
137
  please [file an issue][Issue Tracker] along with a detailed description.
133
138
 
139
+ ## Support
140
+
141
+ If you have any questions or need help, do not hesitate to contact Timo Diepers ([timo.diepers@ltt.rwth-aachen.de](mailto:timo.diepers@ltt.rwth-aachen.de))
134
142
 
135
143
  <!-- github-only -->
136
144
 
@@ -40,11 +40,12 @@ Here's an example of what such a function could look like:
40
40
 
41
41
  ```python
42
42
  def characterize_something(series, period: int = 100, cumulative=False) -> pd.DataFrame:
43
- date_beginning: np.datetime64 = series["date"].to_numpy()
44
- date_characterized: np.ndarray = date_beginning + np.arange(
43
+ date_beginning: np.datetime64 = series.date.to_numpy()
44
+ dates_characterized: np.ndarray = date_beginning + np.arange(
45
45
  start=0, stop=period, dtype="timedelta64[Y]"
46
46
  ).astype("timedelta64[s]")
47
47
 
48
+ # let's assume some simple decay function
48
49
  decay_multipliers: list = np.array(
49
50
  [
50
51
  1.234 * (1 - np.exp(-year / 56.789))
@@ -52,13 +53,14 @@ def characterize_something(series, period: int = 100, cumulative=False) -> pd.Da
52
53
  ]
53
54
  )
54
55
 
55
- forcing = pd.Series(data=series.amount * decay_multipliers, dtype="float64")
56
+ forcing = np.array(series.amount * decay_multipliers, dtype="float64")
57
+
56
58
  if not cumulative:
57
- forcing = forcing.diff(periods=1).fillna(0)
59
+ forcing = np.diff(forcing, prepend=0)
58
60
 
59
61
  return pd.DataFrame(
60
62
  {
61
- "date": pd.Series(data=date_characterized, dtype="datetime64[s]"),
63
+ "date": np.array(dates_characterized, dtype="datetime64[s]"),
62
64
  "amount": forcing,
63
65
  "flow": series.flow,
64
66
  "activity": series.activity,
@@ -95,6 +97,9 @@ _dynamic_characterization_ is free and open source software.
95
97
  If you encounter any problems,
96
98
  please [file an issue][Issue Tracker] along with a detailed description.
97
99
 
100
+ ## Support
101
+
102
+ If you have any questions or need help, do not hesitate to contact Timo Diepers ([timo.diepers@ltt.rwth-aachen.de](mailto:timo.diepers@ltt.rwth-aachen.de))
98
103
 
99
104
  <!-- github-only -->
100
105
 
@@ -4,12 +4,14 @@ Collection of dynamic characterization functions for life cycle inventories with
4
4
 
5
5
  __all__ = (
6
6
  "__version__",
7
+ "dynamic_characterization.characterize_dynamic_inventory",
7
8
  "temporalis",
8
9
  "timex",
9
10
  # Add functions and variables you want exposed in `dynamic_characterization.` namespace here
10
11
  )
11
12
 
12
- __version__ = "0.0.1dev1"
13
+ __version__ = "0.0.3"
13
14
 
15
+ from .dynamic_characterization import characterize_dynamic_inventory
14
16
  from . import temporalis
15
17
  from . import timex
@@ -0,0 +1,5 @@
1
+ from collections import namedtuple
2
+
3
+ CharacterizedRow = namedtuple(
4
+ "CharacterizedRow", ["date", "amount", "flow", "activity"]
5
+ )
@@ -0,0 +1,335 @@
1
+ import json
2
+ import os
3
+ import warnings
4
+ from collections.abc import Collection
5
+ from datetime import datetime
6
+ from typing import Callable, Dict, Tuple
7
+
8
+ import bw2data as bd
9
+ import numpy as np
10
+ import pandas as pd
11
+ from bw2data.utils import UnknownObject
12
+
13
+ from dynamic_characterization.timex.radiative_forcing import (
14
+ characterize_ch4,
15
+ characterize_co,
16
+ characterize_co2,
17
+ characterize_co2_uptake,
18
+ characterize_n2o,
19
+ create_generic_characterization_function,
20
+ )
21
+
22
+
23
+ def characterize_dynamic_inventory(
24
+ dynamic_inventory_df: pd.DataFrame,
25
+ metric: str = "radiative_forcing",
26
+ characterization_function_dict: Dict[str, Callable] = None,
27
+ base_lcia_method: Tuple[str, ...] = None,
28
+ time_horizon: int = 100,
29
+ fixed_time_horizon: bool = False,
30
+ time_horizon_start: datetime = datetime.now(),
31
+ characterization_function_co2: Callable = None,
32
+ ) -> pd.DataFrame:
33
+ """
34
+ Characterizes the dynamic inventory, formatted as a Dataframe, by evaluating each emission (row in DataFrame) using given dynamic characterization functions.
35
+
36
+ Available metrics are radiative forcing [W/m2] and GWP [kg CO2eq], defaulting to `radiative_forcing`.
37
+
38
+ In case users don't provide own dynamic characterization functions, it adds dynamic characterization functions from the timex submodule
39
+ for the GHGs mentioned in the IPCC AR6 Chapter 7, if these GHG are also characterized in the selected static LCA method.
40
+
41
+ This method applies the dynamic characterization functions to each row of the dynamic_inventory_df for the duration of `time_horizon`, defaulting to 100 years.
42
+ The `fixed_time_horizon` parameter determines whether the evaluation time horizon for all emissions is calculated from the
43
+ functional unit (`fixed_time_horizon=True`), regardless of when the actual emission occurs, or from the time of the emission itself(`fixed_time_horizon=False`).
44
+ The former is the implementation of the Levasseur approach (https://doi.org/10.1021/es9030003), while the latter is how conventional LCA is done.
45
+ The Levasseur approach means that earlier emissions are characterized for a longer time period than later emissions.
46
+
47
+ Parameters
48
+ ----------
49
+ dynamic_inventory_df : pd.DataFrame
50
+ Dynamic inventory, formatted as a DataFrame, which contains the timing, id and amount of emissions and the emitting activity.
51
+ metric : str, optional
52
+ The metric for which the dynamic LCIA should be calculated. Default is "GWP". Available: "GWP" and "radiative_forcing". Default is "radiative_forcing".
53
+ characterization_function_dict : dict, optional
54
+ A dictionary of the form {biosphere_flow_id: dynamic_characterization_function} allowing users to specify their own functions and what flows to apply them to.
55
+ Default is none, in which case a set of default functions are added based on the base_lcia_method.
56
+ base_lcia_method : tuple, optional
57
+ Tuple of the selcted the LCIA method, e.g. `("EF v3.1", "climate change", "global warming potential (GWP100)")`. This is
58
+ required for adding the default characterization functions and can be kept empty if custom ones are provided.
59
+ time_horizon: int, optional
60
+ Length of the time horizon for the dynamic characterization. Default is 100 years.
61
+ fixed_time_horizon: bool, optional
62
+ If True, the time horizon is calculated from the time of the functional unit (FU) instead of the time of emission. Default is False.
63
+ time_horizon_start: pd.Timestamp, optional
64
+ The starting timestamp of the time horizon for the dynamic characterization. Only needed for fixed time horizons. Default is datetime.now().
65
+ characterization_function_co2: Callable, optional
66
+ Characterization function for CO2. This is required for the GWP calculation. If None is given, we try using timex' default CO2 function from the (separate) dynamic_characterization package (https://dynamic-characterization.readthedocs.io/en/latest/).
67
+
68
+ Returns
69
+ -------
70
+ pd.DataFrame
71
+ characterized dynamic inventory
72
+ """
73
+
74
+ if metric not in {"radiative_forcing", "GWP"}:
75
+ raise ValueError(
76
+ f"Metric must be either 'radiative_forcing' or 'GWP', not {metric}"
77
+ )
78
+
79
+ if not characterization_function_dict:
80
+ warnings.warn(
81
+ "No custom dynamic characterization functions provided. Using default dynamic characterization functions from `dynamic_characterization` meant to work with biosphere3 flows. The flows that are characterized are based on the selection of the initially chosen impact category. You can look up the mapping in the bw_timex.dynamic_characterizer.characterization_function_dict."
82
+ )
83
+ if not base_lcia_method:
84
+ raise ValueError(
85
+ "Please provide an LCIA method to base the default dynamic characterization functions on."
86
+ )
87
+ characterization_function_dict = create_default_characterization_function_dict(
88
+ base_lcia_method
89
+ )
90
+
91
+ if metric == "GWP" and not characterization_function_co2:
92
+ characterization_function_co2 = _get_default_co2_function()
93
+
94
+ characterized_inventory_data = []
95
+
96
+ for row in dynamic_inventory_df.itertuples(index=False):
97
+
98
+ # skip uncharacterized biosphere flows
99
+ if row.flow not in characterization_function_dict.keys():
100
+ continue
101
+
102
+ dynamic_time_horizon = _calculate_dynamic_time_horizon(
103
+ emission_date=row.date,
104
+ time_horizon_start=time_horizon_start,
105
+ time_horizon=time_horizon,
106
+ fixed_time_horizon=fixed_time_horizon,
107
+ )
108
+
109
+ if metric == "radiative_forcing": # radiative forcing in W/m2
110
+ characterized_inventory_data.append(
111
+ _characterize_radiative_forcing(
112
+ characterization_function_dict, row, dynamic_time_horizon
113
+ )
114
+ )
115
+
116
+ if metric == "GWP": # scale radiative forcing to GWP [kg CO2 equivalent]
117
+ characterized_inventory_data.append(
118
+ _characterize_gwp(
119
+ characterization_function_dict=characterization_function_dict,
120
+ row=row,
121
+ original_time_horizon=time_horizon,
122
+ dynamic_time_horizon=dynamic_time_horizon,
123
+ characterization_function_co2=characterization_function_co2,
124
+ )
125
+ )
126
+
127
+ if not characterized_inventory_data:
128
+ raise ValueError(
129
+ "There are no flows to characterize. Please make sure your time horizon matches the timing of emissions and make sure there are characterization functions for the flows in the dynamic inventories."
130
+ )
131
+
132
+ characterized_inventory = (
133
+ pd.DataFrame(characterized_inventory_data)
134
+ .explode(["amount", "date"])
135
+ .astype({"amount": "float64"})
136
+ .query("amount != 0")[["date", "amount", "flow", "activity"]]
137
+ .sort_values(by="date")
138
+ .reset_index(drop=True)
139
+ )
140
+
141
+ return characterized_inventory
142
+
143
+ def create_default_characterization_function_dict(
144
+ base_lcia_method: Tuple[str, ...]
145
+ ) -> dict:
146
+ """
147
+ Add default dynamic characterization functions from the (separate) dynamic_characterization package (https://dynamic-characterization.readthedocs.io/en/latest/)
148
+ for CO2, CH4, N2O and other GHGs, based on IPCC AR6 Chapter 7 decay curves.
149
+
150
+ Please note: Currently, only CO2, CH4 and N2O include climate-carbon feedbacks.
151
+
152
+ This has not yet been added for other GHGs. Refer to https://esd.copernicus.org/articles/8/235/2017/esd-8-235-2017.html"
153
+ "Methane, non-fossil" is currently also excluded from the default characterization functions, as it has a different static CF than fossil methane and we need to check the correct value (#TODO)
154
+
155
+ Parameters
156
+ ----------
157
+ base_lcia_method : tuple
158
+ Tuple of the selected the LCIA method, e.g. `("EF v3.1", "climate change", "global warming potential (GWP100)")`.
159
+
160
+ Returns
161
+ -------
162
+ None but adds default dynamic characterization functions to the `characterization_function_dict` attribute of the DynamicCharacterization object.
163
+
164
+ """
165
+
166
+ characterization_function_dict = dict()
167
+
168
+ # load pre-calculated decay multipliers for GHGs (except for CO2, CH4, N2O & CO)
169
+ filepath = os.path.join(
170
+ os.path.dirname(os.path.abspath(__file__)), "data", "decay_multipliers.json"
171
+ )
172
+
173
+ with open(filepath) as json_file:
174
+ decay_multipliers = json.load(json_file)
175
+
176
+ # look up which GHGs are characterized in the selected static LCA method
177
+ method_data = bd.Method(base_lcia_method).load()
178
+
179
+ biosphere_db = bd.Database(bd.config.biosphere)
180
+
181
+ # the bioflow-identifier stored in the method data can be the database id or the tuple (database, code)
182
+ def get_bioflow_node(identifier):
183
+ if (
184
+ isinstance(identifier, Collection) and len(identifier) == 2
185
+ ): # is (probably) tuple of (database, code)
186
+ try:
187
+ biosphere_node = biosphere_db.get(
188
+ database=identifier[0], code=identifier[1]
189
+ )
190
+ except UnknownObject as e:
191
+ raise UnknownObject(
192
+ f"Failed to set up the default characterization functions because a biosphere node was not found. Make sure the biosphere is set up correctly or provide a characterization_function_dict. Original error: {e}"
193
+ )
194
+ return biosphere_node
195
+
196
+ if isinstance(identifier, int): # id is an int
197
+ return biosphere_db.get(id=identifier)
198
+ else:
199
+ raise ValueError(
200
+ "The flow-identifier stored in the selected method is neither an id nor the tuple (database, code). No automatic matching possible."
201
+ )
202
+
203
+ bioflow_nodes = set(
204
+ get_bioflow_node(identifier) for identifier, _ in method_data
205
+ )
206
+
207
+ for node in bioflow_nodes:
208
+ if "carbon dioxide" in node["name"].lower():
209
+ if "soil" in node.get("categories", []):
210
+ characterization_function_dict[node.id] = (
211
+ characterize_co2_uptake # negative emission because uptake by soil
212
+ )
213
+
214
+ else:
215
+ characterization_function_dict[node.id] = characterize_co2
216
+
217
+ elif (
218
+ "methane, fossil" in node["name"].lower()
219
+ or "methane, from soil or biomass stock" in node["name"].lower()
220
+ ):
221
+ # TODO Check why "methane, non-fossil" has a CF of 27 instead of 29.8, currently excluded
222
+ characterization_function_dict[node.id] = characterize_ch4
223
+
224
+ elif "dinitrogen monoxide" in node["name"].lower():
225
+ characterization_function_dict[node.id] = characterize_n2o
226
+
227
+ elif "carbon monoxide" in node["name"].lower():
228
+ characterization_function_dict[node.id] = characterize_co
229
+
230
+ else:
231
+ cas_number = node.get("CAS number")
232
+ if cas_number:
233
+ decay_series = decay_multipliers.get(cas_number)
234
+ if decay_series is not None:
235
+ characterization_function_dict[node.id] = (
236
+ create_generic_characterization_function(
237
+ np.array(decay_series)
238
+ )
239
+ )
240
+ return characterization_function_dict
241
+
242
+ def _get_default_co2_function() -> Callable:
243
+ """
244
+ Get the default CO2 characterization function from the (separate) dynamic_characterization package (https://dynamic-characterization.readthedocs.io/en/latest/).
245
+
246
+ Parameters
247
+ ----------
248
+ None
249
+
250
+ Returns
251
+ -------
252
+ Callable
253
+ Default CO2 characterization function
254
+ """
255
+ try:
256
+ from dynamic_characterization.timex.radiative_forcing import (
257
+ characterize_co2,
258
+ )
259
+ except ImportError:
260
+ raise ImportError(
261
+ "The default CO2 characterization function could not be loaded. Please make sure the package 'dynamic_characterization' (https://dynamic-characterization.readthedocs.io/en/latest/) is installed or provide your own function for the dynamic characterization of CO2. This is necessary for the GWP calculations."
262
+ )
263
+ warnings.warn(
264
+ "Using bw_timex's default CO2 characterization function for GWP reference."
265
+ )
266
+ return characterize_co2
267
+
268
+ def _calculate_dynamic_time_horizon(
269
+ emission_date: pd.Timestamp,
270
+ time_horizon_start: pd.Timestamp,
271
+ time_horizon: int,
272
+ fixed_time_horizon: bool,
273
+ ) -> datetime:
274
+ """
275
+ Calculate the dynamic time horizon for the dynamic characterization of an emission.
276
+ Distinguishes between the Levasseur approach (fixed_time_horizon = True) and the conventional approach (fixed_time_horizon = False).
277
+
278
+ Parameters
279
+ ----------
280
+ emission_date: pd.Timestamp
281
+ The date of the emission
282
+ time_horizon_start: pd.Timestamp
283
+ Start timestamp of the time horizon
284
+ time_horizon: int
285
+ Length of the time horizon in years
286
+ fixed_time_horizon: bool
287
+ If True, the time horizon is calculated from the time of the functional unit (FU) instead of the time of emission
288
+
289
+ Returns
290
+ -------
291
+ datetime.datetime
292
+ dynamic time horizon for the specific emission
293
+ """
294
+ if fixed_time_horizon:
295
+ # Levasseur approach: time_horizon for all emissions starts at timing of FU + time_horizon
296
+ # e.g. an emission occuring n years before FU is characterized for time_horizon+n years
297
+ end_time_horizon = time_horizon_start + pd.DateOffset(years=time_horizon)
298
+ emission_datetime = emission_date.to_pydatetime()
299
+
300
+ return max(0, round((end_time_horizon - emission_datetime).days / 365.25))
301
+
302
+ else:
303
+ # conventional approach, emission is calculated from t emission for the length of time horizon
304
+ return time_horizon
305
+
306
+ def _characterize_radiative_forcing(characterization_function_dict, row, time_horizon) -> Dict:
307
+ return characterization_function_dict[row.flow](row, time_horizon)
308
+
309
+ def _characterize_gwp(
310
+ characterization_function_dict,
311
+ row,
312
+ original_time_horizon,
313
+ dynamic_time_horizon,
314
+ characterization_function_co2,
315
+ ) -> Dict:
316
+ radiative_forcing_ghg = characterization_function_dict[row.flow](
317
+ row,
318
+ dynamic_time_horizon,
319
+ )
320
+
321
+ # calculate reference radiative forcing for 1 kg of CO2
322
+ radiative_forcing_co2 = characterization_function_co2(
323
+ row._replace(amount=1), original_time_horizon
324
+ )
325
+
326
+ ghg_integral = radiative_forcing_ghg.amount.sum()
327
+ co2_integral = radiative_forcing_co2.amount.sum()
328
+ co2_equiv = ghg_integral / co2_integral
329
+
330
+ return {
331
+ "date": row.date,
332
+ "amount": co2_equiv,
333
+ "flow": row.flow,
334
+ "activity": row.activity,
335
+ }
@@ -0,0 +1,15 @@
1
+ """
2
+ Dynamic characterization functions from the bw_temporalis package (https://github.com/brightway-lca/bw_temporalis).
3
+ """
4
+
5
+ __all__ = (
6
+ "__version__",
7
+ "characterize_co2",
8
+ "characterize_methane",
9
+ # Add functions and variables you want exposed in `dynamic_characterization.` namespace here
10
+ )
11
+
12
+ __version__ = "0.0.1"
13
+
14
+ from .radiative_forcing import characterize_co2
15
+ from .radiative_forcing import characterize_methane
@@ -0,0 +1,137 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from dynamic_characterization.classes import CharacterizedRow
5
+
6
+
7
+ def characterize_co2(
8
+ series,
9
+ period: int | None = 100,
10
+ cumulative: bool | None = False,
11
+ ) -> CharacterizedRow:
12
+ """
13
+ Calculate the cumulative or marginal radiative forcing (CRF) from CO2 for each year in a given period.
14
+
15
+ If `cumulative` is True, the cumulative CRF is calculated. If `cumulative` is False, the marginal CRF is calculated.
16
+ Takes a single row of the TimeSeries Pandas DataFrame (corresponding to a set of (`date`/`amount`/`flow`/`activity`).
17
+ For each year in the given period, the CRF is calculated.
18
+ Units are watts/square meter/kilogram of CO2.
19
+
20
+ Returns
21
+ -------
22
+ A TimeSeries dataframe with the following columns:
23
+ - date: datetime64[s]
24
+ - amount: float
25
+ - flow: str
26
+ - activity: str
27
+
28
+ Notes
29
+ -----
30
+ See also the relevant scientific publication on CRF: https://doi.org/10.5194/acp-13-2793-2013
31
+ See also the relevant scientific publication on the numerical calculation of CRF: http://pubs.acs.org/doi/abs/10.1021/acs.est.5b01118
32
+
33
+ See Also
34
+ --------
35
+ characterize_methane: The same function for CH4
36
+ """
37
+
38
+ # functional variables and units (from publications listed in docstring)
39
+ RE = 1.76e-15 # Radiative forcing (W/m2/kg)
40
+ alpha_0, alpha_1, alpha_2, alpha_3 = 0.2173, 0.2240, 0.2824, 0.2763
41
+ tau_1, tau_2, tau_3 = 394.4, 36.54, 4.304
42
+ decay_term = lambda year, alpha, tau: alpha * tau * (1 - np.exp(-year / tau))
43
+
44
+ date_beginning: np.datetime64 = series.date.to_numpy()
45
+ dates_characterized: np.ndarray = date_beginning + np.arange(
46
+ start=0, stop=period, dtype="timedelta64[Y]"
47
+ ).astype("timedelta64[s]")
48
+
49
+ decay_multipliers: np.ndarray = np.array(
50
+ [
51
+ RE
52
+ * (
53
+ alpha_0 * year
54
+ + decay_term(year, alpha_1, tau_1)
55
+ + decay_term(year, alpha_2, tau_2)
56
+ + decay_term(year, alpha_3, tau_3)
57
+ )
58
+ for year in range(period)
59
+ ]
60
+ )
61
+
62
+ forcing = pd.Series(data=series.amount * decay_multipliers, dtype="float64")
63
+ if not cumulative:
64
+ forcing = forcing.diff(periods=1).fillna(0)
65
+
66
+ return CharacterizedRow(
67
+ date=np.array(dates_characterized, dtype="datetime64[s]"),
68
+ amount=forcing,
69
+ flow=series.flow,
70
+ activity=series.activity,
71
+ )
72
+
73
+
74
+ def characterize_methane(series, period: int = 100, cumulative=False) -> CharacterizedRow:
75
+ """
76
+ Calculate the cumulative or marginal radiative forcing (CRF) from CH4 for each year in a given period.
77
+
78
+ If `cumulative` is True, the cumulative CRF is calculated. If `cumulative` is False, the marginal CRF is calculated.
79
+ Takes a single row of the TimeSeries Pandas DataFrame (corresponding to a set of (`date`/`amount`/`flow`/`activity`).
80
+ For earch year in the given period, the CRF is calculated.
81
+ Units are watts/square meter/kilogram of CH4.
82
+
83
+ Parameters
84
+ ----------
85
+ series : array-like
86
+ A single row of the TimeSeries dataframe.
87
+ period : int, optional
88
+ Time period for calculation (number of years), by default 100
89
+ cumulative : bool, optional
90
+ Should the RF amounts be summed over time?
91
+
92
+ Returns
93
+ -------
94
+ A TimeSeries dataframe with the following columns:
95
+ - date: datetime64[s]
96
+ - amount: float
97
+ - flow: str
98
+ - activity: str
99
+
100
+ Notes
101
+ -----
102
+ See also the relevant scientific publication on CRF: https://doi.org/10.5194/acp-13-2793-2013
103
+ See also the relevant scientific publication on the numerical calculation of CRF: http://pubs.acs.org/doi/abs/10.1021/acs.est.5b01118
104
+
105
+ See Also
106
+ --------
107
+ characterize_co2: The same function for CO2
108
+ """
109
+
110
+ # functional variables and units (from publications listed in docstring)
111
+ f1 = 0.5 # Unitless
112
+ f2 = 0.15 # Unitless
113
+ alpha = 1.27e-13 # Radiative forcing (W/m2/kg)
114
+ tau = 12.4 # Lifetime (years)
115
+
116
+ date_beginning: np.datetime64 = series.date.to_numpy()
117
+ dates_characterized: np.ndarray = date_beginning + np.arange(
118
+ start=0, stop=period, dtype="timedelta64[Y]"
119
+ ).astype("timedelta64[s]")
120
+
121
+ decay_multipliers: list = np.array(
122
+ [
123
+ (1 + f1 + f2) * alpha * tau * (1 - np.exp(-year / tau))
124
+ for year in range(period)
125
+ ]
126
+ )
127
+
128
+ forcing = pd.Series(data=series.amount * decay_multipliers, dtype="float64")
129
+ if not cumulative:
130
+ forcing = forcing.diff(periods=1).fillna(0)
131
+
132
+ return CharacterizedRow(
133
+ date=np.array(dates_characterized, dtype="datetime64[s]"),
134
+ amount=forcing,
135
+ flow=series.flow,
136
+ activity=series.activity,
137
+ )
@@ -0,0 +1,23 @@
1
+ """
2
+ Dynamic characterization functions from the bw_timex package (https://github.com/brightway-lca/bw_timex).
3
+ """
4
+
5
+ __all__ = (
6
+ "__version__",
7
+ "characterize_co2",
8
+ "characterize_co2_uptake",
9
+ "characterize_co",
10
+ "characterize_ch4",
11
+ "characterize_n2o",
12
+ "create_generic_characterization_function",
13
+ # Add functions and variables you want exposed in `dynamic_characterization.` namespace here
14
+ )
15
+
16
+ __version__ = "0.0.1"
17
+
18
+ from .radiative_forcing import characterize_co2
19
+ from .radiative_forcing import characterize_co2_uptake
20
+ from .radiative_forcing import characterize_co
21
+ from .radiative_forcing import characterize_ch4
22
+ from .radiative_forcing import characterize_n2o
23
+ from .radiative_forcing import create_generic_characterization_function
@@ -0,0 +1,471 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+
4
+ from dynamic_characterization.classes import CharacterizedRow
5
+
6
+
7
+ def IRF_co2(year) -> callable:
8
+ """
9
+ Impulse Resonse Function (IRF) of CO2
10
+
11
+ Parameters
12
+ ----------
13
+ year : int
14
+ The year after emission for which the IRF is calculated.
15
+
16
+ Returns
17
+ -------
18
+ float
19
+ The IRF value for the given year.
20
+
21
+ """
22
+ alpha_0, alpha_1, alpha_2, alpha_3 = 0.2173, 0.2240, 0.2824, 0.2763
23
+ tau_1, tau_2, tau_3 = 394.4, 36.54, 4.304
24
+ exponentials = lambda year, alpha, tau: alpha * tau * (1 - np.exp(-year / tau))
25
+ return (
26
+ alpha_0 * year
27
+ + exponentials(year, alpha_1, tau_1)
28
+ + exponentials(year, alpha_2, tau_2)
29
+ + exponentials(year, alpha_3, tau_3)
30
+ )
31
+
32
+
33
+ def characterize_co2(
34
+ series,
35
+ period: int | None = 100,
36
+ cumulative: bool | None = False,
37
+ ) -> CharacterizedRow:
38
+ """
39
+ Calculate the cumulative or marginal radiative forcing (CRF) from CO2 for each year in a given period.
40
+
41
+ Based on characterize_co2 from bw_temporalis, but updated numerical values from IPCC AR6 Ch7 & SM.
42
+
43
+ If `cumulative` is True, the cumulative CRF is calculated. If `cumulative` is False, the marginal CRF is calculated.
44
+ Takes a single row of the TimeSeries Pandas DataFrame (corresponding to a set of (`date`/`amount`/`flow`/`activity`).
45
+ For each year in the given period, the CRF is calculated.
46
+ Units are watts/square meter/kilogram of CO2.
47
+
48
+ Returns
49
+ -------
50
+ A TimeSeries dataframe with the following columns:
51
+ - date: datetime64[s]
52
+ - amount: float
53
+ - flow: str
54
+ - activity: str
55
+
56
+ See also
57
+ --------
58
+ Joos2013: Relevant scientific publication on CRF: https://doi.org/10.5194/acp-13-2793-2013
59
+ Schivley2015: Relevant scientific publication on the numerical calculation of CRF: https://doi.org/10.1021/acs.est.5b01118
60
+ Forster2023: Updated numerical values from IPCC AR6 Chapter 7 (Table 7.15): https://doi.org/10.1017/9781009157896.009
61
+ """
62
+
63
+ # functional variables and units (from publications listed in docstring)
64
+ radiative_efficiency_ppb = (
65
+ 1.33e-5 # W/m2/ppb; 2019 background co2 concentration; IPCC AR6 Table 7.15
66
+ )
67
+
68
+ # for conversion from ppb to kg-CO2
69
+ M_co2 = 44.01 # g/mol
70
+ M_air = 28.97 # g/mol, dry air
71
+ m_atmosphere = 5.135e18 # kg [Trenberth and Smith, 2005]
72
+
73
+ radiative_efficiency_kg = (
74
+ radiative_efficiency_ppb * M_air / M_co2 * 1e9 / m_atmosphere
75
+ ) # W/m2/kg-CO2
76
+
77
+ date_beginning: np.datetime64 = series.date.to_numpy()
78
+ dates_characterized: np.ndarray = date_beginning + np.arange(
79
+ start=0, stop=period, dtype="timedelta64[Y]"
80
+ ).astype("timedelta64[s]")
81
+
82
+ decay_multipliers: np.ndarray = np.array(
83
+ [radiative_efficiency_kg * IRF_co2(year) for year in range(period)]
84
+ )
85
+
86
+ forcing = np.array(series.amount * decay_multipliers, dtype="float64")
87
+
88
+ if not cumulative:
89
+ forcing = np.diff(forcing, prepend=0)
90
+
91
+ return CharacterizedRow(
92
+ date=np.array(dates_characterized, dtype="datetime64[s]"),
93
+ amount=forcing,
94
+ flow=series.flow,
95
+ activity=series.activity,
96
+ )
97
+
98
+
99
+ def characterize_co2_uptake(
100
+ series,
101
+ period: int | None = 100,
102
+ cumulative: bool | None = False,
103
+ ) -> CharacterizedRow:
104
+ """
105
+ The same as characterize_co2, but with a negative sign for uptake of CO2.
106
+
107
+ Based on characterize_co2 from bw_temporalis, but updated numerical values from IPCC AR6 Ch7 & SM.
108
+
109
+ Calculate the negative cumulative or marginal radiative forcing (CRF) from CO2-uptake for each year in a given period.
110
+
111
+ If `cumulative` is True, the cumulative CRF is calculated. If `cumulative` is False, the marginal CRF is calculated.
112
+ Takes a single row of the TimeSeries Pandas DataFrame (corresponding to a set of (`date`/`amount`/`flow`/`activity`).
113
+ For each year in the given period, the CRF is calculated.
114
+ Units are watts/square meter/kilogram of CO2.
115
+
116
+ Returns
117
+ -------
118
+ A TimeSeries dataframe with the following columns:
119
+ - date: datetime64[s]
120
+ - amount: float
121
+ - flow: str
122
+ - activity: str
123
+
124
+ See also
125
+ --------
126
+ Joos2013: Relevant scientific publication on CRF: https://doi.org/10.5194/acp-13-2793-2013
127
+ Schivley2015: Relevant scientific publication on the numerical calculation of CRF: https://doi.org/10.1021/acs.est.5b01118
128
+ Forster2023: Updated numerical values from IPCC AR6 Chapter 7 (Table 7.15): https://doi.org/10.1017/9781009157896.009
129
+ """
130
+
131
+ # functional variables and units (from publications listed in docstring)
132
+ radiative_efficiency_ppb = (
133
+ 1.33e-5 # W/m2/ppb; 2019 background co2 concentration; IPCC AR6 Table 7.15
134
+ )
135
+
136
+ # for conversion from ppb to kg-CO2
137
+ M_co2 = 44.01 # g/mol
138
+ M_air = 28.97 # g/mol, dry air
139
+ m_atmosphere = 5.135e18 # kg [Trenberth and Smith, 2005]
140
+
141
+ radiative_efficiency_kg = (
142
+ radiative_efficiency_ppb * M_air / M_co2 * 1e9 / m_atmosphere
143
+ ) # W/m2/kg-CO2
144
+
145
+ date_beginning: np.datetime64 = series.date.to_numpy()
146
+ dates_characterized: np.ndarray = date_beginning + np.arange(
147
+ start=0, stop=period, dtype="timedelta64[Y]"
148
+ ).astype("timedelta64[s]")
149
+
150
+ decay_multipliers: np.ndarray = np.array(
151
+ [radiative_efficiency_kg * IRF_co2(year) for year in range(period)]
152
+ )
153
+
154
+ forcing = np.array(series.amount * decay_multipliers, dtype="float64")
155
+
156
+ # flip the sign of the characterization function for CO2 uptake and not release
157
+ forcing = -forcing
158
+
159
+ if not cumulative:
160
+ forcing = np.diff(forcing, prepend=0)
161
+
162
+ return CharacterizedRow(
163
+ date=np.array(dates_characterized, dtype="datetime64[s]"),
164
+ amount=forcing,
165
+ flow=series.flow,
166
+ activity=series.activity,
167
+ )
168
+
169
+
170
+ def characterize_co(
171
+ series,
172
+ period: int | None = 100,
173
+ cumulative: bool | None = False,
174
+ ) -> CharacterizedRow:
175
+ """
176
+ Calculate the cumulative or marginal radiative forcing (CRF) from CO for each year in a given period.
177
+
178
+ This is exactly the same function as for CO2, it's just scaled by the ratio of molar masses of CO and CO2. This is because CO is very short-lived (lifetime ~2 months) and we assume that it completely reacts to CO2 within the first year.
179
+
180
+ Based on characterize_co2 from bw_temporalis, but updated numerical values from IPCC AR6 Ch7 & SM.
181
+
182
+ Calculate the cumulative or marginal radiative forcing (CRF) from CO2 for each year in a given period.
183
+
184
+ If `cumulative` is True, the cumulative CRF is calculated. If `cumulative` is False, the marginal CRF is calculated.
185
+ Takes a single row of the TimeSeries Pandas DataFrame (corresponding to a set of (`date`/`amount`/`flow`/`activity`).
186
+ For each year in the given period, the CRF is calculated.
187
+ Units are watts/square meter/kilogram of CO2.
188
+
189
+ Returns
190
+ -------
191
+ A TimeSeries dataframe with the following columns:
192
+ - date: datetime64[s]
193
+ - amount: float
194
+ - flow: str
195
+ - activity: str
196
+
197
+ See also
198
+ --------
199
+ Joos2013: Relevant scientific publication on CRF: https://doi.org/10.5194/acp-13-2793-2013
200
+ Schivley2015: Relevant scientific publication on the numerical calculation of CRF: https://doi.org/10.1021/acs.est.5b01118
201
+ Forster2023: Updated numerical values from IPCC AR6 Chapter 7 (Table 7.15): https://doi.org/10.1017/9781009157896.009
202
+ """
203
+
204
+ # functional variables and units (from publications listed in docstring)
205
+ radiative_efficiency_ppb = (
206
+ 1.33e-5 # W/m2/ppb; 2019 background co2 concentration; IPCC AR6 Table 7.15
207
+ )
208
+
209
+ # for conversion from ppb to kg-CO2
210
+ M_co2 = 44.01 # g/mol
211
+ M_co = 28.01 # g/mol
212
+ M_air = 28.97 # g/mol, dry air
213
+ m_atmosphere = 5.135e18 # kg [Trenberth and Smith, 2005]
214
+
215
+ radiative_efficiency_kg = (
216
+ radiative_efficiency_ppb * M_air / M_co2 * 1e9 / m_atmosphere
217
+ ) # W/m2/kg-CO2
218
+
219
+ date_beginning: np.datetime64 = series.date.to_numpy()
220
+ dates_characterized: np.ndarray = date_beginning + np.arange(
221
+ start=0, stop=period, dtype="timedelta64[Y]"
222
+ ).astype("timedelta64[s]")
223
+
224
+ decay_multipliers: np.ndarray = np.array(
225
+ [
226
+ M_co2 / M_co * radiative_efficiency_kg * IRF_co2(year)
227
+ for year in range(period)
228
+ ] # <-- Scaling from co2 to co is done here
229
+ )
230
+
231
+ forcing = np.array(series.amount * decay_multipliers, dtype="float64")
232
+
233
+ if not cumulative:
234
+ forcing = np.diff(forcing, prepend=0)
235
+
236
+ return CharacterizedRow(
237
+ date=np.array(dates_characterized, dtype="datetime64[s]"),
238
+ amount=forcing,
239
+ flow=series.flow,
240
+ activity=series.activity,
241
+ )
242
+
243
+
244
+ def characterize_ch4(
245
+ series,
246
+ period: int = 100,
247
+ cumulative=False,
248
+ ) -> CharacterizedRow:
249
+ """
250
+ Calculate the cumulative or marginal radiative forcing (CRF) from CH4 for each year in a given period.
251
+
252
+ Based on characterize_methane from bw_temporalis, but updated numerical values from IPCC AR6 Ch7 & SM.
253
+
254
+ This DOES include indirect effects of CH4 on ozone and water vapor, but DOES NOT include the decay to CO2.
255
+ For more info on that, see the deprecated version of bw_temporalis.
256
+
257
+ If `cumulative` is True, the cumulative CRF is calculated. If `cumulative` is False, the marginal CRF is calculated.
258
+ Takes a single row of the TimeSeries Pandas DataFrame (corresponding to a set of (`date`/`amount`/`flow`/`activity`).
259
+ For earch year in the given period, the CRF is calculated.
260
+ Units are watts/square meter/kilogram of CH4.
261
+
262
+ Parameters
263
+ ----------
264
+ series : array-like
265
+ A single row of the TimeSeries dataframe.
266
+ period : int, optional
267
+ Time period for calculation (number of years), by default 100
268
+ cumulative : bool, optional
269
+ Should the RF amounts be summed over time?
270
+
271
+ Returns
272
+ -------
273
+ A TimeSeries dataframe with the following columns:
274
+ - date: datetime64[s]
275
+ - amount: float
276
+ - flow: str
277
+ - activity: str
278
+
279
+ See also
280
+ --------
281
+ Joos2013: Relevant scientific publication on CRF: https://doi.org/10.5194/acp-13-2793-2013
282
+ Schivley2015: Relevant scientific publication on the numerical calculation of CRF: https://doi.org/10.1021/acs.est.5b01118
283
+ Forster2023: Updated numerical values from IPCC AR6 Chapter 7 (Table 7.15): https://doi.org/10.1017/9781009157896.009
284
+ """
285
+
286
+ # functional variables and units (from publications listed in docstring)
287
+ radiative_efficiency_ppb = 5.7e-4 # # W/m2/ppb; 2019 background cch4 concentration; IPCC AR6 Table 7.15. This number includes indirect effects.
288
+
289
+ # for conversion from ppb to kg-CH4
290
+ M_ch4 = 16.04 # g/mol
291
+ M_air = 28.97 # g/mol, dry air
292
+ m_atmosphere = 5.135e18 # kg [Trenberth and Smith, 2005]
293
+
294
+ radiative_efficiency_kg = (
295
+ radiative_efficiency_ppb * M_air / M_ch4 * 1e9 / m_atmosphere
296
+ ) # W/m2/kg-CH4
297
+
298
+ tau = 11.8 # Lifetime (years)
299
+
300
+ date_beginning: np.datetime64 = series.date.to_numpy()
301
+ dates_characterized: np.ndarray = date_beginning + np.arange(
302
+ start=0, stop=period, dtype="timedelta64[Y]"
303
+ ).astype("timedelta64[s]")
304
+
305
+ decay_multipliers: list = np.array(
306
+ [
307
+ radiative_efficiency_kg * tau * (1 - np.exp(-year / tau))
308
+ for year in range(period)
309
+ ]
310
+ )
311
+
312
+ forcing = np.array(series.amount * decay_multipliers, dtype="float64")
313
+
314
+ if not cumulative:
315
+ forcing = np.diff(forcing, prepend=0)
316
+
317
+ return CharacterizedRow(
318
+ date=np.array(dates_characterized, dtype="datetime64[s]"),
319
+ amount=forcing,
320
+ flow=series.flow,
321
+ activity=series.activity,
322
+ )
323
+
324
+
325
+ def characterize_n2o(
326
+ series,
327
+ period: int = 100,
328
+ cumulative=False,
329
+ ) -> CharacterizedRow:
330
+ """
331
+ Calculate the cumulative or marginal radiative forcing (CRF) from N2O for each year in a given period.
332
+
333
+ Based on characterize_methane from bw_temporalis, but updated numerical values from IPCC AR6 Ch7 & SM.
334
+
335
+ If `cumulative` is True, the cumulative CRF is calculated. If `cumulative` is False, the marginal CRF is calculated.
336
+ Takes a single row of the TimeSeries Pandas DataFrame (corresponding to a set of (`date`/`amount`/`flow`/`activity`).
337
+ For earch year in the given period, the CRF is calculated.
338
+ Units are watts/square meter/kilogram of N2O.
339
+
340
+ Parameters
341
+ ----------
342
+ series : array-like
343
+ A single row of the TimeSeries dataframe.
344
+ period : int, optional
345
+ Time period for calculation (number of years), by default 100
346
+ cumulative : bool, optional
347
+ Should the RF amounts be summed over time?
348
+
349
+ Returns
350
+ -------
351
+ A TimeSeries dataframe with the following columns:
352
+ - date: datetime64[s]
353
+ - amount: float
354
+ - flow: str
355
+ - activity: str
356
+
357
+ See also
358
+ --------
359
+ Joos2013: Relevant scientific publication on CRF: https://doi.org/10.5194/acp-13-2793-2013
360
+ Schivley2015: Relevant scientific publication on the numerical calculation of CRF: https://doi.org/10.1021/acs.est.5b01118
361
+ Forster2023: Updated numerical values from IPCC AR6 Chapter 7 (Table 7.15): https://doi.org/10.1017/9781009157896.009
362
+ """
363
+
364
+ # functional variables and units (from publications listed in docstring)
365
+ radiative_efficiency_ppb = 2.8e-3 # # W/m2/ppb; 2019 background cch4 concentration; IPCC AR6 Table 7.15. This number includes indirect effects.
366
+
367
+ # for conversion from ppb to kg-CH4
368
+ M_n2o = 44.01 # g/mol
369
+ M_air = 28.97 # g/mol, dry air
370
+ m_atmosphere = 5.135e18 # kg [Trenberth and Smith, 2005]
371
+
372
+ radiative_efficiency_kg = (
373
+ radiative_efficiency_ppb * M_air / M_n2o * 1e9 / m_atmosphere
374
+ ) # W/m2/kg-N2O
375
+
376
+ tau = 109 # Lifetime (years)
377
+
378
+ date_beginning: np.datetime64 = series.date.to_numpy()
379
+ dates_characterized: np.ndarray = date_beginning + np.arange(
380
+ start=0, stop=period, dtype="timedelta64[Y]"
381
+ ).astype("timedelta64[s]")
382
+
383
+ decay_multipliers: list = np.array(
384
+ [
385
+ radiative_efficiency_kg * tau * (1 - np.exp(-year / tau))
386
+ for year in range(period)
387
+ ]
388
+ )
389
+
390
+ forcing = np.array(series.amount * decay_multipliers, dtype="float64")
391
+ if not cumulative:
392
+ forcing = np.diff(forcing, prepend=0)
393
+
394
+ return CharacterizedRow(
395
+ date=np.array(dates_characterized, dtype="datetime64[s]"),
396
+ amount=forcing,
397
+ flow=series.flow,
398
+ activity=series.activity,
399
+ )
400
+
401
+
402
+ def create_generic_characterization_function(decay_series) -> CharacterizedRow:
403
+ """
404
+ Creates a characterization function for a GHG based on a decay series, by calling the nested method `characterize_generic()`.
405
+
406
+ Parameters
407
+ ----------
408
+ decay_series : np.ndarray
409
+ A decay series for a specific GHG. This is retrieved from `../data/decay_multipliers.pkl`
410
+
411
+ Returns
412
+ -------
413
+ A function called `characterize_generic`, which in turn returns a TimeSeries dataframe that contains the forcing of the emission of the row over the given period based on the decay series of that biosphere flow.
414
+
415
+ """
416
+
417
+ def characterize_generic(
418
+ series,
419
+ period: int = 100,
420
+ cumulative=False,
421
+ ) -> CharacterizedRow:
422
+ """
423
+ Uses lookup generated in /dev/calculate_metrics.ipynb
424
+ Data originates from https://doi.org/10.1029/2019RG000691
425
+
426
+ Parameters
427
+ ----------
428
+ series : array-like
429
+ A single row of the dynamic inventory dataframe.
430
+ period : int, optional
431
+ Time period for calculation (number of years), by default 100
432
+ cumulative : bool,
433
+ cumulative impact
434
+
435
+ Returns
436
+ -------
437
+ A TimeSeries dataframe that contains the forcing of the point emission from the row for each year in the given period.
438
+ date: datetime64[s]
439
+ amount: float (forcing at this timestep)
440
+ flow: str
441
+ activity: str
442
+
443
+ See also
444
+ --------
445
+ Joos2013: Relevant scientific publication on CRF: https://doi.org/10.5194/acp-13-2793-2013
446
+ Schivley2015: Relevant scientific publication on the numerical calculation of CRF: https://doi.org/10.1021/acs.est.5b01118
447
+ Forster2023: Updated numerical values from IPCC AR6 Chapter 7 (Table 7.15): https://doi.org/10.1017/9781009157896.009
448
+
449
+ """
450
+
451
+ date_beginning: np.datetime64 = series.date.to_numpy()
452
+
453
+ dates_characterized: np.ndarray = date_beginning + np.arange(
454
+ start=0, stop=period, dtype="timedelta64[Y]"
455
+ ).astype("timedelta64[s]")
456
+
457
+ decay_multipliers = decay_series[:period]
458
+
459
+ forcing = np.array(series.amount * decay_multipliers, dtype="float64")
460
+
461
+ if not cumulative:
462
+ forcing = np.diff(forcing, prepend=0)
463
+
464
+ return CharacterizedRow(
465
+ date=np.array(dates_characterized, dtype="datetime64[s]"),
466
+ amount=forcing,
467
+ flow=series.flow,
468
+ activity=series.activity,
469
+ )
470
+
471
+ return characterize_generic
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dynamic_characterization
3
- Version: 0.0.1.dev1
3
+ Version: 0.0.3
4
4
  Summary: Collection of dynamic characterization functions for life cycle inventories with temporal information
5
5
  Author-email: Timo Diepers <timo.diepers@ltt.rwth-aachen.de>
6
6
  Maintainer-email: Timo Diepers <timo.diepers@ltt.rwth-aachen.de>
@@ -20,6 +20,9 @@ Classifier: Topic :: Scientific/Engineering
20
20
  Requires-Python: >=3.9
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
+ Requires-Dist: pandas
24
+ Requires-Dist: numpy
25
+ Requires-Dist: bw2data
23
26
  Provides-Extra: testing
24
27
  Requires-Dist: dynamic_characterization; extra == "testing"
25
28
  Requires-Dist: pytest; extra == "testing"
@@ -76,11 +79,12 @@ Here's an example of what such a function could look like:
76
79
 
77
80
  ```python
78
81
  def characterize_something(series, period: int = 100, cumulative=False) -> pd.DataFrame:
79
- date_beginning: np.datetime64 = series["date"].to_numpy()
80
- date_characterized: np.ndarray = date_beginning + np.arange(
82
+ date_beginning: np.datetime64 = series.date.to_numpy()
83
+ dates_characterized: np.ndarray = date_beginning + np.arange(
81
84
  start=0, stop=period, dtype="timedelta64[Y]"
82
85
  ).astype("timedelta64[s]")
83
86
 
87
+ # let's assume some simple decay function
84
88
  decay_multipliers: list = np.array(
85
89
  [
86
90
  1.234 * (1 - np.exp(-year / 56.789))
@@ -88,13 +92,14 @@ def characterize_something(series, period: int = 100, cumulative=False) -> pd.Da
88
92
  ]
89
93
  )
90
94
 
91
- forcing = pd.Series(data=series.amount * decay_multipliers, dtype="float64")
95
+ forcing = np.array(series.amount * decay_multipliers, dtype="float64")
96
+
92
97
  if not cumulative:
93
- forcing = forcing.diff(periods=1).fillna(0)
98
+ forcing = np.diff(forcing, prepend=0)
94
99
 
95
100
  return pd.DataFrame(
96
101
  {
97
- "date": pd.Series(data=date_characterized, dtype="datetime64[s]"),
102
+ "date": np.array(dates_characterized, dtype="datetime64[s]"),
98
103
  "amount": forcing,
99
104
  "flow": series.flow,
100
105
  "activity": series.activity,
@@ -131,6 +136,9 @@ _dynamic_characterization_ is free and open source software.
131
136
  If you encounter any problems,
132
137
  please [file an issue][Issue Tracker] along with a detailed description.
133
138
 
139
+ ## Support
140
+
141
+ If you have any questions or need help, do not hesitate to contact Timo Diepers ([timo.diepers@ltt.rwth-aachen.de](mailto:timo.diepers@ltt.rwth-aachen.de))
134
142
 
135
143
  <!-- github-only -->
136
144
 
@@ -0,0 +1,16 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ dynamic_characterization/__init__.py
6
+ dynamic_characterization/classes.py
7
+ dynamic_characterization/dynamic_characterization.py
8
+ dynamic_characterization.egg-info/PKG-INFO
9
+ dynamic_characterization.egg-info/SOURCES.txt
10
+ dynamic_characterization.egg-info/dependency_links.txt
11
+ dynamic_characterization.egg-info/requires.txt
12
+ dynamic_characterization.egg-info/top_level.txt
13
+ dynamic_characterization/temporalis/__init__.py
14
+ dynamic_characterization/temporalis/radiative_forcing.py
15
+ dynamic_characterization/timex/__init__.py
16
+ dynamic_characterization/timex/radiative_forcing.py
@@ -31,6 +31,9 @@ requires-python = ">=3.9"
31
31
  dependencies = [
32
32
  # dependencies as strings with quotes, e.g. "foo"
33
33
  # You can add version requirements like "foo>2.0"
34
+ "pandas",
35
+ "numpy",
36
+ "bw2data",
34
37
  ]
35
38
 
36
39
  [project.urls]
@@ -60,7 +63,11 @@ dev = [
60
63
  [tool.setuptools]
61
64
  license-files = ["LICENSE"]
62
65
  include-package-data = true
63
- packages = ["dynamic_characterization"]
66
+ packages = [
67
+ "dynamic_characterization",
68
+ "dynamic_characterization.temporalis",
69
+ "dynamic_characterization.timex",
70
+ ]
64
71
 
65
72
  [tool.setuptools.dynamic]
66
73
  version = {attr = "dynamic_characterization.__version__"}
@@ -1,10 +0,0 @@
1
- LICENSE
2
- MANIFEST.in
3
- README.md
4
- pyproject.toml
5
- dynamic_characterization/__init__.py
6
- dynamic_characterization.egg-info/PKG-INFO
7
- dynamic_characterization.egg-info/SOURCES.txt
8
- dynamic_characterization.egg-info/dependency_links.txt
9
- dynamic_characterization.egg-info/requires.txt
10
- dynamic_characterization.egg-info/top_level.txt