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.