From 2a34537feeafeea11dff9c7234a7f29dc0d98cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Mon, 26 Aug 2024 12:53:42 +0200 Subject: [PATCH] Add length check (seting it) for sequences For now, sequences that are too long are allowed. We might want to warn in these cases. --- src/oemof/solph/_plumbing.py | 16 ++++++++ .../components/_extraction_turbine_chp.py | 6 +-- .../solph/components/_generic_storage.py | 39 +++++++++---------- .../components/experimental/_sink_dsm.py | 19 ++++----- .../solph/flows/_investment_flow_block.py | 10 ++++- .../solph/flows/_non_convex_flow_block.py | 18 +++++++-- src/oemof/solph/flows/_simple_flow_block.py | 22 ++++++++--- .../flows/experimental/_electrical_line.py | 4 +- 8 files changed, 87 insertions(+), 47 deletions(-) diff --git a/src/oemof/solph/_plumbing.py b/src/oemof/solph/_plumbing.py index bb6ed5b80..4e7864ae3 100644 --- a/src/oemof/solph/_plumbing.py +++ b/src/oemof/solph/_plumbing.py @@ -56,6 +56,22 @@ def sequence(iterable_or_scalar): return _FakeSequence(value=iterable_or_scalar) +def valid_sequence(sequence, length: int) -> bool: + if sequence[0] is None: + return False + + if isinstance(sequence, _FakeSequence): + sequence.size = length + return True + if isinstance(sequence, np.ndarray): + if sequence.size >= length: + return True + else: + raise ValueError(f"Lentgh of {sequence} should be {length}") + + return False + + class _FakeSequence: """Emulates a list whose length is not known in advance. diff --git a/src/oemof/solph/components/_extraction_turbine_chp.py b/src/oemof/solph/components/_extraction_turbine_chp.py index cef1a6152..dc8fb393b 100644 --- a/src/oemof/solph/components/_extraction_turbine_chp.py +++ b/src/oemof/solph/components/_extraction_turbine_chp.py @@ -23,8 +23,8 @@ from pyomo.environ import BuildAction from pyomo.environ import Constraint -from oemof.solph._plumbing import sequence as solph_sequence -from oemof.solph.components._converter import Converter +from oemof.solph._plumbing import sequence +from oemof.solph.components import Converter class ExtractionTurbineCHP(Converter): @@ -87,7 +87,7 @@ def __init__( custom_attributes=custom_attributes, ) self.conversion_factor_full_condensation = { - k: solph_sequence(v) + k: sequence(v) for k, v in conversion_factor_full_condensation.items() } diff --git a/src/oemof/solph/components/_generic_storage.py b/src/oemof/solph/components/_generic_storage.py index 653e6f0c4..33358a469 100644 --- a/src/oemof/solph/components/_generic_storage.py +++ b/src/oemof/solph/components/_generic_storage.py @@ -38,7 +38,8 @@ from oemof.solph._helpers import check_node_object_for_missing_attribute from oemof.solph._options import Investment -from oemof.solph._plumbing import sequence as solph_sequence +from oemof.solph._plumbing import sequence +from oemof.solph._plumbing import valid_sequence class GenericStorage(Node): @@ -225,26 +226,22 @@ def __init__( self.initial_storage_level = initial_storage_level self.balanced = balanced - self.loss_rate = solph_sequence(loss_rate) - self.fixed_losses_relative = solph_sequence(fixed_losses_relative) - self.fixed_losses_absolute = solph_sequence(fixed_losses_absolute) - self.inflow_conversion_factor = solph_sequence( - inflow_conversion_factor - ) - self.outflow_conversion_factor = solph_sequence( - outflow_conversion_factor - ) - self.max_storage_level = solph_sequence(max_storage_level) - self.min_storage_level = solph_sequence(min_storage_level) - self.fixed_costs = solph_sequence(fixed_costs) - self.storage_costs = solph_sequence(storage_costs) - self.invest_relation_input_output = solph_sequence( + self.loss_rate = sequence(loss_rate) + self.fixed_losses_relative = sequence(fixed_losses_relative) + self.fixed_losses_absolute = sequence(fixed_losses_absolute) + self.inflow_conversion_factor = sequence(inflow_conversion_factor) + self.outflow_conversion_factor = sequence(outflow_conversion_factor) + self.max_storage_level = sequence(max_storage_level) + self.min_storage_level = sequence(min_storage_level) + self.fixed_costs = sequence(fixed_costs) + self.storage_costs = sequence(storage_costs) + self.invest_relation_input_output = sequence( invest_relation_input_output ) - self.invest_relation_input_capacity = solph_sequence( + self.invest_relation_input_capacity = sequence( invest_relation_input_capacity ) - self.invest_relation_output_capacity = solph_sequence( + self.invest_relation_output_capacity = sequence( invest_relation_output_capacity ) self.lifetime_inflow = lifetime_inflow @@ -607,7 +604,7 @@ def _objective_expression(self): if m.es.periods is not None: for n in self.STORAGES: - if n.fixed_costs[0] is not None: + if valid_sequence(n.fixed_costs, len(m.PERIODS)): fixed_costs += sum( n.nominal_storage_capacity * n.fixed_costs[pp] @@ -619,7 +616,7 @@ def _objective_expression(self): storage_costs = 0 for n in self.STORAGES: - if n.storage_costs[0] is not None: + if valid_sequence(n.storage_costs, len(m.TIMESTEPS)): # We actually want to iterate over all TIMEPOINTS except the # 0th. As integers are used for the index, this is equicalent # to iterating over the TIMESTEPS with one offset. @@ -1875,7 +1872,7 @@ def _objective_expression(self): period_investment_costs[p] += investment_costs_increment for n in self.INVESTSTORAGES: - if n.investment.fixed_costs[0] is not None: + if valid_sequence(n.investment.fixed_costs, len(m.PERIODS)): lifetime = n.investment.lifetime for p in m.PERIODS: range_limit = min( @@ -1893,7 +1890,7 @@ def _objective_expression(self): ) for n in self.EXISTING_INVESTSTORAGES: - if n.investment.fixed_costs[0] is not None: + if valid_sequence(n.investment.fixed_costs, len(m.PERIODS)): lifetime = n.investment.lifetime age = n.investment.age range_limit = min( diff --git a/src/oemof/solph/components/experimental/_sink_dsm.py b/src/oemof/solph/components/experimental/_sink_dsm.py index 1d910f54e..4645a5899 100644 --- a/src/oemof/solph/components/experimental/_sink_dsm.py +++ b/src/oemof/solph/components/experimental/_sink_dsm.py @@ -38,6 +38,7 @@ from oemof.solph._options import Investment from oemof.solph._plumbing import sequence +from oemof.solph._plumbing import valid_sequence from oemof.solph.components._sink import Sink @@ -703,7 +704,7 @@ def _objective_expression(self): * (1 + m.discount_rate) ** (-m.es.periods_years[p]) ) - if g.fixed_costs[0] is not None: + if valid_sequence(g.fixed_costs, len(m.PERIODS)): fixed_costs += sum( max(g.max_capacity_up, g.max_capacity_down) * g.fixed_costs[pp] @@ -1434,7 +1435,7 @@ def _objective_expression(self): * (1 + m.discount_rate) ** (-m.es.periods_years[p]) ) - if g.investment.fixed_costs[0] is not None: + if valid_sequence(g.investment.fixed_costs, len(m.PERIODS)): lifetime = g.investment.lifetime for p in m.PERIODS: range_limit = min( @@ -1452,7 +1453,7 @@ def _objective_expression(self): ) for g in self.EXISTING_INVESTDSM: - if g.investment.fixed_costs[0] is not None: + if valid_sequence(g.investment.fixed_costs, len(m.PERIODS)): lifetime = g.investment.lifetime age = g.investment.age range_limit = min( @@ -2198,7 +2199,7 @@ def _objective_expression(self): * (1 + m.discount_rate) ** (-m.es.periods_years[p]) ) - if g.fixed_costs[0] is not None: + if valid_sequence(g.fixed_costs, len(m.PERIODS)): fixed_costs += sum( max(g.max_capacity_up, g.max_capacity_down) * g.fixed_costs[pp] @@ -3290,7 +3291,7 @@ def _objective_expression(self): * (1 + m.discount_rate) ** (-m.es.periods_years[p]) ) - if g.investment.fixed_costs[0] is not None: + if valid_sequence(g.investment.fixed_costs, len(m.PERIODS)): lifetime = g.investment.lifetime for p in m.PERIODS: range_limit = min( @@ -3308,7 +3309,7 @@ def _objective_expression(self): ) for g in self.EXISTING_INVESTDSM: - if g.investment.fixed_costs[0] is not None: + if valid_sequence(g.investment.fixed_costs, len(m.PERIODS)): lifetime = g.investment.lifetime age = g.investment.age range_limit = min( @@ -4391,7 +4392,7 @@ def _objective_expression(self): * (1 + m.discount_rate) ** (-m.es.periods_years[p]) ) - if g.fixed_costs[0] is not None: + if valid_sequence(g.fixed_costs, len(m.PERIODS)): fixed_costs += sum( max(g.max_capacity_up, g.max_capacity_down) * g.fixed_costs[pp] @@ -5791,7 +5792,7 @@ def _objective_expression(self): * (1 + m.discount_rate) ** (-m.es.periods_years[p]) ) - if g.investment.fixed_costs[0] is not None: + if valid_sequence(g.investment.fixed_costs, len(m.PERIODS)): lifetime = g.investment.lifetime for p in m.PERIODS: range_limit = min( @@ -5809,7 +5810,7 @@ def _objective_expression(self): ) for g in self.EXISTING_INVESTDSM: - if g.investment.fixed_costs[0] is not None: + if valid_sequence(g.investment.fixed_costs, len(m.PERIODS)): lifetime = g.investment.lifetime age = g.investment.age range_limit = min( diff --git a/src/oemof/solph/flows/_investment_flow_block.py b/src/oemof/solph/flows/_investment_flow_block.py index e77ec40b5..2a09f11a2 100644 --- a/src/oemof/solph/flows/_investment_flow_block.py +++ b/src/oemof/solph/flows/_investment_flow_block.py @@ -29,6 +29,8 @@ from pyomo.core import Var from pyomo.core.base.block import ScalarBlock +from oemof.solph._plumbing import valid_sequence + class InvestmentFlowBlock(ScalarBlock): r"""Block for all flows with :attr:`Investment` being not None. @@ -1007,7 +1009,9 @@ def _objective_expression(self): period_investment_costs[p] += investment_costs_increment for i, o in self.INVESTFLOWS: - if m.flows[i, o].investment.fixed_costs[0] is not None: + if valid_sequence( + m.flows[i, o].investment.fixed_costs, len(m.PERIODS) + ): lifetime = m.flows[i, o].investment.lifetime for p in m.PERIODS: range_limit = min( @@ -1022,7 +1026,9 @@ def _objective_expression(self): ) for i, o in self.EXISTING_INVESTFLOWS: - if m.flows[i, o].investment.fixed_costs[0] is not None: + if valid_sequence( + m.flows[i, o].investment.fixed_costs, len(m.PERIODS) + ): lifetime = m.flows[i, o].investment.lifetime age = m.flows[i, o].investment.age range_limit = min( diff --git a/src/oemof/solph/flows/_non_convex_flow_block.py b/src/oemof/solph/flows/_non_convex_flow_block.py index 88eb76850..01e7b63aa 100644 --- a/src/oemof/solph/flows/_non_convex_flow_block.py +++ b/src/oemof/solph/flows/_non_convex_flow_block.py @@ -25,6 +25,8 @@ from pyomo.core import Var from pyomo.core.base.block import ScalarBlock +from oemof.solph._plumbing import valid_sequence + class NonConvexFlowBlock(ScalarBlock): r""" @@ -326,7 +328,9 @@ def _startup_costs(self): m = self.parent_block() for i, o in self.STARTUPFLOWS: - if m.flows[i, o].nonconvex.startup_costs[0] is not None: + if valid_sequence( + m.flows[i, o].nonconvex.startup_costs, len(m.TIMESTEPS) + ): startup_costs += sum( self.startup[i, o, t] * m.flows[i, o].nonconvex.startup_costs[t] @@ -349,7 +353,9 @@ def _shutdown_costs(self): m = self.parent_block() for i, o in self.SHUTDOWNFLOWS: - if m.flows[i, o].nonconvex.shutdown_costs[0] is not None: + if valid_sequence( + m.flows[i, o].nonconvex.shutdown_costs, len(m.TIMESTEPS) + ): shutdown_costs += sum( self.shutdown[i, o, t] * m.flows[i, o].nonconvex.shutdown_costs[t] @@ -372,7 +378,9 @@ def _activity_costs(self): m = self.parent_block() for i, o in self.ACTIVITYCOSTFLOWS: - if m.flows[i, o].nonconvex.activity_costs[0] is not None: + if valid_sequence( + m.flows[i, o].nonconvex.activity_costs, len(m.TIMESTEPS) + ): activity_costs += sum( self.status[i, o, t] * m.flows[i, o].nonconvex.activity_costs[t] @@ -395,7 +403,9 @@ def _inactivity_costs(self): m = self.parent_block() for i, o in self.INACTIVITYCOSTFLOWS: - if m.flows[i, o].nonconvex.inactivity_costs[0] is not None: + if valid_sequence( + m.flows[i, o].nonconvex.inactivity_costs, len(m.TIMESTEPS) + ): inactivity_costs += sum( (1 - self.status[i, o, t]) * m.flows[i, o].nonconvex.inactivity_costs[t] diff --git a/src/oemof/solph/flows/_simple_flow_block.py b/src/oemof/solph/flows/_simple_flow_block.py index dc893592b..0a8c910d4 100644 --- a/src/oemof/solph/flows/_simple_flow_block.py +++ b/src/oemof/solph/flows/_simple_flow_block.py @@ -26,6 +26,8 @@ from pyomo.core import Var from pyomo.core.base.block import ScalarBlock +from oemof.solph._plumbing import valid_sequence + class SimpleFlowBlock(ScalarBlock): r"""Flow block with definitions for standard flows. @@ -172,12 +174,16 @@ def _create_variables(self, group): ) # set upper bound of gradient variable for i, o, f in group: - if m.flows[i, o].positive_gradient_limit[0] is not None: + if valid_sequence( + m.flows[i, o].positive_gradient_limit, len(m.TIMESTEPS) + ): for t in m.TIMESTEPS: self.positive_gradient[i, o, t].setub( f.positive_gradient_limit[t] * f.nominal_value ) - if m.flows[i, o].negative_gradient_limit[0] is not None: + if valid_sequence( + m.flows[i, o].negative_gradient_limit, len(m.TIMESTEPS) + ): for t in m.TIMESTEPS: self.negative_gradient[i, o, t].setub( f.negative_gradient_limit[t] * f.nominal_value @@ -429,7 +435,9 @@ def _objective_expression(self): if m.es.periods is None: for i, o in m.FLOWS: - if m.flows[i, o].variable_costs[0] is not None: + if valid_sequence( + m.flows[i, o].variable_costs, len(m.TIMESTEPS) + ): for t in m.TIMESTEPS: variable_costs += ( m.flow[i, o, t] @@ -439,7 +447,9 @@ def _objective_expression(self): else: for i, o in m.FLOWS: - if m.flows[i, o].variable_costs[0] is not None: + if valid_sequence( + m.flows[i, o].variable_costs, len(m.TIMESTEPS) + ): for p, t in m.TIMEINDEX: variable_costs += ( m.flow[i, o, t] @@ -464,7 +474,7 @@ def _objective_expression(self): # Fixed costs for units with limited lifetime for i, o in self.LIFETIME_FLOWS: - if m.flows[i, o].fixed_costs[0] is not None: + if valid_sequence(m.flows[i, o].fixed_costs, len(m.TIMESTEPS)): range_limit = min( m.es.end_year_of_optimization, m.flows[i, o].lifetime, @@ -477,7 +487,7 @@ def _objective_expression(self): ) for i, o in self.LIFETIME_AGE_FLOWS: - if m.flows[i, o].fixed_costs[0] is not None: + if valid_sequence(m.flows[i, o].fixed_costs, len(m.TIMESTEPS)): range_limit = min( m.es.end_year_of_optimization, m.flows[i, o].lifetime - m.flows[i, o].age, diff --git a/src/oemof/solph/flows/experimental/_electrical_line.py b/src/oemof/solph/flows/experimental/_electrical_line.py index 2b5e2eb85..ad08f99ff 100644 --- a/src/oemof/solph/flows/experimental/_electrical_line.py +++ b/src/oemof/solph/flows/experimental/_electrical_line.py @@ -25,7 +25,7 @@ from pyomo.environ import Set from pyomo.environ import Var -from oemof.solph._plumbing import sequence as solph_sequence +from oemof.solph._plumbing import sequence from oemof.solph.buses.experimental._electrical_bus import ElectricalBus from oemof.solph.flows._flow import Flow @@ -75,7 +75,7 @@ def __init__(self, **kwargs): nonconvex=kwargs.get("nonconvex"), custom_attributes=kwargs.get("costom_attributes"), ) - self.reactance = solph_sequence(kwargs.get("reactance", 0.00001)) + self.reactance = sequence(kwargs.get("reactance", 0.00001)) self.input = kwargs.get("input") self.output = kwargs.get("output")