Files
ortools-clone/ortools/lp_data/sparse_vector.h
2021-11-13 08:36:50 +01:00

1026 lines
40 KiB
C++

// Copyright 2010-2021 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.
// Classes to represent sparse vectors.
//
// The following are very good references for terminology, data structures,
// and algorithms:
//
// I.S. Duff, A.M. Erisman and J.K. Reid, "Direct Methods for Sparse Matrices",
// Clarendon, Oxford, UK, 1987, ISBN 0-19-853421-3,
// http://www.amazon.com/dp/0198534213.
//
//
// T.A. Davis, "Direct methods for Sparse Linear Systems", SIAM, Philadelphia,
// 2006, ISBN-13: 978-0-898716-13, http://www.amazon.com/dp/0898716136.
//
//
// Both books also contain a wealth of references.
#ifndef OR_TOOLS_LP_DATA_SPARSE_VECTOR_H_
#define OR_TOOLS_LP_DATA_SPARSE_VECTOR_H_
#include <algorithm>
#include <cstring>
#include <memory>
#include <string>
#include "absl/strings/str_format.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h" // for CHECK*
#include "ortools/graph/iterators.h"
#include "ortools/lp_data/lp_types.h"
#include "ortools/lp_data/permutation.h"
#include "ortools/util/return_macros.h"
namespace operations_research {
namespace glop {
template <typename IndexType>
class SparseVectorEntry;
// --------------------------------------------------------
// SparseVector
// --------------------------------------------------------
// This class allows to store a vector taking advantage of its sparsity.
// Space complexity is in O(num_entries).
// In the current implementation, entries are stored in a first-in order (order
// of SetCoefficient() calls) when they are added; then the "cleaning" process
// sorts them by index (and duplicates are removed: the last entry takes
// precedence).
// Many methods assume that the entries are sorted by index and without
// duplicates, and DCHECK() that.
//
// Default copy construction is fully supported.
//
// This class uses strong integer types (i.e. no implicit cast to/from other
// integer types) for both:
// - the index of entries (eg. SparseVector<RowIndex> is a SparseColumn,
// see ./sparse_column.h).
// - the *internal* indices of entries in the internal storage, which is an
// entirely different type: EntryType.
// This class can be extended with a custom iterator/entry type for the
// iterator-based API. This can be used to extend the interface with additional
// methods for the entries returned by the iterators; for an example of such
// extension, see SparseColumnEntry in sparse_column.h. The custom entries and
// iterators should be derived from SparseVectorEntry and SparseVectorIterator,
// or at least provide the same public and protected interface.
//
// TODO(user): un-expose this type to client; by getting rid of the
// index-based APIs and leveraging iterator-based APIs; if possible.
template <typename IndexType,
typename IteratorType = VectorIterator<SparseVectorEntry<IndexType>>>
class SparseVector {
public:
typedef IndexType Index;
typedef StrictITIVector<Index, Fractional> DenseVector;
typedef Permutation<Index> IndexPermutation;
using Iterator = IteratorType;
using Entry = typename Iterator::Entry;
SparseVector();
// NOTE(user): STL uses the expensive copy constructor when relocating
// elements of a vector, unless the move constructor exists *and* it is marked
// as noexcept. However, the noexcept annotation is banned by the style guide,
// and the only way to get it is by using the default move constructor and
// assignment operator generated by the compiler.
SparseVector(const SparseVector& other);
#if !defined(_MSC_VER)
SparseVector(SparseVector&& other) = default;
#endif
SparseVector& operator=(const SparseVector& other);
#if !defined(_MSC_VER)
SparseVector& operator=(SparseVector&& other) = default;
#endif
// Read-only API for a given SparseVector entry. The typical way for a
// client to use this is to use the natural range iteration defined by the
// Iterator class below:
// SparseVector<int> v;
// ...
// for (const SparseVector<int>::Entry e : v) {
// LOG(INFO) << "Index: " << e.index() << ", Coeff: " << e.coefficient();
// }
//
// Note that this can only be used when the vector has no duplicates.
//
// Note(user): using either "const SparseVector<int>::Entry&" or
// "const SparseVector<int>::Entry" yields the exact same performance on the
// netlib, thus we recommend to use the latter version, for consistency.
Iterator begin() const;
Iterator end() const;
// Clears the vector, i.e. removes all entries.
void Clear();
// Clears the vector and releases the memory it uses.
void ClearAndRelease();
// Reserve the underlying storage for the given number of entries.
void Reserve(EntryIndex new_capacity);
// Returns true if the vector is empty.
bool IsEmpty() const;
// Cleans the vector, i.e. removes zero-values entries, removes duplicates
// entries and sorts remaining entries in increasing index order.
// Runs in O(num_entries * log(num_entries)).
void CleanUp();
// Returns true if the entries of this SparseVector are in strictly increasing
// index order and if the vector contains no duplicates nor zero coefficients.
// Runs in O(num_entries). It is not const because it modifies
// possibly_contains_duplicates_.
bool IsCleanedUp() const;
// Swaps the content of this sparse vector with the one passed as argument.
// Works in O(1).
void Swap(SparseVector* other);
// Populates the current vector from sparse_vector.
// Runs in O(num_entries).
void PopulateFromSparseVector(const SparseVector& sparse_vector);
// Populates the current vector from dense_vector.
// Runs in O(num_indices_in_dense_vector).
void PopulateFromDenseVector(const DenseVector& dense_vector);
// Appends all entries from sparse_vector to the current vector; the indices
// of the appended entries are increased by offset. If the current vector
// already has a value at an index changed by this method, this value is
// overwritten with the value from sparse_vector.
// Note that while offset may be negative itself, the indices of all entries
// after applying the offset must be non-negative.
void AppendEntriesWithOffset(const SparseVector& sparse_vector, Index offset);
// Returns true when the vector contains no duplicates. Runs in
// O(max_index + num_entries), max_index being the largest index in entry.
// This method allocates (and deletes) a Boolean array of size max_index.
// Note that we use a mutable Boolean to make subsequent call runs in O(1).
bool CheckNoDuplicates() const;
// Same as CheckNoDuplicates() except it uses a reusable boolean vector
// to make the code more efficient. Runs in O(num_entries).
// Note that boolean_vector should be initialized to false before calling this
// method; It will remain equal to false after calls to CheckNoDuplicates().
// Note that we use a mutable Boolean to make subsequent call runs in O(1).
bool CheckNoDuplicates(StrictITIVector<Index, bool>* boolean_vector) const;
// Defines the coefficient at index, i.e. vector[index] = value;
void SetCoefficient(Index index, Fractional value);
// Removes an entry from the vector if present. The order of the other entries
// is preserved. Runs in O(num_entries).
void DeleteEntry(Index index);
// Sets to 0.0 (i.e. remove) all entries whose fabs() is lower or equal to
// the given threshold.
void RemoveNearZeroEntries(Fractional threshold);
// Same as RemoveNearZeroEntries, but the entry magnitude of each row is
// multiplied by weights[row] before being compared with threshold.
void RemoveNearZeroEntriesWithWeights(Fractional threshold,
const DenseVector& weights);
// Moves the entry with given Index to the first position in the vector. If
// the entry is not present, nothing happens.
void MoveEntryToFirstPosition(Index index);
// Moves the entry with given Index to the last position in the vector. If
// the entry is not present, nothing happens.
void MoveEntryToLastPosition(Index index);
// Multiplies all entries by factor.
// i.e. entry.coefficient *= factor.
void MultiplyByConstant(Fractional factor);
// Multiplies all entries by its corresponding factor,
// i.e. entry.coefficient *= factors[entry.index].
void ComponentWiseMultiply(const DenseVector& factors);
// Divides all entries by factor.
// i.e. entry.coefficient /= factor.
void DivideByConstant(Fractional factor);
// Divides all entries by its corresponding factor,
// i.e. entry.coefficient /= factors[entry.index].
void ComponentWiseDivide(const DenseVector& factors);
// Populates a dense vector from the sparse vector.
// Runs in O(num_indices) as the dense vector values have to be reset to 0.0.
void CopyToDenseVector(Index num_indices, DenseVector* dense_vector) const;
// Populates a dense vector from the permuted sparse vector.
// Runs in O(num_indices) as the dense vector values have to be reset to 0.0.
void PermutedCopyToDenseVector(const IndexPermutation& index_perm,
Index num_indices,
DenseVector* dense_vector) const;
// Performs the operation dense_vector += multiplier * this.
// This is known as multiply-accumulate or (fused) multiply-add.
void AddMultipleToDenseVector(Fractional multiplier,
DenseVector* dense_vector) const;
// WARNING: BOTH vectors (the current and the destination) MUST be "clean",
// i.e. sorted and without duplicates.
// Performs the operation accumulator_vector += multiplier * this, removing
// a given index which must be in both vectors, and pruning new entries whose
// absolute value are under the given drop_tolerance.
void AddMultipleToSparseVectorAndDeleteCommonIndex(
Fractional multiplier, Index removed_common_index,
Fractional drop_tolerance, SparseVector* accumulator_vector) const;
// Same as AddMultipleToSparseVectorAndDeleteCommonIndex() but instead of
// deleting the common index, leave it unchanged.
void AddMultipleToSparseVectorAndIgnoreCommonIndex(
Fractional multiplier, Index removed_common_index,
Fractional drop_tolerance, SparseVector* accumulator_vector) const;
// Applies the index permutation to all entries: index = index_perm[index];
void ApplyIndexPermutation(const IndexPermutation& index_perm);
// Same as ApplyIndexPermutation but deletes the index if index_perm[index]
// is negative.
void ApplyPartialIndexPermutation(const IndexPermutation& index_perm);
// Removes the entries for which index_perm[index] is non-negative and appends
// them to output. Note that the index of the entries are NOT permuted.
void MoveTaggedEntriesTo(const IndexPermutation& index_perm,
SparseVector* output);
// Returns the coefficient at position index.
// Call with care: runs in O(number-of-entries) as entries may not be sorted.
Fractional LookUpCoefficient(Index index) const;
// Note this method can only be used when the vector has no duplicates.
EntryIndex num_entries() const {
DCHECK(CheckNoDuplicates());
return EntryIndex(num_entries_);
}
// Returns the first entry's index and coefficient; note that 'first' doesn't
// mean 'entry with the smallest index'.
// Runs in O(1).
// Note this method can only be used when the vector has no duplicates.
Index GetFirstIndex() const {
DCHECK(CheckNoDuplicates());
return GetIndex(EntryIndex(0));
}
Fractional GetFirstCoefficient() const {
DCHECK(CheckNoDuplicates());
return GetCoefficient(EntryIndex(0));
}
// Like GetFirst*, but for the last entry.
Index GetLastIndex() const {
DCHECK(CheckNoDuplicates());
return GetIndex(num_entries() - 1);
}
Fractional GetLastCoefficient() const {
DCHECK(CheckNoDuplicates());
return GetCoefficient(num_entries() - 1);
}
// Allows to loop over the entry indices like this:
// for (const EntryIndex i : sparse_vector.AllEntryIndices()) { ... }
// TODO(user): consider removing this, in favor of the natural range
// iteration.
::util::IntegerRange<EntryIndex> AllEntryIndices() const {
return ::util::IntegerRange<EntryIndex>(EntryIndex(0), num_entries_);
}
// Returns true if this vector is exactly equal to the given one, i.e. all its
// index indices and coefficients appear in the same order and are equal.
bool IsEqualTo(const SparseVector& other) const;
// An exhaustive, pretty-printed listing of the entries, in their
// internal order. a.DebugString() == b.DebugString() iff a.IsEqualTo(b).
std::string DebugString() const;
protected:
// Adds a new entry to the sparse vector, growing the internal buffer if
// needed. It does not set may_contain_duplicates_ to true.
void AddEntry(Index index, Fractional value) {
DCHECK_GE(index, 0);
// Grow the internal storage if there is no space left for the new entry. We
// increase the size to max(4, 1.5*current capacity).
if (num_entries_ == capacity_) {
// Reserve(capacity_ == 0 ? EntryIndex(4)
// : EntryIndex(2 * capacity_.value()));
Reserve(capacity_ == 0 ? EntryIndex(4)
: EntryIndex(2 * capacity_.value()));
DCHECK_LT(num_entries_, capacity_);
}
const EntryIndex new_entry_index = num_entries_;
++num_entries_;
MutableIndex(new_entry_index) = index;
MutableCoefficient(new_entry_index) = value;
}
// Resizes the sparse vector to a smaller size, without re-allocating the
// internal storage.
void ResizeDown(EntryIndex new_size) {
DCHECK_GE(new_size, 0);
DCHECK_LE(new_size, num_entries_);
num_entries_ = new_size;
}
// Read-only access to the indices and coefficients of the entries of the
// sparse vector.
Index GetIndex(EntryIndex i) const {
DCHECK_GE(i, 0);
DCHECK_LT(i, num_entries_);
return index_[i.value()];
}
Fractional GetCoefficient(EntryIndex i) const {
DCHECK_GE(i, 0);
DCHECK_LT(i, num_entries_);
return coefficient_[i.value()];
}
// Mutable access to the indices and coefficients of the entries of the sparse
// vector.
Index& MutableIndex(EntryIndex i) {
DCHECK_GE(i, 0);
DCHECK_LT(i, num_entries_);
return index_[i.value()];
}
Fractional& MutableCoefficient(EntryIndex i) {
DCHECK_GE(i, 0);
DCHECK_LT(i, num_entries_);
return coefficient_[i.value()];
}
// The internal storage of the sparse vector. Both the indices and the
// coefficients are stored in the same buffer; the first
// sizeof(Index)*capacity_ bytes are used for storing the indices, the
// following sizeof(Fractional)*capacity_ bytes contain the values. This
// representation ensures that for small vectors, both the indices and the
// coefficients are in the same page/cache line.
// We use a single buffer for both arrays. The amount of data copied during
// relocations is the same in both cases, and it is much smaller than the cost
// of an additional allocation - especially when the vectors are small.
// Moreover, using two separate vectors/buffers would mean that even small
// vectors would be spread across at least two different cache lines.
std::unique_ptr<char[]> buffer_;
EntryIndex num_entries_;
EntryIndex capacity_;
// Pointers to the first elements of the index and coefficient arrays.
Index* index_;
Fractional* coefficient_;
// This is here to speed up the CheckNoDuplicates() methods and is mutable
// so we can perform checks on const argument.
mutable bool may_contain_duplicates_;
private:
// Actual implementation of AddMultipleToSparseVectorAndDeleteCommonIndex()
// and AddMultipleToSparseVectorAndIgnoreCommonIndex() which is shared.
void AddMultipleToSparseVectorInternal(
bool delete_common_index, Fractional multiplier, Index common_index,
Fractional drop_tolerance, SparseVector* accumulator_vector) const;
};
// --------------------------------------------------------
// SparseVectorEntry
// --------------------------------------------------------
// A reference-like class that points to a certain element of a sparse data
// structure that stores its elements in two parallel arrays. The main purpose
// of the entry class is to support implementation of iterator objects over the
// sparse data structure.
// Note that the entry object does not own the data, and it is valid only as
// long as the underlying sparse data structure; it may also be invalidated if
// the underlying sparse data structure is modified.
template <typename IndexType>
class SparseVectorEntry {
public:
using Index = IndexType;
Index index() const { return index_[i_.value()]; }
Fractional coefficient() const { return coefficient_[i_.value()]; }
protected:
// Creates the sparse vector entry from the given base pointers and the index.
// We accept the low-level data structures rather than a SparseVector
// reference to make it possible to use the SparseVectorEntry and
// SparseVectorIterator classes also for other data structures using the same
// internal data representation.
// Note that the constructor is intentionally made protected, so that the
// entry can be created only as a part of the construction of an iterator over
// a sparse data structure.
SparseVectorEntry(const Index* indices, const Fractional* coefficients,
EntryIndex i)
: i_(i), index_(indices), coefficient_(coefficients) {}
// The index of the sparse vector entry represented by this object.
EntryIndex i_;
// The index and coefficient arrays of the sparse vector.
// NOTE(user): Keeping directly the index and the base pointers gives the
// best performance with a tiny margin of the options:
// 1. keep the base pointers and an index of the current entry,
// 2. keep pointers to the current index and the current coefficient and
// increment both when moving the iterator.
// 3. keep a pointer to the sparse vector object and the index of the current
// entry.
const Index* index_;
const Fractional* coefficient_;
};
template <typename IndexType, typename IteratorType>
IteratorType SparseVector<IndexType, IteratorType>::begin() const {
return Iterator(this->index_, this->coefficient_, EntryIndex(0));
}
template <typename IndexType, typename IteratorType>
IteratorType SparseVector<IndexType, IteratorType>::end() const {
return Iterator(this->index_, this->coefficient_, num_entries_);
}
// --------------------------------------------------------
// SparseVector implementation
// --------------------------------------------------------
template <typename IndexType, typename IteratorType>
SparseVector<IndexType, IteratorType>::SparseVector()
: num_entries_(0),
capacity_(0),
index_(nullptr),
coefficient_(nullptr),
may_contain_duplicates_(false) {}
template <typename IndexType, typename IteratorType>
SparseVector<IndexType, IteratorType>::SparseVector(const SparseVector& other) {
PopulateFromSparseVector(other);
}
template <typename IndexType, typename IteratorType>
SparseVector<IndexType, IteratorType>&
SparseVector<IndexType, IteratorType>::operator=(const SparseVector& other) {
PopulateFromSparseVector(other);
return *this;
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::Clear() {
num_entries_ = EntryIndex(0);
may_contain_duplicates_ = false;
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::ClearAndRelease() {
capacity_ = EntryIndex(0);
num_entries_ = EntryIndex(0);
index_ = nullptr;
coefficient_ = nullptr;
buffer_.reset();
may_contain_duplicates_ = false;
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::Reserve(EntryIndex new_capacity) {
if (new_capacity <= capacity_) return;
// Round up the capacity to a multiple of four. This way, the start of the
// coefficient array will be aligned to 16-bytes, provided that the buffer
// used for storing the data is aligned in that way.
if (new_capacity.value() & 3) {
new_capacity += EntryIndex(4 - (new_capacity.value() & 3));
}
const size_t index_buffer_size = new_capacity.value() * sizeof(Index);
const size_t value_buffer_size = new_capacity.value() * sizeof(Fractional);
const size_t new_buffer_size = index_buffer_size + value_buffer_size;
std::unique_ptr<char[]> new_buffer(new char[new_buffer_size]);
IndexType* const new_index = reinterpret_cast<Index*>(new_buffer.get());
Fractional* const new_coefficient =
reinterpret_cast<Fractional*>(new_index + new_capacity.value());
// Avoid copying the data if the vector is empty.
if (num_entries_ > 0) {
// NOTE(user): We use memmove instead of std::copy, because the latter
// leads to naive copying code when used with strong ints (a loop that
// copies a single 32-bit value in each iteration), and as of 06/2016,
// memmove is 3-4x faster on Haswell.
std::memmove(new_index, index_, sizeof(IndexType) * num_entries_.value());
std::memmove(new_coefficient, coefficient_,
sizeof(Fractional) * num_entries_.value());
}
std::swap(buffer_, new_buffer);
index_ = new_index;
coefficient_ = new_coefficient;
capacity_ = new_capacity;
}
template <typename IndexType, typename IteratorType>
bool SparseVector<IndexType, IteratorType>::IsEmpty() const {
return num_entries_ == EntryIndex(0);
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::Swap(SparseVector* other) {
std::swap(buffer_, other->buffer_);
std::swap(num_entries_, other->num_entries_);
std::swap(capacity_, other->capacity_);
std::swap(may_contain_duplicates_, other->may_contain_duplicates_);
std::swap(index_, other->index_);
std::swap(coefficient_, other->coefficient_);
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::CleanUp() {
// TODO(user): Implement in-place sorting of the entries and cleanup. The
// current version converts the data to an array-of-pairs representation that
// can be sorted easily with std::stable_sort, and the converts the sorted
// data back to the struct-of-arrays implementation.
// The current version is ~20% slower than the in-place sort on the
// array-of-struct representation. It is not visible on GLOP benchmarks, but
// it increases peak memory usage by ~8%.
// Implementing in-place search will require either implementing a custom
// sorting code, or custom iterators that abstract away the internal
// representation.
std::vector<std::pair<Index, Fractional>> entries;
entries.reserve(num_entries_.value());
for (EntryIndex i(0); i < num_entries_; ++i) {
entries.emplace_back(GetIndex(i), GetCoefficient(i));
}
std::stable_sort(
entries.begin(), entries.end(),
[](const std::pair<Index, Fractional>& a,
const std::pair<Index, Fractional>& b) { return a.first < b.first; });
EntryIndex new_size(0);
for (int i = 0; i < num_entries_; ++i) {
const std::pair<Index, Fractional> entry = entries[i];
if (entry.second == 0.0) continue;
if (i + 1 == num_entries_ || entry.first != entries[i + 1].first) {
MutableIndex(new_size) = entry.first;
MutableCoefficient(new_size) = entry.second;
++new_size;
}
}
ResizeDown(new_size);
may_contain_duplicates_ = false;
}
template <typename IndexType, typename IteratorType>
bool SparseVector<IndexType, IteratorType>::IsCleanedUp() const {
Index previous_index(-1);
for (const EntryIndex i : AllEntryIndices()) {
const Index index = GetIndex(i);
if (index <= previous_index || GetCoefficient(i) == 0.0) return false;
previous_index = index;
}
may_contain_duplicates_ = false;
return true;
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::PopulateFromSparseVector(
const SparseVector& sparse_vector) {
// Clear the sparse vector before reserving the new capacity. If we didn't do
// this, Reserve would have to copy the current contents of the vector if it
// allocated a new buffer. This would be wasteful, since we overwrite it in
// the next step anyway.
Clear();
Reserve(sparse_vector.capacity_);
// If there are no entries, then sparse_vector.index_ or .coefficient_
// may be nullptr or invalid, and accessing them in memmove is UB,
// even if the moved size is zero.
if (sparse_vector.num_entries_ > 0) {
// NOTE(user): Using a single memmove would be slightly faster, but it
// would not work correctly if this already had a greater capacity than
// sparse_vector, because the coefficient_ pointer would be positioned
// incorrectly.
std::memmove(index_, sparse_vector.index_,
sizeof(Index) * sparse_vector.num_entries_.value());
std::memmove(coefficient_, sparse_vector.coefficient_,
sizeof(Fractional) * sparse_vector.num_entries_.value());
}
num_entries_ = sparse_vector.num_entries_;
may_contain_duplicates_ = sparse_vector.may_contain_duplicates_;
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::PopulateFromDenseVector(
const DenseVector& dense_vector) {
Clear();
const Index num_indices(dense_vector.size());
for (Index index(0); index < num_indices; ++index) {
if (dense_vector[index] != 0.0) {
SetCoefficient(index, dense_vector[index]);
}
}
may_contain_duplicates_ = false;
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::AppendEntriesWithOffset(
const SparseVector& sparse_vector, Index offset) {
for (const EntryIndex i : sparse_vector.AllEntryIndices()) {
const Index new_index = offset + sparse_vector.GetIndex(i);
DCHECK_GE(new_index, 0);
AddEntry(new_index, sparse_vector.GetCoefficient(i));
}
may_contain_duplicates_ = true;
}
template <typename IndexType, typename IteratorType>
bool SparseVector<IndexType, IteratorType>::CheckNoDuplicates(
StrictITIVector<IndexType, bool>* boolean_vector) const {
RETURN_VALUE_IF_NULL(boolean_vector, false);
// Note(user): Using num_entries() or any function that call
// CheckNoDuplicates() again will cause an infinite loop!
if (!may_contain_duplicates_ || num_entries_ <= 1) return true;
// Update size if needed.
const Index max_index =
*std::max_element(index_, index_ + num_entries_.value());
if (boolean_vector->size() <= max_index) {
boolean_vector->resize(max_index + 1, false);
}
may_contain_duplicates_ = false;
for (const EntryIndex i : AllEntryIndices()) {
const Index index = GetIndex(i);
if ((*boolean_vector)[index]) {
may_contain_duplicates_ = true;
break;
}
(*boolean_vector)[index] = true;
}
// Reset boolean_vector to false.
for (const EntryIndex i : AllEntryIndices()) {
(*boolean_vector)[GetIndex(i)] = false;
}
return !may_contain_duplicates_;
}
template <typename IndexType, typename IteratorType>
bool SparseVector<IndexType, IteratorType>::CheckNoDuplicates() const {
// Using num_entries() or any function in that will call CheckNoDuplicates()
// again will cause an infinite loop!
if (!may_contain_duplicates_ || num_entries_ <= 1) return true;
StrictITIVector<Index, bool> boolean_vector;
return CheckNoDuplicates(&boolean_vector);
}
// Do not filter out zero values, as a zero value can be added to reset a
// previous value. Zero values and duplicates will be removed by CleanUp.
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::SetCoefficient(Index index,
Fractional value) {
AddEntry(index, value);
may_contain_duplicates_ = true;
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::DeleteEntry(Index index) {
DCHECK(CheckNoDuplicates());
EntryIndex i(0);
const EntryIndex end(num_entries());
while (i < end && GetIndex(i) != index) {
++i;
}
if (i == end) return;
const int num_moved_entries = (num_entries_ - i).value() - 1;
std::memmove(index_ + i.value(), index_ + i.value() + 1,
sizeof(Index) * num_moved_entries);
std::memmove(coefficient_ + i.value(), coefficient_ + i.value() + 1,
sizeof(Fractional) * num_moved_entries);
--num_entries_;
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::RemoveNearZeroEntries(
Fractional threshold) {
DCHECK(CheckNoDuplicates());
EntryIndex new_index(0);
for (const EntryIndex i : AllEntryIndices()) {
const Fractional magnitude = fabs(GetCoefficient(i));
if (magnitude > threshold) {
MutableIndex(new_index) = GetIndex(i);
MutableCoefficient(new_index) = GetCoefficient(i);
++new_index;
}
}
ResizeDown(new_index);
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::RemoveNearZeroEntriesWithWeights(
Fractional threshold, const DenseVector& weights) {
DCHECK(CheckNoDuplicates());
EntryIndex new_index(0);
for (const EntryIndex i : AllEntryIndices()) {
if (fabs(GetCoefficient(i)) * weights[GetIndex(i)] > threshold) {
MutableIndex(new_index) = GetIndex(i);
MutableCoefficient(new_index) = GetCoefficient(i);
++new_index;
}
}
ResizeDown(new_index);
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::MoveEntryToFirstPosition(
Index index) {
DCHECK(CheckNoDuplicates());
for (const EntryIndex i : AllEntryIndices()) {
if (GetIndex(i) == index) {
std::swap(MutableIndex(EntryIndex(0)), MutableIndex(i));
std::swap(MutableCoefficient(EntryIndex(0)), MutableCoefficient(i));
return;
}
}
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::MoveEntryToLastPosition(
Index index) {
DCHECK(CheckNoDuplicates());
const EntryIndex last_entry = num_entries() - 1;
for (const EntryIndex i : AllEntryIndices()) {
if (GetIndex(i) == index) {
std::swap(MutableIndex(last_entry), MutableIndex(i));
std::swap(MutableCoefficient(last_entry), MutableCoefficient(i));
return;
}
}
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::MultiplyByConstant(
Fractional factor) {
for (const EntryIndex i : AllEntryIndices()) {
MutableCoefficient(i) *= factor;
}
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::ComponentWiseMultiply(
const DenseVector& factors) {
for (const EntryIndex i : AllEntryIndices()) {
MutableCoefficient(i) *= factors[GetIndex(i)];
}
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::DivideByConstant(
Fractional factor) {
for (const EntryIndex i : AllEntryIndices()) {
MutableCoefficient(i) /= factor;
}
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::ComponentWiseDivide(
const DenseVector& factors) {
for (const EntryIndex i : AllEntryIndices()) {
MutableCoefficient(i) /= factors[GetIndex(i)];
}
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::CopyToDenseVector(
Index num_indices, DenseVector* dense_vector) const {
RETURN_IF_NULL(dense_vector);
dense_vector->AssignToZero(num_indices);
for (const EntryIndex i : AllEntryIndices()) {
(*dense_vector)[GetIndex(i)] = GetCoefficient(i);
}
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::PermutedCopyToDenseVector(
const IndexPermutation& index_perm, Index num_indices,
DenseVector* dense_vector) const {
RETURN_IF_NULL(dense_vector);
dense_vector->AssignToZero(num_indices);
for (const EntryIndex i : AllEntryIndices()) {
(*dense_vector)[index_perm[GetIndex(i)]] = GetCoefficient(i);
}
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::AddMultipleToDenseVector(
Fractional multiplier, DenseVector* dense_vector) const {
RETURN_IF_NULL(dense_vector);
if (multiplier == 0.0) return;
for (const EntryIndex i : AllEntryIndices()) {
(*dense_vector)[GetIndex(i)] += multiplier * GetCoefficient(i);
}
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::
AddMultipleToSparseVectorAndDeleteCommonIndex(
Fractional multiplier, Index removed_common_index,
Fractional drop_tolerance, SparseVector* accumulator_vector) const {
AddMultipleToSparseVectorInternal(true, multiplier, removed_common_index,
drop_tolerance, accumulator_vector);
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::
AddMultipleToSparseVectorAndIgnoreCommonIndex(
Fractional multiplier, Index removed_common_index,
Fractional drop_tolerance, SparseVector* accumulator_vector) const {
AddMultipleToSparseVectorInternal(false, multiplier, removed_common_index,
drop_tolerance, accumulator_vector);
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::AddMultipleToSparseVectorInternal(
bool delete_common_index, Fractional multiplier, Index common_index,
Fractional drop_tolerance, SparseVector* accumulator_vector) const {
// DCHECK that the input is correct.
DCHECK(IsCleanedUp());
DCHECK(accumulator_vector->IsCleanedUp());
DCHECK(CheckNoDuplicates());
DCHECK(accumulator_vector->CheckNoDuplicates());
DCHECK_NE(0.0, LookUpCoefficient(common_index));
DCHECK_NE(0.0, accumulator_vector->LookUpCoefficient(common_index));
// Implementation notes: we create a temporary SparseVector "c" to hold the
// result. We call "a" the first vector (i.e. the current object, which will
// be multiplied by "multiplier"), and "b" the second vector (which will be
// swapped with "c" at the end to hold the result).
// We incrementally build c as: a * multiplier + b.
const SparseVector& a = *this;
const SparseVector& b = *accumulator_vector;
SparseVector c;
EntryIndex ia(0); // Index in the vector "a"
EntryIndex ib(0); // ... and "b"
EntryIndex ic(0); // ... and "c"
const EntryIndex size_a = a.num_entries();
const EntryIndex size_b = b.num_entries();
const int size_adjustment = delete_common_index ? -2 : 0;
const EntryIndex new_size_upper_bound = size_a + size_b + size_adjustment;
c.Reserve(new_size_upper_bound);
c.num_entries_ = new_size_upper_bound;
while ((ia < size_a) && (ib < size_b)) {
const Index index_a = a.GetIndex(ia);
const Index index_b = b.GetIndex(ib);
// Benchmarks done by fdid@ in 2012 showed that it was faster to put the
// "if" clauses in that specific order.
if (index_a == index_b) {
if (index_a != common_index) {
const Fractional a_coeff_mul = multiplier * a.GetCoefficient(ia);
const Fractional b_coeff = b.GetCoefficient(ib);
const Fractional sum = a_coeff_mul + b_coeff;
// We do not want to leave near-zero entries.
// TODO(user): expose the tolerance used here.
if (std::abs(sum) > drop_tolerance) {
c.MutableIndex(ic) = index_a;
c.MutableCoefficient(ic) = sum;
++ic;
}
} else if (!delete_common_index) {
c.MutableIndex(ic) = b.GetIndex(ib);
c.MutableCoefficient(ic) = b.GetCoefficient(ib);
++ic;
}
++ia;
++ib;
} else if (index_a < index_b) {
c.MutableIndex(ic) = index_a;
c.MutableCoefficient(ic) = multiplier * a.GetCoefficient(ia);
++ia;
++ic;
} else { // index_b < index_a
c.MutableIndex(ic) = b.GetIndex(ib);
c.MutableCoefficient(ic) = b.GetCoefficient(ib);
++ib;
++ic;
}
}
while (ia < size_a) {
c.MutableIndex(ic) = a.GetIndex(ia);
c.MutableCoefficient(ic) = multiplier * a.GetCoefficient(ia);
++ia;
++ic;
}
while (ib < size_b) {
c.MutableIndex(ic) = b.GetIndex(ib);
c.MutableCoefficient(ic) = b.GetCoefficient(ib);
++ib;
++ic;
}
c.ResizeDown(ic);
c.may_contain_duplicates_ = false;
c.Swap(accumulator_vector);
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::ApplyIndexPermutation(
const IndexPermutation& index_perm) {
for (const EntryIndex i : AllEntryIndices()) {
MutableIndex(i) = index_perm[GetIndex(i)];
}
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::ApplyPartialIndexPermutation(
const IndexPermutation& index_perm) {
EntryIndex new_index(0);
for (const EntryIndex i : AllEntryIndices()) {
const Index index = GetIndex(i);
if (index_perm[index] >= 0) {
MutableIndex(new_index) = index_perm[index];
MutableCoefficient(new_index) = GetCoefficient(i);
++new_index;
}
}
ResizeDown(new_index);
}
template <typename IndexType, typename IteratorType>
void SparseVector<IndexType, IteratorType>::MoveTaggedEntriesTo(
const IndexPermutation& index_perm, SparseVector* output) {
// Note that this function is called many times, so performance does matter
// and it is why we optimized the "nothing to do" case.
const EntryIndex end(num_entries_);
EntryIndex i(0);
while (true) {
if (i >= end) return; // "nothing to do" case.
if (index_perm[GetIndex(i)] >= 0) break;
++i;
}
output->AddEntry(GetIndex(i), GetCoefficient(i));
for (EntryIndex j(i + 1); j < end; ++j) {
if (index_perm[GetIndex(j)] < 0) {
MutableIndex(i) = GetIndex(j);
MutableCoefficient(i) = GetCoefficient(j);
++i;
} else {
output->AddEntry(GetIndex(j), GetCoefficient(j));
}
}
ResizeDown(i);
// TODO(user): In the way we use this function, we know that will not
// happen, but it is better to be careful so we can check that properly in
// debug mode.
output->may_contain_duplicates_ = true;
}
template <typename IndexType, typename IteratorType>
Fractional SparseVector<IndexType, IteratorType>::LookUpCoefficient(
Index index) const {
Fractional value(0.0);
for (const EntryIndex i : AllEntryIndices()) {
if (GetIndex(i) == index) {
// Keep in mind the vector may contains several entries with the same
// index. In such a case the last one is returned.
// TODO(user): investigate whether an optimized version of
// LookUpCoefficient for "clean" columns yields speed-ups.
value = GetCoefficient(i);
}
}
return value;
}
template <typename IndexType, typename IteratorType>
bool SparseVector<IndexType, IteratorType>::IsEqualTo(
const SparseVector& other) const {
// We do not take into account the mutable value may_contain_duplicates_.
if (num_entries() != other.num_entries()) return false;
for (const EntryIndex i : AllEntryIndices()) {
if (GetIndex(i) != other.GetIndex(i)) return false;
if (GetCoefficient(i) != other.GetCoefficient(i)) return false;
}
return true;
}
template <typename IndexType, typename IteratorType>
std::string SparseVector<IndexType, IteratorType>::DebugString() const {
std::string s;
for (const EntryIndex i : AllEntryIndices()) {
if (i != 0) s += ", ";
absl::StrAppendFormat(&s, "[%d]=%g", GetIndex(i).value(),
GetCoefficient(i));
}
return s;
}
} // namespace glop
} // namespace operations_research
#endif // OR_TOOLS_LP_DATA_SPARSE_VECTOR_H_