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

variable costs pricing #215

Merged
merged 8 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 108 additions & 71 deletions spice_ev/costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# allowed cost calculations
COST_CALCULATION = [
"fixed_wo_plw", "fixed_w_plw",
# "variable_wo_plw", "variable_w_plw", # not implemented yet
"variable_wo_plw", "variable_w_plw",
"balanced_market", "schedule", "flex_window",
]

Expand Down Expand Up @@ -194,6 +194,7 @@ def calculate_costs(cc_type, voltage_level, interval,
:type power_schedule_list: list
:raises NotImplementedError: if cost calculation type is not supported
:raises ValueError: if nom. PV power exceeds max. power for feed-in remuneration in price sheet
:raises ValueError: if price_list is a dictionary without procurement or commodity
:return: total costs per year and simulation period (fees and taxes included)
:rtype: dict
"""
Expand All @@ -216,73 +217,100 @@ def calculate_costs(cc_type, voltage_level, interval,
# only consider positive values of fixed load for cost calculation
power_fix_load_list = [max(v, 0) for v in power_fix_load_list]

# ENERGY SUPPLY
energy_supply_sim = sum(power_grid_supply_list) * interval.total_seconds() / 3600
energy_supply_pa = energy_supply_sim / fraction_year

# APPLY COST CALCULATION METHOD
# sets commodity and capacity charge
if cc_type.startswith("fixed"):
"""
Calculates costs in accordance with existing payment models.
For SLP customers the variable capacity_charge is equivalent to the basic charge
"""
# maximum power supplied from the grid
max_power_grid_supply = max(power_grid_supply_list + [0])

# prices
if max_power_grid_supply == 0:
utilization_time_pa = 0
else:
utilization_time_pa = abs(energy_supply_pa / max_power_grid_supply) # [h/a]
commodity_charge, capacity_charge, fee_type = find_prices(
price_sheet, fee_type, voltage_level,
utilization_time_pa, energy_supply_pa,
)

if cc_type == "fixed_w_plw":
# get peak power inside time windows
if window_signal_list is None:
# no time windows: no window loads -> peak power is set to 0
window_loads = []
else:
window_loads = [l for (l, w) in
zip(power_grid_supply_list, window_signal_list) if w]
peak_power_in_windows = max(window_loads + [0])
# check if cost calculation for peak_load_window can be applied
significance_threshold = 0
if max_power_grid_supply > 0:
significance_threshold = (
(max_power_grid_supply - peak_power_in_windows) / max_power_grid_supply) * 100
significance_threshold_price_sheet = (
price_sheet["strategy_related"]["peak_load_window"]
["significance_threshold"][voltage_level])
# check if cost model can be applied
peak_diff = max_power_grid_supply - peak_power_in_windows
if significance_threshold > significance_threshold_price_sheet and peak_diff > 100:
# only use peak power inside windows for calculation of capacity costs
max_power_grid_supply = peak_power_in_windows
# save threshold from price sheet to update json file
json_results_windows = {
"significance threshold from price sheet": significance_threshold_price_sheet
}
# maximum power supplied from the grid
max_power_grid_supply = max(power_grid_supply_list + [0])

# CAPACITY COSTS
if fee_type == "SLP":
capacity_costs_eur = capacity_charge
else: # RLM
capacity_costs_eur = calculate_capacity_costs_rlm(
capacity_charge, max_power_grid_supply)
# prices
if max_power_grid_supply == 0:
utilization_time_pa = 0
elif cc_type.endswith("w_plw"):
# peak load window costs: always high utilization
utilization_time_pa = UTILIZATION_TIME_PER_YEAR_EC
else:
utilization_time_pa = abs(energy_supply_pa / max_power_grid_supply) # [h/a]
commodity_charge, capacity_charge, fee_type = find_prices(
price_sheet, fee_type, voltage_level,
utilization_time_pa, energy_supply_pa,
)

# COMMODITY COSTS
# APPLY COST CALCULATION METHOD
# COMMODITY COSTS
if cc_type.startswith("fixed"):
# use fixed commodity charge
price_list = [commodity_charge] * len(power_grid_supply_list)
commodity_costs_eur_per_year, commodity_costs_eur_sim = calculate_commodity_costs(
price_list, power_grid_supply_list, interval, fraction_year)

elif cc_type.startswith("variable"):
raise NotImplementedError("variable costs not yet supported")
# apply procurement and commodity costs for each timestep to grid supply
# this is just drawn power, feed-in is handled independently
# prices should be given as dictionary with procurement and/or commodity timeseries
procurement_price_list = price_list.get('procurement')
commodity_price_list = price_list.get('commodity')

if procurement_price_list is None and commodity_price_list is None:
# variable costs pricing needs at least one price series
raise ValueError("Variable pricing must have procurement or commodity costs")
if procurement_price_list is None:
# use fixed procurement costs
power_procurement_charge = price_sheet["power_procurement"]["charge"] # [ct/kWh]
procurement_price_list = [power_procurement_charge] * len(timestamps_list)

if commodity_price_list is None:
# use fixed commodity costs
commodity_price_list = [commodity_charge] * len(timestamps_list)

ts_per_hour = interval.total_seconds() / 3600
commodity_costs_eur_sim = 0
power_procurement_costs_sim = 0
for i, power in enumerate(power_grid_supply_list):
energy_supply_per_timestep = (power * ts_per_hour) # [kWh]
commodity_costs_eur_sim += (
energy_supply_per_timestep * commodity_price_list[i] / 100)
power_procurement_costs_sim += (
energy_supply_per_timestep * procurement_price_list[i] / 100)
commodity_costs_eur_per_year = commodity_costs_eur_sim / fraction_year

if cc_type.endswith("w_plw"):
# peak load windows: adjust capacity costs by changing max_power_grid_supply
# get peak power inside time windows
if window_signal_list is None:
# no time windows: no window loads -> peak power is set to 0
window_loads = []
else:
window_loads = [l for (l, w) in
zip(power_grid_supply_list, window_signal_list) if w]
peak_power_in_windows = max(window_loads + [0])
# check if cost calculation for peak_load_window can be applied
significance_threshold = 0
if max_power_grid_supply > 0:
significance_threshold = (
(max_power_grid_supply - peak_power_in_windows) / max_power_grid_supply) * 100
significance_threshold_price_sheet = (
price_sheet["strategy_related"]["peak_load_window"]
["significance_threshold"][voltage_level])
# check if cost model can be applied
peak_diff = max_power_grid_supply - peak_power_in_windows
if significance_threshold > significance_threshold_price_sheet and peak_diff > 100:
# only use peak power inside windows for calculation of capacity costs
max_power_grid_supply = peak_power_in_windows
# save threshold from price sheet to update json file
json_results_windows = {
"significance threshold from price sheet": significance_threshold_price_sheet
}

# CAPACITY COSTS
# This is only relevant for fixed and variable costs.
# Balanced_market and flex_window have their own section.
if fee_type == "SLP":
capacity_costs_eur = capacity_charge
else: # RLM
capacity_costs_eur = calculate_capacity_costs_rlm(
capacity_charge, max_power_grid_supply)

elif cc_type == "balanced_market":
if cc_type == "balanced_market":
"""Payment model for the charging strategy 'balanced market'.
For the charging strategy a price time series is used.
The fixed and flexible load are charged separately.
Expand Down Expand Up @@ -324,9 +352,9 @@ def calculate_costs(cc_type, voltage_level, interval,

# commodity costs for fixed load
price_list_fix_load = [commodity_charge_fix] * len(power_fix_load_list)
commodity_costs_eur_per_year_fix, commodity_costs_eur_sim_fix = \
commodity_costs_eur_per_year_fix, commodity_costs_eur_sim_fix = (
calculate_commodity_costs(price_list_fix_load, power_fix_load_list,
interval, fraction_year)
interval, fraction_year))

# capacity costs for fixed load
capacity_costs_eur_fix = calculate_capacity_costs_rlm(
Expand All @@ -337,7 +365,15 @@ def calculate_costs(cc_type, voltage_level, interval,
power_flex_load_list = get_flexible_load(power_grid_supply_list, power_fix_load_list)

# adjust given price list (EUR/kWh --> ct/kWh)
price_list = [price * 100 for price in price_list]
# commodity price list may be part of price list dict
if type(price_list) is dict:
price_list = price_list.get("commodity")
if price_list is None:
# use fixed price list
warnings.warn("balanced_market pricing without price timeseries, used fixed prices")
price_list = [commodity_charge]*len(timestamps_list)
else:
price_list = [price * 100 for price in price_list]

# find power at times of high tariff
max_price = max(price_list)
Expand Down Expand Up @@ -413,9 +449,9 @@ def calculate_costs(cc_type, voltage_level, interval,

# commodity costs for fixed load
price_list_fix_load = [commodity_charge_fix] * len(power_fix_load_list)
commodity_costs_eur_per_year_fix, commodity_costs_eur_sim_fix = \
commodity_costs_eur_per_year_fix, commodity_costs_eur_sim_fix = (
calculate_commodity_costs(price_list_fix_load, power_fix_load_list,
interval, fraction_year)
interval, fraction_year))

# capacity costs for fixed load
capacity_costs_eur_fix = calculate_capacity_costs_rlm(
Expand Down Expand Up @@ -511,9 +547,9 @@ def calculate_costs(cc_type, voltage_level, interval,

# commodity costs for fixed load
price_list_fix_load = [commodity_charge_fix, ] * len(power_fix_load_list)
commodity_costs_eur_per_year_fix, commodity_costs_eur_sim_fix = \
commodity_costs_eur_per_year_fix, commodity_costs_eur_sim_fix = (
calculate_commodity_costs(price_list_fix_load, power_fix_load_list,
interval, fraction_year)
interval, fraction_year))

# capacity costs for fixed load
capacity_costs_eur_fix = calculate_capacity_costs_rlm(
Expand All @@ -537,9 +573,9 @@ def calculate_costs(cc_type, voltage_level, interval,

# commodity costs for flexible load
price_list_flex_load = [commodity_charge_flex] * len(power_flex_load_list)
commodity_costs_eur_per_year_flex, commodity_costs_eur_sim_flex = \
commodity_costs_eur_per_year_flex, commodity_costs_eur_sim_flex = (
calculate_commodity_costs(price_list_flex_load, power_flex_load_list,
interval, fraction_year)
interval, fraction_year))

# DEVIATION COSTS

Expand Down Expand Up @@ -574,7 +610,7 @@ def calculate_costs(cc_type, voltage_level, interval,
commodity_costs_eur_per_year = (commodity_costs_eur_per_year_fix
+ commodity_costs_eur_per_year_flex)
capacity_costs_eur = capacity_costs_eur_fix + capacity_costs_eur_flex
else:
elif not cc_type.startswith("fixed") and not cc_type.startswith("variable"):
raise NotImplementedError(f"Cost calculation undefined for {cc_type}")

# COSTS NOT RELATED TO STRATEGIES
Expand All @@ -588,8 +624,9 @@ def calculate_costs(cc_type, voltage_level, interval,
additional_costs_sim = 0

# COSTS FOR POWER PROCUREMENT
power_procurement_charge = price_sheet["power_procurement"]["charge"] # [ct/kWh]
power_procurement_costs_sim = (power_procurement_charge * energy_supply_sim / 100) # [EUR]
if not cc_type.startswith("variable"):
power_procurement_charge = price_sheet["power_procurement"]["charge"] # [ct/kWh]
power_procurement_costs_sim = (power_procurement_charge * energy_supply_sim / 100) # [EUR]
power_procurement_costs_per_year = (power_procurement_costs_sim / fraction_year) # [EUR]

# COSTS FROM LEVIES
Expand Down Expand Up @@ -649,7 +686,7 @@ def calculate_costs(cc_type, voltage_level, interval,
# PV power plant not existing
else:
feed_in_charge_pv = 0 # [ct/kWh]
if power_generation_feed_in_list is not None:
if power_generation_feed_in_list is not None and sum(power_generation_feed_in_list) != 0:
warnings.warn("Nominal power of PV power plant is zero even though there is an "
"existing generation time series")

Expand Down
Loading
Loading