2025-01-10 11:35:44 +01:00
|
|
|
// Copyright 2010-2025 Google LLC
|
2024-10-03 10:59:28 +02:00
|
|
|
// 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/var_domination.h"
|
|
|
|
|
|
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
|
#include "ortools/base/gmock.h"
|
|
|
|
|
#include "ortools/base/parse_test_proto.h"
|
|
|
|
|
#include "ortools/sat/cp_model.pb.h"
|
2024-12-02 17:17:42 +01:00
|
|
|
#include "ortools/sat/integer_base.h"
|
2024-10-03 10:59:28 +02:00
|
|
|
#include "ortools/sat/model.h"
|
|
|
|
|
#include "ortools/sat/presolve_context.h"
|
|
|
|
|
#include "ortools/util/sorted_interval_list.h"
|
|
|
|
|
|
|
|
|
|
namespace operations_research {
|
|
|
|
|
namespace sat {
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
using ::google::protobuf::contrib::parse_proto::ParseTestProto;
|
|
|
|
|
using ::testing::ElementsAre;
|
2024-11-18 11:45:52 +01:00
|
|
|
using ::testing::EqualsProto;
|
2024-10-03 10:59:28 +02:00
|
|
|
using ::testing::IsEmpty;
|
|
|
|
|
using ::testing::UnorderedElementsAre;
|
|
|
|
|
|
|
|
|
|
// X + 2Y + Z = 0
|
|
|
|
|
// X + 2Z >= 2
|
|
|
|
|
//
|
|
|
|
|
// Doing (X--, Z++) is always beneficial if possible.
|
|
|
|
|
TEST(VarDominationTest, BasicExample1) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Z"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 2, 1 ]
|
|
|
|
|
domain: [ 0, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 2 ]
|
|
|
|
|
coeffs: [ 1, 2 ]
|
|
|
|
|
domain: [ 2, 9223372036854775807 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
|
|
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
const IntegerVariable Z = VarDomination::RefToIntegerVariable(2);
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(X), ElementsAre(Z));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Y), IsEmpty());
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Z), IsEmpty());
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(X)), IsEmpty());
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(Y)), IsEmpty());
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(Z)),
|
|
|
|
|
ElementsAre(NegationOf(X)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// X + 2Y + Z = 0
|
|
|
|
|
// X + 2Z >= 2
|
|
|
|
|
//
|
|
|
|
|
// Doing (X--, Z++) is always beneficial if possible.
|
|
|
|
|
TEST(VarDominationTest, ExploitDominanceRelation) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Z"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 2, 1 ]
|
|
|
|
|
domain: [ 0, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 2 ]
|
|
|
|
|
coeffs: [ 1, 2 ]
|
|
|
|
|
domain: [ 2, 9223372036854775807 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
context.UpdateNewConstraintsVariableUsage();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
EXPECT_TRUE(ExploitDominanceRelations(var_dom, &context));
|
|
|
|
|
|
|
|
|
|
// Because X--, Z++ is always ok, we can exclude some value from Z using
|
|
|
|
|
// equation X + 2Z >=2 we see that if Z=5, X >= -8, so we can decrease it,
|
|
|
|
|
// but for Z = 6, X might be -10, so we are not sure.
|
|
|
|
|
//
|
|
|
|
|
// Also not that X can be 10 with Z at 10 too, so we cannot reduced the domain
|
|
|
|
|
// of X.
|
|
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[-10,10]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[-10,10]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(2).ToString(), "[6,10]");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Same example as before but now Z has holes, which complicate a bit the
|
|
|
|
|
// final result.
|
|
|
|
|
TEST(VarDominationTest, ExploitDominanceRelationWithHoles) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Z"
|
|
|
|
|
domain: [ -10, 0, 7, 10 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 2, 1 ]
|
|
|
|
|
domain: [ 0, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 2 ]
|
|
|
|
|
coeffs: [ 1, 2 ]
|
|
|
|
|
domain: [ 2, 9223372036854775807 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
context.UpdateNewConstraintsVariableUsage();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
EXPECT_TRUE(ExploitDominanceRelations(var_dom, &context));
|
|
|
|
|
|
|
|
|
|
// With hole, if Z is 0, we will not be able to increase it up to 6, so we
|
|
|
|
|
// can't remove 0. If it is lower, we can safely increase it to zero though.
|
|
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[-10,10]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[-10,10]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(2).ToString(), "[0][7,10]");
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-18 11:45:52 +01:00
|
|
|
// X - 2Y >= -1
|
|
|
|
|
// X => Y
|
|
|
|
|
//
|
|
|
|
|
// Doing (X--, Y--) is always beneficial if possible.
|
|
|
|
|
TEST(VarDominationTest, ExploitDominanceOfImplicant) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ 0, 1 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ 0, 1 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
enforcement_literal: 0
|
|
|
|
|
bool_and { literals: [ 1 ] }
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
coeffs: [ 1, -2 ]
|
|
|
|
|
domain: [ -1, 10 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
solution_hint {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
values: [ 1, 1 ]
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
context.UpdateNewConstraintsVariableUsage();
|
|
|
|
|
context.LoadSolutionHint();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
EXPECT_TRUE(ExploitDominanceRelations(var_dom, &context));
|
|
|
|
|
|
2024-11-25 14:49:56 +01:00
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(X), ElementsAre(NegationOf(Y)));
|
2024-11-18 11:45:52 +01:00
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[0]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[0]");
|
2025-01-24 14:10:52 +01:00
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[0], 0);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[1], 0);
|
2024-11-18 11:45:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2X - Y >= 0
|
|
|
|
|
// X => Y
|
|
|
|
|
//
|
|
|
|
|
// Doing (X++, Y++) is always beneficial if possible.
|
|
|
|
|
TEST(VarDominationTest, ExploitDominanceOfNegatedImplicand) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ 0, 1 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ 0, 1 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
enforcement_literal: 0
|
|
|
|
|
bool_and { literals: [ 1 ] }
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
coeffs: [ 2, -1 ]
|
|
|
|
|
domain: [ 0, 10 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
solution_hint {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
values: [ 0, 0 ]
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
context.UpdateNewConstraintsVariableUsage();
|
|
|
|
|
context.LoadSolutionHint();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
EXPECT_TRUE(ExploitDominanceRelations(var_dom, &context));
|
|
|
|
|
|
2024-11-25 14:49:56 +01:00
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(X)), ElementsAre(Y));
|
2024-11-18 11:45:52 +01:00
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[1]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[1]");
|
2025-01-24 14:10:52 +01:00
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[0], 1);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[1], 1);
|
2024-11-18 11:45:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// X + 2Y >= 0
|
|
|
|
|
// ExactlyOne(X, Y)
|
|
|
|
|
//
|
|
|
|
|
// Doing (X--, Y++) is always beneficial if possible.
|
|
|
|
|
TEST(VarDominationTest, ExploitDominanceInExactlyOne) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ 0, 1 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ 0, 1 ]
|
|
|
|
|
}
|
|
|
|
|
constraints { exactly_one { literals: [ 0, 1 ] } }
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
coeffs: [ 1, 2 ]
|
|
|
|
|
domain: [ 0, 10 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
solution_hint {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
values: [ 1, 0 ]
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
context.UpdateNewConstraintsVariableUsage();
|
|
|
|
|
context.LoadSolutionHint();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
EXPECT_TRUE(ExploitDominanceRelations(var_dom, &context));
|
|
|
|
|
|
2024-11-25 14:49:56 +01:00
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(X), ElementsAre(Y));
|
2024-11-18 11:45:52 +01:00
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[0]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[0,1]");
|
2025-01-24 14:10:52 +01:00
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[0], 0);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[1], 1);
|
2024-11-18 11:45:52 +01:00
|
|
|
}
|
|
|
|
|
|
2024-11-25 14:49:56 +01:00
|
|
|
// Objective: min(X + Y + 2Z)
|
|
|
|
|
// Constraint: X + Y + Z <= 10
|
|
|
|
|
// X, Y in [-10, 10], Z in [5, 10]
|
|
|
|
|
//
|
|
|
|
|
// Doing (X++, Z--) or (Y++, Z--) is always beneficial if possible.
|
|
|
|
|
TEST(VarDominationTest, ExploitDominanceWithIntegerVariables) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Z"
|
|
|
|
|
domain: [ 5, 10 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1, 1 ]
|
|
|
|
|
domain: [ 0, 10 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
objective {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1, 2 ]
|
|
|
|
|
}
|
|
|
|
|
solution_hint {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
values: [ 1, 1, 8 ]
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
context.UpdateNewConstraintsVariableUsage();
|
|
|
|
|
context.LoadSolutionHint();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
EXPECT_TRUE(ExploitDominanceRelations(var_dom, &context));
|
|
|
|
|
|
|
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
const IntegerVariable Z = VarDomination::RefToIntegerVariable(2);
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Z), ElementsAre(X, Y));
|
|
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[-5]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[0,10]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(2).ToString(), "[5]");
|
2025-01-24 14:10:52 +01:00
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[0], -5);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[1], 10);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[2], 5);
|
2024-11-25 14:49:56 +01:00
|
|
|
}
|
|
|
|
|
|
2024-11-18 11:45:52 +01:00
|
|
|
// Objective: min(X + 2Y)
|
|
|
|
|
// Constraint: BoolOr(X, Y)
|
|
|
|
|
//
|
|
|
|
|
// Doing (X++, Y--) is always beneficial if possible.
|
|
|
|
|
TEST(VarDominationTest, ExploitRemainingDominance) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ 0, 1 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ 0, 1 ]
|
|
|
|
|
}
|
|
|
|
|
constraints { bool_or { literals: [ 0, 1 ] } }
|
|
|
|
|
objective {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
coeffs: [ 1, 2 ]
|
|
|
|
|
}
|
|
|
|
|
solution_hint {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
values: [ 0, 1 ]
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
context.UpdateNewConstraintsVariableUsage();
|
|
|
|
|
context.LoadSolutionHint();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
EXPECT_TRUE(ExploitDominanceRelations(var_dom, &context));
|
|
|
|
|
|
|
|
|
|
// Check that an implication between X and Y was added, and that the hint was
|
|
|
|
|
// updated in consequence.
|
|
|
|
|
EXPECT_EQ(context.working_model->constraints_size(), 2);
|
|
|
|
|
const ConstraintProto expected_constraint_proto =
|
|
|
|
|
ParseTestProto(R"pb(enforcement_literal: -1
|
|
|
|
|
bool_and { literals: -2 })pb");
|
|
|
|
|
EXPECT_THAT(context.working_model->constraints(1),
|
|
|
|
|
EqualsProto(expected_constraint_proto));
|
|
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[0,1]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[0,1]");
|
2025-01-24 14:10:52 +01:00
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[0], 1);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[1], 0);
|
2024-11-18 11:45:52 +01:00
|
|
|
}
|
|
|
|
|
|
2024-11-25 14:49:56 +01:00
|
|
|
// Objective: min(X)
|
|
|
|
|
// Constraint: -5 <= X + Y <= 5
|
|
|
|
|
// Constraint: -15 <= Y + Z <= 15
|
|
|
|
|
// X,Y in [-10, 10], Z in [-5, 5]
|
|
|
|
|
//
|
|
|
|
|
// Doing (X--, Y++) is always beneficial if possible.
|
|
|
|
|
TEST(VarDominationTest, ExploitRemainingDominanceWithIntegerVariables) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Z"
|
|
|
|
|
domain: [ -5, 5 ]
|
|
|
|
|
}
|
|
|
|
|
objective {
|
|
|
|
|
vars: [ 0 ]
|
|
|
|
|
coeffs: [ 1 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
coeffs: [ 1, 1 ]
|
|
|
|
|
domain: [ -5, 5 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1 ]
|
|
|
|
|
domain: [ -15, 15 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
solution_hint {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
values: [ 0, 1, 2 ]
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
context.UpdateNewConstraintsVariableUsage();
|
|
|
|
|
context.LoadSolutionHint();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
EXPECT_TRUE(ExploitDominanceRelations(var_dom, &context));
|
|
|
|
|
|
|
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(X), ElementsAre(Y));
|
|
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[-10,-5]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[5,10]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(2).ToString(), "[5]");
|
2025-01-24 14:10:52 +01:00
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[0], -5);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[1], 6);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[2], 5);
|
2024-11-25 14:49:56 +01:00
|
|
|
}
|
|
|
|
|
|
2024-10-03 10:59:28 +02:00
|
|
|
// X + Y + Z = 0
|
|
|
|
|
// X + 2 Z >= 2
|
|
|
|
|
TEST(VarDominationTest, BasicExample1Variation) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Z"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1, 1 ]
|
|
|
|
|
domain: [ 0, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 2 ]
|
|
|
|
|
coeffs: [ 1, 2 ]
|
|
|
|
|
domain: [ 2, 9223372036854775807 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
|
|
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
const IntegerVariable Z = VarDomination::RefToIntegerVariable(2);
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(X), ElementsAre(Z));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Y), UnorderedElementsAre(X, Z));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Z), IsEmpty());
|
|
|
|
|
|
|
|
|
|
// TODO(user): Transpose is broken.
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(X)),
|
|
|
|
|
ElementsAre(NegationOf(Y)));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(Y)), IsEmpty());
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(Z)),
|
|
|
|
|
UnorderedElementsAre(NegationOf(X), NegationOf(Y)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// X + Y + Z >= 0
|
|
|
|
|
// Y + Z <= 0
|
|
|
|
|
TEST(VarDominationTest, BasicExample2) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Z"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1, 1 ]
|
|
|
|
|
domain: [ 0, 9223372036854775807 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1 ]
|
|
|
|
|
domain: [ -9223372036854775808, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
|
|
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
const IntegerVariable Z = VarDomination::RefToIntegerVariable(2);
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(var_dom.CanFreelyDecrease(X));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(X), IsEmpty());
|
|
|
|
|
EXPECT_TRUE(var_dom.CanFreelyDecrease(NegationOf(X)));
|
|
|
|
|
|
|
|
|
|
// We do not include X in these lists, because X++ can always happen.
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Y), UnorderedElementsAre(Z));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Z), UnorderedElementsAre(Y));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(Y)),
|
|
|
|
|
ElementsAre(NegationOf(Z)));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(Z)),
|
|
|
|
|
ElementsAre(NegationOf(Y)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// X + Y <= 0
|
|
|
|
|
// Y + Z <= 0
|
|
|
|
|
TEST(VarDominationTest, BasicExample3) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Z"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
coeffs: [ 1, 1 ]
|
|
|
|
|
domain: [ -9223372036854775808, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1 ]
|
|
|
|
|
domain: [ -9223372036854775808, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
|
|
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
const IntegerVariable Z = VarDomination::RefToIntegerVariable(2);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(var_dom.CanFreelyDecrease(X));
|
|
|
|
|
EXPECT_TRUE(var_dom.CanFreelyDecrease(Y));
|
|
|
|
|
EXPECT_TRUE(var_dom.CanFreelyDecrease(Z));
|
|
|
|
|
EXPECT_FALSE(var_dom.CanFreelyDecrease(NegationOf(X)));
|
|
|
|
|
EXPECT_FALSE(var_dom.CanFreelyDecrease(NegationOf(Y)));
|
|
|
|
|
EXPECT_FALSE(var_dom.CanFreelyDecrease(NegationOf(Z)));
|
|
|
|
|
|
|
|
|
|
// No domination here, because all the dominator can just freely move in
|
|
|
|
|
// one direction.
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(X)), IsEmpty());
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(Y)), IsEmpty());
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(NegationOf(Z)), IsEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// X + Y >= 0
|
|
|
|
|
// Y + Z >= 0
|
|
|
|
|
TEST(VarDominationTest, BasicExample4) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Z"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
coeffs: [ 1, 1 ]
|
|
|
|
|
domain: [ 0, 9223372036854775807 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1 ]
|
|
|
|
|
domain: [ 0, 9223372036854775807 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
|
|
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
const IntegerVariable Z = VarDomination::RefToIntegerVariable(2);
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(var_dom.CanFreelyDecrease(X));
|
|
|
|
|
EXPECT_FALSE(var_dom.CanFreelyDecrease(Y));
|
|
|
|
|
EXPECT_FALSE(var_dom.CanFreelyDecrease(Z));
|
|
|
|
|
EXPECT_TRUE(var_dom.CanFreelyDecrease(NegationOf(X)));
|
|
|
|
|
EXPECT_TRUE(var_dom.CanFreelyDecrease(NegationOf(Y)));
|
|
|
|
|
EXPECT_TRUE(var_dom.CanFreelyDecrease(NegationOf(Z)));
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(X), IsEmpty());
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Y), IsEmpty());
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Z), IsEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// X + Y + Z = 0
|
|
|
|
|
TEST(VarDominationTest, AllEquivalent) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Z"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1, 1 ]
|
|
|
|
|
domain: [ 0, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
|
|
|
|
|
// Domination is slightly related to symmetry and duplicate columns.
|
|
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
const IntegerVariable Z = VarDomination::RefToIntegerVariable(2);
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(X), UnorderedElementsAre(Y, Z));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Y), UnorderedElementsAre(X, Z));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Z), UnorderedElementsAre(X, Y));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// X + Y + Z <= 0 (to prevent freely moving variables).
|
|
|
|
|
// -X + -2Y + -3Z <= 0
|
|
|
|
|
TEST(VarDominationTest, NegativeCoefficients) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables {
|
|
|
|
|
name: "X"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Y"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
variables {
|
|
|
|
|
name: "Z"
|
|
|
|
|
domain: [ -10, 10 ]
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1, 1 ]
|
|
|
|
|
domain: [ -9223372036854775808, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ -1, -2, -3 ]
|
|
|
|
|
domain: [ -9223372036854775808, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
VarDomination var_dom;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDominanceDetection(context, &var_dom);
|
|
|
|
|
|
|
|
|
|
const IntegerVariable X = VarDomination::RefToIntegerVariable(0);
|
|
|
|
|
const IntegerVariable Y = VarDomination::RefToIntegerVariable(1);
|
|
|
|
|
const IntegerVariable Z = VarDomination::RefToIntegerVariable(2);
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(X), UnorderedElementsAre(Y, Z));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Y), UnorderedElementsAre(Z));
|
|
|
|
|
EXPECT_THAT(var_dom.DominatingVariables(Z), IsEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-28 15:34:01 +01:00
|
|
|
TEST(DualBoundReductionTest, FixVariableToDomainBound) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables { domain: [ -10, 10 ] }
|
|
|
|
|
variables { domain: [ -10, 10 ] }
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0 ]
|
|
|
|
|
coeffs: [ 1 ]
|
|
|
|
|
domain: [ -15, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 1 ]
|
|
|
|
|
coeffs: [ 1 ]
|
|
|
|
|
domain: [ 0, 15 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
solution_hint {
|
|
|
|
|
vars: [ 0, 1 ]
|
|
|
|
|
values: [ -5, 5 ]
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
DualBoundStrengthening dual_bound_strengthening;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.LoadSolutionHint();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDualBoundStrengthening(context, &dual_bound_strengthening);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(dual_bound_strengthening.Strengthen(&context));
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[-10]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[10]");
|
2025-01-24 14:10:52 +01:00
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[0], -10);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[1], 10);
|
2024-11-28 15:34:01 +01:00
|
|
|
}
|
|
|
|
|
|
2024-10-03 10:59:28 +02:00
|
|
|
// Bound propagation see nothing, but if we can remove feasible solution, from
|
|
|
|
|
// this constraint point of view, all variables can freely increase or decrease
|
|
|
|
|
// until zero (because the constraint is trivial above/below).
|
|
|
|
|
//
|
|
|
|
|
// -20 <= X + Y + Z <= 20
|
|
|
|
|
TEST(DualBoundReductionTest, BasicTest) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables { domain: [ -10, 10 ] }
|
|
|
|
|
variables { domain: [ -10, 10 ] }
|
|
|
|
|
variables { domain: [ -10, 10 ] }
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1, 1 ]
|
|
|
|
|
domain: [ -20, 20 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-28 15:34:01 +01:00
|
|
|
solution_hint {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
values: [ 3, 4, 5 ]
|
|
|
|
|
}
|
2024-10-03 10:59:28 +02:00
|
|
|
)pb");
|
|
|
|
|
DualBoundStrengthening dual_bound_strengthening;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
2024-11-28 15:34:01 +01:00
|
|
|
context.LoadSolutionHint();
|
2024-10-03 10:59:28 +02:00
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDualBoundStrengthening(context, &dual_bound_strengthening);
|
2024-11-28 15:34:01 +01:00
|
|
|
|
2024-10-03 10:59:28 +02:00
|
|
|
EXPECT_TRUE(dual_bound_strengthening.Strengthen(&context));
|
2024-11-28 15:34:01 +01:00
|
|
|
|
2024-10-03 10:59:28 +02:00
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[0]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[0]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(2).ToString(), "[0]");
|
2025-01-24 14:10:52 +01:00
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[0], 0);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[1], 0);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[2], 0);
|
2024-10-03 10:59:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DualBoundReductionTest, CarefulWithHoles) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables { domain: [ -10, 10 ] }
|
|
|
|
|
variables { domain: [ -10, 0, 7, 10 ] }
|
|
|
|
|
variables { domain: [ -10, -6, 3, 10 ] }
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1, 1 ]
|
|
|
|
|
domain: [ -15, 15 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
DualBoundStrengthening dual_bound_strengthening;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDualBoundStrengthening(context, &dual_bound_strengthening);
|
2024-11-28 15:34:01 +01:00
|
|
|
|
2024-10-03 10:59:28 +02:00
|
|
|
EXPECT_TRUE(dual_bound_strengthening.Strengthen(&context));
|
2024-11-28 15:34:01 +01:00
|
|
|
|
2024-10-03 10:59:28 +02:00
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[-5,5]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[-5,0][7]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(2).ToString(), "[-6][3,5]");
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-28 15:34:01 +01:00
|
|
|
// Here the inferred bounds crosses, so we have multiple choices, we will fix
|
2024-10-03 10:59:28 +02:00
|
|
|
// to the lowest magnitude.
|
|
|
|
|
TEST(DualBoundReductionTest, Choices) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables { domain: [ -10, 10 ] }
|
|
|
|
|
variables { domain: [ -10, -2, 3, 10 ] }
|
|
|
|
|
variables { domain: [ -10, -3, 2, 10 ] }
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
coeffs: [ 1, 1, 1 ]
|
|
|
|
|
domain: [ -25, 25 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-28 15:34:01 +01:00
|
|
|
solution_hint {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
values: [ 3, 4, 5 ]
|
|
|
|
|
}
|
2024-10-03 10:59:28 +02:00
|
|
|
)pb");
|
|
|
|
|
DualBoundStrengthening dual_bound_strengthening;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
2024-11-28 15:34:01 +01:00
|
|
|
context.LoadSolutionHint();
|
2024-10-03 10:59:28 +02:00
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDualBoundStrengthening(context, &dual_bound_strengthening);
|
2024-11-28 15:34:01 +01:00
|
|
|
|
2024-10-03 10:59:28 +02:00
|
|
|
EXPECT_TRUE(dual_bound_strengthening.Strengthen(&context));
|
2024-11-28 15:34:01 +01:00
|
|
|
|
2024-10-03 10:59:28 +02:00
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[0]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[-2]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(2).ToString(), "[2]");
|
2025-01-24 14:10:52 +01:00
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[0], 0);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[1], -2);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[2], 2);
|
2024-11-28 15:34:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DualBoundReductionTest, AddImplication) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables { domain: [ 0, 1 ] }
|
|
|
|
|
variables { domain: [ 0, 1 ] }
|
|
|
|
|
variables { domain: [ 0, 1 ] }
|
|
|
|
|
constraints {
|
|
|
|
|
# a => (b == c)
|
|
|
|
|
enforcement_literal: 0
|
|
|
|
|
linear {
|
|
|
|
|
vars: [ 1, 2 ]
|
|
|
|
|
coeffs: [ -1, 1 ]
|
|
|
|
|
domain: [ 0, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
solution_hint {
|
|
|
|
|
vars: [ 0, 1, 1 ]
|
|
|
|
|
values: [ 0, 1, 1 ]
|
|
|
|
|
}
|
|
|
|
|
)pb");
|
|
|
|
|
DualBoundStrengthening dual_bound_strengthening;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
|
|
|
|
context.LoadSolutionHint();
|
|
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDualBoundStrengthening(context, &dual_bound_strengthening);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(dual_bound_strengthening.Strengthen(&context));
|
|
|
|
|
|
|
|
|
|
// not(a) => not(b) and not(a) => not(c) should be added.
|
|
|
|
|
ASSERT_EQ(context.working_model->constraints_size(), 3);
|
|
|
|
|
const ConstraintProto expected_constraint_proto1 =
|
2024-12-16 14:20:09 +01:00
|
|
|
ParseTestProto(R"pb(enforcement_literal: -1
|
|
|
|
|
bool_and { literals: -2 })pb");
|
2024-11-28 15:34:01 +01:00
|
|
|
EXPECT_THAT(context.working_model->constraints(1),
|
|
|
|
|
EqualsProto(expected_constraint_proto1));
|
|
|
|
|
const ConstraintProto expected_constraint_proto2 =
|
2024-12-16 14:20:09 +01:00
|
|
|
ParseTestProto(R"pb(enforcement_literal: -1
|
|
|
|
|
bool_and { literals: -3 })pb");
|
2024-11-28 15:34:01 +01:00
|
|
|
EXPECT_THAT(context.working_model->constraints(2),
|
|
|
|
|
EqualsProto(expected_constraint_proto2));
|
|
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[0]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[0,1]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(2).ToString(), "[0,1]");
|
2025-01-24 14:10:52 +01:00
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[0], 0);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[1], 0);
|
|
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[2], 0);
|
2024-10-03 10:59:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DualBoundReductionTest, EquivalenceDetection) {
|
|
|
|
|
CpModelProto model_proto = ParseTestProto(R"pb(
|
|
|
|
|
variables { domain: [ 0, 1 ] }
|
|
|
|
|
variables { domain: [ 0, 1 ] }
|
|
|
|
|
variables { domain: [ 0, 1 ] }
|
|
|
|
|
constraints {
|
|
|
|
|
# a => b
|
|
|
|
|
enforcement_literal: 0
|
|
|
|
|
bool_and { literals: [ 1 ] }
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
linear {
|
|
|
|
|
# b == c (we just want b and c not to freely vary)
|
|
|
|
|
vars: [ 1, 2 ]
|
|
|
|
|
coeffs: [ 1, -1 ]
|
|
|
|
|
domain: [ 0, 0 ]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constraints {
|
|
|
|
|
bool_or {
|
|
|
|
|
literals: [ 0, 2 ] # a + c >= 0
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-28 15:34:01 +01:00
|
|
|
solution_hint {
|
|
|
|
|
vars: [ 0, 1, 2 ]
|
|
|
|
|
values: [ 0, 1, 1 ]
|
|
|
|
|
}
|
2024-10-03 10:59:28 +02:00
|
|
|
)pb");
|
|
|
|
|
DualBoundStrengthening dual_bound_strengthening;
|
|
|
|
|
Model model;
|
|
|
|
|
PresolveContext context(&model, &model_proto, nullptr);
|
|
|
|
|
context.InitializeNewDomains();
|
2024-11-28 15:34:01 +01:00
|
|
|
context.LoadSolutionHint();
|
2024-10-03 10:59:28 +02:00
|
|
|
context.ReadObjectiveFromProto();
|
|
|
|
|
ScanModelForDualBoundStrengthening(context, &dual_bound_strengthening);
|
2024-11-28 15:34:01 +01:00
|
|
|
|
2024-10-03 10:59:28 +02:00
|
|
|
EXPECT_TRUE(dual_bound_strengthening.Strengthen(&context));
|
2024-11-28 15:34:01 +01:00
|
|
|
|
2024-10-03 10:59:28 +02:00
|
|
|
EXPECT_EQ(context.DomainOf(0).ToString(), "[0,1]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(1).ToString(), "[0,1]");
|
|
|
|
|
EXPECT_EQ(context.DomainOf(2).ToString(), "[0,1]");
|
|
|
|
|
// Equivalence between a and b.
|
|
|
|
|
EXPECT_EQ(context.GetLiteralRepresentative(1), 0);
|
2025-01-24 14:10:52 +01:00
|
|
|
EXPECT_EQ(context.solution_crush().GetVarValues()[0], 1);
|
2024-10-03 10:59:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
} // namespace sat
|
|
|
|
|
} // namespace operations_research
|