From 305cd2b2530a71763710f7f5fe04967c9874f848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Wed, 31 Jul 2024 15:31:10 +0200 Subject: [PATCH 1/3] Simplify storage costs constraint The costs are defined on TIMEPOINTS, so these should be used. --- src/oemof/solph/components/_generic_storage.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/oemof/solph/components/_generic_storage.py b/src/oemof/solph/components/_generic_storage.py index ab8141789..20da06c6a 100644 --- a/src/oemof/solph/components/_generic_storage.py +++ b/src/oemof/solph/components/_generic_storage.py @@ -616,12 +616,9 @@ def _objective_expression(self): for n in self.STORAGES: if n.storage_costs[0] is not None: - storage_costs += ( - self.storage_content[n, 0] * n.storage_costs[0] - ) - for t in m.TIMESTEPS: + for t in m.TIMEPOINTS: storage_costs += ( - self.storage_content[n, t + 1] * n.storage_costs[t + 1] + self.storage_content[n, t] * n.storage_costs[t] ) self.storage_costs = Expression(expr=storage_costs) From ba99a2dc567e5b4fb953075892bfb182ae4cfc32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Wed, 31 Jul 2024 15:56:36 +0200 Subject: [PATCH 2/3] Ignore storage_costs for 0th time point In the constraint test, as the initial_storage_level was given, only a constant was dropped. Thus, costs for the initial level never made it to the lp file. I changed the costs to increase over time in the test to make this more transpatrent. --- src/oemof/solph/components/_generic_storage.py | 7 +++++-- tests/constraint_tests.py | 2 +- tests/lp_files/storage.lp | 6 ++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/oemof/solph/components/_generic_storage.py b/src/oemof/solph/components/_generic_storage.py index 20da06c6a..a62d5313e 100644 --- a/src/oemof/solph/components/_generic_storage.py +++ b/src/oemof/solph/components/_generic_storage.py @@ -616,9 +616,12 @@ def _objective_expression(self): for n in self.STORAGES: if n.storage_costs[0] is not None: - for t in m.TIMEPOINTS: + # 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. + for t in m.TIMESTEPS: storage_costs += ( - self.storage_content[n, t] * n.storage_costs[t] + self.storage_content[n, t + 1] * n.storage_costs[t] ) self.storage_costs = Expression(expr=storage_costs) diff --git a/tests/constraint_tests.py b/tests/constraint_tests.py index 07b0c3f0f..f3214c73a 100644 --- a/tests/constraint_tests.py +++ b/tests/constraint_tests.py @@ -283,7 +283,7 @@ def test_storage(self): }, nominal_storage_capacity=1e5, loss_rate=0.13, - storage_costs=0.1, + storage_costs=[0.1, 0.2, 0.3, 0.4], inflow_conversion_factor=0.97, outflow_conversion_factor=0.86, initial_storage_level=0.4, diff --git a/tests/lp_files/storage.lp b/tests/lp_files/storage.lp index d6f075166..603d6cbe6 100644 --- a/tests/lp_files/storage.lp +++ b/tests/lp_files/storage.lp @@ -2,7 +2,6 @@ min objective: -+4000.0 ONE_VAR_CONSTANT +56 flow(electricityBus_storage_no_invest_0) +56 flow(electricityBus_storage_no_invest_1) +56 flow(electricityBus_storage_no_invest_2) @@ -10,8 +9,8 @@ objective: +24 flow(storage_no_invest_electricityBus_1) +24 flow(storage_no_invest_electricityBus_2) +0.1 GenericStorageBlock_storage_content(storage_no_invest_1) -+0.1 GenericStorageBlock_storage_content(storage_no_invest_2) -+0.1 GenericStorageBlock_storage_content(storage_no_invest_3) ++0.2 GenericStorageBlock_storage_content(storage_no_invest_2) ++0.3 GenericStorageBlock_storage_content(storage_no_invest_3) s.t. @@ -72,7 +71,6 @@ c_e_GenericStorageBlock_balanced_cstr(storage_no_invest)_: = 40000.0 bounds - 1 <= ONE_VAR_CONSTANT <= 1 0 <= flow(electricityBus_storage_no_invest_0) <= 16667 0 <= flow(electricityBus_storage_no_invest_1) <= 16667 0 <= flow(electricityBus_storage_no_invest_2) <= 16667 From 4ae6233f6e1ed55cb9fc81253019d08da63abc6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 1 Aug 2024 09:55:37 +0200 Subject: [PATCH 3/3] Document changes to storage_costs --- docs/changelog.rst | 1 + docs/whatsnew/v0-6-0.rst | 38 +++++++++++++++++++ .../solph/components/_generic_storage.py | 5 ++- 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 docs/whatsnew/v0-6-0.rst diff --git a/docs/changelog.rst b/docs/changelog.rst index 63c0c0356..05eaa230c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,7 @@ These are new features and improvements of note in each release :backlinks: top +.. include:: whatsnew/v0-6-0.rst .. include:: whatsnew/v0-5-4.rst .. include:: whatsnew/v0-5-3.rst .. include:: whatsnew/v0-5-2.rst diff --git a/docs/whatsnew/v0-6-0.rst b/docs/whatsnew/v0-6-0.rst new file mode 100644 index 000000000..339fae4d9 --- /dev/null +++ b/docs/whatsnew/v0-6-0.rst @@ -0,0 +1,38 @@ +v0.6.0 +------ + +API changes +########### + +* Costs for energy storage are now defined for N-1 points in time + (initial time step is neglected). This is because with a balanced + storage, content of the initial and the first time step (which is + effectively the same) had double weight before. Also, if the + initial storage level is defined, the costs just offset the + objective value without changing anything else. + +New features +############ + + +Documentation +############# + +Bug fixes +######### + + +Other changes +############# + + +Known issues +############ + +* Incompatible to numpy >= 2.0.0. This is because of Pyomo, but we have to + enforce a lower version in our package. + +Contributors +############ + +* Patrik Schönfeldt diff --git a/src/oemof/solph/components/_generic_storage.py b/src/oemof/solph/components/_generic_storage.py index a62d5313e..19edfebef 100644 --- a/src/oemof/solph/components/_generic_storage.py +++ b/src/oemof/solph/components/_generic_storage.py @@ -110,7 +110,8 @@ class GenericStorage(Node): nominal_storage_capacity should not be set (or set to None) if an investment object is used. storage_costs : numeric (iterable or scalar), :math:`c_{storage}(t)` - Cost (per energy) for having energy in the storage. + Cost (per energy) for having energy in the storage, starting from + time point :math:`t_{1}`. lifetime_inflow : int, :math:`n_{in}` Determine the lifetime of an inflow; only applicable for multi-period models which can invest in storage capacity and have an @@ -423,7 +424,7 @@ class GenericStorageBlock(ScalarBlock): * :attr: `storage_costs` not 0 .. math:: - \sum_{t \in \textrm{TIMESTEPS}} c_{storage}(t) \cdot E(t) + \sum_{t \in \textrm{TIMEPOINTS} > 0} c_{storage}(t) \cdot E(t) *Multi-period model*