Skip to content

refactor(linalg): constrain free Tensor/UniTensor scalar operators to scalar-like types (#1003) - #1093

Merged
yingjerkao merged 3 commits into
masterfrom
refactor/1003-constrain-tensor-operators
Jul 30, 2026
Merged

refactor(linalg): constrain free Tensor/UniTensor scalar operators to scalar-like types (#1003)#1093
yingjerkao merged 3 commits into
masterfrom
refactor/1003-constrain-tensor-operators

Conversation

@yingjerkao

Copy link
Copy Markdown
Collaborator

Problem

Ian's item 3 from the #1003 review. The free namespace-scope arithmetic/comparison operators for Tensor and UniTensor (+ - * / % == paired with a scalar) were declared as unconstrained template <class T>:

template <class T> Tensor operator+(const T&, const Tensor&);
template <class T> cytnx::UniTensor operator*(const T&, const cytnx::UniTensor&);
template <class T> Tensor operator==(const Tensor&, const T&);

So an arbitrary std / user-defined type could be deduced as T and made the operator a viable candidate in overload resolution (e.g. under using namespace cytnx), even though the operators are only ever specialized/instantiated for the cytnx scalar surface.

Fix

Add a cytnx_scalar_like concept in linalg.hpp admitting exactly that surface — the cytnx dtype scalars (reusing the existing CytnxType concept), cytnx::Scalar, and the Tensor::Tproxy / Scalar::Sproxy element proxies — and constrain the 22 free operator primary declarations with it.

  • The Tensor operators are full specializations, so constraining the primary declaration suffices (no .cpp change).
  • The UniTensor operators are generic templates, so their 8 generic definitions in Add/Sub/Mul/Div/Mod.cpp get the matching constraint (a constrained declaration needs a constrained definition).

linalg::Add/Sub/Mul/Div/Mod/Cpr (explicitly named — not an overload-resolution pollution vector; pybind uses these with explicit scalar casts) and the ExpH/ExpM templates are intentionally left unconstrained.

Not a breaking change for valid use: every builtin scalar literal deduces to a cytnx alias (intcytnx_int32, 1.0cytnx_double, …); a non-scalar operand that "compiled" before only ever failed at link (no instantiation exists) and now fails with a clear overload error instead.

Adds tests/operator_constraint_test.cpp: compile-time guards that cytnx_scalar_like admits the scalars/proxies and rejects other types, that scalar-on-the-left <op> Tensor/UniTensor is viable for scalars but not for a non-scalar struct (these fail on pre-fix code), plus a runtime check that scalar operators and Tensor<op>Tensor still work.

Testing

CPU (openblas): library + test_main build clean (the full library build confirmed no other call site relied on the loose operators); OperatorConstraint passes; pybind/tensor_py.cpp and unitensor_py.cpp compile clean. clang-format-14 clean.

Advances #1003 (Ian's operator-hygiene fold-in, item 3). Independent of the sibling #1003 branches (#1091 GPU in-place, #1092 complex_arithmetic).

🤖 Generated with Claude Code

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a C++20 concept cytnx_scalar_like to constrain the free arithmetic and comparison operators (+ - * / % ==) for Tensor and UniTensor to cytnx dtype scalars, cytnx::Scalar, and element proxies. This prevents arbitrary standard or user-defined types from being implicitly converted and used as operands. Corresponding unit tests have been added to verify these constraints at compile-time and runtime. The reviewer suggested explicitly including <type_traits> in include/linalg.hpp to ensure compilation robustness and avoid relying on transitive includes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread include/linalg.hpp
Comment on lines 18 to 19

namespace cytnx {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new cytnx_scalar_like concept relies on std::remove_cvref_t and std::is_same_v, which are defined in <type_traits>. To ensure compilation robustness and avoid relying on transitive includes, please explicitly include <type_traits>.

Suggested change
namespace cytnx {
#include <type_traits>\n\nnamespace cytnx {

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1489ecd8a9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread include/linalg.hpp
Comment on lines +3158 to 3159
template <cytnx_scalar_like T>
Tensor operator+(const Tensor &Lt, const T &rc);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Constrain the Tensor forward declarations too

When a user writes tensor + some_non_scalar (and similarly -, *, or / with the scalar-looking operand on the right), this constrained declaration does not actually remove the unconstrained candidate: include/Tensor.hpp is included before linalg.hpp and still forward-declares template <class T> Tensor operator+(const Tensor&, const T&). In C++ that leaves a separate unconstrained overload viable whenever T fails cytnx_scalar_like, so those calls still compile until the same undefined-reference failure this change was meant to eliminate; the new tests only cover the scalar-on-the-left direction.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Jul 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.06%. Comparing base (cf539f0) to head (c74c495).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1093   +/-   ##
=======================================
  Coverage   73.06%   73.06%           
=======================================
  Files         224      224           
  Lines       27642    27642           
  Branches       71       71           
=======================================
  Hits        20196    20196           
  Misses       7425     7425           
  Partials       21       21           
Flag Coverage Δ
cpp 73.18% <ø> (ø)
python 64.13% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
C++ backend 72.54% <ø> (ø)
Python bindings 76.75% <ø> (ø)
Python package 64.13% <ø> (ø)
Files with missing lines Coverage Δ
include/Tensor.hpp 92.27% <ø> (ø)
src/linalg/Add.cpp 60.29% <ø> (ø)
src/linalg/Div.cpp 86.91% <ø> (ø)
src/linalg/Mod.cpp 77.65% <ø> (ø)
src/linalg/Mul.cpp 76.90% <ø> (ø)
src/linalg/Sub.cpp 86.28% <ø> (ø)

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update cf539f0...c74c495. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

yingjerkao added a commit that referenced this pull request Jul 20, 2026
Review follow-up (#1093): the cytnx_scalar_like concept uses std::remove_cvref_t and
std::is_same_v, so include <type_traits> directly rather than relying on a transitive
include (Gemini).

Also documents in operator_constraint_test.cpp the scalar-on-the-right gap Codex
raised: `Tensor + non_scalar` still binds the unconstrained free operator forward-
declared in Tensor.hpp. Those forward declarations are load-bearing for Tensor's inline
member templates (e.g. `Mul(const T&){ return *this * rhs; }`, and operator* has no
member form), and cannot be constrained in place because cytnx_scalar_like depends on
the not-yet-complete Tensor::Tproxy at that point; closing the gap needs a
concept/header reorganization, deferred to a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bff592b25e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread include/linalg.hpp Outdated
Comment on lines +30 to +31
std::is_same_v<std::remove_cvref_t<T>, Tensor::Tproxy> ||
std::is_same_v<std::remove_cvref_t<T>, Scalar::Sproxy>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude proxies from unsupported operators

Because this one concept is used for all of the constrained % and == declarations too, Tensor::Tproxy and Scalar::Sproxy remain viable operands even where no specialization/definition exists; repo-wide search shows no operator%/operator== proxy specializations, and no Tensor::Tproxy specializations for the UniTensor operators. In contexts such as tensor.at({0}) == tensor or tensor.at({0}) % tensor, overload resolution now accepts the operand as cytnx_scalar_like but the program still fails with an undefined reference, which preserves the link-time failure this change is meant to turn into a non-viable overload.

Useful? React with 👍 / 👎.

@yingjerkao

Copy link
Copy Markdown
Collaborator Author

Thanks — addressed what I safely could:

  • Gemini (<type_traits>): added the explicit include to linalg.hpp.

  • Codex (constrain the Tensor.hpp forward declarations): good catch, and it's real — Tensor + non_scalar still binds the unconstrained forward-declared free operator and compiles. But it's not a small fix. Those forward declarations are load-bearing for Tensor's inline member templates, e.g.

    template <class T> Tensor Mul(const T &rhs) { return *this * rhs; }

    (operator*/operator/ have no member form). Removing them breaks the build at Tensor.hpp:1357. And they can't simply be constrained in place: cytnx_scalar_like depends on Tensor::Tproxy, which is still incomplete at the point of the forward declarations, so the concept isn't available there.

    Closing the scalar-on-the-right gap needs a concept/header reorganization (defining a scalar-like concept early, or reworking those member templates). I've documented the gap in operator_constraint_test.cpp and propose it as a scoped follow-up rather than risking the header restructure in this PR.

@yingjerkao

Copy link
Copy Markdown
Collaborator Author

Review status (bot threads) — all three look already addressed by the current branch head:

  • <type_traits> include for cytnx_scalar_like (@gemini-code-assist): present at include/linalg.hpp:15.
  • Exclude/handle proxies (@chatgpt-codex-connector): the concept already lists Tensor::Tproxy and Scalar::Sproxy (linalg.hpp:30-31).
  • Constrain the Tensor operator forward declarations too (@chatgpt-codex-connector): the forward decls are already template <cytnx_scalar_like T> (e.g. linalg.hpp:3159).

Ready to resolve.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@yingjerkao
yingjerkao requested review from IvanaGyro and ianmccul July 24, 2026 08:56

@ianmccul ianmccul left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Tensor RHS remains unconstrained.

    Cytnx/include/Tensor.hpp

    Lines 30 to 37 in bff592b

    template <class T>
    Tensor operator+(const Tensor &lhs, const T &rc);
    template <class T>
    Tensor operator-(const Tensor &lhs, const T &rhs);
    template <class T>
    Tensor operator*(const Tensor &lhs, const T &rhs);
    template <class T>
    Tensor operator/(const Tensor &lhs, const T &rhs);

  2. cytnx_scalar_like does not describe the implemented concept. Tensor::Tproxy is a deferred Tensor selection, potentially of any rank, not inherently scalar. Some proxy arithmetic works by materializing it as a Tensor:

 Tensor operator+<Tensor::Tproxy>(...) {
   return Tensor(lc) + Rt;
 }

That should be a proxy-specific overload, not membership in a scalar concept.

Worse, the concept admits combinations with no implementation:

     Tproxy % Tensor
     Tproxy == Tensor
     Tproxy + UniTensor
     Scalar % UniTensor
     Sproxy % UniTensor

These declarations participate in overload resolution but end in undefined references.

  1. Implicit Tensor construction still admits non-scalars.
    The test itself avoids containers because std::vector<cytnx_uint64> converts implicitly to Tensor. Therefore:
     std::vector<cytnx_uint64> shape{2, 3};
     auto result = shape + tensor;

can bind Tensor + Tensor. That directly contradicts the PR’s claim that arbitrary standard-library operands become non-viable. The Tensor shape constructors should ultimately be explicit.

  1. The tests encode the known hole.
    They test only selected scalar-left expressions and deliberately leave scalar-right untested. They should cover every affected operator, both directions, and all admitted proxy categories.

yingjerkao and others added 3 commits July 29, 2026 07:16
… scalar-like types (#1003)

Ian's item 3 from the #1003 review. The free namespace-scope arithmetic and
comparison operators for Tensor and UniTensor (`+ - * / % ==` paired with a
scalar) were declared as unconstrained `template <class T>`, so an arbitrary
std / user-defined type could be deduced as T and made the operator a viable
candidate in overload resolution (e.g. under `using namespace cytnx`), even
though the operators are only ever instantiated/specialized for the cytnx
scalar surface.

Introduce a `cytnx_scalar_like` concept in linalg.hpp that admits exactly that
surface -- the cytnx dtype scalars (reusing the existing `CytnxType` concept),
`cytnx::Scalar`, and the `Tensor::Tproxy` / `Scalar::Sproxy` element proxies --
and constrain the 22 free operator primary declarations with it. The Tensor
operators are full specializations (constraining the primary declaration is
enough); the UniTensor operators are generic templates, so their 8 generic
definitions in Add/Sub/Mul/Div/Mod.cpp get the matching constraint.

`linalg::Add/Sub/Mul/Div/Mod/Cpr` (explicitly named, not operator-resolution
pollution) and the matrix-exponential `ExpH/ExpM` templates are intentionally
left unconstrained. This does not break valid use: every builtin scalar literal
deduces to a cytnx alias (int -> cytnx_int32, etc.); a non-scalar operand that
was accepted before only ever failed at link (no instantiation) and now fails
with a clear overload error.

Adds tests/operator_constraint_test.cpp: compile-time guards that
`cytnx_scalar_like` admits the scalars/proxies and rejects other types, that
`scalar-on-the-left <op> Tensor/UniTensor` is viable for scalars and not for a
non-scalar struct (the guards fail on pre-fix code), plus a runtime check that
scalar operators and Tensor<op>Tensor still work.

Testing (CPU, openblas): library + test_main build clean; OperatorConstraint
passes; pybind tensor_py.cpp / unitensor_py.cpp compile clean (they use the
member .Add()/linalg::Add paths, not the free operators). clang-format-14 clean.

Advances #1003 (Ian's operator-hygiene fold-in, item 3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review follow-up (#1093): the cytnx_scalar_like concept uses std::remove_cvref_t and
std::is_same_v, so include <type_traits> directly rather than relying on a transitive
include (Gemini).

Also documents in operator_constraint_test.cpp the scalar-on-the-right gap Codex
raised: `Tensor + non_scalar` still binds the unconstrained free operator forward-
declared in Tensor.hpp. Those forward declarations are load-bearing for Tensor's inline
member templates (e.g. `Mul(const T&){ return *this * rhs; }`, and operator* has no
member form), and cannot be constrained in place because cytnx_scalar_like depends on
the not-yet-complete Tensor::Tproxy at that point; closing the gap needs a
concept/header reorganization, deferred to a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…alar concepts (#1003)

Addresses Ian's review on #1093 ("Tensor RHS remains unconstrained") and the
two Codex findings.

The constraint was cosmetic in the Tensor-on-the-left direction. Tensor.hpp
forward-declares the same free operators that linalg.hpp declares, and those
forward declarations were unconstrained. A differently-constrained declaration
is a *distinct* template, so the unconstrained candidate stayed viable for
every non-scalar T and `Tensor + NotScalar` still compiled through to the
undefined reference the constraint was meant to prevent.

The forward declarations now carry the identical constraint. They had to move
below the class definition: cytnx_scalar_like names Tensor::Tproxy, which is
not available until Tensor is complete. That is safe because the inline member
templates that call them (`Tensor Mul(const T&) { return *this * rhs; }`) form
dependent expressions, bound by ADL at the point of instantiation.

Auditing which operand types are actually instantiated showed the single
concept over-admitted in two directions, each of which accepted an operand at
overload resolution only to fail at link:

  cytnx_scalar_value           dtypes + Scalar             -> % (both), == (Tensor)
  cytnx_unitensor_scalar_like  + Scalar::Sproxy            -> UniTensor + - * /
  cytnx_scalar_like            + both element proxies      -> Tensor + - * /

`%` and `==` have no proxy instantiations at all (Mod.cpp, Cpr.cpp), and the
UniTensor operators are instantiated for Scalar::Sproxy but never for
Tensor::Tproxy (Add.cpp). Verified by search that no specialization anywhere
depends on an excluded type. Declarations and definitions are retargeted
together, since a mismatch would silently split the template again.

Also adds <type_traits> to Tensor.hpp, which is where the concepts now live.

Tests assert the previously-untested RHS direction and the exact membership of
all three concepts. Confirmed the new guards fail on the pre-fix code:
reverting only the forward-declaration constraint fails exactly the four
`!cy_*able<Tensor, NotScalar>` assertions and nothing else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@yingjerkao
yingjerkao force-pushed the refactor/1003-constrain-tensor-operators branch from bff592b to c74c495 Compare July 28, 2026 23:26
@yingjerkao

Copy link
Copy Markdown
Collaborator Author

@ianmccul addressed — you were right, and the gap was larger than it looked. Also rebased onto master (the branch was 179 commits behind, so the previous green checks meant very little).

Your point: the Tensor RHS

  1. Tensor RHS remains unconstrained.

The constraint was cosmetic in that direction. include/Tensor.hpp forward-declares the same free operators that linalg.hpp declares, and those forward declarations were unconstrained:

// Tensor.hpp -- before
template <class T>
Tensor operator+(const Tensor &lhs, const T &rc);

A differently-constrained declaration is a distinct template, so that unconstrained candidate stayed viable for every non-scalar T. Tensor + NotScalar still compiled straight through to the undefined reference the constraint was supposed to turn into an overload-resolution failure. Codex independently flagged the same thing.

Those forward declarations now carry the identical constraint. They had to move below the class definition, because cytnx_scalar_like names Tensor::Tproxy and that nested type isn't available until Tensor is complete. That's safe: the inline member templates that depend on them — Tensor Mul(const T &rhs) { return *this * rhs; }, where operator* has no member form — are dependent expressions, so they bind by ADL at the point of instantiation, not at the point of definition.

What auditing the instantiations turned up

Checking which operand types are actually instantiated showed the single concept over-admitted in two directions, each accepting an operand at overload resolution only to die at link:

concept admits used by
cytnx_scalar_value dtypes + Scalar % (Tensor + UniTensor), == (Tensor)
cytnx_unitensor_scalar_like + Scalar::Sproxy UniTensor + - * /
cytnx_scalar_like + both element proxies Tensor + - * /
  • % and == have no proxy instantiations at all — Mod.cpp and Cpr.cpp specialize only the dtype scalars and Scalar. This is Codex's second finding.
  • The UniTensor operators are instantiated for Scalar::Sproxy but never for Tensor::Tproxy (Add.cpp), so unitensor + tproxy had the identical defect. Codex didn't flag this one; it's the same bug in the other object family, so per the repo guardrail on fixing the whole family I closed it too.

I verified by search that no specialization anywhere depends on a type I excluded, and retargeted declarations and definitions together — a mismatch between them would silently split the template again, which is the exact failure this PR is about.

Verification

The new guards are shown to fail on the pre-fix code, not just pass on the fixed code. Reverting only the forward-declaration constraint and recompiling:

operator_constraint_test.cpp:110: error: static assertion failed
  static_assert(!cy_addable<Tensor, NotScalar>);
operator_constraint_test.cpp:111: error: static assertion failed
  static_assert(!cy_mulable<Tensor, NotScalar>);
operator_constraint_test.cpp:112: error: static assertion failed
  static_assert(!cy_subable<Tensor, NotScalar>);
operator_constraint_test.cpp:113: error: static assertion failed
  static_assert(!cy_divable<Tensor, NotScalar>);

Exactly those four, nothing else — so the guard covers the reported defect and no more. The stale NOTE that documented this as an open follow-up is gone, replaced by the assertions.

test_main builds and passes on debug-openblas-cpu; pre-commit clean (clang-format v14). Checked separately that pybind doesn't instantiate any of the affected operators.

Gemini's <type_traits> note is handled — it now applies to Tensor.hpp, which is where the concepts live.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c74c495a24

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread include/linalg.hpp
Comment on lines +217 to 218
template <cytnx_scalar_value T>
cytnx::UniTensor operator%(const T &lc, const cytnx::UniTensor &Rt);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude Scalar from UniTensor modulo constraints

For Scalar s and UniTensor u, both s % u and u % s pass this constraint because cytnx_scalar_value includes Scalar, but src/linalg/Mod.cpp explicitly instantiates the UniTensor modulo templates only for the eleven dtype types; a repo-wide search finds operator%<Scalar> and Mod<Scalar> only for Tensor. These calls therefore still fail with undefined references instead of being rejected during overload resolution. This is fresh evidence beyond the earlier proxy-specific comment: use a UniTensor-modulo concept that excludes Scalar, or add the corresponding operator and linalg::Mod instantiations in both directions.

Useful? React with 👍 / 👎.

@IvanaGyro
IvanaGyro removed their request for review July 29, 2026 03:06
@yingjerkao
yingjerkao requested a review from ianmccul July 29, 2026 16:34
@yingjerkao

Copy link
Copy Markdown
Collaborator Author

@ianmccul re-review requested — your CHANGES_REQUESTED is the only thing left blocking this one.

Your item is fixed, and it was load-bearing: the constraint was cosmetic in the Tensor-on-the-left direction because Tensor.hpp forward-declared the same operators unconstrained, and a differently-constrained declaration is a distinct template, so the unconstrained candidate stayed viable for every non-scalar T.

Details, including the two related gaps the instantiation audit turned up, are in #1093 (comment). Short version:

  • Your item — the Tensor.hpp forward declarations now carry the identical constraint. They moved below the class, since cytnx_scalar_like names Tensor::Tproxy; safe because the inline members that call them are dependent expressions bound by ADL at instantiation.
  • Concept split — one concept was over-admitting in two directions. %/== have no proxy instantiations at all, and the UniTensor operators are instantiated for Scalar::Sproxy but never Tensor::Tproxy. Now three concepts, each admitting exactly what links.
  • The guard is verified against the bug — reverting only the forward-declaration constraint fails exactly the four new !cy_*able<Tensor, NotScalar> assertions and nothing else.

Also rebased onto master (it was 179 commits behind) and now 20/20 green, including the GPU suite on the self-hosted runner.

One thing worth your eye specifically: the UniTensor / Tproxy narrowing is not something you asked for. unitensor + tproxy previously compiled and failed at link; it is now rejected at overload resolution. That is a deliberate behavior change on the same reasoning as your item, applied to the other object family per the repo guardrail — but if you would rather that stayed as-is, it is a two-line revert.

@ianmccul

Copy link
Copy Markdown
Collaborator

UniTensor / (rank-0 Tproxy) should arguably work, but C++ users won't care, if Python uses another path then there is no problem.

@yingjerkao
yingjerkao merged commit 2066221 into master Jul 30, 2026
26 of 27 checks passed
@yingjerkao
yingjerkao deleted the refactor/1003-constrain-tensor-operators branch July 30, 2026 02:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants