diff --git a/examples/python/flexible_job_shop_sat.py b/examples/python/flexible_job_shop_sat.py index 6692acd59d..41e16d9d7b 100644 --- a/examples/python/flexible_job_shop_sat.py +++ b/examples/python/flexible_job_shop_sat.py @@ -121,7 +121,7 @@ def flexible_jobshop(): starts[(job_id, task_id)] = start # Add precedence with previous task in the same job. - if previous_end: + if previous_end is not None: model.Add(start >= previous_end) previous_end = end diff --git a/ortools/sat/doc/scheduling.md b/ortools/sat/doc/scheduling.md index ccbae05c38..3a3452b214 100644 --- a/ortools/sat/doc/scheduling.md +++ b/ortools/sat/doc/scheduling.md @@ -637,12 +637,12 @@ def RankTasks(model, starts, presences, ranks): for i in range(num_tasks - 1): for j in range(i + 1, num_tasks): tmp_array = [precedences[(i, j)], precedences[(j, i)]] - if presences[i] != 1: + if not cp_model.IsTrueLiteral(presences[i]): tmp_array.append(presences[i].Not()) # Makes sure that if i is not performed, all precedences are false. model.AddImplication(presences[i].Not(), precedences[(i, j)].Not()) model.AddImplication(presences[i].Not(), precedences[(j, i)].Not()) - if presences[j] != 1: + if not cp_model.IsTrueLiteral(presences[j]): tmp_array.append(presences[j].Not()) # Makes sure that if j is not performed, all precedences are false. model.AddImplication(presences[j].Not(), precedences[(i, j)].Not()) @@ -707,10 +707,7 @@ def RankingSampleSat(): # Creates makespan variable. makespan = model.NewIntVar(0, horizon, 'makespan') for t in all_tasks: - if presences[t] == 1: - model.Add(ends[t] <= makespan) - else: - model.Add(ends[t] <= makespan).OnlyEnforceIf(presences[t]) + model.Add(ends[t] <= makespan).OnlyEnforceIf(presences[t]) # Minimizes makespan - fixed gain per tasks performed. # As the fixed cost is less that the duration of the last interval, diff --git a/ortools/sat/python/cp_model.py b/ortools/sat/python/cp_model.py index c5be75a8d3..4175d81ea0 100644 --- a/ortools/sat/python/cp_model.py +++ b/ortools/sat/python/cp_model.py @@ -253,9 +253,41 @@ class LinearExpr(object): 'calling %% on a linear expression is not supported, ' 'please use CpModel.AddModuloEquality') + def __pow__(self, _): + raise NotImplementedError( + 'calling ** on a linear expression is not supported, ' + 'please use CpModel.AddMultiplicationEquality') + + def __lshift__(self, _): + raise NotImplementedError( + 'calling left shift on a linear expression is not supported') + + def __rshift__(self, _): + raise NotImplementedError( + 'calling right shift on a linear expression is not supported') + + def __and__(self, _): + raise NotImplementedError( + 'calling and on a linear expression is not supported, ' + 'please use CpModel.AddBoolAnd') + + def __or__(self, _): + raise NotImplementedError( + 'calling or on a linear expression is not supported, ' + 'please use CpModel.AddBoolOr') + + def __xor__(self, _): + raise NotImplementedError( + 'calling xor on a linear expression is not supported, ' + 'please use CpModel.AddBoolXor') + def __neg__(self): return _ProductCst(self, -1) + def __bool__(self): + raise NotImplementedError( + 'Evaluating a LinearExpr instance as a Boolean is not implemented.') + def __eq__(self, arg): if arg is None: return False @@ -480,6 +512,12 @@ class IntVar(LinearExpr): """Returns the variable protobuf.""" return self.__var + def IsEqualTo(self, other): + """Returns true if self == other in the python sense.""" + if not isinstance(other, IntVar): + return False + return self.Index() == other.Index() + def __str__(self): if not self.__var.name: if len(self.__var.domain @@ -509,7 +547,7 @@ class IntVar(LinearExpr): if bound < 0 or bound > 1: raise TypeError( 'Cannot call Not on a non boolean variable: %s' % self) - if not self.__negation: + if self.__negation is None: self.__negation = _NotBooleanVariable(self) return self.__negation @@ -529,6 +567,10 @@ class _NotBooleanVariable(LinearExpr): def __str__(self): return 'not(%s)' % str(self.__boolvar) + def __bool__(self): + raise NotImplementedError( + 'Evaluating a literal as a Boolean value is not implemented.') + class BoundedLinearExpression(object): """Represents a linear constraint: `lb <= linear expression <= ub`. @@ -569,6 +611,31 @@ class BoundedLinearExpression(object): def Bounds(self): return self.__bounds + def __bool__(self): + # Check for x == y + if self.__bounds == [0, 0]: + coeffs_map, constant = self.__expr.GetVarValueMap() + if constant != 0: + return False + for coeff in coeffs_map.values(): + if coeff != 0: + return False + return True + elif self.__bounds == [INT_MIN, -1, 1, INT_MAX]: + # Check for x != y + coeffs_map, constant = self.__expr.GetVarValueMap() + if constant != 0: + return True + for coeff in coeffs_map.values(): + if coeff != 0: + return True + + return False + + raise NotImplementedError( + 'Evaluating a BoundedLinearExpr as a Boolean value is not supported.' + ) + class Constraint(object): """Base class for constraints. @@ -702,6 +769,36 @@ class IntervalVar(object): return self.__ct.name +def IsTrueLiteral(literal): + """Checks if literal is either True, or a Boolean literals fixed to True.""" + if isinstance(literal, IntVar): + proto = literal.Proto() + return (len(proto.domain) == 2 and proto.domain[0] == 1 and + proto.domain[1] == 1) + if isinstance(literal, _NotBooleanVariable): + proto = literal.Not().Proto() + return (len(proto.domain) == 2 and proto.domain[0] == 0 and + proto.domain[1] == 0) + if isinstance(literal, numbers.Integral): + return literal == 1 + return False + + +def IsFalseLiteral(literal): + """Checks if literal is either False, or a Boolean literals fixed to False.""" + if isinstance(literal, IntVar): + proto = literal.Proto() + return (len(proto.domain) == 2 and proto.domain[0] == 0 and + proto.domain[1] == 0) + if isinstance(literal, _NotBooleanVariable): + proto = literal.Not().Proto() + return (len(proto.domain) == 2 and proto.domain[0] == 1 and + proto.domain[1] == 1) + if isinstance(literal, numbers.Integral): + return literal == 0 + return False + + class CpModel(object): """Methods for building a CP model. diff --git a/ortools/sat/samples/ranking_sample_sat.py b/ortools/sat/samples/ranking_sample_sat.py index 344e7127fb..5e57ec439f 100644 --- a/ortools/sat/samples/ranking_sample_sat.py +++ b/ortools/sat/samples/ranking_sample_sat.py @@ -47,14 +47,14 @@ def RankTasks(model, starts, presences, ranks): for i in range(num_tasks - 1): for j in range(i + 1, num_tasks): tmp_array = [precedences[(i, j)], precedences[(j, i)]] - if presences[i] != 1: + if not cp_model.IsTrueLiteral(presences[i]): tmp_array.append(presences[i].Not()) # Makes sure that if i is not performed, all precedences are false. model.AddImplication(presences[i].Not(), precedences[(i, j)].Not()) model.AddImplication(presences[i].Not(), precedences[(j, i)].Not()) - if presences[j] != 1: + if not cp_model.IsTrueLiteral(presences[j]): tmp_array.append(presences[j].Not()) # Makes sure that if j is not performed, all precedences are false. model.AddImplication(presences[j].Not(), @@ -123,10 +123,7 @@ def RankingSampleSat(): # Creates makespan variable. makespan = model.NewIntVar(0, horizon, 'makespan') for t in all_tasks: - if presences[t] == 1: - model.Add(ends[t] <= makespan) - else: - model.Add(ends[t] <= makespan).OnlyEnforceIf(presences[t]) + model.Add(ends[t] <= makespan).OnlyEnforceIf(presences[t]) # Minimizes makespan - fixed gain per tasks performed. # As the fixed cost is less that the duration of the last interval,