LocalSolver logo
is now
Hexaly logo

We're excited to share that we are moving forward. We're leaving behind the LocalSolver brand and transitioning to our new identity: Hexaly. This represents a leap forward in our mission to enable every organization to make better decisions faster when faced with operational and strategic challenges.


Social Golfer

Principles learned

  • Create a generic model that uses data

  • Use of n-ary operator “and”

Problem

../_images/golfer.svg

In a golf club, there are 32 social golfers, each of whom plays golf once a week, and always in groups of 4. The problem is to build a schedule of play for 10 weeks with maximum socialisation; that is, as few repeated meetings as possible. More generally the problem is to schedule m groups of n golfers over p weeks, with maximum socialisation. The complexity of the problem is unknown. The instance mentioned has a known solution with no repeated meeting. For more details see CSPLib or MathPuzzle.

Download the example


Data

Each data file is made of only three numbers:

  • the number of groups

  • the size of groups

  • the number of weeks

Program

The decisions variables are binaries x[w][gr][gf], equal to 1 if golfer gf is in group gr on week w. Then, the number of meetings between each pair of golfers is computed in nbMeetings[gf0][gf1]. Finally the number of redundant meetings for a pair of golfers is max(0,nbMeetings[gf0][gf1]-1).

Execution:
localsolver social_golfer.lsp inFileName=instances/c_4_3_3.in [lsTimeLimit=] [solFileName=]
use io;

/* Read instance data */
function input() {
    local usage = "Usage: localsolver social_golfer.lsp "
            + "inFileName=inputFile [solFileName=outputFile] [lsTimeLimit=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 (lsTimeLimit == nil) lsTimeLimit = 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=%LS_HOME%\bin\python
python social_golfer.py instances\c_4_3_3.in
Execution (Linux)
export PYTHONPATH=/opt/localsolver_12_5/bin/python
python social_golfer.py instances/c_4_3_3.in
import localsolver
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 localsolver.LocalSolver() as ls:
    #
    # 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 = ls.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 solver
    ls.param.nb_threads = 1
    if len(sys.argv) >= 4:
        ls.param.time_limit = int(sys.argv[3])
    else:
        ls.param.time_limit = 10

    ls.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%LS_HOME%\include /link %LS_HOME%\bin\localsolver125.lib
social_golfer instances\c_4_3_3.in
Compilation / Execution (Linux)
g++ social_golfer.cpp -I/opt/localsolver_12_5/include -llocalsolver125 -lpthread -o social_golfer
./social_golfer instances/c_4_3_3.in
#include "localsolver.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>

using namespace localsolver;
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
    LSExpression obj;

    // LocalSolver
    LocalSolver localsolver;

    // Decisions variables
    vector<vector<vector<LSExpression>>> 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) {
        LSModel model = localsolver.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) {
                LSExpression 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) {
                LSExpression 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<LSExpression>>>> 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<LSExpression>> redundantMeetings;
        redundantMeetings.resize(nbGolfers);
        for (int gf0 = 0; gf0 < nbGolfers; ++gf0) {
            redundantMeetings[gf0].resize(nbGolfers);
            for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1) {
                LSExpression 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 solver
        localsolver.getParam().setTimeLimit(limit);

        localsolver.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 %LS_HOME%\bin\localsolvernet.dll .
csc SocialGolfer.cs /reference:localsolvernet.dll
SocialGolfer instances\c_4_3_3.in
using System;
using System.IO;
using localsolver;

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;

    // LocalSolver
    LocalSolver localsolver;

    // Objective
    LSExpression obj;

    // Decisions variables
    LSExpression[,,] x;

    public SocialGolfer()
    {
        localsolver = new LocalSolver();
    }

    /* 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 (localsolver != null)
            localsolver.Dispose();
    }

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

        // Decision variables
        // 0-1 decisions variables: x[w,gr,gf]=1 if golfer gf is in group gr on week w
        x = new LSExpression[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)
            {
                LSExpression 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)
            {
                LSExpression 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
        LSExpression[,,,] meetings = new LSExpression[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
        LSExpression[,] redundantMeetings = new LSExpression[nbGolfers, nbGolfers];
        for (int gf0 = 0; gf0 < nbGolfers; ++gf0)
        {
            for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1)
            {
                LSExpression 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 solver
        localsolver.GetParam().SetTimeLimit(limit);
        localsolver.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 %LS_HOME%\bin\localsolver.jar
java -cp %LS_HOME%\bin\localsolver.jar;. SocialGolfer instances\c_4_3_3.in
Compilation / Execution (Linux)
javac SocialGolfer.java -cp /opt/localsolver_12_5/bin/localsolver.jar
java -cp /opt/localsolver_12_5/bin/localsolver.jar:. SocialGolfer instances/c_4_3_3.in
import java.util.*;
import java.io.*;
import localsolver.*;

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 LSExpression obj;

    // LocalSolver.
    private final LocalSolver localsolver;

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

    private SocialGolfer(LocalSolver localsolver) {
        this.localsolver = localsolver;
    }

    // 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
        LSModel model = localsolver.getModel();

        x = new LSExpression[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) {
                LSExpression 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) {
                LSExpression 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
        LSExpression[][][][] meetings = new LSExpression[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
        LSExpression[][] redundantMeetings;
        redundantMeetings = new LSExpression[nbGolfers][nbGolfers];
        for (int gf0 = 0; gf0 < nbGolfers; ++gf0) {
            for (int gf1 = gf0 + 1; gf1 < nbGolfers; ++gf1) {
                LSExpression 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 solver
        localsolver.getParam().setTimeLimit(limit);

        localsolver.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 (LocalSolver localsolver = new LocalSolver()) {
            SocialGolfer model = new SocialGolfer(localsolver);
            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);
        }
    }

}