This commit is contained in:
Laurent Perron
2025-04-14 18:23:51 +02:00
parent e311ea1e63
commit 0a0ff22b27
8 changed files with 119 additions and 29 deletions

View File

@@ -101,7 +101,10 @@ NeighborhoodGeneratorHelper::NeighborhoodGeneratorHelper(
}
void NeighborhoodGeneratorHelper::Synchronize() {
if (global_time_limit_->LimitReached()) return;
if (shared_response_->ProblemIsSolved() ||
global_time_limit_->LimitReached()) {
return;
}
if (shared_bounds_ != nullptr) {
std::vector<int> model_variables;
std::vector<int64_t> new_lower_bounds;

View File

@@ -58,10 +58,11 @@ TEST(NeighborhoodGeneratorHelperTest, ActiveVariables) {
const CpSolverResponse solution = SolveCpModel(proto, &model);
EXPECT_EQ(solution.status(), CpSolverStatus::OPTIMAL);
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
SharedBoundsManager shared_bounds_manager(proto);
SatParameters params;
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit, &shared_bounds_manager);
@@ -113,10 +114,11 @@ TYPED_TEST(GeneratorTest, BasicContract) {
EXPECT_EQ(solution.status(), CpSolverStatus::OPTIMAL);
SatParameters params;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
shared_response_manager->InitializeObjective(proto);
Model main_model;
ModelSharedTimeLimit time_limit(&main_model);
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
shared_response_manager->InitializeObjective(proto);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit);
TypeParam generator(&helper, "test");
@@ -150,8 +152,9 @@ TYPED_TEST(GeneratorTest, NoReduction) {
EXPECT_EQ(solution.status(), CpSolverStatus::OPTIMAL);
SatParameters params;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit);
@@ -187,9 +190,10 @@ TYPED_TEST(GeneratorTest, ReadyToGenerate) {
const CpSolverResponse solution = SolveWithParameters(proto, params);
Model model;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
shared_response_manager->InitializeObjective(proto);
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
shared_response_manager->InitializeObjective(proto);
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, model.GetOrCreate<SatParameters>(),
shared_response_manager, &time_limit);
@@ -215,8 +219,9 @@ TYPED_TEST(GeneratorTest, ModelWithoutConstraintDoNotCrash) {
EXPECT_EQ(solution.status(), CpSolverStatus::OPTIMAL);
SatParameters params;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit);
@@ -242,8 +247,9 @@ TEST(GeneratorTest, UCBScore) {
EXPECT_EQ(solution.status(), CpSolverStatus::OPTIMAL);
SatParameters params;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit);
@@ -273,13 +279,14 @@ TEST(RelaxationInducedNeighborhoodGeneratorTest, NoNeighborhoodGeneratedRINS) {
proto.mutable_objective()->add_coeffs(1);
Model model;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
shared_response_manager->InitializeObjective(proto);
SharedLPSolutionRepository lp_solutions(/*num_solutions_to_keep=*/1);
SharedIncompleteSolutionManager incomplete_solutions;
SatParameters params;
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
shared_response_manager->InitializeObjective(proto);
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit);
@@ -315,12 +322,13 @@ TEST(RelaxationInducedNeighborhoodGeneratorTest, NoNeighborhoodGeneratedRENS) {
proto.mutable_objective()->add_coeffs(1);
Model model;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
SharedLPSolutionRepository lp_solutions(/*num_solutions_to_keep=*/1);
SharedIncompleteSolutionManager incomplete_solutions;
SatParameters params;
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit);
@@ -360,8 +368,9 @@ TEST(RelaxationInducedNeighborhoodGeneratorTest, ValueOutOfDomain) {
lp_solutions.Synchronize();
SatParameters params;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit);
@@ -410,12 +419,13 @@ TEST(LocalBranchingNeighborhoodGeneratorTest,
solution: [ 0, 0, 0 ])pb");
Model model;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
SharedBoundsManager shared_bounds_manager(proto);
shared_response_manager->InitializeObjective(proto);
SatParameters params;
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
shared_response_manager->InitializeObjective(proto);
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit);
@@ -479,12 +489,13 @@ TEST(LocalBranchingNeighborhoodGeneratorTest,
solution: [ 1, 1, 2 ])pb");
Model model;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
SharedBoundsManager shared_bounds_manager(proto);
shared_response_manager->InitializeObjective(proto);
SatParameters params;
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
shared_response_manager->InitializeObjective(proto);
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit);
@@ -537,8 +548,9 @@ TEST(NeighborhoodGeneratorHelperTest, BoundAreUpdatedOnSynchronize) {
SharedBoundsManager shared_bounds_manager(proto);
SatParameters params;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit, &shared_bounds_manager);
@@ -594,11 +606,12 @@ TEST(NeighborhoodGeneratorHelperTest, FixGivenVariables) {
solution: [ 2, 3, 4 ])pb");
Model model;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
SharedBoundsManager shared_bounds_manager(proto);
SatParameters params;
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit, &shared_bounds_manager);
@@ -646,11 +659,12 @@ TEST(NeighborhoodGeneratorHelperTest, InfeasibleCopyAndFixVariables) {
solution: [ 2, 3, -2 ])pb");
Model model;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
SharedBoundsManager shared_bounds_manager(proto);
SatParameters params;
Model main_model;
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
ModelSharedTimeLimit time_limit(&main_model);
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit, &shared_bounds_manager);
@@ -800,12 +814,13 @@ TEST(NeighborhoodGeneratorHelperTest, GetSchedulingPrecedences) {
solution: [ 0, 0, 2, 3, 3, 4, 5, 5, 5, 6, 8, 8, 9 ])pb");
Model model;
auto* shared_response_manager = model.GetOrCreate<SharedResponseManager>();
SharedBoundsManager shared_bounds_manager(proto);
SatParameters params;
Model main_model;
ModelSharedTimeLimit time_limit(&main_model);
auto* shared_response_manager =
main_model.GetOrCreate<SharedResponseManager>();
NeighborhoodGeneratorHelper helper(&proto, &params, shared_response_manager,
&time_limit, &shared_bounds_manager);
random_engine_t random;

View File

@@ -13231,11 +13231,10 @@ namespace {
// Updates the solution hint in the proto with the crushed solution values.
void UpdateHintInProto(PresolveContext* context) {
CpModelProto* proto = context->working_model;
if (!proto->has_solution_hint()) return;
if (context->ModelIsUnsat()) return;
SolutionCrush& crush = context->solution_crush();
if (!crush.SolutionIsLoaded()) return;
const int num_vars = context->working_model->variables().size();
for (int i = 0; i < num_vars; ++i) {
// If the initial hint is incomplete or infeasible, the crushed hint might
@@ -13251,7 +13250,7 @@ void UpdateHintInProto(PresolveContext* context) {
i, {{relation.representative, relation.coeff}}, relation.offset);
}
}
crush.StoreSolutionAsHint(*proto);
crush.StoreSolutionAsHint(*context->working_model);
}
// Canonicalizes the routes constraints node expressions. In particular,

View File

@@ -7921,6 +7921,26 @@ TEST(PresolveCpModelTest, InnerObjectiveLowerBound) {
EXPECT_EQ(r.inner_objective_lower_bound(), 8);
}
TEST(PresolveCpModelTest, ModelWithoutVariables) {
const CpModelProto cp_model = ParseTestProto(
R"pb(
constraints {
all_diff {
exprs { offset: 1 }
exprs { offset: 2 }
}
}
)pb");
SatParameters params;
params.set_log_search_progress(true);
params.set_debug_crash_if_presolve_breaks_hint(true);
params.set_cp_model_presolve(false);
CpSolverResponse response = SolveWithParameters(cp_model, params);
EXPECT_EQ(response.status(), CpSolverStatus::OPTIMAL);
}
} // namespace
} // namespace sat
} // namespace operations_research

View File

@@ -4751,6 +4751,57 @@ TEST(PresolveCpModelTest, CumulativeBug3) {
EXPECT_EQ(response.inner_objective_lower_bound(), -6);
}
TEST(PresolveCpModelTest, CumulativeBug4) {
const CpModelProto cp_model = ParseTestProto(
R"pb(
variables { domain: 0 domain: 1 }
variables { domain: 0 domain: 6 }
variables { domain: -920 domain: -15 domain: 1540 domain: 1692 }
variables { domain: 0 domain: 6 }
variables { domain: 0 domain: 1 }
variables { domain: -1 domain: 0 domain: 4096 domain: 4096 }
variables { domain: 0 domain: 17 }
constraints {
enforcement_literal: 0
interval {
start { vars: 1 coeffs: 1 }
end { vars: 3 coeffs: 1 }
size { vars: 2 coeffs: 1 offset: 2 }
}
}
constraints {
enforcement_literal: 4
interval {
start { offset: 2 }
end { vars: 6 coeffs: 1 }
size { vars: 5 coeffs: 1 }
}
}
constraints {
cumulative {
capacity { offset: 1 }
intervals: 1
demands { vars: 6 coeffs: 1 }
}
}
constraints { bool_xor { literals: 0 literals: 4 } }
)pb");
SatParameters params;
params.set_log_search_progress(true);
params.set_debug_crash_if_presolve_breaks_hint(true);
params.set_cp_model_presolve(false);
params.set_cp_model_probing_level(0);
params.set_symmetry_level(0);
CpSolverResponse response = SolveWithParameters(cp_model, params);
EXPECT_EQ(response.status(), CpSolverStatus::OPTIMAL);
params.set_cp_model_presolve(true);
response = SolveWithParameters(cp_model, params);
EXPECT_EQ(response.status(), CpSolverStatus::OPTIMAL);
}
} // namespace
} // namespace sat
} // namespace operations_research

View File

@@ -82,7 +82,7 @@ std::function<void(Model*)> Cumulative(
// If the interval can be of size zero, it currently do not count towards
// the capacity. TODO(user): Change that since we have optional interval
// for this.
if (intervals->MinSize(vars[i]) == 0) {
if (intervals->MinSize(vars[i]) <= 0) {
enforcement_literals.push_back(encoder->GetOrCreateAssociatedLiteral(
intervals->Size(vars[i]).GreaterOrEqual(IntegerValue(1))));
}

View File

@@ -1376,9 +1376,10 @@ void PresolveContext::InitializeNewDomains() {
}
void PresolveContext::LoadSolutionHint() {
absl::flat_hash_map<int, int64_t> hint_values;
if (working_model->has_solution_hint()) {
const int num_vars = working_model->variables().size();
if (working_model->has_solution_hint() || num_vars == 0) {
const auto hint_proto = working_model->solution_hint();
absl::flat_hash_map<int, int64_t> hint_values;
int num_changes = 0;
for (int i = 0; i < hint_proto.vars().size(); ++i) {
const int var = hint_proto.vars(i);
@@ -1393,13 +1394,13 @@ void PresolveContext::LoadSolutionHint() {
if (num_changes > 0) {
UpdateRuleStats("hint: moved var hint within its domain.", num_changes);
}
for (int i = 0; i < working_model->variables().size(); ++i) {
for (int i = 0; i < num_vars; ++i) {
if (!hint_values.contains(i) && IsFixed(i)) {
hint_values[i] = FixedValue(i);
}
}
solution_crush_.LoadSolution(num_vars, hint_values);
}
solution_crush_.LoadSolution(working_model->variables().size(), hint_values);
}
void PresolveContext::CanonicalizeDomainOfSizeTwo(int var) {

View File

@@ -629,6 +629,7 @@ void SolutionCrush::AssignVariableToPackingArea(
const CompactVectorVector<int, Rectangle>& areas, const CpModelProto& model,
absl::Span<const int> x_intervals, absl::Span<const int> y_intervals,
absl::Span<const BoxInAreaLiteral> box_in_area_lits) {
if (!solution_is_loaded_) return;
struct RectangleTypeAndIndex {
enum class Type {
kHintedBox,