@@ -235,26 +235,70 @@ BOOST_AUTO_TEST_CASE(expression_general_product_dense_batched_outer) {
235235 BOOST_CHECK_SMALL (diff_norm (c, c_ref, " b,i,k" ), 1e-10 );
236236}
237237
238- BOOST_AUTO_TEST_CASE (expression_general_product_inner_node_gated) {
239- // THC-style reconstruction:
238+ BOOST_AUTO_TEST_CASE (expression_general_product_noncanonical_root_target) {
239+ // a root-level general product with a NON-canonical target layout: the
240+ // product evaluates canonically (r1,p,q) and is re-permuted to the target
241+ // by the streaming unary eval
242+ auto & world = TA::get_default_world ();
243+ TA ::TiledRange tr_x{{0 , 2 , 4 }, {0 , 3 , 5 }}; // orbital x auxiliary
244+ auto x = make_patterned_array (world, tr_x, 1.0 );
245+
246+ TA ::TArrayD w, i1, w_ref;
247+ BOOST_REQUIRE_NO_THROW (w (" p,q,r1" ) = x (" p,r1" ) * x (" q,r1" ));
248+ i1 (" r1,p,q" ) = x (" p,r1" ) * x (" q,r1" ); // canonical evaluation
249+ w_ref (" p,q,r1" ) = i1 (" r1,p,q" ); // plain permute assignment
250+
251+ BOOST_CHECK_SMALL (diff_norm (w, w_ref, " p,q,r1" ), 1e-10 );
252+ }
253+
254+ BOOST_AUTO_TEST_CASE (expression_general_product_inner_node_depth2) {
255+ // minimal inner-node case: a general product (fused r1) feeding a
256+ // contraction over r1 -- the general child evaluates canonically
257+ // (r1,p,q) and is re-permuted on the fly to the consumer's GEMM layout
258+ auto & world = TA::get_default_world ();
259+ TA ::TiledRange tr_x{{0 , 2 , 4 }, {0 , 3 , 5 }}; // orbital x auxiliary
260+ TA ::TiledRange tr_z{{0 , 3 , 5 }, {0 , 3 , 5 }}; // auxiliary x auxiliary
261+ auto x = make_patterned_array (world, tr_x, 1.0 );
262+ auto z = make_patterned_array (world, tr_z, 2.0 );
263+
264+ TA ::TArrayD w;
265+ BOOST_REQUIRE_NO_THROW (w (" p,q,r2" ) = (x (" p,r1" ) * x (" q,r1" )) * z (" r1,r2" ));
266+
267+ TA ::TArrayD i1, w_ref;
268+ i1 (" r1,p,q" ) = x (" p,r1" ) * x (" q,r1" );
269+ w_ref (" p,q,r2" ) = i1 (" r1,p,q" ) * z (" r1,r2" );
270+
271+ BOOST_CHECK_SMALL (diff_norm (w, w_ref, " p,q,r2" ), 1e-10 );
272+ }
273+
274+ BOOST_AUTO_TEST_CASE (expression_general_product_inner_node_thc) {
275+ // THC-style reconstruction in ONE expression:
240276 // g("p,q,r,s") = X("p,r1") * X("q,r1") * Z("r1,r2") * X("r,r2") * X("s,r2")
241- // r1 is fused in X("p,r1") * X("q,r1") but contracted downstream. The
242- // first product is an INNER node of the expression tree, where the role of
243- // r1 cannot be deduced bottom-up (the target reaches only the root);
244- // resolving this requires top-down index-set deduction (deferred). Until
245- // then: an informative error, not garbage (bottom-up, X*X would contract
246- // r1, orphaning the r1 of Z).
277+ // r1 is fused in X("p,r1") * X("q,r1") but contracted downstream, so the
278+ // first product is a general product at an INNER node of the (left-deep)
279+ // expression tree. The top-down index-set deduction demands r1 of the X*X
280+ // node (its consumer carries it) and contracts it where Z meets that
281+ // subtree; higher up, r1 is dropped from the demand (consumed entirely
282+ // within). Verified against the same chain staged through explicit
283+ // intermediates (the pre-deduction recipe, itself differential-tested
284+ // against einsum in expression_general_product_thc_intermediates).
247285 auto & world = TA::get_default_world ();
248286 TA ::TiledRange tr_x{{0 , 2 , 4 }, {0 , 3 , 5 }}; // orbital x auxiliary
249287 TA ::TiledRange tr_z{{0 , 3 , 5 }, {0 , 3 , 5 }}; // auxiliary x auxiliary
250- TA ::TArrayD x (world, tr_x);
251- TA ::TArrayD z (world, tr_z);
252- x.fill (1.0 );
253- z.fill (1.0 );
288+ auto x = make_patterned_array (world, tr_x, 1.0 );
289+ auto z = make_patterned_array (world, tr_z, 2.0 );
290+
254291 TA ::TArrayD g;
255- BOOST_CHECK_THROW (
256- g (" p,q,r,s" ) = x (" p,r1" ) * x (" q,r1" ) * z (" r1,r2" ) * x (" r,r2" ) * x (" s,r2" ),
257- TiledArray::Exception);
292+ BOOST_REQUIRE_NO_THROW (g (" p,q,r,s" ) = x (" p,r1" ) * x (" q,r1" ) * z (" r1,r2" ) *
293+ x (" r,r2" ) * x (" s,r2" ));
294+
295+ TA ::TArrayD i1, i2, i3, g_ref;
296+ i1 (" r1,p,q" ) = x (" p,r1" ) * x (" q,r1" );
297+ i2 (" p,q,r2" ) = i1 (" r1,p,q" ) * z (" r1,r2" );
298+ i3 (" r2,p,q,r" ) = i2 (" p,q,r2" ) * x (" r,r2" );
299+ g_ref (" p,q,r,s" ) = i3 (" r2,p,q,r" ) * x (" s,r2" );
300+
301+ BOOST_CHECK_SMALL (diff_norm (g, g_ref, " p,q,r,s" ), 1e-10 );
258302}
259303
260304BOOST_AUTO_TEST_CASE (expression_general_product_thc_intermediates) {
0 commit comments