In a nice post on LinkedIn by Alireza Soroudi the author presents a short GAMS integer model for the classical n-queens puzzle: place the maximal number of non-attacking queens on an n\times n chessboard.
We couldn't resist writing it up in Python Fusion as well.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We sometimes get asked how to efficiently perform a "group by" operation on a variable. That is, we have a variable x=(x_0,\ldots,x_{n-1}) naturally divided into groups and we want to add constraints for each group or constraints on aggregate values within groups. This arises for instance in portfolio optimization where the groups are assets, each consisting of a (varying) number of tax lots.
To keep the discussion more concrete, suppose we have a variable x=(x_0,x_1,x_2,x_3,x_4,x_5) which consists of 3 groups (x_0,x_1,x_2), (x_3) and (x_4,x_5). Let's say we want to express:
an upper bound b_i on the total value within each group,
a joint volatility constraint such as \gamma\geq\left\|G\cdot \left[\begin{array}{c} x_0+x_1+x_2-i_0 \\ x_3-i_1 \\ x_4+x_5-i_2\end{array}\right]\right\|_2 for some matrix G and constant index weights i_0,i_1,i_2
the constraint that all values within one group have the same sign.
Pick, slice
A straightforward solution is to pick (Expr.pick) the content of each group into a separate view and add relevant constraints in a loop over the groups. One could also take slices (Expr.slice) when the groups form contiguous subsequences. This approach is implemented below in Python Fusion.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The previous solution requires picking and stacking expressions in a loop over all groups. This can sometimes be slow. A nicer and more efficient solution uses a bit of linear algebra to perform the grouping. We first encode the groups via a sparse membership matrix \mathrm{Memb}, where the rows are groups, columns are entries of x, and there is a 1 whenever an entry belongs to a group. In our example \mathrm{Memb}=\left[\begin{array}{cccccc}1&1&1&0&0&0\\0&0&0&1&0&0\\0&0&0&0&1&1\end{array}\right] .
This matrix is easy to construct in Fusion.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Note that \mathrm{Memb}\cdot x is the vector of all group sums, in our case \mathrm{Memb}\cdot x = \left[\begin{array}{c} x_0+x_1+x_2 \\ x_3 \\ x_4+x_5\end{array}\right].
That means we can express both of the previous models in a single call without looping. Since \mathrm{Memb} is very sparse, this becomes a very efficient representation. Of course it is important to keep \mathrm{Memb} as a sparse matrix.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We can now use the same matrix to model the last problem from the introduction: all entries within each group must have the same sign. We introduce a sequence of binary variables z=(z_0,\ldots,z_{g-1}), one for each of the g groups. The j-th variable will determine the sign of elements in the j-th group. That is imposed by constraints -M(1-z_j)\leq x_i\leq Mz_j, whenever x_i belongs to j-th group. We can use the pick/slice strategy per group, as before, or observe that \mathrm{Memb}^T\cdot z produces the vector with the correct binary variable for each entry in x. I our case, if z=(z_0,z_1,z_2) then \mathrm{Memb}^T\cdot z = (z_0,z_0,z_0,z_1,z_2,z_2)^T. Now each inequality in (4) can be written as a single constraint:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters