Skip to content

Commit

Permalink
Work on updating levelized cost to use discounted consumption
Browse files Browse the repository at this point in the history
  • Loading branch information
invisibleroads committed Sep 20, 2016
1 parent 507ad42 commit c923653
Show file tree
Hide file tree
Showing 15 changed files with 131 additions and 159 deletions.
3 changes: 2 additions & 1 deletion estimate_distance_between_locations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from argparse import ArgumentParser
from geopy.distance import vincenty as get_distance
from invisibleroads_macros.log import format_summary
from invisibleroads_macros.math import divide_safely
from networkx import Graph
from networkx.algorithms import minimum_spanning_tree
from pandas import read_csv
Expand All @@ -23,7 +24,7 @@ def run(location_geotable):
total_distance = sum(edge_d[
'weight'] for node1_id, node2_id, edge_d in tree.edges(data=True))
location_count = len(graph)
average_distance = total_distance / float(location_count)
average_distance = divide_safely(total_distance, location_count, 0)
return [
('total_distance_between_locations_in_meters', total_distance),
('location_count', location_count),
Expand Down
4 changes: 2 additions & 2 deletions estimate_electricity_consumption_using_recent_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

from infrastructure_planning.electricity.consumption.linear import (
estimate_electricity_consumption_using_recent_records)
from infrastructure_planning.exceptions import MissingData
from infrastructure_planning.exceptions import InvalidData


def run(target_folder, *args):
try:
electricity_consumption_by_year_table = \
estimate_electricity_consumption_using_recent_records(*args)
except MissingData as e:
except InvalidData as e:
exit('electricity_consumption_per_capita_by_year_table.error = %s' % e)
electricity_consumption_by_year_table_path = join(
target_folder, 'electricity-consumption-by-year.csv')
Expand Down
32 changes: 17 additions & 15 deletions estimate_electricity_cost_by_technology_from_population.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from invisibleroads_macros.iterable import (
OrderedDefaultDict, merge_dictionaries)
from invisibleroads_macros.log import format_summary
from invisibleroads_macros.math import divide_safely
from networkx import write_gpickle
from os.path import isabs, basename, join, splitext
from pandas import DataFrame, Series, concat
Expand All @@ -24,6 +25,8 @@
get_table_from_variables)

from infrastructure_planning.demography.exponential import estimate_population
from infrastructure_planning.electricity.consumption import (
estimate_consumption_profile)
from infrastructure_planning.electricity.consumption.linear import (
estimate_consumption_from_connection_type)
from infrastructure_planning.electricity.demand import estimate_peak_demand
Expand Down Expand Up @@ -114,19 +117,19 @@ def sequence_total_mv_line_network(target_folder, infrastructure_graph):


def estimate_total_cost(selected_technologies, infrastructure_graph):
# Compute levelized costs for selected technology for each node
for node_id, node_d in infrastructure_graph.nodes_iter(data=True):
if 'name' not in node_d:
continue # We have a fake node
best_standalone_cost = float('inf')
best_standalone_technology = 'grid'
discounted_consumption = node_d['discounted_consumption_in_kwh']
for technology in selected_technologies:
discounted_cost = node_d[
technology + '_internal_discounted_cost'] + node_d[
technology + '_external_discounted_cost']
discounted_production = node_d[
technology + '_electricity_discounted_production_in_kwh']
levelized_cost = discounted_cost / float(
discounted_production) if discounted_production else 0
levelized_cost = divide_safely(
discounted_cost, discounted_consumption, 0)
node_d[technology + '_total_discounted_cost'] = discounted_cost
node_d[technology + '_total_levelized_cost'] = levelized_cost
if technology != 'grid' and discounted_cost < best_standalone_cost:
Expand All @@ -139,30 +142,28 @@ def estimate_total_cost(selected_technologies, infrastructure_graph):
node_d['grid_total_discounted_cost'] = ''
node_d['grid_total_levelized_cost'] = ''
node_d['proposed_technology'] = proposed_technology
node_d['proposed_cost_per_connection'] = node_d[
proposed_technology + '_total_discounted_cost'] / float(node_d[
'final_connection_count'])
node_d['proposed_cost_per_connection'] = divide_safely(
node_d[proposed_technology + '_total_discounted_cost'],
node_d['final_connection_count'], 0)

# Compute levelized costs for selected technology across all nodes
count_by_technology = {x: 0 for x in selected_technologies}
discounted_cost_by_technology = OrderedDefaultDict(int)
discounted_production_by_technology = OrderedDefaultDict(int)
total_discounted_consumption = 0
for node_id, node_d in infrastructure_graph.nodes_iter(data=True):
if 'name' not in node_d:
continue # We have a fake node
technology = node_d['proposed_technology']
count_by_technology[technology] += 1
discounted_cost_by_technology[technology] += node_d[
technology + '_total_discounted_cost']
discounted_production_by_technology[technology] += node_d[
technology + '_electricity_discounted_production_in_kwh']
total_discounted_consumption += node_d['discounted_consumption_in_kwh']
levelized_cost_by_technology = OrderedDict()
for technology in selected_technologies:
discounted_cost = discounted_cost_by_technology[
technology]
discounted_production = discounted_production_by_technology[
technology]
levelized_cost_by_technology[technology] = discounted_cost / float(
discounted_production) if discounted_cost else 0
levelized_cost_by_technology[technology] = divide_safely(
discounted_cost, total_discounted_consumption, 0)
return {
'count_by_technology': count_by_technology,
'discounted_cost_by_technology': discounted_cost_by_technology,
Expand All @@ -173,6 +174,7 @@ def estimate_total_cost(selected_technologies, infrastructure_graph):
MAIN_FUNCTIONS = [
estimate_population,
estimate_consumption_from_connection_type,
estimate_consumption_profile,
estimate_peak_demand,
estimate_internal_cost_by_technology,
estimate_grid_mv_line_budget,
Expand All @@ -188,7 +190,7 @@ def estimate_total_cost(selected_technologies, infrastructure_graph):
'maintenance_lm_cost_per_year',
}
VARIABLE_NAMES_PATH = join('templates', basename(
__file__).replace('.py', '').replace('_', '-'), 'summary-columns.txt')
__file__).replace('.py', '').replace('_', '-'), 'columns.txt')
VARIABLE_NAMES = open(VARIABLE_NAMES_PATH).read().splitlines()


Expand Down
104 changes: 0 additions & 104 deletions experiments/metrics-local.csv

This file was deleted.

4 changes: 2 additions & 2 deletions forecast_demographic_using_recent_records.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from argparse import ArgumentParser
from infrastructure_planning.demography.linear import (
forecast_demographic_using_recent_records)
from infrastructure_planning.exceptions import MissingData
from infrastructure_planning.exceptions import InvalidData
from invisibleroads_macros.disk import make_enumerated_folder_for, make_folder
from invisibleroads_macros.log import format_summary
from os.path import join
Expand All @@ -25,7 +25,7 @@ def run(
demographic_by_year_table_year_column,
demographic_by_year_table_population_column,
default_yearly_population_growth_percent)
except MissingData as e:
except InvalidData as e:
exit('demographic_by_year_table.error = %s' % e)
demographic_by_year_table_path = join(
target_folder, 'demographic-by-year.csv')
Expand Down
10 changes: 5 additions & 5 deletions forecast_electricity_consumption.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from argparse import ArgumentParser
from invisibleroads_macros.disk import make_enumerated_folder_for, make_folder
from invisibleroads_macros.log import format_summary
from infrastructure_planning.exceptions import MissingData
from infrastructure_planning.exceptions import InvalidData
from infrastructure_planning.growth.interpolated import (
get_interpolated_spline_extrapolated_linear_function)
from os.path import join
Expand Down Expand Up @@ -56,7 +56,7 @@ def get_population_electricity_consumption_table(target_year):
electricity_consumption_per_capita = \
estimate_electricity_consumption_per_capita(
target_year, country_name)
except MissingData as e:
except InvalidData as e:
print('Skipping %s: %s' % (country_name.encode('utf-8'), e))
continue
electricity_consumption = \
Expand Down Expand Up @@ -106,7 +106,7 @@ def estimate_population(target_year, country_name):
earliest_estimated_year = min(country_t[
country_t['Variant'] == 'Low variant']['Year(s)'])
except ValueError:
raise MissingData('Missing population')
raise InvalidData('Missing population')
# Get actual population for each year
year_packs = country_t[country_t['Year(s)'] < earliest_estimated_year][[
'Year(s)', 'Value']].values
Expand All @@ -120,7 +120,7 @@ def estimate_electricity_consumption_per_capita(target_year, country_name):
t = ELECTRICITY_CONSUMPTION_PER_CAPITA_BY_YEAR_TABLE
country_t = _get_country_table(t, 'Country Name', country_name)
if not len(country_t):
raise MissingData(
raise InvalidData(
'Missing electricity_consumption_per_capita country_name')
year_packs = []
for column_name in country_t.columns:
Expand All @@ -133,7 +133,7 @@ def estimate_electricity_consumption_per_capita(target_year, country_name):
continue
year_packs.append((year, value))
if not year_packs:
raise MissingData(
raise InvalidData(
'Missing electricity_consumption_per_capita year_value')
estimate_electricity_consumption_per_capita = \
get_interpolated_spline_extrapolated_linear_function(year_packs)
Expand Down
13 changes: 13 additions & 0 deletions infrastructure_planning/electricity/consumption/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from ...finance.valuation import compute_discounted_cash_flow


def estimate_consumption_profile(
connection_count_by_year, consumption_in_kwh_by_year, financing_year,
discount_rate):
discounted_consumption_in_kwh = compute_discounted_cash_flow(
consumption_in_kwh_by_year, financing_year, discount_rate)
return {
'final_connection_count': connection_count_by_year.max(),
'final_consumption_in_kwh_per_year': consumption_in_kwh_by_year.max(),
'discounted_consumption_in_kwh': discounted_consumption_in_kwh,
}
4 changes: 0 additions & 4 deletions infrastructure_planning/electricity/consumption/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ def estimate_consumption_from_connection_type(
return dict(d, **{
'connection_count_by_year': connection_count_by_year,
'consumption_in_kwh_by_year': consumption_by_year,
'final_connection_count': connection_count_by_year.max(),
'final_consumption_in_kwh_per_year': consumption_by_year.max(),
})


Expand All @@ -49,8 +47,6 @@ def estimate_consumption_from_connection_count(
return {
'connection_count_by_year': connection_count_by_year,
'consumption_in_kwh_by_year': consumption_by_year,
'final_connection_count': connection_count_by_year.max(),
'final_consumption_in_kwh_per_year': consumption_by_year.max(),
}


Expand Down
15 changes: 5 additions & 10 deletions infrastructure_planning/electricity/cost/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ def prepare_component_cost_by_year(component_packs, keywords, prefix):
def prepare_internal_cost(functions, keywords):
"""
Each function must return a dictionary with these keys:
electricity_production_in_kwh_by_year
electricity_production_cost_by_year
electricity_internal_distribution_cost_by_year
The keywords dictionary must contain these keys:
financing_year
discount_rate_as_percent_of_cash_flow_per_year
discounted_consumption_in_kwh
"""
d = {}
# Compute
Expand All @@ -83,16 +83,11 @@ def prepare_internal_cost(functions, keywords):
d['electricity_production_cost_by_year'],
d['electricity_internal_distribution_cost_by_year'],
])
financing_year = keywords['financing_year']
discount_rate = keywords['discount_rate_as_percent_of_cash_flow_per_year']
discounted_production_in_kwh = compute_discounted_cash_flow(
d['electricity_production_in_kwh_by_year'],
financing_year, discount_rate)
discounted_cost = compute_discounted_cash_flow(
cost_by_year, financing_year, discount_rate)
levelized_cost = discounted_cost / float(discounted_production_in_kwh)
d['electricity_discounted_production_in_kwh'] = \
discounted_production_in_kwh
cost_by_year, keywords['financing_year'],
keywords['discount_rate_as_percent_of_cash_flow_per_year'])
levelized_cost = discounted_cost / float(keywords[
'discounted_consumption_in_kwh'])
# Summarize
d['internal_discounted_cost'] = discounted_cost
d['internal_levelized_cost'] = levelized_cost
Expand Down
18 changes: 12 additions & 6 deletions infrastructure_planning/electricity/cost/solar.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from invisibleroads_macros.math import divide_safely

from ...exceptions import InvalidData
from ...production import adjust_for_losses, prepare_actual_system_capacity


Expand All @@ -10,8 +13,9 @@ def estimate_panel_cost(
final_production_in_kwh_per_year = adjust_for_losses(
final_consumption_in_kwh_per_year,
system_loss_as_percent_of_total_production / 100.)
desired_system_capacity_in_kw = final_production_in_kwh_per_year / float(
peak_hours_of_sun_per_year)
desired_system_capacity_in_kw = divide_safely(
final_production_in_kwh_per_year, peak_hours_of_sun_per_year,
float('inf'))
# Choose panel type
return prepare_actual_system_capacity(
desired_system_capacity_in_kw, panel_table, 'capacity_in_kw')
Expand All @@ -31,8 +35,9 @@ def estimate_battery_cost(
d['installation_lm_cost'] = installation_lm_cost
d['maintenance_lm_cost_per_year'] = battery_storage_in_kwh * \
battery_maintenance_lm_cost_per_kwh_per_year
d['replacement_lm_cost_per_year'] = installation_lm_cost / float(
battery_lifetime_in_years)
d['replacement_lm_cost_per_year'] = divide_safely(
installation_lm_cost, battery_lifetime_in_years, InvalidData(
'battery_lifetime_in_years must be greater than zero'))
return d


Expand All @@ -48,6 +53,7 @@ def estimate_balance_cost(
d['maintenance_lm_cost_per_year'] = \
panel_actual_system_capacity_in_kw * \
balance_maintenance_lm_cost_per_panel_kw_per_year
d['replacement_lm_cost_per_year'] = installation_lm_cost / float(
balance_lifetime_in_years)
d['replacement_lm_cost_per_year'] = divide_safely(
installation_lm_cost, balance_lifetime_in_years, InvalidData(
'balance_lifetime_in_years must be greater than zero'))
return d
2 changes: 1 addition & 1 deletion infrastructure_planning/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class InfrastructurePlanningError(Exception):
pass


class MissingData(InfrastructurePlanningError):
class InvalidData(InfrastructurePlanningError):
pass


Expand Down
1 change: 1 addition & 0 deletions infrastructure_planning/finance/valuation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


def compute_levelized_cost(time_production_cost_packs, discount_rate_percent):
# TODO: Rename time to period
time_production_cost_array = np.array(time_production_cost_packs)
time_production_packs = time_production_cost_array[:, [0, 1]]
time_cost_packs = time_production_cost_array[:, [0, 2]]
Expand Down
4 changes: 2 additions & 2 deletions infrastructure_planning/growth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np

from ..exceptions import MissingData
from ..exceptions import InvalidData


def get_default_slope(growth_percent, year_packs):
Expand All @@ -21,7 +21,7 @@ def split_xys(xys, default_slope=0):
try:
xs, ys = zip(*xys)
except ValueError:
raise MissingData('must have at least one row')
raise InvalidData('must have at least one row')
if len(set(xs)) == 1:
xs = list(xs) + [xs[0] + 1]
ys = list(ys) + [np.mean(ys) + default_slope]
Expand Down
Loading

0 comments on commit c923653

Please sign in to comment.