Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow rebuilding timeline #94

Merged
merged 9 commits into from
Sep 18, 2024
5 changes: 5 additions & 0 deletions bw_timex/timeline_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class TimelineBuilder:
def __init__(
self,
slca: LCA,
starting_datetime: datetime,
edge_filter_function: Callable,
database_date_dict: dict,
database_date_dict_static_only: dict,
Expand All @@ -45,6 +46,8 @@ def __init__(
----------
slca: LCA
A static LCA object.
starting_datetime: datetime | str, optional
Point in time when the demand occurs.
edge_filter_function: Callable
A callable that filters edges. If not provided, a function that always returns False is used.
database_date_dict: dict
Expand All @@ -65,6 +68,7 @@ def __init__(
Keyword arguments passed to the EdgeExtractor which inherits from TemporalisLCA.
"""
self.slca = slca
self.starting_datetime = starting_datetime
self.edge_filter_function = edge_filter_function
self.database_date_dict = database_date_dict
self.database_date_dict_static_only = database_date_dict_static_only
Expand All @@ -90,6 +94,7 @@ def __init__(

self.edge_extractor = EdgeExtractor(
slca,
starting_datetime=self.starting_datetime,
*args,
edge_filter_function=edge_filter_function,
cutoff=self.cutoff,
Expand Down
43 changes: 32 additions & 11 deletions bw_timex/timex_lca.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@
from .helper_classes import SetList, TimeMappingDict
from .matrix_modifier import MatrixModifier
from .timeline_builder import TimelineBuilder
from .utils import extract_date_as_integer, resolve_temporalized_node_name
from .utils import (
extract_date_as_integer,
resolve_temporalized_node_name,
round_datetime_to_nearest_year,
)


class TimexLCA:
Expand Down Expand Up @@ -144,21 +148,13 @@ def __init__(
self.base_lca.lci()
self.base_lca.lcia()

# Create a time mapping dict that maps each activity to a activity_time_mapping_id in the
# format (('database', 'code'), datetime_as_integer): time_mapping_id)
self.activity_time_mapping_dict = TimeMappingDict(
start_id=bd.backends.ActivityDataset.select(fn.MAX(AD.id)).scalar() + 1
) # making sure we get unique ids by counting up from the highest current activity id

# Create a similar dict for the biosphere flows. Populated by the dynamic_biosphere_builder
self.biosphere_time_mapping_dict = TimeMappingDict(start_id=0)

########################################
# Main functions to be called by users #
########################################

def build_timeline(
self,
starting_datetime: datetime | str = "now",
temporal_grouping: str = "year",
interpolation_type: str = "linear",
edge_filter_function: Callable = None,
Expand All @@ -174,6 +170,9 @@ def build_timeline(

Parameters
----------
starting_datetime: datetime | str, optional
Point in time when the demand occurs. This is the initial starting point of the
timeline. Something like `"now"` or `"2023-01-01"`. Default is `"now"`.
temporal_grouping : str, optional
Time resolution for grouping exchanges over time in the timeline. Default is 'year',
other options are 'month', 'day', 'hour'.
Expand Down Expand Up @@ -219,11 +218,18 @@ def build_timeline(
else:
self.edge_filter_function = edge_filter_function

self.starting_datetime = starting_datetime
self.temporal_grouping = temporal_grouping
self.interpolation_type = interpolation_type
self.cutoff = cutoff
self.max_calc = max_calc

# Create a time mapping dict that maps each activity to a activity_time_mapping_id in the
# format (('database', 'code'), datetime_as_integer): time_mapping_id)
self.activity_time_mapping_dict = TimeMappingDict(
start_id=bd.backends.ActivityDataset.select(fn.MAX(AD.id)).scalar() + 1
) # making sure we get unique ids by counting up from the highest current activity id

# pre-populate the activity time mapping dict with the static activities.
# Doing this here because we need the temporal grouping for consistent times resolution.
self.add_static_activities_to_time_mapping_dict()
Expand All @@ -233,6 +239,7 @@ def build_timeline(
# with the TimelineBuilder.build_timeline() method.
self.timeline_builder = TimelineBuilder(
self.base_lca,
self.starting_datetime,
self.edge_filter_function,
self.database_date_dict,
self.database_date_dict_static_only,
Expand Down Expand Up @@ -385,7 +392,7 @@ def dynamic_lcia(
of the chosen static climate change impact category. If there is no characterization
function for a biosphere flow, it will be ignored.

Two dynamic climate change metrics are provided: "GWP" and "radiative_forcing".
Two dynamic climate change metrics are supported: "GWP" and "radiative_forcing".
The time horizon for the impact assessment can be set with the `time_horizon` parameter,
defaulting to 100 years. The `fixed_time_horizon` parameter determines whether the emission
time horizon for all emissions is calculated from the functional unit
Expand Down Expand Up @@ -438,6 +445,18 @@ def dynamic_lcia(
# Set a default for inventory_in_time_horizon using the full dynamic_inventory_df
inventory_in_time_horizon = self.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(
round_datetime_to_nearest_year
)
inventory_in_time_horizon = (
inventory_in_time_horizon.groupby(
inventory_in_time_horizon.columns.tolist()
)
.sum()
.reset_index()
)

# Calculate the latest considered impact date
t0_date = pd.Timestamp(self.timeline_builder.edge_extractor.t0.date[0])
latest_considered_impact = t0_date + pd.DateOffset(years=time_horizon)
Expand Down Expand Up @@ -570,6 +589,8 @@ def calculate_dynamic_inventory(
[len(bd.Database(db)) for db in self.database_date_dict.keys()]
)

self.biosphere_time_mapping_dict = TimeMappingDict(start_id=0)

self.dynamic_biosphere_builder = DynamicBiosphereBuilder(
self.lca,
self.activity_time_mapping_dict,
Expand Down
21 changes: 21 additions & 0 deletions bw_timex/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,27 @@ def convert_date_string_to_datetime(temporal_grouping, datestring) -> datetime:
return datetime.strptime(datestring, time_res_dict[temporal_grouping])


def round_datetime_to_nearest_year(date: datetime) -> datetime:
"""
Round a datetime object to the nearest year.

Returns
-------
datetime
datetime object rounded to nearest year.
"""
year = date.year
start_of_year = pd.Timestamp(f"{year}-01-01")
start_of_next_year = pd.Timestamp(f"{year+1}-01-01")

mid_year = start_of_year + (start_of_next_year - start_of_year) / 2

if date < mid_year:
return start_of_year
else:
return start_of_next_year


def add_flows_to_characterization_function_dict(
flows: Union[str, List[str]],
func: Callable,
Expand Down
Loading