26#include "absl/container/btree_map.h"
27#include "absl/container/flat_hash_map.h"
28#include "absl/container/flat_hash_set.h"
29#include "absl/log/check.h"
30#include "absl/log/log.h"
31#include "absl/log/vlog_is_on.h"
32#include "absl/meta/type_traits.h"
33#include "absl/types/span.h"
55 if (!VLOG_IS_ON(1))
return;
56 if (shared_stats_ ==
nullptr)
return;
57 std::vector<std::pair<std::string, int64_t>> stats;
58 stats.push_back({
"implied_bound/num_deductions", num_deductions_});
59 stats.push_back({
"implied_bound/num_stored", bounds_.size()});
60 stats.push_back({
"implied_bound/num_promoted_to_equivalence",
61 num_promoted_to_equivalence_});
63 {
"implied_bound/num_stored_with_view", num_enqueued_in_var_to_bounds_});
64 stats.push_back({
"implied_bound/max_changed_domain_complexity",
65 max_changed_domain_complexity_});
66 shared_stats_->AddStats(stats);
70 if (!parameters_.use_implied_bounds())
return true;
71 const IntegerVariable var = integer_literal.
var;
75 const IntegerValue root_lb = integer_trail_->LevelZeroLowerBound(var);
76 if (integer_literal.
bound <= root_lb)
return true;
78 if (integer_literal.
bound > integer_trail_->LevelZeroUpperBound(var)) {
80 sat_solver_->AddClauseDuringSearch({literal.
Negated()});
88 if (root_lb + 1 >= integer_trail_->LevelZeroUpperBound(var))
return true;
91 const auto key = std::make_pair(literal.
Index(), var);
92 auto insert_result = bounds_.insert({key, integer_literal.
bound});
93 if (!insert_result.second) {
94 if (insert_result.first->second < integer_literal.
bound) {
95 insert_result.first->second = integer_literal.
bound;
103 if (integer_trail_->LevelZeroUpperBound(var) == integer_literal.
bound) {
108 if (it != bounds_.end() && it->second == -integer_literal.
bound) {
116 const auto it = bounds_.find(std::make_pair(literal.
NegatedIndex(), var));
117 if (it != bounds_.end()) {
118 if (it->second <= root_lb) {
123 const IntegerValue deduction =
124 std::min(integer_literal.
bound, it->second);
125 DCHECK_GT(deduction, root_lb);
128 if (!integer_trail_->RootLevelEnqueue(
133 VLOG(2) <<
"Deduction old: "
135 var, integer_trail_->LevelZeroLowerBound(var))
140 if (it->second == deduction) {
143 if (integer_literal.
bound == deduction) {
144 bounds_.erase(std::make_pair(literal.
Index(), var));
151 if (integer_literal.
bound <= integer_trail_->LevelZeroLowerBound(var)) {
179 if (it2 != bounds_.end() && -it2->second < integer_literal.
bound &&
180 sat_solver_->CurrentDecisionLevel() == 0) {
183 if (integer_encoder_->GetAssociatedLiteral(integer_literal) !=
185 integer_encoder_->GetAssociatedLiteral(other_integer_literal) !=
187 ++num_promoted_to_equivalence_;
188 integer_encoder_->AssociateToIntegerLiteral(literal, integer_literal);
189 integer_encoder_->AssociateToIntegerLiteral(literal.
Negated(),
190 other_integer_literal);
191 const IntegerValue other_bound = -it2->second;
192 if (integer_literal.
bound - other_bound > 1) {
193 const Domain old_domain = integer_trail_->InitialVariableDomain(var);
195 Domain(other_bound.value() + 1, integer_literal.
bound.value() - 1)
197 max_changed_domain_complexity_ =
198 std::max(max_changed_domain_complexity_, new_domain.
NumIntervals());
199 if (!integer_trail_->UpdateInitialDomain(var, new_domain)) {
208 if (parameters_.linearization_level() == 0)
return true;
209 if (parameters_.cut_level() == 0)
return true;
215 if (var_to_bounds_.size() <= var) {
216 var_to_bounds_.resize(var.value() + 1);
217 has_implied_bounds_.Resize(var + 1);
219 ++num_enqueued_in_var_to_bounds_;
220 has_implied_bounds_.Set(var);
221 var_to_bounds_[var].emplace_back(integer_encoder_->GetLiteralView(literal),
222 integer_literal.
bound);
223 }
else if (integer_encoder_->GetLiteralView(literal.
Negated()) !=
225 if (var_to_bounds_.size() <= var) {
226 var_to_bounds_.resize(var.value() + 1);
227 has_implied_bounds_.Resize(var + 1);
229 ++num_enqueued_in_var_to_bounds_;
230 has_implied_bounds_.Set(var);
231 var_to_bounds_[var].emplace_back(
233 integer_literal.
bound);
239 IntegerVariable var) {
240 if (var >= var_to_bounds_.size())
return empty_implied_bounds_;
247 std::vector<ImpliedBoundEntry>& ref = var_to_bounds_[var];
248 const IntegerValue root_lb = integer_trail_->LevelZeroLowerBound(var);
250 if (entry.lower_bound <= root_lb)
continue;
251 ref[new_size++] = entry;
253 ref.resize(new_size);
260 IntegerValue value) {
265 literal_to_var_to_value_[literal.
Index()][var] = value;
269 if (!parameters_.use_implied_bounds())
return true;
271 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 1);
272 tmp_integer_literals_.clear();
273 integer_trail_->AppendNewBounds(&tmp_integer_literals_);
275 if (!
Add(first_decision, lit))
return false;
281 const std::vector<ValueLiteralPair>& encoding,
282 int exactly_one_index) {
283 if (!var_to_index_to_element_encodings_.contains(var)) {
284 element_encoded_variables_.push_back(var);
286 var_to_index_to_element_encodings_[var][exactly_one_index] = encoding;
289const absl::btree_map<int, std::vector<ValueLiteralPair>>&
291 const auto& it = var_to_index_to_element_encodings_.find(var);
292 if (it == var_to_index_to_element_encodings_.end()) {
293 return empty_element_encoding_;
299const std::vector<IntegerVariable>&
301 return element_encoded_variables_;
321 absl::Span<const ValueLiteralPair> affine_var_encoding,
322 bool put_affine_left_in_result,
IntegerEncoder* integer_encoder) {
323 IntegerVariable binary = size2_affine.
var;
324 std::vector<LiteralValueValue> terms;
326 const std::vector<ValueLiteralPair>& size2_enc =
331 if (size2_enc.size() != 2)
return terms;
333 Literal lit0 = size2_enc[0].literal;
334 IntegerValue value0 = size2_affine.
ValueAt(size2_enc[0].value);
335 Literal lit1 = size2_enc[1].literal;
336 IntegerValue value1 = size2_affine.
ValueAt(size2_enc[1].value);
338 for (
const auto& [unused, candidate_literal] : affine_var_encoding) {
339 if (candidate_literal == lit1) {
340 std::swap(lit0, lit1);
341 std::swap(value0, value1);
343 if (candidate_literal != lit0)
continue;
346 for (
const auto& [value, literal] : affine_var_encoding) {
347 const IntegerValue size_2_value = literal == lit0 ? value0 : value1;
348 const IntegerValue affine_value = affine.
ValueAt(value);
349 if (put_affine_left_in_result) {
350 terms.push_back({literal, affine_value, size_2_value});
352 terms.push_back({literal, size_2_value, affine_value});
366 std::vector<LiteralValueValue> terms;
371 const std::vector<ValueLiteralPair> left_enc =
373 const std::vector<ValueLiteralPair> right_enc =
375 if (left_enc.size() != 2 || right_enc.size() != 2) {
376 VLOG(2) <<
"encodings are not fully propagated";
380 const Literal left_lit0 = left_enc[0].literal;
381 const IntegerValue left_value0 = left.
ValueAt(left_enc[0].value);
382 const Literal left_lit1 = left_enc[1].literal;
383 const IntegerValue left_value1 = left.
ValueAt(left_enc[1].value);
385 const Literal right_lit0 = right_enc[0].literal;
386 const IntegerValue right_value0 = right.
ValueAt(right_enc[0].value);
387 const Literal right_lit1 = right_enc[1].literal;
388 const IntegerValue right_value1 = right.
ValueAt(right_enc[1].value);
390 if (left_lit0 == right_lit0 || left_lit0 == right_lit1.
Negated()) {
391 terms.push_back({left_lit0, left_value0, right_value0});
392 terms.push_back({left_lit0.
Negated(), left_value1, right_value1});
393 }
else if (left_lit0 == right_lit1 || left_lit0 == right_lit0.
Negated()) {
394 terms.push_back({left_lit0, left_value0, right_value1});
395 terms.push_back({left_lit0.
Negated(), left_value1, right_value0});
396 }
else if (left_lit1 == right_lit1 || left_lit1 == right_lit0.
Negated()) {
397 terms.push_back({left_lit1.
Negated(), left_value0, right_value0});
398 terms.push_back({left_lit1, left_value1, right_value1});
399 }
else if (left_lit1 == right_lit0 || left_lit1 == right_lit1.
Negated()) {
400 terms.push_back({left_lit1.
Negated(), left_value0, right_value1});
401 terms.push_back({left_lit1, left_value1, right_value0});
403 VLOG(3) <<
"Complex size 2 encoding case, need to scan exactly_ones";
411 if (integer_trail_->IsFixed(left) || integer_trail_->IsFixed(right)) {
416 const absl::btree_map<int, std::vector<ValueLiteralPair>>& left_encodings =
417 element_encodings_->Get(left.
var);
420 const absl::btree_map<int, std::vector<ValueLiteralPair>>& right_encodings =
421 element_encodings_->Get(right.
var);
423 std::vector<int> compatible_keys;
424 for (
const auto& [index, encoding] : left_encodings) {
425 if (right_encodings.contains(index)) {
426 compatible_keys.push_back(index);
430 if (compatible_keys.empty()) {
431 if (integer_trail_->InitialVariableDomain(left.
var).Size() == 2) {
432 for (
const auto& [index, right_encoding] : right_encodings) {
434 left, right, right_encoding,
435 false, integer_encoder_);
436 if (!result.empty()) {
441 if (integer_trail_->InitialVariableDomain(right.
var).Size() == 2) {
442 for (
const auto& [index, left_encoding] : left_encodings) {
444 right, left, left_encoding,
445 true, integer_encoder_);
446 if (!result.empty()) {
451 if (integer_trail_->InitialVariableDomain(left.
var).Size() == 2 &&
452 integer_trail_->InitialVariableDomain(right.
var).Size() == 2) {
453 const std::vector<LiteralValueValue> result =
455 if (!result.empty()) {
462 if (compatible_keys.size() > 1) {
463 VLOG(3) <<
"More than one exactly_one involved in the encoding of the two "
468 const int min_index =
469 *std::min_element(compatible_keys.begin(), compatible_keys.end());
472 const std::vector<ValueLiteralPair>& left_encoding =
473 left_encodings.at(min_index);
474 const std::vector<ValueLiteralPair>& right_encoding =
475 right_encodings.at(min_index);
476 DCHECK_EQ(left_encoding.size(), right_encoding.size());
479 std::vector<LiteralValueValue> terms;
480 for (
int i = 0;
i < left_encoding.size(); ++
i) {
481 const Literal literal = left_encoding[
i].literal;
482 DCHECK_EQ(literal, right_encoding[
i].literal);
483 terms.push_back({literal, left.
ValueAt(left_encoding[
i].value),
484 right.
ValueAt(right_encoding[
i].value)});
495 DCHECK(builder !=
nullptr);
498 if (integer_trail_->IsFixed(left)) {
499 if (integer_trail_->IsFixed(right)) {
500 builder->
AddConstant(integer_trail_->FixedValue(left) *
501 integer_trail_->FixedValue(right));
504 builder->
AddTerm(right, integer_trail_->FixedValue(left));
508 if (integer_trail_->IsFixed(right)) {
509 builder->
AddTerm(left, integer_trail_->FixedValue(right));
518 const IntegerValue left_coeff =
520 const IntegerValue right_coeff =
523 left_coeff * right_coeff + left.
constant * right_coeff +
529 const std::vector<LiteralValueValue> decomposition =
531 if (decomposition.empty())
return false;
536 std::min(min_coefficient, term.left_value * term.right_value);
539 const IntegerValue coefficient =
540 term.left_value * term.right_value - min_coefficient;
541 if (coefficient == 0)
continue;
552 model->GetOrCreate<
SatParameters>()->detect_linearized_product() &&
553 model->GetOrCreate<
SatParameters>()->linearization_level() > 1),
554 rlt_enabled_(model->GetOrCreate<
SatParameters>()->add_rlt_cuts() &&
557 sat_solver_(model->GetOrCreate<
SatSolver>()),
558 trail_(model->GetOrCreate<
Trail>()),
564 if (!VLOG_IS_ON(1))
return;
565 if (shared_stats_ ==
nullptr)
return;
566 std::vector<std::pair<std::string, int64_t>> stats;
568 {
"product_detector/num_processed_binary", num_processed_binary_});
570 {
"product_detector/num_processed_exactly_one", num_processed_exo_});
572 {
"product_detector/num_processed_ternary", num_processed_ternary_});
573 stats.push_back({
"product_detector/num_trail_updates", num_trail_updates_});
574 stats.push_back({
"product_detector/num_products", num_products_});
575 stats.push_back({
"product_detector/num_conditional_equalities",
576 num_conditional_equalities_});
578 {
"product_detector/num_conditional_zeros", num_conditional_zeros_});
579 stats.push_back({
"product_detector/num_int_products", num_int_products_});
580 shared_stats_->AddStats(stats);
584 absl::Span<const Literal> ternary_clause) {
585 if (ternary_clause.size() != 3)
return;
586 ++num_processed_ternary_;
588 if (rlt_enabled_) ProcessTernaryClauseForRLT(ternary_clause);
589 if (!enabled_)
return;
591 candidates_[GetKey(ternary_clause[0].Index(), ternary_clause[1].Index())]
592 .push_back(ternary_clause[2].Index());
593 candidates_[GetKey(ternary_clause[0].Index(), ternary_clause[2].Index())]
594 .push_back(ternary_clause[1].Index());
595 candidates_[GetKey(ternary_clause[1].Index(), ternary_clause[2].Index())]
596 .push_back(ternary_clause[0].Index());
600 for (
const Literal l : ternary_clause) {
601 if (l.Index() >= seen_.size()) seen_.resize(l.Index() + 1);
602 seen_[l.Index()] =
true;
607void ProductDetector::ProcessTernaryClauseForRLT(
608 absl::Span<const Literal> ternary_clause) {
609 const int old_size = ternary_clauses_with_view_.size();
610 for (
const Literal l : ternary_clause) {
611 const IntegerVariable var =
614 ternary_clauses_with_view_.resize(old_size);
617 ternary_clauses_with_view_.push_back(l.IsPositive() ? var
623 absl::Span<const Literal> ternary_exo) {
624 if (ternary_exo.size() != 3)
return;
625 ++num_processed_exo_;
627 if (rlt_enabled_) ProcessTernaryClauseForRLT(ternary_exo);
628 if (!enabled_)
return;
630 ProcessNewProduct(ternary_exo[0].Index(), ternary_exo[1].NegatedIndex(),
631 ternary_exo[2].NegatedIndex());
632 ProcessNewProduct(ternary_exo[1].Index(), ternary_exo[0].NegatedIndex(),
633 ternary_exo[2].NegatedIndex());
634 ProcessNewProduct(ternary_exo[2].Index(), ternary_exo[0].NegatedIndex(),
635 ternary_exo[1].NegatedIndex());
641 absl::Span<const Literal> binary_clause) {
642 if (!enabled_)
return;
643 if (binary_clause.size() != 2)
return;
644 ++num_processed_binary_;
645 const std::array<LiteralIndex, 2> key =
646 GetKey(binary_clause[0].NegatedIndex(), binary_clause[1].NegatedIndex());
647 std::array<LiteralIndex, 3> ternary;
648 for (
const LiteralIndex l : candidates_[key]) {
652 std::sort(ternary.begin(), ternary.end());
653 const int l_index = ternary[0] == l ? 0 : ternary[1] == l ? 1 : 2;
654 std::bitset<3>& bs = detector_[ternary];
655 if (bs[l_index])
continue;
657 if (bs[0] && bs[1] && l_index != 2) {
658 ProcessNewProduct(ternary[2],
Literal(ternary[0]).NegatedIndex(),
659 Literal(ternary[1]).NegatedIndex());
661 if (bs[0] && bs[2] && l_index != 1) {
662 ProcessNewProduct(ternary[1],
Literal(ternary[0]).NegatedIndex(),
663 Literal(ternary[2]).NegatedIndex());
665 if (bs[1] && bs[2] && l_index != 0) {
666 ProcessNewProduct(ternary[0],
Literal(ternary[1]).NegatedIndex(),
667 Literal(ternary[2]).NegatedIndex());
673 if (!enabled_)
return;
674 for (LiteralIndex a(0); a < seen_.size(); ++a) {
675 if (!seen_[a])
continue;
676 if (trail_->Assignment().LiteralIsAssigned(
Literal(a)))
continue;
685 if (!enabled_)
return;
686 if (trail_->CurrentDecisionLevel() != 1)
return;
687 ++num_trail_updates_;
695 const int current_index = trail_->
Index();
703 const auto it = products_.find(GetKey(a.
Index(),
b.Index()));
708std::array<LiteralIndex, 2> ProductDetector::GetKey(LiteralIndex a,
709 LiteralIndex
b)
const {
710 std::array<LiteralIndex, 2> key{a,
b};
711 if (key[0] > key[1]) std::swap(key[0], key[1]);
715void ProductDetector::ProcessNewProduct(LiteralIndex p, LiteralIndex a,
719 products_[GetKey(a,
b)] = p;
723 GetKey(Literal(a).IsPositive() ? a : Literal(a).NegatedIndex(),
724 Literal(
b).IsPositive() ?
b : Literal(
b).NegatedIndex()));
728 IntegerVariable
b)
const {
729 if (a ==
b)
return true;
734 if (integer_trail_->InitialVariableDomain(a).Size() != 2)
return false;
735 if (integer_trail_->InitialVariableDomain(
b).Size() != 2)
return false;
737 const LiteralIndex la =
739 a, integer_trail_->LevelZeroUpperBound(a)));
742 const LiteralIndex lb =
744 b, integer_trail_->LevelZeroUpperBound(
b)));
748 return has_product_.contains(
749 GetKey(
Literal(la).IsPositive() ? la :
Literal(la).NegatedIndex(),
754 IntegerVariable
b)
const {
760void ProductDetector::ProcessNewProduct(IntegerVariable p,
Literal l,
769 int_products_[{l.
Index(), x}] = p;
774 ++num_conditional_equalities_;
778 for (
int i = 0;
i < 2; ++
i) {
787 std::vector<IntegerVariable>& others =
788 conditional_equalities_[{l.
Index(), x}];
789 for (
const IntegerVariable o : others) {
798 if (conditional_zeros_.contains({l.NegatedIndex(), x})) {
799 ProcessNewProduct(x, l, y);
807 ++num_conditional_zeros_;
809 auto [_, inserted] = conditional_zeros_.insert({l.
Index(), p});
811 const auto it = conditional_equalities_.find({l.
NegatedIndex(), p});
812 if (it != conditional_equalities_.end()) {
813 for (
const IntegerVariable x : it->second) {
814 ProcessNewProduct(p, l.
Negated(), x);
822std::pair<IntegerVariable, IntegerVariable> Canonicalize(IntegerVariable a,
824 if (a <
b)
return {a,
b};
828double GetLiteralLpValue(
830 const util_intops::StrongVector<IntegerVariable, double>& lp_values) {
837void ProductDetector::UpdateRLTMaps(
838 const util_intops::StrongVector<IntegerVariable, double>& lp_values,
839 IntegerVariable var1,
double lp1, IntegerVariable var2,
double lp2,
840 IntegerVariable bound_var,
double bound_lp) {
843 if (bound_lp > lp1 && bound_lp > lp2)
return;
845 const auto [it, inserted] =
846 bool_rlt_ubs_.
insert({Canonicalize(var1, var2), bound_var});
849 if (!inserted && bound_lp < GetLiteralLpValue(it->second, lp_values)) {
850 it->second = bound_var;
854 if (lp1 * lp2 > bound_lp + 1e-4) {
855 bool_rlt_candidates_[var1].push_back(var2);
856 bool_rlt_candidates_[var2].push_back(var1);
862 absl::Span<const IntegerVariable> lp_vars,
867 bool_rlt_ubs_.clear();
871 bool_rlt_candidates_.clear();
872 const int size = ternary_clauses_with_view_.size();
873 if (size == 0)
return;
875 is_in_lp_vars_.resize(integer_trail_->NumIntegerVariables().value());
876 for (
const IntegerVariable var : lp_vars) is_in_lp_vars_.Set(var);
878 for (
int i = 0;
i < size;
i += 3) {
879 const IntegerVariable var1 = ternary_clauses_with_view_[
i];
880 const IntegerVariable var2 = ternary_clauses_with_view_[
i + 1];
881 const IntegerVariable var3 = ternary_clauses_with_view_[
i + 2];
889 const double lp1 = GetLiteralLpValue(var1, lp_values);
890 const double lp2 = GetLiteralLpValue(var2, lp_values);
891 const double lp3 = GetLiteralLpValue(var3, lp_values);
894 1.0 - lp2, var3, lp3);
896 1.0 - lp3, var2, lp2);
898 1.0 - lp3, var1, lp1);
903 for (
const IntegerVariable var : lp_vars) is_in_lp_vars_.ClearBucket(var);
Domain IntersectionWith(const Domain &domain) const
Domain Complement() const
const std::vector< Literal > & DirectImplications(Literal literal)
void Add(IntegerVariable var, const std::vector< ValueLiteralPair > &encoding, int exactly_one_index)
const absl::btree_map< int, std::vector< ValueLiteralPair > > & Get(IntegerVariable var)
const std::vector< IntegerVariable > & GetElementEncodedVariables() const
bool ProcessIntegerTrail(Literal first_decision)
void AddLiteralImpliesVarEqValue(Literal literal, IntegerVariable var, IntegerValue value)
bool Add(Literal literal, IntegerLiteral integer_literal)
const std::vector< ImpliedBoundEntry > & GetImpliedBounds(IntegerVariable var)
const std::vector< ValueLiteralPair > & FullDomainEncoding(IntegerVariable var) const
IntegerVariable GetLiteralView(Literal lit) const
bool VariableIsFullyEncoded(IntegerVariable var) const
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
void AddTerm(IntegerVariable var, IntegerValue coeff)
void AddConstant(IntegerValue value)
LiteralIndex NegatedIndex() const
LiteralIndex Index() const
bool TryToLinearize(const AffineExpression &left, const AffineExpression &right, LinearConstraintBuilder *builder)
std::vector< LiteralValueValue > TryToDecompose(const AffineExpression &left, const AffineExpression &right)
void InitializeBooleanRLTCuts(absl::Span< const IntegerVariable > lp_vars, const util_intops::StrongVector< IntegerVariable, double > &lp_values)
void ProcessTernaryClause(absl::Span< const Literal > ternary_clause)
void ProcessTernaryExactlyOne(absl::Span< const Literal > ternary_exo)
void ProcessImplicationGraph(BinaryImplicationGraph *graph)
LiteralIndex GetProduct(Literal a, Literal b) const
bool ProductIsLinearizable(IntegerVariable a, IntegerVariable b) const
void ProcessConditionalEquality(Literal l, IntegerVariable x, IntegerVariable y)
void ProcessConditionalZero(Literal l, IntegerVariable p)
void ProcessTrailAtLevelOne()
ProductDetector(Model *model)
void ProcessBinaryClause(absl::Span< const Literal > binary_clause)
iterator insert(const_iterator pos, const value_type &x)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::vector< LiteralValueValue > TryToReconcileSize2Encodings(const AffineExpression &left, const AffineExpression &right, IntegerEncoder *integer_encoder)
const LiteralIndex kNoLiteralIndex(-1)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
std::vector< LiteralValueValue > TryToReconcileEncodings(const AffineExpression &size2_affine, const AffineExpression &affine, absl::Span< const ValueLiteralPair > affine_var_encoding, bool put_affine_left_in_result, IntegerEncoder *integer_encoder)
bool VariableIsPositive(IntegerVariable i)
IntegerValue ValueAt(IntegerValue var_value) const
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)