Skip to content

Reading an answer into pandas or xarray

How to hand an answer to code that works in pandas or xarray. A result reads as polars tables, and the bridges below convert one name at a time. specsolve installs neither library, so install the one you need:

pip install pandas xarray

The examples solve dispatch on its committed instance: three generators over four snapshots.

As a pandas table

import specsolve as sps

result = sps.solve('dispatch.yaml', sources)
print(result.to_pandas('p').head(3))
   snapshot generator  value
0         0      wind   60.0
1         0       gas    0.0
2         1      wind   80.0

The table has the shape primal gives: one column per dimension, a value column, and one row per coordinate the model built. kind='dual' reads a constraint's duals, and kind='expression' a named expression:

print(result.to_pandas('power_balance', kind='dual'))
   snapshot  value
0         0   10.0
1         1   50.0
2         2   50.0
3         3   50.0

As a labelled xarray array

print(result.to_dataarray('p'))
<xarray.DataArray 'p' (snapshot: 4, generator: 2)> Size: 64B
array([[  0.,  60.],
       [ 40.,  80.],
       [100.,  80.],
       [ 10.,  80.]])
Coordinates:
  * snapshot   (snapshot) int64 32B 0 1 2 3
  * generator  (generator) object 16B 'gas' 'wind'

The array is dense over the variable's dimensions. A coordinate that a where: removed comes back NaN. A label that no row holds is not on the axis: solar has no capacity, so p has no solar column. to_dataarray takes kind= as to_pandas does.

Every variable as one xarray dataset

result.to_dataset()  # every variable
result.to_dataset('power_balance', kind='dual')  # the duals you name

One call reads one kind, since a dual and a variable can share a name. On a large model, name the few you need: each arrives dense.

From a sweep

A sweep has the same three bridges, and each returns the answer. The slice key of a scenario sweep becomes a dimension, such as scenario. A rolling horizon comes back over the dimension it sliced, so the answer is indexed by time. per_window=True reads it window by window instead:

sweep.to_dataarray('p')  # (scenario, snapshot, generator)
rolling.to_pandas('soc')  # over hour
rolling.to_pandas('soc', per_window=True)  # over hour_start and t, lookahead included