Skip to content
Snippets Groups Projects
Commit 2ff24fdd authored by Christoph von Oy's avatar Christoph von Oy
Browse files

Merge branch 'dev_jgn_bus_charging' into 'main'

Implementation of Bus Charging Stations

See merge request focus/model-library!21
parents 39988265 22dd4e38
No related branches found
No related tags found
No related merge requests found
Showing
with 616 additions and 14 deletions
{
"efficiency": 0.97,
"factor_repair_effort": 0.01,
"factor_servicing_effort": 0.015,
"servicing_effort_hours": 20,
"service_life": 20,
"cost": 250
}
\ No newline at end of file
{
"input_efficiency": 0.99,
"output_efficiency": 0.99,
"cost": 1000,
"fix_cost_factor": 0.008,
"service_life": 20,
"e2p_in": 1,
"e2p_out": 1,
"factor_repair_effort": 0.01,
"factor_servicing_effort": 0.015,
"servicing_effort_hours": 20,
"max_soe": 1,
"min_soe": 0,
"init_soe": 0.5
}
\ No newline at end of file
{
"input_efficiency": 0.99,
"output_efficiency": 0.99,
"cost": 1000,
"fix_cost_factor": 0.008,
"service_life": 20,
"e2p_in": 0.2,
"e2p_out": 0.2,
"factor_repair_effort": 0.01,
"factor_servicing_effort": 0.015,
"servicing_effort_hours": 20,
"max_soe": 1,
"min_soe": 0,
"init_soe": 0.5
}
\ No newline at end of file
efficiency,factor repair effort,factor servicing effort,servicing effort hours,service life,cost
0.95,0.01,0.015,20,20,250
\ No newline at end of file
......@@ -29,8 +29,9 @@ class ComponentKind(Enum):
BASE = 2,
BUSBAR = 3,
CONSUMPTION = 4,
GRID = 5,
STORAGE = 6
GENERATION = 5,
GRID = 6,
STORAGE = 7
class ComponentCommodity(Enum):
ALL = 1,
......
......@@ -44,6 +44,7 @@ class BaseConsumption(AbstractComponent):
self.commodity = commodity
if configuration['type'] != 'DrivingConsumption':
if isinstance(configuration['consumption'], str):
self.consumption = resample(profiles[configuration['consumption']][0], profiles[configuration['consumption']][1], dynamic)
elif isinstance(configuration['consumption'], dict):
......
"""
MIT License
Copyright (c) 2023 RWTH Aachen University
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from Model_Library.Component.model.AbstractComponent import AbstractComponent, ComponentKind, ComponentCommodity
from Tooling.predictor.Predictor import Predictor
from Tooling.dynamics.Dynamic import resample
import pyomo.environ as pyo
class BaseGeneration(AbstractComponent):
def __init__(self, name, type, commodity, configuration, profiles, dynamic):
super().__init__(name=name,
type=type,
commodity_1=None,
commodity_2=None,
commodity_3=commodity,
commodity_4=None,
min_size=None,
max_size=None,
flexible=None,
dynamic=dynamic)
self.commodity = commodity
if configuration['type'] != 'ElectricalGeneration':
if isinstance(configuration['generation'], str):
self.generation = resample(profiles[configuration['generation']][0], profiles[configuration['generation']][1], dynamic)
elif isinstance(configuration['generation'], dict):
self.generation = Predictor(resample(profiles[configuration['generation']['profile']][0], profiles[configuration['generation']['profile']][1], dynamic), configuration['generation']['type'], configuration['generation']['method'], dynamic)
def match(self, kind=ComponentKind.ALL, commodity=ComponentCommodity.ALL):
match_kind = kind == ComponentKind.ALL or kind == ComponentKind.GENERATION
match_commodity = commodity == ComponentCommodity.ALL or \
commodity == self.commodity or \
(isinstance(commodity, list) and self.commodity in commodity)
return match_kind and match_commodity
def get_base_variable_names(self):
return [((self.name, 'output_1'), 'T')]
def _add_variables(self, model, prefix):
model.add(prefix + ('output_1',), pyo.Var(model.T, bounds=(0, None)))
def _add_constraints(self, model, prefix, configuration):
model.add(prefix + ('generation',), pyo.Param(model.T, mutable = True))
if isinstance(self.generation, Predictor):
if 'predict' in configuration:
generation = self.generation.predict(list(model.T))
else:
generation = resample(self.generation.profile, self.dynamic, model.dynamic)
else:
generation = resample(self.generation, self.dynamic, model.dynamic)
model.set_value(prefix + ('generation',), generation)
def rule(m, t):
return model.component_dict[prefix + ('output_1',)][t] == model.component_dict[prefix + ('generation',)][t]
model.add(prefix + ('generation_cons',), pyo.Constraint(model.T, rule = rule))
"""
MIT License
Copyright (c) 2023 RWTH Aachen University
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import pandas
import pandas as pd
from Model_Library.Component.model.AbstractComponent import ComponentCommodity
from Model_Library.Component.model.BaseConsumption import BaseConsumption
import pyomo.environ as pyo
class DrivingConsumption(BaseConsumption):
def __init__(self, name, configuration, model_directory, profiles, dynamic):
super().__init__(name=name,
type='DrivingConsumption',
commodity=ComponentCommodity.ELECTRICITY,
configuration=configuration,
profiles=profiles,
dynamic=dynamic)
if isinstance(configuration['consumption'], str):
if profiles[configuration['consumption']][0].ndim == 1: # object is pandas series, no column names
self.consumption = profiles[configuration['consumption']][0]
else: # object is pandas dataframe, column names can be used
self.consumption = profiles[configuration['consumption']][0].loc[:, 'Power']
def _add_constraints(self, model, prefix, configuration):
super()._add_constraints(model, prefix, configuration)
\ No newline at end of file
......@@ -2,5 +2,6 @@
# -*- coding:utf-8 -*-
# Created by jgn on 30.11.2020.
from .DrivingConsumption import DrivingConsumption
from .ElectricalConsumption import ElectricalConsumption
......@@ -23,14 +23,20 @@ THE SOFTWARE.
"""
from Model_Library.Component.model.AbstractComponent import ComponentCommodity
from Model_Library.Component.model.BaseConsumption import BaseConsumption
from Model_Library.Component.model.BaseGeneration import BaseGeneration
class ChargingInfrastructure(BaseConsumption):
class ElectricalGeneration(BaseGeneration):
def __init__(self, name, configuration, model_directory, profiles, dynamic):
super().__init__(name=name,
type='ChargingInfrastructure',
type='ElectricalGeneration',
commodity=ComponentCommodity.ELECTRICITY,
configuration=configuration,
profiles=profiles,
dynamic=dynamic)
if isinstance(configuration['generation'], str):
if profiles[configuration['generation']][0].ndim == 1: # object is pandas series, no column names
self.generation = profiles[configuration['generation']][0]
else: # object is pandas dataframe, column names (Power,Driving)
self.generation = profiles[configuration['generation']][0].loc[:, 'Power']
......@@ -2,4 +2,5 @@
# -*- coding:utf-8 -*-
# Created by jgn on 30.11.2020.
from .ChargingInfrastructure import ChargingInfrastructure
from .ElectricalGeneration import ElectricalGeneration
"""
MIT License
Copyright (c) 2023 RWTH Aachen University
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from Model_Library.Component.model.AbstractComponent import ComponentCommodity
from Model_Library.Component.model.BaseComponent import BaseComponent
import pyomo.environ as pyo
class DcDcConverter(BaseComponent):
def __init__(self, name, configuration, model_directory, profiles, dynamic):
super().__init__(name=name,
type='DcDcConverter',
commodity_1=ComponentCommodity.ELECTRICITY,
commodity_2=ComponentCommodity.ELECTRICITY,
commodity_3=ComponentCommodity.ELECTRICITY,
commodity_4=ComponentCommodity.ELECTRICITY,
configuration=configuration,
model_directory=model_directory,
dynamic=dynamic)
def get_input_output_variable_names(self):
return [((self.name, 'input_1'), 'T'), ((self.name, 'input_2'), 'T'), ((self.name, 'output_1'), 'T'), ((self.name, 'output_2'), 'T')]
def add_input_output_variables(self, model, prefix):
model.add(prefix + ('input_1',), pyo.Var(model.T, bounds=(0, None)))
model.add(prefix + ('input_2',), pyo.Var(model.T, bounds=(0, None)))
model.add(prefix + ('output_1',), pyo.Var(model.T, bounds=(0, None)))
model.add(prefix + ('output_2',), pyo.Var(model.T, bounds=(0, None)))
def _add_constraints(self, model, prefix, configuration):
super()._add_constraints(model, prefix, configuration)
self._constraint_bi_flow(model, prefix, configuration)
def _constraint_capacity(self, model, prefix):
def rule(m, t):
return model.component_dict[prefix + ('input_1',)][t] <= model.component_dict[prefix + ('capacity',)]
model.add(prefix + ('capacity_cons_1',), pyo.Constraint(model.T, rule = rule))
def rule(m, t):
return model.component_dict[prefix + ('output_1',)][t] <= model.component_dict[prefix + ('capacity',)]
model.add(prefix + ('capacity_cons_2',), pyo.Constraint(model.T, rule = rule))
def _constraint_conser(self, model, prefix, configuration):
def rule(m, t):
return model.component_dict[prefix + ('output_2',)][t] == model.component_dict[prefix + ('input_1',)][t] * self.efficiency
model.add(prefix + ('conser_1',), pyo.Constraint(model.T, rule = rule))
def rule(m, t):
return model.component_dict[prefix + ('output_1',)][t] == model.component_dict[prefix + ('input_2',)][t] * self.efficiency
model.add(prefix + ('conser_2',), pyo.Constraint(model.T, rule = rule))
def _constraint_bi_flow(self, model, prefix, configuration):
model.add(prefix + ('z_1',), pyo.Var(model.T, domain=pyo.Binary))
model.add(prefix + ('z_2',), pyo.Var(model.T, domain=pyo.Binary))
def rule(m, t):
return model.component_dict[prefix + ('z_1',)][t] + model.component_dict[prefix + ('z_2',)][t] <= 1
model.add(prefix + ('bi_flow_1',), pyo.Constraint(model.T, rule = rule))
def rule(m, t):
return model.component_dict[prefix + ('input_1',)][t] <= model.component_dict[prefix + ('z_1',)][t] * 100000
model.add(prefix + ('bi_flow_2',), pyo.Constraint(model.T, rule = rule))
def rule(m, t):
return model.component_dict[prefix + ('output_1',)][t] <= model.component_dict[prefix + ('z_2',)][t] * 100000
model.add(prefix + ('bi_flow_3',), pyo.Constraint(model.T, rule = rule))
# Flexibility stuff
def check_limits(self, input_flows, output_flows, results, time_steps):
"""
ToDO JBR: clean
calculate the consequences of maximal positive and negative flexibility. Positive flexibility can
increase (pos_flex_inc) and decrease (pos_flex_dec) the inputs of an Inverter.
The negative flexibility increases/decreases the outputs of the Inverter.
Therefore, they have to be transformed to input values.
Parameters
----------
input_flows: incoming power flows
output_flows: outgoing power flows
results: df with iniital prosumer results. Needed for obtaining the initial component size.
time_steps: time steps of current RH interval
"""
# flexibility that was already translated to the input side by def adjust_with_efficiency
pos_flex_dec = self.temp_flex['flex_pos_dec'][time_steps]
neg_flex_inc = self.temp_flex['flex_neg_inc'][time_steps]
power_limit = 1 * results[(self.name, 'capacity')] # excluding the excess power for safety
# ----------------CHECK POWER--------------------
# additional input power from positive flexibility
# the increasing part has to be retransformed to the 'other' side of the comp becaus ethat ois the value that is really increased
# the decreasing part stays the same because the input flow of this flex type comes from the grid
max_input_1 = input_flows + self.flex_pos_inc_old - pos_flex_dec
# the decreasing part has to be retransformed to the 'other' side of the comp becaus ethat ois the value that is really increased
# the increasing part stays the same because the input flow of this flex type comes from the grid
# max_input_2 = neg_flex_inc.combine(input_flows - self.flex_neg_dec_old, max) # additional input power from negative flexibility
max_input_2 = input_flows + neg_flex_inc - self.flex_neg_dec_old # additional input power from negative flexibility
# upper limit for pos-inc df
upper_limit = power_limit - input_flows + pos_flex_dec
upper_limit.loc[upper_limit < 0] = 0 # account for the case input_flows > power_limit (excess)
# max pos-inc df on input side of converter
max_pos_inc = self.flex_pos_inc_old.combine(upper_limit, min)
excess_input_1 = max_input_1 - power_limit
excess_input_2 = max_input_2 - power_limit
excess_input_1.loc[excess_input_1 < 0] = 0
excess_input_2.loc[excess_input_2 < 0] = 0
# adjust the flexibility values
# the energy values are not affected because they are still availabele even if inter_comps can not handle the amount
trans_excess_1 = excess_input_1 * self.efficiency
trans_excess_1.loc[trans_excess_1 < 0] = 0
self.temp_flex['flex_pos_inc'] -= trans_excess_1 # excess has to be transformed to inv output value
self.temp_flex.loc[self.temp_flex['flex_pos_inc'] < 0, 'flex_pos_inc'] = 0
self.temp_flex['flex_neg_inc'] -= excess_input_2
self.temp_flex.loc[self.temp_flex['flex_neg_inc'] < 0, 'flex_neg_inc'] = 0
self.temp_flex['flex_pos'] = self.temp_flex['flex_pos_inc'] + self.temp_flex['flex_pos_dec']
self.temp_flex['flex_neg'] = self.temp_flex['flex_neg_inc'] + self.temp_flex['flex_neg_dec']
......@@ -90,6 +90,50 @@ class StaticBiInverter(BaseComponent):
return model.component_dict[prefix + ('output_1',)][t] <= model.component_dict[prefix + ('z_2',)][t] * 100000
model.add(prefix + ('bi_flow_3',), pyo.Constraint(model.T, rule = rule))
def add_operating_costs(self, model, configuration):
# penalize fluctuation of charging curve
prefix = (self.name,)
P = self.max_size
model.add(prefix + ('z_increase',), pyo.Var(model.T, domain=pyo.Binary, initialize=0))
model.add(prefix + ('z_decrease',), pyo.Var(model.T, domain=pyo.Binary, initialize=0))
set_1 = set(list(model.T)[1:])
set_2 = set(list(model.T)[1:-1])
def rule_1(m):
return model.component_dict[prefix + ('z_increase',)][0] == 0
def rule_2(m):
return model.component_dict[prefix + ('z_decrease',)][0] == 0
def rule_3(m,t):
return model.component_dict[prefix + ('z_increase',)][t] >= (model.component_dict[prefix + ('output_1',)][t] - model.component_dict[prefix + ('output_1',)][t-1]) / P
def rule_4(m,t):
return model.component_dict[prefix + ('z_decrease',)][t] >= (model.component_dict[prefix + ('output_1',)][t-1] - model.component_dict[prefix + ('output_1',)][t]) / P
def rule_5(m, t):
return model.component_dict[prefix + ('z_increase',)][t] + model.component_dict[prefix + ('z_decrease',)][t] <= 1
def rule_6(m,t):
return model.component_dict[prefix + ('power_fluctuation',)][t] == (model.component_dict[prefix + ('output_1',)][t] - model.component_dict[prefix + ('output_1',)][t-1]) * model.component_dict[prefix + ('z_increase',)][t] + \
(model.component_dict[prefix + ('output_1',)][t-1] - model.component_dict[prefix + ('output_1',)][t]) * model.component_dict[prefix + ('z_decrease',)][t]
# t=0
model.add(prefix + ('penalty_1',), pyo.Constraint(rule=rule_1))
model.add(prefix + ('penalty_2',), pyo.Constraint(rule=rule_2))
# t=1
model.add(prefix + ('penalty_3',), pyo.Constraint(set_1, rule=rule_3))
model.add(prefix + ('penalty_4',), pyo.Constraint(set_1, rule=rule_4))
model.add(prefix + ('penalty_5',), pyo.Constraint(set_1, rule=rule_5))
model.add(prefix + ('power_fluctuation',), pyo.Var(model.T, within=pyo.NonNegativeReals, initialize=0))
model.add(prefix + ('penalty_6',), pyo.Constraint(set_2, rule=rule_6))
#penalty_cost = pyo.quicksum(model.component_dict[prefix + ('power_fluctuation',)][t] for t in model.T) * 0.001
model.add(prefix + ('operating_cost',), pyo.Var(model.T))
def rule_cost(m,t):
return model.component_dict[prefix + ('operating_cost',)][t] == model.component_dict[prefix + ('power_fluctuation',)][t] * 0.001
model.add(prefix + ('operating_cost_cons',), pyo.Constraint(model.T, rule=rule_cost))
return prefix + ('operating_cost',)
# Flexibility stuff
def check_limits(self, input_flows, output_flows, results, dynamic):
......
......@@ -25,6 +25,8 @@ THE SOFTWARE.
from Model_Library.Component.model.AbstractComponent import ComponentCommodity
from Model_Library.Component.model.BaseComponent import BaseComponent
import pyomo.environ as pyo
class StaticInverter(BaseComponent):
def __init__(self, name, configuration, model_directory, profiles, dynamic):
......@@ -38,6 +40,50 @@ class StaticInverter(BaseComponent):
model_directory=model_directory,
dynamic=dynamic)
def add_operating_costs(self, model, configuration):
# penalize fluctuation of charging curve
prefix = (self.name,)
P = self.max_size # change to size of inverter
model.add(prefix + ('z_increase',), pyo.Var(model.T, domain=pyo.Binary, initialize=0))
model.add(prefix + ('z_decrease',), pyo.Var(model.T, domain=pyo.Binary, initialize=0))
set_1 = set(list(model.T)[1:])
set_2 = set(list(model.T)[1:-1])
def rule_1(m):
return model.component_dict[prefix + ('z_increase',)][0] == 0
def rule_2(m):
return model.component_dict[prefix + ('z_decrease',)][0] == 0
def rule_3(m,t):
return model.component_dict[prefix + ('z_increase',)][t] >= (model.component_dict[prefix + ('output_1',)][t] - model.component_dict[prefix + ('output_1',)][t-1]) / P
def rule_4(m,t):
return model.component_dict[prefix + ('z_decrease',)][t] >= (model.component_dict[prefix + ('output_1',)][t-1] - model.component_dict[prefix + ('output_1',)][t]) / P
def rule_5(m, t):
return model.component_dict[prefix + ('z_increase',)][t] + model.component_dict[prefix + ('z_decrease',)][t] <= 1
def rule_6(m,t):
return model.component_dict[prefix + ('power_fluctuation',)][t] == (model.component_dict[prefix + ('output_1',)][t] - model.component_dict[prefix + ('output_1',)][t-1]) * model.component_dict[prefix + ('z_increase',)][t] + \
(model.component_dict[prefix + ('output_1',)][t-1] - model.component_dict[prefix + ('output_1',)][t]) * model.component_dict[prefix + ('z_decrease',)][t]
# t=0
model.add(prefix + ('penalty_1',), pyo.Constraint(rule=rule_1))
model.add(prefix + ('penalty_2',), pyo.Constraint(rule=rule_2))
# t=1
model.add(prefix + ('penalty_3',), pyo.Constraint(set_1, rule=rule_3))
model.add(prefix + ('penalty_4',), pyo.Constraint(set_1, rule=rule_4))
model.add(prefix + ('penalty_5',), pyo.Constraint(set_1, rule=rule_5))
model.add(prefix + ('power_fluctuation',), pyo.Var(model.T, within=pyo.NonNegativeReals, initialize=0))
model.add(prefix + ('penalty_6',), pyo.Constraint(set_2, rule=rule_6))
#penalty_cost = pyo.quicksum(model.component_dict[prefix + ('power_fluctuation',)][t] for t in model.T) * 0.001
model.add(prefix + ('operating_cost',), pyo.Var(model.T))
def rule_cost(m,t):
return model.component_dict[prefix + ('operating_cost',)][t] == model.component_dict[prefix + ('power_fluctuation',)][t] * 0.001
model.add(prefix + ('operating_cost_cons',), pyo.Constraint(model.T, rule=rule_cost))
return prefix + ('operating_cost',)
# Flexibility stuff
def check_limits(self, input_flows, output_flows, results, dynamic):
......
......@@ -2,6 +2,7 @@
# -*- coding:utf-8 -*-
# Created by jgn on 30.11.2020.
from .DcDcConverter import DcDcConverter
from .DynamicBiInverter import DynamicBiInverter
from .DynamicInverter import DynamicInverter
from .StaticBiInverter import StaticBiInverter
......
"""
MIT License
Copyright (c) 2023 RWTH Aachen University
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from Model_Library.Component.model.electrical_components.storages.ElecVehicle import ElecVehicle
import pyomo.environ as pyo
class ElecBus(ElecVehicle):
def __init__(self, name, configuration, model_directory, profiles, dynamic, type='ElecBus'):
super().__init__(name=name,
configuration=configuration,
model_directory=model_directory,
profiles=profiles,
dynamic=dynamic,
type=type)
def _add_variables(self, model, prefix):
super()._add_variables(model, prefix)
def _add_constraints(self, model, prefix, configuration):
super()._add_constraints(model, prefix, configuration)
self._constraint_final_soe(model, prefix)
self._constraint_SOC_above_below_x(model, prefix, 0.5)
#self._constraint_min_soe(model, prefix)
def _constraint_conser(self, model, prefix, configuration):
if 'storage_connect' in configuration and self.match(commodity=configuration['storage_connect']['commodity']):
last_energy = configuration['storage_connect']['values'][self.name]
model.add(prefix + ('fix_first_energy',), pyo.Constraint(expr = model.component_dict[prefix + ('energy',)][model.T_prime.first()] == last_energy))
else:
model.add(prefix + ('fix_first_energy',), pyo.Constraint(expr=model.component_dict[prefix + ('energy',)][model.T_prime.first()] <= model.component_dict[prefix + ('capacity',)]))
def rule(m, t):
return model.component_dict[prefix + ('energy',)][t] == model.component_dict[prefix + ('energy',)][model.T_prime[model.T_prime.ord(t) - 1]] + model.component_dict[prefix + ('input_1',)][t] * self.input_efficiency * model.step_size(t) - model.component_dict[prefix + ('output_1',)][t] / self.output_efficiency * model.step_size(t)
model.add(prefix + ('conser',), pyo.Constraint(model.T, rule=rule))
def _constraint_final_soe(self, model, prefix):
model.add(prefix + ('fix_final_energy',), pyo.Constraint(expr=model.component_dict[prefix + ('energy',)][model.T.last()] >= model.component_dict[prefix + ('energy',)][model.T_prime.first()]))
def _constraint_min_soe(self, model, prefix):
def rule(m,t):
return model.component_dict[prefix + ('energy',)][t] >= 0.3 * model.component_dict[prefix + ('capacity',)] * model.component_dict[prefix + ('z_input',)][t]
model.add(prefix + ('min_SOE_while_CHA',), pyo.Constraint(model.T, rule=rule))
def add_operating_costs(self, model, configuration):
# opportunity costs for battery_energy_throughput and for SOC > 50%
prefix = (self.name,)
cost_function = lambda t: model.component_dict[prefix + ('input_1',)][t] * 0.2 + model.component_dict[prefix + ('z_SOC_above_50',)][t] * 0.0
model.add(prefix + ('operating_cost',), pyo.Var(model.T))
def rule(m,t ):
return model.component_dict[prefix + ('operating_cost',)][t] == cost_function(t) * model.step_size(t)
model.add(prefix + ('operating_cost_cons',), pyo.Constraint(model.T, rule=rule))
return prefix + ('operating_cost',)
"""
MIT License
Copyright (c) 2023 RWTH Aachen University
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from Model_Library.Component.model.electrical_components.storages.LiionBattery import LiionBattery
import pyomo.environ as pyo
from pyomo.environ import value
class ElecVehicle(LiionBattery):
def __init__(self, name, configuration, model_directory, profiles, dynamic, type='ElecVehicle'):
super().__init__(name=name,
configuration=configuration,
model_directory=model_directory,
profiles=profiles,
dynamic=dynamic,
type=type)
def _add_variables(self, model, prefix):
super()._add_variables(model, prefix)
def _add_constraints(self, model, prefix, configuration):
super()._add_constraints(model, prefix, configuration)
self._constraint_SOC_above_below_x(model, prefix, 0.3)
self._constraint_SOC_above_below_x(model, prefix, 0.99)
# constraint that introduces two binaries for the given SOC threshold, only 1 can be true at the same time for each respective threshold
# binary = 1 indicates that condition is true
# SOC should be passed as decimal number, not as percentage
def _constraint_SOC_above_below_x(self, model, prefix, soc):
model.add(prefix + ('z_SOC_below_' + str(int(soc*100)),), pyo.Var(model.T, within=pyo.Binary))
model.add(prefix + ('z_SOC_above_' + str(int(soc*100)),), pyo.Var(model.T, within=pyo.Binary))
def rule(m,t):
return 1000 * self.max_size * model.component_dict[prefix + ('z_SOC_below_' + str(int(soc*100)),)][t] >= soc * 0.9999 * model.component_dict[prefix + ('capacity',)] - model.component_dict[prefix + ('energy',)][t]
model.add(prefix + ('SOC_below_' + str(int(soc*100)) + '_cons',), pyo.Constraint(model.T, rule=rule))
def rule(m,t):
return 1000 * self.max_size * model.component_dict[prefix + ('z_SOC_above_' + str(int(soc*100)),)][t] >= model.component_dict[prefix + ('energy',)][t] - soc * model.component_dict[prefix + ('capacity',)]
model.add(prefix + ('SOC_above_' + str(int(soc*100)) + '_cons',), pyo.Constraint(model.T, rule=rule))
def rule(m,t):
return model.component_dict[prefix + ('z_SOC_above_' + str(int(soc*100)),)][t] + model.component_dict[prefix + ('z_SOC_below_' + str(int(soc*100)),)][t] <= 1
model.add(prefix + ('SOC_below_above_' + str(int(soc*100)) + '_cons',), pyo.Constraint(model.T, rule=rule))
......@@ -27,9 +27,9 @@ from Model_Library.Component.model.BaseStorage import BaseStorage
class LiionBattery(BaseStorage):
def __init__(self, name, configuration, model_directory, profiles, dynamic):
def __init__(self, name, configuration, model_directory, profiles, dynamic, type='LiionBattery'):
super().__init__(name=name,
type='LiionBattery',
type=type,
commodity=ComponentCommodity.ELECTRICITY,
configuration=configuration,
model_directory=model_directory,
......
......@@ -3,3 +3,5 @@
# Created by jgn on 30.11.2020.
from .LiionBattery import LiionBattery
from .ElecVehicle import ElecVehicle
from .ElecBus import ElecBus
......@@ -28,6 +28,10 @@ THE SOFTWARE.
import Model_Library.Prosumer.model.Prosumer as Prosumer
import Model_Library.Prosumer.model.DistrictAsset as DistrictAsset
import Model_Library.Prosumer.model.BusChargingStation as BusChargingStation
import Model_Library.Prosumer.model.BusChargingStationWithHPC as BusChargingStationWithHPC
import Model_Library.Prosumer.model.OverheadLineForIMC as OverheadLineForIMC
class ProsumerMain:
def __init__(self, configurations, input_profiles, dynamic):
......@@ -35,6 +39,13 @@ class ProsumerMain:
for name, configuration in configurations.items():
# Create prosumer object
if configuration['type'].lower() == 'buschargingstation':
self.prosumers[name] = BusChargingStation(name, configuration, input_profiles, dynamic)
elif configuration['type'].lower() == 'buschargingstationwithhpc':
self.prosumers[name] = BusChargingStationWithHPC(name, configuration, input_profiles, dynamic)
elif configuration['type'].lower() == 'overheadlineforimc':
self.prosumers[name] = OverheadLineForIMC(name, configuration, input_profiles, dynamic)
else:
self.prosumers[name] = Prosumer(name, configuration, input_profiles, dynamic)
def optimize_sizing(self, key, prosumer_sizing_strategy):
......@@ -51,6 +62,7 @@ class ProsumerMain:
for ps in self.prosumers.values():
ps.save_results()
class DistrictAssetMain:
def __init__(self, configurations, input_profiles, dynamic):
self.district_assets = {}
......@@ -58,3 +70,25 @@ class DistrictAssetMain:
for name, configuration in configurations.items():
# Create district_asset object
self.district_assets[name] = DistrictAsset(name, configuration, input_profiles, dynamic)
# class BusChargingStationMain:
# def __init__(self, configurations, input_profiles, t_horizon, t_step):
# self.bus_charging_stations = {}
#
# for name, configuration in configurations.items():
# # Create prosumer object
# self.bus_charging_stations[name] = BusChargingStation(name, configuration, input_profiles, t_horizon, t_step)
#
# def optimize_sizing(self, bus_charging_station_sizing_strategy):
# for bcs in self.bus_charging_stations.values():
# bcs.optimize_sizing(bus_charging_station_sizing_strategy)
#
# def pareto_analysis(self, bus_charging_station_sizing_strategy_1, bus_charging_station_sizing_strategy_2):
# for bcs in self.bus_charging_stations.values():
# bcs.pareto_analysis(bus_charging_station_sizing_strategy_1, bus_charging_station_sizing_strategy_2)
#
# def save_results(self):
# for bcs in self.bus_charging_stations.values():
# bcs.save_results()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment