Helper functions¶
For where
strings and expression
strings, there are many helper functions available to use, to allow for more complex operations to be undertaken within the string.
Their functionality is detailed in the helper function API page.
Here, we give a brief summary.
Helper functions generally require a good understanding of their functionality, so make sure you are comfortable with them beforehand.
any¶
Parameters are indexed over multiple dimensions.
Using any(..., over=...)
in a where
string allows you to check if there is at least one non-NaN value in a given dimension (akin to xarray.DataArray.any).
So, any(cost, over=[nodes, techs])
will check if there is at least one non-NaN tech+node value in the costs
dimension (the other dimension that the cost
decision variable is indexed over).
defined¶
Similar to any, using defined(..., within=...)
in a where
string allows you to check for non-NaN values along dimensions.
In the case of defined
, you can check if e.g., certain technologies have been defined within the nodes or certain carriers are defined within a group of techs or nodes.
So, for the definition:
techs:
tech1:
base_tech: conversion
carrier_in: electricity
carrier_out: heat
tech2:
base_tech: conversion
carrier_in: [coal, biofuel]
carrier_out: electricity
nodes:
node1:
techs: {tech1}
node2:
techs: {tech1, tech2}
defined(carriers=electricity, within=techs)
would yield a list of [True, True]
as both technologies define electricity.
defined(techs=[tech1, tech2], within=nodes)
would yield a list of [True, True]
as both nodes define at least one of tech1
or tech2
.
defined(techs=[tech1, tech2], within=nodes, how=all)
would yield a list of [False, True]
as only node2
defines both tech1
and tech2
.
sum¶
Using sum(..., over=)
in an expression allows you to sum over one or more dimensions of your component array (be it a parameter, decision variable, or global expression).
select_from_lookup_arrays¶
Some of our arrays in model.inputs
are not data arrays, but "lookup" arrays.
These arrays are used to map the array's index items to other index items.
For instance when using time clustering, the lookup_cluster_last_timestep
array is used to get the timestep resolution and the stored energy for the last timestep in each cluster.
Using select_from_lookup_arrays(..., dim_name=lookup_array)
allows you to apply this lookup array to your data array.
get_val_at_index¶
If you want to access an integer index in your dimension, use get_val_at_index(dim_name=integer_index)
.
For example, get_val_at_index(timesteps=0)
will get the first timestep in your timeseries, get_val_at_index(timesteps=-1)
will get the final timestep.
This is mostly used when conditionally applying a different expression in the first / final timestep of the timeseries.
It can be used in the where
string (e.g., timesteps=get_val_at_index(timesteps=0)
to mask all other timesteps) and the expression string
(via slices - storage[timesteps=$first_timestep]
and first_timestep
expression being get_val_at_index(timesteps=0)
).
roll¶
We do not use for-loops in our math.
This can be difficult to get your head around initially, but it means that to define expressions of the form var[t] == var[t-1] + param[t]
requires shifting all the data in your component array by N places.
Using roll(..., dimension_name=N)
allows you to do this.
For example, roll(storage, timesteps=1)
will shift all the storage decision variable objects by one timestep in the array.
Then, storage == roll(storage, timesteps=1) + 1
is equivalent to applying storage[t] == storage[t - 1] + 1
in a for-loop.
default_if_empty¶
We work with quite sparse arrays in our models.
So, although your arrays are indexed over e.g., nodes
, techs
and carriers
, a decision variable or parameter might only have one or two values in the array, with the rest being NaN.
This can play havoc with defining math, with nan
values making their way into your optimisation problem and then killing the solver or the solver interface.
Using default_if_empty(..., default=...)
in your expression
string allows you to put a placeholder value in, which will be used if the math expression unavoidably needs a value.
Usually you shouldn't need to use this, as your where
string will mask those NaN values.
But if you're having trouble setting up your math, it is a useful function to getting it over the line.
Note
Our internally defined parameters, listed in the Parameters
section of our pre-defined base math documentation all have default values which propagate to the math.
You only need to use default_if_empty
for decision variables and global expressions, and for user-defined parameters.
where¶
Where strings only allow you to apply conditions across the whole expression equations.
Sometimes, it's necessary to apply specific conditions to different components within the expression.
Using where(<math_component>, <condition>)
helper function enables this,
where <math_component>
is a reference to a parameter, variable, or global expression and <condition>
is a reference to an array in your model inputs that contains only True
/1
and False
/0
/NaN
values.
<condition>
will then be applied to <math_component>
, keeping only the values in <math_component>
where <condition>
is True
/1
.
This helper function can also be used to extend the dimensions of a <math_component>
.
If the <condition>
has any dimensions not present in <math_component>
, <math_component>
will be broadcast to include those dimensions.
Note
Where
gets referred to a lot in Calliope math.
It always means the same thing: applying xarray.DataArray.where.