Skip to content

Commit 0e53eb8

Browse files
committed
expressions: gate no-external ToT general products; tests for sums under products, kitchen-sink, blocks in trees
A ToT x ToT general product with no external (free) outer indices -- every outer index fused or contracted -- segfaulted in the folded GEMM; gate it with an informative error (einsum() evaluates this shape natively via its no-external regime). New tests: a SUM nested under a product with a general summand (the down-pass prunes summand-internal contraction indices from the sum's demand by construction); the kitchen-sink expression combining a THC-like batching index, a mixed T x ToT general product, a ToT x ToT general product with an inner outer-product, and a ScalMult prefactor; a block leaf under an inner general node; a re-permuted general product assigned into a block view; the no-external gate.
1 parent 1d086cb commit 0e53eb8

2 files changed

Lines changed: 121 additions & 0 deletions

File tree

src/TiledArray/expressions/cont_engine.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,19 @@ class ContEngine : public BinaryEngine<Derived> {
666666
TA_ASSERT(nh > 0u); // else this is a pure contraction
667667
n_fused_modes_ = nh;
668668

669+
// a general product of tensors-of-tensors with NO external (free) outer
670+
// indices (every outer index fused or contracted, e.g.
671+
// C("i,j;a,b") = A("x,i,j;a") * B("x,i,j;b")) is not supported by the
672+
// batched tile op yet (its folded GEMM has no free modes);
673+
// einsum() evaluates this shape natively
674+
if constexpr (TiledArray::detail::is_tensor_of_tensor_v<value_type>) {
675+
if (outer_size(indices_) == nh)
676+
TA_EXCEPTION(
677+
"general products of tensors-of-tensors without external (free) "
678+
"outer indices are not yet supported in the expression layer; "
679+
"use TiledArray::einsum() for this contraction");
680+
}
681+
669682
// initialize perm_; a target that differs from the canonical (fused...,
670683
// left-free..., right-free...) result layout cannot be folded into the
671684
// batched tile op (BatchedContractReduce must be perm-free), so the

tests/general_product.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,114 @@ BOOST_AUTO_TEST_CASE(expression_general_product_into_block) {
639639
BOOST_CHECK_SMALL(diff_norm(w, w_ref, "b,i,k"), 1e-10);
640640
}
641641

642+
BOOST_AUTO_TEST_CASE(expression_general_product_block_in_tree) {
643+
// a BLOCK leaf under an inner general node of a deeper tree:
644+
// w("p,q,r2") = (x.block("p,r1") * y("q,r1")) * z("r1,r2")
645+
// r1 is fused at the inner (general) node, contracted at the root
646+
auto& world = TA::get_default_world();
647+
TA::TiledRange tr_x{{0, 2, 4}, {0, 3, 5}}; // p x r1
648+
TA::TiledRange tr_z{{0, 3, 5}, {0, 3, 5}}; // r1 x r2
649+
auto x = make_patterned_array(world, tr_x, 1.0);
650+
auto y = make_patterned_array(world, tr_x, 1.5);
651+
auto z = make_patterned_array(world, tr_z, 2.0);
652+
653+
TA::TArrayD xb, i1, w_ref, w;
654+
xb("p,r1") = x("p,r1").block({0, 0}, {1, 2});
655+
i1("r1,p,q") = xb("p,r1") * y("q,r1");
656+
w_ref("p,q,r2") = i1("r1,p,q") * z("r1,r2");
657+
658+
BOOST_REQUIRE_NO_THROW(
659+
w("p,q,r2") = (x("p,r1").block({0, 0}, {1, 2}) * y("q,r1")) * z("r1,r2"));
660+
BOOST_CHECK_SMALL(diff_norm(w, w_ref, "p,q,r2"), 1e-10);
661+
}
662+
663+
BOOST_AUTO_TEST_CASE(expression_general_product_repermute_into_block) {
664+
// a general product with a NON-canonical target layout (streaming
665+
// re-permute) assigned INTO a block view of the result
666+
auto& world = TA::get_default_world();
667+
TA::TiledRange tr_x{{0, 2}, {0, 3, 5}}; // p x r1
668+
TA::TiledRange tr_w{{0, 2, 4}, {0, 2, 4}, {0, 3, 5}}; // p, q, r1
669+
auto x = make_patterned_array(world, tr_x, 1.0);
670+
auto y = make_patterned_array(world, tr_x, 1.5);
671+
672+
TA::TArrayD i1, w(world, tr_w), w_ref(world, tr_w);
673+
w.fill(0.0);
674+
w_ref.fill(0.0);
675+
i1("r1,p,q") = x("p,r1") * y("q,r1"); // canonical evaluation
676+
w_ref("p,q,r1").block({0, 0, 0}, {1, 1, 2}) = i1("r1,p,q");
677+
678+
BOOST_REQUIRE_NO_THROW(w("p,q,r1").block({0, 0, 0}, {1, 1, 2}) =
679+
x("p,r1") * y("q,r1"));
680+
BOOST_CHECK_SMALL(diff_norm(w, w_ref, "p,q,r1"), 1e-10);
681+
}
682+
683+
BOOST_AUTO_TEST_CASE(expression_general_sum_under_product) {
684+
// a SUM nested under a product, with a general product as one summand:
685+
// F("i,j") = A("x,i") * (B("x,k") * C("x,k,j") + D("x,j"))
686+
// x is fused inside B*C (demanded by the sum's consumer), k is contracted
687+
// within the summand (never escapes), and x is contracted at the root.
688+
// The down-pass prunes k from the sum's demand automatically (it appears
689+
// in neither the target nor A) and hands (j,x) to BOTH summands.
690+
auto& world = TA::get_default_world();
691+
TA::TiledRange tr_a{{0, 3, 5}, {0, 2, 4}}; // x, i
692+
TA::TiledRange tr_b{{0, 3, 5}, {0, 2, 3}}; // x, k
693+
TA::TiledRange tr_c{{0, 3, 5}, {0, 2, 3}, {0, 2, 4}}; // x, k, j
694+
TA::TiledRange tr_d{{0, 3, 5}, {0, 2, 4}}; // x, j
695+
auto a = make_patterned_array(world, tr_a, 1.0);
696+
auto b = make_patterned_array(world, tr_b, 2.0);
697+
auto c = make_patterned_array(world, tr_c, 3.0);
698+
auto d = make_patterned_array(world, tr_d, 4.0);
699+
700+
TA::TArrayD i1, s, f_ref, f;
701+
i1("x,j") = b("x,k") * c("x,k,j"); // general: x fused, k contracted
702+
s("x,j") = i1("x,j") + d("x,j");
703+
f_ref("i,j") = a("x,i") * s("x,j");
704+
705+
BOOST_REQUIRE_NO_THROW(f("i,j") =
706+
a("x,i") * (b("x,k") * c("x,k,j") + d("x,j")));
707+
BOOST_CHECK_SMALL(diff_norm(f, f_ref, "i,j"), 1e-10);
708+
}
709+
710+
BOOST_AUTO_TEST_CASE(expression_general_kitchen_sink) {
711+
// one expression combining a THC-like batching index (x: fused at the
712+
// inner node, contracted at the root), a mixed T x ToT general product,
713+
// a ToT x ToT general product with an inner outer-product, and a scalar
714+
// prefactor (ScalMult at the root):
715+
// W("i,j,m;a,b") = 2 * ((g("x,i") * cv("x,j;a")) * dv("x,i,m;b"))
716+
auto& world = TA::get_default_world();
717+
TA::TiledRange tr_g{{0, 3, 5}, {0, 2, 4}}; // x, i
718+
TA::TiledRange tr_cv{{0, 3, 5}, {0, 2, 3}}; // x, j
719+
TA::TiledRange tr_dv{{0, 3, 5}, {0, 2, 4}, {0, 2, 3}}; // x, i, m
720+
auto g = make_patterned_array(world, tr_g, 1.0);
721+
auto cv = make_patterned_tot_array(world, tr_cv, {2}, 2.0);
722+
auto dv = make_patterned_tot_array(world, tr_dv, {3}, 3.0);
723+
724+
TArrayToT i1, i2, w_ref, w;
725+
i1("x,i,j;a") = g("x,i") * cv("x,j;a"); // T x ToT general
726+
i2("i,j,m;a,b") = i1("x,i,j;a") * dv("x,i,m;b"); // ToT x ToT, inner outer
727+
w_ref("i,j,m;a,b") = 2.0 * i2("i,j,m;a,b");
728+
729+
BOOST_REQUIRE_NO_THROW(w("i,j,m;a,b") =
730+
2.0 * ((g("x,i") * cv("x,j;a")) * dv("x,i,m;b")));
731+
BOOST_CHECK_SMALL(tot_max_abs_diff(w, w_ref), 1e-10);
732+
}
733+
734+
BOOST_AUTO_TEST_CASE(expression_general_product_tot_no_externals_gated) {
735+
// a ToT x ToT general product with NO external (free) outer indices --
736+
// every outer index fused or contracted -- is not supported by the
737+
// batched tile op (and used to segfault in the folded GEMM); the engine
738+
// must reject it with an informative error (einsum() handles this shape
739+
// natively via its no-external regime)
740+
auto& world = TA::get_default_world();
741+
TA::TiledRange tr{{0, 3, 5}, {0, 2, 4}, {0, 2, 3}}; // x, i, j
742+
auto a = make_patterned_tot_array(world, tr, {2}, 1.0);
743+
auto b = make_patterned_tot_array(world, tr, {3}, 2.0);
744+
745+
TArrayToT c;
746+
BOOST_CHECK_THROW(c("i,j;a,b") = a("x,i,j;a") * b("x,i,j;b"),
747+
TiledArray::Exception);
748+
}
749+
642750
BOOST_AUTO_TEST_CASE(expression_general_product_tot_inner_outer_product) {
643751
// the PNO-CC PPL building-block shape: ToT x ToT with an EMPTY right
644752
// outer-external set and an inner OUTER-product:

0 commit comments

Comments
 (0)