// Copyright 2010-2025 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "ortools/sat/python/linear_expr.h" #include #include #include #include #include #include #include #include "absl/hash/hash.h" #include "absl/log/check.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" #include "ortools/sat/cp_model.pb.h" #include "ortools/sat/cp_model_utils.h" #include "ortools/util/fp_roundtrip_conv.h" #include "ortools/util/sorted_interval_list.h" namespace operations_research { namespace sat { namespace python { bool LinearExpr::IsInteger() { IntExprVisitor lin; lin.AddToProcess(shared_from_this(), 1); return lin.ProcessAll(); } std::shared_ptr LinearExpr::TermInt( std::shared_ptr expr, int64_t coeff) { return std::make_shared(expr, coeff, 0); } std::shared_ptr LinearExpr::TermFloat( std::shared_ptr expr, double coeff) { return std::make_shared(expr, coeff, 0.0); } std::shared_ptr LinearExpr::AffineInt( std::shared_ptr expr, int64_t coeff, int64_t offset) { if (coeff == 1 && offset == 0) return expr; return std::make_shared(expr, coeff, offset); } std::shared_ptr LinearExpr::AffineFloat( std::shared_ptr expr, double coeff, double offset) { if (coeff == 1.0 && offset == 0.0) return expr; return std::make_shared(expr, coeff, offset); } std::shared_ptr LinearExpr::ConstantInt(int64_t value) { return std::make_shared(value); } std::shared_ptr LinearExpr::ConstantFloat(double value) { return std::make_shared(value); } std::shared_ptr LinearExpr::Add(std::shared_ptr other) { std::vector> exprs; exprs.push_back(shared_from_this()); exprs.push_back(other); return std::make_shared(exprs); } std::shared_ptr LinearExpr::AddInt(int64_t cst) { if (cst == 0) return shared_from_this(); return std::make_shared(shared_from_this(), 1, cst); } std::shared_ptr LinearExpr::AddFloat(double cst) { if (cst == 0.0) return shared_from_this(); return std::make_shared(shared_from_this(), 1.0, cst); } std::shared_ptr LinearExpr::Sub(std::shared_ptr other) { std::vector> exprs; exprs.push_back(shared_from_this()); exprs.push_back(other->MulInt(-1)); return std::make_shared(exprs); } std::shared_ptr LinearExpr::SubInt(int64_t cst) { if (cst == 0) return shared_from_this(); return std::make_shared(shared_from_this(), 1, -cst); } std::shared_ptr LinearExpr::SubFloat(double cst) { if (cst == 0.0) return shared_from_this(); return std::make_shared(shared_from_this(), 1.0, -cst); } std::shared_ptr LinearExpr::RSub( std::shared_ptr other) { std::vector> exprs; exprs.push_back(shared_from_this()); exprs.push_back(other); const std::vector coeffs = {-1, 1}; return std::make_shared(exprs, coeffs, 0); } std::shared_ptr LinearExpr::RSubInt(int64_t cst) { return std::make_shared(shared_from_this(), -1, cst); } std::shared_ptr LinearExpr::RSubFloat(double cst) { return std::make_shared(shared_from_this(), -1.0, cst); } std::shared_ptr LinearExpr::MulInt(int64_t cst) { if (cst == 0) return std::make_shared(0); if (cst == 1) return shared_from_this(); return std::make_shared(shared_from_this(), cst, 0); } std::shared_ptr LinearExpr::MulFloat(double cst) { if (cst == 0.0) return std::make_shared(0); if (cst == 1.0) return shared_from_this(); return std::make_shared(shared_from_this(), cst, 0.0); } std::shared_ptr LinearExpr::Neg() { return std::make_shared(shared_from_this(), -1, 0); } void FloatExprVisitor::AddToProcess(std::shared_ptr expr, double coeff) { to_process_.push_back(std::make_pair(expr, coeff)); } void FloatExprVisitor::AddConstant(double constant) { offset_ += constant; } void FloatExprVisitor::AddVarCoeff(std::shared_ptr var, double coeff) { canonical_terms_[var] += coeff; } void FloatExprVisitor::ProcessAll() { while (!to_process_.empty()) { const auto [expr, coeff] = to_process_.back(); to_process_.pop_back(); expr->VisitAsFloat(*this, coeff); } } double FloatExprVisitor::Process(std::vector>* vars, std::vector* coeffs) { ProcessAll(); vars->clear(); coeffs->clear(); for (const auto& [var, coeff] : canonical_terms_) { if (coeff == 0.0) continue; vars->push_back(var); coeffs->push_back(coeff); } return offset_; } double FloatExprVisitor::Evaluate(const CpSolverResponse& solution) { ProcessAll(); for (const auto& [var, coeff] : canonical_terms_) { if (coeff == 0) continue; offset_ += coeff * solution.solution(var->index()); } return offset_; } FlatFloatExpr::FlatFloatExpr(std::shared_ptr expr) { FloatExprVisitor lin; lin.AddToProcess(expr, 1.0); offset_ = lin.Process(&vars_, &coeffs_); } void FlatFloatExpr::VisitAsFloat(FloatExprVisitor& lin, double c) { for (int i = 0; i < vars_.size(); ++i) { lin.AddVarCoeff(vars_[i], coeffs_[i] * c); } lin.AddConstant(offset_ * c); } std::string FlatFloatExpr::ToString() const { if (vars_.empty()) { return absl::StrCat(RoundTripDoubleFormat(offset_)); } std::string s = "("; bool first_printed = true; for (int i = 0; i < vars_.size(); ++i) { if (coeffs_[i] == 0.0) continue; if (first_printed) { first_printed = false; if (coeffs_[i] == 1.0) { absl::StrAppend(&s, vars_[i]->ToString()); } else if (coeffs_[i] == -1.0) { absl::StrAppend(&s, "-", vars_[i]->ToString()); } else { absl::StrAppend(&s, RoundTripDoubleFormat(coeffs_[i]), " * ", vars_[i]->ToString()); } } else { if (coeffs_[i] == 1.0) { absl::StrAppend(&s, " + ", vars_[i]->ToString()); } else if (coeffs_[i] == -1.0) { absl::StrAppend(&s, " - ", vars_[i]->ToString()); } else if (coeffs_[i] > 0.0) { absl::StrAppend(&s, " + ", RoundTripDoubleFormat(coeffs_[i]), " * ", vars_[i]->ToString()); } else { absl::StrAppend(&s, " - ", RoundTripDoubleFormat(-coeffs_[i]), " * ", vars_[i]->ToString()); } } } // If there are no terms, just print the offset. if (first_printed) { return absl::StrCat(RoundTripDoubleFormat(offset_)); } // If there is an offset, print it. if (offset_ != 0.0) { if (offset_ > 0.0) { absl::StrAppend(&s, " + ", RoundTripDoubleFormat(offset_)); } else { absl::StrAppend(&s, " - ", RoundTripDoubleFormat(-offset_)); } } absl::StrAppend(&s, ")"); return s; } std::string FlatFloatExpr::DebugString() const { return absl::StrCat( "FlatFloatExpr([", absl::StrJoin(vars_, ", ", [](std::string* out, std::shared_ptr e) { absl::StrAppend(out, e->DebugString()); }), "], [", absl::StrJoin(coeffs_, ", ", [](std::string* out, double coeff) { absl::StrAppend(out, RoundTripDoubleFormat(coeff)); }), "], ", RoundTripDoubleFormat(offset_), ")"); } FlatIntExpr::FlatIntExpr(std::shared_ptr expr) { IntExprVisitor lin; lin.AddToProcess(expr, 1); ok_ = lin.Process(&vars_, &coeffs_, &offset_); } std::string FlatIntExpr::ToString() const { if (vars_.empty()) { return absl::StrCat(offset_); } std::string s = "("; bool first_printed = true; for (int i = 0; i < vars_.size(); ++i) { if (coeffs_[i] == 0) continue; if (first_printed) { first_printed = false; if (coeffs_[i] == 1) { absl::StrAppend(&s, vars_[i]->ToString()); } else if (coeffs_[i] == -1) { absl::StrAppend(&s, "-", vars_[i]->ToString()); } else { absl::StrAppend(&s, coeffs_[i], " * ", vars_[i]->ToString()); } } else { if (coeffs_[i] == 1) { absl::StrAppend(&s, " + ", vars_[i]->ToString()); } else if (coeffs_[i] == -1) { absl::StrAppend(&s, " - ", vars_[i]->ToString()); } else if (coeffs_[i] > 1) { absl::StrAppend(&s, " + ", coeffs_[i], " * ", vars_[i]->ToString()); } else { absl::StrAppend(&s, " - ", -coeffs_[i], " * ", vars_[i]->ToString()); } } } // If there are no terms, just print the offset. if (first_printed) { return absl::StrCat(offset_); } // If there is an offset, print it. if (offset_ != 0) { if (offset_ > 0) { absl::StrAppend(&s, " + ", offset_); } else { absl::StrAppend(&s, " - ", -offset_); } } absl::StrAppend(&s, ")"); return s; } std::string FlatIntExpr::DebugString() const { return absl::StrCat( "FlatIntExpr([", absl::StrJoin(vars_, ", ", [](std::string* out, std::shared_ptr var) { absl::StrAppend(out, var->DebugString()); }), "], [", absl::StrJoin(coeffs_, ", "), "], ", offset_, ")"); } void FloatConstant::VisitAsFloat(FloatExprVisitor& lin, double c) { lin.AddConstant(value_ * c); } std::string FloatConstant::ToString() const { return absl::StrCat(value_); } std::string FloatConstant::DebugString() const { return absl::StrCat("FloatConstant(", RoundTripDoubleFormat(value_), ")"); } SumArray::SumArray(std::vector> exprs, int64_t int_offset, double double_offset) : exprs_(std::move(exprs)), int_offset_(int_offset), double_offset_(double_offset) { DCHECK_GE(exprs_.size(), 2); } std::shared_ptr SumArray::AddInPlace( std::shared_ptr expr) { exprs_.push_back(std::move(expr)); return shared_from_this(); } std::shared_ptr SumArray::AddIntInPlace(int64_t cst) { int_offset_ += cst; return shared_from_this(); } std::shared_ptr SumArray::AddFloatInPlace(double cst) { double_offset_ += cst; return shared_from_this(); } bool SumArray::VisitAsInt(IntExprVisitor& lin, int64_t c) { if (double_offset_ != 0.0) return false; for (int i = 0; i < exprs_.size(); ++i) { lin.AddToProcess(exprs_[i], c); } lin.AddConstant(int_offset_ * c); return true; } void SumArray::VisitAsFloat(FloatExprVisitor& lin, double c) { for (int i = 0; i < exprs_.size(); ++i) { lin.AddToProcess(exprs_[i], c); } if (int_offset_ != 0) { lin.AddConstant(int_offset_ * c); } if (double_offset_ != 0.0) { lin.AddConstant(double_offset_ * c); } } std::string SumArray::ToString() const { DCHECK(!exprs_.empty()); std::string s = "("; for (int i = 0; i < exprs_.size(); ++i) { if (i > 0) { absl::StrAppend(&s, " + "); } absl::StrAppend(&s, exprs_[i]->ToString()); } if (double_offset_ != 0.0) { if (double_offset_ > 0.0) { absl::StrAppend(&s, " + ", double_offset_); } else { absl::StrAppend(&s, " - ", -double_offset_); } } if (int_offset_ != 0) { if (int_offset_ > 0) { absl::StrAppend(&s, " + ", int_offset_); } else { absl::StrAppend(&s, " - ", -int_offset_); } } absl::StrAppend(&s, ")"); return s; } std::string SumArray::DebugString() const { std::string s = absl::StrCat( "SumArray(", absl::StrJoin(exprs_, ", ", [](std::string* out, std::shared_ptr expr) { absl::StrAppend(out, expr->DebugString()); })); if (int_offset_ != 0) { absl::StrAppend(&s, ", int_offset=", int_offset_); } if (double_offset_ != 0.0) { absl::StrAppend(&s, ", double_offset=", RoundTripDoubleFormat(double_offset_)); } absl::StrAppend(&s, ")"); return s; } FloatWeightedSum::FloatWeightedSum( absl::Span> exprs, absl::Span coeffs, double offset) : exprs_(exprs.begin(), exprs.end()), coeffs_(coeffs.begin(), coeffs.end()), offset_(offset) { DCHECK_GE(exprs_.size(), 2); } void FloatWeightedSum::VisitAsFloat(FloatExprVisitor& lin, double c) { for (int i = 0; i < exprs_.size(); ++i) { lin.AddToProcess(exprs_[i], coeffs_[i] * c); } lin.AddConstant(offset_ * c); } std::string FloatWeightedSum::ToString() const { std::string s = "("; bool first_printed = true; for (int i = 0; i < exprs_.size(); ++i) { if (coeffs_[i] == 0.0) continue; if (first_printed) { first_printed = false; if (coeffs_[i] == 1.0) { absl::StrAppend(&s, exprs_[i]->ToString()); } else if (coeffs_[i] == -1.0) { absl::StrAppend(&s, "-", exprs_[i]->ToString()); } else { absl::StrAppend(&s, RoundTripDoubleFormat(coeffs_[i]), " * ", exprs_[i]->ToString()); } } else { if (coeffs_[i] == 1.0) { absl::StrAppend(&s, " + ", exprs_[i]->ToString()); } else if (coeffs_[i] == -1.0) { absl::StrAppend(&s, " - ", exprs_[i]->ToString()); } else if (coeffs_[i] > 0.0) { absl::StrAppend(&s, " + ", RoundTripDoubleFormat(coeffs_[i]), " * ", exprs_[i]->ToString()); } else { absl::StrAppend(&s, " - ", RoundTripDoubleFormat(-coeffs_[i]), " * ", exprs_[i]->ToString()); } } } // If there are no terms, just print the offset. if (first_printed) { return absl::StrCat(RoundTripDoubleFormat(offset_)); } // If there is an offset, print it. if (offset_ != 0.0) { if (offset_ > 0.0) { absl::StrAppend(&s, " + ", RoundTripDoubleFormat(offset_)); } else { absl::StrAppend(&s, " - ", RoundTripDoubleFormat(-offset_)); } } absl::StrAppend(&s, ")"); return s; } std::string FloatWeightedSum::DebugString() const { return absl::StrCat( "FloatWeightedSum([", absl::StrJoin(exprs_, ", ", [](std::string* out, std::shared_ptr e) { absl::StrAppend(out, e->DebugString()); }), "], [", absl::StrJoin(coeffs_, ", ", [](std::string* out, double coeff) { absl::StrAppend(out, RoundTripDoubleFormat(coeff)); }), RoundTripDoubleFormat(offset_), ")"); } IntWeightedSum::IntWeightedSum( absl::Span> exprs, absl::Span coeffs, int64_t offset) : exprs_(exprs.begin(), exprs.end()), coeffs_(coeffs.begin(), coeffs.end()), offset_(offset) {} void IntWeightedSum::VisitAsFloat(FloatExprVisitor& lin, double c) { for (int i = 0; i < exprs_.size(); ++i) { lin.AddToProcess(exprs_[i], coeffs_[i] * c); } lin.AddConstant(offset_ * c); } bool IntWeightedSum::VisitAsInt(IntExprVisitor& lin, int64_t c) { for (int i = 0; i < exprs_.size(); ++i) { lin.AddToProcess(exprs_[i], coeffs_[i] * c); } lin.AddConstant(offset_ * c); return true; } std::string IntWeightedSum::ToString() const { if (exprs_.empty()) { return absl::StrCat(offset_); } std::string s = "("; bool first_printed = true; for (int i = 0; i < exprs_.size(); ++i) { if (coeffs_[i] == 0) continue; if (first_printed) { first_printed = false; if (coeffs_[i] == 1) { absl::StrAppend(&s, exprs_[i]->ToString()); } else if (coeffs_[i] == -1) { absl::StrAppend(&s, "-", exprs_[i]->ToString()); } else { absl::StrAppend(&s, coeffs_[i], " * ", exprs_[i]->ToString()); } } else { if (coeffs_[i] == 1) { absl::StrAppend(&s, " + ", exprs_[i]->ToString()); } else if (coeffs_[i] == -1) { absl::StrAppend(&s, " - ", exprs_[i]->ToString()); } else if (coeffs_[i] > 1) { absl::StrAppend(&s, " + ", coeffs_[i], " * ", exprs_[i]->ToString()); } else { absl::StrAppend(&s, " - ", -coeffs_[i], " * ", exprs_[i]->ToString()); } } } // If there are no terms, just print the offset. if (first_printed) { return absl::StrCat(offset_); } // If there is an offset, print it. if (offset_ != 0) { if (offset_ > 0) { absl::StrAppend(&s, " + ", offset_); } else { absl::StrAppend(&s, " - ", -offset_); } } absl::StrAppend(&s, ")"); return s; } std::string IntWeightedSum::DebugString() const { return absl::StrCat( "IntWeightedSum([", absl::StrJoin(exprs_, ", ", [](std::string* out, std::shared_ptr expr) { absl::StrAppend(out, expr->DebugString()); }), "], [", absl::StrJoin(coeffs_, ", "), "], ", offset_, ")"); } FloatAffine::FloatAffine(std::shared_ptr expr, double coeff, double offset) : expr_(expr), coeff_(coeff), offset_(offset) {} void FloatAffine::VisitAsFloat(FloatExprVisitor& lin, double c) { lin.AddToProcess(expr_, c * coeff_); lin.AddConstant(offset_ * c); } std::string FloatAffine::ToString() const { std::string s = "("; if (coeff_ == 1.0) { absl::StrAppend(&s, expr_->ToString()); } else if (coeff_ == -1.0) { absl::StrAppend(&s, "-", expr_->ToString()); } else { absl::StrAppend(&s, coeff_, " * ", expr_->ToString()); } if (offset_ > 0.0) { absl::StrAppend(&s, " + ", offset_); } else if (offset_ < 0.0) { absl::StrAppend(&s, " - ", -offset_); } absl::StrAppend(&s, ")"); return s; } std::string FloatAffine::DebugString() const { return absl::StrCat("FloatAffine(expr=", expr_->DebugString(), ", coeff=", coeff_, ", offset=", offset_, ")"); } IntAffine::IntAffine(std::shared_ptr expr, int64_t coeff, int64_t offset) : expr_(expr), coeff_(coeff), offset_(offset) {} bool IntAffine::VisitAsInt(IntExprVisitor& lin, int64_t c) { lin.AddToProcess(expr_, c * coeff_); lin.AddConstant(offset_ * c); return true; } void IntAffine::VisitAsFloat(FloatExprVisitor& lin, double c) { lin.AddToProcess(expr_, c * coeff_); lin.AddConstant(offset_ * c); } std::string IntAffine::ToString() const { std::string s = "("; if (coeff_ == 1) { absl::StrAppend(&s, expr_->ToString()); } else if (coeff_ == -1) { absl::StrAppend(&s, "-", expr_->ToString()); } else { absl::StrAppend(&s, coeff_, " * ", expr_->ToString()); } if (offset_ > 0) { absl::StrAppend(&s, " + ", offset_); } else if (offset_ < 0) { absl::StrAppend(&s, " - ", -offset_); } absl::StrAppend(&s, ")"); return s; } std::string IntAffine::DebugString() const { return absl::StrCat("IntAffine(expr=", expr_->DebugString(), ", coeff=", coeff_, ", offset=", offset_, ")"); } std::shared_ptr IntAffine::AddInt(int64_t cst) { if (cst == 0) return shared_from_this(); return LinearExpr::AffineInt(expr_, coeff_, offset_ + cst); } std::shared_ptr IntAffine::SubInt(int64_t cst) { if (cst == 0) return shared_from_this(); return LinearExpr::AffineInt(expr_, coeff_, offset_ - cst); } std::shared_ptr IntAffine::RSubInt(int64_t cst) { return LinearExpr::AffineInt(expr_, -coeff_, cst - offset_); } std::shared_ptr IntAffine::MulInt(int64_t cst) { if (cst == 0) return std::make_shared(0); if (cst == 1) return shared_from_this(); return LinearExpr::AffineInt(expr_, coeff_ * cst, offset_ * cst); } std::shared_ptr IntAffine::Neg() { return LinearExpr::AffineInt(expr_, -coeff_, -offset_); } std::shared_ptr LinearExpr::Eq( std::shared_ptr rhs) { return std::make_shared(shared_from_this(), rhs, Domain(0)); } std::shared_ptr LinearExpr::EqCst(int64_t rhs) { return std::make_shared(shared_from_this(), Domain(rhs)); } std::shared_ptr LinearExpr::Ne( std::shared_ptr rhs) { return std::make_shared(shared_from_this(), rhs, Domain(0).Complement()); } std::shared_ptr LinearExpr::NeCst(int64_t rhs) { return std::make_shared(shared_from_this(), Domain(rhs).Complement()); } std::shared_ptr LinearExpr::Le( std::shared_ptr rhs) { return std::make_shared( shared_from_this(), rhs, Domain(std::numeric_limits::min(), 0)); } std::shared_ptr LinearExpr::LeCst(int64_t rhs) { return std::make_shared( shared_from_this(), Domain(std::numeric_limits::min(), rhs)); } std::shared_ptr LinearExpr::Lt( std::shared_ptr rhs) { return std::make_shared( shared_from_this(), rhs, Domain(std::numeric_limits::min(), -1)); } std::shared_ptr LinearExpr::LtCst(int64_t rhs) { return std::make_shared( shared_from_this(), Domain(std::numeric_limits::min(), rhs - 1)); } std::shared_ptr LinearExpr::Ge( std::shared_ptr rhs) { return std::make_shared( shared_from_this(), rhs, Domain(0, std::numeric_limits::max())); } std::shared_ptr LinearExpr::GeCst(int64_t rhs) { return std::make_shared( shared_from_this(), Domain(rhs, std::numeric_limits::max())); } std::shared_ptr LinearExpr::Gt( std::shared_ptr rhs) { return std::make_shared( shared_from_this(), rhs, Domain(1, std::numeric_limits::max())); } std::shared_ptr LinearExpr::GtCst(int64_t rhs) { return std::make_shared( shared_from_this(), Domain(rhs + 1, std::numeric_limits::max())); } void IntExprVisitor::AddToProcess(std::shared_ptr expr, int64_t coeff) { to_process_.push_back(std::make_pair(expr, coeff)); } void IntExprVisitor::AddConstant(int64_t constant) { offset_ += constant; } void IntExprVisitor::AddVarCoeff(std::shared_ptr var, int64_t coeff) { canonical_terms_[var] += coeff; } bool IntExprVisitor::ProcessAll() { while (!to_process_.empty()) { const auto [expr, coeff] = to_process_.back(); to_process_.pop_back(); if (!expr->VisitAsInt(*this, coeff)) return false; } return true; } bool IntExprVisitor::Process(std::vector>* vars, std::vector* coeffs, int64_t* offset) { if (!ProcessAll()) return false; vars->clear(); coeffs->clear(); for (const auto& [var, coeff] : canonical_terms_) { if (coeff == 0) continue; vars->push_back(var); coeffs->push_back(coeff); } *offset = offset_; return true; } bool IntExprVisitor::Evaluate(const CpSolverResponse& solution, int64_t* value) { if (!ProcessAll()) return false; *value = offset_; for (const auto& [var, coeff] : canonical_terms_) { if (coeff == 0) continue; *value += coeff * solution.solution(var->index()); } return true; } // TODO(user): This hash method does not distinguish between variables with // the same index and different models. int64_t Literal::Hash() const { return absl::HashOf(index()); } bool IntVarComparator::operator()(std::shared_ptr lhs, std::shared_ptr rhs) const { return lhs->index() < rhs->index(); } std::string IntVar::name() const { if (model_proto_ == nullptr || index_ >= model_proto_->variables_size()) { return ""; } return model_proto_->variables(index_).name(); } void IntVar::SetName(absl::string_view name) { if (model_proto_ == nullptr || index_ >= model_proto_->variables_size()) { return; } if (name.empty()) { model_proto_->mutable_variables(index_)->clear_name(); } else { model_proto_->mutable_variables(index_)->set_name(name); } } Domain IntVar::domain() const { if (model_proto_ == nullptr || index_ >= model_proto_->variables_size()) { return Domain(); } return ReadDomainFromProto(model_proto_->variables(index_)); } void IntVar::SetDomain(const Domain& domain) { if (model_proto_ == nullptr || index_ >= model_proto_->variables_size()) { return; } FillDomainInProto(domain, model_proto_->mutable_variables(index_)); } std::shared_ptr IntVar::model_proto() const { return model_proto_; } IntegerVariableProto* IntVar::proto() const { if (model_proto_ == nullptr || index_ >= model_proto_->variables_size()) { return nullptr; } return model_proto_->mutable_variables(index_); } bool IntVar::is_boolean() const { IntegerVariableProto* var_proto = proto(); if (var_proto == nullptr) return false; return var_proto->domain_size() == 2 && var_proto->domain(0) >= 0 && var_proto->domain(1) <= 1; } bool IntVar::is_fixed() const { IntegerVariableProto* var_proto = proto(); if (var_proto == nullptr) return false; return var_proto->domain_size() == 2 && var_proto->domain(0) == var_proto->domain(1); } std::shared_ptr IntVar::negated() const { return std::make_shared(model_proto_, index_); } namespace { std::string VarDomainToString(IntegerVariableProto* var_proto) { std::string domain_str; for (int i = 0; i < var_proto->domain_size(); i += 2) { const int64_t lb = var_proto->domain(i); const int64_t ub = var_proto->domain(i + 1); if (i > 0) absl::StrAppend(&domain_str, ", "); if (lb == ub) { absl::StrAppend(&domain_str, lb); } else { absl::StrAppend(&domain_str, lb, "..", ub); } } return domain_str; } } // namespace std::string IntVar::ToString() const { std::string var_name = name(); IntegerVariableProto* var_proto = proto(); if (var_name.empty()) { if (is_fixed() && var_proto != nullptr && var_proto->domain_size() >= 2) { return absl::StrCat(var_proto->domain(0)); } else if (is_boolean()) { return absl::StrCat("b", index_); } else { return absl::StrCat("x", index_); } } return var_name; } std::string IntVar::DebugString() const { std::string var_name = name(); if (var_name.empty()) { if (is_boolean()) { var_name = absl::StrCat("b", index_); } else { var_name = absl::StrCat("x", index_); } } IntegerVariableProto* var_proto = proto(); if (var_proto == nullptr) return var_name; return absl::StrCat(var_name, "(", VarDomainToString(var_proto), ")"); } int NotBooleanVariable::index() const { return NegatedRef(var_index_); } /** * Returns the negation of the current literal, that is the original Boolean * variable. */ std::shared_ptr NotBooleanVariable::negated() const { return std::make_shared(model_proto_, var_index_); } bool NotBooleanVariable::VisitAsInt(IntExprVisitor& lin, int64_t c) { lin.AddVarCoeff(std::make_shared(model_proto_, var_index_), -c); lin.AddConstant(c); return true; } void NotBooleanVariable::VisitAsFloat(FloatExprVisitor& lin, double c) { lin.AddVarCoeff(std::make_shared(model_proto_, var_index_), -c); lin.AddConstant(c); } std::string NotBooleanVariable::ToString() const { return absl::StrCat("not(", negated()->ToString(), ")"); } std::string NotBooleanVariable::DebugString() const { return absl::StrCat("NotBooleanVariable(var_index=", var_index_, ")"); } BoundedLinearExpression::BoundedLinearExpression( std::shared_ptr expr, const Domain& bounds) : bounds_(bounds) { IntExprVisitor lin; lin.AddToProcess(expr, 1); ok_ = lin.Process(&vars_, &coeffs_, &offset_); } BoundedLinearExpression::BoundedLinearExpression( std::shared_ptr pos, std::shared_ptr neg, const Domain& bounds) : bounds_(bounds) { IntExprVisitor lin; lin.AddToProcess(pos, 1); lin.AddToProcess(neg, -1); ok_ = lin.Process(&vars_, &coeffs_, &offset_); } const Domain& BoundedLinearExpression::bounds() const { return bounds_; } const std::vector>& BoundedLinearExpression::vars() const { return vars_; } const std::vector& BoundedLinearExpression::coeffs() const { return coeffs_; } int64_t BoundedLinearExpression::offset() const { return offset_; } bool BoundedLinearExpression::ok() const { return ok_; } std::string BoundedLinearExpression::ToString() const { if (!ok_) return "Invalid BoundedLinearExpression"; std::string s; if (vars_.empty()) { s = absl::StrCat(offset_); } else if (vars_.size() == 1) { const std::string var_name = vars_[0]->ToString(); if (coeffs_[0] == 1) { s = var_name; } else if (coeffs_[0] == -1) { s = absl::StrCat("-", var_name); } else { s = absl::StrCat(coeffs_[0], " * ", var_name); } if (offset_ > 0) { absl::StrAppend(&s, " + ", offset_); } else if (offset_ < 0) { absl::StrAppend(&s, " - ", -offset_); } } else { s = "("; for (int i = 0; i < vars_.size(); ++i) { const std::string var_name = vars_[i]->ToString(); if (i == 0) { if (coeffs_[i] == 1) { absl::StrAppend(&s, var_name); } else if (coeffs_[i] == -1) { absl::StrAppend(&s, "-", var_name); } else { absl::StrAppend(&s, coeffs_[i], " * ", var_name); } } else { if (coeffs_[i] == 1) { absl::StrAppend(&s, " + ", var_name); } else if (coeffs_[i] == -1) { absl::StrAppend(&s, " - ", var_name); } else if (coeffs_[i] > 1) { absl::StrAppend(&s, " + ", coeffs_[i], " * ", var_name); } else { absl::StrAppend(&s, " - ", -coeffs_[i], " * ", var_name); } } } if (offset_ > 0) { absl::StrAppend(&s, " + ", offset_); } else if (offset_ < 0) { absl::StrAppend(&s, " - ", -offset_); } absl::StrAppend(&s, ")"); } if (bounds_.IsFixed()) { absl::StrAppend(&s, " == ", bounds_.Min()); } else if (bounds_.NumIntervals() == 1) { if (bounds_.Min() == std::numeric_limits::min()) { if (bounds_.Max() == std::numeric_limits::max()) { return absl::StrCat("True (unbounded expr ", s, ")"); } else { absl::StrAppend(&s, " <= ", bounds_.Max()); } } else if (bounds_.Max() == std::numeric_limits::max()) { absl::StrAppend(&s, " >= ", bounds_.Min()); } else { return absl::StrCat(bounds_.Min(), " <= ", s, " <= ", bounds_.Max()); } } else if (bounds_.Complement().IsFixed()) { absl::StrAppend(&s, " != ", bounds_.Complement().Min()); } else { absl::StrAppend(&s, " in ", bounds_.ToString()); } return s; } std::string BoundedLinearExpression::DebugString() const { if (!ok_) return "Invalid BoundedLinearExpression"; return absl::StrCat( "BoundedLinearExpression(vars=[", absl::StrJoin(vars_, ", ", [](std::string* out, std::shared_ptr var) { absl::StrAppend(out, var->DebugString()); }), "], coeffs=[", absl::StrJoin(coeffs_, ", "), "], offset=", offset_, ", bounds=", bounds_.ToString(), ")"); } bool BoundedLinearExpression::CastToBool(bool* result) const { if (!ok_) return false; const bool is_zero = bounds_.IsFixed() && bounds_.FixedValue() == 0; const Domain complement = bounds_.Complement(); const bool is_all_but_zero = complement.IsFixed() && complement.FixedValue() == 0; if (is_zero || is_all_but_zero) { if (vars_.empty()) { *result = is_zero; return true; } else if (vars_.size() == 2 && coeffs_[0] + coeffs_[1] == 0 && std::abs(coeffs_[0]) == 1) { *result = is_all_but_zero; return true; } } return false; } } // namespace python } // namespace sat } // namespace operations_research