Program Listing for File index.hpp¶
↰ Return to documentation for file (SeQuant/core/index.hpp)
//
// Created by Eduard Valeyev on 3/20/18.
//
#ifndef SEQUANT_INDEX_H
#define SEQUANT_INDEX_H
#include <SeQuant/core/container.hpp>
#include <SeQuant/core/hash.hpp>
#include <SeQuant/core/index_space_registry.hpp>
#include <SeQuant/core/space.hpp>
#include <SeQuant/core/tag.hpp>
#include <SeQuant/core/utility/macros.hpp>
#include <SeQuant/core/utility/string.hpp>
#include <SeQuant/core/utility/swap.hpp>
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <cwchar>
#include <functional>
#include <initializer_list>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include <range/v3/all.hpp>
// change to 1 to make thread-safe
#define SEQUANT_INDEX_THREADSAFE 1
namespace sequant {
class Index;
using WstrList = std::initializer_list<std::wstring_view>;
using IndexList = std::initializer_list<Index>;
template <typename T>
concept index_or_index_label =
(std::is_same_v<std::remove_cvref_t<T>, Index> ||
meta::is_basic_string_convertible_v<std::remove_cvref_t<T>>);
template <typename T>
concept range_of_castables_to_index =
(meta::is_statically_castable_v<meta::range_value_t<T>, Index>);
// clang-format off
// clang-format on
class Index : public Taggable {
public:
using ordinal_type = std::uint64_t;
const static IndexSpace::Attr default_space_attr;
private:
static auto &tmp_index_accessor() {
// initialized so that the first call to next_tmp_index will return
// min_tmp_index()
static std::atomic<std::size_t> index = min_tmp_index() - 1;
return index;
}
std::optional<ordinal_type> to_ordinal(meta::integral auto i) noexcept {
if constexpr (std::is_signed_v<decltype(i)>) {
SEQUANT_ASSERT(i >= 0);
}
if constexpr (std::numeric_limits<decltype(i)>::max() >
std::numeric_limits<ordinal_type>::max()) {
SEQUANT_ASSERT(i <= std::numeric_limits<ordinal_type>::max());
}
return static_cast<ordinal_type>(i);
}
public:
using index_vector = container::vector<Index>;
static std::optional<std::size_t> get_ordinal(std::wstring_view label) {
const auto underscore_position = label.rfind(L'_');
if (underscore_position != std::wstring::npos) {
SEQUANT_ASSERT(underscore_position + 1 <
label.size()); // check that there is at least one char
// past the underscore
return std::wcstol(
label.substr(underscore_position + 1, std::wstring::npos).data(),
NULL, 10);
} else
return {};
}
Index() = default;
const static Index null;
Index(const Index &idx) noexcept
: Taggable(idx),
space_(idx.space_),
ordinal_(idx.ordinal_),
proto_indices_(idx.proto_indices_),
symmetric_proto_indices_(idx.symmetric_proto_indices_) {}
Index(Index &&idx) noexcept
: Taggable(std::move(idx)),
space_(std::move(idx.space_)),
ordinal_(std::move(idx.ordinal_)),
proto_indices_(std::move(idx.proto_indices_)),
symmetric_proto_indices_(idx.symmetric_proto_indices_),
label_(std::move(idx.label_)),
full_label_(std::move(idx.full_label_)) {
idx.symmetric_proto_indices_ = true;
// moving std::optional surprisingly leaves a nonnull std::optional in its
// wake
idx.ordinal_ = std::nullopt;
idx.label_ = std::nullopt;
idx.full_label_ = std::nullopt;
}
Index &operator=(const Index &idx) {
Taggable::operator=(idx);
space_ = idx.space_;
ordinal_ = idx.ordinal_;
proto_indices_ = idx.proto_indices_;
symmetric_proto_indices_ = idx.symmetric_proto_indices_;
// We might not copy memoized data, but we do have to reset it or else it
// might end up being wrong
label_.reset();
full_label_.reset();
return *this;
}
Index &operator=(Index &&idx) noexcept {
static_cast<Taggable &>(*this) = static_cast<Taggable &&>(idx);
space_ = std::move(idx.space_);
ordinal_ = std::move(idx.ordinal_);
proto_indices_ = std::move(idx.proto_indices_);
symmetric_proto_indices_ = idx.symmetric_proto_indices_;
label_ = std::move(idx.label_);
full_label_ = std::move(idx.full_label_);
idx.symmetric_proto_indices_ = true;
// moving std::optional surprisingly leaves a nonnull std::optional in its
// wake
idx.ordinal_ = std::nullopt;
idx.label_ = std::nullopt;
idx.full_label_ = std::nullopt;
return *this;
}
explicit Index(const IndexSpace &space) : Taggable(), space_(space) {
check_nonreserved();
}
Index(const IndexSpace &space, meta::integral auto ord)
: space_(space), ordinal_(to_ordinal(ord)) {
check_nonreserved();
}
template <meta::integral I>
Index(const IndexSpace &space, std::optional<I> ord)
: space_(space), ordinal_(ord) {
check_nonreserved();
}
Index(const IndexSpace &space, container::vector<Index> proto_indices,
bool symmetric_proto_indices = true)
: Index(space) {
proto_indices_ = std::move(proto_indices);
symmetric_proto_indices_ = symmetric_proto_indices;
canonicalize_proto_indices();
validate_proto_indices();
}
Index(const IndexSpace &space, meta::integral auto ord,
IndexList proto_indices, bool symmetric_proto_indices = true)
: Index(space, ord) {
proto_indices_ = std::move(proto_indices);
symmetric_proto_indices_ = symmetric_proto_indices;
canonicalize_proto_indices();
validate_proto_indices();
}
template <meta::integral I>
Index(const IndexSpace &space, std::optional<I> ord, IndexList proto_indices,
bool symmetric_proto_indices = true)
: Index(space, ord) {
proto_indices_ = std::move(proto_indices);
symmetric_proto_indices_ = symmetric_proto_indices;
canonicalize_proto_indices();
validate_proto_indices();
}
Index(const IndexSpace &space, IndexList proto_indices,
bool symmetric_proto_indices = true)
: Index(space) {
proto_indices_ = std::move(proto_indices);
symmetric_proto_indices_ = symmetric_proto_indices;
canonicalize_proto_indices();
validate_proto_indices();
}
Index(const IndexSpace &space, meta::integral auto ord,
container::vector<Index> proto_indices,
bool symmetric_proto_indices = true)
: Index(space, ord) {
proto_indices_ = std::move(proto_indices);
symmetric_proto_indices_ = symmetric_proto_indices;
canonicalize_proto_indices();
validate_proto_indices();
}
template <meta::integral I>
Index(const IndexSpace &space, std::optional<I> ord,
container::vector<Index> proto_indices,
bool symmetric_proto_indices = true)
: Index(space, ord) {
proto_indices_ = std::move(proto_indices);
symmetric_proto_indices_ = symmetric_proto_indices;
canonicalize_proto_indices();
validate_proto_indices();
}
template <basic_string_convertible String>
Index(String &&label)
: Index(obtain_default_index_registry()
? obtain_default_index_registry()->retrieve(label)
: IndexSpace{base_label(label), IndexSpace::Type::reserved,
IndexSpace::QuantumNumbers::reserved},
to_ordinal(label), {}) {
check_nonreserved();
if constexpr (std::is_same_v<String, std::wstring>) {
label_ = std::move(label);
}
}
template <index_or_index_label IndexOrIndexLabel, typename I>
Index(IndexOrIndexLabel &&index_or_index_label,
std::initializer_list<I> proto_indices,
bool symmetric_proto_indices = true)
: symmetric_proto_indices_(symmetric_proto_indices) {
if constexpr (!std::is_same_v<std::decay_t<IndexOrIndexLabel>, Index>) {
auto index = Index(index_or_index_label); // give index_or_index_label by
// ref to avoid scavenging it
space_ = index.space();
ordinal_ = index.ordinal_;
if constexpr (!std::is_reference_v<IndexOrIndexLabel>)
label_ = std::move(index_or_index_label);
} else {
space_ = index_or_index_label.space();
ordinal_ = index_or_index_label.ordinal_;
}
if constexpr (!std::is_same_v<std::decay_t<I>, Index>) {
if (proto_indices.size() != 0) {
proto_indices_.reserve(proto_indices.size());
for (const auto &plabel : proto_indices)
proto_indices_.push_back(Index(plabel));
}
} else
proto_indices_ = std::move(proto_indices);
canonicalize_proto_indices();
validate_proto_indices();
}
template <index_or_index_label IndexOrIndexLabel, typename IndexContainer,
typename = std::enable_if_t<std::is_convertible_v<
std::remove_cvref_t<IndexContainer>, container::vector<Index>>>>
Index(IndexOrIndexLabel &&index_or_index_label,
IndexContainer &&proto_indices, bool symmetric_proto_indices = true)
: proto_indices_(std::forward<IndexContainer>(proto_indices)),
symmetric_proto_indices_(symmetric_proto_indices) {
if constexpr (!std::is_same_v<std::decay_t<IndexOrIndexLabel>, Index>) {
auto index = Index(index_or_index_label); // give index_or_index_label by
// ref to avoid scavenging it
space_ = index.space();
ordinal_ = index.ordinal_;
if constexpr (!std::is_reference_v<IndexOrIndexLabel>)
label_ = std::move(index_or_index_label);
} else {
space_ = index_or_index_label.space();
ordinal_ = index_or_index_label.ordinal_;
}
canonicalize_proto_indices();
validate_proto_indices();
}
template <typename IndexOrIndexLabel>
Index(IndexOrIndexLabel &&index_or_index_label, IndexSpace space) {
if constexpr (std::is_same_v<std::decay_t<IndexOrIndexLabel>, Index>) {
*this = std::forward<IndexOrIndexLabel>(index_or_index_label);
space_ = std::move(space);
} else {
space_ = std::move(space);
ordinal_ = to_ordinal(index_or_index_label);
check_nonreserved();
}
}
explicit operator bool() const { return *this != null; }
bool nonnull() const { return static_cast<bool>(*this); }
[[nodiscard]] Index replace_space(IndexSpace space) const {
return Index(*this, std::move(space));
}
[[nodiscard]] Index replace_qns(QuantumNumbersAttr qns) const {
return Index(*this, IndexSpace(this->space().base_key(),
this->space().attr(), std::move(qns)));
}
Taggable &tag() { return static_cast<Taggable &>(*this); }
const Taggable &tag() const { return static_cast<const Taggable &>(*this); }
void reset_tag() const {
this->tag().reset();
ranges::for_each(proto_indices_,
[](const Index &idx) { idx.tag().reset(); });
}
static Index make_tmp_index(const IndexSpace &space) {
return Index(space, next_tmp_index(), IndexFactoryTag{});
}
template <typename IndexRange, typename = std::enable_if_t<meta::is_range_v<
std::remove_reference_t<IndexRange>>>>
static Index make_tmp_index(const IndexSpace &space,
IndexRange &&proto_indices,
bool symmetric_proto_indices = true) {
Index result(space, next_tmp_index(), IndexFactoryTag{});
if constexpr (std::is_convertible_v<std::remove_reference_t<IndexRange>,
Index::index_vector>) {
result.proto_indices_ = std::forward<IndexRange>(proto_indices);
} else {
result.proto_indices_ = proto_indices | ranges::to<Index::index_vector>;
}
result.symmetric_proto_indices_ = symmetric_proto_indices;
result.canonicalize_proto_indices();
result.validate_proto_indices();
return result;
}
static std::wstring base_label(std::wstring_view label) {
auto underscore_position = label.find(L'_');
if (underscore_position == std::wstring::npos)
return {label.begin(), label.end()};
else
return {label.data(), label.data() + underscore_position};
}
static std::wstring base_label(std::string_view label) {
auto underscore_position = label.find('_');
if (underscore_position == std::string::npos)
return to_wstring(std::string({label.begin(), label.end()}));
else
return to_wstring(
std::string({label.data(), label.data() + underscore_position}));
}
template <typename Char, typename = std::enable_if_t<meta::is_char_v<Char>>>
static std::wstring base_label(Char label) {
return to_wstring(label);
}
std::wstring_view label() const {
if (!label_) {
label_ = space_.base_key();
if (ordinal_) {
*label_ += L'_';
*label_ += std::to_wstring(*ordinal_);
}
}
return *label_;
}
std::optional<ordinal_type> ordinal() const noexcept { return ordinal_; }
[[deprecated(
"use to_string to produce TiledArray-compatible index label "
"representation")]] std::string
ascii_label() const;
std::string to_string() const;
std::wstring_view full_label() const {
if (!has_proto_indices()) return label();
if (full_label_) return *full_label_;
std::wstring result(label());
result += L"<";
using namespace std::literals;
result +=
ranges::views::transform(proto_indices_,
[](const Index &idx) -> std::wstring_view {
return idx.full_label();
}) |
ranges::views::join(L", "sv) | ranges::to<std::wstring>();
result += L">";
full_label_ = result;
return *full_label_;
}
template <typename WS, typename = std::enable_if_t<(
meta::is_wstring_convertible_v<std::decay_t<WS>>)>>
[[nodiscard]] std::wstring make_label_plus_suffix(WS &&suffix) const {
return Index::make_label_plus_suffix(this->label(),
std::forward<WS>(suffix));
}
template <typename WS1, typename WS2,
typename = std::enable_if_t<
(meta::is_wstring_or_view_v<std::decay_t<WS1>> &&
meta::is_wstring_convertible_v<std::decay_t<WS2>>)>>
[[nodiscard]] static std::wstring make_label_plus_suffix(WS1 &&label,
WS2 &&suffix) {
auto underscore_position = label.find(L'_');
std::wstring result;
if (underscore_position == std::wstring::npos) {
result = std::forward<WS1>(label);
result += suffix;
} else {
result = label.substr(0, underscore_position);
result += suffix;
result += label.substr(underscore_position);
}
return result;
}
template <typename WS, typename = std::enable_if_t<(
meta::is_wstring_convertible_v<std::decay_t<WS>>)>>
[[nodiscard]] std::wstring make_label_minus_substring(WS &&substr) const {
return Index::make_label_minus_substring(this->label(),
std::forward<WS>(substr));
}
template <typename WS1, typename WS2,
typename = std::enable_if_t<
(meta::is_wstring_or_view_v<std::decay_t<WS1>> &&
meta::is_wstring_convertible_v<std::decay_t<WS2>>)>>
[[nodiscard]] static std::wstring make_label_minus_substring(WS1 &&label,
WS2 &&substr) {
auto underscore_position = label.find(L'_');
std::wstring result;
auto erase = [](auto &result, const auto &substr) {
auto pos = result.find(substr);
if (pos != std::wstring::npos) {
if constexpr (std::is_same_v<std::decay_t<WS2>, std::wstring> ||
std::is_same_v<std::decay_t<WS2>, std::wstring_view>) {
result.erase(pos, substr.size());
} else if constexpr (std::is_same_v<std::decay_t<WS2>,
const wchar_t[]> ||
std::is_same_v<std::decay_t<WS2>, wchar_t[]> ||
std::is_same_v<std::decay_t<WS2>,
const wchar_t *> ||
std::is_same_v<std::decay_t<WS2>, wchar_t *>) {
result.erase(pos, std::strlen(substr));
} else {
result.erase(pos, 1);
}
}
};
if (underscore_position == std::wstring::npos) {
result = std::forward<WS1>(label);
erase(result, substr);
} else {
result = label.substr(0, underscore_position);
erase(result, substr);
result += label.substr(underscore_position);
}
return result;
}
const IndexSpace &space() const noexcept { return space_; }
bool has_proto_indices() const noexcept { return !proto_indices_.empty(); }
const index_vector &proto_indices() const noexcept { return proto_indices_; }
bool symmetric_proto_indices() const noexcept {
return symmetric_proto_indices_;
}
Index drop_proto_indices() const noexcept {
return Index(this->label(), this->space());
}
std::wstring to_latex() const noexcept;
/*template <typename... Attrs>
std::wstring to_wolfram(Attrs &&...attrs) const {
auto protect_subscript = [](const std::wstring_view str) {
auto subsc_pos = str.rfind(L'_');
if (subsc_pos == std::wstring_view::npos)
return std::wstring(str);
else {
SEQUANT_ASSERT(subsc_pos + 1 < str.size());
std::wstring result = L"\\!\\(\\*SubscriptBox[\\(";
result += std::wstring(str.substr(0, subsc_pos));
result += L"\\), \\(";
result += std::wstring(str.substr(subsc_pos + 1));
result += L"\\)]\\)";
return result;
}
};
using namespace std::literals;
std::wstring result =
L"particleIndex[\""s + protect_subscript(this->label()) + L"\"";
if (this->has_proto_indices()) {
SEQUANT_ASSERT(false && "not yet supported");
}
using namespace std::literals;
result += L","s + ::sequant::to_wolfram(space());
((result += ((L","s + ::sequant::to_wolfram(std::forward<Attrs>(attrs))))),
...);
result += L"]";
return result;
}*/
template <typename Range>
static auto proto_indices_color(const Range &protoindex_range) noexcept {
auto space_attr_view =
protoindex_range | ranges::views::transform([](const Index &idx) {
return static_cast<int64_t>(idx.space().attr());
});
return hash::range(ranges::begin(space_attr_view),
ranges::end(space_attr_view));
}
auto proto_indices_color() const {
return proto_indices_color(proto_indices_);
}
auto color() const {
if (has_proto_indices()) {
auto result = proto_indices_color();
hash::combine(result, int64_t(space().attr()));
return result;
} else {
auto result = hash::value(int64_t(space().attr()));
return result;
}
}
static std::size_t min_tmp_index() noexcept;
static std::size_t next_tmp_index() noexcept {
return ++tmp_index_accessor();
}
static void reset_tmp_index() noexcept;
// clang-format off
// clang-format off
template <template <typename, typename, typename... Args> class Map,
typename... Args>
bool transform(const Map<Index, Index, Args...> &index_map) noexcept {
bool mutated = false;
// outline:
// - try replacing this first
// - if this is replaced by an index with protoindices, the protoindices
// should not be tagged since they are original and may need to be
// replaced also
// - if not found, try replacing protoindices
// - if protoindices mutated, try replacing this again
// is this tagged already? if yes, can't skip, the protoindices may need to
// be transformed also
const auto this_is_tagged = this->tag().has_value();
// sanity check that tag = 0
if (this_is_tagged) {
SEQUANT_ASSERT(this->tag().value<int>() == 0);
} else { // only try replacing this if not already tagged
auto it = index_map.find(*this);
if (it != index_map.end()) {
*this = it->second;
this->tag().assign(0);
mutated = true;
}
}
if (!mutated) {
bool proto_indices_transformed = false;
for (auto &&subidx : proto_indices_) {
SEQUANT_ASSERT(!subidx.has_proto_indices());
if (subidx.transform(index_map)) proto_indices_transformed = true;
}
if (proto_indices_transformed) {
mutated = true;
canonicalize_proto_indices();
if (!this_is_tagged) { // if protoindices were mutated, try again, but
// only if no tag yet
auto it = index_map.find(*this);
if (it != index_map.end()) {
*this = it->second;
this->tag().assign(0);
mutated = true;
}
}
}
}
if (mutated) {
label_.reset();
full_label_.reset();
}
return mutated;
}
struct LabelCompare {
bool operator()(const Index &first, const Index &second) const {
if (first.space() != second.space())
return first.space() < second.space();
else
return first.ordinal() < second.ordinal();
}
};
struct FullLabelCompare {
bool operator()(const Index &first, const Index &second) const {
if (first.space() != second.space())
return first.space() < second.space();
else if (first.ordinal() != second.ordinal())
return first.ordinal() < second.ordinal();
else
return ranges::lexicographical_compare(
first.proto_indices(), second.proto_indices(), FullLabelCompare{});
}
};
struct TypeCompare {
bool operator()(const Index &first, const Index &second) const {
if (first.space() != second.space())
return first.space() < second.space();
else
return ranges::lexicographical_compare(
first.proto_indices(), second.proto_indices(), FullLabelCompare{});
}
};
struct TypeEquality {
bool operator()(const Index &first, const Index &second) const {
bool result = (first.space() == second.space()) &&
(first.proto_indices() == second.proto_indices());
return result;
}
};
private:
IndexSpace space_;
std::optional<ordinal_type> ordinal_;
// an unordered set of unique indices on which this index depends on
// whether proto_indices_ is symmetric w.r.t. permutations; if true,
// proto_indices_ will be ordered
index_vector proto_indices_{};
bool symmetric_proto_indices_ = true;
mutable std::optional<std::wstring> label_;
mutable std::optional<std::wstring> full_label_;
inline void canonicalize_proto_indices() noexcept;
inline void validate_proto_indices() const;
inline void check_nonreserved() const {
if (ordinal_ && *ordinal_ >= min_tmp_index()) {
throw std::invalid_argument(
"Index ctor: ordinal must be less than the value returned by "
"min_tmp_index()");
}
}
template <typename Char, typename = std::enable_if_t<meta::is_char_v<Char>>>
static inline std::optional<ordinal_type> to_ordinal(Char) noexcept {
return std::nullopt;
}
static inline std::optional<ordinal_type> to_ordinal(
std::string_view label) noexcept {
const auto underscore_position = label.rfind('_');
if (underscore_position != std::wstring::npos) {
// check that there is at least one char past the underscore
SEQUANT_ASSERT(underscore_position + 1 < label.size());
return std::atol(
label.substr(underscore_position + 1, std::string::npos).data());
} else
return std::nullopt;
}
static inline std::optional<ordinal_type> to_ordinal(
std::wstring_view label) noexcept {
const auto underscore_position = label.rfind(L'_');
if (underscore_position != std::wstring::npos) {
SEQUANT_ASSERT(underscore_position + 1 <
label.size()); // check that there is at least one char past the
// underscore
return std::wcstol(
label.substr(underscore_position + 1, std::wstring::npos).data(),
NULL, 10);
} else
return std::nullopt;
}
friend class IndexFactory;
struct IndexFactoryTag {};
// this ctor is only used by make_tmp_index and IndexFactory and bypasses
// check for nontmp index
Index(const IndexSpace &space, ordinal_type ordinal, IndexFactoryTag) noexcept
: space_(space), ordinal_(ordinal), proto_indices_() {}
friend bool operator==(const Index &i1, const Index &i2) noexcept {
return i1.space() == i2.space() &&
(i1.space().attr() != default_space_attr ||
i1.space().base_key() == i2.space().base_key()) &&
i1.ordinal() == i2.ordinal() &&
i1.proto_indices() == i2.proto_indices();
}
friend bool operator!=(const Index &i1, const Index &i2) noexcept {
return !(i1 == i2);
}
friend std::strong_ordering operator<=>(const Index &i1,
const Index &i2) noexcept {
using SO = std::strong_ordering;
auto compare_sans_tag = [&i1, &i2]() {
const auto cmp_space = i1.space() <=> i2.space();
if (cmp_space != SO::equal) return cmp_space;
if (i1.ordinal_ != i2.ordinal_) {
return i1.ordinal_ < i2.ordinal_ ? SO::less : SO::greater;
} else if (i1.proto_indices() == i2.proto_indices())
return SO::equal;
else
return i1.proto_indices() < i2.proto_indices() ? SO::less : SO::greater;
};
const auto i1_Q = i1.space().qns();
const auto i2_Q = i2.space().qns();
if (i1_Q == i2_Q) {
const bool have_tags = i1.tag().has_value() && i2.tag().has_value();
if (!have_tags || i1.tag() == i2.tag()) {
return compare_sans_tag();
}
return i1.tag() < i2.tag() ? SO::less : SO::greater;
}
return i1_Q < i2_Q ? SO::less : SO::greater;
}
static std::shared_ptr<const IndexSpaceRegistry> obtain_default_index_registry();
}; // class Index
inline const IndexSpace::Attr Index::default_space_attr{
IndexSpace::Attr::reserved};
inline const Index Index::null;
void Index::validate_proto_indices() const {
#ifdef SEQUANT_ASSERT_ENABLED
if (!proto_indices_.empty()) {
SEQUANT_ASSERT(!ranges::contains(proto_indices_, null) && "Index ctor: null proto index detected");
if (!symmetric_proto_indices_) { // if proto indices not symmetric, sort
// via
// ptrs
container::svector<Index const *> vp;
vp.reserve(proto_indices_.size());
for (size_t i = 0; i < proto_indices_.size(); ++i)
vp.push_back(&proto_indices_[i]);
std::sort(vp.begin(), vp.end(),
[](Index const *l, Index const *r) { return *l < *r; });
SEQUANT_ASSERT(std::adjacent_find(vp.begin(), vp.end(),
[](Index const *l, Index const *r) {
return *l == *r;
}) == vp.end() &&
"Index ctor: duplicate proto indices detected");
} else { // else search directly
SEQUANT_ASSERT(std::adjacent_find(begin(proto_indices_), end(proto_indices_)) ==
proto_indices_.end() &&
"Index ctor: duplicate proto indices detected");
}
}
#endif
}
void Index::canonicalize_proto_indices() noexcept {
if (symmetric_proto_indices_)
std::stable_sort(begin(proto_indices_), end(proto_indices_));
}
inline void swap(Index &first, Index &second) noexcept {
std::swap(first, second);
detail::count_swap<Index>();
}
class IndexFactory {
public:
IndexFactory() = default;
template <typename IndexValidator>
requires((std::is_invocable_r_v<bool, IndexValidator, const Index &>))
explicit IndexFactory(IndexValidator validator,
size_t min_index = Index::min_tmp_index())
: min_index_(min_index), validator_(std::move(validator)) {
SEQUANT_ASSERT(min_index_ > 0);
}
Index make(const IndexSpace &space) {
Index result;
bool valid = false;
do {
auto counter_it = counters_.begin();
{ // if don't have a counter for this space
#if SEQUANT_INDEX_THREADSAFE
std::scoped_lock lock(mutex_);
#endif
if ((counter_it = counters_.find(space)) == counters_.end()) {
bool inserted = false;
std::tie(counter_it, inserted) =
counters_.emplace(space, min_index_ - 1);
SEQUANT_ASSERT(inserted);
}
}
result = Index(space, ++(counter_it->second), Index::IndexFactoryTag{});
valid = validator_ ? validator_(result) : true;
} while (!valid);
return result;
}
Index make(const Index &idx) {
const auto &space = idx.space();
Index result;
bool valid = false;
do {
auto counter_it = counters_.begin();
{ // if don't have a counter for this space
#if SEQUANT_INDEX_THREADSAFE
std::scoped_lock lock(mutex_);
#endif
if ((counter_it = counters_.find(space)) == counters_.end()) {
counters_[space] = min_index_ - 1;
counter_it = counters_.find(space);
}
}
result =
Index(Index(space, ++(counter_it->second), Index::IndexFactoryTag{}),
idx.proto_indices());
valid = validator_ ? validator_(result) : true;
} while (!valid);
return result;
}
private:
std::size_t min_index_ = Index::min_tmp_index();
std::function<bool(const Index &)> validator_ = {};
#if SEQUANT_INDEX_THREADSAFE
std::mutex mutex_;
// boost::container::flat_map needs copyable value, which std::atomic is not,
// so must use std::map
std::map<IndexSpace, std::atomic<std::size_t>> counters_;
#else
// until multithreaded skip atomic
container::map<IndexSpace, std::size_t> counters_;
#endif
};
inline auto hash_value(const Index &idx) {
const auto &proto_indices = idx.proto_indices();
using std::begin;
using std::end;
auto val = hash::range(begin(proto_indices), end(proto_indices));
hash::combine(val, idx.space());
if (idx.ordinal()) hash::combine(val, idx.ordinal().value());
return val;
}
template <typename Container>
auto make_indices(WstrList index_labels = {}) {
Container result;
for (const auto &label : index_labels) {
result.push_back(Index{label});
}
return result;
}
} // namespace sequant
#endif // SEQUANT_INDEX_H