2727#define TILEDARRAY_EXPRESSIONS_CONT_ENGINE_H__INCLUDED
2828
2929#include < TiledArray/dist_eval/contraction_eval.h>
30+ #include < TiledArray/dist_eval/unary_eval.h>
3031#include < TiledArray/expressions/binary_engine.h>
3132#include < TiledArray/expressions/permopt.h>
3233#include < TiledArray/pmap/slabbed_pmap.h>
3637#include < TiledArray/tile_op/batched_contract_reduce.h>
3738#include < TiledArray/tile_op/contract_reduce.h>
3839#include < TiledArray/tile_op/mult.h>
40+ #include < TiledArray/tile_op/noop.h>
3941
4042namespace TiledArray {
4143namespace expressions {
@@ -179,6 +181,10 @@ class ContEngine : public BinaryEngine<Derived> {
179181 // General (fused + contracted + free indices) products only:
180182 unsigned int n_fused_modes_ = 0 ; // /< # of leading fused (outer) modes
181183 size_type n_slabs_ = 1 ; // /< # of fused-index tile slabs
184+ bool general_repermute_ = false ; // /< whether the target layout differs
185+ // /< from the canonical result layout, so
186+ // /< the evaluated result is re-permuted
187+ // /< by a streaming unary eval
182188
183189 static unsigned int find (const BipartiteIndexList& indices,
184190 const std::string& index_label, unsigned int i,
@@ -660,16 +666,13 @@ class ContEngine : public BinaryEngine<Derived> {
660666 TA_ASSERT (nh > 0u ); // else this is a pure contraction
661667 n_fused_modes_ = nh;
662668
663- // initialize perm_; an interleaved target (a result permutation that
664- // mixes fused and free modes) is not yet supported -- the canonical
665- // result layout must equal the target
669+ // initialize perm_; a target that differs from the canonical (fused...,
670+ // left-free..., right-free...) result layout cannot be folded into the
671+ // batched tile op (BatchedContractReduce must be perm-free), so the
672+ // product is evaluated in its canonical layout and re-permuted to the
673+ // target by a streaming unary eval (see make_dist_eval_general)
666674 this ->init_perm (target_indices);
667- if (outer (target_indices) != outer (indices_))
668- TA_EXCEPTION (
669- " general products (fused + contracted + free indices): targets "
670- " that interleave fused and free indices are not yet supported; "
671- " reorder the result annotation to (fused..., left-free..., "
672- " right-free...)" );
675+ general_repermute_ = (outer (target_indices) != outer (indices_));
673676
674677 // the tile op operates on the folded (fused-mode-free) shapes
675678 const auto left_op = to_cblas_op (left_outer_permtype_);
@@ -712,6 +715,12 @@ class ContEngine : public BinaryEngine<Derived> {
712715
713716 trange_ = make_trange_general ();
714717 shape_ = make_shape_general ();
718+ if (general_repermute_) {
719+ // consumers see the target layout; the canonical structures are
720+ // recomputed in make_dist_eval_general for the inner Summa
721+ trange_ = outer (perm_) * trange_;
722+ shape_ = shape_.perm (outer (perm_));
723+ }
715724
716725 if (ExprEngine_::override_ptr_ && ExprEngine_::override_ptr_->shape ) {
717726 shape_ = shape_.mask (*ExprEngine_::override_ptr_->shape );
@@ -834,9 +843,37 @@ class ContEngine : public BinaryEngine<Derived> {
834843 }
835844 }
836845
846+ // / Streaming tile re-permute op for general products whose target layout
847+ // / differs from the canonical (fused..., free...) result layout: the
848+ // / batched tile op must stay perm-free, so the consumer-side unary eval
849+ // / applies the result permutation per tile instead
850+ struct GeneralRepermuteOp {
851+ typedef value_type result_type;
852+ typedef value_type argument_type;
853+ static constexpr bool is_consumable = false ;
854+ // / Only the *outer* (result-layout) permutation is applied here; inner
855+ // / (within-cell) permutation of tensor-of-tensor results is handled
856+ // / separately (see init_struct_general / implicit_permute_inner_), so this
857+ // / op stores a plain outer Permutation to avoid accidentally permuting
858+ // / inner contents.
859+ Permutation perm;
860+ // / false when the consumer fuses the permutation into its own operation
861+ // / (implicit permute, e.g. a transposed GEMM): then only the tile
862+ // / ordinals/trange are remapped (by the host UnaryEvalImpl) and the tile
863+ // / contents are delivered in the canonical layout
864+ bool permute_contents = true ;
865+ result_type operator ()(const argument_type& tile) const {
866+ if (!permute_contents) return tile;
867+ TiledArray::detail::Noop<value_type, value_type, false > noop;
868+ return noop (tile, perm);
869+ }
870+ };
871+
837872 // / Construct the distributed evaluator of a general product
838873
839- // / \return The batched-Summa distributed evaluator for this expression
874+ // / \return The batched-Summa distributed evaluator for this expression,
875+ // / wrapped in a streaming re-permute when the target layout differs from
876+ // / the canonical result layout
840877 dist_eval_type make_dist_eval_general () const {
841878 typedef TiledArray::detail::BatchedContractReduce<op_type> batched_op_type;
842879 typedef TiledArray::detail::Summa<typename left_type::dist_eval_type,
@@ -847,11 +884,46 @@ class ContEngine : public BinaryEngine<Derived> {
847884 typename left_type::dist_eval_type left = left_.make_dist_eval ();
848885 typename right_type::dist_eval_type right = right_.make_dist_eval ();
849886
850- std::shared_ptr<impl_type> pimpl = std::make_shared<impl_type>(
851- left, right, *world_, trange_, shape_, pmap_, perm_,
852- batched_op_type (op_, n_fused_modes_), K_ , proc_grid_, n_slabs_);
887+ if (!general_repermute_) {
888+ std::shared_ptr<impl_type> pimpl = std::make_shared<impl_type>(
889+ left, right, *world_, trange_, shape_, pmap_, perm_,
890+ batched_op_type (op_, n_fused_modes_), K_ , proc_grid_, n_slabs_);
891+ return dist_eval_type (pimpl);
892+ }
853893
854- return dist_eval_type (pimpl);
894+ // evaluate in the canonical layout (Summa with perm-free op), then
895+ // re-permute tiles to the target layout with a streaming unary eval;
896+ // trange_/shape_ hold the target-layout structures (see
897+ // init_struct_general), the canonical ones are recomputed here
898+ auto const canonical_trange = make_trange_general ();
899+ auto const canonical_shape = [this ]() {
900+ auto s = make_shape_general ();
901+ if (ExprEngine_::override_ptr_ && ExprEngine_::override_ptr_->shape ) {
902+ // the consumer-supplied mask is expressed in the target layout
903+ auto const inv_perm = outer (perm_).inv ();
904+ s = s.mask (ExprEngine_::override_ptr_->shape ->perm (inv_perm));
905+ }
906+ return s;
907+ }();
908+ // the inner Summa's result placement must be slab-replicated (the owner
909+ // of a tile independent of its slab index), regardless of the
910+ // (target-layout) pmap the consumer supplied for this node
911+ auto canonical_pmap = std::make_shared<TiledArray::detail::SlabbedPmap>(
912+ *world_, proc_grid_.make_pmap (), n_slabs_);
913+ std::shared_ptr<impl_type> pimpl = std::make_shared<impl_type>(
914+ left, right, *world_, canonical_trange, canonical_shape, canonical_pmap,
915+ BipartitePermutation{}, batched_op_type (op_, n_fused_modes_), K_ ,
916+ proc_grid_, n_slabs_);
917+ dist_eval_type canonical (pimpl);
918+
919+ typedef TiledArray::detail::UnaryEvalImpl<
920+ dist_eval_type, GeneralRepermuteOp, typename Derived::policy>
921+ repermute_impl_type;
922+ std::shared_ptr<repermute_impl_type> wrapper =
923+ std::make_shared<repermute_impl_type>(
924+ canonical, *world_, trange_, shape_, pmap_, perm_,
925+ GeneralRepermuteOp{outer (perm_), !this ->implicit_permute_outer_ });
926+ return dist_eval_type (wrapper);
855927 }
856928
857929 // / Expression identification tag
0 commit comments