Skip to content

Commit

Permalink
Merge pull request #18 from danyoungday/time
Browse files Browse the repository at this point in the history
Added timeline
  • Loading branch information
danyoungday authored Nov 4, 2024
2 parents 501845d + c596553 commit 7442079
Show file tree
Hide file tree
Showing 26 changed files with 952 additions and 538 deletions.
3 changes: 2 additions & 1 deletion app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from app.utils import EvolutionHandler

evolution_handler = EvolutionHandler()
actions = evolution_handler.actions
metrics = evolution_handler.outcomes.keys()
# The candidates are sorted by rank then distance so the 'best' ones are the first 10
sample_idxs = list(range(10))
Expand All @@ -22,7 +23,7 @@
context_component = ContextComponent()
filter_component = FilterComponent(metrics)
outcome_component = OutcomeComponent(evolution_handler)
link_component = LinkComponent(sample_idxs)
link_component = LinkComponent(sample_idxs, actions)
references_component = ReferencesComponent()

# Initialize the Dash app
Expand Down
37 changes: 6 additions & 31 deletions app/components/link.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
"""
Link Component.
"""
import json

from dash import Input, Output, State, html, dcc
import dash_bootstrap_components as dbc
import pandas as pd
import plotly.graph_objects as go

from app.classes import JUMBOTRON, CONTAINER, DESC_TEXT, HEADER
from app.components.timeline import TimelineComponent
from enroadspy import load_input_specs
from enroadspy.generate_url import actions_to_url

Expand All @@ -18,17 +17,17 @@ class LinkComponent():
Component in charge of displaying the links to En-ROADS.
"""

def __init__(self, cand_idxs: list[int]):
self.cand_idxs = cand_idxs
def __init__(self, cand_idxs: list[int], actions: list[str]):
self.cand_idxs = [i for i in cand_idxs]

self.colors = ["brown", "red", "blue", "green", "pink", "lightblue", "orange"]
self.energies = ["coal", "oil", "gas", "renew and hydro", "bio", "nuclear", "new tech"]
self.demands = [f"Primary energy demand of {energy}" for energy in self.energies]

with open("app/categories.json", "r", encoding="utf-8") as f:
self.categories = json.load(f)
self.input_specs = load_input_specs()

self.timeline_component = TimelineComponent(actions)

def plot_energy_policy(self, energy_policy_jsonl: list[dict[str, list]], cand_idx: int) -> go.Figure:
"""
Plots density chart from energy policy.
Expand Down Expand Up @@ -79,30 +78,6 @@ def plot_energy_policy(self, energy_policy_jsonl: list[dict[str, list]], cand_id
)
return fig

def translate_context_actions_dict(self, context_actions_dict: dict[str, float]) -> html.Div:
"""
Translates a context actions dict into a nice div to display
"""
children = []
for category in self.categories:
children.append(html.H4(category))
remove = True # If we don't have any actions in this category, remove it
for action in self.categories[category]:
if action in context_actions_dict:
remove = False
input_spec = self.input_specs[self.input_specs["varId"] == action].iloc[0]
val = context_actions_dict[action]
if input_spec["kind"] == "slider":
formatting = input_spec["format"]
val_formatted = f"{val:{formatting}}"
else:
val_formatted = "on" if val else "off"
children.append(html.P(f"{input_spec['varName']}: {val_formatted}"))
if remove:
children.pop()

return html.Div(children)

def create_link_div(self):
"""
Creates content box containing links to the candidates to view.
Expand Down Expand Up @@ -236,5 +211,5 @@ def update_actions_body(context_actions_dicts: list[dict[str, float]], cand_idx)
"""
if cand_idx is not None:
context_actions_dict = context_actions_dicts[cand_idx]
return self.translate_context_actions_dict(context_actions_dict), False
return self.timeline_component.create_timeline_div(context_actions_dict), False
return "", True
112 changes: 112 additions & 0 deletions app/components/timeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""
Timeline component.
"""
from collections import defaultdict

import numpy as np
from dash import html
import yaml

from enroadspy import load_input_specs


class TimelineComponent():
"""
Component handling generation of a timeline of actions taken.
"""
def __init__(self, actions: list[str]):
self.actions = [a for a in actions]

# Pre-compute timeline actions for later when we show actions
with open("app/timeline.yaml", "r", encoding="utf-8") as f:
self.timeline = yaml.load(f, Loader=yaml.FullLoader)
self.timeline_actions = []
for action, details in self.timeline.items():
self.timeline_actions.append(action)
for d in details.values():
self.timeline_actions.append(d)

self.input_specs = load_input_specs()

def _create_timeline_events(self,
action: str,
details: dict,
context_actions_dict: dict[str, float]) -> dict[int, str]:
"""
Creates 0 or more timeline events for a given action.
We have to manually handle electric standard being active and if this is the final carbon tax as they rely on
other actions.
We also manually handle if the action is a different type of bioenergy
Returns a dict with the year as the key and the event as the value.
"""
# Electric standard needs to be active
if action == "_electric_standard_target" and not context_actions_dict["_electric_standard_active"]:
return {}
# If we're doing subsidy by feedstock ignore the default bio subsidy
if action == "_source_subsidy_delivered_bio_boe" and context_actions_dict["_use_subsidies_by_feedstock"]:
return {}
# If we're not doing subsidy by feedstock ignore the specific feedstock subsidies
if action in ["_wood_feedstock_subsidy_boe", "_crop_feedstock_subsidy_boe", "_other_feedstock_subsidy_boe"] \
and not context_actions_dict["_use_subsidies_by_feedstock"]:
return {}

events = {}
row = self.input_specs[self.input_specs["varId"] == action].iloc[0]
name = row["varName"]
decimal = np.ceil(-1 * np.log10(row["step"])).astype(int)
value = context_actions_dict[action]

start_year = context_actions_dict[details["start"]]

# Carbon price phasing start date needs to be after previous carbon price phase end date
if action == "_carbon_tax_final_target":
initial_end = context_actions_dict["_carbon_tax_phase_1_start"] + \
context_actions_dict["_carbon_tax_time_to_achieve_initial_target"]
if start_year < initial_end:
start_year = initial_end

# Compute the stop year from the length if necessary
if "stop" in details or "length" in details:
stop_year = context_actions_dict[details["stop"]] if "stop" in details else start_year + \
context_actions_dict[details["length"]]
if start_year < stop_year:
events[int(start_year)] = f"start {name}: {value:.{decimal}f}"
events[int(stop_year)] = f"end {name}"
else:
events[int(start_year)] = f"{name}: {value:.{decimal}f}"

return events

def create_timeline_div(self, context_actions_dict: dict[str, float]) -> html.Div:
"""
Creates a nice timeline of actions taken as well as the initial actions taken.
"""
# Generate initial actions
children = [html.H3("Initial Actions")]
for action in context_actions_dict:
if action not in self.timeline_actions and action in self.actions:
input_spec = self.input_specs[self.input_specs["varId"] == action].iloc[0]
val = context_actions_dict[action]
if input_spec["kind"] == "slider":
formatting = input_spec["format"]
val_formatted = f"{val:{formatting}}"
else:
val_formatted = "on" if val else "off"
children.append(html.P(f"{input_spec['varName']}: {val_formatted}"))

# Generate timeline
timeline = defaultdict(list)
for action, details in self.timeline.items():
if action in context_actions_dict:
events = self._create_timeline_events(action, details, context_actions_dict)
for year, event in events.items():
timeline[year].append(event)

# Sort the timeline by year so that we can display it in order
children.append(html.H3("Timeline"))
for year in sorted(timeline.keys()):
children.append(html.H4(str(year)))
for event in timeline[year]:
children.append(html.P(event))

return html.Div(children)
76 changes: 76 additions & 0 deletions app/timeline.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
_source_subsidy_delivered_coal_tce:
type: subsidy
start: _source_subsidy_start_time_delivered_coal
stop: _source_subsidy_stop_time_delivered_coal
_no_new_coal:
type: no_new
start: _year_of_no_new_capacity_coal
_utilization_adjustment_factor_delivered_coal:
type: adjustment
start: _utilization_policy_start_time_delivered_coal
stop: _utilization_policy_stop_time_delivered_coal
_source_subsidy_delivered_oil_boe:
type: subsidy
start: _source_subsidy_start_time_delivered_oil
stop: _source_subsidy_stop_time_delivered_oil
_no_new_oil:
type: no_new
start: _year_of_no_new_capacity_oil
_utilization_adjustment_factor_delivered_oil:
type: adjustment
start: _utilization_policy_start_time_delivered_oil
stop: _utilization_policy_stop_time_delivered_oil
_source_subsidy_delivered_gas_mcf:
type: subsidy
start: _source_subsidy_start_time_delivered_gas
stop: _source_subsidy_stop_time_delivered_gas
_no_new_gas:
type: no_new
start: _year_of_no_new_capacity_gas
_utilization_adjustment_factor_delivered_gas:
type: adjustment
start: _utilization_policy_start_time_delivered_gas
stop: _utilization_policy_stop_time_delivered_gas
_source_subsidy_renewables_kwh:
type: subsidy
start: _source_subsidy_start_time_renewables
stop: _source_subsidy_stop_time_renewables
_source_subsidy_delivered_bio_boe:
type: subsidy
start: _source_subsidy_start_time_delivered_bio
stop: _source_subsidy_stop_time_delivered_bio
_wood_feedstock_subsidy_boe:
type: bio
start: _source_subsidy_start_time_delivered_bio
stop: _source_subsidy_stop_time_delivered_bio
_crop_feedstock_subsidy_boe:
type: bio
start: _source_subsidy_start_time_delivered_bio
stop: _source_subsidy_stop_time_delivered_bio
_other_feedstock_subsidy_boe:
type: bio
start: _source_subsidy_start_time_delivered_bio
stop: _source_subsidy_stop_time_delivered_bio
_no_new_bio:
type: no_new
start: _year_of_no_new_capacity_bio
_source_subsidy_nuclear_kwh:
type: subsidy
start: _source_subsidy_start_time_nuclear
stop: _source_subsidy_stop_time_nuclear
_carbon_tax_initial_target:
type: carbon_tax
start: _carbon_tax_phase_1_start
length: _carbon_tax_time_to_achieve_initial_target
_carbon_tax_final_target:
type: carbon_tax
start: _carbon_tax_phase_3_start
length: _carbon_tax_time_to_achieve_final_target
# Note: we need to see if electric_standard_active is true for this
_electric_standard_target:
start: _electric_standard_start_year
length: _electric_standard_target_time
_emissions_performance_standard:
start: _performance_standard_time


2 changes: 1 addition & 1 deletion app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def prescribe_all(self, context_dict: dict[str, float]):
"""
context_actions_dicts = []
for x in self.X:
candidate = Candidate.from_pymoo_params(x, self.model_params, self.actions, self.outcomes)
candidate = Candidate.from_pymoo_params(x, self.model_params, self.actions)
# Process context_dict into tensor
context_list = [context_dict[context] for context in self.context_df.columns]
context_scaled = self.scaler.transform([context_list])
Expand Down
3 changes: 1 addition & 2 deletions enroadspy/generate_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ def open_browser(results_dir, cand_id, input_idx):
evaluator = Evaluator(config["context"], config["actions"], config["outcomes"])
candidate = Candidate.from_seed(results_dir / cand_id.split("_")[0] / f"{cand_id}.pt",
config["model_params"],
config["actions"],
config["outcomes"])
config["actions"])
context_tensor, context_vals = evaluator.context_dataset[input_idx]
device = "mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu"
actions_dicts = candidate.prescribe(context_tensor.to(device).unsqueeze(0))
Expand Down
Loading

0 comments on commit 7442079

Please sign in to comment.