Show / Hide Table of Contents

    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, or 1.
      • Lower- and UpperBound can only be set to 0 or 1.
      • Note that Variable.LowerBound = 1 and Variable.UpperBound = 0 are valid assignments.

    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.

    Back to top Copyright © OPTANO GmbH generated with DocFX
    Privacy Policy | Impressum – Legal Notice