Skip to content

Commit ef9e954

Browse files
committed
[CP-SAT] fix more fuzzer bugs; polish python code
1 parent 05d979a commit ef9e954

13 files changed

+305
-262
lines changed

ortools/sat/cp_model_presolve.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto* ct) {
608608

609609
// By domination argument, we can fix to false everything but the minimum.
610610
if (singleton_literal_with_cost.size() > 1) {
611-
std::sort(
611+
std::stable_sort(
612612
singleton_literal_with_cost.begin(),
613613
singleton_literal_with_cost.end(),
614614
[](const std::pair<int, int64_t>& a,
@@ -1629,8 +1629,8 @@ bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) {
16291629
PossibleIntegerOverflow(*context_->working_model, lin->vars(),
16301630
lin->coeffs(), lin->domain(0))) {
16311631
context_->working_model->mutable_constraints()->RemoveLast();
1632-
// Re-add a new term with the constant factor.
1633-
ct->mutable_int_prod()->add_exprs()->set_offset(constant_factor);
1632+
// The constant factor will be handled by the creation of an affine
1633+
// relation below.
16341634
} else { // Replace with a linear equation.
16351635
context_->UpdateNewConstraintsVariableUsage();
16361636
context_->UpdateRuleStats("int_prod: linearize product by constant.");

ortools/sat/cp_model_table.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ void CompressTuples(absl::Span<const int64_t> domain_sizes,
191191
std::vector<int> to_remove;
192192
std::vector<int64_t> tuple_minus_var_i(num_vars - 1);
193193
for (int i = 0; i < num_vars; ++i) {
194-
const int domain_size = domain_sizes[i];
194+
const int64_t domain_size = domain_sizes[i];
195195
if (domain_size == 1) continue;
196196
absl::flat_hash_map<std::vector<int64_t>, std::vector<int>>
197197
masked_tuples_to_indices;

ortools/sat/cp_model_table_test.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,18 @@ TEST(CompressTuplesTest, NotPerfect) {
196196
EXPECT_EQ(tuples, expected_tuples);
197197
}
198198

199+
TEST(CompressTuplesTest, BigInteger) {
200+
const std::vector<int64_t> domain_sizes = {576460752303423490};
201+
const std::vector<std::vector<int64_t>> original_tuples = {
202+
{1},
203+
{2},
204+
};
205+
std::vector<std::vector<int64_t>> tuples = original_tuples;
206+
CompressTuples(domain_sizes, &tuples);
207+
208+
EXPECT_EQ(tuples, original_tuples);
209+
}
210+
199211
TEST(FullyCompressTuplesTest, BasicTest) {
200212
const std::vector<int64_t> domain_sizes = {4, 4};
201213
std::vector<std::vector<int64_t>> tuples = {

ortools/sat/diffn.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,14 @@ void AddDiffnCumulativeRelationOnX(SchedulingConstraintHelper* x,
106106
const IntegerVariable max_end_var =
107107
CreateVariableAtOrBelowMaxOf(y->Ends(), model);
108108

109-
// (max_end - min_start) >= capacity.
110109
auto* integer_trail = model->GetOrCreate<IntegerTrail>();
110+
if (integer_trail->UpperBound(max_end_var) <
111+
integer_trail->LowerBound(min_start_var)) {
112+
// Trivial infeasible case, will be handled by the linear constraint
113+
// from the interval.
114+
return;
115+
}
116+
// (max_end - min_start) >= capacity.
111117
const AffineExpression capacity(model->Add(NewIntegerVariable(
112118
0, CapSub(integer_trail->UpperBound(max_end_var).value(),
113119
integer_trail->LowerBound(min_start_var).value()))));

ortools/sat/linear_constraint.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ void LinearConstraintBuilder::AddTerm(AffineExpression expr,
6161
terms_.push_back({NegationOf(expr.var), -coeff * expr.coeff});
6262
}
6363
}
64+
DCHECK(!ProdOverflow(coeff, expr.constant));
6465
offset_ += coeff * expr.constant;
6566
}
6667

ortools/sat/linear_programming_constraint.cc

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,13 @@ LinearProgrammingConstraint::LinearProgrammingConstraint(
359359
lp_reduced_cost_.assign(vars.size(), 0.0);
360360

361361
if (!vars.empty()) {
362-
const int max_index = NegationOf(vars.back()).value();
362+
const IntegerVariable max_index = std::max(
363+
NegationOf(vars.back()), integer_trail_->NumIntegerVariables() - 1);
363364
if (max_index >= expanded_lp_solution_.size()) {
364-
expanded_lp_solution_.assign(max_index + 1, 0.0);
365+
expanded_lp_solution_.assign(max_index.value() + 1, 0.0);
365366
}
366367
if (max_index >= expanded_reduced_costs_.size()) {
367-
expanded_reduced_costs_.assign(max_index + 1, 0.0);
368+
expanded_reduced_costs_.assign(max_index.value() + 1, 0.0);
368369
}
369370
}
370371
}
@@ -805,6 +806,17 @@ void LinearProgrammingConstraint::SetLevel(int level) {
805806
lp_solution_is_set_ = true;
806807
lp_solution_ = level_zero_lp_solution_;
807808
lp_solution_level_ = 0;
809+
// Add the fixed variables. They might have been skipped when we did the
810+
// linear relaxation of the model, but cut generators expect all variables
811+
// to have an LP value.
812+
if (expanded_lp_solution_.size() < integer_trail_->NumIntegerVariables()) {
813+
expanded_lp_solution_.resize(integer_trail_->NumIntegerVariables());
814+
}
815+
for (IntegerVariable i(0); i < integer_trail_->NumIntegerVariables(); ++i) {
816+
if (integer_trail_->IsFixed(i)) {
817+
expanded_lp_solution_[i] = ToDouble(integer_trail_->LowerBound(i));
818+
}
819+
}
808820
for (int i = 0; i < lp_solution_.size(); i++) {
809821
const IntegerVariable var = extended_integer_variables_[i];
810822
expanded_lp_solution_[var] = lp_solution_[i];
@@ -1175,7 +1187,7 @@ bool LinearProgrammingConstraint::AnalyzeLp() {
11751187
// linear expression == rhs, we can use this to propagate more!
11761188
//
11771189
// TODO(user): Also propagate on -cut ? in practice we already do that in many
1178-
// places were we try to generate the cut on -cut... But we coould do it sooner
1190+
// places were we try to generate the cut on -cut... But we could do it sooner
11791191
// and more cleanly here.
11801192
bool LinearProgrammingConstraint::PreprocessCut(IntegerVariable first_slack,
11811193
CutData* cut) {

ortools/sat/linear_relaxation.cc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,7 +1302,10 @@ void AppendLinearConstraintRelaxation(const ConstraintProto& ct,
13021302
const IntegerVariable int_var = mapping->Integer(ref);
13031303
lc.AddTerm(int_var, coeff);
13041304
}
1305-
relaxation->linear_constraints.push_back(lc.Build());
1305+
LinearConstraint built_ct = lc.Build();
1306+
if (!PossibleOverflow(*integer_trail, built_ct)) {
1307+
relaxation->linear_constraints.push_back(std::move(built_ct));
1308+
}
13061309
}
13071310
if (rhs_domain_max < max_activity) {
13081311
// And(ei) => terms <= rhs_domain_max
@@ -1319,7 +1322,10 @@ void AppendLinearConstraintRelaxation(const ConstraintProto& ct,
13191322
const IntegerVariable int_var = mapping->Integer(ref);
13201323
lc.AddTerm(int_var, coeff);
13211324
}
1322-
relaxation->linear_constraints.push_back(lc.Build());
1325+
LinearConstraint built_ct = lc.Build();
1326+
if (!PossibleOverflow(*integer_trail, built_ct)) {
1327+
relaxation->linear_constraints.push_back(std::move(built_ct));
1328+
}
13231329
}
13241330
}
13251331

ortools/sat/python/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ cc_library(
2323
hdrs = ["linear_expr.h"],
2424
deps = [
2525
"//ortools/sat:cp_model_cc_proto",
26+
"//ortools/util:fp_roundtrip_conv",
2627
"//ortools/util:sorted_interval_list",
2728
"@com_google_absl//absl/container:btree",
2829
"@com_google_absl//absl/container:fixed_array",

ortools/sat/python/cp_model.py

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,7 @@ def new_int_var_series(
721721
if not isinstance(index, pd.Index):
722722
raise TypeError("Non-index object is used as index")
723723
if not name.isidentifier():
724-
raise ValueError(f"name={name} is not a valid identifier")
724+
raise ValueError(f"name={name!r} is not a valid identifier")
725725
if (
726726
isinstance(lower_bounds, IntegralTypes)
727727
and isinstance(upper_bounds, IntegralTypes)
@@ -775,7 +775,7 @@ def new_bool_var_series(
775775
if not isinstance(index, pd.Index):
776776
raise TypeError("Non-index object is used as index")
777777
if not name.isidentifier():
778-
raise ValueError(f"name={name} is not a valid identifier")
778+
raise ValueError(f"name={name!r} is not a valid identifier")
779779
return pd.Series(
780780
index=index,
781781
data=[
@@ -824,17 +824,9 @@ def add_linear_expression_in_domain(
824824
else:
825825
return self.add_bool_and([]) # Evaluate to true.
826826
raise TypeError(
827-
"not supported: CpModel.add_linear_expression_in_domain("
828-
+ str(linear_expr)
829-
+ " "
830-
+ str(type(linear_expr))
831-
+ " "
832-
+ str(linear_expr.is_integer())
833-
+ " "
834-
+ str(domain)
835-
+ " "
836-
+ str(type(domain))
837-
+ ")"
827+
f"not supported: CpModel.add_linear_expression_in_domain({linear_expr} "
828+
f" {type(linear_expr)} {linear_expr.is_integer()} {domain} "
829+
f"{type(domain)}"
838830
)
839831

840832
def add(self, ct: Union[BoundedLinearExpression, bool, np.bool_]) -> Constraint:
@@ -1642,7 +1634,7 @@ def new_interval_var_series(
16421634
if not isinstance(index, pd.Index):
16431635
raise TypeError("Non-index object is used as index")
16441636
if not name.isidentifier():
1645-
raise ValueError(f"name={name} is not a valid identifier")
1637+
raise ValueError(f"name={name!r} is not a valid identifier")
16461638

16471639
starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
16481640
sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index)
@@ -1715,7 +1707,7 @@ def new_fixed_size_interval_var_series(
17151707
if not isinstance(index, pd.Index):
17161708
raise TypeError("Non-index object is used as index")
17171709
if not name.isidentifier():
1718-
raise ValueError(f"name={name} is not a valid identifier")
1710+
raise ValueError(f"name={name!r} is not a valid identifier")
17191711

17201712
starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
17211713
sizes = _convert_to_integral_series_and_validate_index(sizes, index)
@@ -1819,7 +1811,7 @@ def new_optional_interval_var_series(
18191811
if not isinstance(index, pd.Index):
18201812
raise TypeError("Non-index object is used as index")
18211813
if not name.isidentifier():
1822-
raise ValueError(f"name={name} is not a valid identifier")
1814+
raise ValueError(f"name={name!r} is not a valid identifier")
18231815

18241816
starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
18251817
sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index)
@@ -1913,7 +1905,7 @@ def new_optional_fixed_size_interval_var_series(
19131905
if not isinstance(index, pd.Index):
19141906
raise TypeError("Non-index object is used as index")
19151907
if not name.isidentifier():
1916-
raise ValueError(f"name={name} is not a valid identifier")
1908+
raise ValueError(f"name={name!r} is not a valid identifier")
19171909

19181910
starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
19191911
sizes = _convert_to_integral_series_and_validate_index(sizes, index)
@@ -2857,7 +2849,8 @@ def on_solution_callback(self) -> None:
28572849
obj = self.objective_value
28582850
print(
28592851
f"Solution {self.__solution_count}, time ="
2860-
f" {current_time - self.__start_time:0.2f} s, objective = {obj}"
2852+
f" {current_time - self.__start_time:0.2f} s, objective = {obj}",
2853+
flush=True,
28612854
)
28622855
self.__solution_count += 1
28632856

@@ -2885,7 +2878,7 @@ def on_solution_callback(self) -> None:
28852878
)
28862879
for v in self.__variables:
28872880
print(f" {v} = {self.value(v)}", end=" ")
2888-
print()
2881+
print(flush=True)
28892882
self.__solution_count += 1
28902883

28912884
@property
@@ -2912,7 +2905,7 @@ def on_solution_callback(self) -> None:
29122905
)
29132906
for v in self.__variables:
29142907
print(f" {v} = {self.value(v)}", end=" ")
2915-
print()
2908+
print(flush=True)
29162909
self.__solution_count += 1
29172910

29182911
@property

0 commit comments

Comments
 (0)