Skip to main content

A variable over a subset

In a network model, most pairs of a set product are not connected: a plant serves some warehouses, a line joins two of many buses. A variable declared over the full product has a column for every pair, including the ones that do not exist. subset= restricts the variable to the members that do, and the others have no column at all.

Declaring the subset​

subset(sets, columns) lists members of a set product by label, one column per set, read in parallel. m.var(..., subset=arcs) declares the variable over those members.

import numpy as np
from nimopt import Model, Set, Sum, product, subset

P = Set("P", np.array(["p0", "p1"]))
W = Set("W", np.array(["w0", "w1", "w2"]))

arcs = subset(
(P, W),
{"P": np.array(["p0", "p0", "p1"]), "W": np.array(["w0", "w1", "w2"])},
)

m = Model("network")
x = m.var("x", (P, W), subset=arcs)

print(product((P, W)).size)
print(x.n_columns)
print(m.assemble().to_dense())
Output
6
3
[]

The product has six members and the subset three, and x has three columns. The assembled matrix is empty: no constraint has been added. A model over a sparse network costs its arcs, not the grid that contains them.

By label or by position​

subset takes labels and subset_of takes integer positions. Both read their columns in parallel: the k-th entry of each column belongs to the same member. A subset is a list of members, not a cross product of its columns.

import numpy as np
from nimopt import Set, subset, subset_of

P = Set("P", np.array(["p0", "p1"]))
W = Set("W", np.array(["w0", "w1", "w2"]))

by_label = subset(
(P, W),
{"P": np.array(["p0", "p1"]), "W": np.array(["w0", "w2"])},
)
by_index = subset_of((P, W), np.array([[0, 1], [0, 2]]))

print(by_label.size, by_index.size)
print(by_label.labels())
Output
2 2
{'P': array(['p0', 'p1'], dtype='<U2'), 'W': array(['w0', 'w2'], dtype='<U2')}

Both list the same two members, (p0, w0) and (p1, w2). subset_of resolves no label when positions are already known.

Constraints over a subset variable​

A sum over a subset variable runs over the members the variable has. A row therefore contains the arcs at that member and nothing else.

import numpy as np
from nimopt import Model, Set, Sum, subset

P = Set("P", np.array(["p0", "p1"]))
W = Set("W", np.array(["w0", "w1", "w2"]))

arcs = subset(
(P, W),
{"P": np.array(["p0", "p0", "p1"]), "W": np.array(["w0", "w1", "w2"])},
)

m = Model("network")
x = m.var("x", (P, W), subset=arcs)
rows = m.constraint("capacity", Sum(W, x[P, W]) <= 10.0)

print(rows.n_rows, rows.nnz)
print(m.assemble().to_dense())
Output
2 3
[[1. 1. 0.]
[0. 0. 1.]]

Two rows over three columns: p0 has two arcs and p1 one.

Bounds over a subset​

A bound applies to every column of the variable. A parameter indexed over fewer dimensions than the variable is broadcast over the rest. A bound per plant therefore applies to each arc of that plant.

import numpy as np
from nimopt import Model, Param, Set, subset

P = Set("P", np.array(["p0", "p1"]))
W = Set("W", np.array(["w0", "w1", "w2"]))

arcs = subset(
(P, W),
{"P": np.array(["p0", "p0", "p1"]), "W": np.array(["w0", "w1", "w2"])},
)
cap = Param.from_dense("cap", (P,), np.array([4.0, 9.0]))

m = Model("network")
m.var("x", (P, W), subset=arcs, upper=cap)

print(m.column_bounds()[1])
Output
[4. 4. 9.]

The two p0 arcs take 4.0 and the p1 arc 9.0.