Skip to content

Commit

Permalink
Merge branch 'dev' into feature/extended_prices
Browse files Browse the repository at this point in the history
  • Loading branch information
stefan.schirmeister committed Oct 16, 2024
2 parents 368f8c1 + 598b02c commit 0a726ba
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 53 deletions.
9 changes: 5 additions & 4 deletions data/examples/simba.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ scenario_name = example
schedule_path = data/examples/trips_example.csv
# Output files are stored here (defaults to: data/sim_outputs)
# Attention: In Windows the path-length is limited to 256 characters!
# Deactivate storage of output by setting output_directory = null
# Deactivate storage of output by setting output_path = null
output_path = data/output/
# Electrified stations (required)
electrified_stations_path = data/examples/electrified_stations.json
Expand Down Expand Up @@ -76,9 +76,10 @@ strategy_opps = greedy
strategy_options_deps = {"CONCURRENCY": 1}
strategy_options_opps = {}

# Cost calculation strategy
cost_calculation_strategy_deps = balanced
cost_calculation_strategy_opps = greedy
# Cost calculation to use. Remove to use default for strategy.
# Options: fixed_wo_plw, fixed_w_plw, variable_wo_plw, variable_w_plw, balanced_market, flex_window
cost_calculation_method_deps = fixed_wo_plw
cost_calculation_method_opps = fixed_wo_plw

##### Physical setup of environment #####
### Parametrization of the physical setup ###
Expand Down
16 changes: 8 additions & 8 deletions docs/source/simulation_parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ The example (data/simba.cfg) contains parameter descriptions which are explained
- greedy
- SpiceEV Strategies (greedy, balanced, peak_shaving, peak_load_windows, balanced_market)
- Charging strategy used in opportunity stations.
* - cost_calculation_strategy_deps
- strategy_deps value
- SpiceEV Strategies (greedy, balanced, peak_shaving, peak_load_windows, balanced_market)
- Strategy for cost calculation at depots.
* - cost_calculation_strategy_opps
- strategy_opps value
- SpiceEV Strategies (greedy, balanced, peak_shaving, peak_load_windows, balanced_market)
- Strategy for cost calculation at opportunity stations.
* - cost_calculation_method_deps
- fixed_wo_plw
- SpiceEV cost calculation type (fixed_wo_plw, fixed_w_plw, variable_wo_plw, variable_w_plw, balanced_market, flex_window)
- Method for cost calculation at depots.
* - cost_calculation_method_opps
- fixed_wo_plw
- SpiceEV cost calculation type, same choices as in depot
- Method for cost calculation at opportunity stations.
* - preferred_charging_type
- depb
- depb, oppb
Expand Down
36 changes: 20 additions & 16 deletions simba/costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import traceback
import warnings

import spice_ev.scenario
from spice_ev.costs import calculate_costs as calc_costs_spice_ev

import simba.schedule

import spice_ev.costs
import spice_ev.scenario
import spice_ev.util


def calculate_costs(c_params, scenario, schedule, args):
""" Calculates annual costs of all necessary vehicles and infrastructure.
Expand Down Expand Up @@ -323,21 +324,23 @@ def set_electricity_costs(self):
# use procurement and commodity costs read from CSV instead of SpiceEV prices, if exist
prices = station.get("prices", timeseries.get("price [EUR/kWh]"))

# Get the calculation strategy / method from args.
# If no value is set, use the same strategy as the charging strategy
default_cost_strategy = vars(self.args)["strategy_" + station.get("type")]

cost_strategy_name = "cost_calculation_strategy_" + station.get("type")
cost_calculation_strategy = (vars(self.args).get(cost_strategy_name)
or default_cost_strategy)
# Get the calculation method from args.
cost_calculation_name = "cost_calculation_method_" + station.get("type")
cost_calculation_method = vars(self.args).get(cost_calculation_name)
if cost_calculation_method is None:
# not given in config: use the same method as the charging strategy
default_strategy = vars(self.args)["strategy_" + station.get("type")]
cost_calculation_method = spice_ev.costs.DEFAULT_COST_CALCULATION[default_strategy]

# calculate costs for electricity
try:
if cost_calculation_strategy == "peak_load_window":
if timeseries.get("window signal [-]") is None:
raise Exception("No peak load window signal provided for cost calculation")
costs_electricity = calc_costs_spice_ev(
strategy=cost_calculation_strategy,
is_peak_load_window = cost_calculation_method.endswith("w_plw")
if is_peak_load_window and timeseries.get("window signal [-]") is None:
logging.info("Generating peak load window signal for cost calculation")
timeseries["window signal [-]"] = spice_ev.util.get_time_windows_from_json(
self.args.time_windows, gc.grid_operator, gc.voltage_level, self.scenario)
costs_electricity = spice_ev.costs.calculate_costs(
cc_type=cost_calculation_method,
voltage_level=gc.voltage_level,
interval=self.scenario.interval,
timestamps_list=timeseries.get("time"),
Expand All @@ -347,9 +350,10 @@ def set_electricity_costs(self):
power_generation_feed_in_list=timeseries.get("generation feed-in [kW]"),
power_v2g_feed_in_list=timeseries.get("V2G feed-in [kW]"),
power_battery_feed_in_list=timeseries.get("battery feed-in [kW]"),
charging_signal_list=timeseries.get("window signal [-]"),
window_signal_list=timeseries.get("window signal [-]"),
price_sheet_path=self.args.cost_parameters_path,
grid_operator=gc.grid_operator,
fee_type=None, # "RLM" or "SLP" or None (based on energy consumption)
power_pv_nominal=pv,
)
except Exception:
Expand Down
1 change: 1 addition & 0 deletions simba/rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def __init__(self, id, vehicle_type, schedule) -> None:
self.trips = []
self.schedule = schedule

self.allow_opp_charging_for_oppb: bool = True
self.vehicle_type = vehicle_type
self.vehicle_id = None
self.charging_type = None
Expand Down
7 changes: 5 additions & 2 deletions simba/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,8 +928,11 @@ def generate_scenario(self, args):
# assume electrified station
station = self.stations[gc_name]
station_type = station["type"]
if station_type == 'opps' and trip.rotation.charging_type == 'depb':
# a depot bus cannot charge at an opp station
if (station_type == 'opps' and
(trip.rotation.charging_type == 'depb' or
not trip.rotation.allow_opp_charging_for_oppb)):
# a depot bus cannot charge at an opp station.
# a bus cannot charge at opps if it's not allowed
station_type = None
else:
# get desired soc by station type and trip
Expand Down
11 changes: 6 additions & 5 deletions simba/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import subprocess
from datetime import datetime, timedelta

from spice_ev.costs import COST_CALCULATION
from spice_ev.strategy import STRATEGIES
from spice_ev.util import set_options_from_config

Expand Down Expand Up @@ -531,11 +532,11 @@ def get_parser():
parser.add_argument('--strategy-opps', default='greedy', choices=STRATEGIES,
help='strategy to use at station')

# #### Cost calculation strategy #####
parser.add_argument('--cost-calculation-strategy-deps', choices=STRATEGIES,
help='Strategy for cost calculation to use in depot')
parser.add_argument('--cost-calculation-strategy-opps', choices=STRATEGIES,
help='Strategy for cost calculation to use at station')
# #### Cost calculation method #####
parser.add_argument('--cost-calculation-method-deps', choices=COST_CALCULATION,
help='Cost calculation to use in depot')
parser.add_argument('--cost-calculation-method-opps', choices=COST_CALCULATION,
help='Cost calculation to use at station')

parser.add_argument('--strategy-options-deps', default={},
type=lambda s: s if type(s) is dict else json.loads(s),
Expand Down
31 changes: 13 additions & 18 deletions tests/test_cost_calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ def test_cost_calculation(self):
assert args.strategy_deps == "balanced"
assert args.strategy_opps == "greedy"

args.cost_calculation_strategy_deps = None
args.cost_calculation_strategy_opps = None
args.cost_calculation_method_deps = None
args.cost_calculation_method_opps = None

costs_vanilla = calculate_costs(cost_params, scenario, schedule, args)

assert args.strategy_deps == "balanced"
assert args.strategy_opps == "greedy"

args.cost_calculation_strategy_deps = "balanced"
args.cost_calculation_strategy_opps = "greedy"
args.cost_calculation_method_deps = "fixed_wo_plw"
args.cost_calculation_method_opps = "fixed_wo_plw"
costs_with_same_strat = calculate_costs(cost_params, scenario, schedule, args)

# assert all costs are the same
Expand All @@ -31,8 +31,9 @@ def test_cost_calculation(self):
assert (costs_vanilla.costs_per_gc[station][key] ==
costs_with_same_strat.costs_per_gc[station][key]), station

args.cost_calculation_strategy_opps = "balanced_market"
args.cost_calculation_strategy_deps = "balanced_market"
# test with different method (balanced_market costs for balanced strategy)
args.cost_calculation_method_deps = "balanced_market"
args.cost_calculation_method_opps = "balanced_market"
costs_with_other_strat = calculate_costs(cost_params, scenario, schedule, args)
print(costs_vanilla.costs_per_gc["cumulated"]["c_total_annual"])
print(costs_with_other_strat.costs_per_gc["cumulated"]["c_total_annual"])
Expand All @@ -43,21 +44,15 @@ def test_cost_calculation(self):
assert (costs_vanilla.costs_per_gc[station][key] !=
costs_with_other_strat.costs_per_gc[station][key]), key

args.cost_calculation_strategy_opps = "peak_load_window"
args.cost_calculation_strategy_deps = "peak_load_window"
# PLW: will create window time series before cost calculation
args.cost_calculation_method_deps = "fixed_w_plw"
args.cost_calculation_method_opps = "fixed_w_plw"
costs_with_other_strat = calculate_costs(cost_params, scenario, schedule, args)
"""
station = "cumulated"
for key in costs_vanilla.costs_per_gc[station]:
if "el_energy" not in key:
continue
assert (costs_vanilla.costs_per_gc[station][key] !=
costs_with_other_strat.costs_per_gc[station][key]), key

args.cost_calculation_strategy_opps = "peak_shaving"
args.cost_calculation_strategy_deps = "peak_shaving"
costs_with_other_strat = calculate_costs(cost_params, scenario, schedule, args)
# assert all costs are the same
for station in costs_vanilla.costs_per_gc:
for key in costs_vanilla.costs_per_gc[station]:
assert (costs_vanilla.costs_per_gc[station][key] ==
costs_with_other_strat.costs_per_gc[station][key]), station
costs_with_other_strat.costs_per_gc[station][key]), key
"""
26 changes: 26 additions & 0 deletions tests/test_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,32 @@ def basic_run(self):


class TestSchedule(BasicSchedule):
def test_allow_opp_charging(self):
# test if the schedule properly skips charging events if the rotation is not allowed
# to opportunity charge
sched, scen, args = BasicSchedule().basic_run()
oppb_rotations = [rot for rot in sched.rotations.values() if rot.charging_type == "oppb"]
assert len(oppb_rotations) >= 1
oppb_rotation = oppb_rotations[0]
vehicle = oppb_rotation.vehicle_id
assert oppb_rotation.allow_opp_charging_for_oppb is True
index = list(scen.components.vehicles.keys()).index(vehicle)
min_soc = min(s[index] for s in scen.socs if s[index] is not None)
vehicle_event = [e for e in scen.events.vehicle_events if e.vehicle_id == vehicle]
charge_events = 0
for e in vehicle_event:
if vars(e).get("update", {}).get("connected_charging_station") is not None:
charge_events += 1

assert charge_events > 0, \
"Rotation has no charge events to check if allow_opp_charging_for_oppb works"
for rot in oppb_rotations:
rot.allow_opp_charging_for_oppb = False
scen2 = sched.run(args)

index = list(scen2.components.vehicles.keys()).index(vehicle)
min_soc_charging_not_allowed = min(s[index] for s in scen2.socs if s[index] is not None)
assert min_soc_charging_not_allowed < min_soc

def test_optional_timeseries(self):
# Test if simulation runs if level of loading and temperature timeseries is not given
Expand Down

0 comments on commit 0a726ba

Please sign in to comment.