diff --git a/examples/cpp/jobshop_sat.cc b/examples/cpp/jobshop_sat.cc index 64e1716a39..bf866ddee6 100644 --- a/examples/cpp/jobshop_sat.cc +++ b/examples/cpp/jobshop_sat.cc @@ -106,7 +106,7 @@ int64_t ComputeHorizon(const JsspInputProblem& problem) { struct JobTaskData { IntervalVar interval; IntVar start; - IntVar duration; + LinearExpr duration; IntVar end; }; @@ -488,7 +488,8 @@ void CreateObjective( for (int a = 0; a < num_alternatives; ++a) { // Add cost if present. if (task.cost_size() > 0) { - objective_vars.push_back(job_task_to_alternatives[j][t][a].presence); + objective_vars.push_back( + IntVar(job_task_to_alternatives[j][t][a].presence)); objective_coeffs.push_back(task.cost(a)); } } @@ -626,14 +627,13 @@ void AddMakespanRedundantConstraints( const int num_machines = problem.machines_size(); // Global energetic reasoning. - std::vector all_task_durations; + LinearExpr sum_of_duration; for (const std::vector& tasks : job_to_tasks) { for (const JobTaskData& task : tasks) { - all_task_durations.push_back(task.duration); + sum_of_duration += task.duration; } } - cp_model.AddLessOrEqual(LinearExpr::Sum(all_task_durations), - makespan * num_machines); + cp_model.AddLessOrEqual(sum_of_duration, makespan * num_machines); } void DisplayJobStatistics( @@ -648,8 +648,7 @@ void DisplayJobStatistics( for (const std::vector& job : job_to_tasks) { num_tasks += job.size(); for (const JobTaskData& task : job) { - if (task.duration.Proto().domain_size() != 2 || - task.duration.Proto().domain(0) != task.duration.Proto().domain(1)) { + if (!task.duration.IsConstant()) { num_tasks_with_variable_duration++; } } diff --git a/examples/cpp/network_routing_sat.cc b/examples/cpp/network_routing_sat.cc index 5180b26f95..7eab24a6cb 100644 --- a/examples/cpp/network_routing_sat.cc +++ b/examples/cpp/network_routing_sat.cc @@ -578,7 +578,7 @@ class NetworkRoutingSolver { // Node - Graph Constraint. for (int demand_index = 0; demand_index < num_demands; ++demand_index) { for (int arc = 0; arc < num_arcs; ++arc) { - path_vars[demand_index].push_back(cp_model.NewBoolVar()); + path_vars[demand_index].push_back(IntVar(cp_model.NewBoolVar())); } // Fill Tuple Set for AllowedAssignment constraint. TableConstraint path_ct = diff --git a/examples/cpp/shift_minimization_sat.cc b/examples/cpp/shift_minimization_sat.cc index 955c030dcd..24692b7b2d 100644 --- a/examples/cpp/shift_minimization_sat.cc +++ b/examples/cpp/shift_minimization_sat.cc @@ -262,7 +262,7 @@ void LoadAndSolve(const std::string& file_name) { } // Check that we have not already visited this exact set of candidate jobs. - if (gtl::ContainsKey(visited_job_lists, intersecting_jobs)) continue; + if (visited_job_lists.contains(intersecting_jobs)) continue; visited_job_lists.insert(intersecting_jobs); // Collect the relevant worker job vars. diff --git a/examples/cpp/sports_scheduling_sat.cc b/examples/cpp/sports_scheduling_sat.cc index 444af85140..c897fb16c9 100644 --- a/examples/cpp/sports_scheduling_sat.cc +++ b/examples/cpp/sports_scheduling_sat.cc @@ -107,14 +107,14 @@ void OpponentModel(int num_teams) { std::vector day_home_aways; for (int t = 0; t < num_teams; ++t) { day_opponents.push_back(opponents[t][d]); - day_home_aways.push_back(home_aways[t][d]); + day_home_aways.push_back(IntVar(home_aways[t][d])); } builder.AddInverseConstraint(day_opponents, day_opponents); for (int first_team = 0; first_team < num_teams; ++first_team) { - IntVar first_home = day_home_aways[first_team]; - IntVar second_home = builder.NewBoolVar(); + const IntVar first_home = IntVar(day_home_aways[first_team]); + const IntVar second_home = IntVar(builder.NewBoolVar()); builder.AddVariableElement(day_opponents[first_team], day_home_aways, second_home); builder.AddEquality(first_home + second_home, 1); diff --git a/examples/tests/cp_model_test.py b/examples/tests/cp_model_test.py index 70a86198a5..e82be11c92 100755 --- a/examples/tests/cp_model_test.py +++ b/examples/tests/cp_model_test.py @@ -466,37 +466,37 @@ class CpModelTest(unittest.TestCase): def testAssertIsInt64(self): print('testAssertIsInt64') - cp_model_helper.AssertIsInt64(123) - cp_model_helper.AssertIsInt64(2**63 - 1) - cp_model_helper.AssertIsInt64(-2**63) + cp_model_helper.assert_is_int64(123) + cp_model_helper.assert_is_int64(2**63 - 1) + cp_model_helper.assert_is_int64(-2**63) def testCapInt64(self): print('testCapInt64') self.assertEqual( - cp_model_helper.CapInt64(cp_model.INT_MAX), cp_model.INT_MAX) + cp_model_helper.to_capped_int64(cp_model.INT_MAX), cp_model.INT_MAX) self.assertEqual( - cp_model_helper.CapInt64(cp_model.INT_MAX + 1), cp_model.INT_MAX) + cp_model_helper.to_capped_int64(cp_model.INT_MAX + 1), cp_model.INT_MAX) self.assertEqual( - cp_model_helper.CapInt64(cp_model.INT_MIN), cp_model.INT_MIN) + cp_model_helper.to_capped_int64(cp_model.INT_MIN), cp_model.INT_MIN) self.assertEqual( - cp_model_helper.CapInt64(cp_model.INT_MIN - 1), cp_model.INT_MIN) - self.assertEqual(cp_model_helper.CapInt64(15), 15) + cp_model_helper.to_capped_int64(cp_model.INT_MIN - 1), cp_model.INT_MIN) + self.assertEqual(cp_model_helper.to_capped_int64(15), 15) def testCapSub(self): print('testCapSub') - self.assertEqual(cp_model_helper.CapSub(10, 5), 5) + self.assertEqual(cp_model_helper.capped_subtraction(10, 5), 5) self.assertEqual( - cp_model_helper.CapSub(cp_model.INT_MIN, 5), cp_model.INT_MIN) + cp_model_helper.capped_subtraction(cp_model.INT_MIN, 5), cp_model.INT_MIN) self.assertEqual( - cp_model_helper.CapSub(cp_model.INT_MIN, -5), cp_model.INT_MIN) + cp_model_helper.capped_subtraction(cp_model.INT_MIN, -5), cp_model.INT_MIN) self.assertEqual( - cp_model_helper.CapSub(cp_model.INT_MAX, 5), cp_model.INT_MAX) + cp_model_helper.capped_subtraction(cp_model.INT_MAX, 5), cp_model.INT_MAX) self.assertEqual( - cp_model_helper.CapSub(cp_model.INT_MAX, -5), cp_model.INT_MAX) + cp_model_helper.capped_subtraction(cp_model.INT_MAX, -5), cp_model.INT_MAX) self.assertEqual( - cp_model_helper.CapSub(2, cp_model.INT_MIN), cp_model.INT_MAX) + cp_model_helper.capped_subtraction(2, cp_model.INT_MIN), cp_model.INT_MAX) self.assertEqual( - cp_model_helper.CapSub(2, cp_model.INT_MAX), cp_model.INT_MIN) + cp_model_helper.capped_subtraction(2, cp_model.INT_MAX), cp_model.INT_MIN) def testGetOrMakeIndexFromConstant(self): print('testGetOrMakeIndexFromConstant') @@ -539,8 +539,8 @@ class CpModelTest(unittest.TestCase): y = model.NewIntVar(0, 3, 'y') self.assertEqual(repr(x), 'x(0..4)') self.assertEqual(repr(x * 2), 'ProductCst(x(0..4), 2)') - self.assertEqual(repr(x + y), 'SumArray(x(0..4), y(0..3), 0)') - self.assertEqual(repr(x + 5), 'SumArray(x(0..4), 5)') + self.assertEqual(repr(x + y), 'Sum(x(0..4), y(0..3))') + self.assertEqual(repr(x + 5), 'Sum(x(0..4), 5)') def testDisplayBounds(self): print('testDisplayBounds') diff --git a/ortools/java/com/google/ortools/sat/CpModel.java b/ortools/java/com/google/ortools/sat/CpModel.java index 2178694565..a314ad8d4f 100644 --- a/ortools/java/com/google/ortools/sat/CpModel.java +++ b/ortools/java/com/google/ortools/sat/CpModel.java @@ -1033,6 +1033,8 @@ public final class CpModel { * rectangle is aligned with the X and Y axis, and is defined by two intervals which represent its * projection onto the X and Y axis. * + *

Furthermore, one box is optional if at least one of the x or y interval is optional. + * * @param xIntervals the X coordinates of the rectangles * @param yIntervals the Y coordinates of the rectangles * @return an instance of the Constraint class diff --git a/ortools/sat/cp_model.cc b/ortools/sat/cp_model.cc index de2ca5c56a..a57c61a529 100644 --- a/ortools/sat/cp_model.cc +++ b/ortools/sat/cp_model.cc @@ -33,6 +33,8 @@ BoolVar::BoolVar(int index, CpModelBuilder* builder) : builder_(builder), index_(index) {} BoolVar BoolVar::WithName(const std::string& name) { + DCHECK(builder_ != nullptr); + if (builder_ == nullptr) return *this; builder_->MutableProto() ->mutable_variables(PositiveRef(index_)) ->set_name(name); @@ -40,6 +42,7 @@ BoolVar BoolVar::WithName(const std::string& name) { } std::string BoolVar::Name() const { + if (builder_ == nullptr) return "null"; const std::string& name = builder_->Proto().variables(PositiveRef(index_)).name(); if (RefIsPositive(index_)) { @@ -50,6 +53,7 @@ std::string BoolVar::Name() const { } std::string BoolVar::DebugString() const { + if (builder_ == nullptr) return "null"; if (index_ < 0) { return absl::StrFormat("Not(%s)", Not().DebugString()); } else { @@ -91,28 +95,48 @@ IntVar::IntVar(int index, CpModelBuilder* builder) } IntVar::IntVar(const BoolVar& var) { + if (var.builder_ == nullptr) { + *this = IntVar(); + return; + } builder_ = var.builder_; index_ = builder_->GetOrCreateIntegerIndex(var.index_); DCHECK(RefIsPositive(index_)); } BoolVar IntVar::ToBoolVar() const { - CHECK_EQ(2, Proto().domain_size()); - CHECK_GE(Proto().domain(0), 0); - CHECK_LE(Proto().domain(1), 1); + if (builder_ != nullptr) { + const IntegerVariableProto& proto = builder_->Proto().variables(index_); + DCHECK_EQ(2, proto.domain_size()); + DCHECK_GE(proto.domain(0), 0); + DCHECK_LE(proto.domain(1), 1); + } return BoolVar(index_, builder_); } IntVar IntVar::WithName(const std::string& name) { + DCHECK(builder_ != nullptr); + if (builder_ == nullptr) return *this; builder_->MutableProto()->mutable_variables(index_)->set_name(name); return *this; } +std::string IntVar::Name() const { + if (builder_ == nullptr) return "null"; + return builder_->Proto().variables(index_).name(); +} + LinearExpr IntVar::AddConstant(int64_t value) const { return LinearExpr(*this).AddConstant(value); } +Domain IntVar::Domain() const { + if (builder_ == nullptr) return Domain(); + return ReadDomainFromProto(builder_->Proto().variables(index_)); +} + std::string IntVar::DebugString() const { + if (builder_ == nullptr) return "null"; return VarDebugString(builder_->Proto(), index_); } @@ -146,14 +170,6 @@ std::string VarDebugString(const CpModelProto& proto, int index) { return output; } -const IntegerVariableProto& IntVar::Proto() const { - return builder_->Proto().variables(index_); -} - -IntegerVariableProto* IntVar::MutableProto() const { - return builder_->MutableProto()->mutable_variables(index_); -} - std::ostream& operator<<(std::ostream& os, const IntVar& var) { os << var.DebugString(); return os; @@ -269,12 +285,14 @@ LinearExpr& LinearExpr::AddVar(IntVar var) { return AddTerm(var, 1); } LinearExpr& LinearExpr::AddVar(BoolVar var) { return AddTerm(var, 1); } LinearExpr& LinearExpr::AddTerm(IntVar var, int64_t coeff) { + DCHECK(var.builder_ != nullptr); variables_.push_back(var.index_); coefficients_.push_back(coeff); return *this; } LinearExpr& LinearExpr::AddTerm(BoolVar var, int64_t coeff) { + DCHECK(var.builder_ != nullptr); const int index = var.index_; if (RefIsPositive(index)) { variables_.push_back(index); @@ -628,32 +646,48 @@ IntervalVar::IntervalVar(int index, CpModelBuilder* builder) : builder_(builder), index_(index) {} IntervalVar IntervalVar::WithName(const std::string& name) { + DCHECK(builder_ != nullptr); + if (builder_ == nullptr) return *this; builder_->MutableProto()->mutable_constraints(index_)->set_name(name); return *this; } LinearExpr IntervalVar::StartExpr() const { - return LinearExpr::FromProto(Proto().start()); + DCHECK(builder_ != nullptr); + if (builder_ == nullptr) return LinearExpr(); + return LinearExpr::FromProto( + builder_->Proto().constraints(index_).interval().start()); } LinearExpr IntervalVar::SizeExpr() const { - return LinearExpr::FromProto(Proto().size()); + DCHECK(builder_ != nullptr); + if (builder_ == nullptr) return LinearExpr(); + return LinearExpr::FromProto( + builder_->Proto().constraints(index_).interval().size()); } LinearExpr IntervalVar::EndExpr() const { - return LinearExpr::FromProto(Proto().end()); + DCHECK(builder_ != nullptr); + if (builder_ == nullptr) return LinearExpr(); + return LinearExpr::FromProto( + builder_->Proto().constraints(index_).interval().end()); } BoolVar IntervalVar::PresenceBoolVar() const { + DCHECK(builder_ != nullptr); + if (builder_ == nullptr) return BoolVar(); return BoolVar(builder_->Proto().constraints(index_).enforcement_literal(0), builder_); } -const std::string& IntervalVar::Name() const { +std::string IntervalVar::Name() const { + if (builder_ == nullptr) return "null"; return builder_->Proto().constraints(index_).name(); } std::string IntervalVar::DebugString() const { + if (builder_ == nullptr) return "null"; + CHECK_GE(index_, 0); const CpModelProto& proto = builder_->Proto(); const ConstraintProto& ct_proto = proto.constraints(index_); @@ -670,16 +704,6 @@ std::string IntervalVar::DebugString() const { return output; } -const IntervalConstraintProto& IntervalVar::Proto() const { - return builder_->Proto().constraints(index_).interval(); -} - -IntervalConstraintProto* IntervalVar::MutableProto() const { - return builder_->MutableProto() - ->mutable_constraints(index_) - ->mutable_interval(); -} - std::ostream& operator<<(std::ostream& os, const IntervalVar& var) { os << var.DebugString(); return os; @@ -1261,6 +1285,16 @@ void CpModelBuilder::AddHint(IntVar var, int64_t value) { cp_model_.mutable_solution_hint()->add_values(value); } +void CpModelBuilder::AddHint(BoolVar var, bool value) { + if (var.index_ >= 0) { + cp_model_.mutable_solution_hint()->add_vars(var.index_); + cp_model_.mutable_solution_hint()->add_values(value); + } else { + cp_model_.mutable_solution_hint()->add_vars(PositiveRef(var.index_)); + cp_model_.mutable_solution_hint()->add_values(!value); + } +} + void CpModelBuilder::ClearHints() { cp_model_.mutable_solution_hint()->Clear(); } diff --git a/ortools/sat/cp_model.h b/ortools/sat/cp_model.h index 0ab6a15b3e..9d8f5825bc 100644 --- a/ortools/sat/cp_model.h +++ b/ortools/sat/cp_model.h @@ -62,13 +62,17 @@ class IntVar; /** * A Boolean variable. * - * This class wraps an IntegerVariableProto with domain [0, 1]. - * It supports the logical negation (Not). + * This class refer to an IntegerVariableProto with domain [0, 1] or to its + * logical negation (Not). This is called a Boolean Literal in other context. * * This can only be constructed via \c CpModelBuilder.NewBoolVar(). */ class BoolVar { public: + /// A default constructed BoolVar can be used to mean not defined yet. + /// However, it shouldn't be passed to any of the functions in this file. + /// Doing so will crash in debug mode and will result in an invalid model in + /// opt mode. BoolVar(); /// Sets the name of the variable. @@ -81,17 +85,14 @@ class BoolVar { /// Returns the logical negation of the current Boolean variable. BoolVar Not() const { return BoolVar(NegatedRef(index_), builder_); } - /// Equality test with another boolvar. bool operator==(const BoolVar& other) const { return other.builder_ == builder_ && other.index_ == index_; } - /// Dis-Equality test. bool operator!=(const BoolVar& other) const { return other.builder_ != builder_ || other.index_ != index_; } - /// Debug string. std::string DebugString() const; /** @@ -133,59 +134,55 @@ BoolVar Not(BoolVar x); * * This class wraps an IntegerVariableProto. * This can only be constructed via \c CpModelBuilder.NewIntVar(). - * - * Note that a BoolVar can be used in any place that accept an - * IntVar via an implicit cast. It will simply take the value - * 0 (when false) or 1 (when true). */ class IntVar { public: + /// A default constructed IntVar can be used to mean not defined yet. + /// However, it shouldn't be passed to any of the functions in this file. + /// Doing so will crash in debug mode and will result in an invalid model in + /// opt mode. IntVar(); - /// Implicit cast BoolVar -> IntVar. + /// Cast BoolVar -> IntVar. + /// The IntVar will take the value 1 (when the bool is true) and 0 otherwise. /// /// Warning: If you construct an IntVar from a negated BoolVar, this might - /// create a new variable in the model. + /// create a new variable in the model. Otherwise this just point to the same + /// underlying variable. IntVar(const BoolVar& var); // NOLINT(runtime/explicit) /// Cast IntVar -> BoolVar. /// - /// Warning: This checks that the domain of the var is within {0,1} and - /// crash if it is not the case. Use at your own risk. + /// Warning: The domain of the var must be within {0,1}. If not, we crash + /// in debug mode, and in opt mode you will get an invalid model if you use + /// this BoolVar anywhere since it will not have a valid domain. BoolVar ToBoolVar() const; /// Sets the name of the variable. IntVar WithName(const std::string& name); /// Returns the name of the variable (or the empty string if not set). - const std::string& Name() const { return Proto().name(); } + std::string Name() const; - /// Adds a constant value to an integer variable and returns a linear - /// expression. - LinearExpr AddConstant(int64_t value) const; - - /// Equality test with another IntVar. bool operator==(const IntVar& other) const { return other.builder_ == builder_ && other.index_ == index_; } - /// Difference test with another IntVar. bool operator!=(const IntVar& other) const { return other.builder_ != builder_ || other.index_ != index_; } - /// Returns a debug string. + // Returns the domain of the variable. + Domain Domain() const; + std::string DebugString() const; - /// Returns the underlying protobuf object (useful for testing). - const IntegerVariableProto& Proto() const; - - /// Returns the mutable underlying protobuf object (useful for model edition). - IntegerVariableProto* MutableProto() const; - /// Returns the index of the variable in the model. This will be non-negative. int index() const { return index_; } + /// Deprecated. Just do var + cte where needed. + LinearExpr AddConstant(int64_t value) const; + private: friend class BoolVar; friend class CpModelBuilder; @@ -213,8 +210,8 @@ std::ostream& operator<<(std::ostream& os, const IntVar& var); * x when added to the linear expression. It also support operator overloads to * construct the linear expression naturally. * - * Furthermore, static methods allows sums and scalar products, with or without - * an additional constant. + * Furthermore, static methods allow to construct a linear expression from sums + * or scalar products. * * Usage: * \code @@ -240,13 +237,11 @@ std::ostream& operator<<(std::ostream& os, const IntVar& var); */ class LinearExpr { public: + /// Creates an empty linear expression with value zero. LinearExpr() = default; - /** - * Constructs a linear expression from a Boolean variable. - * - * It deals with logical negation correctly. - */ + /// Constructs a linear expression from a Boolean variable. + /// It deals with logical negation correctly. LinearExpr(BoolVar var); // NOLINT(runtime/explicit) /// Constructs a linear expression from an integer variable. @@ -261,7 +256,7 @@ class LinearExpr { /// Constructs the sum of a list of Boolean variables. static LinearExpr Sum(absl::Span vars); - /// Constructs the sum of a list of Boolean variables. + /// Constructs the sum of a list of variables. // TODO(user): Remove when the operators + and * are implemented. static LinearExpr Sum(std::initializer_list vars); @@ -313,6 +308,9 @@ class LinearExpr { /// Returns the vector of coefficients. const std::vector& coefficients() const { return coefficients_; } + /// Returns true if the expression has no variables. + const bool IsConstant() const { return variables_.empty(); } + /// Returns the constant term. int64_t constant() const { return constant_; } @@ -371,11 +369,8 @@ class DoubleLinearExpr { public: DoubleLinearExpr(); - /** - * Constructs a linear expression from a Boolean variable. - * - * It deals with logical negation correctly. - */ + /// Constructs a linear expression from a Boolean variable. + /// It deals with logical negation correctly. explicit DoubleLinearExpr(BoolVar var); /// Constructs a linear expression from an integer variable. @@ -440,6 +435,9 @@ class DoubleLinearExpr { /// Returns the vector of coefficients. const std::vector& coefficients() const { return coefficients_; } + // Returns true if the expression has no variable. + const bool IsConstant() const { return variables_.empty(); } + /// Returns the constant term. double constant() const { return constant_; } @@ -476,14 +474,17 @@ std::ostream& operator<<(std::ostream& os, const DoubleLinearExpr& e); */ class IntervalVar { public: - /// Default ctor. + /// A default constructed IntervalVar can be used to mean not defined yet. + /// However, it shouldn't be passed to any of the functions in this file. + /// Doing so will crash in debug mode and will result in an invalid model in + /// opt mode. IntervalVar(); /// Sets the name of the variable. IntervalVar WithName(const std::string& name); /// Returns the name of the interval (or the empty string if not set). - const std::string& Name() const; + std::string Name() const; /// Returns the start linear expression. Note that this rebuilds the /// expression each time this method is called. @@ -517,12 +518,6 @@ class IntervalVar { /// Returns a debug string. std::string DebugString() const; - /// Returns the underlying protobuf object (useful for testing). - const IntervalConstraintProto& Proto() const; - - /// Returns the mutable underlying protobuf object (useful for model edition). - IntervalConstraintProto* MutableProto() const; - /// Returns the index of the interval constraint in the model. int index() const { return index_; } @@ -1072,6 +1067,9 @@ class CpModelBuilder { /// Adds hinting to a variable. void AddHint(IntVar var, int64_t value); + /// Adds hinting to a Boolean variable. + void AddHint(BoolVar var, bool value); + /// Removes all hints. void ClearHints(); @@ -1209,7 +1207,10 @@ inline LinearExpr operator*(int64_t factor, LinearExpr expr) { // For DoubleLinearExpr. -inline DoubleLinearExpr operator-(DoubleLinearExpr expr) { return expr *= -1; } +inline DoubleLinearExpr operator-(DoubleLinearExpr expr) { + expr *= -1; + return expr; +} inline DoubleLinearExpr operator+(const DoubleLinearExpr& lhs, const DoubleLinearExpr& rhs) { @@ -1238,23 +1239,13 @@ inline DoubleLinearExpr operator+(DoubleLinearExpr&& lhs, } } -inline DoubleLinearExpr operator+(const DoubleLinearExpr& lhs, double rhs) { - DoubleLinearExpr temp(lhs); - temp += rhs; - return temp; +inline DoubleLinearExpr operator+(DoubleLinearExpr expr, double rhs) { + expr += rhs; + return expr; } -inline DoubleLinearExpr operator+(DoubleLinearExpr&& lhs, double rhs) { - lhs += rhs; - return std::move(lhs); -} -inline DoubleLinearExpr operator+(double lhs, DoubleLinearExpr&& rhs) { - rhs += lhs; - return std::move(rhs); -} -inline DoubleLinearExpr operator+(double lhs, const DoubleLinearExpr& rhs) { - DoubleLinearExpr temp(rhs); - temp += lhs; - return temp; +inline DoubleLinearExpr operator+(double lhs, DoubleLinearExpr expr) { + expr += lhs; + return expr; } inline DoubleLinearExpr operator-(const DoubleLinearExpr& lhs, @@ -1284,24 +1275,14 @@ inline DoubleLinearExpr operator-(DoubleLinearExpr&& lhs, } } -inline DoubleLinearExpr operator-(const DoubleLinearExpr& lhs, double rhs) { - DoubleLinearExpr temp(lhs); - temp -= rhs; - return temp; +inline DoubleLinearExpr operator-(DoubleLinearExpr epxr, double rhs) { + epxr -= rhs; + return epxr; } -inline DoubleLinearExpr operator-(DoubleLinearExpr&& lhs, double rhs) { - lhs -= rhs; - return std::move(lhs); -} -inline DoubleLinearExpr operator-(double lhs, DoubleLinearExpr&& rhs) { - rhs *= -1; - rhs += lhs; - return std::move(rhs); -} -inline DoubleLinearExpr operator-(double lhs, const DoubleLinearExpr& rhs) { - DoubleLinearExpr temp = -rhs; - temp += lhs; - return temp; +inline DoubleLinearExpr operator-(double lhs, DoubleLinearExpr expr) { + expr *= -1; + expr += lhs; + return expr; } inline DoubleLinearExpr operator*(DoubleLinearExpr expr, double factor) { diff --git a/ortools/sat/linear_relaxation.cc b/ortools/sat/linear_relaxation.cc index 2da0b271ba..4f64173c63 100644 --- a/ortools/sat/linear_relaxation.cc +++ b/ortools/sat/linear_relaxation.cc @@ -798,6 +798,7 @@ void AppendNoOverlap2dRelaxation(const ConstraintProto& ct, Model* model, integer_trail->LevelZeroLowerBound(x_sizes[i]) * integer_trail->LevelZeroLowerBound(y_sizes[i]); if (area_min != 0) { + // Not including the term if we don't have a view is ok. (void)lc.AddLiteralTerm(presence_literal, area_min); } } @@ -1288,17 +1289,15 @@ void AddNoOverlap2dCutGenerator(const ConstraintProto& ct, Model* m, m->GetOrCreate(); bool has_variable_part = false; for (int i = 0; i < x_intervals.size(); ++i) { - // Ignore absent intervals. + // Ignore absent rectangles. if (intervals_repository->IsAbsent(x_intervals[i]) || intervals_repository->IsAbsent(y_intervals[i])) { continue; } // Checks non-present intervals. - if ((intervals_repository->IsOptional(x_intervals[i]) && - !intervals_repository->IsPresent(x_intervals[i])) || - (intervals_repository->IsOptional(y_intervals[i]) && - !intervals_repository->IsPresent(y_intervals[i]))) { + if (!intervals_repository->IsPresent(x_intervals[i]) || + !intervals_repository->IsPresent(y_intervals[i])) { has_variable_part = true; break; } diff --git a/ortools/sat/python/cp_model.py b/ortools/sat/python/cp_model.py index 62680d15ca..42075d3b10 100644 --- a/ortools/sat/python/cp_model.py +++ b/ortools/sat/python/cp_model.py @@ -196,7 +196,7 @@ class LinearExpr(object): @classmethod def Term(cls, expression, coefficient): """Creates `expression * coefficient`.""" - if coefficient == 0: + if cmh.is_zero(coefficient): return 0 else: return expression * coefficient @@ -204,7 +204,7 @@ class LinearExpr(object): @classmethod def IsEmptyOrAllNull(cls, coefficients): for c in coefficients: - if c != 0: + if not cmh.is_zero(c): return False return True @@ -224,41 +224,92 @@ class LinearExpr(object): for index, coeff in zip(proto.vars(), proto.coeffs()): variables.append(IntVar(model, index, None)) coeffs.append(coeff) - if coeff != 1: + if not cmh.is_one(coeff): all_ones = False if all_ones: return _SumArray(variables, offset) else: return _ScalProd(variables, coeffs, offset) - def GetVarValueMap(self): - """Scans the expression, and return a list of (var_coef_map, constant).""" + def GetIntegerVarValueMap(self): + """Scans the expression, and returns (var_coef_map, constant).""" coeffs = collections.defaultdict(int) constant = 0 to_process = [(self, 1)] while to_process: # Flatten to avoid recursion. - expr, coef = to_process.pop() - if isinstance(expr, _ProductCst): + expr, coeff = to_process.pop() + if cmh.is_integral(expr): + constant += coeff * int(expr) + elif isinstance(expr, _ProductCst): to_process.append( - (expr.Expression(), coef * expr.Coefficient())) + (expr.Expression(), coeff * expr.Coefficient())) + elif isinstance(expr, _Sum): + to_process.append((expr.Left(), coeff)) + to_process.append((expr.Right(), coeff)) elif isinstance(expr, _SumArray): for e in expr.Expressions(): - to_process.append((e, coef)) - constant += expr.Constant() * coef + to_process.append((e, coeff)) + constant += expr.Constant() * coeff elif isinstance(expr, _ScalProd): for e, c in zip(expr.Expressions(), expr.Coefficients()): - to_process.append((e, coef * c)) - constant += expr.Constant() * coef + to_process.append((e, coeff * c)) + constant += expr.Constant() * coeff elif isinstance(expr, IntVar): - coeffs[expr] += coef + coeffs[expr] += coeff elif isinstance(expr, _NotBooleanVariable): - constant += coef - coeffs[expr.Not()] -= coef + constant += coeff + coeffs[expr.Not()] -= coeff else: raise TypeError('Unrecognized linear expression: ' + str(expr)) return coeffs, constant + def GetFloatVarValueMap(self): + """Scans the expression. Returns (var_coef_map, constant, is_integer).""" + coeffs = {} + constant = 0 + to_process = [(self, 1)] + while to_process: # Flatten to avoid recursion. + expr, coeff = to_process.pop() + if cmh.is_integral(expr): # Keep integrality. + constant += coeff * int(expr) + elif cmh.is_a_number(expr): + constant += coeff * float(expr) + elif isinstance(expr, _ProductCst): + to_process.append( + (expr.Expression(), coeff * expr.Coefficient())) + elif isinstance(expr, _Sum): + to_process.append((expr.Left(), coeff)) + to_process.append((expr.Right(), coeff)) + elif isinstance(expr, _SumArray): + for e in expr.Expressions(): + to_process.append((e, coeff)) + constant += expr.Constant() * coeff + elif isinstance(expr, _ScalProd): + for e, c in zip(expr.Expressions(), expr.Coefficients()): + to_process.append((e, coeff * c)) + constant += expr.Constant() * coeff + elif isinstance(expr, IntVar): + if expr in coeffs: + coeffs[expr] += coeff + else: + coeffs[expr] = coeff + elif isinstance(expr, _NotBooleanVariable): + constant += coeff + if expr.Not() in coeffs: + coeffs[expr.Not()] -= coeff + else: + coeffs[expr.Not()] = -coeff + else: + raise TypeError('Unrecognized linear expression: ' + str(expr)) + is_integer = cmh.is_integral(constant) + if is_integer: + for coeff in coeffs.values(): + if not cmh.is_integral(coeff): + is_integer = False + break + return coeffs, constant, is_integer + def __hash__(self): return object.__hash__(self) @@ -268,36 +319,36 @@ class LinearExpr(object): 'please use CpModel.AddAbsEquality') def __add__(self, arg): - if cmh.IsIntegral(arg) and int(arg) == 0: + if cmh.is_zero(arg): return self - return _SumArray([self, arg]) + return _Sum(self, arg) def __radd__(self, arg): - if cmh.IsIntegral(arg) and int(arg) == 0: + if cmh.is_zero(arg): return self - return _SumArray([self, arg]) + return _Sum(self, arg) def __sub__(self, arg): - if cmh.IsIntegral(arg) and int(arg) == 0: + if cmh.is_zero(arg): return self - return _SumArray([self, -arg]) + return _Sum(self, -arg) def __rsub__(self, arg): - return _SumArray([-self, arg]) + return _Sum(-self, arg) def __mul__(self, arg): - arg = cmh.AssertIsInt64(arg) - if arg == 1: + arg = cmh.assert_is_a_number(arg) + if cmh.is_one(arg): return self - elif arg == 0: + elif cmh.is_zero(arg): return 0 return _ProductCst(self, arg) def __rmul__(self, arg): - arg = cmh.AssertIsInt64(arg) - if arg == 1: + arg = cmh.assert_is_a_number(arg) + if cmh.is_one(arg): return self - elif arg == 0: + elif cmh.is_zero(arg): return 0 return _ProductCst(self, arg) @@ -354,29 +405,29 @@ class LinearExpr(object): def __eq__(self, arg): if arg is None: return False - if cmh.IsIntegral(arg): - arg = cmh.AssertIsInt64(arg) + if cmh.is_integral(arg): + arg = cmh.assert_is_int64(arg) return BoundedLinearExpression(self, [arg, arg]) else: return BoundedLinearExpression(self - arg, [0, 0]) def __ge__(self, arg): - if cmh.IsIntegral(arg): - arg = cmh.AssertIsInt64(arg) + if cmh.is_integral(arg): + arg = cmh.assert_is_int64(arg) return BoundedLinearExpression(self, [arg, INT_MAX]) else: return BoundedLinearExpression(self - arg, [0, INT_MAX]) def __le__(self, arg): - if cmh.IsIntegral(arg): - arg = cmh.AssertIsInt64(arg) + if cmh.is_integral(arg): + arg = cmh.assert_is_int64(arg) return BoundedLinearExpression(self, [INT_MIN, arg]) else: return BoundedLinearExpression(self - arg, [INT_MIN, 0]) def __lt__(self, arg): - if cmh.IsIntegral(arg): - arg = cmh.AssertIsInt64(arg) + if cmh.is_integral(arg): + arg = cmh.assert_is_int64(arg) if arg == INT_MIN: raise ArithmeticError('< INT_MIN is not supported') return BoundedLinearExpression(self, [INT_MIN, arg - 1]) @@ -384,8 +435,8 @@ class LinearExpr(object): return BoundedLinearExpression(self - arg, [INT_MIN, -1]) def __gt__(self, arg): - if cmh.IsIntegral(arg): - arg = cmh.AssertIsInt64(arg) + if cmh.is_integral(arg): + arg = cmh.assert_is_int64(arg) if arg == INT_MAX: raise ArithmeticError('> INT_MAX is not supported') return BoundedLinearExpression(self, [arg + 1, INT_MAX]) @@ -395,8 +446,8 @@ class LinearExpr(object): def __ne__(self, arg): if arg is None: return True - if cmh.IsIntegral(arg): - arg = cmh.AssertIsInt64(arg) + if cmh.is_integral(arg): + arg = cmh.assert_is_int64(arg) if arg == INT_MAX: return BoundedLinearExpression(self, [INT_MIN, INT_MAX - 1]) elif arg == INT_MIN: @@ -409,17 +460,40 @@ class LinearExpr(object): [INT_MIN, -1, 1, INT_MAX]) +class _Sum(LinearExpr): + """Represents the sum of two LinearExprs.""" + + def __init__(self, left, right): + for x in [left, right]: + if not cmh.is_a_number(x) and not isinstance(x, LinearExpr): + raise TypeError('Not an linear expression: ' + str(x)) + self.__left = left + self.__right = right + + def Left(self): + return self.__left + + def Right(self): + return self.__right + + def __str__(self): + return f'({self.__left} + {self.__right})' + + def __repr__(self): + return f'Sum({repr(self.__left)}, {repr(self.__right)})' + + class _ProductCst(LinearExpr): """Represents the product of a LinearExpr by a constant.""" - def __init__(self, expr, coef): - coef = cmh.AssertIsInt64(coef) + def __init__(self, expr, coeff): + coeff = cmh.assert_is_a_number(coeff) if isinstance(expr, _ProductCst): self.__expr = expr.Expression() - self.__coef = expr.Coefficient() * coef + self.__coef = expr.Coefficient() * coeff else: self.__expr = expr - self.__coef = coef + self.__coef = coeff def __str__(self): if self.__coef == -1: @@ -445,8 +519,10 @@ class _SumArray(LinearExpr): self.__expressions = [] self.__constant = constant for x in expressions: - if cmh.IsIntegral(x): - x = cmh.AssertIsInt64(x) + if cmh.is_a_number(x): + if cmh.is_zero(x): + continue + x = cmh.assert_is_a_number(x) self.__constant += x elif isinstance(x, LinearExpr): self.__expressions.append(x) @@ -483,11 +559,11 @@ class _ScalProd(LinearExpr): 'In the LinearExpr.ScalProd method, the expression array and the ' ' coefficient array must have the same length.') for e, c in zip(expressions, coefficients): - c = cmh.AssertIsInt64(c) - if c == 0: + c = cmh.assert_is_a_number(c) + if cmh.is_zero(c): continue - if cmh.IsIntegral(e): - e = cmh.AssertIsInt64(e) + if cmh.is_a_number(e): + e = cmh.assert_is_a_number(e) self.__constant += e * c elif isinstance(e, LinearExpr): self.__expressions.append(e) @@ -498,15 +574,15 @@ class _ScalProd(LinearExpr): def __str__(self): output = None for expr, coeff in zip(self.__expressions, self.__coefficients): - if not output and coeff == 1: + if not output and cmh.is_one(coeff): output = str(expr) - elif not output and coeff == -1: + elif not output and cmh.is_minus_one(coeff): output = '-' + str(expr) elif not output: output = '{} * {}'.format(coeff, str(expr)) - elif coeff == 1: + elif cmh.is_one(coeff): output += ' + {}'.format(str(expr)) - elif coeff == -1: + elif cmh.is_minus_one(coeff): output += ' - {}'.format(str(expr)) elif coeff > 1: output += ' + {} * {}'.format(coeff, str(expr)) @@ -560,7 +636,7 @@ class IntVar(LinearExpr): # model is a CpModelProto, domain is a Domain, and name is a string. # case 2: # model is a CpModelProto, domain is an index (int), and name is None. - if cmh.IsIntegral(domain) and name is None: + if cmh.is_integral(domain) and name is None: self.__index = int(domain) self.__var = model.variables[domain] else: @@ -681,7 +757,7 @@ class BoundedLinearExpression(object): return self.__bounds def __bool__(self): - coeffs_map, constant = self.__expr.GetVarValueMap() + coeffs_map, constant = self.__expr.GetIntegerVarValueMap() all_coeffs = set(coeffs_map.values()) same_var = set([0]) eq_bounds = [0, 0] @@ -701,136 +777,6 @@ class BoundedLinearExpression(object): + ' is not supported.') -class DoubleLinearExpr(object): - """Holds an double linear expression. - - * In CP-SAT, the objective can be a linear expression with double - coefficients: - - ``` - model.Minimize(cp_model.DoubleLinearExpr.Sum(expressions)) - model.Maximize(cp_model.DoubleLinearExpr.ScalProd(expressions, coefficients)) - ``` - """ - - @classmethod - def Sum(cls, expressions): - """Creates the expression sum(expressions).""" - if len(expressions) == 1: - return expressions[0] - coefficients = [1.0] * len(expressions) - return DoubleLinearExpr(expressions, coefficients) - - @classmethod - def ScalProd(cls, expressions, coefficients): - """Creates the expression sum(expressions[i] * coefficients[i]).""" - if LinearExpr.IsEmptyOrAllNull(coefficients): - return 0.0 - else: - return DoubleLinearExpr(expressions, coefficients) - - @classmethod - def Term(cls, expression, coefficient): - """Creates `expression * coefficient`.""" - if coefficient == 0: - return 0 - else: - return DoubleLinearExpr([expression], [coefficient]) - - @classmethod - def IsEmptyOrAllNull(cls, coefficients): - for c in coefficients: - if c != 0.0: - return False - return True - - def Expressions(self): - return self.__expressions - - def Coefficients(self): - return self.__coefficients - - def Constant(self): - return self.__constant - - def GetVarValueMap(self): - """Scans the expression, and return a list of (var_coef_map, constant).""" - coeffs = collections.defaultdict(int) - constant = 0.0 - to_process = [(self, 1.0)] - while to_process: # Flatten to avoid recursion. - expr, coef = to_process.pop() - if isinstance(expr, IntVar): - coeffs[expr] += coef - elif isinstance(expr, _NotBooleanVariable): - constant += coef - coeffs[expr.Not()] -= coef - elif isinstance(expr, DoubleLinearExpr): - for e, c in zip(expr.Expressions(), expr.Coefficients()): - to_process.append((e, coef * c)) - constant += expr.Constant() * coef - else: - raise TypeError('Unrecognized linear expression: ' + str(expr)) - - return coeffs, constant - - def __init__(self, expressions, coefficients, constant=0): - self.__expressions = [] - self.__coefficients = [] - self.__constant = constant - if len(expressions) != len(coefficients): - raise TypeError( - 'In the DoubleLinearExpr builders, the expression array and the ' - ' coefficient array must have the same length.') - for e, c in zip(expressions, coefficients): - c = cmh.AssertIsNumber(c) - if c == 0.0: - continue - if cmh.IsNumber(e): - e = cmh.AssertIsNumber(e) - self.__constant += e * c - elif isinstance(e, DoubleLinearExpr): - self.__constant += c * e.Constant() - for ee, cc in zip(e.Expressions(), e.Coefficients()): - self.__expressions.append(ee) - self.__coefficients.append(cc) - elif isinstance(e, IntVar) or isinstance(e, _NotBooleanVariable): - self.__expressions.append(e) - self.__coefficients.append(c) - else: - raise TypeError('Not a double linear expression: ' + str(e)) - - def __str__(self): - output = None - for expr, coeff in zip(self.__expressions, self.__coefficients): - if not output and coeff == 1.0: - output = str(expr) - elif not output and coeff == -1.0: - output = '-' + str(expr) - elif not output: - output = '{} * {}'.format(coeff, str(expr)) - elif coeff == 1.0: - output += ' + {}'.format(str(expr)) - elif coeff == -1.0: - output += ' - {}'.format(str(expr)) - elif coeff > 1.0: - output += ' + {} * {}'.format(coeff, str(expr)) - elif coeff < -1.0: - output += ' - {} * {}'.format(-coeff, str(expr)) - if self.__constant > 0.0: - output += ' + {}'.format(self.__constant) - elif self.__constant < 0.0: - output += ' - {}'.format(-self.__constant) - if output is None: - output = '0.0' - return output - - def __repr__(self): - return 'DoubleLinearExpr([{}], [{}], {})'.format( - ', '.join(map(repr, self.__expressions)), - ', '.join(map(repr, self.__coefficients)), self.__constant) - - class Constraint(object): """Base class for constraints. @@ -868,12 +814,12 @@ class Constraint(object): self. """ - if cmh.IsIntegral(boolvar) and int(boolvar) == 1: + if cmh.is_integral(boolvar) and int(boolvar) == 1: # Always true. Do nothing. pass elif isinstance(boolvar, list): for b in boolvar: - if cmh.IsIntegral(b) and int(b) == 1: + if cmh.is_integral(b) and int(b) == 1: pass else: self.__constraint.enforcement_literal.append(b.Index()) @@ -983,7 +929,7 @@ def ObjectIsATrueLiteral(literal): proto = literal.Not().Proto() return (len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0) - if cmh.IsIntegral(literal): + if cmh.is_integral(literal): return int(literal) == 1 return False @@ -998,7 +944,7 @@ def ObjectIsAFalseLiteral(literal): proto = literal.Not().Proto() return (len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1) - if cmh.IsIntegral(literal): + if cmh.is_integral(literal): return int(literal) == 0 return False @@ -1072,17 +1018,19 @@ class CpModel(object): if isinstance(linear_expr, LinearExpr): ct = Constraint(self.__model.constraints) model_ct = self.__model.constraints[ct.Index()] - coeffs_map, constant = linear_expr.GetVarValueMap() + coeffs_map, constant = linear_expr.GetIntegerVarValueMap() for t in coeffs_map.items(): if not isinstance(t[0], IntVar): raise TypeError('Wrong argument' + str(t)) - c = cmh.AssertIsInt64(t[1]) + c = cmh.assert_is_int64(t[1]) model_ct.linear.vars.append(t[0].Index()) model_ct.linear.coeffs.append(c) - model_ct.linear.domain.extend( - [cmh.CapSub(x, constant) for x in domain.FlattenedIntervals()]) + model_ct.linear.domain.extend([ + cmh.capped_subtraction(x, constant) + for x in domain.FlattenedIntervals() + ]) return ct - elif cmh.IsIntegral(linear_expr): + elif cmh.is_integral(linear_expr): if not domain.Contains(int(linear_expr)): return self.AddBoolOr([]) # Evaluate to false. # Nothing to do otherwise. @@ -1135,7 +1083,7 @@ class CpModel(object): if not variables: raise ValueError('AddElement expects a non-empty variables array') - if cmh.IsIntegral(index): + if cmh.is_integral(index): return self.Add(list(variables)[int(index)] == target) ct = Constraint(self.__model.constraints) @@ -1173,8 +1121,8 @@ class CpModel(object): ct = Constraint(self.__model.constraints) model_ct = self.__model.constraints[ct.Index()] for arc in arcs: - tail = cmh.AssertIsInt32(arc[0]) - head = cmh.AssertIsInt32(arc[1]) + tail = cmh.assert_is_int32(arc[0]) + head = cmh.assert_is_int32(arc[1]) lit = self.GetOrMakeBooleanIndex(arc[2]) model_ct.circuit.tails.append(tail) model_ct.circuit.heads.append(head) @@ -1217,7 +1165,7 @@ class CpModel(object): raise TypeError('Tuple ' + str(t) + ' has the wrong arity') ar = [] for v in t: - ar.append(cmh.AssertIsInt64(v)) + ar.append(cmh.assert_is_int64(v)) model_ct.table.values.extend(ar) return ct @@ -1308,18 +1256,18 @@ class CpModel(object): model_ct = self.__model.constraints[ct.Index()] model_ct.automaton.vars.extend( [self.GetOrMakeIndex(x) for x in transition_variables]) - starting_state = cmh.AssertIsInt64(starting_state) + starting_state = cmh.assert_is_int64(starting_state) model_ct.automaton.starting_state = starting_state for v in final_states: - v = cmh.AssertIsInt64(v) + v = cmh.assert_is_int64(v) model_ct.automaton.final_states.append(v) for t in transition_triples: if len(t) != 3: raise TypeError('Tuple ' + str(t) + ' has the wrong arity (!= 3)') - tail = cmh.AssertIsInt64(t[0]) - label = cmh.AssertIsInt64(t[1]) - head = cmh.AssertIsInt64(t[2]) + tail = cmh.assert_is_int64(t[0]) + label = cmh.assert_is_int64(t[1]) + head = cmh.assert_is_int64(t[2]) model_ct.automaton.transition_tail.append(tail) model_ct.automaton.transition_label.append(label) model_ct.automaton.transition_head.append(head) @@ -1653,7 +1601,7 @@ class CpModel(object): Returns: An `IntervalVar` object. """ - size = cmh.AssertIsInt64(size) + size = cmh.assert_is_int64(size) start_expr = self.ParseLinearExpression(start) size_expr = self.ParseLinearExpression(size) end_expr = self.ParseLinearExpression(start + size) @@ -1724,7 +1672,7 @@ class CpModel(object): Returns: An `IntervalVar` object. """ - size = cmh.AssertIsInt64(size) + size = cmh.assert_is_int64(size) start_expr = self.ParseLinearExpression(start) size_expr = self.ParseLinearExpression(size) end_expr = self.ParseLinearExpression(start + size) @@ -1871,8 +1819,8 @@ class CpModel(object): elif (isinstance(arg, _ProductCst) and isinstance(arg.Expression(), IntVar) and arg.Coefficient() == -1): return -arg.Expression().Index() - 1 - elif cmh.IsIntegral(arg): - arg = cmh.AssertIsInt64(arg) + elif cmh.is_integral(arg): + arg = cmh.assert_is_int64(arg) return self.GetOrMakeIndexFromConstant(arg) else: raise TypeError('NotSupported: model.GetOrMakeIndex(' + str(arg) + @@ -1886,8 +1834,8 @@ class CpModel(object): elif isinstance(arg, _NotBooleanVariable): self.AssertIsBooleanVariable(arg.Not()) return arg.Index() - elif cmh.IsIntegral(arg): - cmh.AssertIsBoolean(arg) + elif cmh.is_integral(arg): + cmh.assert_is_boolean(arg) return self.GetOrMakeIndexFromConstant(int(arg)) else: raise TypeError('NotSupported: model.GetOrMakeBooleanIndex(' + @@ -1917,7 +1865,7 @@ class CpModel(object): """Returns a LinearExpressionProto built from a LinearExpr instance.""" result = cp_model_pb2.LinearExpressionProto() mult = -1 if negate else 1 - if cmh.IsIntegral(linear_expr): + if cmh.is_integral(linear_expr): result.offset = int(linear_expr) * mult return result @@ -1926,12 +1874,12 @@ class CpModel(object): result.coeffs.append(mult) return result - coeffs_map, constant = linear_expr.GetVarValueMap() + coeffs_map, constant = linear_expr.GetIntegerVarValueMap() result.offset = constant * mult for t in coeffs_map.items(): if not isinstance(t[0], IntVar): raise TypeError('Wrong argument' + str(t)) - c = cmh.AssertIsInt64(t[1]) + c = cmh.assert_is_int64(t[1]) result.vars.append(t[0].Index()) result.coeffs.append(c * mult) return result @@ -1949,28 +1897,29 @@ class CpModel(object): else: self.__model.objective.vars.append(self.Negated(obj.Index())) self.__model.objective.scaling_factor = -1 - elif isinstance(obj, DoubleLinearExpr): - coeffs_map, constant = obj.GetVarValueMap() - self.__model.floating_point_objective.maximize = not minimize - self.__model.floating_point_objective.offset = constant - for v, c, in coeffs_map.items(): - self.__model.floating_point_objective.coeffs.append(c) - self.__model.floating_point_objective.vars.append(v.Index()) elif isinstance(obj, LinearExpr): - coeffs_map, constant = obj.GetVarValueMap() - if minimize: - self.__model.objective.scaling_factor = 1 - self.__model.objective.offset = constant - else: - self.__model.objective.scaling_factor = -1 - self.__model.objective.offset = -constant - for v, c, in coeffs_map.items(): - self.__model.objective.coeffs.append(c) + coeffs_map, constant, is_integer = obj.GetFloatVarValueMap() + if is_integer: if minimize: - self.__model.objective.vars.append(v.Index()) + self.__model.objective.scaling_factor = 1 + self.__model.objective.offset = constant else: - self.__model.objective.vars.append(self.Negated(v.Index())) - elif cmh.IsIntegral(obj): + self.__model.objective.scaling_factor = -1 + self.__model.objective.offset = -constant + for v, c, in coeffs_map.items(): + self.__model.objective.coeffs.append(c) + if minimize: + self.__model.objective.vars.append(v.Index()) + else: + self.__model.objective.vars.append( + self.Negated(v.Index())) + else: + self.__model.floating_point_objective.maximize = not minimize + self.__model.floating_point_objective.offset = constant + for v, c, in coeffs_map.items(): + self.__model.floating_point_objective.coeffs.append(c) + self.__model.floating_point_objective.vars.append(v.Index()) + elif cmh.is_integral(obj): self.__model.objective.offset = int(obj) self.__model.objective.scaling_factor = 1 else: @@ -2062,7 +2011,7 @@ class CpModel(object): def EvaluateLinearExpr(expression, solution): """Evaluate a linear expression against a solution.""" - if cmh.IsIntegral(expression): + if cmh.is_integral(expression): return int(expression) if not isinstance(expression, LinearExpr): raise TypeError('Cannot interpret %s as a linear expression.' % @@ -2071,27 +2020,35 @@ def EvaluateLinearExpr(expression, solution): value = 0 to_process = [(expression, 1)] while to_process: - expr, coef = to_process.pop() - if isinstance(expr, _ProductCst): - to_process.append((expr.Expression(), coef * expr.Coefficient())) + expr, coeff = to_process.pop() + if cmh.is_integral(expr): + value += int(expr) * coeff + elif isinstance(expr, _ProductCst): + to_process.append((expr.Expression(), coeff * expr.Coefficient())) + elif isinstance(expr, _Sum): + to_process.append((expr.Left(), coeff)) + to_process.append((expr.Right(), coeff)) elif isinstance(expr, _SumArray): for e in expr.Expressions(): - to_process.append((e, coef)) - value += expr.Constant() * coef + to_process.append((e, coeff)) + value += expr.Constant() * coeff elif isinstance(expr, _ScalProd): for e, c in zip(expr.Expressions(), expr.Coefficients()): - to_process.append((e, coef * c)) - value += expr.Constant() * coef + to_process.append((e, coeff * c)) + value += expr.Constant() * coeff elif isinstance(expr, IntVar): - value += coef * solution.solution[expr.Index()] + value += coeff * solution.solution[expr.Index()] elif isinstance(expr, _NotBooleanVariable): - value += coef * (1 - solution.solution[expr.Not().Index()]) + value += coeff * (1 - solution.solution[expr.Not().Index()]) + else: + raise TypeError(f'Cannot interpret {expr} as a linear expression.') + return value def EvaluateBooleanExpression(literal, solution): """Evaluate a boolean expression against a solution.""" - if cmh.IsIntegral(literal): + if cmh.is_integral(literal): return bool(literal) elif isinstance(literal, IntVar) or isinstance(literal, _NotBooleanVariable): @@ -2101,8 +2058,7 @@ def EvaluateBooleanExpression(literal, solution): else: return not solution.solution[-index - 1] else: - raise TypeError('Cannot interpret %s as a boolean expression.' % - literal) + raise TypeError(f'Cannot interpret {literal} as a boolean expression.') class CpSolver(object): @@ -2308,14 +2264,13 @@ class CpSolverSolutionCallback(pywrapsat.SolutionCallback): """ if not self.HasResponse(): raise RuntimeError('Solve() has not be called.') - if cmh.IsIntegral(lit): + if cmh.is_integral(lit): return bool(lit) elif isinstance(lit, IntVar) or isinstance(lit, _NotBooleanVariable): index = lit.Index() return self.SolutionBooleanValue(index) else: - raise TypeError('Cannot interpret %s as a boolean expression.' % - lit) + raise TypeError(f'Cannot interpret {lit} as a boolean expression.') def Value(self, expression): """Evaluates an linear expression in the current solution. @@ -2332,32 +2287,36 @@ class CpSolverSolutionCallback(pywrapsat.SolutionCallback): """ if not self.HasResponse(): raise RuntimeError('Solve() has not be called.') - if cmh.IsIntegral(expression): - return int(expression) - if not isinstance(expression, LinearExpr): - raise TypeError('Cannot interpret %s as a linear expression.' % - expression) value = 0 to_process = [(expression, 1)] while to_process: - expr, coef = to_process.pop() - if isinstance(expr, _ProductCst): + expr, coeff = to_process.pop() + if cmh.is_integral(expr): + value += int(expr) * coeff + elif isinstance(expr, _ProductCst): to_process.append( - (expr.Expression(), coef * expr.Coefficient())) + (expr.Expression(), coeff * expr.Coefficient())) + elif isinstance(expr, _Sum): + to_process.append((expr.Left(), coeff)) + to_process.append((expr.Right(), coeff)) elif isinstance(expr, _SumArray): for e in expr.Expressions(): - to_process.append((e, coef)) - value += expr.Constant() * coef + to_process.append((e, coeff)) + value += expr.Constant() * coeff elif isinstance(expr, _ScalProd): for e, c in zip(expr.Expressions(), expr.Coefficients()): - to_process.append((e, coef * c)) - value += expr.Constant() * coef + to_process.append((e, coeff * c)) + value += expr.Constant() * coeff elif isinstance(expr, IntVar): - value += coef * self.SolutionIntegerValue(expr.Index()) + value += coeff * self.SolutionIntegerValue(expr.Index()) elif isinstance(expr, _NotBooleanVariable): - value += coef * (1 - - self.SolutionIntegerValue(expr.Not().Index())) + value += coeff * (1 - + self.SolutionIntegerValue(expr.Not().Index())) + else: + raise TypeError( + f'Cannot interpret {expression} as a linear expression.') + return value diff --git a/ortools/sat/python/cp_model_helper.py b/ortools/sat/python/cp_model_helper.py index d1e7b4d5b1..c185ee0abe 100644 --- a/ortools/sat/python/cp_model_helper.py +++ b/ortools/sat/python/cp_model_helper.py @@ -21,48 +21,70 @@ INT32_MIN = -2147483648 INT32_MAX = 2147483647 -def IsIntegral(x): +def is_integral(x): """Checks if x has either a number.Integral or a np.integer type.""" return isinstance(x, numbers.Integral) or isinstance(x, np.integer) -def IsNumber(x): +def is_a_number(x): """Checks if x has either a number.Number or a np.double type.""" - return isinstance(x, numbers.Number) or isinstance(x, np.double) + return isinstance(x, numbers.Number) or isinstance( + x, np.double) or isinstance(x, np.integer) -def AssertIsInt64(x): +def is_zero(x): + """Checks if the x is 0 or 0.0.""" + return (is_integral(x) and int(x) == 0) or (is_a_number(x) and + float(x) == 0.0) + + +def is_one(x): + """Checks if x is 1 or 1.0.""" + return (is_integral(x) and int(x) == 1) or (is_a_number(x) and + float(x) == 1.0) + + +def is_minus_one(x): + """Checks if x is -1 or -1.0.""" + return (is_integral(x) and int(x) == -1) or (is_a_number(x) and + float(x) == -1.0) + + +def assert_is_int64(x): """Asserts that x is integer and x is in [min_int_64, max_int_64].""" - if not IsIntegral(x): + if not is_integral(x): raise TypeError('Not an integer: %s' % x) if x < INT_MIN or x > INT_MAX: raise OverflowError('Does not fit in an int64_t: %s' % x) return int(x) -def AssertIsInt32(x): +def assert_is_int32(x): """Asserts that x is integer and x is in [min_int_32, max_int_32].""" - if not IsIntegral(x): + if not is_integral(x): raise TypeError('Not an integer: %s' % x) if x < INT32_MIN or x > INT32_MAX: raise OverflowError('Does not fit in an int32_t: %s' % x) return int(x) -def AssertIsBoolean(x): +def assert_is_boolean(x): """Asserts that x is 0 or 1.""" - if not IsIntegral(x) or x < 0 or x > 1: + if not is_integral(x) or x < 0 or x > 1: raise TypeError('Not an boolean: %s' % x) -def AssertIsNumber(x): +def assert_is_a_number(x): """Asserts that x is a number and returns it.""" - if not IsNumber(x): + if not is_a_number(x): raise TypeError('Not an number: %s' % x) - return float(x) + elif is_integral(x): + return int(x) + else: + return float(x) -def CapInt64(v): +def to_capped_int64(v): """Restrict v within [INT_MIN..INT_MAX] range.""" if v > INT_MAX: return INT_MAX @@ -71,10 +93,10 @@ def CapInt64(v): return v -def CapSub(x, y): +def capped_subtraction(x, y): """Saturated arithmetics. Returns x - y truncated to the int64_t range.""" - AssertIsInt64(x) - AssertIsInt64(y) + assert_is_int64(x) + assert_is_int64(y) if y == 0: return x if x == y: @@ -88,4 +110,4 @@ def CapSub(x, y): return INT_MIN if y == INT_MIN: return INT_MAX - return CapInt64(x - y) + return to_capped_int64(x - y) diff --git a/ortools/sat/samples/nurses_sat.cc b/ortools/sat/samples/nurses_sat.cc index 32d9d4a23d..a63277af95 100644 --- a/ortools/sat/samples/nurses_sat.cc +++ b/ortools/sat/samples/nurses_sat.cc @@ -54,7 +54,7 @@ void NurseSat() { // Creates shift variables. // shifts[(n, d, s)]: nurse 'n' works shift 's' on day 'd'. // [START variables] - std::map, IntVar> shifts; + std::map, BoolVar> shifts; for (int n : all_nurses) { for (int d : all_days) { for (int s : all_shifts) { @@ -70,12 +70,12 @@ void NurseSat() { // [START exactly_one_nurse] for (int d : all_days) { for (int s : all_shifts) { - std::vector x; + LinearExpr sum; for (int n : all_nurses) { auto key = std::make_tuple(n, d, s); - x.push_back(shifts[key]); + sum += shifts[key]; } - cp_model.AddEquality(LinearExpr::Sum(x), 1); + cp_model.AddEquality(sum, 1); } } // [END exactly_one_nurse] @@ -84,12 +84,12 @@ void NurseSat() { // [START at_most_one_shift] for (int n : all_nurses) { for (int d : all_days) { - std::vector x; + LinearExpr sum; for (int s : all_shifts) { auto key = std::make_tuple(n, d, s); - x.push_back(shifts[key]); + sum += shifts[key]; } - cp_model.AddLessOrEqual(LinearExpr::Sum(x), 1); + cp_model.AddLessOrEqual(sum, 1); } } // [END at_most_one_shift] @@ -107,7 +107,7 @@ void NurseSat() { max_shifts_per_nurse = min_shifts_per_nurse + 1; } for (int n : all_nurses) { - std::vector num_shifts_worked; + std::vector num_shifts_worked; // int num_shifts_worked = 0; for (int d : all_days) { for (int s : all_shifts) { diff --git a/ortools/sat/scheduling_cuts.cc b/ortools/sat/scheduling_cuts.cc index 845a9dfc62..8377292fb5 100644 --- a/ortools/sat/scheduling_cuts.cc +++ b/ortools/sat/scheduling_cuts.cc @@ -1183,7 +1183,7 @@ void GenerateNoOverlap2dEnergyCut( // TODO(user): use the offset of the energy expression if better // than size_min * demand_min. DCHECK(!x_helper->IsPresent(t) || !y_helper->IsPresent(t)); - const Literal lit = x_helper->IsOptional(t) && !x_helper->IsPresent(t) + const Literal lit = !x_helper->IsPresent(t) ? x_helper->PresenceLiteral(t) : y_helper->PresenceLiteral(t); if (cut.AddLiteralTerm(lit, @@ -1252,8 +1252,7 @@ CutGenerator CreateNoOverlap2dEnergyCutGenerator( if (y_helper->IsAbsent(box) || y_helper->IsAbsent(box)) continue; // We cannot consider boxes controlled by 2 active enforcement // literals. - if (x_helper->IsOptional(box) && y_helper->IsOptional(box) && - !x_helper->IsPresent(box) && !y_helper->IsPresent(box) && + if (!x_helper->IsPresent(box) && !y_helper->IsPresent(box) && x_helper->PresenceLiteral(box) != y_helper->PresenceLiteral(box)) { continue;