Skip to content

Commit

Permalink
dynamic_lcia with disaggregated background lcis
Browse files Browse the repository at this point in the history
  • Loading branch information
Arthur Jakobs committed Jan 31, 2025
1 parent c67700e commit eebac3f
Showing 1 changed file with 90 additions and 42 deletions.
132 changes: 90 additions & 42 deletions bw_timex/timex_lca.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ def lci(
build_datapackage: Method to create the datapackages that contain the modifications to the technosphere and biosphere matrix using the `MatrixModifier` class.
calculate_dynamic_inventory: Method to calculate the dynamic inventory if `build_dynamic_biosphere` is True.
"""
self.dynamic_inventory = None # reset dynamic inventory
self.expand_technosphere = expand_technosphere

if not expand_technosphere and not build_dynamic_biosphere:
Expand Down Expand Up @@ -348,6 +349,10 @@ def lci(
data_obs = self.data_objs + self.datapackage
self.expanded_technosphere = True # set flag for later static lcia usage
else: # setup for timeline approach
warnings.warn(
"Building the dynamic inventory directly from the timeline. This feature is under development.\
Use at your own risk... and check your results! Disaggregated lci is not yet implemented."
)
self.collect_temporalized_processes_from_timeline()
data_obs = self.data_objs
self.expanded_technosphere = False # set flag for later lcia usage
Expand All @@ -370,28 +375,47 @@ def lci(
else:
self.calculate_dynamic_inventory(from_timeline=True)

def lci_with_background_inventory(self) -> None:
def disaggregate_background_lci(self) -> None:
if not hasattr(self, "dynamic_inventory"):
raise AttributeError(
"Dynamic lci not yet calculated. Call TimexLCA.lci(build_dynamic_biosphere=True) first."
)
self.dynamic_inventory_bg = self.dynamic_inventory.tocsc()
# 1) set all temporal market emissions to zero
if not self.expanded_technosphere:
raise NotImplementedError(
"Currently the disaggregation of background processes is only possible if the expanded matrix has been built. Please call TimexLCA.lci(expand_technosphere=True) first."
)
# create array_dict for fast lookup
# (key becomes index, value becomes value of 1D array)
bio_dict_array = np.zeros(
max(self.lca.dicts.biosphere.reversed.keys()) + 1, dtype=int
)
for key, value in self.lca.dicts.biosphere.reversed.items():
bio_dict_array[key] = value

# create biosphere_time_mapping_dict_int for fast lookup
biosphere_time_mapping_dict_int = {
(key[0], key[1].astype("int64")): value
for key, value in self.biosphere_time_mapping_dict.items()
}

self.dynamic_inventory_disaggregated = self.dynamic_inventory.tocsc()
# 1) set all temporal market emissions to zero
for col in self.dynamic_biosphere_builder.temporal_markets_col_list:
self.dynamic_inventory_bg.data[
self.dynamic_inventory_bg.indptr[
self.dynamic_inventory_disaggregated.data[
self.dynamic_inventory_disaggregated.indptr[
col
] : self.dynamic_inventory_bg.indptr[col + 1]
] : self.dynamic_inventory_disaggregated.indptr[col + 1]
] = 0
self.dynamic_inventory_bg.eliminate_zeros()
self.dynamic_inventory_disaggregated.eliminate_zeros()
# 2) add all background inventory to the dynamic inventory for all temporal markets

self.dynamic_inventory_bg = self.dynamic_inventory_bg.tocoo()
self.dynamic_inventory_disaggregated = (
self.dynamic_inventory_disaggregated.tocoo()
)

dynamic_inv_row_ids = self.dynamic_inventory_bg.row.tolist()
dynamic_inv_col_ids = self.dynamic_inventory_bg.col.tolist()
dynamic_inv_data = self.dynamic_inventory_bg.data.tolist()
dynamic_inv_row_ids = self.dynamic_inventory_disaggregated.row.tolist()
dynamic_inv_col_ids = self.dynamic_inventory_disaggregated.col.tolist()
dynamic_inv_data = self.dynamic_inventory_disaggregated.data.tolist()

for id_, lci in self.temporal_market_lci_dict.items():

Expand All @@ -401,30 +425,29 @@ def lci_with_background_inventory(self) -> None:
time_in_datetime = convert_date_string_to_datetime(
self.temporal_grouping, str(time)
)
time_in_datetime = np.datetime64(time_in_datetime).astype(
"datetime64[s]"
time_int = (
np.datetime64(time_in_datetime).astype("datetime64[s]").astype("int64")
) # now time is a numpy datetime

lci = lci.tocoo()
t0 = ti.time()

our_list = [
self.biosphere_time_mapping_dict[
(self.lca.dicts.biosphere.reversed[row_idx], time_in_datetime)
]
for row_idx in lci.row
]
t1 = ti.time()
print(f"Time for our_list: {t1-t0}")
dynamic_inv_row_ids.extend(our_list)
# create list of tuples for fast lookup
time_array = np.ones(len(lci.row), dtype="int64") * time_int
list_of_tuples = list(zip(bio_dict_array[lci.row], time_array))

new_rows = [biosphere_time_mapping_dict_int[x] for x in list_of_tuples]
dynamic_inv_row_ids.extend(new_rows)
dynamic_inv_col_ids.extend(lci.col) # (c for c in lci.col)
dynamic_inv_data.extend(lci.data) # (d for d in lci.data)

dynamic_inventory_bg = sparse.coo_matrix( # construct the new dynamic inventory including background inventory instead of aggregated temporal market emissions
dynamic_inventory_disaggregated = sparse.coo_matrix( # construct the new dynamic inventory including background inventory instead of aggregated temporal market emissions
(dynamic_inv_data, (dynamic_inv_row_ids, dynamic_inv_col_ids)),
shape=self.dynamic_inventory_bg.shape,
shape=self.dynamic_inventory_disaggregated.shape,
)
self.dynamic_inventory_disaggregated = dynamic_inventory_disaggregated.tocsr()
self.dynamic_inventory_disaggregated_df = (
self.create_dynamic_inventory_dataframe(use_disaggregated_lci=True)
)
self.dynamic_inventory_bg = dynamic_inventory_bg.tocsr()

def static_lcia(self) -> None:
"""
Expand Down Expand Up @@ -477,6 +500,7 @@ def dynamic_lcia(
time_horizon_start: datetime = None,
characterization_function_dict: dict = None,
characterization_function_co2: dict = None,
use_disaggregated_lci: bool = False,
) -> pd.DataFrame:
"""
Calculates dynamic LCIA with the `DynamicCharacterization` class using the dynamic inventory
Expand Down Expand Up @@ -531,17 +555,34 @@ def dynamic_lcia(
--------
dynamic_characterization: Package handling the dynamic characterization: https://dynamic-characterization.readthedocs.io/en/latest/
"""

if not hasattr(self, "dynamic_inventory"):
if not self.dynamic_inventory:

This comment has been minimized.

Copy link
@TimoDiepers

TimoDiepers Feb 3, 2025

Member

@jakobsarthur why was this changed from hasattr, did it break anything?

Image

This comment has been minimized.

Copy link
@TimoDiepers

TimoDiepers Feb 3, 2025

Member

@jakobsarthur why was this changed from hasattr, did it break anything?

Image

This comment has been minimized.

Copy link
@jakobsarthur

jakobsarthur via email Feb 3, 2025

Collaborator
raise AttributeError(
"Dynamic lci not yet calculated. Call TimexLCA.lci(build_dynamic_biosphere=True) first."
)

self.current_metric = metric
self.current_time_horizon = time_horizon

if use_disaggregated_lci:
if not self.expanded_technosphere:
raise NotImplementedError(
"Currently the disaggregation of background processes is only possible if the \
expanded matrix has been built. Please call TimexLCA.lci(expand_technosphere=True) first."
)
# Check if disaggregated inventory is available
# otherwise disaggregate the background LCI
if not hasattr(self, "dynamic_inventory_disaggregated"):
print("Disaggregating background LCI...")
self.disaggregate_background_lci()
dynamic_inventory_df = self.dynamic_inventory_disaggregated_df
else:
dynamic_inventory_df = self.dynamic_inventory_df

print("Calculating dynamic LCIA...")

# Set a default for inventory_in_time_horizon using the full dynamic_inventory_df
inventory_in_time_horizon = self.dynamic_inventory_df
inventory_in_time_horizon = dynamic_inventory_df

# Round dates to nearest year and sum up emissions for each year
inventory_in_time_horizon.date = inventory_in_time_horizon.date.apply(
Expand All @@ -561,14 +602,14 @@ def dynamic_lcia(

# Update inventory_in_time_horizon if a fixed time horizon is used
if fixed_time_horizon:
last_emission = self.dynamic_inventory_df.date.max()
last_emission = dynamic_inventory_df.date.max()
if latest_considered_impact < last_emission:
warnings.warn(
"An emission occurs outside of the specified time horizon and will not be \
characterized. Please make sure this is intended."
)
inventory_in_time_horizon = self.dynamic_inventory_df[
self.dynamic_inventory_df.date <= latest_considered_impact
inventory_in_time_horizon = dynamic_inventory_df[
dynamic_inventory_df.date <= latest_considered_impact
]

if not time_horizon_start:
Expand Down Expand Up @@ -709,7 +750,7 @@ def calculate_dynamic_inventory(
self.interdatabase_activity_mapping,
from_timeline=from_timeline,
)
self.dynamic_biomatrix, self.temporal_market_lci_dict = (
self.dynamic_biosphere_matrix, self.temporal_market_lci_dict = (
self.dynamic_biosphere_builder.build_dynamic_biosphere_matrix(
from_timeline=from_timeline
)
Expand All @@ -722,7 +763,7 @@ def calculate_dynamic_inventory(
diagonal_supply_array = sparse.spdiags(
[self.dynamic_biosphere_builder.dynamic_supply_array], [0], count, count
)
self.dynamic_inventory = self.dynamic_biomatrix @ diagonal_supply_array
self.dynamic_inventory = self.dynamic_biosphere_matrix @ diagonal_supply_array

self.biosphere_time_mapping_dict_reversed = {
v: k for k, v in self.biosphere_time_mapping_dict.items()
Expand All @@ -732,7 +773,11 @@ def calculate_dynamic_inventory(
from_timeline
)

def create_dynamic_inventory_dataframe(self, from_timeline=False) -> pd.DataFrame:
def create_dynamic_inventory_dataframe(
self,
from_timeline=False,
use_disaggregated_lci=False,
) -> pd.DataFrame:
"""
Brings the dynamic inventory from its matrix form in `dynamic_inventory` into the
format of a pandas.DataFrame, with the right structure to later apply dynamic
Expand Down Expand Up @@ -765,15 +810,18 @@ def create_dynamic_inventory_dataframe(self, from_timeline=False) -> pd.DataFram
pandas.DataFrame, dynamic inventory in DataFrame format
"""

if use_disaggregated_lci:
dynamic_inventory = self.dynamic_inventory_disaggregated
else:
dynamic_inventory = self.dynamic_inventory
dataframe_rows = []
for i in range(self.dynamic_inventory.shape[0]):
row_start = self.dynamic_inventory.indptr[i]
row_end = self.dynamic_inventory.indptr[i + 1]
for i in range(dynamic_inventory.shape[0]):
row_start = dynamic_inventory.indptr[i]
row_end = dynamic_inventory.indptr[i + 1]
for j in range(row_start, row_end):
row = i
col = self.dynamic_inventory.indices[j]
value = self.dynamic_inventory.data[j]
col = dynamic_inventory.indices[j]
value = dynamic_inventory.data[j]

if from_timeline:
emitting_process_id = self.timeline.iloc[col][
Expand Down Expand Up @@ -1409,7 +1457,7 @@ def create_labelled_dynamic_biosphere_dataframe(self) -> pd.DataFrame:
dynamic biosphere matrix as a pandas.DataFrame with comprehensible labels
instead of ids.
"""
df = pd.DataFrame(self.dynamic_biomatrix.toarray())
df = pd.DataFrame(self.dynamic_biosphere_matrix.toarray())
df.rename( # from matrix id to activity id
index=self.biosphere_time_mapping_dict_reversed,
columns=self.lca.dicts.activity.reversed,
Expand Down

0 comments on commit eebac3f

Please sign in to comment.