Add Tensor.IndexOfMaxNumber - #129582

Open
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber
Open

Add Tensor.IndexOfMaxNumber#129582
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber

Conversation

@lilinus

Copy link
Copy Markdown
Contributor

Closes#98862

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 18, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics-tensors
See info in area-owners.md if you want to be subscribed.

@lilinuslilinus changed the title Tensor.IndexOfMaxNumberAdd Tensor.IndexOfMaxNumberJun 22, 2026

@jeffhandleyjeffhandley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note

This review was generated by the holistic code review workflow being iterated on as part of #130339. Please treat its findings as assistive input for human review.

Holistic Review

Motivation: The linked API proposal covers a real parity gap in TensorPrimitives: the Number min/max variants existed, but their index-returning counterparts did not. The PR is appropriately scoped to the remaining approved APIs.

Approach: Reusing the existing IndexOfMinMaxCore shape is the right general direction, and the scalar comparisons match the intended NaN-ignoring and signed-zero semantics. However, the vectorized path appears to miss the existing Half-specific treatment used by the sibling *Number aggregation APIs.

Summary: ⚠️ Needs Changes. API approval check passed: #98862 has api-approved, applied by @bartonjs, and the ref additions match the remaining approved IndexOf*Number(ReadOnlySpan<T> x) signatures and parameter names. The blocking concern is that Half can enter the vectorized index core, while the new vector comparisons only implement maximumNumber/minimumNumber NaN handling for float/double, which can return -1 or the wrong index when non-NaN Half values follow NaNs.


Detailed Findings

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

The new Number operators handle NaN-ignoring semantics in scalar code, but their vector Compare methods special-case only double and float before falling back to plain GreaterThan/LessThan (for example TensorPrimitives.IndexOfMaxNumber.cs lines 59-81, TensorPrimitives.IndexOfMinNumber.cs lines 60-82, and the magnitude variants at lines 71-116). IndexOfMinMaxCore dispatches to vectorized implementations solely from Vector128/256/512<T>.IsSupported (TensorPrimitives.IIndexOfMinMaxOperator.cs lines 24-51), with no Half-specific escape hatch.

That differs from the existing sibling aggregation APIs, which explicitly route Half through a float-based helper for these operations (MaxNumber.cs line 27, MinNumber.cs line 27, MaxMagnitudeNumber.cs line 30, MinMagnitudeNumber.cs line 30). If a Half input reaches the vectorized index path with an initial vector/lane containing NaN and a later non-NaN in the same lane, the fallback GreaterThan/LessThan comparison against NaN is false, so the non-NaN value does not replace the NaN; the final aggregate can remain NaN and the method returns -1 even though a valid number exists. Please either add equivalent Half handling for the index operators or keep Half on the scalar/fallback path, and include a regression case where the first vector is NaN-filled and the selected non-NaN appears later.

✅ API Approval — Approved surface matches the PR additions

New public API surface is present in src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs lines 992-998. I verified linked issue #98862 has the api-approved label; the timeline shows @bartonjs applied it immediately after posting the approved C# shape on 2024-04-11. The PR adds the remaining approved methods with matching names, return type, ReadOnlySpan<T> x parameter name, and where T : INumber<T> constraints; the other APIs from the approved shape were already present from prior PRs.

✅ Tests — Important semantics are covered, but add the Half regression with the fix

The new tests cover empty input, all-NaN input, NaNs being ignored, signed-zero/magnitude tie-breaking, duplicate ties choosing the first matching index, and very large indices (TensorPrimitives.Generic.cs lines 2864-3232). Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

@lilinus

Copy link
Copy Markdown
ContributorAuthor

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

This should be disregarded. Half is not supported for vectorized path, hence the code (should) produce correct results. Half is well covered by unit tests so if it will be supported by vectors in the future, unit tests will start failing and the implementation will have to be slightly adjusted. If we want to optimize this without vector support for Half using e.g. widening, I would suggest doing it in a separate PR.

Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

This is alread covered in e.g. IndexOfMinMagnitudeNumber_NaNsNotReturned.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_reviewed_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_recorded_worker_run_id": "29729479578",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "b5b4ee386b773b8653af85d469005483d9a0b4f1",
"review_id": 4730737620
},
{
"commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"review_id": 4733540120
}
]
}

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber overloads that slipped through when the Number (NaN-ignoring) variants of Min/Max were added, closing approved API issue #98862 (labeled api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and consistent with the codebase. Rather than duplicating the vectorized index search, the PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so the NaN-propagating operators keep their fast early-exit while the new Number operators skip it and instead fall through to the aggregate, returning -1 only when the whole span is NaN. The per-lane and per-vector Compare implementations mirror the established MaxNumber/MinNumberOperator semantics (NaN-ignoring, signed-zero tiebreak, overflow-aware magnitude handling).

Summary: ✅ LGTM. New public API is approved and matches the ref exactly; the refactor is minimal and reuses the existing search infrastructure; tests cover empty spans, all-NaN spans, NaN-not-returned, signed-zero/sign-magnitude tiebreaks, all tensor lengths, and the large-index (>int.MaxValue byte offset) path across the generic numeric type matrix, including the new IsUnsignedInteger/NFloat guards. One non-blocking nit noted inline about operator-precedence style consistency in dead-code branches.


Detailed Findings

✅ API surface — matches approved proposal

The four new int IndexOf*Number<T>(ReadOnlySpan<T>) where T : INumber<T> methods in ref/System.Numerics.Tensors.netcore.cs correspond exactly to the api-approved proposal in #98862, and all four new source files are registered in the csproj Compile list.

✅ Correctness — NaN and edge-case semantics

The ShouldEarlyExitOnNan => false operators correctly defer the NaN decision to the aggregate: IndexOfMinMaxFallback now returns -1 when the final result is NaN, and each vectorized SizeN tail checks T.IsNaN(aggResult) before computing the index, so an all-NaN span yields -1 while a mixed span ignores NaN lanes. The scalar and vector Compare overloads consistently implement NaN-ignoring comparison with the positive/negative-zero (and sign-magnitude) tiebreak, matching the documented maximumNumber/minimumNumber IEEE semantics and the existing non-index MaxNumber/MinMagnitudeNumber operators.

⚠️ Style — operator-precedence inconsistency (non-blocking, flagged inline)

Two of the migrated conditions (IndexOfMinMaxVectorized128Size2 and ...Size1) omit the parentheses around the typeof(T) == float || typeof(T) == double sub-expression that all other 22 call sites use. It is harmless because those methods only handle 1-/2-byte integer element types, making the float/double test dead, but the inconsistency is a copy/paste hazard. See the inline comment.

✅ Test quality — thorough

Tests are added per-operation and exercise: empty → -1, all-NaN → -1, NaN values never returned, signed-zero and negative-vs-positive magnitude tiebreaks, all Helpers.TensorLengths (covering scalar/128/256/512 paths and remainders), and the IndexOfSizeExceedingMaxValue large-index path. The new IsUnsignedInteger predicate (extended for UInt128/nuint) properly gates the sign-magnitude tests, and IsFloatingPoint is correctly extended to include NFloat. The AllLengths assertions tolerate ties by verifying the returned index's value equals the expected extreme, which is the right contract for an index-of over duplicate extremes.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 150.9 AIC · ⌖ 11.1 AIC · ⊞ 10K

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified and unchanged. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber NaN-ignoring overloads that close approved API issue #98862 (api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and unchanged. The PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so NaN-propagating operators keep the fast early-exit while the new Number operators defer the NaN decision to the aggregate, returning -1 only for an all-NaN span. The new commit ba0db92 ("Add missing parenthesis") adds the parentheses around the typeof(T) == float || typeof(T) == double sub-expression at the three remaining call sites, so all ShouldEarlyExitOnNan && (float || double) conditions are now consistently grouped.

Summary: ✅ LGTM. The sole new commit resolves the operator-precedence style nit that the initial review flagged inline. No behavioral change (the affected branches were dead code for the 1-/2-byte integer element types those methods handle), and the fix removes a copy/paste hazard. No new actionable findings.

Assessment History

  • review 4730737620 (commit b5b4ee3): verdict ✅ LGTM. Current verdict: ✅ LGTM — unchanged. Motivation, approach, and risk are unchanged; the only new patch (ba0db92) simply applies the parenthesization suggested by that review's non-blocking inline nit, so the previously-noted style inconsistency is now resolved.

Detailed Findings

✅ Resolved — operator-precedence consistency

The incremental commit adds the missing parentheses at the three previously-inconsistent sites (IndexOfMinMaxVectorized128Size2, ...Size1, and the additional migrated condition), so every ShouldEarlyExitOnNan && (typeof(T) == typeof(float) || typeof(T) == typeof(double)) guard now reads uniformly. This addresses the non-blocking style finding from the prior review with no semantic change.

No new findings in the incremental scope.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.5 AIC · ⌖ 9.86 AIC · ⊞ 10K

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

Labels

area-System.Numerics.Tensorscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Add missing Min/MaxNumber generic math APIs on TensorPrimitives

3 participants

@lilinus@jeffhandley@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add Tensor.IndexOfMaxNumber - #129582

Open
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber
Open

Add Tensor.IndexOfMaxNumber#129582
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber

Conversation

@lilinus

Copy link
Copy Markdown
Contributor

Closes#98862

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 18, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics-tensors
See info in area-owners.md if you want to be subscribed.

@lilinuslilinus changed the title Tensor.IndexOfMaxNumberAdd Tensor.IndexOfMaxNumberJun 22, 2026

@jeffhandleyjeffhandley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note

This review was generated by the holistic code review workflow being iterated on as part of #130339. Please treat its findings as assistive input for human review.

Holistic Review

Motivation: The linked API proposal covers a real parity gap in TensorPrimitives: the Number min/max variants existed, but their index-returning counterparts did not. The PR is appropriately scoped to the remaining approved APIs.

Approach: Reusing the existing IndexOfMinMaxCore shape is the right general direction, and the scalar comparisons match the intended NaN-ignoring and signed-zero semantics. However, the vectorized path appears to miss the existing Half-specific treatment used by the sibling *Number aggregation APIs.

Summary: ⚠️ Needs Changes. API approval check passed: #98862 has api-approved, applied by @bartonjs, and the ref additions match the remaining approved IndexOf*Number(ReadOnlySpan<T> x) signatures and parameter names. The blocking concern is that Half can enter the vectorized index core, while the new vector comparisons only implement maximumNumber/minimumNumber NaN handling for float/double, which can return -1 or the wrong index when non-NaN Half values follow NaNs.


Detailed Findings

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

The new Number operators handle NaN-ignoring semantics in scalar code, but their vector Compare methods special-case only double and float before falling back to plain GreaterThan/LessThan (for example TensorPrimitives.IndexOfMaxNumber.cs lines 59-81, TensorPrimitives.IndexOfMinNumber.cs lines 60-82, and the magnitude variants at lines 71-116). IndexOfMinMaxCore dispatches to vectorized implementations solely from Vector128/256/512<T>.IsSupported (TensorPrimitives.IIndexOfMinMaxOperator.cs lines 24-51), with no Half-specific escape hatch.

That differs from the existing sibling aggregation APIs, which explicitly route Half through a float-based helper for these operations (MaxNumber.cs line 27, MinNumber.cs line 27, MaxMagnitudeNumber.cs line 30, MinMagnitudeNumber.cs line 30). If a Half input reaches the vectorized index path with an initial vector/lane containing NaN and a later non-NaN in the same lane, the fallback GreaterThan/LessThan comparison against NaN is false, so the non-NaN value does not replace the NaN; the final aggregate can remain NaN and the method returns -1 even though a valid number exists. Please either add equivalent Half handling for the index operators or keep Half on the scalar/fallback path, and include a regression case where the first vector is NaN-filled and the selected non-NaN appears later.

✅ API Approval — Approved surface matches the PR additions

New public API surface is present in src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs lines 992-998. I verified linked issue #98862 has the api-approved label; the timeline shows @bartonjs applied it immediately after posting the approved C# shape on 2024-04-11. The PR adds the remaining approved methods with matching names, return type, ReadOnlySpan<T> x parameter name, and where T : INumber<T> constraints; the other APIs from the approved shape were already present from prior PRs.

✅ Tests — Important semantics are covered, but add the Half regression with the fix

The new tests cover empty input, all-NaN input, NaNs being ignored, signed-zero/magnitude tie-breaking, duplicate ties choosing the first matching index, and very large indices (TensorPrimitives.Generic.cs lines 2864-3232). Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

@lilinus

Copy link
Copy Markdown
ContributorAuthor

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

This should be disregarded. Half is not supported for vectorized path, hence the code (should) produce correct results. Half is well covered by unit tests so if it will be supported by vectors in the future, unit tests will start failing and the implementation will have to be slightly adjusted. If we want to optimize this without vector support for Half using e.g. widening, I would suggest doing it in a separate PR.

Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

This is alread covered in e.g. IndexOfMinMagnitudeNumber_NaNsNotReturned.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_reviewed_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_recorded_worker_run_id": "29729479578",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "b5b4ee386b773b8653af85d469005483d9a0b4f1",
"review_id": 4730737620
},
{
"commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"review_id": 4733540120
}
]
}

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber overloads that slipped through when the Number (NaN-ignoring) variants of Min/Max were added, closing approved API issue #98862 (labeled api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and consistent with the codebase. Rather than duplicating the vectorized index search, the PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so the NaN-propagating operators keep their fast early-exit while the new Number operators skip it and instead fall through to the aggregate, returning -1 only when the whole span is NaN. The per-lane and per-vector Compare implementations mirror the established MaxNumber/MinNumberOperator semantics (NaN-ignoring, signed-zero tiebreak, overflow-aware magnitude handling).

Summary: ✅ LGTM. New public API is approved and matches the ref exactly; the refactor is minimal and reuses the existing search infrastructure; tests cover empty spans, all-NaN spans, NaN-not-returned, signed-zero/sign-magnitude tiebreaks, all tensor lengths, and the large-index (>int.MaxValue byte offset) path across the generic numeric type matrix, including the new IsUnsignedInteger/NFloat guards. One non-blocking nit noted inline about operator-precedence style consistency in dead-code branches.


Detailed Findings

✅ API surface — matches approved proposal

The four new int IndexOf*Number<T>(ReadOnlySpan<T>) where T : INumber<T> methods in ref/System.Numerics.Tensors.netcore.cs correspond exactly to the api-approved proposal in #98862, and all four new source files are registered in the csproj Compile list.

✅ Correctness — NaN and edge-case semantics

The ShouldEarlyExitOnNan => false operators correctly defer the NaN decision to the aggregate: IndexOfMinMaxFallback now returns -1 when the final result is NaN, and each vectorized SizeN tail checks T.IsNaN(aggResult) before computing the index, so an all-NaN span yields -1 while a mixed span ignores NaN lanes. The scalar and vector Compare overloads consistently implement NaN-ignoring comparison with the positive/negative-zero (and sign-magnitude) tiebreak, matching the documented maximumNumber/minimumNumber IEEE semantics and the existing non-index MaxNumber/MinMagnitudeNumber operators.

⚠️ Style — operator-precedence inconsistency (non-blocking, flagged inline)

Two of the migrated conditions (IndexOfMinMaxVectorized128Size2 and ...Size1) omit the parentheses around the typeof(T) == float || typeof(T) == double sub-expression that all other 22 call sites use. It is harmless because those methods only handle 1-/2-byte integer element types, making the float/double test dead, but the inconsistency is a copy/paste hazard. See the inline comment.

✅ Test quality — thorough

Tests are added per-operation and exercise: empty → -1, all-NaN → -1, NaN values never returned, signed-zero and negative-vs-positive magnitude tiebreaks, all Helpers.TensorLengths (covering scalar/128/256/512 paths and remainders), and the IndexOfSizeExceedingMaxValue large-index path. The new IsUnsignedInteger predicate (extended for UInt128/nuint) properly gates the sign-magnitude tests, and IsFloatingPoint is correctly extended to include NFloat. The AllLengths assertions tolerate ties by verifying the returned index's value equals the expected extreme, which is the right contract for an index-of over duplicate extremes.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 150.9 AIC · ⌖ 11.1 AIC · ⊞ 10K

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified and unchanged. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber NaN-ignoring overloads that close approved API issue #98862 (api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and unchanged. The PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so NaN-propagating operators keep the fast early-exit while the new Number operators defer the NaN decision to the aggregate, returning -1 only for an all-NaN span. The new commit ba0db92 ("Add missing parenthesis") adds the parentheses around the typeof(T) == float || typeof(T) == double sub-expression at the three remaining call sites, so all ShouldEarlyExitOnNan && (float || double) conditions are now consistently grouped.

Summary: ✅ LGTM. The sole new commit resolves the operator-precedence style nit that the initial review flagged inline. No behavioral change (the affected branches were dead code for the 1-/2-byte integer element types those methods handle), and the fix removes a copy/paste hazard. No new actionable findings.

Assessment History

  • review 4730737620 (commit b5b4ee3): verdict ✅ LGTM. Current verdict: ✅ LGTM — unchanged. Motivation, approach, and risk are unchanged; the only new patch (ba0db92) simply applies the parenthesization suggested by that review's non-blocking inline nit, so the previously-noted style inconsistency is now resolved.

Detailed Findings

✅ Resolved — operator-precedence consistency

The incremental commit adds the missing parentheses at the three previously-inconsistent sites (IndexOfMinMaxVectorized128Size2, ...Size1, and the additional migrated condition), so every ShouldEarlyExitOnNan && (typeof(T) == typeof(float) || typeof(T) == typeof(double)) guard now reads uniformly. This addresses the non-blocking style finding from the prior review with no semantic change.

No new findings in the incremental scope.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.5 AIC · ⌖ 9.86 AIC · ⊞ 10K

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

Labels

area-System.Numerics.Tensorscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Add missing Min/MaxNumber generic math APIs on TensorPrimitives

3 participants

@lilinus@jeffhandley@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add Tensor.IndexOfMaxNumber - #129582

Open
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber
Open

Add Tensor.IndexOfMaxNumber#129582
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber

Conversation

@lilinus

Copy link
Copy Markdown
Contributor

Closes#98862

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 18, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics-tensors
See info in area-owners.md if you want to be subscribed.

@lilinuslilinus changed the title Tensor.IndexOfMaxNumberAdd Tensor.IndexOfMaxNumberJun 22, 2026

@jeffhandleyjeffhandley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note

This review was generated by the holistic code review workflow being iterated on as part of #130339. Please treat its findings as assistive input for human review.

Holistic Review

Motivation: The linked API proposal covers a real parity gap in TensorPrimitives: the Number min/max variants existed, but their index-returning counterparts did not. The PR is appropriately scoped to the remaining approved APIs.

Approach: Reusing the existing IndexOfMinMaxCore shape is the right general direction, and the scalar comparisons match the intended NaN-ignoring and signed-zero semantics. However, the vectorized path appears to miss the existing Half-specific treatment used by the sibling *Number aggregation APIs.

Summary: ⚠️ Needs Changes. API approval check passed: #98862 has api-approved, applied by @bartonjs, and the ref additions match the remaining approved IndexOf*Number(ReadOnlySpan<T> x) signatures and parameter names. The blocking concern is that Half can enter the vectorized index core, while the new vector comparisons only implement maximumNumber/minimumNumber NaN handling for float/double, which can return -1 or the wrong index when non-NaN Half values follow NaNs.


Detailed Findings

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

The new Number operators handle NaN-ignoring semantics in scalar code, but their vector Compare methods special-case only double and float before falling back to plain GreaterThan/LessThan (for example TensorPrimitives.IndexOfMaxNumber.cs lines 59-81, TensorPrimitives.IndexOfMinNumber.cs lines 60-82, and the magnitude variants at lines 71-116). IndexOfMinMaxCore dispatches to vectorized implementations solely from Vector128/256/512<T>.IsSupported (TensorPrimitives.IIndexOfMinMaxOperator.cs lines 24-51), with no Half-specific escape hatch.

That differs from the existing sibling aggregation APIs, which explicitly route Half through a float-based helper for these operations (MaxNumber.cs line 27, MinNumber.cs line 27, MaxMagnitudeNumber.cs line 30, MinMagnitudeNumber.cs line 30). If a Half input reaches the vectorized index path with an initial vector/lane containing NaN and a later non-NaN in the same lane, the fallback GreaterThan/LessThan comparison against NaN is false, so the non-NaN value does not replace the NaN; the final aggregate can remain NaN and the method returns -1 even though a valid number exists. Please either add equivalent Half handling for the index operators or keep Half on the scalar/fallback path, and include a regression case where the first vector is NaN-filled and the selected non-NaN appears later.

✅ API Approval — Approved surface matches the PR additions

New public API surface is present in src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs lines 992-998. I verified linked issue #98862 has the api-approved label; the timeline shows @bartonjs applied it immediately after posting the approved C# shape on 2024-04-11. The PR adds the remaining approved methods with matching names, return type, ReadOnlySpan<T> x parameter name, and where T : INumber<T> constraints; the other APIs from the approved shape were already present from prior PRs.

✅ Tests — Important semantics are covered, but add the Half regression with the fix

The new tests cover empty input, all-NaN input, NaNs being ignored, signed-zero/magnitude tie-breaking, duplicate ties choosing the first matching index, and very large indices (TensorPrimitives.Generic.cs lines 2864-3232). Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

@lilinus

Copy link
Copy Markdown
ContributorAuthor

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

This should be disregarded. Half is not supported for vectorized path, hence the code (should) produce correct results. Half is well covered by unit tests so if it will be supported by vectors in the future, unit tests will start failing and the implementation will have to be slightly adjusted. If we want to optimize this without vector support for Half using e.g. widening, I would suggest doing it in a separate PR.

Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

This is alread covered in e.g. IndexOfMinMagnitudeNumber_NaNsNotReturned.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_reviewed_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_recorded_worker_run_id": "29729479578",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "b5b4ee386b773b8653af85d469005483d9a0b4f1",
"review_id": 4730737620
},
{
"commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"review_id": 4733540120
}
]
}

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber overloads that slipped through when the Number (NaN-ignoring) variants of Min/Max were added, closing approved API issue #98862 (labeled api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and consistent with the codebase. Rather than duplicating the vectorized index search, the PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so the NaN-propagating operators keep their fast early-exit while the new Number operators skip it and instead fall through to the aggregate, returning -1 only when the whole span is NaN. The per-lane and per-vector Compare implementations mirror the established MaxNumber/MinNumberOperator semantics (NaN-ignoring, signed-zero tiebreak, overflow-aware magnitude handling).

Summary: ✅ LGTM. New public API is approved and matches the ref exactly; the refactor is minimal and reuses the existing search infrastructure; tests cover empty spans, all-NaN spans, NaN-not-returned, signed-zero/sign-magnitude tiebreaks, all tensor lengths, and the large-index (>int.MaxValue byte offset) path across the generic numeric type matrix, including the new IsUnsignedInteger/NFloat guards. One non-blocking nit noted inline about operator-precedence style consistency in dead-code branches.


Detailed Findings

✅ API surface — matches approved proposal

The four new int IndexOf*Number<T>(ReadOnlySpan<T>) where T : INumber<T> methods in ref/System.Numerics.Tensors.netcore.cs correspond exactly to the api-approved proposal in #98862, and all four new source files are registered in the csproj Compile list.

✅ Correctness — NaN and edge-case semantics

The ShouldEarlyExitOnNan => false operators correctly defer the NaN decision to the aggregate: IndexOfMinMaxFallback now returns -1 when the final result is NaN, and each vectorized SizeN tail checks T.IsNaN(aggResult) before computing the index, so an all-NaN span yields -1 while a mixed span ignores NaN lanes. The scalar and vector Compare overloads consistently implement NaN-ignoring comparison with the positive/negative-zero (and sign-magnitude) tiebreak, matching the documented maximumNumber/minimumNumber IEEE semantics and the existing non-index MaxNumber/MinMagnitudeNumber operators.

⚠️ Style — operator-precedence inconsistency (non-blocking, flagged inline)

Two of the migrated conditions (IndexOfMinMaxVectorized128Size2 and ...Size1) omit the parentheses around the typeof(T) == float || typeof(T) == double sub-expression that all other 22 call sites use. It is harmless because those methods only handle 1-/2-byte integer element types, making the float/double test dead, but the inconsistency is a copy/paste hazard. See the inline comment.

✅ Test quality — thorough

Tests are added per-operation and exercise: empty → -1, all-NaN → -1, NaN values never returned, signed-zero and negative-vs-positive magnitude tiebreaks, all Helpers.TensorLengths (covering scalar/128/256/512 paths and remainders), and the IndexOfSizeExceedingMaxValue large-index path. The new IsUnsignedInteger predicate (extended for UInt128/nuint) properly gates the sign-magnitude tests, and IsFloatingPoint is correctly extended to include NFloat. The AllLengths assertions tolerate ties by verifying the returned index's value equals the expected extreme, which is the right contract for an index-of over duplicate extremes.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 150.9 AIC · ⌖ 11.1 AIC · ⊞ 10K

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified and unchanged. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber NaN-ignoring overloads that close approved API issue #98862 (api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and unchanged. The PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so NaN-propagating operators keep the fast early-exit while the new Number operators defer the NaN decision to the aggregate, returning -1 only for an all-NaN span. The new commit ba0db92 ("Add missing parenthesis") adds the parentheses around the typeof(T) == float || typeof(T) == double sub-expression at the three remaining call sites, so all ShouldEarlyExitOnNan && (float || double) conditions are now consistently grouped.

Summary: ✅ LGTM. The sole new commit resolves the operator-precedence style nit that the initial review flagged inline. No behavioral change (the affected branches were dead code for the 1-/2-byte integer element types those methods handle), and the fix removes a copy/paste hazard. No new actionable findings.

Assessment History

  • review 4730737620 (commit b5b4ee3): verdict ✅ LGTM. Current verdict: ✅ LGTM — unchanged. Motivation, approach, and risk are unchanged; the only new patch (ba0db92) simply applies the parenthesization suggested by that review's non-blocking inline nit, so the previously-noted style inconsistency is now resolved.

Detailed Findings

✅ Resolved — operator-precedence consistency

The incremental commit adds the missing parentheses at the three previously-inconsistent sites (IndexOfMinMaxVectorized128Size2, ...Size1, and the additional migrated condition), so every ShouldEarlyExitOnNan && (typeof(T) == typeof(float) || typeof(T) == typeof(double)) guard now reads uniformly. This addresses the non-blocking style finding from the prior review with no semantic change.

No new findings in the incremental scope.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.5 AIC · ⌖ 9.86 AIC · ⊞ 10K

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

Labels

area-System.Numerics.Tensorscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Add missing Min/MaxNumber generic math APIs on TensorPrimitives

3 participants

@lilinus@jeffhandley@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add Tensor.IndexOfMaxNumber - #129582

Open
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber
Open

Add Tensor.IndexOfMaxNumber#129582
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber

Conversation

@lilinus

Copy link
Copy Markdown
Contributor

Closes#98862

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 18, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics-tensors
See info in area-owners.md if you want to be subscribed.

@lilinuslilinus changed the title Tensor.IndexOfMaxNumberAdd Tensor.IndexOfMaxNumberJun 22, 2026

@jeffhandleyjeffhandley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note

This review was generated by the holistic code review workflow being iterated on as part of #130339. Please treat its findings as assistive input for human review.

Holistic Review

Motivation: The linked API proposal covers a real parity gap in TensorPrimitives: the Number min/max variants existed, but their index-returning counterparts did not. The PR is appropriately scoped to the remaining approved APIs.

Approach: Reusing the existing IndexOfMinMaxCore shape is the right general direction, and the scalar comparisons match the intended NaN-ignoring and signed-zero semantics. However, the vectorized path appears to miss the existing Half-specific treatment used by the sibling *Number aggregation APIs.

Summary: ⚠️ Needs Changes. API approval check passed: #98862 has api-approved, applied by @bartonjs, and the ref additions match the remaining approved IndexOf*Number(ReadOnlySpan<T> x) signatures and parameter names. The blocking concern is that Half can enter the vectorized index core, while the new vector comparisons only implement maximumNumber/minimumNumber NaN handling for float/double, which can return -1 or the wrong index when non-NaN Half values follow NaNs.


Detailed Findings

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

The new Number operators handle NaN-ignoring semantics in scalar code, but their vector Compare methods special-case only double and float before falling back to plain GreaterThan/LessThan (for example TensorPrimitives.IndexOfMaxNumber.cs lines 59-81, TensorPrimitives.IndexOfMinNumber.cs lines 60-82, and the magnitude variants at lines 71-116). IndexOfMinMaxCore dispatches to vectorized implementations solely from Vector128/256/512<T>.IsSupported (TensorPrimitives.IIndexOfMinMaxOperator.cs lines 24-51), with no Half-specific escape hatch.

That differs from the existing sibling aggregation APIs, which explicitly route Half through a float-based helper for these operations (MaxNumber.cs line 27, MinNumber.cs line 27, MaxMagnitudeNumber.cs line 30, MinMagnitudeNumber.cs line 30). If a Half input reaches the vectorized index path with an initial vector/lane containing NaN and a later non-NaN in the same lane, the fallback GreaterThan/LessThan comparison against NaN is false, so the non-NaN value does not replace the NaN; the final aggregate can remain NaN and the method returns -1 even though a valid number exists. Please either add equivalent Half handling for the index operators or keep Half on the scalar/fallback path, and include a regression case where the first vector is NaN-filled and the selected non-NaN appears later.

✅ API Approval — Approved surface matches the PR additions

New public API surface is present in src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs lines 992-998. I verified linked issue #98862 has the api-approved label; the timeline shows @bartonjs applied it immediately after posting the approved C# shape on 2024-04-11. The PR adds the remaining approved methods with matching names, return type, ReadOnlySpan<T> x parameter name, and where T : INumber<T> constraints; the other APIs from the approved shape were already present from prior PRs.

✅ Tests — Important semantics are covered, but add the Half regression with the fix

The new tests cover empty input, all-NaN input, NaNs being ignored, signed-zero/magnitude tie-breaking, duplicate ties choosing the first matching index, and very large indices (TensorPrimitives.Generic.cs lines 2864-3232). Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

@lilinus

Copy link
Copy Markdown
ContributorAuthor

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

This should be disregarded. Half is not supported for vectorized path, hence the code (should) produce correct results. Half is well covered by unit tests so if it will be supported by vectors in the future, unit tests will start failing and the implementation will have to be slightly adjusted. If we want to optimize this without vector support for Half using e.g. widening, I would suggest doing it in a separate PR.

Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

This is alread covered in e.g. IndexOfMinMagnitudeNumber_NaNsNotReturned.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_reviewed_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_recorded_worker_run_id": "29729479578",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "b5b4ee386b773b8653af85d469005483d9a0b4f1",
"review_id": 4730737620
},
{
"commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"review_id": 4733540120
}
]
}

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber overloads that slipped through when the Number (NaN-ignoring) variants of Min/Max were added, closing approved API issue #98862 (labeled api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and consistent with the codebase. Rather than duplicating the vectorized index search, the PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so the NaN-propagating operators keep their fast early-exit while the new Number operators skip it and instead fall through to the aggregate, returning -1 only when the whole span is NaN. The per-lane and per-vector Compare implementations mirror the established MaxNumber/MinNumberOperator semantics (NaN-ignoring, signed-zero tiebreak, overflow-aware magnitude handling).

Summary: ✅ LGTM. New public API is approved and matches the ref exactly; the refactor is minimal and reuses the existing search infrastructure; tests cover empty spans, all-NaN spans, NaN-not-returned, signed-zero/sign-magnitude tiebreaks, all tensor lengths, and the large-index (>int.MaxValue byte offset) path across the generic numeric type matrix, including the new IsUnsignedInteger/NFloat guards. One non-blocking nit noted inline about operator-precedence style consistency in dead-code branches.


Detailed Findings

✅ API surface — matches approved proposal

The four new int IndexOf*Number<T>(ReadOnlySpan<T>) where T : INumber<T> methods in ref/System.Numerics.Tensors.netcore.cs correspond exactly to the api-approved proposal in #98862, and all four new source files are registered in the csproj Compile list.

✅ Correctness — NaN and edge-case semantics

The ShouldEarlyExitOnNan => false operators correctly defer the NaN decision to the aggregate: IndexOfMinMaxFallback now returns -1 when the final result is NaN, and each vectorized SizeN tail checks T.IsNaN(aggResult) before computing the index, so an all-NaN span yields -1 while a mixed span ignores NaN lanes. The scalar and vector Compare overloads consistently implement NaN-ignoring comparison with the positive/negative-zero (and sign-magnitude) tiebreak, matching the documented maximumNumber/minimumNumber IEEE semantics and the existing non-index MaxNumber/MinMagnitudeNumber operators.

⚠️ Style — operator-precedence inconsistency (non-blocking, flagged inline)

Two of the migrated conditions (IndexOfMinMaxVectorized128Size2 and ...Size1) omit the parentheses around the typeof(T) == float || typeof(T) == double sub-expression that all other 22 call sites use. It is harmless because those methods only handle 1-/2-byte integer element types, making the float/double test dead, but the inconsistency is a copy/paste hazard. See the inline comment.

✅ Test quality — thorough

Tests are added per-operation and exercise: empty → -1, all-NaN → -1, NaN values never returned, signed-zero and negative-vs-positive magnitude tiebreaks, all Helpers.TensorLengths (covering scalar/128/256/512 paths and remainders), and the IndexOfSizeExceedingMaxValue large-index path. The new IsUnsignedInteger predicate (extended for UInt128/nuint) properly gates the sign-magnitude tests, and IsFloatingPoint is correctly extended to include NFloat. The AllLengths assertions tolerate ties by verifying the returned index's value equals the expected extreme, which is the right contract for an index-of over duplicate extremes.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 150.9 AIC · ⌖ 11.1 AIC · ⊞ 10K

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified and unchanged. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber NaN-ignoring overloads that close approved API issue #98862 (api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and unchanged. The PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so NaN-propagating operators keep the fast early-exit while the new Number operators defer the NaN decision to the aggregate, returning -1 only for an all-NaN span. The new commit ba0db92 ("Add missing parenthesis") adds the parentheses around the typeof(T) == float || typeof(T) == double sub-expression at the three remaining call sites, so all ShouldEarlyExitOnNan && (float || double) conditions are now consistently grouped.

Summary: ✅ LGTM. The sole new commit resolves the operator-precedence style nit that the initial review flagged inline. No behavioral change (the affected branches were dead code for the 1-/2-byte integer element types those methods handle), and the fix removes a copy/paste hazard. No new actionable findings.

Assessment History

  • review 4730737620 (commit b5b4ee3): verdict ✅ LGTM. Current verdict: ✅ LGTM — unchanged. Motivation, approach, and risk are unchanged; the only new patch (ba0db92) simply applies the parenthesization suggested by that review's non-blocking inline nit, so the previously-noted style inconsistency is now resolved.

Detailed Findings

✅ Resolved — operator-precedence consistency

The incremental commit adds the missing parentheses at the three previously-inconsistent sites (IndexOfMinMaxVectorized128Size2, ...Size1, and the additional migrated condition), so every ShouldEarlyExitOnNan && (typeof(T) == typeof(float) || typeof(T) == typeof(double)) guard now reads uniformly. This addresses the non-blocking style finding from the prior review with no semantic change.

No new findings in the incremental scope.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.5 AIC · ⌖ 9.86 AIC · ⊞ 10K

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

Labels

area-System.Numerics.Tensorscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Add missing Min/MaxNumber generic math APIs on TensorPrimitives

3 participants

@lilinus@jeffhandley@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add Tensor.IndexOfMaxNumber - #129582

Open
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber
Open

Add Tensor.IndexOfMaxNumber#129582
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber

Conversation

@lilinus

Copy link
Copy Markdown
Contributor

Closes#98862

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 18, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics-tensors
See info in area-owners.md if you want to be subscribed.

@lilinuslilinus changed the title Tensor.IndexOfMaxNumberAdd Tensor.IndexOfMaxNumberJun 22, 2026

@jeffhandleyjeffhandley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note

This review was generated by the holistic code review workflow being iterated on as part of #130339. Please treat its findings as assistive input for human review.

Holistic Review

Motivation: The linked API proposal covers a real parity gap in TensorPrimitives: the Number min/max variants existed, but their index-returning counterparts did not. The PR is appropriately scoped to the remaining approved APIs.

Approach: Reusing the existing IndexOfMinMaxCore shape is the right general direction, and the scalar comparisons match the intended NaN-ignoring and signed-zero semantics. However, the vectorized path appears to miss the existing Half-specific treatment used by the sibling *Number aggregation APIs.

Summary: ⚠️ Needs Changes. API approval check passed: #98862 has api-approved, applied by @bartonjs, and the ref additions match the remaining approved IndexOf*Number(ReadOnlySpan<T> x) signatures and parameter names. The blocking concern is that Half can enter the vectorized index core, while the new vector comparisons only implement maximumNumber/minimumNumber NaN handling for float/double, which can return -1 or the wrong index when non-NaN Half values follow NaNs.


Detailed Findings

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

The new Number operators handle NaN-ignoring semantics in scalar code, but their vector Compare methods special-case only double and float before falling back to plain GreaterThan/LessThan (for example TensorPrimitives.IndexOfMaxNumber.cs lines 59-81, TensorPrimitives.IndexOfMinNumber.cs lines 60-82, and the magnitude variants at lines 71-116). IndexOfMinMaxCore dispatches to vectorized implementations solely from Vector128/256/512<T>.IsSupported (TensorPrimitives.IIndexOfMinMaxOperator.cs lines 24-51), with no Half-specific escape hatch.

That differs from the existing sibling aggregation APIs, which explicitly route Half through a float-based helper for these operations (MaxNumber.cs line 27, MinNumber.cs line 27, MaxMagnitudeNumber.cs line 30, MinMagnitudeNumber.cs line 30). If a Half input reaches the vectorized index path with an initial vector/lane containing NaN and a later non-NaN in the same lane, the fallback GreaterThan/LessThan comparison against NaN is false, so the non-NaN value does not replace the NaN; the final aggregate can remain NaN and the method returns -1 even though a valid number exists. Please either add equivalent Half handling for the index operators or keep Half on the scalar/fallback path, and include a regression case where the first vector is NaN-filled and the selected non-NaN appears later.

✅ API Approval — Approved surface matches the PR additions

New public API surface is present in src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs lines 992-998. I verified linked issue #98862 has the api-approved label; the timeline shows @bartonjs applied it immediately after posting the approved C# shape on 2024-04-11. The PR adds the remaining approved methods with matching names, return type, ReadOnlySpan<T> x parameter name, and where T : INumber<T> constraints; the other APIs from the approved shape were already present from prior PRs.

✅ Tests — Important semantics are covered, but add the Half regression with the fix

The new tests cover empty input, all-NaN input, NaNs being ignored, signed-zero/magnitude tie-breaking, duplicate ties choosing the first matching index, and very large indices (TensorPrimitives.Generic.cs lines 2864-3232). Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

@lilinus

Copy link
Copy Markdown
ContributorAuthor

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

This should be disregarded. Half is not supported for vectorized path, hence the code (should) produce correct results. Half is well covered by unit tests so if it will be supported by vectors in the future, unit tests will start failing and the implementation will have to be slightly adjusted. If we want to optimize this without vector support for Half using e.g. widening, I would suggest doing it in a separate PR.

Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

This is alread covered in e.g. IndexOfMinMagnitudeNumber_NaNsNotReturned.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_reviewed_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_recorded_worker_run_id": "29729479578",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "b5b4ee386b773b8653af85d469005483d9a0b4f1",
"review_id": 4730737620
},
{
"commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"review_id": 4733540120
}
]
}

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber overloads that slipped through when the Number (NaN-ignoring) variants of Min/Max were added, closing approved API issue #98862 (labeled api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and consistent with the codebase. Rather than duplicating the vectorized index search, the PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so the NaN-propagating operators keep their fast early-exit while the new Number operators skip it and instead fall through to the aggregate, returning -1 only when the whole span is NaN. The per-lane and per-vector Compare implementations mirror the established MaxNumber/MinNumberOperator semantics (NaN-ignoring, signed-zero tiebreak, overflow-aware magnitude handling).

Summary: ✅ LGTM. New public API is approved and matches the ref exactly; the refactor is minimal and reuses the existing search infrastructure; tests cover empty spans, all-NaN spans, NaN-not-returned, signed-zero/sign-magnitude tiebreaks, all tensor lengths, and the large-index (>int.MaxValue byte offset) path across the generic numeric type matrix, including the new IsUnsignedInteger/NFloat guards. One non-blocking nit noted inline about operator-precedence style consistency in dead-code branches.


Detailed Findings

✅ API surface — matches approved proposal

The four new int IndexOf*Number<T>(ReadOnlySpan<T>) where T : INumber<T> methods in ref/System.Numerics.Tensors.netcore.cs correspond exactly to the api-approved proposal in #98862, and all four new source files are registered in the csproj Compile list.

✅ Correctness — NaN and edge-case semantics

The ShouldEarlyExitOnNan => false operators correctly defer the NaN decision to the aggregate: IndexOfMinMaxFallback now returns -1 when the final result is NaN, and each vectorized SizeN tail checks T.IsNaN(aggResult) before computing the index, so an all-NaN span yields -1 while a mixed span ignores NaN lanes. The scalar and vector Compare overloads consistently implement NaN-ignoring comparison with the positive/negative-zero (and sign-magnitude) tiebreak, matching the documented maximumNumber/minimumNumber IEEE semantics and the existing non-index MaxNumber/MinMagnitudeNumber operators.

⚠️ Style — operator-precedence inconsistency (non-blocking, flagged inline)

Two of the migrated conditions (IndexOfMinMaxVectorized128Size2 and ...Size1) omit the parentheses around the typeof(T) == float || typeof(T) == double sub-expression that all other 22 call sites use. It is harmless because those methods only handle 1-/2-byte integer element types, making the float/double test dead, but the inconsistency is a copy/paste hazard. See the inline comment.

✅ Test quality — thorough

Tests are added per-operation and exercise: empty → -1, all-NaN → -1, NaN values never returned, signed-zero and negative-vs-positive magnitude tiebreaks, all Helpers.TensorLengths (covering scalar/128/256/512 paths and remainders), and the IndexOfSizeExceedingMaxValue large-index path. The new IsUnsignedInteger predicate (extended for UInt128/nuint) properly gates the sign-magnitude tests, and IsFloatingPoint is correctly extended to include NFloat. The AllLengths assertions tolerate ties by verifying the returned index's value equals the expected extreme, which is the right contract for an index-of over duplicate extremes.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 150.9 AIC · ⌖ 11.1 AIC · ⊞ 10K

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified and unchanged. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber NaN-ignoring overloads that close approved API issue #98862 (api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and unchanged. The PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so NaN-propagating operators keep the fast early-exit while the new Number operators defer the NaN decision to the aggregate, returning -1 only for an all-NaN span. The new commit ba0db92 ("Add missing parenthesis") adds the parentheses around the typeof(T) == float || typeof(T) == double sub-expression at the three remaining call sites, so all ShouldEarlyExitOnNan && (float || double) conditions are now consistently grouped.

Summary: ✅ LGTM. The sole new commit resolves the operator-precedence style nit that the initial review flagged inline. No behavioral change (the affected branches were dead code for the 1-/2-byte integer element types those methods handle), and the fix removes a copy/paste hazard. No new actionable findings.

Assessment History

  • review 4730737620 (commit b5b4ee3): verdict ✅ LGTM. Current verdict: ✅ LGTM — unchanged. Motivation, approach, and risk are unchanged; the only new patch (ba0db92) simply applies the parenthesization suggested by that review's non-blocking inline nit, so the previously-noted style inconsistency is now resolved.

Detailed Findings

✅ Resolved — operator-precedence consistency

The incremental commit adds the missing parentheses at the three previously-inconsistent sites (IndexOfMinMaxVectorized128Size2, ...Size1, and the additional migrated condition), so every ShouldEarlyExitOnNan && (typeof(T) == typeof(float) || typeof(T) == typeof(double)) guard now reads uniformly. This addresses the non-blocking style finding from the prior review with no semantic change.

No new findings in the incremental scope.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.5 AIC · ⌖ 9.86 AIC · ⊞ 10K

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

Labels

area-System.Numerics.Tensorscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Add missing Min/MaxNumber generic math APIs on TensorPrimitives

3 participants

@lilinus@jeffhandley@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add Tensor.IndexOfMaxNumber - #129582

Open
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber
Open

Add Tensor.IndexOfMaxNumber#129582
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber

Conversation

@lilinus

Copy link
Copy Markdown
Contributor

Closes#98862

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 18, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics-tensors
See info in area-owners.md if you want to be subscribed.

@lilinuslilinus changed the title Tensor.IndexOfMaxNumberAdd Tensor.IndexOfMaxNumberJun 22, 2026

@jeffhandleyjeffhandley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note

This review was generated by the holistic code review workflow being iterated on as part of #130339. Please treat its findings as assistive input for human review.

Holistic Review

Motivation: The linked API proposal covers a real parity gap in TensorPrimitives: the Number min/max variants existed, but their index-returning counterparts did not. The PR is appropriately scoped to the remaining approved APIs.

Approach: Reusing the existing IndexOfMinMaxCore shape is the right general direction, and the scalar comparisons match the intended NaN-ignoring and signed-zero semantics. However, the vectorized path appears to miss the existing Half-specific treatment used by the sibling *Number aggregation APIs.

Summary: ⚠️ Needs Changes. API approval check passed: #98862 has api-approved, applied by @bartonjs, and the ref additions match the remaining approved IndexOf*Number(ReadOnlySpan<T> x) signatures and parameter names. The blocking concern is that Half can enter the vectorized index core, while the new vector comparisons only implement maximumNumber/minimumNumber NaN handling for float/double, which can return -1 or the wrong index when non-NaN Half values follow NaNs.


Detailed Findings

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

The new Number operators handle NaN-ignoring semantics in scalar code, but their vector Compare methods special-case only double and float before falling back to plain GreaterThan/LessThan (for example TensorPrimitives.IndexOfMaxNumber.cs lines 59-81, TensorPrimitives.IndexOfMinNumber.cs lines 60-82, and the magnitude variants at lines 71-116). IndexOfMinMaxCore dispatches to vectorized implementations solely from Vector128/256/512<T>.IsSupported (TensorPrimitives.IIndexOfMinMaxOperator.cs lines 24-51), with no Half-specific escape hatch.

That differs from the existing sibling aggregation APIs, which explicitly route Half through a float-based helper for these operations (MaxNumber.cs line 27, MinNumber.cs line 27, MaxMagnitudeNumber.cs line 30, MinMagnitudeNumber.cs line 30). If a Half input reaches the vectorized index path with an initial vector/lane containing NaN and a later non-NaN in the same lane, the fallback GreaterThan/LessThan comparison against NaN is false, so the non-NaN value does not replace the NaN; the final aggregate can remain NaN and the method returns -1 even though a valid number exists. Please either add equivalent Half handling for the index operators or keep Half on the scalar/fallback path, and include a regression case where the first vector is NaN-filled and the selected non-NaN appears later.

✅ API Approval — Approved surface matches the PR additions

New public API surface is present in src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs lines 992-998. I verified linked issue #98862 has the api-approved label; the timeline shows @bartonjs applied it immediately after posting the approved C# shape on 2024-04-11. The PR adds the remaining approved methods with matching names, return type, ReadOnlySpan<T> x parameter name, and where T : INumber<T> constraints; the other APIs from the approved shape were already present from prior PRs.

✅ Tests — Important semantics are covered, but add the Half regression with the fix

The new tests cover empty input, all-NaN input, NaNs being ignored, signed-zero/magnitude tie-breaking, duplicate ties choosing the first matching index, and very large indices (TensorPrimitives.Generic.cs lines 2864-3232). Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

@lilinus

Copy link
Copy Markdown
ContributorAuthor

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

This should be disregarded. Half is not supported for vectorized path, hence the code (should) produce correct results. Half is well covered by unit tests so if it will be supported by vectors in the future, unit tests will start failing and the implementation will have to be slightly adjusted. If we want to optimize this without vector support for Half using e.g. widening, I would suggest doing it in a separate PR.

Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

This is alread covered in e.g. IndexOfMinMagnitudeNumber_NaNsNotReturned.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_reviewed_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_recorded_worker_run_id": "29729479578",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "b5b4ee386b773b8653af85d469005483d9a0b4f1",
"review_id": 4730737620
},
{
"commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"review_id": 4733540120
}
]
}

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber overloads that slipped through when the Number (NaN-ignoring) variants of Min/Max were added, closing approved API issue #98862 (labeled api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and consistent with the codebase. Rather than duplicating the vectorized index search, the PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so the NaN-propagating operators keep their fast early-exit while the new Number operators skip it and instead fall through to the aggregate, returning -1 only when the whole span is NaN. The per-lane and per-vector Compare implementations mirror the established MaxNumber/MinNumberOperator semantics (NaN-ignoring, signed-zero tiebreak, overflow-aware magnitude handling).

Summary: ✅ LGTM. New public API is approved and matches the ref exactly; the refactor is minimal and reuses the existing search infrastructure; tests cover empty spans, all-NaN spans, NaN-not-returned, signed-zero/sign-magnitude tiebreaks, all tensor lengths, and the large-index (>int.MaxValue byte offset) path across the generic numeric type matrix, including the new IsUnsignedInteger/NFloat guards. One non-blocking nit noted inline about operator-precedence style consistency in dead-code branches.


Detailed Findings

✅ API surface — matches approved proposal

The four new int IndexOf*Number<T>(ReadOnlySpan<T>) where T : INumber<T> methods in ref/System.Numerics.Tensors.netcore.cs correspond exactly to the api-approved proposal in #98862, and all four new source files are registered in the csproj Compile list.

✅ Correctness — NaN and edge-case semantics

The ShouldEarlyExitOnNan => false operators correctly defer the NaN decision to the aggregate: IndexOfMinMaxFallback now returns -1 when the final result is NaN, and each vectorized SizeN tail checks T.IsNaN(aggResult) before computing the index, so an all-NaN span yields -1 while a mixed span ignores NaN lanes. The scalar and vector Compare overloads consistently implement NaN-ignoring comparison with the positive/negative-zero (and sign-magnitude) tiebreak, matching the documented maximumNumber/minimumNumber IEEE semantics and the existing non-index MaxNumber/MinMagnitudeNumber operators.

⚠️ Style — operator-precedence inconsistency (non-blocking, flagged inline)

Two of the migrated conditions (IndexOfMinMaxVectorized128Size2 and ...Size1) omit the parentheses around the typeof(T) == float || typeof(T) == double sub-expression that all other 22 call sites use. It is harmless because those methods only handle 1-/2-byte integer element types, making the float/double test dead, but the inconsistency is a copy/paste hazard. See the inline comment.

✅ Test quality — thorough

Tests are added per-operation and exercise: empty → -1, all-NaN → -1, NaN values never returned, signed-zero and negative-vs-positive magnitude tiebreaks, all Helpers.TensorLengths (covering scalar/128/256/512 paths and remainders), and the IndexOfSizeExceedingMaxValue large-index path. The new IsUnsignedInteger predicate (extended for UInt128/nuint) properly gates the sign-magnitude tests, and IsFloatingPoint is correctly extended to include NFloat. The AllLengths assertions tolerate ties by verifying the returned index's value equals the expected extreme, which is the right contract for an index-of over duplicate extremes.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 150.9 AIC · ⌖ 11.1 AIC · ⊞ 10K

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified and unchanged. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber NaN-ignoring overloads that close approved API issue #98862 (api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and unchanged. The PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so NaN-propagating operators keep the fast early-exit while the new Number operators defer the NaN decision to the aggregate, returning -1 only for an all-NaN span. The new commit ba0db92 ("Add missing parenthesis") adds the parentheses around the typeof(T) == float || typeof(T) == double sub-expression at the three remaining call sites, so all ShouldEarlyExitOnNan && (float || double) conditions are now consistently grouped.

Summary: ✅ LGTM. The sole new commit resolves the operator-precedence style nit that the initial review flagged inline. No behavioral change (the affected branches were dead code for the 1-/2-byte integer element types those methods handle), and the fix removes a copy/paste hazard. No new actionable findings.

Assessment History

  • review 4730737620 (commit b5b4ee3): verdict ✅ LGTM. Current verdict: ✅ LGTM — unchanged. Motivation, approach, and risk are unchanged; the only new patch (ba0db92) simply applies the parenthesization suggested by that review's non-blocking inline nit, so the previously-noted style inconsistency is now resolved.

Detailed Findings

✅ Resolved — operator-precedence consistency

The incremental commit adds the missing parentheses at the three previously-inconsistent sites (IndexOfMinMaxVectorized128Size2, ...Size1, and the additional migrated condition), so every ShouldEarlyExitOnNan && (typeof(T) == typeof(float) || typeof(T) == typeof(double)) guard now reads uniformly. This addresses the non-blocking style finding from the prior review with no semantic change.

No new findings in the incremental scope.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.5 AIC · ⌖ 9.86 AIC · ⊞ 10K

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

Labels

area-System.Numerics.Tensorscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Add missing Min/MaxNumber generic math APIs on TensorPrimitives

3 participants

@lilinus@jeffhandley@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add Tensor.IndexOfMaxNumber - #129582

Open
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber
Open

Add Tensor.IndexOfMaxNumber#129582
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber

Conversation

@lilinus

Copy link
Copy Markdown
Contributor

Closes#98862

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 18, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics-tensors
See info in area-owners.md if you want to be subscribed.

@lilinuslilinus changed the title Tensor.IndexOfMaxNumberAdd Tensor.IndexOfMaxNumberJun 22, 2026

@jeffhandleyjeffhandley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note

This review was generated by the holistic code review workflow being iterated on as part of #130339. Please treat its findings as assistive input for human review.

Holistic Review

Motivation: The linked API proposal covers a real parity gap in TensorPrimitives: the Number min/max variants existed, but their index-returning counterparts did not. The PR is appropriately scoped to the remaining approved APIs.

Approach: Reusing the existing IndexOfMinMaxCore shape is the right general direction, and the scalar comparisons match the intended NaN-ignoring and signed-zero semantics. However, the vectorized path appears to miss the existing Half-specific treatment used by the sibling *Number aggregation APIs.

Summary: ⚠️ Needs Changes. API approval check passed: #98862 has api-approved, applied by @bartonjs, and the ref additions match the remaining approved IndexOf*Number(ReadOnlySpan<T> x) signatures and parameter names. The blocking concern is that Half can enter the vectorized index core, while the new vector comparisons only implement maximumNumber/minimumNumber NaN handling for float/double, which can return -1 or the wrong index when non-NaN Half values follow NaNs.


Detailed Findings

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

The new Number operators handle NaN-ignoring semantics in scalar code, but their vector Compare methods special-case only double and float before falling back to plain GreaterThan/LessThan (for example TensorPrimitives.IndexOfMaxNumber.cs lines 59-81, TensorPrimitives.IndexOfMinNumber.cs lines 60-82, and the magnitude variants at lines 71-116). IndexOfMinMaxCore dispatches to vectorized implementations solely from Vector128/256/512<T>.IsSupported (TensorPrimitives.IIndexOfMinMaxOperator.cs lines 24-51), with no Half-specific escape hatch.

That differs from the existing sibling aggregation APIs, which explicitly route Half through a float-based helper for these operations (MaxNumber.cs line 27, MinNumber.cs line 27, MaxMagnitudeNumber.cs line 30, MinMagnitudeNumber.cs line 30). If a Half input reaches the vectorized index path with an initial vector/lane containing NaN and a later non-NaN in the same lane, the fallback GreaterThan/LessThan comparison against NaN is false, so the non-NaN value does not replace the NaN; the final aggregate can remain NaN and the method returns -1 even though a valid number exists. Please either add equivalent Half handling for the index operators or keep Half on the scalar/fallback path, and include a regression case where the first vector is NaN-filled and the selected non-NaN appears later.

✅ API Approval — Approved surface matches the PR additions

New public API surface is present in src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs lines 992-998. I verified linked issue #98862 has the api-approved label; the timeline shows @bartonjs applied it immediately after posting the approved C# shape on 2024-04-11. The PR adds the remaining approved methods with matching names, return type, ReadOnlySpan<T> x parameter name, and where T : INumber<T> constraints; the other APIs from the approved shape were already present from prior PRs.

✅ Tests — Important semantics are covered, but add the Half regression with the fix

The new tests cover empty input, all-NaN input, NaNs being ignored, signed-zero/magnitude tie-breaking, duplicate ties choosing the first matching index, and very large indices (TensorPrimitives.Generic.cs lines 2864-3232). Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

@lilinus

Copy link
Copy Markdown
ContributorAuthor

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

This should be disregarded. Half is not supported for vectorized path, hence the code (should) produce correct results. Half is well covered by unit tests so if it will be supported by vectors in the future, unit tests will start failing and the implementation will have to be slightly adjusted. If we want to optimize this without vector support for Half using e.g. widening, I would suggest doing it in a separate PR.

Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

This is alread covered in e.g. IndexOfMinMagnitudeNumber_NaNsNotReturned.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_reviewed_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_recorded_worker_run_id": "29729479578",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "b5b4ee386b773b8653af85d469005483d9a0b4f1",
"review_id": 4730737620
},
{
"commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"review_id": 4733540120
}
]
}

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber overloads that slipped through when the Number (NaN-ignoring) variants of Min/Max were added, closing approved API issue #98862 (labeled api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and consistent with the codebase. Rather than duplicating the vectorized index search, the PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so the NaN-propagating operators keep their fast early-exit while the new Number operators skip it and instead fall through to the aggregate, returning -1 only when the whole span is NaN. The per-lane and per-vector Compare implementations mirror the established MaxNumber/MinNumberOperator semantics (NaN-ignoring, signed-zero tiebreak, overflow-aware magnitude handling).

Summary: ✅ LGTM. New public API is approved and matches the ref exactly; the refactor is minimal and reuses the existing search infrastructure; tests cover empty spans, all-NaN spans, NaN-not-returned, signed-zero/sign-magnitude tiebreaks, all tensor lengths, and the large-index (>int.MaxValue byte offset) path across the generic numeric type matrix, including the new IsUnsignedInteger/NFloat guards. One non-blocking nit noted inline about operator-precedence style consistency in dead-code branches.


Detailed Findings

✅ API surface — matches approved proposal

The four new int IndexOf*Number<T>(ReadOnlySpan<T>) where T : INumber<T> methods in ref/System.Numerics.Tensors.netcore.cs correspond exactly to the api-approved proposal in #98862, and all four new source files are registered in the csproj Compile list.

✅ Correctness — NaN and edge-case semantics

The ShouldEarlyExitOnNan => false operators correctly defer the NaN decision to the aggregate: IndexOfMinMaxFallback now returns -1 when the final result is NaN, and each vectorized SizeN tail checks T.IsNaN(aggResult) before computing the index, so an all-NaN span yields -1 while a mixed span ignores NaN lanes. The scalar and vector Compare overloads consistently implement NaN-ignoring comparison with the positive/negative-zero (and sign-magnitude) tiebreak, matching the documented maximumNumber/minimumNumber IEEE semantics and the existing non-index MaxNumber/MinMagnitudeNumber operators.

⚠️ Style — operator-precedence inconsistency (non-blocking, flagged inline)

Two of the migrated conditions (IndexOfMinMaxVectorized128Size2 and ...Size1) omit the parentheses around the typeof(T) == float || typeof(T) == double sub-expression that all other 22 call sites use. It is harmless because those methods only handle 1-/2-byte integer element types, making the float/double test dead, but the inconsistency is a copy/paste hazard. See the inline comment.

✅ Test quality — thorough

Tests are added per-operation and exercise: empty → -1, all-NaN → -1, NaN values never returned, signed-zero and negative-vs-positive magnitude tiebreaks, all Helpers.TensorLengths (covering scalar/128/256/512 paths and remainders), and the IndexOfSizeExceedingMaxValue large-index path. The new IsUnsignedInteger predicate (extended for UInt128/nuint) properly gates the sign-magnitude tests, and IsFloatingPoint is correctly extended to include NFloat. The AllLengths assertions tolerate ties by verifying the returned index's value equals the expected extreme, which is the right contract for an index-of over duplicate extremes.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 150.9 AIC · ⌖ 11.1 AIC · ⊞ 10K

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified and unchanged. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber NaN-ignoring overloads that close approved API issue #98862 (api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and unchanged. The PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so NaN-propagating operators keep the fast early-exit while the new Number operators defer the NaN decision to the aggregate, returning -1 only for an all-NaN span. The new commit ba0db92 ("Add missing parenthesis") adds the parentheses around the typeof(T) == float || typeof(T) == double sub-expression at the three remaining call sites, so all ShouldEarlyExitOnNan && (float || double) conditions are now consistently grouped.

Summary: ✅ LGTM. The sole new commit resolves the operator-precedence style nit that the initial review flagged inline. No behavioral change (the affected branches were dead code for the 1-/2-byte integer element types those methods handle), and the fix removes a copy/paste hazard. No new actionable findings.

Assessment History

  • review 4730737620 (commit b5b4ee3): verdict ✅ LGTM. Current verdict: ✅ LGTM — unchanged. Motivation, approach, and risk are unchanged; the only new patch (ba0db92) simply applies the parenthesization suggested by that review's non-blocking inline nit, so the previously-noted style inconsistency is now resolved.

Detailed Findings

✅ Resolved — operator-precedence consistency

The incremental commit adds the missing parentheses at the three previously-inconsistent sites (IndexOfMinMaxVectorized128Size2, ...Size1, and the additional migrated condition), so every ShouldEarlyExitOnNan && (typeof(T) == typeof(float) || typeof(T) == typeof(double)) guard now reads uniformly. This addresses the non-blocking style finding from the prior review with no semantic change.

No new findings in the incremental scope.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.5 AIC · ⌖ 9.86 AIC · ⊞ 10K

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

Labels

area-System.Numerics.Tensorscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Add missing Min/MaxNumber generic math APIs on TensorPrimitives

3 participants

@lilinus@jeffhandley@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add Tensor.IndexOfMaxNumber - #129582

Open
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber
Open

Add Tensor.IndexOfMaxNumber#129582
lilinus wants to merge 8 commits into
dotnet:mainfrom
lilinus:tensor-indexofminmaxnumber

Conversation

@lilinus

Copy link
Copy Markdown
Contributor

Closes#98862

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 18, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics-tensors
See info in area-owners.md if you want to be subscribed.

@lilinuslilinus changed the title Tensor.IndexOfMaxNumberAdd Tensor.IndexOfMaxNumberJun 22, 2026

@jeffhandleyjeffhandley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note

This review was generated by the holistic code review workflow being iterated on as part of #130339. Please treat its findings as assistive input for human review.

Holistic Review

Motivation: The linked API proposal covers a real parity gap in TensorPrimitives: the Number min/max variants existed, but their index-returning counterparts did not. The PR is appropriately scoped to the remaining approved APIs.

Approach: Reusing the existing IndexOfMinMaxCore shape is the right general direction, and the scalar comparisons match the intended NaN-ignoring and signed-zero semantics. However, the vectorized path appears to miss the existing Half-specific treatment used by the sibling *Number aggregation APIs.

Summary: ⚠️ Needs Changes. API approval check passed: #98862 has api-approved, applied by @bartonjs, and the ref additions match the remaining approved IndexOf*Number(ReadOnlySpan<T> x) signatures and parameter names. The blocking concern is that Half can enter the vectorized index core, while the new vector comparisons only implement maximumNumber/minimumNumber NaN handling for float/double, which can return -1 or the wrong index when non-NaN Half values follow NaNs.


Detailed Findings

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

The new Number operators handle NaN-ignoring semantics in scalar code, but their vector Compare methods special-case only double and float before falling back to plain GreaterThan/LessThan (for example TensorPrimitives.IndexOfMaxNumber.cs lines 59-81, TensorPrimitives.IndexOfMinNumber.cs lines 60-82, and the magnitude variants at lines 71-116). IndexOfMinMaxCore dispatches to vectorized implementations solely from Vector128/256/512<T>.IsSupported (TensorPrimitives.IIndexOfMinMaxOperator.cs lines 24-51), with no Half-specific escape hatch.

That differs from the existing sibling aggregation APIs, which explicitly route Half through a float-based helper for these operations (MaxNumber.cs line 27, MinNumber.cs line 27, MaxMagnitudeNumber.cs line 30, MinMagnitudeNumber.cs line 30). If a Half input reaches the vectorized index path with an initial vector/lane containing NaN and a later non-NaN in the same lane, the fallback GreaterThan/LessThan comparison against NaN is false, so the non-NaN value does not replace the NaN; the final aggregate can remain NaN and the method returns -1 even though a valid number exists. Please either add equivalent Half handling for the index operators or keep Half on the scalar/fallback path, and include a regression case where the first vector is NaN-filled and the selected non-NaN appears later.

✅ API Approval — Approved surface matches the PR additions

New public API surface is present in src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs lines 992-998. I verified linked issue #98862 has the api-approved label; the timeline shows @bartonjs applied it immediately after posting the approved C# shape on 2024-04-11. The PR adds the remaining approved methods with matching names, return type, ReadOnlySpan<T> x parameter name, and where T : INumber<T> constraints; the other APIs from the approved shape were already present from prior PRs.

✅ Tests — Important semantics are covered, but add the Half regression with the fix

The new tests cover empty input, all-NaN input, NaNs being ignored, signed-zero/magnitude tie-breaking, duplicate ties choosing the first matching index, and very large indices (TensorPrimitives.Generic.cs lines 2864-3232). Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

@lilinus

Copy link
Copy Markdown
ContributorAuthor

❌ Correctness — Half vectorized paths do not implement Number NaN-ignoring semantics

This should be disregarded. Half is not supported for vectorized path, hence the code (should) produce correct results. Half is well covered by unit tests so if it will be supported by vectors in the future, unit tests will start failing and the implementation will have to be slightly adjusted. If we want to optimize this without vector support for Half using e.g. widening, I would suggest doing it in a separate PR.

Once the Half vectorized behavior is fixed, please add/ensure a case specifically exercises NaNs in the first vector with the first non-NaN after that vector so this does not regress.

This is alread covered in e.g. IndexOfMinMagnitudeNumber_NaNsNotReturned.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_reviewed_commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "71d81c1705aa1f00e0b57073c97efdd5496f6b9f",
"last_recorded_worker_run_id": "29729479578",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "b5b4ee386b773b8653af85d469005483d9a0b4f1",
"review_id": 4730737620
},
{
"commit": "ba0db923c0b0164ae013f4e3787e1740602e9f2c",
"review_id": 4733540120
}
]
}

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber overloads that slipped through when the Number (NaN-ignoring) variants of Min/Max were added, closing approved API issue #98862 (labeled api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and consistent with the codebase. Rather than duplicating the vectorized index search, the PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so the NaN-propagating operators keep their fast early-exit while the new Number operators skip it and instead fall through to the aggregate, returning -1 only when the whole span is NaN. The per-lane and per-vector Compare implementations mirror the established MaxNumber/MinNumberOperator semantics (NaN-ignoring, signed-zero tiebreak, overflow-aware magnitude handling).

Summary: ✅ LGTM. New public API is approved and matches the ref exactly; the refactor is minimal and reuses the existing search infrastructure; tests cover empty spans, all-NaN spans, NaN-not-returned, signed-zero/sign-magnitude tiebreaks, all tensor lengths, and the large-index (>int.MaxValue byte offset) path across the generic numeric type matrix, including the new IsUnsignedInteger/NFloat guards. One non-blocking nit noted inline about operator-precedence style consistency in dead-code branches.


Detailed Findings

✅ API surface — matches approved proposal

The four new int IndexOf*Number<T>(ReadOnlySpan<T>) where T : INumber<T> methods in ref/System.Numerics.Tensors.netcore.cs correspond exactly to the api-approved proposal in #98862, and all four new source files are registered in the csproj Compile list.

✅ Correctness — NaN and edge-case semantics

The ShouldEarlyExitOnNan => false operators correctly defer the NaN decision to the aggregate: IndexOfMinMaxFallback now returns -1 when the final result is NaN, and each vectorized SizeN tail checks T.IsNaN(aggResult) before computing the index, so an all-NaN span yields -1 while a mixed span ignores NaN lanes. The scalar and vector Compare overloads consistently implement NaN-ignoring comparison with the positive/negative-zero (and sign-magnitude) tiebreak, matching the documented maximumNumber/minimumNumber IEEE semantics and the existing non-index MaxNumber/MinMagnitudeNumber operators.

⚠️ Style — operator-precedence inconsistency (non-blocking, flagged inline)

Two of the migrated conditions (IndexOfMinMaxVectorized128Size2 and ...Size1) omit the parentheses around the typeof(T) == float || typeof(T) == double sub-expression that all other 22 call sites use. It is harmless because those methods only handle 1-/2-byte integer element types, making the float/double test dead, but the inconsistency is a copy/paste hazard. See the inline comment.

✅ Test quality — thorough

Tests are added per-operation and exercise: empty → -1, all-NaN → -1, NaN values never returned, signed-zero and negative-vs-positive magnitude tiebreaks, all Helpers.TensorLengths (covering scalar/128/256/512 paths and remainders), and the IndexOfSizeExceedingMaxValue large-index path. The new IsUnsignedInteger predicate (extended for UInt128/nuint) properly gates the sign-magnitude tests, and IsFloatingPoint is correctly extended to include NFloat. The AllLengths assertions tolerate ties by verifying the returned index's value equals the expected extreme, which is the right contract for an index-of over duplicate extremes.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 150.9 AIC · ⌖ 11.1 AIC · ⊞ 10K

@github-actionsgithub-actionsBot 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.

Holistic Review

Motivation: Justified and unchanged. This adds the IndexOfMaxNumber/IndexOfMinNumber/IndexOfMaxMagnitudeNumber/IndexOfMinMagnitudeNumber NaN-ignoring overloads that close approved API issue #98862 (api-approved, milestone 11.0.0). The ref surface matches the approved proposal exactly.

Approach: Sound and unchanged. The PR parameterizes the existing IIndexOfMinMaxOperator<T> with a new static abstract bool ShouldEarlyExitOnNan so NaN-propagating operators keep the fast early-exit while the new Number operators defer the NaN decision to the aggregate, returning -1 only for an all-NaN span. The new commit ba0db92 ("Add missing parenthesis") adds the parentheses around the typeof(T) == float || typeof(T) == double sub-expression at the three remaining call sites, so all ShouldEarlyExitOnNan && (float || double) conditions are now consistently grouped.

Summary: ✅ LGTM. The sole new commit resolves the operator-precedence style nit that the initial review flagged inline. No behavioral change (the affected branches were dead code for the 1-/2-byte integer element types those methods handle), and the fix removes a copy/paste hazard. No new actionable findings.

Assessment History

  • review 4730737620 (commit b5b4ee3): verdict ✅ LGTM. Current verdict: ✅ LGTM — unchanged. Motivation, approach, and risk are unchanged; the only new patch (ba0db92) simply applies the parenthesization suggested by that review's non-blocking inline nit, so the previously-noted style inconsistency is now resolved.

Detailed Findings

✅ Resolved — operator-precedence consistency

The incremental commit adds the missing parentheses at the three previously-inconsistent sites (IndexOfMinMaxVectorized128Size2, ...Size1, and the additional migrated condition), so every ShouldEarlyExitOnNan && (typeof(T) == typeof(float) || typeof(T) == typeof(double)) guard now reads uniformly. This addresses the non-blocking style finding from the prior review with no semantic change.

No new findings in the incremental scope.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.5 AIC · ⌖ 9.86 AIC · ⊞ 10K

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

Labels

area-System.Numerics.Tensorscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Add missing Min/MaxNumber generic math APIs on TensorPrimitives

3 participants

@lilinus@jeffhandley@tannergooding