Skip to content

Commit

Permalink
Merge branch 'update_ev_example'
Browse files Browse the repository at this point in the history
  • Loading branch information
TimoDiepers committed Sep 12, 2024
2 parents eb901b5 + 6fd140c commit ff5c1dc
Show file tree
Hide file tree
Showing 61 changed files with 8,373 additions and 3,149 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:

# Create the conda environment
- name: Create conda environment
run: conda create -n timex -c conda-forge -c cmutel -c diepers brightway25 bw_temporalis dynamic_characterization matplotlib seaborn
run: conda create -n timex -c conda-forge -c cmutel -c diepers brightway25 bw_temporalis dynamic_characterization matplotlib seaborn

# Install testing dependencies from pyproject.toml
- name: Install testing dependencies
Expand All @@ -73,4 +73,3 @@ jobs:
source $(conda info --base)/etc/profile.d/conda.sh
conda activate timex
pytest
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,4 @@ poetry.toml
# LSP config files
pyrightconfig.json

# End of https://www.toptal.com/developers/gitignore/api/jupyternotebooks,python
# End of https://www.toptal.com/developers/gitignore/api/jupyternotebooks,python
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ repos:
[
"-rn", # Only display messages
"-sn", # Don't display the score
]
]
4 changes: 2 additions & 2 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ sphinx:
configuration: docs/conf.py

submodules:
include: all
include: all

build:
os: "ubuntu-lts-latest" # https://docs.readthedocs.io/en/stable/config-file/v2.html#build-os
tools:
python: "mambaforge-latest" # https://docs.readthedocs.io/en/stable/config-file/v2.html#build-tools-python, mamba instead of conda for better build performance
python: "mambaforge-latest" # https://docs.readthedocs.io/en/stable/config-file/v2.html#build-tools-python, mamba instead of conda for better build performance
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ If you want to contribute to the development our code with a new feature, want t
Something is not working as expected? You have two options:

### 🥈 Report an error
Please open a new issue in the `bw_timex` [repository](https://github.com/TimoDiepers/timex/issues), describing the error and where you found it.
Please open a new issue in the `bw_timex` [repository](https://github.com/TimoDiepers/timex/issues), describing the error and where you found it.
A member of the bw_timex developer community will then take care of the issue, but it may take some time for your issue to be resolved.

### 🥇 Fix an error yourself
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
BSD 3-Clause License

Copyright (c) 2024, Institute of Technical Thermodynamics @ RWTH Aachen University, Institute of Environmental Sciences @ Leiden University, Flemish Institute for Technology Research, Paul Scherrer Institut.
Copyright (c) 2024, Institute of Technical Thermodynamics @ RWTH Aachen University, Institute of Environmental Sciences @ Leiden University, Flemish Institute for Technology Research, Paul Scherrer Institut.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
This is a python package for time-explicit Life Cycle Assessment that helps you assess the environmental impacts of products and processes over time. `bw_timex` builds on top of the [Brightway LCA framework](https://docs.brightway.dev/en/latest).

## Features
This package enables you to account for:
This package enables you to account for:
- **Timing of processes** throughout the supply chain (e.g., end-of-life treatment occurs 20 years after construction)
- **Variable** and/or **evolving** supply chains & technologies (e.g., increasing shares of renewable electricity in the future)
- **Timing of emissions** (by applying dynamic characterization functions)
Expand Down
2 changes: 1 addition & 1 deletion bw_timex/data/decay_multipliers.json

Large diffs are not rendered by default.

88 changes: 52 additions & 36 deletions bw_timex/dynamic_biosphere_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from scipy import sparse as sp

from .helper_classes import SetList
from .utils import convert_date_string_to_datetime
from .utils import convert_date_string_to_datetime, resolve_temporalized_node_name


class DynamicBiosphereBuilder:
Expand Down Expand Up @@ -92,7 +92,7 @@ def __init__(
self.rows = []
self.cols = []
self.values = []
self.unique_rows_cols = set() # To keep track of (row, col) pairs
self.unique_rows_cols = set() # To keep track of (row, col) pairs

def build_dynamic_biosphere_matrix(
self,
Expand All @@ -104,12 +104,12 @@ def build_dynamic_biosphere_matrix(
The timing of the emitting process and potential additional temporal information of the bioshpere flow (e.g. delay of emission compared to timing of process) are considered.
Absolute Temporal Distributions for biosphere exchanges are dealt with as a look up function:
If an activity happens at timestamp X then and the biosphere exchange has an absolute temporal
distribution (ATD), it looks up the amount from from the ATD correspnding to timestamp X.
If an activity happens at timestamp X then and the biosphere exchange has an absolute temporal
distribution (ATD), it looks up the amount from from the ATD correspnding to timestamp X.
E.g.: X = 2024, TD=(data=[2020,2021,2022,2023,2024,.....,2120 ], amount=[3,4,4,5,6,......,3]),
it will look up the value 6 corresponding 2024. If timestamp X does not exist it find the nearest
it will look up the value 6 corresponding 2024. If timestamp X does not exist it find the nearest
timestamp available (if two timestamps are equally close, it will take the first in order of
apearance (see numpy.argmin() for this behabiour).
apearance (see numpy.argmin() for this behabiour).
Parameters
Expand Down Expand Up @@ -137,6 +137,7 @@ def build_dynamic_biosphere_matrix(
) = self.activity_time_mapping_dict.reversed()[ # time is here an integer, with various length depending on temporal grouping, e.g. [Y] -> 2024, [M] - > 202401
idx
]

if idx in self.node_id_collection_dict["temporalized_processes"]:

time_in_datetime = convert_date_string_to_datetime(
Expand All @@ -149,23 +150,30 @@ def build_dynamic_biosphere_matrix(
).date
date = td_producer[0]

act = bd.get_node(database=original_db, code=original_code)
if original_db == "temporalized":
act = bd.get_node(code=original_code)
else:
act = bd.get_node(database=original_db, code=original_code)

for exc in act.biosphere():
if exc.get("temporal_distribution"):
td_dates = exc["temporal_distribution"].date
td_dates = exc["temporal_distribution"].date
td_values = exc["temporal_distribution"].amount
if type(td_dates[0])==np.datetime64: # If the biosphere flows have an absolute TD, this means we have to look up the biosphere flow for the activity time (td_producer)
if (
type(td_dates[0]) == np.datetime64
): # If the biosphere flows have an absolute TD, this means we have to look up the biosphere flow for the activity time (td_producer)
dates = td_producer # datetime array, same time as producer
values = [
exc["amount"] * td_values[
exc["amount"]
* td_values[
np.argmin(
np.abs(
td_dates.astype(self.time_res)-td_producer.astype(self.time_res)
td_dates.astype(self.time_res)
- td_producer.astype(self.time_res)
)
)
]
] # look up the value correponding to the absolute producer time
] # look up the value correponding to the absolute producer time
else:
dates = (
td_producer + td_dates
Expand All @@ -191,38 +199,48 @@ def build_dynamic_biosphere_matrix(
amount=amount,
)
elif idx in self.node_id_collection_dict["temporal_markets"]:
(
(original_db, original_code),
time,
) = self.activity_time_mapping_dict.reversed()[ # time is here an integer, with various length depending on temporal grouping, e.g. [Y] -> 2024, [M] - > 202401
idx
]

if from_timeline:
demand = self.demand_from_timeline(row, original_db)
else:
demand = self.demand_from_technosphere(idx, process_col_index)

self.lca_obj.redo_lci(demand)
if demand:
self.lca_obj.redo_lci(demand)

aggregated_inventory = self.lca_obj.inventory.sum(
axis=1
) # aggregated biosphere flows of background supply chain emissions. Rows are bioflows.
aggregated_inventory = self.lca_obj.inventory.sum(
axis=1
) # aggregated biosphere flows of background supply chain emissions. Rows are bioflows.

for row_idx, amount in enumerate(aggregated_inventory.A1):
bioflow = self.lca_obj.dicts.biosphere.reversed[row_idx]
((_, _), time) = self.activity_time_mapping_dict.reversed()[idx]
for row_idx, amount in enumerate(aggregated_inventory.A1):
bioflow = self.lca_obj.dicts.biosphere.reversed[row_idx]
((_, _), time) = self.activity_time_mapping_dict.reversed()[idx]

time_in_datetime = convert_date_string_to_datetime(
self.temporal_grouping, str(time)
) # now time is a datetime
time_in_datetime = convert_date_string_to_datetime(
self.temporal_grouping, str(time)
) # now time is a datetime

td_producer = TemporalDistribution(
date=np.array([str(time_in_datetime)], dtype=self.time_res),
amount=np.array([1]),
).date # TODO: Simplify
date = td_producer[0]
td_producer = TemporalDistribution(
date=np.array([str(time_in_datetime)], dtype=self.time_res),
amount=np.array([1]),
).date # TODO: Simplify
date = td_producer[0]

time_mapped_matrix_id = self.biosphere_time_mapping_dict.add(
(bioflow, date)
)
time_mapped_matrix_id = self.biosphere_time_mapping_dict.add(
(bioflow, date)
)

self.add_matrix_entry_for_biosphere_flows(
row=time_mapped_matrix_id, col=process_col_index, amount=amount
)
self.add_matrix_entry_for_biosphere_flows(
row=time_mapped_matrix_id,
col=process_col_index,
amount=amount,
)

# now build the dynamic biosphere matrix
if from_timeline:
Expand Down Expand Up @@ -282,7 +300,7 @@ def demand_from_technosphere(self, idx, process_col_index):
def add_matrix_entry_for_biosphere_flows(self, row, col, amount):
"""
Adds an entry to the lists of row, col and values, which are then used to construct the dynamic biosphere matrix.
Only unqiue entries are added, i.e. if the same row and col index already exists, the value is not added again.
Only unqiue entries are added, i.e. if the same row and col index already exists, the value is not added again.
Parameters
----------
Expand All @@ -305,5 +323,3 @@ def add_matrix_entry_for_biosphere_flows(self, row, col, amount):
self.values.append(amount)

self.unique_rows_cols.add((row, col))


37 changes: 19 additions & 18 deletions bw_timex/edge_extractor.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from dataclasses import dataclass
from heapq import heappop, heappush
from typing import Callable, List
from numbers import Number
from typing import Callable, List

import numpy as np
from bw_temporalis import TemporalisLCA, TemporalDistribution
from bw_temporalis import TemporalDistribution, TemporalisLCA

datetime_type = np.dtype("datetime64[s]")
timedelta_type = np.dtype("timedelta64[s]")
Expand All @@ -19,7 +20,8 @@ class Edge:
function).
"""
edge_type : str

edge_type: str
distribution: TemporalDistribution
leaf: bool
consumer: int
Expand All @@ -28,7 +30,6 @@ class Edge:
td_consumer: TemporalDistribution
abs_td_producer: TemporalDistribution = None
abs_td_consumer: TemporalDistribution = None



class EdgeExtractor(TemporalisLCA):
Expand All @@ -51,10 +52,10 @@ def __init__(self, *args, edge_filter_function: Callable = None, **kwargs) -> No
Returns
-------
None
"""
super().__init__(*args, **kwargs) #use __init__ of TemporalisLCA
super().__init__(*args, **kwargs) # use __init__ of TemporalisLCA

if edge_filter_function:
self.edge_ff = edge_filter_function
else:
Expand Down Expand Up @@ -94,10 +95,10 @@ def build_edge_timeline(self) -> list:
node,
),
)

timeline.append(
Edge(
edge_type = "production", # FU exchange always type production (?)
edge_type="production", # FU exchange always type production (?)
distribution=self.t0 * edge.amount,
leaf=False,
consumer=self.unique_id,
Expand All @@ -119,7 +120,9 @@ def build_edge_timeline(self) -> list:
output_id=col_id,
)

edge_type = exchange.data["type"] # can be technosphere, substitution, production or other string
edge_type = exchange.data[
"type"
] # can be technosphere, substitution, production or other string

td_producer = ( # td_producer is the TemporalDistribution of the edge
self._exchange_value(
Expand All @@ -128,25 +131,25 @@ def build_edge_timeline(self) -> list:
col_id=col_id,
matrix_label="technosphere_matrix",
)
/ node.reference_product_production_amount
/ abs(node.reference_product_production_amount)
)
producer = self.nodes[edge.producer_unique_id]
leaf = self.edge_ff(row_id)

# If an edge does not have a TD, give it a td with timedelta=0 and the amount= 'edge value'
if isinstance(td_producer, Number):
td_producer = TemporalDistribution(
date=np.array([0], dtype="timedelta64[Y]"),
date=np.array([0], dtype="timedelta64[Y]"),
amount=np.array([td_producer]),
)

distribution = (
td * td_producer
).simplify() # convolution-multiplication of TemporalDistribution of consuming node (td) and consumed edge (edge) gives TD of producing node

timeline.append(
Edge(
edge_type = edge_type,
edge_type=edge_type,
distribution=distribution,
leaf=leaf,
consumer=node.activity_datapackage_id,
Expand All @@ -157,8 +160,7 @@ def build_edge_timeline(self) -> list:
td_producer, abs_td
),
abs_td_consumer=abs_td,

)
)
)
if not leaf:
heappush(
Expand All @@ -175,7 +177,6 @@ def build_edge_timeline(self) -> list:
)
return timeline


def join_datetime_and_timedelta_distributions(
self, td_producer: TemporalDistribution, td_consumer: TemporalDistribution
) -> TemporalDistribution:
Expand Down Expand Up @@ -208,7 +209,7 @@ def join_datetime_and_timedelta_distributions(
td_producer, Number
):
return td_consumer

# Else, if both consumer and producer have a td (absolute and relative, respectively) join to TDs
if isinstance(td_producer, TemporalDistribution) and isinstance(
td_consumer, TemporalDistribution
Expand Down
Loading

0 comments on commit ff5c1dc

Please sign in to comment.