'Impose Constraint on CpModel only when some relation between IntVars is true

tl;dr: I'm using Google OR-Tools in Java. I want to add a conditional constraint to a CpModel depending on whether clause involving IntVar variables are true. Basically, something like

CpModel model = new CpModel;
List<Literal> workers = getActiveWorkers();
IntVar startTimeVar = model.newIntVar(0, 100, "start");
int currentTime = getCurrentTime(); //guaranteed to be < 100, say.
IntVar currentTimeVar = model.newConstant(currentTime);
// What I'd want to write but can't
model.addExactlyOne(workers).onlyEnforceIf(currentTimeVar >= startTimeVar);

To replace "currentTime >= startTime", I'd maybe want something like model.addGreaterOrEqual(currentTimeVar, startTimeVar), but this returns (and adds to the model) a Constraint, not a Literal . (I know the parameter type of onlyEnforceIf is Literal[]). What can I do instead?

More Context: I'm trying to generalize the Employee Schedule and Job Shop Scheduling examples on the OR Tools site. I have J jobs, each with some duration called j.duration, to be assigned to W workers. There are two main constraints:

  1. Each job must have a unique worker, and;
  2. Workers can have at most one job at a time.

I want to minimize the makespan, which is bounded above by T = (the sum of all job durations). My issue is, to enforce constraint (1), I need to enforce that for each job j, there is one worker assigned only for the times t satisfying j.start <= t < j.end.

My code is very similar to the two examples I linked. Like the Employee Schedule example, I keep track of an assignment array assignment[w][j][t] of boolean literals, representing if worker w is on job j at time t.

public static void main(String[] args) {
        class Job {
            int duration;
            String name;
        }
        class JobVariable {
            IntVar start, end;
            IntervalVar duration;
        }
        Loader.loadNativeLibraries();
        List<Job> jobs = getJobData();

        final int numJobs = jobs.size();
        final int numWorkers = 3;
        final int timeUpperBound = items.stream().mapToInt(j -> j.duration).sum();

        CpModel model = new CpModel();

        //Initialize assignments array.
        Literal[][][] assignments = new Literal[numWorkers][numJobs][timeUpperBound];
        for(int w = 0; w < numWorkers; w++){
            for(int j = 0; j < numJobs; j++){
                for(int t = 0; t < timeUpperBound; t++){
                    assignments[w][j][t] = model.newBoolVar("Worker"+w+"Job"+j+"Time"+t);
                }
            }
        }

        //Initialize IntVars and IntervalVar for each job
        Map<Job, JobVariable> jobToVariable = new HashMap<>();
        for(Job job : jobs){
            JobVariable jobVar = new JobVariable();
            jobVar.start = model.newIntVar(0, timeUpperBound, "start:" + job.name);
            jobVar.end = model.newIntVar(0, timeUpperBound, "end:" + job.name);
            jobVar.duration = model.newIntervalVar(jobVar.start, 
            LinearExpr.constant(job.duration), jobVar.end, "interval:" + job.name);
            jobToVariable.put(job, jobVar);
        }
        eachWorkerHasAtMostOneTaskAtATime(assignments, numWorkers, numJobs, timeUpperBound, model);

        eachTaskHasUniqueWorker(assignments, numWorkers, numJobs, timeUpperBound, model, jobToVariable);

        //Solve model...
        CpSolver solver = new CpSolver();
        CpSolverStatus status = solver.solve(model);
    }

The method eachWorkerHasAtMostOneTaskAtATime looks just like the sample code, "summing" across workers for each job and time, and is identical to a snippet in the Employee Scheduling link. The other method is where my problem arises - I need to ensure that (a) each job has one worker while it is active, and (b) the same worker handles the job while it is active. The mathematical constraints for these are in the commented in the code snippet below:

public static void eachJobHasUniqueWorker(Literal[][][] assignments,
                                             int numWorkers,
                                             int numJobs,
                                             int timeUpperBound,
                                             CpModel model,
                                             Map<Job, JobVariable> jobToVariable,
                                             List<Job> jobs){
        // Constraint (a): 
        // for all jobs j,
        //  and for all time t where j.start <= t < j.end,
        //   ensure that exactly one worker is assigned to job j.

        for(int j = 0; j < numJobs; j++){
            JobVariable jobVariable = jobToVariable.get(jobs.get(j));
            for(int t = 0; t < timeUpperBound; t++){
                Literal timeInJobInterval = model.newBoolVar("time_"+t+"job_"+j);
                
                //tie the value of timeInJobInterval
                //to something reflecting j.start <= t < j.end
                model.AddBoolAnd(timeInJobInterval, SOMETHING GOES HERE?)

                List<Literal> workers = new ArrayList<>();
                for(int w = 0; w < numWorkers; w++){
                    workers.add(assignments[w][j][t]);
                }
 
                model.addExactlyOne(workers).onlyEnforceIf(timeInJobInterval);
            }
        }

        // Constraint (b):
        // for all jobs j,
        //  if assignment[w][j][t] == 1 and t < j.end - 1,
        //   then assignment[w][j][t+1] == 1
        for(int j = 0; j < numJobs; j++){
            for(int t = 0; t < timeUpperBound; t++){
                for(int w = 0; w < numWorkers; w++){
                    Literal timeStrictlyBeforeStepEnd = model.newBoolVar("time"+t+"job"+j);
                    // again, tie the above value to
                    // the condition t < j.end - 1
                    model.addBoolAnd(timeStrictlyBeforeStepEnd, SOMETHING HERE);
                    model.addImplication(assignments[w][j][t], assignments[w][j][t+1]).onlyEnforceIf(timeStrictlyBeforeStepEnd);
                }
            }
        }
    }

As indicated, I want the timeInJobInterval Literal to reflect if the index t lies in the Job's range - if it does then I'm set, but I don't see how to ensure that. The approach outlined in the tl;dr doesn't work either.

Any help would be appreciated. Also, if you notice a cleaner approach than the one I've outlined, please let me know.



Solution 1:[1]

Imagine you have a condition cond between two integer variables and a resulting constraint ct, and you want to enforce cond implies ct.

The best method is to create a Boolean variable cond_is_true, then link it to both ct and cond.

    Literal condIsTrue = model.newBoolVar("condIsTrue");
    model.add(cond).onlyEnforceIf(condIsTrue);
    model.add(negation_of_cond).onlyEnforceIf(condIsTrue.not());
    model.add(ct).onlyEnforceIf(condIsTrue);

See this channeling documentation page.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Laurent Perron