Social Golfer Problem

Problem

In the Social Golfer Problem, we consider a golf club with 32 members. Each of them plays golf once a week, always in groups of 4. The goal of the problem is to build a 10-week schedule for the golfers with maximum socialization, that is, as few repeated meetings as possible. More generally, the problem consists in scheduling m groups of n golfers over p weeks with maximum socialization. For more details, see CSPLib or MathPuzzle.

Principles learned

Data

The instance files we provide comprise three numbers:

  • the number of groups
  • the size of the groups
  • the number of weeks

The instance we use in the example has a known solution with no repeated meetings.

Program

The Hexaly model for the Social Golfer Problem uses Boolean decision variables. For each week w, each golfer gf, and each group gr, x[w][gr][gf] is equal to 1 if golfer gf is in group gr on week w, else 0. Using the ‘sum’ operator, we constrain each golfer to be in exactly one group each week. We also ensure that each group has the right size.

Using the ‘and’ operator, we compute intermediate expressions to determine, for each week and each pair of golfers, whether these golfers meet on that week. We can then use these expressions to compute the total number of meetings between each pair of golfers. Finally, thanks to the ‘max’ operator, we can compute the objective function to minimize, which is the maximum number of times any pair of golfers meets during the season.

Execution
hexaly social_golfer.hxm inFileName=instances/c_4_3_3.in [hxTimeLimit=] [solFileName=]
use io;

/* Read instance data */
function input() {
    local usage = "Usage: hexaly social_golfer.hxm "
            + "inFileName=inputFile [solFileName=outputFile] [hxTimeLimit=timeLimit]";

    if (inFileName == nil) throw usage;

    local inFile = io.openRead(inFileName);
    nbGroups = inFile.readInt();
    groupSize = inFile.readInt();
    nbWeeks = inFile.readInt();

    nbGolfers = nbGroups * groupSize;
}

/* Declare the optimization model */
function model() {
    // Decision variables
    // 0-1 decisions variables: x[w][gr][gf]=1 if golfer gf is in group gr on week w
    x[w in 0...nbWeeks][gr in 0...nbGroups][gf in 0...nbGolfers] <- bool();

    // Each week, each golfer is assigned to exactly one group
    for [w in 0...nbWeeks][gf in 0...nbGolfers]
        constraint sum[gr in 0...nbGroups](x[w][gr][gf]) == 1;

    // Each week, each group contains exactly groupSize golfers
    for [w in 0...nbWeeks][gr in 0...nbGroups]
        constraint sum[gf in 0...nbGolfers](x[w][gr][gf]) == groupSize;

    // Golfers gf0 and gf1 meet in group gr on week w if both are
    // assigned to this group for week w
    meetings[w in 0...nbWeeks][gr in 0...nbGroups][gf0 in 0...nbGolfers][gf1 in gf0+1...nbGolfers]
            <- and(x[w][gr][gf0], x[w][gr][gf1]);

    // The number of meetings of golfers gf0 and gf1 is the sum
    // of their meeting variables over all weeks and groups
    for [gf0 in 0...nbGolfers][gf1 in gf0+1...nbGolfers] {
        nb_meetings[gf0][gf1] <- sum[w in 0...nbWeeks][gr in 0...nbGroups](meetings[w][gr][gf0][gf1]);
        redundant_meetings[gf0][gf1] <- max(nb_meetings[gf0][gf1] -1, 0);
    }

    // The goal is to minimize the number of redundant meetings
    obj <- sum[gf0 in 0...nbGolfers][gf1 in gf0+1...nbGolfers](redundant_meetings[gf0][gf1]);
    minimize obj;
}

/* Parametrize the solver */
function param() {
    if (hxTimeLimit == nil) hxTimeLimit = 10;
}

/* Write the solution in a file with the following format:
 * - the objective value
 * - for each week and each group, write the golfers of the group 
 * (nbWeeks x nbGroupes lines of groupSize numbers). */
function output() {
    if (solFileName == nil) return;
    local solution = io.openWrite(solFileName);
    solution.println(obj.value);
    for [w in 0...nbWeeks] {
        for [gr in 0...nbGroups] {
            for [gf in 0...nbGolfers] {
                if (x[w][gr][gf].value==true) {
                    solution.print(gf, " ");
                }
            }
            solution.println();
        }
        solution.println();
    }
}
Execution (Windows)
set PYTHONPATH=%HX_HOME%\bin\python
python social_golfer.py instances\c_4_3_3.in
 
Execution (Linux)
export PYTHONPATH=/opt/hexaly_13_0/bin/python
python social_golfer.py instances/c_4_3_3.in
import hexaly.optimizer
import sys

if len(sys.argv) < 2:
    print("Usage: python social_golfer.py inputFile [outputFile] [timeLimit]")
    sys.exit(1)


def read_integers(filename):
    with open(filename) as f:
        return [int(elem) for elem in f.read().split()]


with hexaly.optimizer.HexalyOptimizer() as optimizer:
    #
    # Read instance data
    #
    file_it = iter(read_integers(sys.argv[1]))
    nb_groups = next(file_it)
    group_size = next(file_it)
    nb_weeks = next(file_it)
    nb_golfers = nb_groups * group_size

    #
    # Declare the optimization model
    #
    model = optimizer.model

    # Decision variables
    # 0-1 decisions variables: x[w][gr][gf]=1 if golfer gf is in group gr on week w
    x = [[[model.bool() for gf in range(nb_golfers)]
          for gr in range(nb_groups)] for w in range(nb_weeks)]

    # Each week, each golfer is assigned to exactly one group
    for w in range(nb_weeks):
        for gf in range(nb_golfers):
            model.constraint(
                model.eq(model.sum(x[w][gr][gf] for gr in range(nb_groups)), 1))

    # Each week, each group contains exactly group_size golfers
    for w in range(nb_weeks):
        for gr in range(nb_groups):
            model.constraint(
                model.eq(model.sum(x[w][gr][gf] for gf in range(nb_golfers)), group_size))

    # Golfers gf0 and gf1 meet in group gr on week w if both are
    # assigned to this group for week w
    meetings = [None] * nb_weeks
    for w in range(nb_weeks):
        meetings[w] = [None] * nb_groups
        for gr in range(nb_groups):
            meetings[w][gr] = [None] * nb_golfers
            for gf0 in range(nb_golfers):
                meetings[w][gr][gf0] = [None] * nb_golfers
                for gf1 in range(gf0 + 1, nb_golfers):
                    meetings[w][gr][gf0][gf1] = model.and_(x[w][gr][gf0], x[w][gr][gf1])

    # The number of meetings of golfers gf0 and gf1 is the sum
    # of their meeting variables over all weeks and groups
    redundant_meetings = [None] * nb_golfers
    for gf0 in range(nb_golfers):
        redundant_meetings[gf0] = [None] * nb_golfers
        for gf1 in range(gf0 + 1, nb_golfers):
            nb_meetings = model.sum(meetings[w][gr][gf0][gf1] for w in range(nb_weeks)
                                    for gr in range(nb_groups))
            redundant_meetings[gf0][gf1] = model.max(nb_meetings - 1, 0)

    # the goal is to minimize the number of redundant meetings
    obj = model.sum(redundant_meetings[gf0][gf1] for gf0 in range(nb_golfers)
                    for gf1 in range(gf0 + 1, nb_golfers))
    model.minimize(obj)

    model.close()

    # Parameterize the optimizer
    optimizer.param.nb_threads = 1
    if len(sys.argv) >= 4:
        optimizer.param.time_limit = int(sys.argv[3])
    else:
        optimizer.param.time_limit = 10

    optimizer.solve()

    #
    # Write the solution in a file with the following format:
    # - the objective value
    # - for each week and each group, write the golfers of the group
    # (nb_weeks x nbGroupes lines of group_size numbers).
    #
    if len(sys.argv) >= 3:
        with open(sys.argv[2], 'w') as f:
            f.write("%d\n" % obj.value)
            for w in range(nb_weeks):
                for gr in range(nb_groups):
                    for gf in range(nb_golfers):
                        if x[w][gr][gf].value:
                            f.write("%d " % (gf))
                    f.write("\n")
                f.write("\n")
Compilation / Execution (Windows)
cl /EHsc social_golfer.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.lib
social_golfer instances\c_4_3_3.in
 
Compilation / Execution (Linux)
g++ social_golfer.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o social_golfer
./social_golfer instances/c_4_3_3.in
#include "optimizer/hexalyoptimizer.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>

using namespace hexaly;
using namespace std;

class SocialGolfer {
public:
    // Number of groups
    int nbGroups;
    // Size of each group
    int groupSize;
    // Number of week
    int nbWeeks;
    // Number of golfers
    int nbGolfers;

    // Objective
    HxExpression obj;

    // Hexaly Optimizer
    HexalyOptimizer optimizer;

    // Decisions variables
    vector<vector<vector<HxExpression>>> x;

    // Read instance data
    void readInstance(const string& fileName) {
        ifstream infile;
        infile.exceptions(ifstream::failbit | ifstream::badbit);
        infile.open(fileName.c_str());

        infile >> nbGroups;
        infile >> groupSize;
        infile >> nbWeeks;
        infile.close();

        nbGolfers = nbGroups * groupSize;
    }

    // Declare the optimization model
    void solve(int limit) {
        HxModel model = optimizer.getModel();

        // Decision variables
        // 0-1 decisions variables: x[w][gr][gf]=1 if golfer gf is in group gr on week w
        x.resize(nbWeeks);
        for (int w = 0; w < nbWeeks; ++w) {
            x[w].resize(nbGroups);
            for (int gr = 0; gr < nbGroups; ++gr) {
                x[w][gr].resize(nbGolfers);
                for (int gf = 0; gf < nbGolfers; ++gf) {
                    x[w][gr][gf] = model.boolVar();
                }
            }
        }

        // Each week, each golfer is assigned to exactly one group
        for (int w = 0; w < nbWeeks; ++w) {
            for (int gf = 0; gf < nbGolfers; ++gf) {
                HxExpression nbGroupsAssigned = model.sum();
                for (int gr = 0; gr < nbGroups; ++gr) {
                    nbGroupsAssigned.addOperand(x[w][gr][gf]);
                }
                model.constraint(nbGroupsAssigned == 1);
            }
        }

        // Each week, each group contains exactly groupSize golfers
        for (int w = 0; w < nbWeeks; ++w) {
            for (int gr = 0; gr < nbGroups; ++gr) {
                HxExpression nbGolfersInGroup = model.sum();
                for (int gf = 0; gf < nbGolfers; ++gf) {
                    nbGolfersInGroup.addOperand(x[w][gr][gf]);
                }
                model.constraint(nbGolfersInGroup == groupSize);
            }
        }

        // Golfers gf0 and gf1 meet in group gr on week w if both are
        // assigned to this group for week w
        vector<vector<vector<vector<HxExpression>>>> meetings;
        meetings.resize(nbWeeks);
        for (int w = 0; w < nbWeeks; ++w) {
            meetings[w].resize(nbGroups);
            for (int gr = 0; gr < nbGroups; ++gr) {
                meetings[w][gr].resize(nbGolfers);
                for (int gf0 = 0; gf0 < nbGolfers; ++gf0) {
                    meetings[w][gr][gf0].resize(nbGolfers);
                    for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1) {
                        meetings[w][gr][gf0][gf1] = model.and_(x[w][gr][gf0], x[w][gr][gf1]);
                    }
                }
            }
        }

        // The number of meetings of golfers gf0 and gf1 is the sum of
        // their meeting variables over all weeks and groups
        vector<vector<HxExpression>> redundantMeetings;
        redundantMeetings.resize(nbGolfers);
        for (int gf0 = 0; gf0 < nbGolfers; ++gf0) {
            redundantMeetings[gf0].resize(nbGolfers);
            for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1) {
                HxExpression nbMeetings = model.sum();
                for (int w = 0; w < nbWeeks; ++w) {
                    for (int gr = 0; gr < nbGroups; ++gr) {
                        nbMeetings.addOperand(meetings[w][gr][gf0][gf1]);
                    }
                }
                redundantMeetings[gf0][gf1] = model.max(nbMeetings - 1, 0);
            }
        }

        // The goal is to minimize the number of redundant meetings
        obj = model.sum();
        for (int gf0 = 0; gf0 < nbGolfers; ++gf0) {
            for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1) {
                obj.addOperand(redundantMeetings[gf0][gf1]);
            }
        }
        model.minimize(obj);

        model.close();
        // Parametrize the optimizer
        optimizer.getParam().setTimeLimit(limit);

        optimizer.solve();
    }

    /* Write the solution in a file with the following format:
     *  - the objective value
     *  - for each week and each group, write the golfers of the group
     * (nbWeeks x nbGroupes lines of groupSize numbers). */
    void writeSolution(const string& fileName) {
        ofstream outfile;
        outfile.exceptions(ofstream::failbit | ofstream::badbit);
        outfile.open(fileName.c_str());

        outfile << obj.getValue() << endl;
        for (int w = 0; w < nbWeeks; ++w) {
            for (int gr = 0; gr < nbGroups; ++gr) {
                for (int gf = 0; gf < nbGolfers; ++gf) {
                    if (x[w][gr][gf].getValue()) {
                        outfile << gf << " ";
                    }
                }
                outfile << endl;
            }
            outfile << endl;
        }
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        cerr << "Usage: social_golfer inputFile [outputFile] [timeLimit]" << endl;
        return 1;
    }

    const char* instanceFile = argv[1];
    const char* solFile = argc > 2 ? argv[2] : NULL;
    const char* strTimeLimit = argc > 3 ? argv[3] : "10";

    try {
        SocialGolfer model;
        model.readInstance(instanceFile);
        model.solve(atoi(strTimeLimit));
        if (solFile != NULL)
            model.writeSolution(solFile);
        return 0;
    } catch (const exception& e) {
        cerr << "An error occurred: " << e.what() << endl;
        return 1;
    }
}
Compilation / Execution (Windows)
copy %HX_HOME%\bin\Hexaly.NET.dll .
csc SocialGolfer.cs /reference:Hexaly.NET.dll
SocialGolfer instances\c_4_3_3.in
using System;
using System.IO;
using Hexaly.Optimizer;

public class SocialGolfer : IDisposable
{
    // Number of groups
    int nbGroups;

    // Size of each group
    int groupSize;

    // Number of week
    int nbWeeks;

    // Number of golfers
    int nbGolfers;

    // Hexaly Optimizer
    HexalyOptimizer optimizer;

    // Objective
    HxExpression obj;

    // Decisions variables
    HxExpression[,,] x;

    public SocialGolfer()
    {
        optimizer = new HexalyOptimizer();
    }

    /* Read instance data */
    public void ReadInstance(string fileName)
    {
        using (StreamReader input = new StreamReader(fileName))
        {
            var tokens = input.ReadLine().Split(' ');
            nbGroups = int.Parse(tokens[0]);
            groupSize = int.Parse(tokens[1]);
            nbWeeks = int.Parse(tokens[2]);
        }
        nbGolfers = nbGroups * groupSize;
    }

    public void Dispose()
    {
        if (optimizer != null)
            optimizer.Dispose();
    }

    // Declare the optimization model
    public void Solve(int limit)
    {
        HxModel model = optimizer.GetModel();

        // Decision variables
        // 0-1 decisions variables: x[w,gr,gf]=1 if golfer gf is in group gr on week w
        x = new HxExpression[nbWeeks, nbGroups, nbGolfers];
        for (int w = 0; w < nbWeeks; ++w)
        {
            for (int gr = 0; gr < nbGroups; ++gr)
            {
                for (int gf = 0; gf < nbGolfers; ++gf)
                    x[w, gr, gf] = model.Bool();
            }
        }

        // Each week, each golfer is assigned to exactly one group
        for (int w = 0; w < nbWeeks; ++w)
        {
            for (int gf = 0; gf < nbGolfers; ++gf)
            {
                HxExpression nbGroupsAssigned = model.Sum();
                for (int gr = 0; gr < nbGroups; ++gr)
                    nbGroupsAssigned.AddOperand(x[w, gr, gf]);
                model.Constraint(nbGroupsAssigned == 1);
            }
        }

        // Each week, each group contains exactly groupSize golfers
        for (int w = 0; w < nbWeeks; ++w)
        {
            for (int gr = 0; gr < nbGroups; ++gr)
            {
                HxExpression nbGolfersInGroup = model.Sum();
                for (int gf = 0; gf < nbGolfers; ++gf)
                    nbGolfersInGroup.AddOperand(x[w, gr, gf]);
                model.Constraint(nbGolfersInGroup == groupSize);
            }
        }

        // Golfers gf0 and gf1 meet in group gr on week w if both are
        // assigned to this group for week w
        HxExpression[,,,] meetings = new HxExpression[nbWeeks, nbGroups, nbGolfers, nbGolfers];
        for (int w = 0; w < nbWeeks; ++w)
        {
            for (int gr = 0; gr < nbGroups; ++gr)
            {
                for (int gf0 = 0; gf0 < nbGolfers; ++gf0)
                {
                    for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1)
                        meetings[w, gr, gf0, gf1] = model.And(x[w, gr, gf0], x[w, gr, gf1]);
                }
            }
        }

        // The number of meetings of golfers gf0 and gf1 is the sum
        // of their meeting variables over all weeks and groups
        HxExpression[,] redundantMeetings = new HxExpression[nbGolfers, nbGolfers];
        for (int gf0 = 0; gf0 < nbGolfers; ++gf0)
        {
            for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1)
            {
                HxExpression nbMeetings = model.Sum();
                for (int w = 0; w < nbWeeks; ++w)
                {
                    for (int gr = 0; gr < nbGroups; ++gr)
                        nbMeetings.AddOperand(meetings[w, gr, gf0, gf1]);
                }
                redundantMeetings[gf0, gf1] = model.Max(nbMeetings - 1, 0);
            }
        }

        // The goal is to minimize the number of redundant meetings
        obj = model.Sum();
        for (int gf0 = 0; gf0 < nbGolfers; ++gf0)
        {
            for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1)
                obj.AddOperand(redundantMeetings[gf0, gf1]);
        }
        model.Minimize(obj);

        model.Close();

        // Parametrize the optimizer
        optimizer.GetParam().SetTimeLimit(limit);
        optimizer.Solve();
    }

    /* Write the solution in a file with the following format:
     * - the objective value
     * - for each week and each group, write the golfers of the group
     * (nbWeeks x nbGroupes lines of groupSize numbers). */
    public void WriteSolution(string fileName)
    {
        using (StreamWriter output = new StreamWriter(fileName))
        {
            output.WriteLine(obj.GetValue());
            for (int w = 0; w < nbWeeks; ++w)
            {
                for (int gr = 0; gr < nbGroups; ++gr)
                {
                    for (int gf = 0; gf < nbGolfers; ++gf)
                    {
                        if (x[w, gr, gf].GetValue() == 1)
                            output.Write(gf + " ");
                    }
                    output.WriteLine();
                }
                output.WriteLine();
            }
        }
    }

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: SocialGolfer inputFile [solFile] [timeLimit]");
            Environment.Exit(1);
        }

        string instanceFile = args[0];
        string outputFile = args.Length > 1 ? args[1] : null;
        string strTimeLimit = args.Length > 2 ? args[2] : "10";

        using (SocialGolfer model = new SocialGolfer())
        {
            model.ReadInstance(instanceFile);
            model.Solve(int.Parse(strTimeLimit));
            if (outputFile != null)
                model.WriteSolution(outputFile);
        }
    }
}
Compilation / Execution (Windows)
javac SocialGolfer.java -cp %HX_HOME%\bin\hexaly.jar
java -cp %HX_HOME%\bin\hexaly.jar;. SocialGolfer instances\c_4_3_3.in
Compilation / Execution (Linux)
javac SocialGolfer.java -cp /opt/hexaly_13_0/bin/hexaly.jar
java -cp /opt/hexaly_13_0/bin/hexaly.jar:. SocialGolfer instances/c_4_3_3.in
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;

public class SocialGolfer {
    // Number of groups
    private int nbGroups;
    // Size of each group
    private int groupSize;
    // Number of week
    private int nbWeeks;
    // Number of golfers
    private int nbGolfers;

    // Objective
    private HxExpression obj;

    // Hexaly Optimizer.
    private final HexalyOptimizer optimizer;

    // Decisions variables
    private HxExpression[][][] x;

    private SocialGolfer(HexalyOptimizer optimizer) {
        this.optimizer = optimizer;
    }

    // Read instance data
    private void readInstance(String fileName) throws IOException {
        try (Scanner input = new Scanner(new File(fileName))) {
            nbGroups = input.nextInt();
            groupSize = input.nextInt();
            nbWeeks = input.nextInt();
        }
        nbGolfers = nbGroups * groupSize;
    }

    private void solve(int limit) {
        // Declare the optimization model
        HxModel model = optimizer.getModel();

        x = new HxExpression[nbWeeks][nbGroups][nbGolfers];

        // Decision variables
        // 0-1 decisions variables: x[w][gr][gf]=1 if golfer gf is in group gr on week w
        for (int w = 0; w < nbWeeks; ++w) {
            for (int gr = 0; gr < nbGroups; ++gr) {
                for (int gf = 0; gf < nbGolfers; ++gf) {
                    x[w][gr][gf] = model.boolVar();
                }
            }
        }

        // Each week, each golfer is assigned to exactly one group
        for (int w = 0; w < nbWeeks; ++w) {
            for (int gf = 0; gf < nbGolfers; ++gf) {
                HxExpression nbGroupsAssigned = model.sum();
                for (int gr = 0; gr < nbGroups; ++gr) {
                    nbGroupsAssigned.addOperand(x[w][gr][gf]);
                }
                model.constraint(model.eq(nbGroupsAssigned, 1));
            }
        }

        // Each week, each group contains exactly groupSize golfers
        for (int w = 0; w < nbWeeks; ++w) {
            for (int gr = 0; gr < nbGroups; ++gr) {
                HxExpression nbGolfersInGroup = model.sum();
                for (int gf = 0; gf < nbGolfers; ++gf) {
                    nbGolfersInGroup.addOperand(x[w][gr][gf]);
                }
                model.constraint(model.eq(nbGolfersInGroup, groupSize));
            }
        }

        // Golfers gf0 and gf1 meet in group gr on week w if both are
        // assigned to this group for week w
        HxExpression[][][][] meetings = new HxExpression[nbWeeks][nbGroups][nbGolfers][nbGolfers];
        for (int w = 0; w < nbWeeks; ++w) {
            for (int gr = 0; gr < nbGroups; ++gr) {
                for (int gf0 = 0; gf0 < nbGolfers; ++gf0) {
                    for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1) {
                        meetings[w][gr][gf0][gf1] = model.and(x[w][gr][gf0], x[w][gr][gf1]);
                    }
                }
            }
        }

        // The number of meetings of golfers gf0 and gf1 is the sum
        // of their meeting variables over all weeks and groups
        HxExpression[][] redundantMeetings;
        redundantMeetings = new HxExpression[nbGolfers][nbGolfers];
        for (int gf0 = 0; gf0 < nbGolfers; ++gf0) {
            for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1) {
                HxExpression nbMeetings = model.sum();
                for (int w = 0; w < nbWeeks; ++w) {
                    for (int gr = 0; gr < nbGroups; ++gr) {
                        nbMeetings.addOperand(meetings[w][gr][gf0][gf1]);
                    }
                }
                redundantMeetings[gf0][gf1] = model.max(model.sub(nbMeetings, 1), 0);
            }
        }

        // the goal is to minimize the number of redundant meetings
        obj = model.sum();
        for (int gf0 = 0; gf0 < nbGolfers; ++gf0) {
            for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1) {
                obj.addOperand(redundantMeetings[gf0][gf1]);
            }
        }
        model.minimize(obj);

        model.close();

        // Parametrize the optimizer
        optimizer.getParam().setTimeLimit(limit);

        optimizer.solve();
    }

    /* Write the solution in a file with the following format:
     *- the objective value
     * - for each week and each group, write the golfers of the group
     * (nbWeeks x nbGroupes lines of groupSize numbers). */
    private void writeSolution(String fileName) throws IOException {
        try (PrintWriter output = new PrintWriter(fileName)) {
            output.println(obj.getValue());
            for (int w = 0; w < nbWeeks; ++w) {
                for (int gr = 0; gr < nbGroups; ++gr) {
                    for (int gf = 0; gf < nbGolfers; ++gf) {
                        if (x[w][gr][gf].getValue() == 1) {
                            output.print(gf + " ");
                        }
                    }
                    output.println();
                }
                output.println();
            }
        }
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.err.println("Usage: java SocialGolfer inputFile [outputFile] [timeLimit]");
            System.exit(1);
        }

        String instanceFile = args[0];
        String outputFile = args.length > 1 ? args[1] : null;
        String strTimeLimit = args.length > 2 ? args[2] : "10";

        try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
            SocialGolfer model = new SocialGolfer(optimizer);
            model.readInstance(instanceFile);
            model.solve(Integer.parseInt(strTimeLimit));
            if (outputFile != null) {
                model.writeSolution(outputFile);
            }
        } catch (Exception ex) {
            System.err.println(ex);
            ex.printStackTrace();
            System.exit(1);
        }
    }

}