Adding new constraints to the EHubModel¶
This notebook demonstrates three ways of adding new constraints to extend the model:
- A constraint which limits a function of existing variables and parameters in the model.
- A constraint that is indexed by timeseries data.
- A list of constraints.
[ ]:
from pyehub.energy_hub.ehub_model import EHubModel
from pyehub.energy_hub.utils import constraint, constraint_list
from pyehub.outputter import pretty_print
The following is a custom class which extends the existing EHubModel
class. Here we define three different ways of adding constraints. Every ‘constraint’ must be preceded by @constraint()
decorator and every ‘constraint list’ must be preceded by @constraint_list()
decorator. This is how the model recognizes constraints.
A simple constraint¶
new_constraint(self)
imposes a single constraint, here _sum of capacities of boiler and heat pump <= 10’
A constraint that is indexed by some data¶
indexed_constraint(self, t, export_stream)
is a constraint indexed by time and output_stream: energy_exported at every time step from every export_stream = zero.
A list of constraints¶
constraint_list_example(self)
is a list of constraints, i.e. we are imposing various constraints at once: for time steps 8 to 10, the energy imported from ‘Grid’ >= 10. This is equivalent to three@constraint()
s.
[ ]:
class MyModel(EHubModel):
"""
This is a subclass of EHubModel where we can add our own constraints.
"""
@constraint()
def new_constraint(self):
"""
This is a new constraint of the model.
We flag constraints for the compiler using the @constraint() function decorator on the line above the definition
Returns:
Some constraint that relates variables, parameters, etc. with each other.
"""
# Here we impose the constraint. You can restrict any parameter's value, or bound the sum of parameters, etc.
return self.Boiler + self.HP <= 10
@constraint(
"time", "export_streams"
) # the function decorator can also define the index(es) of the constraint
def indexed_constraint(self, t, export_stream):
"""
This is an example of a constraint that is indexed by some data.
Each of the arguments to `@contraint` are the names of sets of data that the model (self) has.
The constraint is then passed each element of those sets to this method.
It acts much like:
for t in model.time:
for export_stream in model.export_streams:
indexed_constraint(model, t, export_stream)
Args:
t: A specific time step in `self.time`
export_stream: A specific export energy stream from `self.export_streams`.
Returns:
Set of constraints imposing that energy_exported at every time step from every export_stream be zero.
"""
# This says the the energy exported at every time step and every export stream has to be 0.
return self.energy_exported[t][export_stream] == 0
@constraint_list()
def constraint_list_example(self):
"""
This is an example of using the constraint_list decorator.
This makes a method "return" a list of constraints for some data.
This is mostly used for some data that would make it too complicated to have it in a lot of regular @constraint methods.
Yields:
Constraints
[This function returns 'generators'. Lookup `Python generators` to learn more on what this does.]
"""
for t in range(len(self.time)):
if 8 <= t <= 10:
#
yield self.energy_imported[t]["Grid"] >= 10
Now we load and run a model.
[ ]:
excel_file = "test_file_all_constraints_work.xlsx" # name of the excel file. [This must be in the current directory]
my_model = MyModel(
excel=excel_file
) # instantiate our model. Nothing is solved at this point.
results = my_model.solve() # solve the model and get back our results
pretty_print(results) # print the results to the console
[ ]: