% Elitserien handboll -- generic model, needs specific: % - sets of teams for N and S divisions % - which teams to form complementary divisions % - derby sets % - venue unavailabilities % % Jeff Larson % Mats Carlsson % * 18-team league % * Form 2 divisions which hold a single round-robin tournament % 1. Each 9-team division must hold a SRRT to start the season. % 2. This must be followed by two SRRTs between the entire league, the % second SRRT being a mirror of the first. % 3. There must be a minimum number of breaks in the schedule % (home-home pair or away-away pair). % 4. Each team has one bye during the season (to occur during the % divisional RRT). % 5. At no point during the season can the number of home and away % games played by a team differ by more than 1. % 6. Any pair of teams must have consecutive meetings occur at different % venues. (AVR) include "globals.mzn"; %% version codes bool: dopresolve; %% HAP patterns int: A = 1; % away int: B = 2; % bye int: H = 3; % home int: divsize = 9; set of int: north_team = 1..9; set of int: south_team = 10..18; set of int: team = 1..18; set of int: first_tour = 1..9; set of int: second_tour = 10..26; set of int: third_tour = 27..43; set of int: period = 1..26; set of int: allperiod = 1..43; set of int: breakable = {0,11,13,15,17,19,21,23,25}; array[period,team] of var A..H: hap; array[period,team] of var team: contestant; array[team] of var breakable: break; var int: cost; array[team] of var team: rowof; % rowof[i]=j means TEAMi gets row j of template array[team] of var team: teamof;% the inverse of rowof %% venue unavailability array[team,allperiod] of 0..1: nohome; %% division assignment set of int: group1; set of int: group2; %% derby constraints array[int] of set of int: derby_set; % unused array[int] of period: derby_period; % unused %% complementary schedules array[int] of set of int: cpairs; % unused %% cost table array[int,int] of int: costtable; % unused %% search heuristic array[team] of team: heur; % unused %% DFA states int: INIT = 1; int: B_1 = 2; int: A_1 = 3; int: H_1 = 4; int: A_2 = 5; int: H_2 = 6; int: AB_2 = 7; int: HB_2 = 8; int: A_3 = 9; int: H_3 = 10; int: AB_3 = 11; int: HB_3 = 12; int: A_4 = 13; int: H_4 = 14; int: AB_4 = 15; int: HB_4 = 16; int: A_5 = 17; int: H_5 = 18; int: AB_5 = 19; int: HB_5 = 20; int: A_6 = 21; int: H_6 = 22; int: AB_6 = 23; int: HB_6 = 24; int: A_7 = 25; int: H_7 = 26; int: AB_7 = 27; int: HB_7 = 28; int: A_8 = 29; int: H_8 = 30; int: AB_8 = 31; int: HB_8 = 32; int: AB_9 = 33; int: HB_9 = 34; int: AB_10 = 35; int: HB_10 = 36; int: AB_11 = 37; int: HB_11 = 38; int: AB_20 = 39; int: HB_20 = 40; array[int,int] of int: transition = [| A_1, B_1, H_1 % INIT, start state | AB_2, 0, HB_2 % B_1 | 0, AB_2, H_2 % A_1 | A_2, HB_2, 0 % H_1 | 0, AB_3, H_3 % A_2 | A_3, HB_3, 0 % H_2 | 0, 0, HB_3 % AB_2 | AB_3, 0, 0 % HB_2 | 0, AB_4, H_4 % A_3 | A_4, HB_4, 0 % H_3 | 0, 0, HB_4 % AB_3 | AB_4, 0, 0 % HB_3 | 0, AB_5, H_5 % A_4 | A_5, HB_5, 0 % H_4 | 0, 0, HB_5 % AB_4 | AB_5, 0, 0 % HB_4 | 0, AB_6, H_6 % A_5 | A_6, HB_6, 0 % H_5 | 0, 0, HB_6 % AB_5 | AB_6, 0, 0 % HB_5 | 0, AB_7, H_7 % A_6 | A_7, HB_7, 0 % H_6 | 0, 0, HB_7 % AB_6 | AB_7, 0, 0 % HB_6 | 0, AB_8, H_8 % A_7 | A_8, HB_8, 0 % H_7 | 0, 0, HB_8 % AB_7 | AB_8, 0, 0 % HB_7 | 0, AB_9, 0 % A_8 | 0, HB_9, 0 % H_8 | 0, 0, HB_9 % AB_8 | AB_9, 0, 0 % HB_8 | 0, 0, HB_10 % AB_9 |AB_10, 0, 0 % HB_9 | 0, 0, HB_11 % AB_10, accept state | AB_11, 0, 0 % HB_10, accept state |AB_20, 0, HB_10 % AB_11 |AB_10, 0, HB_20 % HB_11 | 0, 0, HB_20 % AB_20, accept state |AB_20, 0, 0 % HB_20, accept state |]; % cost function predicate part_cost(var breakable: bt, var team: r, var team: t, var 0..max(allperiod): part) ::presolve = ( if dopresolve then bt in breakable /\ let {array[period] of var A..H: lhap} in ( channel_break_hap(bt, lhap, r) /\ part = sum(p in period where nohome[t,p]=1)(bool2int(lhap[p] =H)) + sum(p in third_tour where nohome[t,p]=1)(bool2int(lhap[min(third_tour)+max(period)-p]=A)) ) else part = sum(p in period where nohome[t,p]=1)(bool2int(hap[p,r] =H)) + sum(p in third_tour where nohome[t,p]=1)(bool2int(hap[min(third_tour)+max(period)-p,r]=A)) endif ); constraint if not dopresolve then cost = sum(t in team, p in period where nohome[t,p]=1)(bool2int(hap[p,rowof[t]]=H)) + sum(t in team, p in third_tour where nohome[t,p]=1)(bool2int(hap[min(third_tour)+max(period)-p,rowof[t]]=A)) else let {array[team] of var 0..max(allperiod): rowcost} in ( forall(t in team)(part_cost(break[t], t, teamof[t], rowcost[t])) /\ cost = sum(t in team)(rowcost[t]) ) endif; % division assignment constraint let {var bool: NS} in ( forall(t in group1)(NS <-> rowof[t] in north_team) /\ forall(t in group2)(NS <-> rowof[t] in south_team) ); constraint inverse(rowof,teamof) :: domain; %% STRUCTURAL CONSTAINTS % first round robin tournament (domains) constraint forall(p in first_tour, t in north_team)( contestant[p,t] in north_team ) /\ forall(p in first_tour, t in south_team)( contestant[p,t] in south_team ); % channel break <---> hap (2) predicate channel_break_hap(var breakable: bt, array[period] of var A..H: hapt, var team: t) ::presolve(model) = bt in breakable /\ (t in north_team -> hapt[(t mod divsize)+1] = A) /\ (t in south_team -> hapt[(t mod divsize)+1] = H) /\ hapt[((t-1) mod divsize)+1] = B /\ forall(p in breakable diff {0})( bt = p <-> hapt[p]=hapt[p+1] ) /\ regular([hapt[p] | p in period], HB_20, % last state H, % last symbol transition, INIT, % start state {AB_10,HB_10,AB_20,HB_20} % accept states ); constraint forall(t in team)( channel_break_hap(break[t], [hap[p,t] | p in period], t) ); % RRT (5) constraint forall(p in period)( inverse([contestant[p,t] | t in team],[contestant[p,t] | t in team]) :: domain ); % RRT (6) constraint forall(t in team)( alldifferent([contestant[p,t] | p in first_tour]) :: domain /\ alldifferent([contestant[p,t] | p in second_tour]) :: domain ); % either we at home and contestant away (7) % or we away and contestant at home % or bye (we = contestant) constraint forall(p in period, t in team)( let {var team: ctw = contestant[p,t]} in ( hap[p,ctw] + hap[p,t] = A+H ) ); % AVR rule (1) (8) constraint forall(t in team)( alldifferent([3*contestant[p,t]+hap[p,t] | p in period]) :: domain ); %% IMPLIED CONSTRAINTS % channel contestant <---> hap (3) constraint forall(t in team, p in period)( contestant[p,t]=t <-> hap[p,t]=B ); % exactly two occurrences of every break period (12) constraint global_cardinality_closed(break, [i | i in breakable], [2 | i in breakable]) :: domain; %% SYMMETRY-BREAKING CONSTAINTS % 1st row and 1st column complementary for each division (18) constraint forall(t in north_team)( hap[t,1] + hap[1,t] = A+H /\ hap[t,1+divsize] + hap[1,t+divsize] = A+H ); %% SOLVING AND OUTPUTTING solve :: seq_search( [int_search([hap[p,t] | p in period, t in team], input_order, indomain_min, complete), int_search(rowof, input_order, indomain_min, complete), int_search([contestant[p,t] | t in team, p in period], input_order, indomain_min, complete) ] ) minimize(cost); output [show(cost)];