Multi-stage optimisation

Note

This section describes how to run multi-stage optimisations with SpineOpt using the stage class - not to be confused with the rolling horizon optimisation technique described in Temporal Framework, nor the Benders decomposition algorithm described in Decomposition.

Warning

This feature is experimental. It may change in future versions without notice.

By default, SpineOpt is solved as a 'single-stage' optimisation problem. However you can add additional stages to the optimisation by creating stage objects in your DB.

To motivate this discussion, say you want to model a storage over a year with hourly resolution. The model is large, so you would like to solve it using a rolling horizon of, say, one day - so it solves quickly (see roll_forward and the Temporal Framework section). But this wouldn't capture the long-term value of your storage!

To remediate this, you can introduce an additional 'stage' that solves the entire year at once with a lower temporal resolution (say, one day instead of one hour), and then fixes the storage level at certain points for your higher-resolution rolling horizon model. Both models, the year-long model at daily resolution and the rolling horizon model at hourly resolution, will solve faster than the year-long model at hourly resolution - hopefully much faster - leading to a good compromise between speed and accuracy.

So how do you do that? You use a stage.

The stage class

In SpineOpt, a stage is an additional optimisation model that fixes certain outputs for another set of models declared as their children.

The children of a stage are defined via stage__child_stage relationships (with the parent stage in the first dimension). If a stage has no stage__child_stage relationships as a parent, then it is assumed to have only one children: the model itself.

The outputs that a stage fixes for its children are defined via stage__output relationships. By default, the output is fixed at the end of each child's rolling window. However, you can fix it at other points in time by specifying the output_resolution parameter as a duration (or array of durations) relative to the start of the child's rolling window.

For example, if you specify an output_resolution of 1 day, then the output will be fixed at one day after the child's window start. If you specify something like [1 day, 2 days], then it will be fixed at one day after the window start, and then at two days after that (i.e., three days after the window start).

The optimisation model that a stage solves is given by the stage_scenario parameter value, which must be a scenario in your DB.

And that's basically it!

Example

In case of the year-long storage model with hourly resolution, here is how you would do it.

First, the basic setup:

  1. Create your model.
  2. Specify model_start and model_end for your model to cover the year of interest.
  3. Specify roll_forward for your model as 1 day.
  4. Create a temporal_block called "flat".
  5. Specify resolution for your temporal_block as 1 hour.
  6. Create a model__default_temporal_block between your model and your temporal_block (to keep things simple, but of course you can use node__temporal_block, etc., as needed).
  7. Create the rest of your model (the storage node, etc.)

With the above, you will have a rolling-horizon model that would probably solve in reasonable time but wouldn't capture the long-term value of your storage.

Now, the 'stage' stuff:

  1. Create an alternative called "ltstoragealt".
  2. Create a scenario called "ltstoragescen" with the "ltstoragealt" alternative in the highest rank.
  3. Create a stage called "lt_storage".
  4. (Don't create any stage__child_stage relationsips - the only child is the model - plus you don't have/need other stages).
  5. Create a stage__output between your stage and the "node_state" output.
  6. Don't specify output_resolution so the output is fixed at the end of the model's rolling window.
  7. Specify roll_forward for your model in the "ltstoragealt" alternative as nothing - so the model doesn't roll - the entire year is solved at once.
  8. Specify resolution for the "flat" temporal_block in the "ltstoragealt" alternative as 1 day.
  9. Specify stage_scenario for the "ltstorage" stage as `"ltstorage_scen"`.