IBM ILOG Solver User's Manual > More on Modeling > Using Table Constraints: Scheduling Teams > Model

Once you have written a description of your problem, you can use Concert Technology classes to model it.

Step 2   -  

Open the example file

Open the example file YourSolverHome/examples/src/tutorial/sports_basic_partial.cpp in your development environment.

In this exercise, you use constrained integer variables and therefore you use the include file <ilsolver/ilosolverint.h>.

First, you represent the data of the program. The number of team, nbTeams, is set to 8 in this example, but can be easily modified. (For this problem, the number of teams must always be even. If the number of teams is not even, it is increased by 1.) The number of weeks, nbWeeks, is nbTeams-1, or 7 in this example. The number of periods, nbPeriods, is nbTeams/2, or 4 in this example. The number of slots, nbSlots, is equal to the number of weeks times the number of periods, or 28 in this example. This code is provided for you:

int main(int argc, char** argv){
  IloInt i;
  IloInt j;
  IloInt m;
  IloInt p;
  IloInt w;

  nbTeams = (argc>1? atol(argv[1] ) : 8 );
  if ( nbTeams % 2 ) ++nbTeams;
  nbWeeks = nbTeams-1;
  nbPeriods = nbTeams /2;
  nbSlots = nbWeeks * nbPeriods;

Next, you create an environment and a model. This code is provided for you:

  IloEnv env;
    try {
      IloModel model(env);

First, you create a matrix, an array of arrays, to represent the slotVars, the slot variables. You use a for loop to create an array with a size of 4 (the number of periods). Then, for each period, you create an array of size 7 (the number of weeks). Each variable is represented by the coordinates: the period, the week.

Step 3   -  

Create the matrix of slots

Add the following code after the comment //Create the matrix of slots

      IloArray<IloIntVarArray> slotVars(env, nbPeriods);

      for(i = 0; i < nbPeriods; i++)
        slotVars[i] = IloIntVarArray(env, nbWeeks, 0, nbSlots-1);

You create an array to represent the matchVars, the match variables. The match variables are identified by a single integer. The array matchVars has nbSlots elements, or 28 in this example. You use two nested for loops. You create one element of the array matchVars for each element of the matrix slotVars. For example, to represent the first period of the first week, you declare matchVars[0] which corresponds to slotVars[0][0]. (Remember that Solver arrays start at [0].) You then loop to the second week in the first period, and declare matchVars[1] which corresponds to slotVars[0][1]. The loop continues until all matchVars elements are declared.

Step 4   -  

Declare the match variables

Add the following code after the comment //Declare the match variables

      IloIntVarArray matchVars(env, nbSlots);
      IloInt s = 0;
      for (p = 0; p < nbPeriods; ++p) {
        for (w = 0; w < nbWeeks; ++w) {
          matchVars[s] = slotVars[p][w];
          ++s;
        }
      }

Next, you declare the home team and away team variables. One matrix of variables is created for homeTeamVars and a second matrix is created for awayTeamVars. The lower bound of possible values for each variable is 0 and the upper bound is nbTeams-1 or 7. These values represent the teams: Team 0, Team 1, Team 2, Team 3, Team 4, Team 5, Team 6, Team 7. Each variable can also be represented by its matrix coordinates: the period, the week.

Step 5   -  

Declare the home team and away team variables

Add the following code after the comment //Declare the home team and away team variables

      IloArray<IloIntVarArray> homeTeamVars(env, nbPeriods);
      IloArray<IloIntVarArray> awayTeamVars(env, nbPeriods);

      for (p = 0; p < nbPeriods; ++p) {
        homeTeamVars[p]  = IloIntVarArray(env, nbWeeks, 0, nbTeams-1);
        awayTeamVars[p] = IloIntVarArray(env, nbWeeks, 0, nbTeams-1);
      }

Now that you have declared all the variables, you need a way to link them together. Since the matchVars are defined based on slotVars, you can focus on linking slotVars, homeTeamVars, and awayTeamVars. Concert Technology offers a way to do this--table constraints. Table constraints provide a way to add a constraint using a table, a single store of related information. The information can be in the form of combinations of values that are allowed or in the form of combinations of values that are forbidden.The information used by a table constraint can also be in the form of a predicate. A predicate is a logical formula that contains variables. This formula can be evaluated as either true or false.

To demonstrate how table constraints work, imagine that the teams in this example are split into leagues. If Teams 0, 1, 2, and 3 are in League 0 and Teams 4, 5, 6, and 7 are in League 1, you might want to constrain that only teams in the same league can play each other. You could create a table, a set of allowed combinations of values (for example, Team 0 can play Team 2), and use the table constraint to state that only these combinations of values are allowed.

In this lesson, you use the table constraint to constrain that only certain configurations of home team, away team, and slots are allowed. To do this, you first create a tuple set. An integer tuple is an ordered set of values. For example, if you state that Team 1 must play Team 2 in Match 7, this is a tuple. You would write it as (1, 2, 7). The number of values in a tuple is known as the arity of the tuple. The tuple (1, 2, 7) has an arity of 3.

Sets of tuples are represented in Concert Technology by an instance of the class IloIntTupleSet. The first parameter of the constructor is the environment and the second parameter is the arity. You use the member function IloIntTupleSet::add to add tuples to the set. Here is a constructor for IloIntTupleSet:

IloIntTupleSet(IloEnv env, const int arity);

Now, you create the tuple set.

Figure 9.1

Step 6   -  

Create the tuple set

Add the following code after the comment //Create the tuple set

      IloIntTupleSet tuple(env, 3);
      IloInt m = 0;
        for (i=0; i < nbTeams; ++i) {
          for ( j=i+1; j < nbTeams; ++j) {
            tuple.add(IloIntArray(env, 3, i, j, m));
            ++m;
          }
        }
      assert( m == nbSlots );

The set of tuples is created using a matrix (like much else in this lesson). The matrix is created using two nested for loops. The variable i represents the home team, the variable j represents the away team, and the variable m represents the match. The tuples are derived from this matrix. For example, the first tuple (i, j, m) is (0, 1, 0). This means that Team 0 (home) must play Team 1 (away) in Match 0. You can derive the rest of the tuples in the set from this matrix.

Table 9.2 Tuple Sets

 
Team 0 Home 
Team 1 Home 
Team 2 Home 
Team 3 Home 
Team 4 Home 
Team 5 Home 
Team 6 
Home 
Team 7 
Home 
Team 0 Away 

 

 

 

 

 

 

 

 
Team 1 Away 
Match 0 

 

 

 

 

 

 

 
Team 2 Away 
Match 1 
Match 7 

 

 

 

 

 

 
Team 3 Away 
Match 2 
Match 8 
Match 13 

 

 

 

 

 
Team 4 Away 
Match 3 
Match 9 
Match 14 
Match 18 

 

 

 

 
Team 5 Away 
Match 4 
Match 10 
Match 15 
Match 19 
Match 22 

 

 

 
Team 6 Away 
Match 5 
Match 11 
Match 16 
Match 20 
Match 23 
Match 25 

 

 
Team 7 Away 
Match 6 
Match 12 
Match 17 
Match 21 
Match 24 
Match 26 
Match 27 

 

After creating the tuple set, you declare a table constraint to state that these tuples are the only allowed combinations of values for the constrained variables.

Table constraints are represented in Concert Technology by an instance of the class IloTableConstraint. The first parameter of the constructor is the environment. The second parameter is an array of variables. The third parameter is a set of tuples that designates combinations of values. The fourth constraint is a Boolean. If this parameter is IloTrue, the tuples represent the allowed values for the array of variables. If this parameter is IloFalse, the tuples represent the forbidden values for the array of variables. Here is a constructor for IloTableConstraint:

IloConstraint IloTableConstraint(const IloEnv env,
                                 const IloNumVarArray vars,
                                 const IloNumTupleSet set,                             
                                 IloBool compatible);

Now, you add the table constraint. This makes sure that home teams, away teams, and slots are properly linked. It also forbids impossible schedule combinations. For example, a team playing itself would not form part of the allowed set of tuples.

Step 7   -  

Add the table constraint

Add the following code after the comment //Add the table constraint

      for (p = 0; p < nbPeriods; ++p) {
        for (w = 0; w < nbWeeks; ++w) {
          IloIntVarArray slotConsistencyTest(env, 3);
          slotConsistencyTest[0] = homeTeamVars[p][w];
          slotConsistencyTest[1] = awayTeamVars[p][w];
          slotConsistencyTest[2] = slotVars[p][w];
          model.add(IloTableConstraint(env,
                                       slotConsistencyTest,
                                       tuple,
                                       IloTrue));
        }
      }

Again, the code creates a matrix based on periods and weeks. At the place in the matrix designated by the coordinates Period 0 and Week 0, you create an array of variables slotConsistencyTest. This array has three elements. The first element is homeTeamVars[0][0] or the home team playing in the slot Period 0 of Week 0. The second element is awayTeamVars[0][0] or the away team playing in the slot Period 0 of Week 0. The third element is slotVars[0][0] or the slot representing Period 0 of Week 0 (and also matchVars[0] which is created from slotVars). Then you add the table constraint. It constrains that the three variables in slotConsistencyTest must be assigned values that are represented in one of the tuple sets declared in tuple. For example, an accepted combination of values is (0, 1, 0). This means that Team 0 must play Team 1 in Match 0.

Now that you have linked the variables, you can add the other constraints.

You use the predefined constraint IloAllDiff to add the constraint that all matches are different. (The constraint IloAllDiff is introduced in Chapter 4, Searching with Predefined Goals: Magic Square.) In other words, each team must play each other exactly once. In this constraint, you use the array of variables matchVars. Each variable in this array has a value of an integer and IloAllDiff constrains that all these values must be different. (There are three representations of matches in this model: matchVars, slotVars, and the value m representing the match in each tuple. They are linked to each other through the table constraint.)

Step 8   -  

Add the constraint that all matches are different

Add the following code after the comment
//Add the constraint that all matches are different

      model.add( IloAllDiff(env, matchVars));

You also use IloAllDiff to add the constraint that every team must play each week, but only once. For each week, you create an array of variables weekTeams. For each period in this week, you create two variables, one to represent the home team playing in that slot and one to represent the away team playing in that slot. Then you use IloAllDiff to constrain that the 8 variables (one home team and one away team in each of 4 periods) in the array weekTeams must all be different. You use a for loop to add this constraint for each week.

Step 9   -  

Add the constraint that every team must play each week, but only once

Add the following code after the comment
//Add the constraint that every team must play each week, but only once

      for (w = 0; w < nbWeeks; ++w) {
        IloIntVarArray weekTeams(env, nbTeams);
        for (p = 0; p < nbPeriods; ++p) {
          weekTeams[2*p]   = homeTeamVars [p][w];
          weekTeams[2*p+1] = awayTeamVars[p][w];
        }
        model.add( IloAllDiff(env, weekTeams) );
      }

You use the predefined constraint IloDistribute to add the constraint that each team plays at least once, but no more than twice in each period. As you remember from Chapter 6, Using the Distribute Constraint: Car Sequencing, the constraint IloDistribute takes three arrays as parameters: cards, values, and vars. The variables in the array cards are equal to the number of occurrences in the array vars of the values in the array values.

First, you create the array of possible values teamValues. The values in this array represent the teams: Team 0, Team 1, Team 2, Team 3, Team 4, Team 5, Team 6, Team 7.

Step 10   -  

Declare the teamValues array

Add the following code after the comment //Declare the teamValues array

      IloIntArray teams(env, nbTeams);
        for (m = 0; m < nbTeams; ++m)
          teams[m] = m;

Then, you create the array of variables. For each period, you create an array of variables periodTeamVars. For each week in this period, you create two variables, one to represent the home team playing in that slot and one to represent the away team playing in that slot. These variables can take the values declared in the array teamValues array.

Step 11   -  

Declare the periodTeamVars array

Add the following code after the comment //Declare the periodTeamVars array

          for (p = 0; p < nbPeriods; ++p) {
            IloIntVarArray periodTeams(env, 2*nbWeeks);
            for ( w = 0; w < nbWeeks; ++w) {
              periodTeams[ 2*w ]   = homeTeamVars [p][w];
              periodTeams[ 2*w+1 ] = awayTeamVars[p][w];
            };

Since you know that teams must play in at least one game, but no more than two games, the array cards is easy to create. Each variable in this array can have a value of either 1 or 2.

Step 12   -  

Declare the cards array

Add the following code after the comment //Declare the cards array

            IloIntVarArray cardVars(env, nbTeams, 1, 2);

Now, you can add the distribute constraint to the model.

Step 13   -  

Add the distribute constraint

Add the following code after the comment //Add the distribute constraint

            model.add( IloDistribute(env, cardVars, teams, periodTeams) );
          }

This constraint states that the array of variables periodTeamVars can take any value in the array TeamValues. However, the values must all be used at least once, but not more than two times. In other words, each team must play at least once, but not more than twice in each period.