Files
ortools-clone/ortools/util/monoid_operation_tree.h
Corentin Le Molgat b4b226801b update include guards
2025-11-05 11:54:02 +01:00

254 lines
8.2 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_UTIL_MONOID_OPERATION_TREE_H_
#define ORTOOLS_UTIL_MONOID_OPERATION_TREE_H_
#include <algorithm>
#include <string>
#include <vector>
#include "absl/strings/str_format.h"
#include "ortools/base/logging.h"
namespace operations_research {
// A monoid is an algebraic structure consisting of a set S with an associative
// binary operation * :S x S -> S that has an identity element.
// Associative means a*(b*c) = (a*b)*c for all a,b,c in S.
// An identity element is an element e in S such that for all a in S,
// e*a = a*e = a.
// See http://en.wikipedia.org/wiki/Monoid for more details.
//
// A MonoidOperationTree is a data structure that maintains a product
// a_1 * a_2 * ... * a_n for a given (fixed) n, and that supports the
// following functions:
// - Setting the k-th operand to a given value in O(log n) calls to the *
// operation
// - Querying the result in O(1)
//
// Note that the monoid is not required to be commutative.
//
// The parameter class T represents an element of the set S.
// It must:
// * Have a public no-argument constructor producing the identity element.
// * Have a = operator method that sets its value to the given one.
// * Have a Compute(const T& left, const T& right) method that sets its value
// to the result of the binary operation for the two given operands.
// * Have a string DebugString() const method.
//
// Possible use cases are:
// * Maintain a sum or a product of doubles, with a guarantee that the queried
// result is independent of the order of past numerical issues
// * Maintain a product of identically sized square matrices, which is an
// example of use with non-commutative operations.
template <class T>
class MonoidOperationTree {
public:
// Constructs a MonoidOperationTree able to store 'size' operands.
explicit MonoidOperationTree(int size);
// This type is neither copyable nor movable.
MonoidOperationTree(const MonoidOperationTree&) = delete;
MonoidOperationTree& operator=(const MonoidOperationTree&) = delete;
// Returns the root of the tree, containing the result of the operation.
const T& result() const { return *result_; }
// Resets the argument of given index.
void Reset(int argument_index);
// Sets the argument of given index.
void Set(int argument_index, const T& argument);
// Resets all arguments.
void Clear();
// Returns the leaf node corresponding to the given argument index.
const T& GetOperand(int argument_index) const {
return nodes_[PositionOfLeaf(argument_index)];
}
// Dive down a branch of the operation tree, and then come back up.
template <class Diver>
void DiveInTree(Diver* const diver) const {
DiveInTree(0, diver);
}
std::string DebugString() const;
private:
// Computes the index of the first leaf for the given size.
static int ComputeLeafOffset(int size);
// Computes the total number of nodes we need to store non-leaf nodes and
// leaf nodes.
static int ComputeNumberOfNodes(int leaf_offset);
// Computes the whole path from the node of given position up to the root,
// excluding the bottom node.
void ComputeAbove(int position);
// Computes the node of given position, and no other.
void Compute(int position);
// Returns the position of the leaf node of given index.
int PositionOfLeaf(int index) const { return leaf_offset_ + index; }
// Returns true if the node of given position is a leaf.
bool IsLeaf(int position) const { return position >= leaf_offset_; }
// Returns the index of the argument stored in the node of given position.
int ArgumentIndexOfLeafPosition(int position) const {
DCHECK(IsLeaf(position));
return position - leaf_offset_;
}
template <class Diver>
void DiveInTree(int position, Diver* diver) const;
static int father(int pos) { return (pos - 1) >> 1; }
static int left(int pos) { return (pos << 1) + 1; }
static int right(int pos) { return (pos + 1) << 1; }
// The number of arguments that can be stored in this tree. That is, the
// number of used leaves. (There may be unused leaves, too)
const int size_;
// The index of the first leaf.
const int leaf_offset_;
// Number of nodes, both non-leaves and leaves.
const int num_nodes_;
// All the nodes, both non-leaves and leaves.
std::vector<T> nodes_;
// A pointer to the root node
T const* result_;
};
// --------------------------------------------------------------------- //
// Implementation
// --------------------------------------------------------------------- //
template <class T>
int MonoidOperationTree<T>::ComputeLeafOffset(int size) {
int smallest_pow_two_not_less_than_size = 1;
while (smallest_pow_two_not_less_than_size < size) {
smallest_pow_two_not_less_than_size <<= 1;
}
return std::max(1, smallest_pow_two_not_less_than_size - 1);
}
template <class T>
int MonoidOperationTree<T>::ComputeNumberOfNodes(int leaf_offset) {
// leaf_offset should be a power of 2 minus 1.
DCHECK_EQ(0, (leaf_offset) & (leaf_offset + 1));
const int num_leaves = leaf_offset + 1;
const int num_nodes = leaf_offset + num_leaves;
DCHECK_GE(num_nodes, 3); // We need at least the root and its 2 children
return num_nodes;
}
template <class T>
MonoidOperationTree<T>::MonoidOperationTree(int size)
: size_(size),
leaf_offset_(ComputeLeafOffset(size)),
num_nodes_(ComputeNumberOfNodes(leaf_offset_)),
nodes_(num_nodes_, T()),
result_(&(nodes_[0])) {}
template <class T>
void MonoidOperationTree<T>::Clear() {
const int size = nodes_.size();
nodes_.assign(size, T());
}
template <class T>
void MonoidOperationTree<T>::Reset(int argument_index) {
Set(argument_index, T());
}
template <class T>
void MonoidOperationTree<T>::Set(int argument_index, const T& argument) {
CHECK_LT(argument_index, size_);
const int position = leaf_offset_ + argument_index;
nodes_[position] = argument;
ComputeAbove(position);
}
template <class T>
void MonoidOperationTree<T>::ComputeAbove(int position) {
int pos = father(position);
while (pos > 0) {
Compute(pos);
pos = father(pos);
}
Compute(0);
}
template <class T>
void MonoidOperationTree<T>::Compute(int position) {
const T& left_child = nodes_[left(position)];
const T& right_child = nodes_[right(position)];
nodes_[position].Compute(left_child, right_child);
}
template <class T>
std::string MonoidOperationTree<T>::DebugString() const {
std::string out;
int layer = 0;
for (int i = 0; i < num_nodes_; ++i) {
if (((i + 1) & i) == 0) {
// New layer
absl::StrAppendFormat(&out, "-------------- Layer %d ---------------\n",
layer);
++layer;
}
absl::StrAppendFormat(&out, "Position %d: %s\n", i,
nodes_[i].DebugString());
}
return out;
}
template <class T>
template <class Diver>
void MonoidOperationTree<T>::DiveInTree(int position, Diver* diver) const {
// Are we at a leaf?
if (IsLeaf(position)) {
const int index = ArgumentIndexOfLeafPosition(position);
const T& argument = nodes_[position];
diver->OnArgumentReached(index, argument);
} else {
const T& current = nodes_[position];
const T& left_child = nodes_[left(position)];
const T& right_child = nodes_[right(position)];
if (diver->ChooseGoLeft(current, left_child, right_child)) {
// Go left
DiveInTree(left(position), diver);
// Come back up
diver->OnComeBackFromLeft(current, left_child, right_child);
} else {
// Go right
DiveInTree(right(position), diver);
// Come back up
diver->OnComeBackFromRight(current, left_child, right_child);
}
}
}
} // namespace operations_research
#endif // ORTOOLS_UTIL_MONOID_OPERATION_TREE_H_