Adding new constraints to the EHubModel

This notebook demonstrates three ways of adding new constraints to extend the model:

  1. A constraint which limits a function of existing variables and parameters in the model.
  2. A constraint that is indexed by timeseries data.
  3. 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
[ ]: