00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058 #include "base/commandlineflags.h"
00059 #include "base/commandlineflags.h"
00060 #include "base/integral_types.h"
00061 #include "base/logging.h"
00062 #include "base/scoped_ptr.h"
00063 #include "constraint_solver/constraint_solver.h"
00064 #include "constraint_solver/constraint_solveri.h"
00065
00066
00067 DEFINE_int32(num_teams, 10, "Number of teams in the problem.");
00068
00069
00070 DEFINE_int32(time_limit, 20000, "Time limit in ms.");
00071
00072
00073 DEFINE_bool(run_all_heuristics,
00074 true,
00075 "Run all heuristics in impact search, see DefaultPhaseParameters"
00076 " in constraint_solver/constraint_solver.h for details.");
00077 DEFINE_int32(heuristics_period,
00078 30,
00079 "Frequency to run all heuristics, see DefaultPhaseParameters"
00080 " in constraint_solver/constraint_solver.h for details.");
00081 DEFINE_double(restart_log_size,
00082 8.0,
00083 "Threshold for automatic restarting the search in default phase,"
00084 " see DefaultPhaseParameters in "
00085 "constraint_solver/constraint_solver.h for details.");
00086
00087 namespace operations_research {
00088
00089
00090
00091
00092
00093
00094 void ComputeOneDayOneTeamTuples(int num_teams, IntTupleSet* const tuples) {
00095 for (int home_away = 0; home_away <= 1; ++home_away) {
00096 for (int opponent = 0; opponent < num_teams; ++opponent) {
00097 tuples->Insert3(opponent, home_away, opponent + home_away * num_teams);
00098 }
00099 }
00100 }
00101
00102 void AddOneDayOneTeamConstraint(Solver* const solver,
00103 IntVar* const opponent,
00104 IntVar* const home_away,
00105 IntVar* const signed_opponent,
00106 const IntTupleSet& intra_day_tuples) {
00107 std::vector<IntVar*> tmp_vars;
00108 tmp_vars.push_back(opponent);
00109 tmp_vars.push_back(home_away);
00110 tmp_vars.push_back(signed_opponent);
00111 solver->AddConstraint(
00112 solver->MakeAllowedAssignments(tmp_vars, intra_day_tuples));
00113 }
00114
00115
00116
00117
00118
00119 void ComputeOneDayTuples(int num_teams, IntTupleSet* const day_tuples) {
00120 LOG(INFO) << "Compute possible opponents and locations for any day.";
00121 Solver solver("ComputeOneDayTuples");
00122
00123
00124 std::vector<IntVar*> opponents;
00125 std::vector<IntVar*> home_aways;
00126 std::vector<IntVar*> signed_opponents;
00127 solver.MakeIntVarArray(num_teams, 0, num_teams - 1, "opponent_", &opponents);
00128 solver.MakeBoolVarArray(num_teams, "home_away_", &home_aways);
00129 solver.MakeIntVarArray(num_teams,
00130 0,
00131 2 * num_teams - 1,
00132 "signed_opponent_",
00133 &signed_opponents);
00134
00135
00136 solver.AddConstraint(solver.MakeAllDifferent(opponents));
00137
00138
00139 for (int i = 0; i < num_teams; ++i) {
00140 solver.AddConstraint(solver.MakeNonEquality(opponents[i], i));
00141 }
00142
00143
00144 for (int i = 0; i < num_teams; ++i) {
00145 for (int j = 0; j < num_teams; ++j) {
00146 if (i != j) {
00147 solver.AddConstraint(
00148 solver.MakeEquality(solver.MakeIsEqualCstVar(opponents[i], j),
00149 solver.MakeIsEqualCstVar(opponents[j], i)));
00150 }
00151 }
00152 }
00153
00154
00155 solver.AddConstraint(solver.MakeSumEquality(home_aways, num_teams / 2));
00156
00157
00158 IntTupleSet one_day_one_team_tuples(3);
00159 ComputeOneDayOneTeamTuples(num_teams, &one_day_one_team_tuples);
00160 for (int team_index = 0; team_index < num_teams; ++team_index) {
00161 std::vector<IntVar*> tmp_vars;
00162 tmp_vars.push_back(opponents[team_index]);
00163 tmp_vars.push_back(home_aways[team_index]);
00164 tmp_vars.push_back(signed_opponents[team_index]);
00165 solver.AddConstraint(
00166 solver.MakeAllowedAssignments(tmp_vars, one_day_one_team_tuples));
00167 }
00168
00169
00170 for (int first_team = 0; first_team < num_teams; ++first_team) {
00171 IntVar* const first_home_away = home_aways[first_team];
00172 IntVar* const second_home_away =
00173 solver.MakeElement(home_aways, opponents[first_team])->Var();
00174 IntVar* const reverse_second_home_away =
00175 solver.MakeDifference(1, second_home_away)->Var();
00176 solver.AddConstraint(
00177 solver.MakeEquality(first_home_away, reverse_second_home_away));
00178 }
00179
00180
00181 DecisionBuilder* const db = solver.MakePhase(signed_opponents,
00182 Solver::CHOOSE_FIRST_UNBOUND,
00183 Solver::ASSIGN_MIN_VALUE);
00184 solver.NewSearch(db);
00185 while (solver.NextSolution()) {
00186 std::vector<int> solution;
00187 for (int i = 0; i < num_teams; ++i) {
00188 solution.push_back(signed_opponents[i]->Value());
00189 }
00190 day_tuples->Insert(solution);
00191 }
00192 solver.EndSearch();
00193 LOG(INFO) << day_tuples->NumTuples()
00194 << " solutions to the one-day matching problem";
00195 }
00196
00197
00198 void AddOneTeamConstraints(Solver* const solver,
00199 const std::vector<IntVar*>& opponents,
00200 const std::vector<IntVar*>& home_aways,
00201 const std::vector<IntVar*>& signed_opponents,
00202 const IntTupleSet& home_away_tuples,
00203 IntVar* const break_var,
00204 int num_teams) {
00205 const int half_season = num_teams - 1;
00206
00207
00208 for (int half = 0; half <= 1; ++half) {
00209 for (int team_index = 0; team_index < num_teams; ++team_index) {
00210 std::vector<IntVar*> tmp_vars;
00211 for (int day = 0; day < half_season; ++day) {
00212 tmp_vars.push_back(opponents[half * half_season + day]);
00213 }
00214 solver->AddConstraint(solver->MakeAllDifferent(tmp_vars));
00215 }
00216 }
00217
00218
00219 for (int team_index = 0; team_index < num_teams; ++team_index) {
00220 solver->AddConstraint(solver->MakeAllDifferent(signed_opponents));
00221 }
00222
00223
00224 for (int i = 0; i < num_teams; ++i) {
00225 std::vector<IntVar*> tmp_vars(home_aways);
00226 tmp_vars.push_back(break_var);
00227 solver->AddConstraint(
00228 solver->MakeAllowedAssignments(tmp_vars, home_away_tuples));
00229 }
00230 }
00231
00232
00233
00234
00235
00236 void ComputeOneTeamHomeAwayTuples(int num_teams,
00237 IntTupleSet* const home_away_tuples) {
00238 LOG(INFO) << "Compute possible sequence of home and aways for any team.";
00239 const int half_season = num_teams - 1;
00240 const int full_season = 2 * half_season;
00241
00242 Solver solver("compute_home_aways");
00243 std::vector<IntVar*> home_aways;
00244 solver.MakeBoolVarArray(full_season, "home_away_", &home_aways);
00245 for (int day = 0; day < full_season - 2; ++day) {
00246 std::vector<IntVar*> tmp_vars;
00247 tmp_vars.push_back(home_aways[day]);
00248 tmp_vars.push_back(home_aways[day + 1]);
00249 tmp_vars.push_back(home_aways[day + 2]);
00250 IntVar* const partial_sum = solver.MakeSum(tmp_vars)->Var();
00251 solver.AddConstraint(solver.MakeBetweenCt(partial_sum, 1, 2));
00252 }
00253 DecisionBuilder* const db = solver.MakePhase(home_aways,
00254 Solver::CHOOSE_FIRST_UNBOUND,
00255 Solver::ASSIGN_MIN_VALUE);
00256 solver.NewSearch(db);
00257 while (solver.NextSolution()) {
00258 std::vector<int> solution;
00259 for (int i = 0; i < full_season; ++i) {
00260 solution.push_back(home_aways[i]->Value());
00261 }
00262 int breaks = 0;
00263 for (int i = 0; i < full_season - 1; ++i) {
00264 breaks += (solution[i] == solution[i + 1]);
00265 }
00266 solution.push_back(breaks);
00267 home_away_tuples->Insert(solution);
00268 }
00269 solver.EndSearch();
00270 LOG(INFO) << home_away_tuples->NumTuples()
00271 << " combination of home_aways for a team on the full season";
00272 }
00273
00274
00275
00276
00277 void SportsScheduling(int num_teams) {
00278 const int half_season = num_teams - 1;
00279 const int full_season = 2 * half_season;
00280
00281 Solver solver("Sports Scheduling");
00282
00283
00284
00285
00286 std::vector<std::vector<IntVar*> > opponents(num_teams);
00287
00288 std::vector<std::vector<IntVar*> > home_aways(num_teams);
00289
00290
00291 std::vector<std::vector<IntVar*> > signed_opponents(num_teams);
00292 for (int team_index = 0; team_index < num_teams; ++team_index) {
00293 solver.MakeIntVarArray(full_season,
00294 0,
00295 num_teams - 1,
00296 StringPrintf("opponent_%d_", team_index),
00297 &opponents[team_index]);
00298 solver.MakeBoolVarArray(full_season,
00299 StringPrintf("home_away_%d_", team_index),
00300 &home_aways[team_index]);
00301 solver.MakeIntVarArray(full_season,
00302 0,
00303 2 * num_teams - 1,
00304 StringPrintf("signed_opponent_%d", team_index),
00305 &signed_opponents[team_index]);
00306 }
00307
00308
00309
00310 IntTupleSet one_day_tuples(num_teams);
00311 ComputeOneDayTuples(num_teams, &one_day_tuples);
00312 for (int day = 0; day < full_season; ++day) {
00313 std::vector<IntVar*> all_vars;
00314 for (int i = 0; i < num_teams; ++i) {
00315 all_vars.push_back(signed_opponents[i][day]);
00316 }
00317 solver.AddConstraint(
00318 solver.MakeAllowedAssignments(all_vars, one_day_tuples));
00319 }
00320
00321
00322 IntTupleSet one_day_one_team_tuples(3);
00323 ComputeOneDayOneTeamTuples(num_teams, &one_day_one_team_tuples);
00324 for (int day = 0; day < full_season; ++day) {
00325 for (int team_index = 0; team_index < num_teams; ++team_index) {
00326 AddOneDayOneTeamConstraint(&solver,
00327 opponents[team_index][day],
00328 home_aways[team_index][day],
00329 signed_opponents[team_index][day],
00330 one_day_one_team_tuples);
00331 }
00332 }
00333
00334
00335 IntTupleSet home_away_tuples(full_season + 1);
00336 ComputeOneTeamHomeAwayTuples(num_teams, &home_away_tuples);
00337 std::vector<IntVar*> team_breaks;
00338 solver.MakeIntVarArray(num_teams,
00339 0,
00340 full_season,
00341 "team_break_",
00342 &team_breaks);
00343 for (int team = 0; team < num_teams; ++team) {
00344 AddOneTeamConstraints(&solver,
00345 opponents[team],
00346 home_aways[team],
00347 signed_opponents[team],
00348 home_away_tuples,
00349 team_breaks[team],
00350 num_teams);
00351 }
00352
00353
00354
00355 std::vector<SearchMonitor*> monitors;
00356
00357
00358 IntVar* const objective_var =
00359 solver.MakeSum(team_breaks)->VarWithName("SumOfBreaks");
00360 OptimizeVar* const objective_monitor = solver.MakeMinimize(objective_var, 1);
00361 monitors.push_back(objective_monitor);
00362
00363
00364 std::vector<IntVar*> all_signed_opponents;
00365 for (int team_index = 0; team_index < num_teams; ++team_index) {
00366 for (int day = 0; day < full_season; ++day) {
00367 all_signed_opponents.push_back(signed_opponents[team_index][day]);
00368 }
00369 }
00370
00371
00372 DefaultPhaseParameters parameters;
00373 parameters.run_all_heuristics = FLAGS_run_all_heuristics;
00374 parameters.heuristic_period = FLAGS_heuristics_period;
00375 parameters.restart_log_size = FLAGS_restart_log_size;
00376 DecisionBuilder* const db =
00377 solver.MakeDefaultPhase(all_signed_opponents, parameters);
00378
00379
00380 SearchMonitor* const log = solver.MakeSearchLog(1000000, objective_monitor);
00381 monitors.push_back(log);
00382
00383
00384 SearchLimit* const limit = solver.MakeTimeLimit(FLAGS_time_limit);
00385 monitors.push_back(limit);
00386
00387
00388 SolutionCollector* const collector = solver.MakeLastSolutionCollector();
00389 for (int team_index = 0; team_index < num_teams; ++team_index) {
00390 collector->Add(opponents[team_index]);
00391 collector->Add(home_aways[team_index]);
00392 }
00393 monitors.push_back(collector);
00394
00395
00396 solver.Solve(db, monitors);
00397
00398
00399 if (collector->solution_count() == 1) {
00400 LOG(INFO) << "Solution found in " << solver.wall_time() << " ms, and "
00401 << solver.failures() << " failures.";
00402 for (int team_index = 0; team_index < num_teams; ++team_index) {
00403 string line;
00404 for (int day = 0; day < full_season; ++day) {
00405 const int opponent = collector->Value(0, opponents[team_index][day]);
00406 const int home_away = collector->Value(0, home_aways[team_index][day]);
00407 line += StringPrintf("%2d%s ", opponent, home_away ? "@" : " ");
00408 }
00409 LOG(INFO) << line;
00410 }
00411 }
00412 }
00413 }
00414
00415 static const char kUsage[] =
00416 "Usage: see flags.\nThis program runs a sports scheduling problem."
00417 "There is no output besides the debug LOGs of the solver.";
00418
00419 int main(int argc, char **argv) {
00420 google::SetUsageMessage(kUsage);
00421 google::ParseCommandLineFlags(&argc, &argv, true);
00422 CHECK_EQ(0, FLAGS_num_teams % 2) << "The number of teams must be even";
00423 CHECK_GE(FLAGS_num_teams, 2) << "At least 2 teams";
00424 CHECK_LT(FLAGS_num_teams, 16) << "The model does not scale beyond 14 teams";
00425 operations_research::SportsScheduling(FLAGS_num_teams);
00426 return 0;
00427 }