Skip to content

Commit

Permalink
Merge pull request #1097 from oemof/features/lower_generic_integral_l…
Browse files Browse the repository at this point in the history
…imit

Allow lower limit for generic_integral_limit
  • Loading branch information
p-snft authored Aug 17, 2024
2 parents a73941f + 3753d5d commit 7ed0fa6
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 17 deletions.
52 changes: 41 additions & 11 deletions src/oemof/solph/constraints/integral_limit.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
SPDX-License-Identifier: MIT
"""
import warnings

from pyomo import environ as po

Expand All @@ -29,7 +30,7 @@ def emission_limit(om, flows=None, limit=None):
"""
generic_integral_limit(
om, keyword="emission_factor", flows=flows, limit=limit
om, keyword="emission_factor", flows=flows, upper_limit=limit
)


Expand All @@ -49,7 +50,9 @@ def emission_limit_per_period(om, flows=None, limit=None):
)


def generic_integral_limit(om, keyword, flows=None, limit=None):
def generic_integral_limit(
om, keyword, flows=None, upper_limit=None, lower_limit=None, limit=None
):
r"""Set a global limit for flows weighted by attribute named keyword.
The attribute named keyword has to be added
to every flow you want to take into account.
Expand All @@ -69,8 +72,10 @@ def generic_integral_limit(om, keyword, flows=None, limit=None):
used.
keyword : string
attribute to consider
limit : numeric
Absolute limit of keyword attribute for the energy system.
upper_limit : numeric
Absolute upper limit of keyword attribute for the energy system.
lower_limit : numeric
Absolute lower limit of keyword attribute for the energy system.
Note
----
Expand All @@ -80,7 +85,10 @@ def generic_integral_limit(om, keyword, flows=None, limit=None):
**Constraint:**
.. math:: \sum_{i \in F_E} \sum_{t \in T} P_i(p, t) \cdot w_i(t)
\cdot \tau(t) \leq M
\cdot \tau(t) \leq UB
.. math:: \sum_{i \in F_E} \sum_{t \in T} P_i(p, t) \cdot w_i(t)
\cdot \tau(t) \geq LB
With `F_I` being the set of flows considered for the integral limit and
Expand All @@ -95,7 +103,8 @@ def generic_integral_limit(om, keyword, flows=None, limit=None):
:math:`P_n(p, t)` V power flow :math:`n` at time index :math:`p, t`
:math:`w_N(t)` P weight given to Flow named according to `keyword`
:math:`\tau(t)` P width of time step :math:`t`
:math:`L` P global limit given by keyword `limit`
:math:`UB` P global limit given by keyword `upper_limit`
:math:`LB` P global limit given by keyword `lower_limit`
================= ==== ====================================================
Examples
Expand Down Expand Up @@ -124,6 +133,20 @@ def generic_integral_limit(om, keyword, flows=None, limit=None):
flows = _check_and_set_flows(om, flows, keyword)
limit_name = "integral_limit_" + keyword

if limit is not None:
msg = (
"The keyword argument 'limit' to generic_integral_limit has been"
"renamed to 'upper_limit'. The transitional wrapper will be"
"deleted in a future version."
)
warnings.warn(msg, FutureWarning)
upper_limit = limit

if upper_limit is None and lower_limit is None:
raise ValueError(
"At least one of upper_limit and lower_limit needs to be defined."
)

setattr(
om,
limit_name,
Expand All @@ -138,11 +161,18 @@ def generic_integral_limit(om, keyword, flows=None, limit=None):
),
)

setattr(
om,
limit_name + "_constraint",
po.Constraint(expr=(getattr(om, limit_name) <= limit)),
)
if upper_limit is not None:
setattr(
om,
limit_name + "_upper_limit",
po.Constraint(expr=(getattr(om, limit_name) <= upper_limit)),
)
if lower_limit is not None:
setattr(
om,
limit_name + "_lower_limit",
po.Constraint(expr=(getattr(om, limit_name) >= lower_limit)),
)

return om

Expand Down
23 changes: 23 additions & 0 deletions tests/constraint_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,29 @@ def test_flow_without_emission_for_emission_constraint_no_error(self):

self.compare_lp_files("emission_limit_no_error.lp", my_om=om)

def test_flow_without_emission_for_emission_constraint_lower(self):
"""Test that no error is thrown if no flows are explicitly passed"""
bel = solph.buses.Bus(label="electricityBus")
source1 = solph.components.Source(
label="source1",
outputs={
bel: solph.flows.Flow(
nominal_value=100,
custom_attributes={"emission_factor": 0.8},
)
},
)
source2 = solph.components.Source(
label="source2", outputs={bel: solph.flows.Flow(nominal_value=100)}
)
self.energysystem.add(bel, source1, source2)
om = self.get_om()
solph.constraints.generic_integral_limit(
om, keyword="emission_factor", lower_limit=777
)

self.compare_lp_files("emission_limit_lower.lp", my_om=om)

def test_equate_variables_constraint(self):
"""Testing the equate_variables function in the constraint module."""
bus1 = solph.buses.Bus(label="Bus1")
Expand Down
2 changes: 1 addition & 1 deletion tests/lp_files/emission_budget_limit.lp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ objective:

s.t.

c_u_integral_limit_emission_factor_constraint_:
c_u_integral_limit_emission_factor_upper_limit_:
+0.5 flow(source1_electricityBus_0)
-1.0 flow(source1_electricityBus_1)
+2.0 flow(source1_electricityBus_2)
Expand Down
2 changes: 1 addition & 1 deletion tests/lp_files/emission_limit.lp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ objective:

s.t.

c_u_integral_limit_emission_factor_constraint_:
c_u_integral_limit_emission_factor_upper_limit_:
+0.5 flow(source1_electricityBus_0)
-1.0 flow(source1_electricityBus_1)
+2.0 flow(source1_electricityBus_2)
Expand Down
38 changes: 38 additions & 0 deletions tests/lp_files/emission_limit_lower.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
\* Source Pyomo model name=Model *\

min
objective:
+0 ONE_VAR_CONSTANT

s.t.

c_l_integral_limit_emission_factor_lower_limit_:
+0.8 flow(source1_electricityBus_0)
+0.8 flow(source1_electricityBus_1)
+0.8 flow(source1_electricityBus_2)
>= 777

c_e_BusBlock_balance(electricityBus_0)_:
+1 flow(source1_electricityBus_0)
+1 flow(source2_electricityBus_0)
= 0

c_e_BusBlock_balance(electricityBus_1)_:
+1 flow(source1_electricityBus_1)
+1 flow(source2_electricityBus_1)
= 0

c_e_BusBlock_balance(electricityBus_2)_:
+1 flow(source1_electricityBus_2)
+1 flow(source2_electricityBus_2)
= 0

bounds
1 <= ONE_VAR_CONSTANT <= 1
0 <= flow(source1_electricityBus_0) <= 100
0 <= flow(source1_electricityBus_1) <= 100
0 <= flow(source1_electricityBus_2) <= 100
0 <= flow(source2_electricityBus_0) <= 100
0 <= flow(source2_electricityBus_1) <= 100
0 <= flow(source2_electricityBus_2) <= 100
end
2 changes: 1 addition & 1 deletion tests/lp_files/emission_limit_no_error.lp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ objective:

s.t.

c_u_integral_limit_emission_factor_constraint_:
c_u_integral_limit_emission_factor_upper_limit_:
+0.8 flow(source1_electricityBus_0)
+0.8 flow(source1_electricityBus_1)
+0.8 flow(source1_electricityBus_2)
Expand Down
22 changes: 19 additions & 3 deletions tests/test_constraints_module.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import pandas as pd
import pytest

from oemof import solph


def test_special():
def test_integral_limit_wrapper():
date_time_index = pd.date_range("1/1/2012", periods=5, freq="h")
energysystem = solph.EnergySystem(
timeindex=date_time_index,
Expand All @@ -22,9 +23,24 @@ def test_special():
flow_with_keyword = {
(src1, bel): flow1,
}
solph.constraints.generic_integral_limit(
model, "my_factor", flow_with_keyword, limit=777
with pytest.warns(FutureWarning):
solph.constraints.generic_integral_limit(
model, "my_factor", flow_with_keyword, limit=777
)


def test_limetless_limit():
date_time_index = pd.date_range("1/1/2012", periods=5, freq="h")
energysystem = solph.EnergySystem(
timeindex=date_time_index,
infer_last_interval=True,
)
model = solph.Model(energysystem)
with pytest.raises(
ValueError,
match="At least one of upper_limit and lower_limit",
):
solph.constraints.generic_integral_limit(model, "my_factor")


def test_something_else():
Expand Down

0 comments on commit 7ed0fa6

Please sign in to comment.