Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow lower limit for generic_integral_limit #1097

Merged
merged 5 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading