Skip to content

Commit cf58a68

Browse files
authored
Merge pull request #68 from FBumann/feature/fixed-profile
Feature/fixed profile
2 parents d146bf2 + 3ba030a commit cf58a68

File tree

10 files changed

+59
-54
lines changed

10 files changed

+59
-54
lines changed

examples/Ex00_minmal/minimal_example.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@
3333

3434
# Heat load component with a fixed thermal demand profile
3535
heat_load = fx.Sink('Heat Demand',
36-
sink=fx.Flow(label='Thermal Load', bus=heat_bus, size=1, relative_maximum=max(thermal_load_profile),
37-
fixed_relative_value=thermal_load_profile))
36+
sink=fx.Flow(label='Thermal Load', bus=heat_bus, size=1, fixed_relative_profile=thermal_load_profile))
3837

3938
# Gas source component with cost-effect per flow hour
4039
gas_source = fx.Source('Natural Gas Tariff',

examples/Ex01_simple/simple_example.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@
6060
# Heat Demand Sink: Represents a fixed heat demand profile
6161
heat_sink = fx.Sink(label='Heat Demand',
6262
sink=fx.Flow(label='Q_th_Last', bus=Fernwaerme, size=1,
63-
relative_maximum=max(heat_demand_per_h),
64-
fixed_relative_value=heat_demand_per_h))
63+
fixed_relative_profile=heat_demand_per_h))
6564

6665
# Gas Source: Gas tariff source with associated costs and CO2 emissions
6766
gas_source = fx.Source(label='Gastarif',

examples/Ex02_complex/complex_example.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@
109109
# 5.a) Heat demand profile
110110
Waermelast = fx.Sink('Wärmelast', sink=fx.Flow('Q_th_Last', # Heat sink
111111
bus=Fernwaerme, # Linked bus
112-
size=1, relative_maximum=max(heat_demand), fixed_relative_value=heat_demand)) # Fixed demand profile
112+
size=1,
113+
fixed_relative_profile=heat_demand)) # Fixed demand profile
113114

114115
# 5.b) Gas tariff
115116
Gasbezug = fx.Source('Gastarif', source=fx.Flow('Q_Gas', bus=Gas, # Gas source

examples/Ex03_calculation_types/example_calculation_types.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,11 @@
9292
# 4. Sinks and Sources
9393
# Heat Load Profile
9494
a_waermelast = fx.Sink('Wärmelast',
95-
sink=fx.Flow('Q_th_Last', bus=Fernwaerme, size=1,
96-
relative_maximum=max(TS_heat_demand.data), fixed_relative_value=TS_heat_demand))
95+
sink=fx.Flow('Q_th_Last', bus=Fernwaerme, size=1,fixed_relative_profile=TS_heat_demand))
9796

9897
# Electricity Feed-in
9998
a_strom_last = fx.Sink('Stromlast',
100-
sink=fx.Flow('P_el_Last', bus=Strom, size=1,
101-
relative_maximum=max(TS_electricity_demand.data),
102-
fixed_relative_value=TS_electricity_demand))
99+
sink=fx.Flow('P_el_Last', bus=Strom, size=1, fixed_relative_profile=TS_electricity_demand))
103100

104101
# Gas Tariff
105102
a_gas_tarif = fx.Source('Gastarif',

flixOpt/components.py

+14-10
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ def __init__(self, element: Storage):
269269
def do_modeling(self, system_model):
270270
super().do_modeling(system_model)
271271

272-
lb, ub = self.charge_state_bounds
272+
lb, ub = self.absolute_charge_state_bounds
273273
self.charge_state = create_variable('charge_state', self, system_model.nr_of_time_steps + 1, lower_bound=lb,
274274
upper_bound=ub)
275275

@@ -303,10 +303,8 @@ def do_modeling(self, system_model):
303303
1 / self.element.eta_discharge.active_data * system_model.dt_in_hours)
304304

305305
if isinstance(self.element.capacity_in_flow_hours, InvestParameters):
306-
self._investment = InvestmentModel(
307-
self.element, self.element.capacity_in_flow_hours, self.charge_state,
308-
(self.element.relative_minimum_charge_state.active_data,
309-
self.element.relative_maximum_charge_state.active_data))
306+
self._investment = InvestmentModel(self.element, self.element.capacity_in_flow_hours, self.charge_state,
307+
self.relative_charge_state_bounds)
310308
self.sub_models.append(self._investment)
311309
self._investment.do_modeling(system_model)
312310

@@ -346,13 +344,19 @@ def _model_initial_and_final_charge_state(self, system_model):
346344
eq_min.add_constant(- self.element.minimal_final_charge_state)
347345

348346
@property
349-
def charge_state_bounds(self) -> Tuple[Numeric, Numeric]:
347+
def absolute_charge_state_bounds(self) -> Tuple[Numeric, Numeric]:
348+
relative_lower_bound, relative_upper_bound = self.relative_charge_state_bounds
350349
if not isinstance(self.element.capacity_in_flow_hours, InvestParameters):
351-
return (self.element.relative_minimum_charge_state.active_data * self.element.capacity_in_flow_hours,
352-
self.element.relative_maximum_charge_state.active_data * self.element.capacity_in_flow_hours)
350+
return (relative_lower_bound * self.element.capacity_in_flow_hours,
351+
relative_upper_bound * self.element.capacity_in_flow_hours)
353352
else:
354-
return (self.element.relative_minimum_charge_state.active_data * self.element.capacity_in_flow_hours.minimum_size,
355-
self.element.relative_maximum_charge_state.active_data * self.element.capacity_in_flow_hours.maximum_size)
353+
return (relative_lower_bound * self.element.capacity_in_flow_hours.minimum_size,
354+
relative_upper_bound * self.element.capacity_in_flow_hours.maximum_size)
355+
356+
@property
357+
def relative_charge_state_bounds(self) -> Tuple[Numeric, Numeric]:
358+
return (self.element.relative_minimum_charge_state.active_data,
359+
self.element.relative_maximum_charge_state.active_data)
356360

357361

358362
class SourceAndSink(Component):

flixOpt/core.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ def __init__(self,
3636
EXAMPLE solar:
3737
you have several solar timeseries. These should not be overweighted
3838
compared to the remaining timeseries (i.g. heat load, price)!
39-
fixed_relative_value_solar1 = TimeSeriesData(sol_array_1, type = 'solar')
40-
fixed_relative_value_solar2 = TimeSeriesData(sol_array_2, type = 'solar')
41-
fixed_relative_value_solar3 = TimeSeriesData(sol_array_3, type = 'solar')
39+
fixed_relative_profile_solar1 = TimeSeriesData(sol_array_1, type = 'solar')
40+
fixed_relative_profile_solar2 = TimeSeriesData(sol_array_2, type = 'solar')
41+
fixed_relative_profile_solar3 = TimeSeriesData(sol_array_3, type = 'solar')
4242
--> this 3 series of same type share one weight, i.e. internally assigned each weight = 1/3
4343
(instead of standard weight = 1)
4444

flixOpt/elements.py

+25-22
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,13 @@ class Flow(Element):
139139
flows are inputs and outputs of components
140140
'''
141141

142-
# static var:
143142
_default_size = 1e9 # Großer Gültigkeitsbereich als Standard
144143

145144
def __init__(self,
146145
label: str,
147-
bus: Bus = None, # TODO: Is this for sure Optional?
146+
bus: Bus,
148147
size: Union[Skalar, InvestParameters] = _default_size,
149-
fixed_relative_value: Optional[Numeric_TS] = None, # TODO: Rename?
148+
fixed_relative_profile: Optional[Numeric_TS] = None,
150149
relative_minimum: Numeric_TS = 0,
151150
relative_maximum: Numeric_TS = 1,
152151
effects_per_flow_hour: Optional[EffectValues] = None,
@@ -190,10 +189,10 @@ def __init__(self,
190189
flow_hours_total_min : TYPE, optional
191190
minimum flow-hours ("flow-work")
192191
(if size is not const, maybe load_factor_min fits better for you!)
193-
fixed_relative_value : scalar, array, TimeSeriesData, optional
192+
fixed_relative_profile : scalar, array, TimeSeriesData, optional
194193
fixed relative values for flow (if given).
195-
val(t) := fixed_relative_value(t) * size(t)
196-
With this value, the flow-value is no opt-variable anymore;
194+
val(t) := fixed_relative_profile(t) * size(t)
195+
With this value, the flow_rate is no opt-variable anymore;
197196
(relative_minimum u. relative_maximum are making sense anymore)
198197
used for fixed load profiles, i.g. heat demand, wind-power, solarthermal
199198
If the load-profile is just an upper limit, use relative_maximum instead.
@@ -204,7 +203,7 @@ def __init__(self,
204203
self.size = size
205204
self.relative_minimum = relative_minimum
206205
self.relative_maximum = relative_maximum
207-
self.fixed_relative_value = fixed_relative_value
206+
self.fixed_relative_profile = fixed_relative_profile
208207

209208
self.load_factor_min = load_factor_min
210209
self.load_factor_max = load_factor_max
@@ -228,7 +227,7 @@ def create_model(self) -> 'FlowModel':
228227
def transform_data(self):
229228
self.relative_minimum = _create_time_series(f'relative_minimum', self.relative_minimum, self)
230229
self.relative_maximum = _create_time_series(f'relative_maximum', self.relative_maximum, self)
231-
self.fixed_relative_value = _create_time_series(f'fixed_relative_value', self.fixed_relative_value, self)
230+
self.fixed_relative_profile = _create_time_series(f'fixed_relative_profile', self.fixed_relative_profile, self)
232231
self.effects_per_flow_hour = effect_values_to_time_series(f'per_flow_hour', self.effects_per_flow_hour, self)
233232
if self.on_off_parameters is not None:
234233
self.on_off_parameters.transform_data(self)
@@ -245,9 +244,9 @@ def _plausibility_checks(self) -> None:
245244
if np.any(self.relative_minimum > self.relative_maximum):
246245
raise Exception(self.label_full + ': Take care, that relative_minimum <= relative_maximum!')
247246

248-
if self.size == Flow._default_size and self.fixed_relative_value is not None: #Default Size --> Most likely by accident
249-
raise Exception('Achtung: Wenn fixed_relative_value genutzt wird, muss zugehöriges size definiert werden, '
250-
'da: value = fixed_relative_value * size!')
247+
if self.size == Flow._default_size and self.fixed_relative_profile is not None: #Default Size --> Most likely by accident
248+
raise Exception('Achtung: Wenn fixed_relative_profile genutzt wird, muss zugehöriges size definiert werden, '
249+
'da: value = fixed_relative_profile * size!')
251250

252251
@property
253252
def label_full(self) -> str:
@@ -283,15 +282,16 @@ def __init__(self, element: Flow):
283282
def do_modeling(self, system_model: SystemModel):
284283
lower_bound, upper_bound, fixed_flow_rate = 0, None, None # Standard configuration
285284
if not isinstance(self.element.size, InvestParameters):
286-
if self.element.fixed_relative_value is not None: # Size is fixed -> flow_rate can be fixed
287-
fixed_flow_rate = self.element.fixed_relative_value.active_data * self.element.size
288-
if self.element.on_off_parameters is None: # Size is fixed and No On-Variable -> Bounds can be set
285+
if self.element.fixed_relative_profile is not None: # Size is fixed -> flow_rate can be fixed
286+
lower_bound, upper_bound = None, None # Ignoring bounds
287+
fixed_flow_rate = self.element.fixed_relative_profile.active_data * self.element.size
288+
elif self.element.on_off_parameters is None: # Size is fixed, no profile and No On-Variable -> Bounds can be set
289289
lower_bound = self.element.relative_minimum.active_data * self.element.size
290290
upper_bound = self.element.relative_maximum.active_data * self.element.size
291291

292292
# eq relative_minimum(t) * size <= flow_rate(t) <= relative_maximum(t) * size
293-
self.flow_rate = create_variable('flow_rate', self, system_model.nr_of_time_steps, fixed_value=fixed_flow_rate,
294-
lower_bound=lower_bound, upper_bound=upper_bound,
293+
self.flow_rate = create_variable('flow_rate', self, system_model.nr_of_time_steps,
294+
fixed_value=fixed_flow_rate, lower_bound=lower_bound, upper_bound=upper_bound,
295295
previous_values=self.element.previous_flow_rate)
296296

297297
# OnOff
@@ -307,6 +307,7 @@ def do_modeling(self, system_model: SystemModel):
307307
self._investment = InvestmentModel(self.element, self.element.size,
308308
self.flow_rate,
309309
self.relative_flow_rate_bounds,
310+
self.element.fixed_relative_profile,
310311
on_variable=self._on.on if self._on is not None else None)
311312
self._investment.do_modeling(system_model)
312313
self.sub_models.append(self._investment)
@@ -358,19 +359,21 @@ def _create_bounds_for_load_factor(self, system_model: SystemModel):
358359

359360
@property
360361
def absolute_flow_rate_bounds(self) -> Tuple[Numeric, Numeric]:
362+
relative_lower_bound, relative_upper_bound = self.relative_flow_rate_bounds
361363
if not isinstance(self.element.size, InvestParameters):
362-
return (self.element.relative_minimum.active_data * self.element.size,
363-
self.element.relative_maximum.active_data * self.element.size)
364+
return (relative_lower_bound * self.element.size,
365+
relative_upper_bound * self.element.size)
364366
else:
365-
return (self.element.relative_minimum.active_data * self.element.size.minimum_size,
366-
self.element.relative_maximum.active_data * self.element.size.maximum_size)
367+
return (relative_lower_bound * self.element.size.minimum_size,
368+
relative_upper_bound * self.element.size.maximum_size)
367369

368370
@property
369371
def relative_flow_rate_bounds(self) -> Tuple[Numeric, Numeric]:
370-
if self.element.fixed_relative_value is None:
372+
if self.element.fixed_relative_profile is None:
371373
return self.element.relative_minimum.active_data, self.element.relative_maximum.active_data
372374
else:
373-
return self.element.fixed_relative_value.active_data, self.element.fixed_relative_value.active_data
375+
return (np.minimum(self.element.fixed_relative_profile.active_data, self.element.relative_minimum.active_data),
376+
np.maximum(self.element.fixed_relative_profile.active_data, self.element.relative_maximum.active_data))
374377

375378

376379
class BusModel(ElementModel):

flixOpt/features.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ def __init__(self, element: Union['Flow', 'Storage'],
3030
invest_parameters: InvestParameters,
3131
defining_variable: [VariableTS],
3232
relative_bounds_of_defining_variable: Tuple[Numeric, Numeric],
33+
fixed_relative_profile: Optional[Numeric] = None,
3334
label: str = 'Investment',
3435
on_variable: Optional[VariableTS] = None):
3536
"""
36-
if relative_bounds are both equal, then its like a fixed relative value
37+
If fixed relative profile is used, the relative bounds are ignored
3738
"""
3839
super().__init__(element, label)
3940
self.element: Union['Flow', 'Storage'] = element
@@ -45,6 +46,7 @@ def __init__(self, element: Union['Flow', 'Storage'],
4546
self._on_variable = on_variable
4647
self._defining_variable = defining_variable
4748
self._relative_bounds_of_defining_variable = relative_bounds_of_defining_variable
49+
self._fixed_relative_profile = fixed_relative_profile
4850
self._invest_parameters = invest_parameters
4951

5052
def do_modeling(self, system_model: SystemModel):
@@ -121,14 +123,14 @@ def _create_bounds_for_optional_investment(self, system_model: SystemModel):
121123

122124
def _create_bounds_for_defining_variable(self, system_model: SystemModel):
123125
label = self._defining_variable.label
124-
relative_minimum, relative_maximum = self._relative_bounds_of_defining_variable
125126
# fixed relative value
126-
if np.array_equal(relative_minimum, relative_maximum):
127+
if self._fixed_relative_profile is not None:
127128
# TODO: Allow Off? Currently not...
128129
eq_fixed = create_equation(f'fixed_{label}', self)
129130
eq_fixed.add_summand(self._defining_variable, 1)
130-
eq_fixed.add_summand(self.size, np.multiply(-1, relative_maximum))
131+
eq_fixed.add_summand(self.size, np.multiply(-1, self._fixed_relative_profile))
131132
else:
133+
relative_minimum, relative_maximum = self._relative_bounds_of_defining_variable
132134
eq_upper = create_equation(f'ub_{label}', self, 'ineq')
133135
# eq: defining_variable(t) <= size * upper_bound(t)
134136
eq_upper.add_summand(self._defining_variable, 1)

0 commit comments

Comments
 (0)