Program Listing for File space.hpp

Return to documentation for file (SeQuant/core/space.hpp)

//
// Created by Eduard Valeyev on 3/20/18.
//

#ifndef SEQUANT_CORE_SPACE_H
#define SEQUANT_CORE_SPACE_H

#include <SeQuant/core/fwd.hpp>

#include <SeQuant/core/attr.hpp>
#include <SeQuant/core/container.hpp>
#include <SeQuant/core/hash.hpp>
#include <SeQuant/core/utility/macros.hpp>
#include <SeQuant/core/utility/string.hpp>
#include <SeQuant/core/wstring.hpp>

#include <range/v3/algorithm/any_of.hpp>

#include <cmath>
#include <string_view>

namespace sequant {

class QuantumNumbersAttr;  // used to constrain TypeAttr ctor

class Index;  // friend of TypeAttr

class TypeAttr {
 public:
  constexpr TypeAttr() noexcept = default;

  const static TypeAttr null;

  const static TypeAttr reserved;


  explicit constexpr TypeAttr(bitset_t bitset) noexcept : bitset(bitset) {
    SEQUANT_ASSERT((this->bitset & bitset::reserved) == bitset::null);
  }

  template <typename T,
            typename = std::enable_if_t<
                meta::is_statically_castable_v<std::decay_t<T>, bitset_t> &&
                !std::is_same_v<std::decay_t<T>, bool> &&
                !std::is_same_v<std::decay_t<T>, QuantumNumbersAttr> &&
                !std::is_same_v<std::decay_t<T>, TypeAttr>>>
  constexpr TypeAttr(T &&value) noexcept
      : TypeAttr(static_cast<bitset_t>(std::forward<T>(value))) {}

  constexpr explicit operator int64_t() const {
    return static_cast<int64_t>(bitset);
  }
  constexpr explicit operator bitset_t() const { return bitset; }
  constexpr bitset_t to_bitset() const { return bitset; }
  constexpr int32_t to_int32() const { return bitset; }

  constexpr explicit operator bool() const { return bitset != 0; }

  constexpr TypeAttr(const TypeAttr &other) { *this = other; }

  constexpr TypeAttr &operator=(const TypeAttr &other) {
    bitset = other.to_int32();
    return *this;
  }

  constexpr TypeAttr unIon(TypeAttr other) const {
    return TypeAttr(this->to_int32() | other.to_int32());
  }

  friend constexpr TypeAttr operator|(const TypeAttr a, const TypeAttr b) {
    return a.unIon(b);
  }

  constexpr const TypeAttr xOr(TypeAttr other) const {
    return TypeAttr(this->to_int32() ^ other.to_int32());
  }

  friend constexpr TypeAttr operator^(const TypeAttr a, const TypeAttr b) {
    return a.xOr(b);
  }

  constexpr const TypeAttr intersection(TypeAttr other) const {
    return TypeAttr(this->to_int32() & other.to_int32());
  }

  friend constexpr TypeAttr operator&(const TypeAttr a, const TypeAttr b) {
    return a.intersection(b);
  }

  constexpr TypeAttr operator~() const { return ~this->to_int32(); }

  friend constexpr bool operator==(const TypeAttr lhs, const TypeAttr rhs) {
    return lhs.to_int32() == rhs.to_int32();
  }
  friend constexpr bool operator!=(const TypeAttr lhs, const TypeAttr rhs) {
    return !(lhs == rhs);
  }

  constexpr bool includes(TypeAttr other) const {
    return intersection(other) == other;
  }
  friend constexpr bool operator<(TypeAttr a, TypeAttr b) {
    return a.to_int32() < b.to_int32();
  }

 private:
  bitset_t bitset = 0;

  friend class Index;

  static TypeAttr make_reserved() {
    TypeAttr result;
    result.bitset = 0x80000000;
    return result;
  }
};  // struct TypeAttr

inline const TypeAttr TypeAttr::null;
inline const TypeAttr TypeAttr::reserved = TypeAttr::make_reserved();

class QuantumNumbersAttr {
 public:
  constexpr QuantumNumbersAttr() noexcept = default;

  const static QuantumNumbersAttr null;

  const static QuantumNumbersAttr reserved;


  explicit constexpr QuantumNumbersAttr(bitset_t bitset) noexcept
      : bitset(bitset) {
    SEQUANT_ASSERT((this->bitset & bitset::reserved) == bitset::null);
  }

  template <typename QN,
            typename = std::enable_if_t<
                meta::is_statically_castable_v<std::decay_t<QN>, bitset_t> &&
                !std::is_same_v<std::decay_t<QN>, bool> &&
                !std::is_same_v<std::decay_t<QN>, TypeAttr> &&
                !std::is_same_v<std::decay_t<QN>, QuantumNumbersAttr>>>
  constexpr QuantumNumbersAttr(QN &&value) noexcept
      : bitset(static_cast<bitset_t>(std::forward<QN>(value))) {
    SEQUANT_ASSERT((this->bitset & bitset::reserved) == bitset::null);
  }

  constexpr explicit operator int64_t() const {
    return static_cast<int64_t>(bitset);
  }
  constexpr explicit operator bitset_t() const { return bitset; }
  constexpr bitset_t to_bitset() const { return bitset; }
  constexpr int32_t to_int32() const { return bitset; }

  constexpr explicit operator bool() const { return bitset != 0; }

  constexpr QuantumNumbersAttr xOr(QuantumNumbersAttr other) const {
    return QuantumNumbersAttr(this->to_int32() ^ other.to_int32());
  }

  friend constexpr QuantumNumbersAttr operator^(const QuantumNumbersAttr a,
                                                const QuantumNumbersAttr b) {
    return a.xOr(b);
  }

  constexpr QuantumNumbersAttr unIon(QuantumNumbersAttr other) const {
    return QuantumNumbersAttr(this->to_int32() | other.to_int32());
  }

  friend constexpr QuantumNumbersAttr operator|(const QuantumNumbersAttr a,
                                                const QuantumNumbersAttr b) {
    return a.unIon(b);
  }

  constexpr QuantumNumbersAttr intersection(QuantumNumbersAttr other) const {
    return QuantumNumbersAttr(this->to_int32() & other.to_int32());
  }

  friend constexpr QuantumNumbersAttr operator&(const QuantumNumbersAttr a,
                                                const QuantumNumbersAttr b) {
    return a.intersection(b);
  }

  constexpr QuantumNumbersAttr operator~() const { return ~this->to_int32(); }

  friend constexpr bool operator==(QuantumNumbersAttr lhs,
                                   QuantumNumbersAttr rhs) {
    return lhs.to_int32() == rhs.to_int32();
  }
  friend constexpr bool operator!=(QuantumNumbersAttr lhs,
                                   QuantumNumbersAttr rhs) {
    return !(lhs == rhs);
  }

  bool includes(QuantumNumbersAttr other) const {
    return intersection(other) == other;
  }
  friend constexpr bool operator<(QuantumNumbersAttr lhs,
                                  QuantumNumbersAttr rhs) {
    return lhs.to_int32() < rhs.to_int32();
  }

 private:
  bitset_t bitset = bitset::null;

  friend class Index;

  static QuantumNumbersAttr make_reserved() {
    QuantumNumbersAttr result;
    result.bitset = bitset::reserved;
    return result;
  }
};  // struct QuantumNumbersAttr

inline const QuantumNumbersAttr QuantumNumbersAttr::null;
inline const QuantumNumbersAttr QuantumNumbersAttr::reserved =
    QuantumNumbersAttr::make_reserved();

class IndexSpace {
 public:
  struct Attr : TypeAttr, QuantumNumbersAttr {
    Attr(TypeAttr type, QuantumNumbersAttr qns) noexcept
        : TypeAttr(type), QuantumNumbersAttr(qns){};
    Attr(int32_t type, int32_t qns) noexcept
        : TypeAttr(type), QuantumNumbersAttr(qns){};

    Attr() = default;
    Attr(const Attr &) = default;
    Attr(Attr &&) = default;
    Attr &operator=(const Attr &) = default;
    Attr &operator=(Attr &&) = default;

    const static Attr null;

    const static Attr reserved;

    constexpr const TypeAttr &type() const {
      return static_cast<const TypeAttr &>(*this);
    }
    constexpr TypeAttr &type() { return static_cast<TypeAttr &>(*this); }
    constexpr const QuantumNumbersAttr &qns() const {
      return static_cast<const QuantumNumbersAttr &>(*this);
    }
    constexpr QuantumNumbersAttr &qns() {
      return static_cast<QuantumNumbersAttr &>(*this);
    }

    constexpr explicit operator int64_t() const {
      return (static_cast<int64_t>(this->type()) << 32) +
             static_cast<int64_t>(this->qns());
    }

    explicit operator bool() const {
      return static_cast<bool>(this->type()) || static_cast<bool>(this->qns());
    }

    Attr unIon(Attr other) const {
      return {this->type().unIon(other.type()), this->qns().unIon(other.qns())};
    }

    friend Attr operator|(Attr a, Attr b) { return a.unIon(b); }

    Attr intersection(Attr other) const {
      return Attr(this->type().intersection(other.type()),
                  this->qns().intersection(other.qns()));
    }
    friend Attr operator&(Attr a, Attr b) { return a.intersection(b); }

    bool includes(Attr other) const {
      return this->type().includes(other.type()) &&
             this->qns().includes(other.qns());
    }

    constexpr bool operator==(Attr other) const {
      return this->type() == other.type() && this->qns() == other.qns();
    }
    constexpr bool operator!=(Attr other) const { return !(*this == other); }

    constexpr bool operator<(Attr other) const {
      if (this->qns() == other.qns()) {
        return this->type() < other.type();
      } else {
        return this->qns() < other.qns();
      }
    }

    static Attr make_reserved() {
      Attr result{Type::reserved, QuantumNumbers::reserved};
      return result;
    }
  };  // struct Attr

  using Type = TypeAttr;
  using QuantumNumbers = QuantumNumbersAttr;

  struct bad_key : std::invalid_argument {
    bad_key() : std::invalid_argument("bad key") {}
    template <basic_string_convertible S>
    bad_key(S &&key)
        : std::invalid_argument(std::string("bad key: ") +
                                sequant::to_string(key)) {}
  };

  struct KeyCompare {
    using is_transparent = void;
    bool operator()(const IndexSpace &a, const IndexSpace &b) const {
      return a.base_key() < b.base_key();
    }
    bool operator()(const std::wstring &a, const IndexSpace &b) const {
      return a < b.base_key();
    }
    bool operator()(const std::wstring_view &a, const IndexSpace &b) const {
      return a < b.base_key();
    }
    bool operator()(const IndexSpace &a, const std::wstring &b) const {
      return a.base_key() < b;
    }
    bool operator()(const IndexSpace &a, const std::wstring_view &b) const {
      return a.base_key() < b;
    }
    bool operator()(const std::wstring &a, const std::wstring &b) const {
      return a < b;
    }
    bool operator()(const std::wstring &a, const std::wstring_view &b) const {
      return a < b;
    }
    bool operator()(const std::wstring_view &a, const std::wstring &b) const {
      return a < b;
    }
  };

  struct AttrCompare {
    using is_transparent = void;
    bool operator()(const IndexSpace &a, const IndexSpace &b) const {
      return a.attr() < b.attr();
    }
    bool operator()(const IndexSpace &a, const IndexSpace::Attr &b) const {
      return a.attr() < b;
    }
    bool operator()(const IndexSpace::Attr &a, const IndexSpace &b) const {
      return a < b.attr();
    }
  };

  friend constexpr bool operator==(IndexSpace const &,
                                   IndexSpace const &) noexcept;
  friend constexpr std::strong_ordering operator<=>(
      const IndexSpace &s1, const IndexSpace &s2) noexcept;

  constexpr Attr attr() const noexcept { return attr_; }
  constexpr Type type() const noexcept { return attr().type(); }
  QuantumNumbers qns() const noexcept { return attr().qns(); }

  IndexSpace() noexcept = default;

  const static IndexSpace null;

  explicit operator bool() const { return *this != null; }

  template <basic_string_convertible S>
  IndexSpace(S &&type_label, TypeAttr typeattr,
             QuantumNumbersAttr qnattr = QuantumNumbersAttr{0},
             unsigned long approximate_size = 10)
      : attr_(typeattr, qnattr),
        base_key_(sequant::to_wstring(std::forward<S>(type_label))),
        approximate_size_(approximate_size) {}

  explicit IndexSpace(std::string_view label);
  explicit IndexSpace(std::wstring_view label);

  IndexSpace(const IndexSpace &other) = default;
  IndexSpace(IndexSpace &&other) noexcept
      : attr_(std::move(other.attr_)),
        base_key_(std::move(other.base_key_)),
        approximate_size_(std::move(other.approximate_size_)) {
    other = null;
  }
  IndexSpace &operator=(const IndexSpace &other) = default;
  IndexSpace &operator=(IndexSpace &&other) {
    attr_ = std::move(other.attr_);
    base_key_ = std::move(other.base_key_);
    approximate_size_ = std::move(other.approximate_size_);
    other = null;
    return *this;
  }

  const std::wstring &base_key() const { return base_key_; }
  static std::wstring_view reduce_key(std::wstring_view key) {
    const auto underscore_position = key.rfind(L'_');
    if (underscore_position != std::wstring::npos) {  // key can be reduced
      return key.substr(0, underscore_position);
    } else {
      return key;
    }
  }
  static std::wstring reduce_key(std::string_view key) {
    const auto underscore_position = key.rfind(L'_');
    if (underscore_position != std::string::npos) {  // key can be reduced
      return sequant::to_wstring(key.substr(0, underscore_position));
    } else {
      return sequant::to_wstring(key);
    }
  }

  std::size_t approximate_size() const { return approximate_size_; }

  void approximate_size(size_t n) { approximate_size_ = n; }

 private:
  Attr attr_ = {};
  std::wstring base_key_ = {};
  std::size_t approximate_size_ = {};

  static std::wstring to_wstring(std::wstring_view key) {
    return std::wstring(key.begin(), key.end());
  }
};  // class IndexSpace

inline const IndexSpace IndexSpace::null;
inline const IndexSpace::Attr IndexSpace::Attr::null;
inline const IndexSpace::Attr IndexSpace::Attr::reserved =
    IndexSpace::Attr::make_reserved();

inline auto hash_value(const IndexSpace &space) {
  auto result = hash_value(static_cast<std::int64_t>(space.attr()));
  hash::combine(result, space.base_key());
  // approximate_size is an attribute that should not affect expression
  // manipulation, so we do not include it in hash
  return result;
}

inline bool includes(IndexSpace::Type type1, IndexSpace::Type type2) {
  return type1.includes(type2);
}
inline bool includes(IndexSpace::QuantumNumbers qns1,
                     IndexSpace::QuantumNumbers qns2) {
  return qns1.includes(qns2);
}
inline bool includes(const IndexSpace &space, const IndexSpace &subspace) {
  return space.attr().includes(subspace.attr());
}

[[nodiscard]] inline constexpr std::strong_ordering operator<=>(
    const IndexSpace &s1, const IndexSpace &s2) noexcept {
  using SO = std::strong_ordering;

  if (s1.attr() != s2.attr()) {
    return s1.attr() < s2.attr() ? SO::less : SO::greater;
  }
  if (s1.attr() == IndexSpace::Attr::reserved &&
      s1.base_key() != s2.base_key()) {
    return s1.base_key() < s2.base_key() ? SO::less : SO::greater;
  }
  return SO::equal;
}

[[nodiscard]] inline constexpr bool operator==(
    IndexSpace const &space1, IndexSpace const &space2) noexcept {
  return space1.attr() == space2.attr() &&
         space1.base_key() == space2.base_key();
}

std::string to_string(IndexSpace::Type type);
std::string to_string(IndexSpace::QuantumNumbers qns);
std::string to_string(IndexSpace::Attr attr);
std::string to_string(const IndexSpace &space);

template <typename T>
concept index_space_or_label =
    (std::is_same_v<std::remove_cvref_t<T>, IndexSpace> ||
     meta::is_basic_string_convertible_v<std::remove_cvref_t<T>>);

}  // namespace sequant

#endif  // SEQUANT_CORE_SPACE_H