Index sets

An index set decides what an expression is evaluated over. Four kinds are supported. Every block on this page runs, in order, against this model:

import examodels as exa

N, T = 8, 4
core = exa.Core()
x = exa.add_var(core, N, start=1.0)

Ranges

exa.add_con(core, lambda i: x[i] - x[i+1], over=range(N - 1))
exa.add_con(core, lambda i: x[i] - 1.0, over=range(1, 9, 2))    # stepped

A stepped range works as an index set. It cannot be a dimension of a variable block — the backend defines lengths only for whole ranges — and saying so is the error you get.

Tables

For anything data-driven, rows are named tuples:

from collections import namedtuple

Gen = namedtuple("Gen", "i bus cost1 cost2 cost3")
gen = [Gen(0, 3, 1100.0, 500.0, 0.0),
       Gen(1, 5, 850.0, 400.0, 0.0)]

pg = exa.add_var(core, len(gen))
exa.add_obj(core, lambda g: g.cost1 * pg[g.i]**2 + g.cost2 * pg[g.i] + g.cost3, over=gen)

Fields are read off the traced row. Those named in index hold positions of variables and are kept as integers; the rest become floats.

Products

For a rectangular set of indices, declare the block with one dimension per axis:

y = exa.add_var(core, T, N)                       # index as y[t, i]
exa.add_con(core, lambda t, i: y[t, i] - y[t-1, i],
            over=exa.product(range(1, T), range(N)))

The traced function takes one index per dimension. In the generator form the same thing is written for t, i in exa.product(...).

Subexpressions

A named expression, reusable across objectives and constraints. It is inlined at each use — it adds no variables and no constraint rows:

sq = exa.add_expr(core, lambda i: x[i]**2, over=range(N))
exa.add_obj(core, lambda i: (sq[i] - 1.0)**2, over=range(N))
exa.add_con(core, lambda i: sq[i] + sq[i+1], over=range(N - 1))

model = exa.Model(core)
model.nvar, model.ncon

Uses that share a structure share derivative code, exactly as if written out.