How to use Variable Collections with multiple indices
This page shows some examples and How-Tos about the VariableCollection{T} class.
Create a VariableCollection with multiple indices
The following statements creates a VariableCollection that uses four indices.
// Define variable collection for supply decision
var dd = new VariableCollection<tblTimePeriod, tblNode, tblProduct, tblTransportMode>(
this.Model, // this Model
this.AllT, // first Index of type tblTimePeriod
this.AllN, // second Index of type tblNode
this.AllP, // third Index of type tblProduct
this.AllTM, // fourth Index of type tblTransportMode
"SupplyDecision", // variable collection's name
(t, n, p, tm) => $"t_{t}_n_{n}_p_{p}_tm_{tm}",
(t, n, p, tm) => 0, // same as null, 0 is default
(t, n, p, tm) => double.PositiveInfinity, // Upper bound: some as null, double.PositiveInfinity is default and is 'unbounded'
(t, n, p, tm) => VariableType.Continuous); // Continuous is the default VariableType
// The variables of this collection will be referred to with
// t of type tblTimePeriod, n of type tblNode, p of type tblProduct and tm of tblTransportMode:
dd[t, n, p, tm]
Calculation of a Sum over several indices
Using VariableCollection with several indices in an Expression.Sum(IEnumerable<Expression>
):
// The sum of all variables of each transport mode shall be equal to 1
foreach (var t in this.AllT)
foreach (var n in this.AllN)
foreach (var p in this.AllP)
{
this.Model.AddConstraint(
Expression.Sum(this.AllTM.Select(tm => dd[t, n, p, tm])) == 1, // constraint expression
$"DD_EQ_t_{t}_n_{n}_p_{p}_tm_{tm}"); // constraint name
}
The second examples is taken from a Sudoku Problem. It demonstrates usage of Linq-Expressions:
// set up ModelScope with Configuration
// ...
var model = new Model();
// define index sets
var indexX = Enumerable.Range(1, 9).ToArray();
var indexY = Enumerable.Range(1, 9).ToArray();
var indexVal = Enumerable.Range(1, 9).ToArray();
// create VariableCollection that holds a binary decision for each indexVal per cell in the indexX x indexY grid.
var assignValueToCell = new VariableCollection<int, int, int>(
model,
indexX,
indexY,
indexVal,
"sudokuGrid",
(x, y, v) => $"var_{x}_{y}_{z}",
null, // use default lower bound. Will always be 0 for VariableType.Binary
null, // use default upper bound. Will always 1 for VariableType.Binary
(x, y, v) => VariableType.Binary); // all decisions are binary.
// In every 3x3 block, each Number has to be set
// will create groups 1:(1, 2, 3), 2:(4, 5, 6), 3:(7, 8, 9)
foreach (var blockX in indexX.GroupBy(x => (x - 1) / 3 + 1))
{
// same as for X-dimension
foreach (var blockY in indexY.GroupBy(y => (y - 1) / 3 + 1))
{
// select all variables of the current block
foreach (var v in indexVal)
{
var vcs = from x in blockX
from y in blockY
select assignValueToCell[x, y, v];
// each value needs to occur exactly once per block
var constraint = Expression.Sum(vcs) == 1;
model.AddConstraint(constraint, $"block_x_y_{blockX.Key}_{blockY.Key}");
}
}
}
// ...
// analogous for rows, columns
Using Variable Types
All variables of a VariableCollection will have the same VariableType.This matches the usual pattern in mathematical models, as x is all continues, y is all binary ...
Variables that belong to a VariableCollection can have different VariableTypes. This enables the developer to relax some variables within a model for a (sub-)set of given indices.
E.g., variables that represent time periods in the "distant future" can be declared as VariableType.Continuous, while more "relevant" (i.e. earlier) decisions can be represented by integral variables.
The following snippet illustrates that use case:
// example for time-dependent a VariableTypeGenerator
var continuousThreshold = 14;
var timeIndices = Enumerable.Range(0, 31).ToList();
var mixedVariableTypeCollection = new VariableCollection<T, int, T3>(
model,
someListOfT,
timeIndices,
someListOfT3,
"mixedTypeCollection",
variableTypeGenerator: (t, time, t3) => time < continuousThreshold
? VariableType.Integer
: VariableType.Continuous); // only use integral variables for the first 14 periods
// ...
Variable Types
The following Variable Types are available:
- VariableType.Continuous represents a (continuous) floating point variable in the model. The default bounds are
[0;inf]
. - VariableType.Integer represents an integral decision variable. The default bounds are
[0;inf]
. - VariableType.Binary represents a boolean variable. I.e. it can either take the value
0
, or1
.- Lower- and UpperBound can only be set to
0
or1
. - Note that Variable.LowerBound = 1 and Variable.UpperBound = 0 are valid assignments.
- Lower- and UpperBound can only be set to
When no VariableTypeGenerator is specified, VariableType.Continuous is used for all Variables in the respective VariableCollection. This is also the default for independently created Variables.
Variable Names
Variable Names are required to reference a variable when a model is transmitted to a solver, and its solution is retrieved. To prevent name conflicts (duplicates) and to save memory, OPTANO.Modeling creates short names by default (see also Short Names).
The VariableCollection's constructor takes a function that returns a custom string for a given index of the collection. This string will be used as Variable.Name for variables that belong to the variable collection. These might help debugging, as developers can add additional information into the model code, for example the index list for the respective variable. The NameHandlingStyle can be set in the ModelScope Configuration.
- The delegate will only be invoked, when the selected NameHandlingStyle is set to NameHandlingStyle.Manual or NameHandlingStyle.UniqueLongNames.
- In case of UniqueLongNames, a base64-index will be appended to the generated string.
- This is not done for NameHandlingStyle.Manual.
- Make sure that your provided name generator produces unique names.
- Otherwise, Exceptions will be thrown in case of duplicate names.
- With NameHandlingStyle.UniqueShortNames, all names in the model consist of base64-indices in order to make them as short as possible.
More information concerning the ModelScope and its Configuration can be found here.
Lower- and Upper Bounds
A VariableCollection will assign a lower- and upper bound to each Variable when it is created. The bounds are be calculated by a Lower-/UpperBoundGenerator, i.e. a function that takes a variable's index and returns a specific bound, or simply the same value for all variables/indices. double.PositiveInfinity
and double.NegativeInfinity
can be used as constants for unbounded
variables.
If no BoundGenerator is given, the defaults as described in the previous section will be used.