Skip to content

complex64 matrix exponential unitarity check fails intermittently #18

Description

@ultimatile

TEST_CASE("TCI Matrix exponential - complex64 dtype preservation") in test/source/test_linear_algebra.cpp fails in about half of full-suite runs and passes in the rest, from one binary with no rebuild in between. The assertion that fails is the unitarity check, not the dtype check.

Both observations below are on f59751a, with external/Cytnx and external/tcict — the conformance-test suite, both public per .gitmodules — at their pinned revisions. All commands run from the repository root. Environment: macOS arm64, Apple clang version 21.0.0 (clang-2100.1.1.101), Command Line Tools 26.6.0, Homebrew OpenBLAS 0.3.34.

Reproduction

cmake -Stest -Bbuild-a -DCMAKE_BUILD_TYPE=Debug \
      -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/macos-homebrew.cmake
cmake --build build-a -j8 --target TCITests

This is the configure shape .github/workflows/macos.yml and .github/workflows/ubuntu.yml use, plus the toolchain file needed to find keg-only OpenBLAS. The source root is test/, so the binary lands at build-a/TCITests.

Running it eight times: four runs reported the failure, four did not. Every failure reported the same value, and it is far outside the tolerance rather than marginally over it:

===============================================================================
test/source/test_linear_algebra.cpp:36:
TEST CASE:  TCI Matrix exponential - complex64 dtype preservation

test/source/test_linear_algebra.cpp:56: ERROR: CHECK( std::abs(col0_norm_sq - 1.0f) < 1e-2f ) is NOT correct!
  values: CHECK( 0.492126 <  0.01 )

Six runs of the same binary restricted to --test-case="TCI Matrix exponential*" all passed. The suite is doctest and add_test passes no ordering flags, so cases run in declaration order with no randomization — yet the same binary in the same order gives different answers across runs.

The sanitizer build does not reproduce it

brew-debug is in the tracked CMakePresets.json and roots at the repository, so its binary is one level deeper:

cmake --preset brew-debug -Bbuild-b
cmake --build build-b -j8 --target TCITests
ASAN_OPTIONS=detect_odr_violation=1:detect_container_overflow=0:abort_on_error=0:halt_on_error=0 \
  build-b/test/TCITests

Eight runs, none reported the failure. Both arms were driven the same way — the test binary executed directly, no ctest wrapper — so the harness is not the variable.

Two caveats on this arm, both of which weaken it as evidence:

  • The preset sets ENABLE_ASAN_ODR=ON, declared in the root CMakeLists.txt, which adds -fsanitize=address -g -O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls. That is a change of optimization level as well as a sanitizer, so this arm does not isolate the sanitizer. A third build at -O1 without it would separate them; I have not run one.
  • The ASAN_OPTIONS string is the one the root CMakeLists.txt attaches to the TCITests ctest entry. Its detect_container_overflow=0 is load-bearing — without it the binary aborts early on a libc++ container check — but that detector is also how ASan would report an out-of-bounds write, which is one of the shapes that could explain a run-to-run-varying element. So this arm's silence may be partly self-inflicted.

Why a precision explanation does not fit

The case builds a 2x2 tensor, zero-initialized, then sets two entries:

tci::CytnxTensor<cytnx::cytnx_complex64> anti_herm_f;
tci::zeros(ctx, {2, 2}, anti_herm_f);
tci::set_elem(ctx, anti_herm_f, {0, 1}, cytnx::cytnx_complex64(1.0f, 0.0f));
tci::set_elem(ctx, anti_herm_f, {1, 0}, cytnx::cytnx_complex64(-1.0f, 0.0f));

The exponential of that matrix is a plane rotation, so its first column has unit norm exactly, and 0.492126 is wrong by roughly half rather than by a rounding step.

I have not identified the mechanism. The fact to explain is that the result varies run to run for a fixed binary and a fixed case order.

Note

The case creates its context as tci::context_handle_t<tci::CytnxTensor<cytnx::cytnx_complex128>> and then passes it to cytnx::cytnx_complex64 tensors and operations. CytnxContextHandle wraps only an int device id, so the mismatch looks cosmetic rather than causal — recorded in case it is not.

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