Skip to content

Commit

Permalink
Extract VertexPainter implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Krzmbrzl committed Jan 16, 2025
1 parent 3c596c2 commit 0931c3d
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 209 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ set(SeQuant_src
SeQuant/core/utility/singleton.hpp
SeQuant/core/utility/string.hpp
SeQuant/core/utility/string.cpp
SeQuant/core/vertex_painter.cpp
SeQuant/core/vertex_painter.hpp
SeQuant/core/wick.hpp
SeQuant/core/wick.impl.hpp
SeQuant/core/wolfram.hpp
Expand Down
210 changes: 1 addition & 209 deletions SeQuant/core/tensor_network_v2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <SeQuant/core/tag.hpp>
#include <SeQuant/core/tensor_canonicalizer.hpp>
#include <SeQuant/core/tensor_network_v2.hpp>
#include <SeQuant/core/vertex_painter.hpp>
#include <SeQuant/core/wstring.hpp>

#include <algorithm>
Expand Down Expand Up @@ -639,215 +640,6 @@ ExprPtr TensorNetworkV2::canonicalize(
return (byproduct->as<Constant>().value() == 1) ? nullptr : byproduct;
}

using ProtoBundle =
std::decay_t<decltype(std::declval<const Index &>().proto_indices())>;

struct BraGroup {
explicit BraGroup(std::size_t id) : id(id) {}

std::size_t id;
};
struct KetGroup {
explicit KetGroup(std::size_t id) : id(id) {}

std::size_t id;
};
struct AuxGroup {
explicit AuxGroup(std::size_t id) : id(id) {}

std::size_t id;
};
struct ParticleGroup {
explicit ParticleGroup(std::size_t id) : id(id) {}

std::size_t id;
};

class VertexPainter {
public:
using Color = TensorNetworkV2::Graph::VertexColor;
using VertexData =
std::variant<const AbstractTensor *, Index, const ProtoBundle *, BraGroup,
KetGroup, AuxGroup, ParticleGroup>;
using ColorMap = container::map<Color, VertexData>;

VertexPainter(const TensorNetworkV2::NamedIndexSet &named_indices)
: used_colors_(), named_indices_(named_indices) {}

const ColorMap &used_colors() const { return used_colors_; }

Color operator()(const AbstractTensor &tensor) {
Color color = to_color(hash::value(label(tensor)));

return ensure_uniqueness(color, tensor);
}

Color operator()(const BraGroup &group) {
Color color = to_color(group.id + 0xff);

return ensure_uniqueness(color, group);
}

Color operator()(const KetGroup &group) {
Color color = to_color(group.id + 0xff00);

return ensure_uniqueness(color, group);
}

Color operator()(const AuxGroup &group) {
Color color = to_color(group.id + 3 * 0xff0000);

return ensure_uniqueness(color, group);
}

Color operator()(const ParticleGroup &group) {
Color color = to_color(group.id);

return ensure_uniqueness(color, group);
}

Color operator()(const Index &idx) {
auto it = named_indices_.find(idx);

// TODO: shift
std::size_t pre_color;
if (it == named_indices_.end()) {
// anonymous index
pre_color = idx.color();
} else {
pre_color = static_cast<decltype(pre_color)>(
std::distance(named_indices_.begin(), it));
}

return ensure_uniqueness(to_color(pre_color), idx);
}

Color operator()(const ProtoBundle &bundle) {
Color color = to_color(Index::proto_indices_color(bundle));

return ensure_uniqueness(color, bundle);
}

private:
ColorMap used_colors_;
const TensorNetworkV2::NamedIndexSet &named_indices_;

Color to_color(std::size_t color) const {
// Due to the way we compute the input color, different colors might only
// differ by a value of 1. This is fine for the algorithmic purpose (after
// all, colors need only be different - by how much is irrelevant), but
// sometimes we'll want to use those colors as actual colors to show to a
// human being. In those cases, having larger differences makes it easier to
// recognize different colors. Therefore, we hash-combined with an
// arbitrarily chosen salt with the goal that this will uniformly spread out
// all input values and therefore increase color differences.
constexpr std::size_t salt = 0x43d2c59cb15b73f0;
hash::combine(color, salt);

if constexpr (sizeof(Color) >= sizeof(std::size_t)) {
return color;
}

// Need to somehow fit the color into a lower precision integer. In the
// general case, this is necessarily a lossy conversion. We make the
// assumption that the input color is
// - a hash, or
// - computed from some object ID
// In the first case, we assume that the used hash function has a uniform
// distribution or if there is a bias, the bias is towards lower numbers.
// This allows us to simply reuse the lower x bits of the hash as a new hash
// (where x == CHAR_BIT * sizeof(VertexColor)). In the second case we assume
// that such values never exceed the possible value range of VertexColor so
// that again, we can simply take the lower x bits of color and in this case
// even retain the numeric value representing the color. Handily, this is
// exactly what happens when we perform a conversion into a narrower type.
// We only have to make sure that the underlying types are unsigned as
// otherwise the behavior is undefined.
static_assert(sizeof(Color) < sizeof(std::size_t));
static_assert(std::is_unsigned_v<TensorNetworkV2::Graph::VertexColor>,
"Narrowing conversion are undefined for signed integers");
static_assert(std::is_unsigned_v<std::size_t>,
"Narrowing conversion are undefined for signed integers");
return static_cast<Color>(color);
}

template <typename T>
Color ensure_uniqueness(Color color, const T &val) {
auto it = used_colors_.find(color);
while (it != used_colors_.end() && !may_have_same_color(it->second, val)) {
// Color collision: val was computed to have the same color
// as another object, but these objects do not compare equal (for
// the purpose of color assigning).
// -> Need to modify color until conflict is resolved.
color++;
it = used_colors_.find(color);
}

if (it == used_colors_.end()) {
// We have not yet seen this color before -> add it to cache
if constexpr (std::is_same_v<T, AbstractTensor> ||
std::is_same_v<T, ProtoBundle>) {
used_colors_[color] = &val;
} else {
used_colors_[color] = val;
}
}

return color;
}

bool may_have_same_color(const VertexData &data,
const AbstractTensor &tensor) {
return std::holds_alternative<const AbstractTensor *>(data) &&
label(*std::get<const AbstractTensor *>(data)) == label(tensor);
}

bool may_have_same_color(const VertexData &data, const BraGroup &group) {
return std::holds_alternative<BraGroup>(data) &&
std::get<BraGroup>(data).id == group.id;
}

bool may_have_same_color(const VertexData &data, const KetGroup &group) {
return std::holds_alternative<KetGroup>(data) &&
std::get<KetGroup>(data).id == group.id;
}

bool may_have_same_color(const VertexData &data, const AuxGroup &group) {
return std::holds_alternative<AuxGroup>(data) &&
std::get<AuxGroup>(data).id == group.id;
}

bool may_have_same_color(const VertexData &data, const ParticleGroup &group) {
return std::holds_alternative<ParticleGroup>(data) &&
std::get<ParticleGroup>(data).id == group.id;
}

bool may_have_same_color(const VertexData &data, const Index &idx) {
if (!std::holds_alternative<Index>(data)) {
return false;
}

const Index &lhs = std::get<Index>(data);

auto it1 = named_indices_.find(lhs);
auto it2 = named_indices_.find(idx);

if (it1 != it2) {
// Either one index is named and the other is not or both are named, but
// are different indices
return false;
}

return lhs.color() == idx.color();
}

bool may_have_same_color(const VertexData &data, const ProtoBundle &bundle) {
return std::holds_alternative<const ProtoBundle *>(data) &&
Index::proto_indices_color(*std::get<const ProtoBundle *>(data)) ==
Index::proto_indices_color(bundle);
}
};

TensorNetworkV2::Graph TensorNetworkV2::create_graph(
const NamedIndexSet *named_indices_ptr) const {
assert(have_edges_);
Expand Down
159 changes: 159 additions & 0 deletions SeQuant/core/vertex_painter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#include <SeQuant/core/hash.hpp>
#include <SeQuant/core/vertex_painter.hpp>

namespace sequant {

VertexPainter::VertexPainter(
const TensorNetworkV2::NamedIndexSet &named_indices)
: used_colors_(), named_indices_(named_indices) {}

VertexPainter::Color VertexPainter::operator()(const AbstractTensor &tensor) {
Color color = to_color(hash::value(label(tensor)));

return ensure_uniqueness(color, tensor);
}

VertexPainter::Color VertexPainter::operator()(const BraGroup &group) {
Color color = to_color(group.id + 0xff);

return ensure_uniqueness(color, group);
}

VertexPainter::Color VertexPainter::operator()(const KetGroup &group) {
Color color = to_color(group.id + 0xff00);

return ensure_uniqueness(color, group);
}

VertexPainter::Color VertexPainter::operator()(const AuxGroup &group) {
Color color = to_color(group.id + 3 * 0xff0000);

return ensure_uniqueness(color, group);
}

VertexPainter::Color VertexPainter::operator()(const ParticleGroup &group) {
Color color = to_color(group.id);

return ensure_uniqueness(color, group);
}

VertexPainter::Color VertexPainter::operator()(const Index &idx) {
auto it = named_indices_.find(idx);

std::size_t pre_color;
if (it == named_indices_.end()) {
// anonymous index
pre_color = idx.color();
} else {
pre_color = static_cast<decltype(pre_color)>(
std::distance(named_indices_.begin(), it));
}
// shift
pre_color += 0xaa;

return ensure_uniqueness(to_color(pre_color), idx);
}

VertexPainter::Color VertexPainter::operator()(const ProtoBundle &bundle) {
Color color = to_color(Index::proto_indices_color(bundle));

return ensure_uniqueness(color, bundle);
}

VertexPainter::Color VertexPainter::to_color(std::size_t color) const {
// Due to the way we compute the input color, different colors might only
// differ by a value of 1. This is fine for the algorithmic purpose (after
// all, colors need only be different - by how much is irrelevant), but
// sometimes we'll want to use those colors as actual colors to show to a
// human being. In those cases, having larger differences makes it easier to
// recognize different colors. Therefore, we hash-combine with an
// arbitrarily chosen salt with the goal that this will uniformly spread out
// all input values and therefore increase color differences.
constexpr std::size_t salt = 0x43d2c59cb15b73f0;
hash::combine(color, salt);

if constexpr (sizeof(Color) >= sizeof(std::size_t)) {
return color;
}

// Need to somehow fit the color into a lower precision integer. In the
// general case, this is necessarily a lossy conversion. We make the
// assumption that the input color is
// - a hash, or
// - computed from some object ID
// In the first case, we assume that the used hash function has a uniform
// distribution or if there is a bias, the bias is towards lower numbers.
// This allows us to simply reuse the lower x bits of the hash as a new hash
// (where x == CHAR_BIT * sizeof(VertexColor)). In the second case we assume
// that such values never exceed the possible value range of VertexColor so
// that again, we can simply take the lower x bits of color and in this case
// even retain the numeric value representing the color. Handily, this is
// exactly what happens when we perform a conversion into a narrower type.
// We only have to make sure that the underlying types are unsigned as
// otherwise the behavior is undefined.
static_assert(sizeof(Color) < sizeof(std::size_t));
static_assert(std::is_unsigned_v<TensorNetworkV2::Graph::VertexColor>,
"Narrowing conversion are undefined for signed integers");
static_assert(std::is_unsigned_v<std::size_t>,
"Narrowing conversion are undefined for signed integers");
return static_cast<Color>(color);
}

bool VertexPainter::may_have_same_color(const VertexData &data,
const AbstractTensor &tensor) {
return std::holds_alternative<const AbstractTensor *>(data) &&
label(*std::get<const AbstractTensor *>(data)) == label(tensor);
}

bool VertexPainter::may_have_same_color(const VertexData &data,
const BraGroup &group) {
return std::holds_alternative<BraGroup>(data) &&
std::get<BraGroup>(data).id == group.id;
}

bool VertexPainter::may_have_same_color(const VertexData &data,
const KetGroup &group) {
return std::holds_alternative<KetGroup>(data) &&
std::get<KetGroup>(data).id == group.id;
}

bool VertexPainter::may_have_same_color(const VertexData &data,
const AuxGroup &group) {
return std::holds_alternative<AuxGroup>(data) &&
std::get<AuxGroup>(data).id == group.id;
}

bool VertexPainter::may_have_same_color(const VertexData &data,
const ParticleGroup &group) {
return std::holds_alternative<ParticleGroup>(data) &&
std::get<ParticleGroup>(data).id == group.id;
}

bool VertexPainter::may_have_same_color(const VertexData &data,
const Index &idx) {
if (!std::holds_alternative<Index>(data)) {
return false;
}

const Index &lhs = std::get<Index>(data);

auto it1 = named_indices_.find(lhs);
auto it2 = named_indices_.find(idx);

if (it1 != it2) {
// Either one index is named and the other is not or both are named, but
// are different indices
return false;
}

return lhs.color() == idx.color();
}

bool VertexPainter::may_have_same_color(const VertexData &data,
const ProtoBundle &bundle) {
return std::holds_alternative<const ProtoBundle *>(data) &&
Index::proto_indices_color(*std::get<const ProtoBundle *>(data)) ==
Index::proto_indices_color(bundle);
}

} // namespace sequant
Loading

0 comments on commit 0931c3d

Please sign in to comment.