Skip to content

ContractReduce's conj finalization requires conj_to, so a tile with only value-returning conj fails on the unpermuted path #585

Description

@evaleev

TiledArray::conj and TiledArray::conj_to are two independent ADL customization points in tile_op/tile_interface.h, and the default conj_to is SFINAE-constrained on a conj_to() member:

template <typename Arg>                      // value-returning
inline auto conj(const Arg& arg) { return arg.conj(); }

template <typename Result,
          typename = std::enable_if_t<
              detail::has_member_function_conj_to_anyreturn_v<Result&&>>>
inline decltype(auto) conj_to(Result&& result) { // in-place
  return std::forward<Result>(result).conj_to();
}

A custom tile that implements value-returning conj but no conj_to is therefore a legal partial implementation of the tile interface — the pattern AGENTS.md/CLAUDE.md explicitly blesses (tests/sparse_tile.h's EigenSparseTile omits subt for the same reason) — yet it cannot be used in a conjugated contraction.

The asymmetry

Grepping for consumers: conj and conj_to have exactly one call site each in the whole library, and it is the same functionContractReduce's ComplexConjugate finalization — picking between them on whether a result permutation is present:

src/TiledArray/tile_op/contract_reduce.h   conj_to(temp)        // unpermuted
src/TiledArray/tile_op/contract_reduce.h   conj(temp, perm)     // permuted

So for such a tile:

c("k,i") = conj(a("i,j") * b("j,k"));   // compiles   (permuted   -> conj)
c("i,k") = conj(a("i,j") * b("j,k"));   // hard error (unpermuted -> conj_to)

The same operation compiles or not depending on whether the caller happened to permute the result. Unary conj(a("i,j")) is unaffected — it is a Scal expression with a ComplexConjugate factor and goes through scale/scale_to, not these CPOs.

Scope

Latent, not live: every in-tree tile supplies conj_toTA::Tensor, TensorInterface, ArenaTensor, TA::Tile (forwards to the wrapped tensor), and btas::Tensor. This predates #574, which moved the two ComplexConjugate specializations behind one if constexpr without touching the dispatch.

Fix

Prefer in-place conj_to when it is viable and fall back to value-returning conj otherwise, keeping the in-place path for Tensor/ArenaTensor/Tile/btas::Tensor. Both unpermuted branches need it (conj_to(temp) and conj_to(temp, factor)).

Trap to avoid: the detector must test the ADL expression conj_to(temp), not detail::has_member_function_conj_to_anyreturn_v. btas::Tensor has no conj_to member — only free functions in namespace btas (external/btas.h) — so a member-based detector would classify BTAS as lacking conj_to and silently demote it from in-place conjugation to an allocate/copy/free per tile. That is a performance regression that would pass every existing test.

Wants a regression test with a tile type that provides conj and no conj_to, plus a check that BTAS still takes the in-place path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions