Skip to content

Nested constraints and expressions #2

Description

@brynpickering

What can be improved?

There are currently two approaches to defining constraints / expressions in the spec:

  1. One expression + mask combo for a named expression/constraint

    constraints
      storage_balance_first_ts:
        foreach: [name, snapshot]
        mask: not cyclic_state_of_charge==True and snapshot == get_val_at_index(snapshot=0)
        expression: $soc + ($dispatch * snapshot_weightings) = state_of_charge_initial
      all_other_ts:     
         foreach: [name, snapshot]
         mask: (not cyclic_state_of_charge and not snapshot == get_val_at_index(snapshot=0)) or cyclic_state_of_charge
         expression: $soc + ($dispatch * snapshot_weightings) = roll($soc * (standing_loss ** snapshot_weightings), snapshot=1)
  2. Possibility of a list of expression + mask combos, where the masks must be disjoint

    constraints
      storage_balance:
        foreach: [name, snapshot]
        equations: 
        - mask: not cyclic_state_of_charge==True and snapshot == get_val_at_index(snapshot=0)
          expression: $soc + ($dispatch * snapshot_weightings) = state_of_charge_initial
         - mask: (not cyclic_state_of_charge and not snapshot == get_val_at_index(snapshot=0)) or cyclic_state_of_charge
           expression: $soc + ($dispatch * snapshot_weightings) = roll($soc * (standing_loss ** snapshot_weightings), snapshot=1)

(2) is where we got to with the original spec but @FBumann has tended more towards (1).

The reason for (2) was to keep the number of resulting named constraints/expressions small. There are lots of cases where you have a general concept (e.g., balance storage device levels in each timestep) that comes with several variants (what to do in the first timestep differently to all other timesteps). Rather than ask the user to come up with a name for each, they can just provide lists of each variant. It also leads to much more dense expression/constraint arrays which, when storing the definition in an xarray dataset, is beneficial to reduce memory footprint of the built optimisation problem.

However, the list-of-variants approach has a few downsides:

  1. it's confusing, hard to debug, and not super readable nor easy to document
  2. overriding by merging YAMLs requires overriding all lists at once as you don't have a named key to check against

One downside both approaches have is that you will always want to define variants with disjoint masks and we have no way of knowing that they're disjoint until we evaluate. It's not something we can catch when parsing the spec.

A middle ground might be to enforce naming of variants. As with current list-of-variants approach, these would be rendered as cases of constraints in the latex math and would live in the same, single expression array of expressions.

constraints
  storage_balance:
    foreach: [name, snapshot]
    cases:
      first_ts:
        mask: not cyclic_state_of_charge==True and snapshot == get_val_at_index(snapshot=0)
        expression: $soc + ($dispatch * snapshot_weightings) = state_of_charge_initial
      all_other_ts:     
        mask: (not cyclic_state_of_charge and not snapshot == get_val_at_index(snapshot=0)) or cyclic_state_of_charge
        expression: >-
          $soc + ($dispatch * snapshot_weightings) =
          roll($soc * (standing_loss ** snapshot_weightings), snapshot=1)

constraint groups

constraint_groups:
  storage_balance: [ ... ]

Version

v0.0.0

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions