Files
ortools-clone/ortools/sat/presolve_context_test.cc

1176 lines
39 KiB
C++
Raw Normal View History

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/presolve_context.h"
#include <cstdint>
#include "absl/container/flat_hash_set.h"
#include "absl/types/span.h"
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/base/parse_test_proto.h"
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/cp_model_utils.h"
#include "ortools/sat/model.h"
#include "ortools/sat/solution_crush.h"
2024-10-03 10:59:28 +02:00
#include "ortools/util/affine_relation.h"
#include "ortools/util/sorted_interval_list.h"
namespace operations_research {
namespace sat {
namespace {
using ::google::protobuf::contrib::parse_proto::ParseTestProto;
TEST(PresolveContextTest, GetOrCreateEncodingOnIntVar) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
context.NewIntVar({1, 5});
EXPECT_EQ(1, context.GetOrCreateVarValueEncoding(0, 2));
EXPECT_EQ(2, context.GetOrCreateVarValueEncoding(0, 4));
EXPECT_EQ(1, context.GetOrCreateVarValueEncoding(0, 2));
EXPECT_EQ(1, context.GetOrCreateVarValueEncoding(-1, -2));
}
TEST(PresolveContextTest, GetOrCreateEncodingOnBoolVar) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
context.NewBoolVar("test");
EXPECT_EQ(0, context.GetOrCreateVarValueEncoding(0, 1));
EXPECT_EQ(-1, context.GetOrCreateVarValueEncoding(0, 0));
}
TEST(PresolveContextTest, GetOrCreateEncodingOnSize2Var) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
context.NewIntVar(Domain::FromValues({1, 4}));
EXPECT_EQ(-2, context.GetOrCreateVarValueEncoding(0, 1));
EXPECT_EQ(1, context.GetOrCreateVarValueEncoding(0, 4));
}
TEST(PresolveContextTest, GetOrCreateEncodingOnSize2VarBis) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
context.NewIntVar(Domain::FromValues({1, 4}));
EXPECT_EQ(1, context.GetOrCreateVarValueEncoding(0, 4));
EXPECT_EQ(-2, context.GetOrCreateVarValueEncoding(0, 1));
}
TEST(PresolveContextTest, InsertVarValueEncodingOnIntVar) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
context.NewIntVar({1, 5});
context.NewBoolVar("test");
context.InsertVarValueEncoding(1, 0, 2);
EXPECT_EQ(1, context.GetOrCreateVarValueEncoding(0, 2));
EXPECT_EQ(1, context.GetOrCreateVarValueEncoding(-1, -2));
}
TEST(PresolveContextTest, InsertVarValueEncodingOnSize2Var) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
context.NewIntVar(Domain::FromValues({1, 4}));
context.NewBoolVar("test");
context.InsertVarValueEncoding(1, 0, 1);
EXPECT_EQ(1, context.GetOrCreateVarValueEncoding(0, 1));
EXPECT_EQ(-2, context.GetOrCreateVarValueEncoding(0, 4));
}
TEST(PresolveContextTest, InsertVarValueEncodingOnSize2VarBis) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
context.NewIntVar(Domain::FromValues({1, 4}));
context.NewBoolVar("test");
context.InsertVarValueEncoding(1, 0, 4);
EXPECT_EQ(1, context.GetOrCreateVarValueEncoding(0, 4));
EXPECT_EQ(-2, context.GetOrCreateVarValueEncoding(0, 1));
}
TEST(PresolveContextTest, InsertVarValueEncodingOnPosLitMinLit) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
const int a = context.NewBoolVar("test");
const int b = context.NewBoolVar("test");
context.InsertVarValueEncoding(a, b, 0);
EXPECT_EQ(context.GetLiteralRepresentative(b), NegatedRef(a));
EXPECT_TRUE(context.VarToConstraints(a).contains(kAffineRelationConstraint));
EXPECT_TRUE(context.VarToConstraints(b).contains(kAffineRelationConstraint));
}
TEST(PresolveContextTest, InsertVarValueEncodingOnPosLitPosMaxLit) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
const int a = context.NewBoolVar("test");
const int b = context.NewBoolVar("test");
context.InsertVarValueEncoding(a, b, 1);
EXPECT_EQ(context.GetLiteralRepresentative(b), a);
EXPECT_TRUE(context.VarToConstraints(a).contains(kAffineRelationConstraint));
EXPECT_TRUE(context.VarToConstraints(b).contains(kAffineRelationConstraint));
}
TEST(PresolveContextTest, InsertVarValueEncodingOnNegLitMinLit) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
const int a = context.NewBoolVar("test");
const int b = context.NewBoolVar("test");
context.InsertVarValueEncoding(NegatedRef(a), b, 0);
EXPECT_EQ(context.GetLiteralRepresentative(b), a);
EXPECT_TRUE(context.VarToConstraints(a).contains(kAffineRelationConstraint));
EXPECT_TRUE(context.VarToConstraints(b).contains(kAffineRelationConstraint));
}
TEST(PresolveContextTest, InsertVarValueEncodingOnNegLitMaxLit) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
const int a = context.NewBoolVar("test");
const int b = context.NewBoolVar("test");
context.InsertVarValueEncoding(NegatedRef(a), b, 1);
EXPECT_EQ(context.GetLiteralRepresentative(b), NegatedRef(a));
EXPECT_TRUE(context.VarToConstraints(a).contains(kAffineRelationConstraint));
EXPECT_TRUE(context.VarToConstraints(b).contains(kAffineRelationConstraint));
}
TEST(PresolveContextTest, InsertVarValueEncodingOnPosLitMinVar) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
const int a = context.NewBoolVar("test");
const int b = context.NewIntVar(Domain::FromValues({2, 5}));
context.InsertVarValueEncoding(a, b, 2);
EXPECT_EQ(context.GetAffineRelation(b).representative, a);
EXPECT_EQ(context.GetAffineRelation(b).coeff, -3);
EXPECT_EQ(context.GetAffineRelation(b).offset, 5);
EXPECT_TRUE(context.VarToConstraints(a).contains(kAffineRelationConstraint));
EXPECT_TRUE(context.VarToConstraints(b).contains(kAffineRelationConstraint));
}
TEST(PresolveContextTest, InsertVarValueEncodingOnPosLitMaxVar) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
const int a = context.NewBoolVar("test");
const int b = context.NewIntVar(Domain::FromValues({2, 5}));
context.InsertVarValueEncoding(a, b, 5);
EXPECT_EQ(context.GetAffineRelation(b).representative, a);
EXPECT_EQ(context.GetAffineRelation(b).coeff, 3);
EXPECT_EQ(context.GetAffineRelation(b).offset, 2);
EXPECT_TRUE(context.VarToConstraints(a).contains(kAffineRelationConstraint));
EXPECT_TRUE(context.VarToConstraints(b).contains(kAffineRelationConstraint));
}
TEST(PresolveContextTest, InsertVarValueEncodingOnNegLitMinVar) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
const int a = context.NewBoolVar("test");
const int b = context.NewIntVar(Domain::FromValues({2, 5}));
context.InsertVarValueEncoding(NegatedRef(a), b, 2);
EXPECT_EQ(context.GetAffineRelation(b).representative, a);
EXPECT_EQ(context.GetAffineRelation(b).coeff, 3);
EXPECT_EQ(context.GetAffineRelation(b).offset, 2);
EXPECT_TRUE(context.VarToConstraints(a).contains(kAffineRelationConstraint));
EXPECT_TRUE(context.VarToConstraints(b).contains(kAffineRelationConstraint));
}
TEST(PresolveContextTest, InsertVarValueEncodingOnNegLitMaxVar) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
const int a = context.NewBoolVar("test");
const int b = context.NewIntVar(Domain::FromValues({2, 5}));
context.InsertVarValueEncoding(NegatedRef(a), b, 5);
EXPECT_EQ(context.GetAffineRelation(b).representative, a);
EXPECT_EQ(context.GetAffineRelation(b).coeff, -3);
EXPECT_EQ(context.GetAffineRelation(b).offset, 5);
EXPECT_TRUE(context.VarToConstraints(a).contains(kAffineRelationConstraint));
EXPECT_TRUE(context.VarToConstraints(b).contains(kAffineRelationConstraint));
}
TEST(PresolveContextTest, DomainContainsExpr) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
const int var = context.NewIntVar({1, 5});
LinearExpressionProto expr;
expr.add_vars(var);
expr.add_coeffs(3);
expr.set_offset(2);
EXPECT_FALSE(context.DomainContains(expr, 2));
EXPECT_FALSE(context.DomainContains(expr, 7));
EXPECT_TRUE(context.DomainContains(expr, 11));
LinearExpressionProto fixed;
fixed.set_offset(-1);
EXPECT_FALSE(context.DomainContains(fixed, 2));
EXPECT_TRUE(context.DomainContains(fixed, -1));
LinearExpressionProto coeff0;
coeff0.add_vars(var);
coeff0.add_coeffs(0);
coeff0.set_offset(5);
EXPECT_FALSE(context.DomainContains(coeff0, 2));
EXPECT_TRUE(context.DomainContains(coeff0, 5));
}
TEST(PresolveContextTest, GetOrCreateEncodingOnAffine) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
const int var = context.NewIntVar({1, 5});
LinearExpressionProto expr;
expr.add_vars(var);
expr.add_coeffs(3);
expr.set_offset(2);
const int zero = context.GetFalseLiteral();
const int one = context.GetTrueLiteral();
EXPECT_EQ(zero, context.GetOrCreateAffineValueEncoding(expr, 2));
EXPECT_EQ(zero, context.GetOrCreateAffineValueEncoding(expr, 7));
EXPECT_EQ(context.GetOrCreateAffineValueEncoding(expr, 11),
context.GetOrCreateVarValueEncoding(var, 3));
LinearExpressionProto fixed;
fixed.set_offset(-1);
EXPECT_EQ(zero, context.GetOrCreateAffineValueEncoding(fixed, 2));
EXPECT_EQ(one, context.GetOrCreateAffineValueEncoding(fixed, -1));
}
TEST(PresolveContextTest, LinearExpressionMinMax) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
context.NewIntVar(Domain(0, 1));
context.NewIntVar(Domain(0, 1));
const LinearExpressionProto expr = ParseTestProto(R"pb(
vars: [ 0, 1 ]
coeffs: [ 2, -3 ]
offset: 5
)pb");
EXPECT_EQ(2, context.MinOf(expr));
EXPECT_EQ(7, context.MaxOf(expr));
}
TEST(PresolveContextTest, ObjectiveReadCanonicalizeWrite) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 8 ] }
variables { domain: [ 3, 3 ] }
variables { domain: [ -2, 7 ] }
variables { domain: [ -2, -2 ] }
variables { domain: [ -4, 11 ] }
objective {
vars: [ 0, 4, 2, 3, 1 ]
coeffs: [ 2, 4, -2, -4, -2 ]
domain: [ 0, 1000 ]
offset: 3
}
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.ReadObjectiveFromProto();
EXPECT_TRUE(context.CanonicalizeObjective());
context.WriteObjectiveToProto();
const CpModelProto expected = ParseTestProto(R"pb(
variables { domain: [ 0, 8 ] }
variables { domain: [ 3, 3 ] }
variables { domain: [ -2, 7 ] }
variables { domain: [ -2, -2 ] }
variables { domain: [ -4, 11 ] }
objective {
vars: [ 0, 2, 4 ]
coeffs: [ 1, -1, 2 ]
domain: [ -1, 32 ]
offset: 2.5
scaling_factor: 2
integer_before_offset: 1
integer_scaling_factor: 2
}
)pb");
EXPECT_THAT(working_model, testing::EqualsProto(expected));
}
TEST(PresolveContextTest, ExploitAtMostOneInObjective) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
objective {
vars: [ 0, 1, 2, 3 ]
coeffs: [ 2, 3, 7, 4 ]
}
constraints { bool_or { literals: [ 0, 1, 2, 3 ] } }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.ReadObjectiveFromProto();
EXPECT_TRUE(context.CanonicalizeObjective());
// Do not crash if called with empty exactly one. The problem should be UNSAT
// in this case, but we might call this before reporting it.
EXPECT_FALSE(context.ExploitExactlyOneInObjective({}));
EXPECT_TRUE(context.ExploitExactlyOneInObjective({0, 1, 2}));
EXPECT_TRUE(context.CanonicalizeObjective());
context.WriteObjectiveToProto();
const CpModelProto expected = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
objective {
vars: [ 1, 2, 3 ]
coeffs: [ 1, 5, 4 ]
domain: [ 0, 10 ]
offset: 2
scaling_factor: 1
integer_before_offset: 2
}
constraints { bool_or { literals: [ 0, 1, 2, 3 ] } }
)pb");
EXPECT_THAT(working_model, testing::EqualsProto(expected));
}
TEST(PresolveContextTest, ExploitAtMostOneInObjectiveNegatedRef) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
constraints { bool_or { literals: [ 0, 1, 2, 3 ] } }
objective {
vars: [ 0, 1, 2, 3 ]
coeffs: [ 2, 3, 7, 4 ]
}
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.ReadObjectiveFromProto();
EXPECT_TRUE(context.CanonicalizeObjective());
EXPECT_TRUE(context.ExploitExactlyOneInObjective({0, NegatedRef(1), 2}));
EXPECT_TRUE(context.CanonicalizeObjective());
context.WriteObjectiveToProto();
// The objective is 2X + 3(1 - Y) + 7Z with X + Y + Z = 1
// So we get 3 + 2 X - 3 Y + 7 Z and when shifted by -3, we get 5X + 10Z.
const CpModelProto expected = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
constraints { bool_or { literals: [ 0, 1, 2, 3 ] } }
objective {
vars: [ 0, 2, 3 ]
coeffs: [ 5, 10, 4 ]
domain: [ 0, 15 ] # We get 15 because 16 is not reachable.
scaling_factor: 1
}
)pb");
EXPECT_THAT(working_model, testing::EqualsProto(expected));
}
TEST(PresolveContextTest, ObjectiveSubstitution) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
objective {
vars: [ 0 ]
coeffs: [ 1 ]
domain: [ 0, 1000 ]
offset: 3
}
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.ReadObjectiveFromProto();
EXPECT_TRUE(context.CanonicalizeObjective());
const ConstraintProto constraint = ParseTestProto(R"pb(
linear {
vars: [ 0, 1, 2 ]
coeffs: [ -1, 1, 1 ]
domain: [ 6, 6 ]
}
)pb");
EXPECT_TRUE(context.SubstituteVariableInObjective(0, -1, constraint));
context.WriteObjectiveToProto();
const CpModelProto expected = ParseTestProto(R"pb(
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
objective {
vars: [ 1, 2 ]
coeffs: [ 1, 1 ]
domain: [ 6, 15 ] # [0, 9] initially, + 6 offset.
offset: -3
integer_before_offset: -6
scaling_factor: 1
}
)pb");
EXPECT_THAT(working_model, testing::EqualsProto(expected));
}
TEST(PresolveContextTest, ObjectiveSubstitutionWithLargeCoeff) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
objective {
vars: [ 0 ]
coeffs: [ 4 ]
domain: [ 0, 1000 ]
offset: 3
}
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.ReadObjectiveFromProto();
const ConstraintProto constraint = ParseTestProto(R"pb(
linear {
vars: [ 0, 1, 2 ]
coeffs: [ -2, 1, 1 ]
domain: [ 6, 6 ]
}
)pb");
EXPECT_TRUE(context.SubstituteVariableInObjective(0, -2, constraint));
context.WriteObjectiveToProto();
const CpModelProto expected = ParseTestProto(R"pb(
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
objective {
vars: [ 1, 2 ]
coeffs: [ 2, 2 ]
domain: [ 12, 36 ] # [0, 1000] initially, + 2*6 offset.
2024-10-03 10:59:28 +02:00
offset: -9
integer_before_offset: -12
scaling_factor: 1
}
)pb");
EXPECT_THAT(working_model, testing::EqualsProto(expected));
}
TEST(PresolveContextTest, VarValueEncoding) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 9 ] }
variables { domain: [ 0, 9 ] }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
EXPECT_TRUE(context.StoreLiteralImpliesVarEqValue(0, 2, 4));
EXPECT_FALSE(context.StoreLiteralImpliesVarEqValue(0, 2, 4));
EXPECT_FALSE(context.HasVarValueEncoding(2, 4));
2025-11-05 13:55:12 +01:00
EXPECT_TRUE(context.StoreLiteralImpliesVarNeValue(-1, 2, 4));
EXPECT_FALSE(context.StoreLiteralImpliesVarNeValue(-1, 2, 4));
2024-10-03 10:59:28 +02:00
EXPECT_TRUE(context.HasVarValueEncoding(2, 4));
2025-11-05 13:55:12 +01:00
EXPECT_TRUE(context.StoreLiteralImpliesVarNeValue(0, 1, 4));
EXPECT_FALSE(context.StoreLiteralImpliesVarNeValue(0, 1, 4));
2024-10-03 10:59:28 +02:00
EXPECT_FALSE(context.HasVarValueEncoding(1, 4));
EXPECT_TRUE(context.StoreLiteralImpliesVarEqValue(-1, 1, 4));
EXPECT_FALSE(context.StoreLiteralImpliesVarEqValue(-1, 1, 4));
EXPECT_TRUE(context.HasVarValueEncoding(1, 4));
}
TEST(PresolveContextTest, DetectVarEqValueHalfEncoding) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 9 ] }
constraints {
enforcement_literal: 1
linear {
vars: [ 2 ]
coeffs: [ 1 ]
domain: [ 6, 6 ]
}
}
constraints {
enforcement_literal: -2
linear {
vars: [ 2 ]
coeffs: [ 1 ]
domain: [ 0, 5, 7, 9 ]
}
}
)pb");
const int kLiteral = 1;
const int kVar = 2;
const int64_t kValue = 6;
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.StoreLiteralImpliesVarEqValue(kLiteral, kVar, kValue);
2025-11-05 13:55:12 +01:00
context.StoreLiteralImpliesVarNeValue(NegatedRef(kLiteral), kVar, kValue);
2024-10-03 10:59:28 +02:00
int encoding_literal = 0;
EXPECT_TRUE(context.HasVarValueEncoding(kVar, kValue, &encoding_literal));
EXPECT_EQ(encoding_literal, kLiteral);
}
TEST(PresolveContextTest, GetLiteralRepresentative) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
EXPECT_NE(context.GetLiteralRepresentative(0),
context.GetLiteralRepresentative(1));
EXPECT_NE(context.GetLiteralRepresentative(0),
context.GetLiteralRepresentative(2));
EXPECT_NE(context.GetLiteralRepresentative(1),
context.GetLiteralRepresentative(2));
EXPECT_NE(context.GetLiteralRepresentative(-1),
context.GetLiteralRepresentative(-2));
EXPECT_NE(context.GetLiteralRepresentative(-1),
context.GetLiteralRepresentative(-3));
EXPECT_NE(context.GetLiteralRepresentative(-2),
context.GetLiteralRepresentative(-3));
EXPECT_TRUE(context.StoreBooleanEqualityRelation(0, 1));
2024-10-03 10:59:28 +02:00
EXPECT_EQ(context.GetLiteralRepresentative(0),
context.GetLiteralRepresentative(1));
EXPECT_NE(context.GetLiteralRepresentative(0),
context.GetLiteralRepresentative(2));
EXPECT_NE(context.GetLiteralRepresentative(1),
context.GetLiteralRepresentative(2));
EXPECT_EQ(context.GetLiteralRepresentative(-1),
context.GetLiteralRepresentative(-2));
EXPECT_NE(context.GetLiteralRepresentative(-1),
context.GetLiteralRepresentative(-3));
EXPECT_NE(context.GetLiteralRepresentative(-2),
context.GetLiteralRepresentative(-3));
EXPECT_TRUE(context.StoreBooleanEqualityRelation(0, -3));
2024-10-03 10:59:28 +02:00
EXPECT_EQ(context.GetLiteralRepresentative(0),
context.GetLiteralRepresentative(1));
EXPECT_EQ(context.GetLiteralRepresentative(0),
context.GetLiteralRepresentative(-3));
EXPECT_EQ(context.GetLiteralRepresentative(1),
context.GetLiteralRepresentative(-3));
EXPECT_EQ(context.GetLiteralRepresentative(-1),
context.GetLiteralRepresentative(-2));
EXPECT_EQ(context.GetLiteralRepresentative(-1),
context.GetLiteralRepresentative(2));
EXPECT_EQ(context.GetLiteralRepresentative(-2),
context.GetLiteralRepresentative(2));
}
TEST(PresolveContextTest, VarIsOnlyUsedInEncoding) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
constraints {
enforcement_literal: 0
linear {
vars: [ 2 ]
coeffs: [ 1 ]
domain: [ 3, 15 ]
}
}
constraints {
enforcement_literal: 1
linear {
vars: [ 2 ]
coeffs: [ 1 ]
domain: [ 7, 8 ]
}
}
constraints {
linear {
vars: [ 3, 4 ]
coeffs: [ 1, 1 ]
domain: [ 5, 5 ]
}
}
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.UpdateNewConstraintsVariableUsage();
EXPECT_FALSE(context.VariableIsOnlyUsedInEncodingAndMaybeInObjective(0));
EXPECT_FALSE(context.VariableIsOnlyUsedInEncodingAndMaybeInObjective(1));
EXPECT_TRUE(context.VariableIsOnlyUsedInEncodingAndMaybeInObjective(2));
EXPECT_FALSE(context.VariableIsOnlyUsedInEncodingAndMaybeInObjective(3));
EXPECT_FALSE(context.VariableIsOnlyUsedInEncodingAndMaybeInObjective(4));
}
TEST(PresolveContextTest, ReifiedConstraintCache) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
solution_hint {
vars: [ 0, 1, 2, 3 ]
values: [ 1, 1, 5, 7 ]
}
2024-10-03 10:59:28 +02:00
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.UpdateNewConstraintsVariableUsage();
context.LoadSolutionHint();
2024-10-03 10:59:28 +02:00
LinearExpressionProto expr1;
expr1.add_vars(2);
expr1.add_coeffs(1);
LinearExpressionProto expr2;
expr2.add_vars(3);
expr2.add_coeffs(1);
const int var2_before_var3 =
context.GetOrCreateReifiedPrecedenceLiteral(expr1, expr2, 0, 1);
EXPECT_EQ(var2_before_var3,
context.GetOrCreateReifiedPrecedenceLiteral(expr1, expr2, 0, 1));
EXPECT_EQ(var2_before_var3,
context.GetOrCreateReifiedPrecedenceLiteral(expr1, expr2, 1, 0));
EXPECT_NE(var2_before_var3,
context.GetOrCreateReifiedPrecedenceLiteral(expr2, expr1, 1, 0));
ConstraintProto bool_or = ParseTestProto(R"pb(
bool_or { literals: [ 5, 4, -2, -1 ] })pb");
// 2 x (2 implications , 2 enforced linear) + bool_or.
ASSERT_EQ(9, working_model.constraints_size());
EXPECT_THAT(working_model.constraints(8), ::testing::EqualsProto(bool_or));
EXPECT_TRUE(context.DebugTestHintFeasibility());
2024-10-03 10:59:28 +02:00
}
TEST(PresolveContextTest, ExploitFixedDomainOverflow) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: 0 domain: 0 }
variables { domain: 34359738368 domain: 34359738368 }
constraints { dummy_constraint { vars: 0 vars: 1 } }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.UpdateNewConstraintsVariableUsage();
}
TEST(PresolveContextTest, IntersectDomainWithConstant) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
LinearExpressionProto constant;
constant.set_offset(3);
EXPECT_TRUE(context.IntersectDomainWith(constant, Domain(2, 3)));
EXPECT_FALSE(context.IntersectDomainWith(constant, Domain(2, 2)));
}
// Most of the logic is already tested by the Domain() manipulation function,
// we just test a simple case here.
2024-10-03 10:59:28 +02:00
TEST(PresolveContextTest, IntersectDomainWithAffineExpression) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: 0 domain: 5 }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
// -2 x + 3 in [2, 3] so -2x in [-1, 0] and x must be in [0, 1].
LinearExpressionProto expr;
expr.add_vars(0);
expr.add_coeffs(-1);
expr.set_offset(3);
EXPECT_TRUE(context.IntersectDomainWith(expr, Domain(2, 3)));
EXPECT_EQ(context.DomainOf(0), Domain(0, 1));
}
TEST(PresolveContextTest, IntersectDomainWithUpdatesHint) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
solution_hint {
vars: [ 0 ]
values: [ 3 ]
}
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.LoadSolutionHint();
EXPECT_TRUE(context.IntersectDomainWith(0, Domain(5, 20)));
EXPECT_EQ(context.DomainOf(0), Domain(5, 10));
EXPECT_EQ(context.solution_crush().GetVarValues()[0], 5);
}
2024-10-03 10:59:28 +02:00
TEST(PresolveContextTest, DomainSuperSetOf) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: 0 domain: 1000 }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
const LinearExpressionProto expr1 =
ParseTestProto(R"pb(vars: 0 coeffs: 1 offset: 4)pb");
EXPECT_EQ(context.DomainSuperSetOf(expr1), Domain(4, 1004));
const LinearExpressionProto expr2 =
ParseTestProto(R"pb(vars: 0 coeffs: 2 offset: 4)pb");
EXPECT_EQ(context.DomainSuperSetOf(expr2), Domain(4, 2004));
}
TEST(PresolveContextTest, DomainSuperSetOfDiscrete) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: 0 domain: 1 }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
const LinearExpressionProto expr1 =
ParseTestProto(R"pb(vars: 0 coeffs: -2 offset: 4)pb");
EXPECT_EQ(context.DomainSuperSetOf(expr1), Domain::FromValues({2, 4}));
}
TEST(PresolveContextTest, AddAffineRelation) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 1000 ] }
variables { domain: [ 0, 1000 ] }
variables { domain: [ 0, 1000 ] }
variables { domain: [ 0, 1000 ] }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.UpdateNewConstraintsVariableUsage();
EXPECT_TRUE(context.StoreAffineRelation(0, 1, 3, 0)); // x0 = 3x1
EXPECT_TRUE(context.StoreAffineRelation(2, 3, 5, 0)); // x2 = 5x3
EXPECT_TRUE(context.StoreAffineRelation(0, 2, 2, 0)); // x0 = 2x2 !
// A new variable is created: x4 !
// x0 = 2x2 get expanded into 3x1 = 10 x3, so x1 is a multiple of 10.
EXPECT_EQ(context.GetAffineRelation(1).representative, 4);
EXPECT_EQ(context.GetAffineRelation(1).coeff, 10);
EXPECT_EQ(context.DomainOf(4).ToString(), "[0,33]");
// x0 = 3x1 multiple of 30.
EXPECT_EQ(context.GetAffineRelation(0).representative, 4);
EXPECT_EQ(context.GetAffineRelation(0).coeff, 30);
// x3 is a multiple of 3.
EXPECT_EQ(context.GetAffineRelation(3).representative, 4);
EXPECT_EQ(context.GetAffineRelation(3).coeff, 3);
// x2 = 5x3 is a multiple of 15.
EXPECT_EQ(context.GetAffineRelation(2).representative, 4);
EXPECT_EQ(context.GetAffineRelation(2).coeff, 15);
}
TEST(PresolveContextTest, AddAffineRelationWithOffset) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 1000 ] }
variables { domain: [ 0, 1000 ] }
variables { domain: [ 0, 1000 ] }
variables { domain: [ 0, 1000 ] }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.UpdateNewConstraintsVariableUsage();
EXPECT_TRUE(context.StoreAffineRelation(0, 1, 3, 10)); // x0 = 3x1 + 10
EXPECT_TRUE(context.StoreAffineRelation(2, 3, 1, 30)); // x2 = x3 + 30
EXPECT_TRUE(context.StoreAffineRelation(0, 2, 1, 0)); // x0 = x2 !
// x0 = 3x1 + 10
EXPECT_EQ(context.GetAffineRelation(0).representative, 1);
EXPECT_EQ(context.GetAffineRelation(0).coeff, 3);
EXPECT_EQ(context.GetAffineRelation(0).offset, 10);
// x3 = x2 - 30 = 3x1 - 20
EXPECT_EQ(context.GetAffineRelation(3).representative, 1);
EXPECT_EQ(context.GetAffineRelation(3).coeff, 3);
EXPECT_EQ(context.GetAffineRelation(3).offset, -20);
// x2 same as x0
EXPECT_EQ(context.GetAffineRelation(2).representative, 1);
EXPECT_EQ(context.GetAffineRelation(2).coeff, 3);
EXPECT_EQ(context.GetAffineRelation(2).offset, 10);
}
TEST(PresolveContextTest, AddAffineRelationPreventOverflow) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 1000000 ] }
variables { domain: [ 100000001, 100000004 ] }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.UpdateNewConstraintsVariableUsage();
// x0 = 10 x2 - 1e9.
EXPECT_TRUE(context.StoreAffineRelation(0, 1, 10, -1000000000));
// To avoid "future" overflow a new variable is created.
// And everything is expressed using that one.
EXPECT_EQ(context.GetAffineRelation(1).representative, 2);
EXPECT_EQ(context.GetAffineRelation(1).coeff, 1);
EXPECT_EQ(context.GetAffineRelation(1).offset, 100000001);
EXPECT_EQ(context.DomainOf(2).ToString(), "[0,3]");
// And x0 is in term of that one.
EXPECT_EQ(context.GetAffineRelation(0).representative, 2);
EXPECT_EQ(context.GetAffineRelation(0).coeff, 10);
EXPECT_EQ(context.DomainOf(0).ToString(), "[10][20][30][40]");
}
TEST(PresolveContextTest, ObjectiveScalingMinimize) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 20 ] }
variables { domain: [ 10, 30 ] }
floating_point_objective {
vars: [ 0, 1 ],
coeffs: [ 3.5, -1.3333333333 ],
maximize: false,
offset: 1.0
}
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
ASSERT_TRUE(ScaleFloatingPointObjective(context.params(), context.logger(),
&working_model));
2024-10-03 10:59:28 +02:00
ASSERT_TRUE(working_model.has_objective());
ASSERT_FALSE(working_model.has_floating_point_objective());
const CpObjectiveProto& obj = working_model.objective();
EXPECT_EQ(2, obj.vars_size());
EXPECT_FLOAT_EQ(obj.scaling_factor() * obj.coeffs(0), 3.5);
EXPECT_NEAR(obj.scaling_factor() * obj.coeffs(1), -4.0 / 3.0, 1e-5);
EXPECT_FLOAT_EQ(obj.scaling_factor() * obj.offset(), 1.0);
}
TEST(PresolveContextTest, ObjectiveScalingMaximize) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 20 ] }
variables { domain: [ 10, 30 ] }
floating_point_objective {
vars: [ 0, 1 ],
coeffs: [ 3.5, -1.3333333333 ],
maximize: true,
offset: 1.0
}
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
ASSERT_TRUE(ScaleFloatingPointObjective(context.params(), context.logger(),
&working_model));
2024-10-03 10:59:28 +02:00
ASSERT_TRUE(working_model.has_objective());
ASSERT_FALSE(working_model.has_floating_point_objective());
const CpObjectiveProto& obj = working_model.objective();
EXPECT_EQ(2, obj.vars_size());
EXPECT_FLOAT_EQ(obj.scaling_factor() * obj.coeffs(0), 3.5);
EXPECT_NEAR(obj.scaling_factor() * obj.coeffs(1), -4.0 / 3.0, 1e-5);
EXPECT_FLOAT_EQ(obj.scaling_factor() * obj.offset(), 1.0);
}
TEST(ExpressionIsALiteralTest, BasicApi) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 5 ] }
variables { domain: [ 0, 1 ] }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.UpdateNewConstraintsVariableUsage();
int ref;
const LinearExpressionProto expr1 = ParseTestProto(R"pb(
vars: 0 coeffs: 1
)pb");
EXPECT_FALSE(context.ExpressionIsALiteral(expr1));
const LinearExpressionProto expr2 = ParseTestProto(R"pb(
vars: 1 coeffs: 1
)pb");
EXPECT_TRUE(context.ExpressionIsALiteral(expr2, &ref));
EXPECT_EQ(1, ref);
const LinearExpressionProto expr3 =
ParseTestProto(R"pb(
vars: 1 coeffs: -1 offset: 1
)pb");
EXPECT_TRUE(context.ExpressionIsALiteral(expr3, &ref));
EXPECT_EQ(-2, ref);
const LinearExpressionProto expr4 =
ParseTestProto(R"pb(
vars: 1 coeffs: -1 offset: 2
)pb");
EXPECT_FALSE(context.ExpressionIsALiteral(expr4));
const LinearExpressionProto expr5 =
ParseTestProto(R"pb(
vars: -2 coeffs: 1 offset: 1
)pb");
EXPECT_TRUE(context.ExpressionIsALiteral(expr5, &ref));
EXPECT_EQ(-2, ref);
}
TEST(PresolveContextTest, CanonicalizeAffineVariable) {
Model model;
CpModelProto working_model;
PresolveContext context(&model, &working_model, nullptr);
const int x = context.NewIntVar(Domain(0, 15));
// 3 * x + 9 is a multiple of 6.
// This is the same as x + 3 is a multiple of 2.
EXPECT_TRUE(context.CanonicalizeAffineVariable(x, 3, 6, 9));
const AffineRelation::Relation r = context.GetAffineRelation(x);
EXPECT_EQ(r.coeff, 2);
}
TEST(PresolveContextTest, ComputeMinMaxActivity) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 8 ] }
variables { domain: [ 3, 3 ] }
variables { domain: [ -2, 7 ] }
variables { domain: [ -2, -2 ] }
variables { domain: [ -4, 11 ] }
objective {
vars: [ 0, 1, 2, 3, 4 ]
coeffs: [ 2, 4, -2, -4, -2 ]
domain: [ 0, 1000 ]
offset: 3
}
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
const auto [min_activity, max_activity] =
context.ComputeMinMaxActivity(working_model.objective());
EXPECT_EQ(min_activity, 2 * 0 + 4 * 3 - 2 * 7 - 4 * -2 - 2 * 11);
EXPECT_EQ(max_activity, 2 * 8 + 4 * 3 - 2 * -2 - 4 * -2 - 2 * -4);
}
TEST(PresolveContextTest, CanonicalizeLinearConstraint) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 8 ] }
variables { domain: [ 0, 8 ] }
variables { domain: [ 0, 8 ] }
variables { domain: [ 0, 8 ] }
constraints {
linear {
vars: [ 0, 1, 2, 0, 1 ]
coeffs: [ 2, 4, -2, -4, -2 ]
domain: [ 0, 1000 ]
}
}
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.CanonicalizeLinearConstraint(working_model.mutable_constraints(0));
const ConstraintProto expected = ParseTestProto(R"pb(
linear {
vars: [ 0, 1, 2 ]
coeffs: [ -2, 2, -2 ]
domain: [ 0, 16 ]
2024-10-03 10:59:28 +02:00
}
)pb");
EXPECT_THAT(working_model.constraints(0), testing::EqualsProto(expected));
}
TEST(PresolveContextTest, LoadSolutionHint) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 5, 5 ] }
variables { domain: [ 0, 1 ] }
solution_hint {
vars: [ 0, 2 ]
values: [ 12, 0 ]
}
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
context.LoadSolutionHint();
context.solution_crush().StoreSolutionAsHint(working_model);
// All hints should be clamped to their respective domains, and new hints
// should be added for the fixed variables.
PartialVariableAssignment expected_solution_hint =
ParseTestProto(R"pb(vars: [ 0, 1, 2 ]
values: [ 10, 5, 0 ])pb");
EXPECT_THAT(working_model.solution_hint(),
testing::EqualsProto(expected_solution_hint));
}
2025-11-05 13:55:12 +01:00
TEST(PresolveContextTest, IntersectionOfAffineExprsIsNotEmpty_easy) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
LinearExpressionProto expr1;
expr1.add_vars(0);
expr1.add_coeffs(1);
LinearExpressionProto expr2;
expr2.add_vars(1);
expr2.add_coeffs(1);
EXPECT_TRUE(context.IntersectionOfAffineExprsIsNotEmpty(expr1, expr2));
}
TEST(PresolveContextTest, IntersectionOfAffineExprsIsNotEmpty_shifted) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
LinearExpressionProto expr1;
expr1.add_vars(0);
expr1.add_coeffs(2);
LinearExpressionProto expr2;
expr2.add_vars(1);
expr2.add_coeffs(2);
expr2.set_offset(1);
EXPECT_FALSE(context.IntersectionOfAffineExprsIsNotEmpty(expr1, expr2));
}
TEST(PresolveContextTest, IntersectionOfAffineExprsIsNotEmpty_prime_nosol) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 1, 6 ] }
variables { domain: [ 1, 4 ] }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
LinearExpressionProto expr1;
expr1.add_vars(0);
expr1.add_coeffs(5);
LinearExpressionProto expr2;
expr2.add_vars(1);
expr2.add_coeffs(7);
EXPECT_FALSE(context.IntersectionOfAffineExprsIsNotEmpty(expr1, expr2));
}
TEST(PresolveContextTest, IntersectionOfAffineExprsIsNotEmpty_prime_nosol2) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 1, 6 ] }
variables { domain: [ 1, 5 ] }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
LinearExpressionProto expr1;
expr1.add_vars(0);
expr1.add_coeffs(5);
LinearExpressionProto expr2;
expr2.add_vars(1);
expr2.add_coeffs(7);
EXPECT_FALSE(context.IntersectionOfAffineExprsIsNotEmpty(expr1, expr2));
}
TEST(PresolveContextTest, IntersectionOfAffineExprsIsNotEmpty_prime_reach) {
Model model;
CpModelProto working_model = ParseTestProto(R"pb(
variables { domain: [ 1, 7 ] }
variables { domain: [ 1, 5 ] }
)pb");
PresolveContext context(&model, &working_model, nullptr);
context.InitializeNewDomains();
LinearExpressionProto expr1;
expr1.add_vars(0);
expr1.add_coeffs(5);
LinearExpressionProto expr2;
expr2.add_vars(1);
expr2.add_coeffs(7);
EXPECT_TRUE(context.IntersectionOfAffineExprsIsNotEmpty(expr1, expr2));
}
2024-10-03 10:59:28 +02:00
} // namespace
} // namespace sat
} // namespace operations_research