Files
ortools-clone/ortools/sat/go/cpmodel/domain.go
Corentin Le Molgat a66a6daac7 Bump Copyright to 2025
2025-01-10 11:35:44 +01:00

184 lines
5.7 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 (
"fmt"
"math"
"sort"
)
// ClosedInterval stores the closed interval `[start,end]`. If the `Start` is greater
// than the `End`, the interval is considered empty.
type ClosedInterval struct {
Start int64
End int64
}
// checkOverflowAndAdd first checks if adding `delta` to `i` will cause an integer overflow.
// It will return the value of the summation if there is no overflow. Otherwise, it will
// return MaxInt64 or MinInt64 depending on the direction of the overflow.
func checkOverflowAndAdd(i, delta int64) int64 {
if i == math.MinInt64 || i == math.MaxInt64 {
return i
}
s := i + delta
if delta < 0 && s > i {
return math.MinInt64
}
if delta > 0 && s < i {
return math.MaxInt64
}
return s
}
// Offset adds an offset to both the `Start` and `End` of the ClosedInterval `c`. If the `Start`
// is equal to MinInt or if `End` is equal to MaxInt, the offset does not get added since those
// values represent an unbounded domain. Both `Start` and `End` are clamped at math.MinInt64 and
// Math.MaxInt64.
func (c ClosedInterval) Offset(delta int64) ClosedInterval {
return ClosedInterval{checkOverflowAndAdd(c.Start, delta), checkOverflowAndAdd(c.End, delta)}
}
// Domain stores an ordered list of ClosedIntervals. This represents any subset
// of `[MinInt64,MaxInt64]`. This type can be used to represent such a set efficiently
// as a sorted and non-adjacent list of intervals. This is efficient as long as the
// size of such a list stays reasonable.
type Domain struct {
intervals []ClosedInterval
}
// joinIntervals sorts the intervals in domain and combines two consecutive intervals
// if they overlap or the start of the second is exactly one more than the end of the first.
// If an interval's `start` is greater than its `end`, then the interval is considered empty.
func (d *Domain) joinIntervals() {
var itvs []ClosedInterval
for _, v := range d.intervals {
if v.Start <= v.End {
itvs = append(itvs, v)
}
}
d.intervals = itvs
if len(d.intervals) == 0 {
return
}
sort.Slice(d.intervals, func(i, j int) bool {
if d.intervals[i].Start != d.intervals[j].Start {
return d.intervals[i].Start < d.intervals[j].Start
}
return d.intervals[i].End < d.intervals[j].End
})
newIntervals := []ClosedInterval{d.intervals[0]}
for i := 1; i < len(d.intervals); i++ {
lastInt := &newIntervals[len(newIntervals)-1]
if lastInt.End+1 >= d.intervals[i].Start {
if lastInt.End < d.intervals[i].End {
lastInt.End = d.intervals[i].End
}
} else {
newIntervals = append(newIntervals, d.intervals[i])
}
}
d.intervals = newIntervals
}
// NewEmptyDomain creates an empty Domain.
func NewEmptyDomain() Domain {
return Domain{}
}
// NewSingleDomain creates a new singleton domain `[val]`.
func NewSingleDomain(val int64) Domain {
return Domain{[]ClosedInterval{{val, val}}}
}
// NewDomain creates a new domain of a single interval `[left,right]`.
// If `left > right`, an empty domain is returned.
func NewDomain(left, right int64) Domain {
if left > right {
return NewEmptyDomain()
}
return Domain{[]ClosedInterval{{left, right}}}
}
// FromValues creates a new domain from `values`. `values` need not be
// sorted and can repeat.
func FromValues(values []int64) Domain {
var d Domain
for _, v := range values {
d.intervals = append(d.intervals, ClosedInterval{v, v})
}
d.joinIntervals()
return d
}
// FromIntervals creates a domain from the union of the set of unordered `intervals`.
// If an interval's `start` is greater than its `end`, the interval is considered empty.
func FromIntervals(intervals []ClosedInterval) Domain {
itvs := make([]ClosedInterval, len(intervals))
copy(itvs, intervals)
domain := Domain{itvs}
domain.joinIntervals()
return domain
}
// FromFlatIntervals creates a new domain from a flattened list of intervals. If there is an
// interval where the start is greater than the end, the interval is considered empty. Returns
// an error if the length of `values` is not even.
func FromFlatIntervals(values []int64) (Domain, error) {
if len(values) == 0 {
return NewEmptyDomain(), nil
}
if len(values)%2 != 0 {
return NewEmptyDomain(), fmt.Errorf("len(values)=%v must be a multiple of 2", len(values))
}
var intervals []ClosedInterval
for i := 1; i < len(values); i += 2 {
intervals = append(intervals, ClosedInterval{values[i-1], values[i]})
}
d := Domain{intervals}
d.joinIntervals()
return d, nil
}
// FlattenedIntervals returns the flattened list of interval bounds of the domain.
// For example, if Domain d is equal to `[0,2][5,5][9,10]` will return `[0,2,5,5,9,10]`.
func (d Domain) FlattenedIntervals() []int64 {
var result []int64
for _, i := range d.intervals {
result = append(result, i.Start, i.End)
}
return result
}
// Min returns the minimum value of the domain, and returns false if no minimum exists,
// i.e. if the domain is empty.
func (d Domain) Min() (int64, bool) {
if len(d.intervals) == 0 {
return 0, false
}
return d.intervals[0].Start, true
}
// Max returns the maximum value of the domain, and returns false if no maximum exists,
// i.e. if the domain is empty.
func (d Domain) Max() (int64, bool) {
if len(d.intervals) == 0 {
return 0, false
}
return d.intervals[len(d.intervals)-1].End, true
}