Files
ortools-clone/ortools/sat/go/cpmodel/cp_model_test.go
Corentin Le Molgat 4151254eba sat: backport from main
2025-07-23 15:04:05 +02:00

1799 lines
49 KiB
Go

// Copyright 2010-2025 Google LLC
// 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.
package cpmodel
import (
"errors"
"fmt"
"math"
"sort"
"testing"
log "github.com/golang/glog"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/testing/protocmp"
cmpb "github.com/google/or-tools/ortools/sat/proto/cpmodel"
)
func Example() {
model := NewCpModelBuilder()
x := model.NewIntVar(1, 3)
y := model.NewIntVar(1, 3)
b := model.NewBoolVar()
model.AddLessOrEqual(x, NewConstant(1)).OnlyEnforceIf(b)
model.AddLessOrEqual(y, NewConstant(1)).OnlyEnforceIf(b.Not())
obj := NewLinearExpr().AddSum(x, b.Not()).AddTerm(y, 5)
model.Maximize(obj)
m, err := model.Model()
if err != nil {
log.Fatalf("Building model returned with error %v", err)
}
res, err := SolveCpModel(m)
if err != nil {
log.Fatalf("CP solver returned with unexpected err %v", err)
}
if res.GetStatus() != cmpb.CpSolverStatus_FEASIBLE && res.GetStatus() != cmpb.CpSolverStatus_OPTIMAL {
log.Fatalf("CP solver returned with status %v", res.GetStatus())
}
fmt.Println("Objective:", res.GetObjectiveValue())
fmt.Println("x:", SolutionIntegerValue(res, x))
fmt.Println("y:", SolutionIntegerValue(res, y))
fmt.Println("Int b:", SolutionIntegerValue(res, b))
fmt.Println("Bool b:", SolutionBooleanValue(res, b))
// Output:
// Objective: 16
// x: 1
// y: 3
// Int b: 1
// Bool b: true
}
func TestBoolVar_Not(t *testing.T) {
model := NewCpModelBuilder()
bv1 := model.NewBoolVar().WithName("bv1")
bv2 := bv1.Not()
bv3 := bv2.Not()
want := 1*bv1.Index() - 1
if got := bv2.Index(); got != want {
t.Errorf("Index() = %v, want %v", got, want)
}
want = bv1.Index()
if got := bv3.Index(); got != want {
t.Errorf("Index() = %v, want %v", got, want)
}
}
func TestVar_Name(t *testing.T) {
testCases := []struct {
name string
varName func() string
want string
}{
{
name: "IntVarName",
varName: func() string {
model := NewCpModelBuilder()
iv := model.NewIntVar(0, 10).WithName("iv1")
return iv.Name()
},
want: "iv1",
},
{
name: "BoolVarName",
varName: func() string {
model := NewCpModelBuilder()
bv := model.NewBoolVar().WithName("bv1")
return bv.Name()
},
want: "bv1",
},
{
name: "IntervalVarName",
varName: func() string {
model := NewCpModelBuilder()
iv := model.NewIntVar(0, 10)
interval := model.NewIntervalVar(iv, iv, iv).WithName("interval")
return interval.Name()
},
want: "interval",
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got := test.varName()
if got != test.want {
t.Errorf("test.varName() = %#v, want %#v", got, test.want)
}
})
}
}
func TestVar_BoolVarDomain(t *testing.T) {
model := NewCpModelBuilder()
bv1 := model.NewBoolVar()
got, err := bv1.Domain()
want := NewDomain(0, 1)
if err != nil {
t.Fatalf("Domain() returned with unexpected error %v", err)
}
if diff := cmp.Diff(want, got, cmp.AllowUnexported(Domain{}, ClosedInterval{})); diff != "" {
t.Errorf("Domain() = %v, want %v", got, want)
}
}
func TestVar_IntVarDomain(t *testing.T) {
model := NewCpModelBuilder()
testCases := []struct {
name string
intVar IntVar
want Domain
}{
{
name: "DomainWithSingleInterval",
intVar: model.NewIntVar(-10, 10),
want: NewDomain(-10, 10),
},
{
name: "DomainWithMultipleIntervals",
intVar: model.NewIntVarFromDomain(FromValues([]int64{1, 2, 3, 5, 4, 6, 10, 12, 11, 15, 8})),
want: FromValues([]int64{1, 2, 3, 5, 4, 6, 10, 12, 11, 15, 8}),
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got, err := test.intVar.Domain()
if err != nil {
t.Fatalf("Domain() returned with unexpected error %v", err)
}
if diff := cmp.Diff(test.want, got, cmp.AllowUnexported(Domain{}, ClosedInterval{})); diff != "" {
t.Errorf("Domain() returned with unexpected diff (-want+got):\n%s", diff)
}
})
}
}
func TestVar_IntegerVariableProto(t *testing.T) {
testCases := []struct {
name string
varIndex func(model *Builder) VarIndex
want *cmpb.IntegerVariableProto
}{
{
name: "BoolVar",
varIndex: func(model *Builder) VarIndex {
bv := model.NewBoolVar()
return bv.Index()
},
want: &cmpb.IntegerVariableProto{
Domain: []int64{0, 1},
},
},
{
name: "IntVar",
varIndex: func(model *Builder) VarIndex {
iv := model.NewIntVar(-10, 10)
return iv.Index()
},
want: &cmpb.IntegerVariableProto{
Domain: []int64{-10, 10},
},
},
{
name: "IntVarFromDomain",
varIndex: func(model *Builder) VarIndex {
iv := model.NewIntVarFromDomain(FromValues([]int64{1, 2, 3, 5, 4, 6, 10, 12, 11, 15, 8}))
return iv.Index()
},
want: &cmpb.IntegerVariableProto{
Domain: FromValues([]int64{1, 2, 3, 5, 4, 6, 10, 12, 11, 15, 8}).FlattenedIntervals(),
},
},
{
name: "ConstVar",
varIndex: func(model *Builder) VarIndex {
cv := model.NewConstant(10)
return cv.Index()
},
want: &cmpb.IntegerVariableProto{
Domain: []int64{10, 10},
},
},
{
name: "TrueVar",
varIndex: func(model *Builder) VarIndex {
tv := model.TrueVar()
return tv.Index()
},
want: &cmpb.IntegerVariableProto{
Domain: []int64{1, 1},
},
},
{
name: "FalseVar",
varIndex: func(model *Builder) VarIndex {
fv := model.FalseVar()
return fv.Index()
},
want: &cmpb.IntegerVariableProto{
Domain: []int64{0, 0},
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
model := NewCpModelBuilder()
varIndex := test.varIndex(model)
m := mustModel(t, model)
got := m.GetVariables()[varIndex]
if diff := cmp.Diff(test.want, got, protocmp.Transform()); diff != "" {
t.Errorf("test.variable() returned with unexpected diff (-want+got):\n%s", diff)
}
})
}
}
func TestVar_EvaluateSolutionValue(t *testing.T) {
testCases := []struct {
name string
evaluateSolutionValue func() int64
want int64
}{
{
name: "IntVarEvaluateSolutionValue",
evaluateSolutionValue: func() int64 {
model := NewCpModelBuilder()
iv := model.NewIntVar(0, 10)
response := &cmpb.CpSolverResponse{
Solution: []int64{5},
}
return iv.evaluateSolutionValue(response)
},
want: 5,
},
{
name: "BoolVarEvaluateSolutionValue",
evaluateSolutionValue: func() int64 {
model := NewCpModelBuilder()
bv := model.NewBoolVar()
response := &cmpb.CpSolverResponse{
Solution: []int64{0},
}
return bv.evaluateSolutionValue(response)
},
want: 0,
},
{
name: "BoolVarNotEvaluateSolutionValue",
evaluateSolutionValue: func() int64 {
model := NewCpModelBuilder()
bv := model.NewBoolVar()
response := &cmpb.CpSolverResponse{
Solution: []int64{0},
}
return bv.Not().evaluateSolutionValue(response)
},
want: 1,
},
{
name: "AddLinExpr",
evaluateSolutionValue: func() int64 {
model := NewCpModelBuilder()
iv := model.NewIntVar(0, 10)
bv := model.NewBoolVar()
le := NewLinearExpr().AddTerm(iv, 10).AddTerm(bv, 20).AddConstant(5)
response := &cmpb.CpSolverResponse{
Solution: []int64{5, 1},
}
return le.evaluateSolutionValue(response)
},
want: 75,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got := test.evaluateSolutionValue()
if got != test.want {
t.Errorf("test.evaluateSolutionValue() = %v, want %v", got, test.want)
}
})
}
}
func TestVar_AddToLinearExpr(t *testing.T) {
model := NewCpModelBuilder()
iv := model.NewIntVar(-10, 10)
bv := model.NewBoolVar()
linExpr := NewLinearExpr().AddTerm(iv, 10).AddTerm(bv, 20).AddConstant(5)
testCases := []struct {
name string
addToLinExpr func() *LinearExpr
want *LinearExpr
}{
{
name: "AddIntVar",
addToLinExpr: func() *LinearExpr {
addLinExpr := NewLinearExpr()
iv.addToLinearExpr(addLinExpr, 2)
return addLinExpr
},
want: &LinearExpr{varCoeffs: []varCoeff{{ind: iv.Index(), coeff: 2}}},
},
{
name: "AddBoolVar",
addToLinExpr: func() *LinearExpr {
addLinExpr := NewLinearExpr()
bv.addToLinearExpr(addLinExpr, 3)
return addLinExpr
},
want: &LinearExpr{varCoeffs: []varCoeff{{ind: bv.Index(), coeff: 3}}},
},
{
name: "AddBoolVarNot",
addToLinExpr: func() *LinearExpr {
addLinExpr := NewLinearExpr()
bv.Not().addToLinearExpr(addLinExpr, 4)
return addLinExpr
},
want: &LinearExpr{varCoeffs: []varCoeff{{ind: bv.Index(), coeff: -4}}, offset: 4},
},
{
name: "AddLinExpr",
addToLinExpr: func() *LinearExpr {
addLinExpr := NewLinearExpr()
linExpr.addToLinearExpr(addLinExpr, 5)
return addLinExpr
},
want: &LinearExpr{
varCoeffs: []varCoeff{
{ind: iv.Index(), coeff: 50},
{ind: bv.Index(), coeff: 100},
},
offset: 25,
},
},
{
name: "AddMultipleTerms",
addToLinExpr: func() *LinearExpr {
addLinExpr := NewLinearExpr()
iv.addToLinearExpr(addLinExpr, 2)
bv.addToLinearExpr(addLinExpr, 3)
bv.Not().addToLinearExpr(addLinExpr, 4)
linExpr.addToLinearExpr(addLinExpr, 5)
return addLinExpr
},
want: &LinearExpr{
varCoeffs: []varCoeff{
{ind: iv.Index(), coeff: 2},
{ind: bv.Index(), coeff: 3},
{ind: bv.Index(), coeff: -4},
{ind: iv.Index(), coeff: 50},
{ind: bv.Index(), coeff: 100},
},
offset: 29,
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got := test.addToLinExpr()
if diff := cmp.Diff(test.want, got, cmp.AllowUnexported(LinearExpr{}, varCoeff{})); diff != "" {
t.Errorf("test.addToLinExpr() returned with unexpected diff (-want+got):\n%s", diff)
}
})
}
}
func TestVar_AsLinearExpressionProto(t *testing.T) {
model := NewCpModelBuilder()
iv := model.NewIntVar(-10, 10)
bv := model.NewBoolVar()
linExpr := NewLinearExpr().AddTerm(iv, 10).AddTerm(bv, 20).AddConstant(5)
testCases := []struct {
name string
buildProto func() *cmpb.LinearExpressionProto
want *cmpb.LinearExpressionProto
}{
{
name: "IntVar",
buildProto: func() *cmpb.LinearExpressionProto {
return iv.asLinearExpressionProto()
},
want: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv.Index())},
Coeffs: []int64{1},
},
},
{
name: "BoolVar",
buildProto: func() *cmpb.LinearExpressionProto {
return bv.asLinearExpressionProto()
},
want: &cmpb.LinearExpressionProto{
Vars: []int32{int32(bv.Index())},
Coeffs: []int64{1},
},
},
{
name: "BoolVarNot",
buildProto: func() *cmpb.LinearExpressionProto {
return bv.Not().asLinearExpressionProto()
},
want: &cmpb.LinearExpressionProto{
Vars: []int32{int32(bv.Index())},
Coeffs: []int64{-1},
Offset: 1,
},
},
{
name: "LinearExpr",
buildProto: func() *cmpb.LinearExpressionProto {
return linExpr.asLinearExpressionProto()
},
want: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv.Index()), int32(bv.Index())},
Coeffs: []int64{10, 20},
Offset: 5,
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got := test.buildProto()
if diff := cmp.Diff(test.want, got, protocmp.Transform()); diff != "" {
t.Errorf("test.buildProto() returned with unexpected diff (-want+got):\n%s", diff)
}
})
}
}
func TestVar_AsNegatedLinearExpressionProto(t *testing.T) {
model := NewCpModelBuilder()
// These variables are declared separately since they need to be accessed by both the
// `buildNegatedProto` and the `want` in the tests.
iv := model.NewIntVar(-10, 10)
bv := model.NewBoolVar()
linExpr := NewLinearExpr().AddTerm(iv, 10).AddTerm(bv, 20).AddConstant(5)
testCases := []struct {
name string
buildNegatedProto func() *cmpb.LinearExpressionProto
want *cmpb.LinearExpressionProto
}{
{
name: "IntVar",
buildNegatedProto: func() *cmpb.LinearExpressionProto {
return asNegatedLinearExpressionProto(iv)
},
want: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv.Index())},
Coeffs: []int64{-1},
},
},
{
name: "BoolVar",
buildNegatedProto: func() *cmpb.LinearExpressionProto {
return asNegatedLinearExpressionProto(bv)
},
want: &cmpb.LinearExpressionProto{
Vars: []int32{int32(bv.Index())},
Coeffs: []int64{-1},
},
},
{
name: "BoolVarNot",
buildNegatedProto: func() *cmpb.LinearExpressionProto {
return asNegatedLinearExpressionProto(bv.Not())
},
want: &cmpb.LinearExpressionProto{
Vars: []int32{int32(bv.Index())},
Coeffs: []int64{1},
Offset: -1,
},
},
{
name: "LinearExpr",
buildNegatedProto: func() *cmpb.LinearExpressionProto {
return asNegatedLinearExpressionProto(linExpr)
},
want: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv.Index()), int32(bv.Index())},
Coeffs: []int64{-10, -20},
Offset: -5,
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got := test.buildNegatedProto()
if diff := cmp.Diff(test.want, got, protocmp.Transform()); diff != "" {
t.Errorf("test.buildNegatedProto() returned with unexpected diff (-want+got):\n%s", diff)
}
})
}
}
func TestLinearExpr(t *testing.T) {
model := NewCpModelBuilder()
iv1 := model.NewIntVar(2, 8).WithName("iv1")
iv2 := model.NewIntVar(1, 5).WithName("iv2")
bv := model.NewBoolVar().WithName("bv1")
lin := NewLinearExpr().AddWeightedSum([]LinearArgument{iv1, bv}, []int64{10, 20})
testCases := []struct {
name string
buildExpr func() *LinearExpr
want *LinearExpr
}{
{
name: "NewConstant",
buildExpr: func() *LinearExpr { return NewConstant(42) },
want: &LinearExpr{varCoeffs: nil, offset: 42},
},
{
name: "AddIntVar",
buildExpr: func() *LinearExpr { return NewLinearExpr().Add(iv1) },
want: &LinearExpr{
varCoeffs: []varCoeff{{ind: iv1.Index(), coeff: 1}},
offset: 0},
},
{
name: "AddBoolVar",
buildExpr: func() *LinearExpr { return NewLinearExpr().Add(bv) },
want: &LinearExpr{
varCoeffs: []varCoeff{{ind: bv.Index(), coeff: 1}},
offset: 0},
},
{
name: "AddBoolVarNot",
buildExpr: func() *LinearExpr { return NewLinearExpr().Add(bv.Not()) },
want: &LinearExpr{
varCoeffs: []varCoeff{{ind: bv.Index(), coeff: -1}},
offset: 1},
},
{
name: "AddConstant",
buildExpr: func() *LinearExpr { return NewLinearExpr().Add(bv).AddConstant(100) },
want: &LinearExpr{
varCoeffs: []varCoeff{{ind: bv.Index(), coeff: 1}},
offset: 100},
},
{
name: "AddTerm",
buildExpr: func() *LinearExpr { return NewLinearExpr().AddTerm(iv2, 10) },
want: &LinearExpr{
varCoeffs: []varCoeff{{ind: iv2.Index(), coeff: 10}},
offset: 0},
},
{
name: "AddSameVar",
buildExpr: func() *LinearExpr { return NewLinearExpr().Add(iv1).AddTerm(iv1, 2) },
want: &LinearExpr{
varCoeffs: []varCoeff{
{ind: iv1.Index(), coeff: 1},
{ind: iv1.Index(), coeff: 2},
},
offset: 0},
},
{
name: "AddSumIntVar",
buildExpr: func() *LinearExpr { return NewLinearExpr().AddSum(iv1) },
want: &LinearExpr{
varCoeffs: []varCoeff{{ind: iv1.Index(), coeff: 1}},
offset: 0},
},
{
name: "AddSumBoolVar",
buildExpr: func() *LinearExpr { return NewLinearExpr().AddSum(bv.Not()) },
want: &LinearExpr{
varCoeffs: []varCoeff{{ind: bv.Index(), coeff: -1}},
offset: 1},
},
{
name: "AddSumManyVars",
buildExpr: func() *LinearExpr { return NewLinearExpr().AddSum(iv1, iv2, bv, bv.Not()) },
want: &LinearExpr{
varCoeffs: []varCoeff{
{ind: iv1.Index(), coeff: 1},
{ind: iv2.Index(), coeff: 1},
{ind: bv.Index(), coeff: 1},
{ind: bv.Index(), coeff: -1},
},
offset: 1,
},
},
{
name: "AddSumLinearExpr",
buildExpr: func() *LinearExpr { return NewLinearExpr().AddSum(lin) },
want: &LinearExpr{
varCoeffs: []varCoeff{
{ind: iv1.Index(), coeff: 10},
{ind: bv.Index(), coeff: 20},
},
offset: 0,
},
},
{
name: "AddWeightedSumIntVar",
buildExpr: func() *LinearExpr {
return NewLinearExpr().AddWeightedSum([]LinearArgument{iv1}, []int64{10})
},
want: &LinearExpr{
varCoeffs: []varCoeff{{ind: iv1.Index(), coeff: 10}},
offset: 0,
},
},
{
name: "AddWeightedSumBoolVar",
buildExpr: func() *LinearExpr {
return NewLinearExpr().AddWeightedSum([]LinearArgument{bv}, []int64{-10})
},
want: &LinearExpr{
varCoeffs: []varCoeff{{ind: bv.Index(), coeff: -10}},
offset: 0,
},
},
{
name: "AddWeightedSumManyVars",
buildExpr: func() *LinearExpr {
return NewLinearExpr().AddWeightedSum([]LinearArgument{iv1, iv2, bv, bv.Not()}, []int64{10, 20, 30, 40})
},
want: &LinearExpr{
varCoeffs: []varCoeff{
{ind: iv1.Index(), coeff: 10},
{ind: iv2.Index(), coeff: 20},
{ind: bv.Index(), coeff: 30},
{ind: bv.Index(), coeff: -40},
},
offset: 40,
},
},
{
name: "AddWeightedSumLinearExpr",
buildExpr: func() *LinearExpr {
return NewLinearExpr().AddWeightedSum([]LinearArgument{lin}, []int64{3})
},
want: &LinearExpr{
varCoeffs: []varCoeff{
{ind: iv1.Index(), coeff: 30},
{ind: bv.Index(), coeff: 60},
},
offset: 0,
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got := test.buildExpr()
if diff := cmp.Diff(test.want, got, cmp.AllowUnexported(LinearExpr{}, varCoeff{})); diff != "" {
t.Errorf("test.buildExpr() returned with unexpected diff (-want+got):\n%s", diff)
}
})
}
}
func mustModel(t *testing.T, builder *Builder) *cmpb.CpModelProto {
m, err := builder.Model()
if err != nil {
t.Fatalf("Model() returned with unexpected err %v", err)
}
return m
}
func TestIntervalVar(t *testing.T) {
model := NewCpModelBuilder()
iv1 := model.NewIntVar(0, 10)
iv2 := model.NewIntVar(0, 10)
bv1 := model.NewBoolVar()
trueVar := model.TrueVar()
testCases := []struct {
name string
intervalVar func() *cmpb.ConstraintProto
want *cmpb.ConstraintProto
}{
{
name: "NewIntervalVar",
intervalVar: func() *cmpb.ConstraintProto {
iv := model.NewIntervalVar(NewConstant(1), iv1, iv2)
m := mustModel(t, model)
return m.GetConstraints()[iv.Index()]
},
want: &cmpb.ConstraintProto{
EnforcementLiteral: []int32{int32(trueVar.Index())},
Constraint: &cmpb.ConstraintProto_Interval{&cmpb.IntervalConstraintProto{
Start: &cmpb.LinearExpressionProto{Offset: 1},
Size: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
},
End: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv2.Index())},
Coeffs: []int64{1},
},
}},
},
},
{
name: "NewFixedSizeIntervalVar",
intervalVar: func() *cmpb.ConstraintProto {
iv := model.NewFixedSizeIntervalVar(iv1, 5)
m := mustModel(t, model)
return m.GetConstraints()[iv.Index()]
},
want: &cmpb.ConstraintProto{
EnforcementLiteral: []int32{int32(trueVar.Index())},
Constraint: &cmpb.ConstraintProto_Interval{&cmpb.IntervalConstraintProto{
Start: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
},
Size: &cmpb.LinearExpressionProto{Offset: 5},
End: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
Offset: 5,
},
}},
},
},
{
name: "NewOptionalIntervalVar",
intervalVar: func() *cmpb.ConstraintProto {
iv := model.NewOptionalIntervalVar(NewConstant(1), iv1, iv2, bv1)
m := mustModel(t, model)
return m.GetConstraints()[iv.Index()]
},
want: &cmpb.ConstraintProto{
EnforcementLiteral: []int32{int32(bv1.Index())},
Constraint: &cmpb.ConstraintProto_Interval{&cmpb.IntervalConstraintProto{
Start: &cmpb.LinearExpressionProto{Offset: 1},
Size: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
},
End: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv2.Index())},
Coeffs: []int64{1},
},
}},
},
},
{
name: "NewOptionalFixedSizeIntervalVar",
intervalVar: func() *cmpb.ConstraintProto {
iv := model.NewOptionalFixedSizeIntervalVar(iv1, 5, bv1)
m := mustModel(t, model)
return m.GetConstraints()[iv.Index()]
},
want: &cmpb.ConstraintProto{
EnforcementLiteral: []int32{int32(bv1.Index())},
Constraint: &cmpb.ConstraintProto_Interval{&cmpb.IntervalConstraintProto{
Start: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
},
Size: &cmpb.LinearExpressionProto{Offset: 5},
End: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
Offset: 5,
},
}},
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got := test.intervalVar()
if diff := cmp.Diff(test.want, got, protocmp.Transform()); diff != "" {
t.Errorf("test.intervalVar() returned with unexpected diff (-want+got):\n%s", diff)
}
})
}
}
func TestConstraint_Name(t *testing.T) {
model := NewCpModelBuilder()
bv1 := model.NewBoolVar()
bv2 := model.NewBoolVar()
ct := model.AddBoolOr(bv1, bv2).WithName("name")
got := ct.Name()
want := "name"
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("Name() = %#v, want %#v", got, want)
}
}
func TestCpModelBuilder_Constraints(t *testing.T) {
model := NewCpModelBuilder()
bv1 := model.NewBoolVar()
bv2 := model.NewBoolVar()
bv3 := model.NewBoolVar()
iv1 := model.NewIntVar(-10, 10)
iv2 := model.NewIntVar(-10, 10)
iv3 := model.NewIntVar(-10, 10)
iv4 := model.NewIntVar(-10, 10)
interval1 := model.NewIntervalVar(iv1, iv2, iv3)
interval2 := model.NewIntervalVar(iv3, iv2, iv4)
interval3 := model.NewIntervalVar(iv2, iv3, iv4)
interval4 := model.NewIntervalVar(iv4, iv3, iv1)
one := model.NewConstant(1)
testCases := []struct {
name string
constraint func() *cmpb.ConstraintProto
want *cmpb.ConstraintProto
}{
{
name: "AddBoolOr",
constraint: func() *cmpb.ConstraintProto {
c := model.AddBoolOr(bv1, bv2.Not()).OnlyEnforceIf(bv3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
EnforcementLiteral: []int32{int32(bv3.Index())},
Constraint: &cmpb.ConstraintProto_BoolOr{&cmpb.BoolArgumentProto{
Literals: []int32{int32(bv1.Index()), int32(bv2.Not().Index())},
}},
},
},
{
name: "AddBoolAnd",
constraint: func() *cmpb.ConstraintProto {
c := model.AddBoolAnd(bv1, bv2.Not()).OnlyEnforceIf(bv3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
EnforcementLiteral: []int32{int32(bv3.Index())},
Constraint: &cmpb.ConstraintProto_BoolAnd{&cmpb.BoolArgumentProto{
Literals: []int32{int32(bv1.Index()), int32(bv2.Not().Index())},
}},
},
},
{
name: "AddBoolXor",
constraint: func() *cmpb.ConstraintProto {
c := model.AddBoolXor(bv1, bv2.Not()).OnlyEnforceIf(bv3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
EnforcementLiteral: []int32{int32(bv3.Index())},
Constraint: &cmpb.ConstraintProto_BoolXor{&cmpb.BoolArgumentProto{
Literals: []int32{int32(bv1.Index()), int32(bv2.Not().Index())},
}},
},
},
{
name: "AddAtLeastOne",
constraint: func() *cmpb.ConstraintProto {
c := model.AddAtLeastOne(bv1, bv2.Not()).OnlyEnforceIf(bv3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
EnforcementLiteral: []int32{int32(bv3.Index())},
Constraint: &cmpb.ConstraintProto_BoolOr{&cmpb.BoolArgumentProto{
Literals: []int32{int32(bv1.Index()), int32(bv2.Not().Index())},
}},
},
},
{
name: "AddAtMostOne",
constraint: func() *cmpb.ConstraintProto {
c := model.AddAtMostOne(bv1, bv2.Not()).OnlyEnforceIf(bv3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
EnforcementLiteral: []int32{int32(bv3.Index())},
Constraint: &cmpb.ConstraintProto_AtMostOne{&cmpb.BoolArgumentProto{
Literals: []int32{int32(bv1.Index()), int32(bv2.Not().Index())},
}},
},
},
{
name: "AddExactlyOne",
constraint: func() *cmpb.ConstraintProto {
c := model.AddExactlyOne(bv1, bv2.Not()).OnlyEnforceIf(bv3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
EnforcementLiteral: []int32{int32(bv3.Index())},
Constraint: &cmpb.ConstraintProto_ExactlyOne{&cmpb.BoolArgumentProto{
Literals: []int32{int32(bv1.Index()), int32(bv2.Not().Index())},
}},
},
},
{
name: "AddImplication",
constraint: func() *cmpb.ConstraintProto {
c := model.AddImplication(bv1, bv2.Not())
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_BoolOr{&cmpb.BoolArgumentProto{
Literals: []int32{int32(bv1.Not().Index()), int32(bv2.Not().Index())},
}},
},
},
{
name: "AddLinearConstraintForDomain",
constraint: func() *cmpb.ConstraintProto {
d := FromIntervals([]ClosedInterval{{0, 1}, {3, 4}, {11, 20}})
c := model.AddLinearConstraintForDomain(NewLinearExpr().Add(iv1).Add(bv1).AddConstant(5), d)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Linear{&cmpb.LinearConstraintProto{
Vars: []int32{int32(iv1.Index()), int32(bv1.Index())},
Coeffs: []int64{1, 1},
Domain: []int64{-5, -4, -2, -1, 6, 15},
}},
},
},
{
name: "AddLinearConstraint",
constraint: func() *cmpb.ConstraintProto {
c := model.AddLinearConstraint(NewLinearExpr().Add(iv1).Add(bv1), 2, 6)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Linear{&cmpb.LinearConstraintProto{
Vars: []int32{int32(iv1.Index()), int32(bv1.Index())},
Coeffs: []int64{1, 1},
Domain: []int64{2, 6},
}},
},
},
{
name: "AddEquality",
constraint: func() *cmpb.ConstraintProto {
c := model.AddEquality(iv1, NewConstant(10))
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Linear{&cmpb.LinearConstraintProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
Domain: []int64{10, 10},
}},
},
},
{
name: "AddLessOrEqual",
constraint: func() *cmpb.ConstraintProto {
c := model.AddLessOrEqual(iv1, NewConstant(10))
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Linear{&cmpb.LinearConstraintProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
Domain: []int64{math.MinInt64, 10},
}},
},
},
{
name: "AddLessThan",
constraint: func() *cmpb.ConstraintProto {
c := model.AddLessThan(iv1, NewConstant(10))
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Linear{&cmpb.LinearConstraintProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
Domain: []int64{math.MinInt64, 9},
}},
},
},
{
name: "AddGreaterOrEqual",
constraint: func() *cmpb.ConstraintProto {
c := model.AddGreaterOrEqual(iv1, NewConstant(10))
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Linear{&cmpb.LinearConstraintProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
Domain: []int64{10, math.MaxInt64},
}},
},
},
{
name: "AddGreaterThan",
constraint: func() *cmpb.ConstraintProto {
c := model.AddGreaterThan(iv1, NewConstant(10))
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Linear{&cmpb.LinearConstraintProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
Domain: []int64{11, math.MaxInt64},
}},
},
},
{
name: "AddNotEqual",
constraint: func() *cmpb.ConstraintProto {
c := model.AddNotEqual(iv1, NewConstant(10))
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Linear{&cmpb.LinearConstraintProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
Domain: []int64{math.MinInt64, 9, 11, math.MaxInt64},
}},
},
},
{
name: "AddAllDifferent",
constraint: func() *cmpb.ConstraintProto {
c := model.AddAllDifferent(iv1, bv1, bv2.Not(), NewConstant(10))
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_AllDiff{&cmpb.AllDifferentConstraintProto{
Exprs: []*cmpb.LinearExpressionProto{
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
},
&cmpb.LinearExpressionProto{
Vars: []int32{int32(bv1.Index())},
Coeffs: []int64{1},
},
&cmpb.LinearExpressionProto{
Vars: []int32{int32(bv2.Index())},
Coeffs: []int64{-1},
Offset: 1,
},
&cmpb.LinearExpressionProto{
Vars: []int32{},
Coeffs: []int64{},
Offset: 10,
},
},
}},
},
},
{
name: "AddVariableElement",
constraint: func() *cmpb.ConstraintProto {
c := model.AddVariableElement(iv1, []IntVar{iv2, iv3}, iv4)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Element{&cmpb.ElementConstraintProto{
Index: int32(iv1.Index()),
Target: int32(iv4.Index()),
Vars: []int32{int32(iv2.Index()), int32(iv3.Index())},
}},
},
},
{
name: "AddElement",
constraint: func() *cmpb.ConstraintProto {
c := model.AddElement(iv1, []int64{10, 20}, iv4)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Element{&cmpb.ElementConstraintProto{
Index: int32(iv1.Index()),
Target: int32(iv4.Index()),
Vars: []int32{
int32(model.NewConstant(10).Index()),
int32(model.NewConstant(20).Index()),
},
}},
},
},
{
name: "AddInverseConstraint",
constraint: func() *cmpb.ConstraintProto {
c := model.AddInverseConstraint([]IntVar{iv1, iv2}, []IntVar{iv3, iv4})
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Inverse{&cmpb.InverseConstraintProto{
FDirect: []int32{int32(iv1.Index()), int32(iv2.Index())},
FInverse: []int32{int32(iv3.Index()), int32(iv4.Index())},
}},
},
},
{
name: "AddMinEquality",
constraint: func() *cmpb.ConstraintProto {
c := model.AddMinEquality(iv1, iv2, iv3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_LinMax{&cmpb.LinearArgumentProto{
Target: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{-1},
},
Exprs: []*cmpb.LinearExpressionProto{
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv2.Index())},
Coeffs: []int64{-1},
},
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv3.Index())},
Coeffs: []int64{-1},
},
},
}},
},
},
{
name: "AddMaxEquality",
constraint: func() *cmpb.ConstraintProto {
c := model.AddMaxEquality(iv1, iv2, iv3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_LinMax{&cmpb.LinearArgumentProto{
Target: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
},
Exprs: []*cmpb.LinearExpressionProto{
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv2.Index())},
Coeffs: []int64{1},
},
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv3.Index())},
Coeffs: []int64{1},
},
},
}},
},
},
{
name: "AddMultiplicationEquality",
constraint: func() *cmpb.ConstraintProto {
c := model.AddMultiplicationEquality(iv1, iv2, iv3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_IntProd{&cmpb.LinearArgumentProto{
Target: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
},
Exprs: []*cmpb.LinearExpressionProto{
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv2.Index())},
Coeffs: []int64{1},
},
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv3.Index())},
Coeffs: []int64{1},
},
},
}},
},
},
{
name: "AddDivisionEquality",
constraint: func() *cmpb.ConstraintProto {
c := model.AddDivisionEquality(iv1, iv2, iv3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_IntDiv{&cmpb.LinearArgumentProto{
Target: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
},
Exprs: []*cmpb.LinearExpressionProto{
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv2.Index())},
Coeffs: []int64{1},
},
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv3.Index())},
Coeffs: []int64{1},
},
},
}},
},
},
{
name: "AddAbsEquality",
constraint: func() *cmpb.ConstraintProto {
c := model.AddAbsEquality(iv1, iv2)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_LinMax{&cmpb.LinearArgumentProto{
Target: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
},
Exprs: []*cmpb.LinearExpressionProto{
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv2.Index())},
Coeffs: []int64{1},
},
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv2.Index())},
Coeffs: []int64{-1},
},
},
}},
},
},
{
name: "AddModuloEquality",
constraint: func() *cmpb.ConstraintProto {
c := model.AddModuloEquality(iv1, iv2, iv3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_IntMod{&cmpb.LinearArgumentProto{
Target: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
},
Exprs: []*cmpb.LinearExpressionProto{
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv2.Index())},
Coeffs: []int64{1},
},
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv3.Index())},
Coeffs: []int64{1},
},
},
}},
},
},
{
name: "AddNoOverlap",
constraint: func() *cmpb.ConstraintProto {
c := model.AddNoOverlap(interval1, interval2)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_NoOverlap{&cmpb.NoOverlapConstraintProto{
Intervals: []int32{int32(interval1.Index()), int32(interval2.Index())},
}},
},
},
{
name: "AddNoOverlap2D",
constraint: func() *cmpb.ConstraintProto {
c := model.AddNoOverlap2D()
c.AddRectangle(interval1, interval2)
c.AddRectangle(interval3, interval4)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_NoOverlap_2D{&cmpb.NoOverlap2DConstraintProto{
XIntervals: []int32{int32(interval1.Index()), int32(interval3.Index())},
YIntervals: []int32{int32(interval2.Index()), int32(interval4.Index())},
}},
},
},
{
name: "AddCircuitConstraint",
constraint: func() *cmpb.ConstraintProto {
c := model.AddCircuitConstraint()
c.AddArc(0, 1, bv1)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Circuit{&cmpb.CircuitConstraintProto{
Tails: []int32{0},
Heads: []int32{1},
Literals: []int32{int32(bv1.Index())},
}},
},
},
{
name: "AddMultipleCircuitConstraint",
constraint: func() *cmpb.ConstraintProto {
c := model.AddMultipleCircuitConstraint()
c.AddRoute(0, 1, bv1)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Routes{&cmpb.RoutesConstraintProto{
Tails: []int32{0},
Heads: []int32{1},
Literals: []int32{int32(bv1.Index())},
}},
},
},
{
name: "AddAllowedAssignments",
constraint: func() *cmpb.ConstraintProto {
c := model.AddAllowedAssignments(iv1, iv2)
c.AddTuple(0, 2)
c.AddTuple(1, 3)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Table{&cmpb.TableConstraintProto{
Vars: []int32{int32(iv1.Index()), int32(iv2.Index())},
Values: []int64{0, 2, 1, 3},
}},
},
},
{
name: "AddReservoirConstraint",
constraint: func() *cmpb.ConstraintProto {
c := model.AddReservoirConstraint(10, 20)
c.AddEvent(NewLinearExpr().AddTerm(iv1, 2), NewConstant(15))
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Reservoir{&cmpb.ReservoirConstraintProto{
MinLevel: 10,
MaxLevel: 20,
TimeExprs: []*cmpb.LinearExpressionProto{
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{2},
},
},
LevelChanges: []*cmpb.LinearExpressionProto{
&cmpb.LinearExpressionProto{
Offset: 15,
},
},
ActiveLiterals: []int32{int32(one.Index())},
}},
},
},
{
name: "AddAutomaton",
constraint: func() *cmpb.ConstraintProto {
c := model.AddAutomaton([]IntVar{iv1, iv2}, 0, []int64{5, 10})
c.AddTransition(0, 1, 10)
c.AddTransition(2, 3, 15)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Automaton{&cmpb.AutomatonConstraintProto{
Vars: []int32{int32(iv1.Index()), int32(iv2.Index())},
StartingState: 0,
FinalStates: []int64{5, 10},
TransitionTail: []int64{0, 2},
TransitionHead: []int64{1, 3},
TransitionLabel: []int64{10, 15},
}},
},
},
{
name: "AddCumulative",
constraint: func() *cmpb.ConstraintProto {
c := model.AddCumulative(iv1)
c.AddDemand(interval1, iv2)
m := mustModel(t, model)
return m.GetConstraints()[c.Index()]
},
want: &cmpb.ConstraintProto{
Constraint: &cmpb.ConstraintProto_Cumulative{&cmpb.CumulativeConstraintProto{
Capacity: &cmpb.LinearExpressionProto{
Vars: []int32{int32(iv1.Index())},
Coeffs: []int64{1},
},
Intervals: []int32{int32(interval1.Index())},
Demands: []*cmpb.LinearExpressionProto{
&cmpb.LinearExpressionProto{
Vars: []int32{int32(iv2.Index())},
Coeffs: []int64{1},
},
},
}},
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got := test.constraint()
if diff := cmp.Diff(test.want, got, protocmp.Transform()); diff != "" {
t.Errorf("test.buildModel() returned with unexpected diff (-want+got):\n%s", diff)
}
})
}
}
func TestCpModelBuilder_Minimize(t *testing.T) {
model := NewCpModelBuilder()
iv1 := model.NewIntVar(-10, 10)
iv2 := model.NewIntVar(-10, 10)
model.Minimize(NewLinearExpr().AddTerm(iv1, 3).AddTerm(iv2, 5))
m := mustModel(t, model)
want := cmpb.CpObjectiveProto{
Vars: []int32{int32(iv1.Index()), int32(iv2.Index())},
Coeffs: []int64{3, 5},
}
got := m.GetObjective()
if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
t.Errorf("GetObjective() returned unexpected diff (-want+got): %v", diff)
}
}
func TestCpModelBuilder_Maximize(t *testing.T) {
model := NewCpModelBuilder()
iv1 := model.NewIntVar(-10, 10)
iv2 := model.NewIntVar(-10, 10)
model.Maximize(NewLinearExpr().AddTerm(iv1, 3).AddTerm(iv2, 5).AddConstant(7))
want := cmpb.CpObjectiveProto{
Vars: []int32{int32(iv1.Index()), int32(iv2.Index())},
Coeffs: []int64{-3, -5},
ScalingFactor: -1.0,
Offset: -7,
}
m := mustModel(t, model)
got := m.GetObjective()
if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
t.Errorf("GetObjective() returned unexpected diff (-want+got): %v", diff)
}
}
func TestCpModelBuilder_ConstantVars(t *testing.T) {
model := NewCpModelBuilder()
trueVar := model.TrueVar()
falseVar := model.FalseVar()
constVar := model.NewConstant(10)
wantTrue := BoolVar{cpb: model, ind: trueVar.Index()}
wantFalse := BoolVar{cpb: model, ind: falseVar.Index()}
wantConst := IntVar{cpb: model, ind: constVar.Index()}
if trueVar != wantTrue {
t.Errorf("TrueVar() = %+v, want %+v", trueVar, wantTrue)
}
if falseVar != wantFalse {
t.Errorf("FalseVar() = %+v, want %+v", falseVar, wantFalse)
}
if constVar != wantConst {
t.Errorf("NewConstant() = %+v, want %+v", constVar, wantConst)
}
gotDom, err := constVar.Domain()
wantDom := NewSingleDomain(10)
if err != nil {
t.Fatalf("Domain() returned with unexpected error %v", err)
}
if diff := cmp.Diff(wantDom, gotDom, cmp.AllowUnexported(Domain{}, ClosedInterval{})); diff != "" {
t.Errorf("Domain() = %v, want %v", gotDom, wantDom)
}
m := mustModel(t, model)
// Test that constant variables are only created once.
want := len(m.GetVariables())
model.TrueVar()
model.FalseVar()
model.NewConstant(10)
got := len(m.GetVariables())
if got != want {
t.Errorf("len(GetVariables()) = %v, want %v", got, want)
}
}
func TestCpModelBuilder_IndexValueSlices(t *testing.T) {
indices := []int32{5, 1, 3}
values := []int64{10, 11, 8}
sort.Sort(indexValueSlices{indices, values})
wantIndices := []int32{1, 3, 5}
wantValues := []int64{11, 8, 10}
if diff := cmp.Diff(wantIndices, indices); diff != "" {
t.Errorf("Sort indexValueSlices return unexpected indices diff (-want+got): %v", diff)
}
if diff := cmp.Diff(wantValues, values); diff != "" {
t.Errorf("Sort indexValueSlices return unexpected values diff (-want+got): %v", diff)
}
}
func TestCpModelBuilder_SetHint(t *testing.T) {
model := NewCpModelBuilder()
iv := model.NewIntVar(-10, 10)
bv1 := model.NewBoolVar()
bv2 := model.NewBoolVar()
hint := &Hint{
Ints: map[IntVar]int64{iv: 7},
Bools: map[BoolVar]bool{bv2.Not(): false, bv1: true},
}
model.SetHint(hint)
m := mustModel(t, model)
got := m.GetSolutionHint()
want := cmpb.PartialVariableAssignment{
Vars: []int32{int32(iv.Index()), int32(bv1.Index()), int32(bv2.Index())},
Values: []int64{7, 1, 1},
}
if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
t.Errorf("GetSolutionHint() returned unexpected diff (-want+got): %v", diff)
}
}
func TestCpModelBuilder_ClearHint(t *testing.T) {
model := NewCpModelBuilder()
iv := model.NewIntVar(-10, 10)
bv1 := model.NewBoolVar()
bv2 := model.NewBoolVar()
hint := &Hint{
Ints: map[IntVar]int64{iv: 7},
Bools: map[BoolVar]bool{bv1: true, bv2.Not(): false},
}
model.SetHint(hint)
model.ClearHint()
m := mustModel(t, model)
got := m.GetSolutionHint()
if got != nil {
t.Errorf("ClearHint() returned %v, want nil", got)
}
}
func TestCpModelBuilder_AddAssumption(t *testing.T) {
model := NewCpModelBuilder()
bv1 := model.NewBoolVar()
bv2 := model.NewBoolVar()
bv3 := model.NewBoolVar()
model.AddAssumption(bv1, bv2)
model.AddAssumption(bv3.Not())
m := mustModel(t, model)
got := m.GetAssumptions()
want := []int32{int32(bv1.Index()), int32(bv2.Index()), int32(bv3.Not().Index())}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("GetAssumptions() returned unexpected diff (-want+got): %v", diff)
}
}
func TestCpModelBuilder_ClearAssumption(t *testing.T) {
model := NewCpModelBuilder()
bv1 := model.NewBoolVar()
bv2 := model.NewBoolVar()
bv3 := model.NewBoolVar()
model.AddAssumption(bv1, bv2)
model.AddAssumption(bv3.Not())
model.ClearAssumption()
m := mustModel(t, model)
got := m.GetAssumptions()
if got != nil {
t.Errorf("ClearAssumption() returned %v, want nil", got)
}
}
func TestCpModelBuilder_AddDecisionStrategy(t *testing.T) {
model := NewCpModelBuilder()
iv := model.NewIntVar(-10, 10)
bv := model.NewBoolVar()
model.AddDecisionStrategy([]IntVar{iv, IntVar(bv)}, cmpb.DecisionStrategyProto_CHOOSE_HIGHEST_MAX, cmpb.DecisionStrategyProto_SELECT_LOWER_HALF)
model.AddDecisionStrategy([]IntVar{iv}, cmpb.DecisionStrategyProto_CHOOSE_LOWEST_MIN, cmpb.DecisionStrategyProto_SELECT_UPPER_HALF)
m := mustModel(t, model)
got := m.GetSearchStrategy()
want := []*cmpb.DecisionStrategyProto{
&cmpb.DecisionStrategyProto{
Variables: []int32{int32(iv.Index()), int32(bv.Index())},
VariableSelectionStrategy: cmpb.DecisionStrategyProto_CHOOSE_HIGHEST_MAX,
DomainReductionStrategy: cmpb.DecisionStrategyProto_SELECT_LOWER_HALF,
},
&cmpb.DecisionStrategyProto{
Variables: []int32{int32(iv.Index())},
VariableSelectionStrategy: cmpb.DecisionStrategyProto_CHOOSE_LOWEST_MIN,
DomainReductionStrategy: cmpb.DecisionStrategyProto_SELECT_UPPER_HALF,
},
}
if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
t.Errorf("GetSearchStrategy() returned unexpected diff (-want+got): %v", diff)
}
}
func TestCpModelBuilder_ErrorHandling(t *testing.T) {
testCases := []struct {
name string
builder func() *Builder
}{
{
name: "AddRectangle",
builder: func() *Builder {
model1 := NewCpModelBuilder()
model2 := NewCpModelBuilder()
i1 := model1.NewFixedSizeIntervalVar(NewConstant(0), 10)
i2 := model2.NewFixedSizeIntervalVar(NewConstant(10), 11)
c := model1.AddNoOverlap2D()
c.AddRectangle(i1, i2)
return model1
},
},
{
name: "AddArc",
builder: func() *Builder {
model1 := NewCpModelBuilder()
model2 := NewCpModelBuilder()
c := model1.AddCircuitConstraint()
c.AddArc(0, 10, model2.NewBoolVar())
return model1
},
},
{
name: "AddRoute",
builder: func() *Builder {
model1 := NewCpModelBuilder()
model2 := NewCpModelBuilder()
c := model1.AddMultipleCircuitConstraint()
c.AddRoute(0, 1, model2.NewBoolVar())
return model1
},
},
{
name: "AddDemand",
builder: func() *Builder {
model1 := NewCpModelBuilder()
model2 := NewCpModelBuilder()
cap := NewConstant(10)
dem := NewConstant(11)
c := model1.AddCumulative(cap)
c.AddDemand(model2.NewFixedSizeIntervalVar(cap, 10), dem)
return model1
},
},
{
name: "AddBoolOrs",
builder: func() *Builder {
model1 := NewCpModelBuilder()
model2 := NewCpModelBuilder()
model1.AddBoolOr(model2.NewBoolVar())
return model1
},
},
{
name: "AddNoOverlap",
builder: func() *Builder {
model1 := NewCpModelBuilder()
model2 := NewCpModelBuilder()
model1.AddNoOverlap(model2.NewFixedSizeIntervalVar(NewConstant(10), 10))
return model1
},
},
{
name: "AddAutomaton",
builder: func() *Builder {
model1 := NewCpModelBuilder()
model2 := NewCpModelBuilder()
model1.AddAutomaton([]IntVar{model2.NewIntVar(0, 10)}, 0, []int64{10})
return model1
},
},
{
name: "AddAssumption",
builder: func() *Builder {
model1 := NewCpModelBuilder()
model2 := NewCpModelBuilder()
model1.AddAssumption(model2.NewBoolVar())
return model1
},
},
{
name: "AddDecisionStrategy",
builder: func() *Builder {
model1 := NewCpModelBuilder()
model2 := NewCpModelBuilder()
model1.AddDecisionStrategy([]IntVar{model2.NewIntVar(0, 10)}, cmpb.DecisionStrategyProto_CHOOSE_HIGHEST_MAX, cmpb.DecisionStrategyProto_SELECT_LOWER_HALF)
return model1
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got, err := test.builder().Model()
if !errors.Is(err, ErrMixedModels) {
t.Errorf("test.Model() returned with unexpected error %v; want ErrMixedModels error", err)
}
if got != nil {
t.Errorf("test.Model() returned with unexpected model %v; want nil", got)
}
})
}
}