Implementation of future values

As outlined on the page implementation of future values, we implemented a new AbstractElement for representing the future value in dynamic constraints. So far, two subtype are implemented through StorageValueCuts and TypeFutureValue. It is however possible to provide new subtypes representing different implementations for calculating or constraining a future value.

Function flow for incorporating future values

As mentioned, FutureValue is implemented as subtype of AbstractElement. This implies that the package must provide methods for a variety of individual functions from EnergyModelsBase.

The new methods for variable creation are

This implies that we currently only include a single additional variable for the future value, $\texttt{future\_value}(v)$. This variable is created for all subtypes of FutureValue.

The individual constraints are added through the methods

There are two things to highlight in this design:

  1. the default method for EMRH.create_future_value does not create any constraints and
  2. EMRH.create_future_value_couple is only declared for StorageValueCuts.

Point 1 implies that the variable $\texttt{future\_value}(v)$ does not have any internal constraints by default. It is instead constrained for the implementation of StorageValueCuts through the function EMRH.create_future_value_couple. The thought process behind this approach is that StorageValueCuts are constrained through Storage nodes. It is hence a constraints_couple constraint, even if the function has only a single input, 𝒱::Vector{<:FutureValue}.

We furthermore decided to split the overall contribution to the cost function in the current design into additional subfunctions through EMRH.get_future_value_expression for the individual supertypes. While this is not strictly necessary, it is one approach for differentiating between subtypes of FutureValue.

Within the concept of EnergyModelsRecedingHorizon, we added as well an additional function EMRH._update_future_value! for updating parameters of the FutureValue before each optimization run.

Default methods

We do not include default methods for the subfunctions. These subfunctions are EMRH.create_future_value_couple and EMRH.get_future_value_expression. As a consequence, you must declare them for your new type.

The philosophy here is that it is preferable that the developer of new methods receives a method error instead of no error when he implements a wrong method.

Requirements for new future values

It is possible to create new FutureValues. This requires a combination of new types and new metods. The following steps must be conducted to incorporate a new type:

  1. Create a new type as composite type:

    struct NewFutureValue <: FutureValue
        id::Any
    end

    It can include as many fields as desired, but it 1. must include the field id and 2. cannot include a dictionary in which a Node is a key (you may use ElementValue instead if necessary).

  2. Create a new method for the function EMRH.create_future_value_couple for your NewFutureValue. In general, you should have the corresponding Node as field of your NewFutureValue. If this is not the case, e.g., through assigning the same future value for all instances of a given variable of a type, it is necessary to provide new methods for EMB.constraints_couple which also includes a potentially empty method for FutureValue.

  3. Create a new method for the function EMRH.get_future_value_expression which returns a JuMP expression indexed over the strategic periods which corresponds to the contribution to the cost function.

  4. Create a new method for the function EMRH._update_future_value!. This function is used for updating potential fields that are dependent on the elapsed time before the optimization problem is solved. If the future value for your NewFutureValue is independent of the elapsed time, you can provide an empty method.

Additional variables and internal constraints

If your NewFutureValue requires additional variables, you can create a new method for EMB.variables_element. These variables will then be accessible in all potential subsequent functions. If you have internal constraints, that is constraints only accessing variables indexed over Vector{<:NewFutureValue>}, you must create a new method for EMRH.create_future_value and add these constraints in this function.