Files
ortools-clone/ortools/sat/container.h
Corentin Le Molgat 1b4d75ceb3 sat: backport from main
2025-11-05 13:55:12 +01:00

261 lines
8.6 KiB
C++

// 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.
#ifndef ORTOOLS_SAT_CONTAINER_H_
#define ORTOOLS_SAT_CONTAINER_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <type_traits>
#include <utility>
#include "absl/container/inlined_vector.h"
#include "absl/log/check.h"
#include "absl/types/span.h"
#include "ortools/base/stl_util.h"
#include "ortools/sat/sat_base.h"
namespace operations_research {
namespace sat {
// This is a very specific container that is optimized for the specific usage
// patterns of BinaryImplicationGraph.
// It stores an ordered set of literals and an unordered set of offsets.
// internally, both arrays are stored contiguously, literals first, then
// offsets. There might be a hole between the two arrays. In comments, we denote
// literals as `L` and offsets as `O`, and holes as `.`. For example, `LLL..OO`
// has 3 literals and 2 offsets, with a hole of size 2 in between.
class LiteralsOrOffsets {
public:
LiteralsOrOffsets() = default;
~LiteralsOrOffsets() {
if (capacity_ > kInlineElements) free(data_.ptr);
}
LiteralsOrOffsets(const LiteralsOrOffsets&) = delete;
LiteralsOrOffsets& operator=(const LiteralsOrOffsets&) = delete;
LiteralsOrOffsets(LiteralsOrOffsets&& o) {
num_literals_ = o.num_literals_;
num_offsets_ = o.num_offsets_;
capacity_ = o.capacity_;
data_ = o.data_;
o.capacity_ = kInlineElements; // So we don't double-free.
}
LiteralsOrOffsets& operator=(LiteralsOrOffsets&& o) {
num_literals_ = o.num_literals_;
num_offsets_ = o.num_offsets_;
capacity_ = o.capacity_;
data_ = o.data_;
o.capacity_ = kInlineElements; // So we don't double-free.
return *this;
}
using Offset = int;
// Adds a literal to the end of the list of literals.
void PushBackLiteral(Literal literal) {
if (num_literals_ + num_offsets_ >= capacity_) {
GrowCapacity();
}
DCHECK_LT(num_literals_ + num_offsets_, capacity_);
InternalAddress()[num_literals_++].literal = literal;
}
// Adds an offset to the set of offsets.
void InsertOffset(Offset offset) {
if (num_literals_ + num_offsets_ >= capacity_) {
GrowCapacity();
}
DCHECK_LT(num_literals_ + num_offsets_, capacity_);
InternalAddress()[capacity_ - (++num_offsets_)].offset = offset;
}
int num_literals() const { return num_literals_; }
int num_offsets() const { return num_offsets_; }
absl::Span<const Literal> literals() const;
absl::Span<Literal> literals();
// Note that order is arbitrary.
absl::Span<const Offset> offsets() const;
absl::Span<Offset> offsets();
// Clearing functions.
// Call ShrinkToFit() if you want to save memory.
void ClearLiterals() { num_literals_ = 0; }
void ClearOffsets() { num_offsets_ = 0; }
void Clear() {
num_literals_ = 0;
num_offsets_ = 0;
}
// A bit faster than Clear() + ShrinkToFit().
void ClearAndReleaseMemory() {
num_literals_ = 0;
num_offsets_ = 0;
if (capacity_ > kInlineElements) free(data_.ptr);
capacity_ = kInlineElements;
}
// Use as little memory as possible.
void ShrinkToFit() {
if (capacity_ == kInlineElements) return; // Smallest possible size.
const uint32_t needed = num_literals_ + num_offsets_;
if (needed == capacity_) return;
DCHECK_GT(capacity_, kInlineElements);
LiteralOrOffset* old_ptr = data_.ptr; // This is valid memory.
if (needed > kInlineElements) {
LiteralOrOffset* new_ptr = static_cast<LiteralOrOffset*>(
malloc(static_cast<ptrdiff_t>(needed) * sizeof(LiteralOrOffset)));
CopyFromOldToNew(old_ptr, capacity_, new_ptr, needed);
capacity_ = needed;
data_.ptr = new_ptr;
} else {
CopyFromOldToNew(old_ptr, capacity_, data_.inlined, kInlineElements);
capacity_ = kInlineElements;
}
free(old_ptr);
}
// Resizes the list of literals to or shorter length.
void TruncateLiterals(int new_size) {
CHECK_GE(new_size, 0);
CHECK_LE(new_size, num_literals_);
num_literals_ = new_size;
}
// Removes all literals for which `predicate` returns true, and truncates the
// list of literals to the number of remaining literals (memory is not
// released).
template <typename Predicate>
void RemoveLiteralsIf(Predicate predicate);
// Returns the backing capacity for literals and offsets.
uint32_t capacity() const { return capacity_; }
static constexpr uint32_t kInlineElements = 4;
private:
// Elements are either literals or offsets.
union LiteralOrOffset {
Literal literal;
Offset offset;
};
static_assert(std::is_trivially_destructible_v<LiteralOrOffset>);
static_assert(std::is_trivially_copyable_v<LiteralOrOffset>);
LiteralOrOffset* InternalAddress() {
return capacity_ == kInlineElements ? data_.inlined : data_.ptr;
}
const LiteralOrOffset* InternalAddress() const {
return capacity_ == kInlineElements ? data_.inlined : data_.ptr;
}
void CopyFromOldToNew(LiteralOrOffset* old_ptr, uint32_t old_capacity,
LiteralOrOffset* new_ptr, uint32_t new_capacity) {
memcpy(new_ptr, old_ptr, num_literals_ * sizeof(LiteralOrOffset));
memcpy(new_ptr + (new_capacity - num_offsets_),
old_ptr + (old_capacity - num_offsets_),
num_offsets_ * sizeof(LiteralOrOffset));
}
void GrowCapacity() {
// TODO(user): crash later.
// For now, we do if we use more than 2 GB per LiteralOrOffsets.
CHECK_LE(capacity_, std::numeric_limits<uint32_t>::max() / 2);
const uint32_t new_capacity =
static_cast<uint32_t>(1.3 * static_cast<double>(capacity_));
CHECK_GT(new_capacity, kInlineElements);
LiteralOrOffset* new_ptr = static_cast<LiteralOrOffset*>(
malloc(static_cast<ptrdiff_t>(new_capacity) * sizeof(LiteralOrOffset)));
CopyFromOldToNew(InternalAddress(), capacity_, new_ptr, new_capacity);
if (capacity_ > kInlineElements) {
free(data_.ptr);
}
capacity_ = new_capacity;
data_.ptr = new_ptr;
}
// Invariants:
// num_literals_ + num_offsets_ <= capacity_
// capacity_ >= kInlineElements.
uint32_t num_literals_ = 0;
uint32_t num_offsets_ = 0;
uint32_t capacity_ = kInlineElements;
// Inlined memory or a pointer to the content.
//
// TODO(user): If we want to be more compact, we can set kInlineElements == 2.
// We can probably fit a third one, if we don't care about the aligment since
// we only use 3 * int32_t above. Experiments.
union {
LiteralOrOffset inlined[kInlineElements];
LiteralOrOffset* ptr;
} data_;
};
inline absl::Span<const Literal> LiteralsOrOffsets::literals() const {
DCHECK_LE(num_literals_, capacity_);
// Casting is OK because "pointer to LiteralOrOffset" is
// pointer-interconvertible with "pointer to Literal": `LiteralOrOffset` is
// an union object and the other is a non-static data member of that object.
return absl::MakeConstSpan(
reinterpret_cast<const Literal*>(InternalAddress()), num_literals_);
}
inline absl::Span<Literal> LiteralsOrOffsets::literals() {
DCHECK_LE(num_literals_, capacity_);
return absl::MakeSpan(reinterpret_cast<Literal*>(InternalAddress()),
num_literals_);
}
inline absl::Span<const LiteralsOrOffsets::Offset> LiteralsOrOffsets::offsets()
const {
DCHECK_LE(num_offsets_, capacity_);
return absl::MakeConstSpan(
reinterpret_cast<const Offset*>(InternalAddress() +
(capacity_ - num_offsets_)),
num_offsets_);
}
inline absl::Span<LiteralsOrOffsets::Offset> LiteralsOrOffsets::offsets() {
DCHECK_LE(num_offsets_, capacity_);
return absl::MakeSpan(
reinterpret_cast<Offset*>(InternalAddress() + (capacity_ - num_offsets_)),
num_offsets_);
}
template <typename Predicate>
void LiteralsOrOffsets::RemoveLiteralsIf(Predicate predicate) {
const auto new_end = std::remove_if(literals().begin(), literals().end(),
std::move(predicate));
num_literals_ = new_end - literals().begin();
}
} // namespace sat
} // namespace operations_research
#endif // ORTOOLS_SAT_CONTAINER_H_