Repository navigation
Expand file tree
/
Copy pathnp.diag.cs
More file actions
134 lines (125 loc) · 6.62 KB
/
Copy pathnp.diag.cs
File metadata and controls
134 lines (125 loc) · 6.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using System;
using NumSharp.Backends;
namespace NumSharp
{
public static partial class np
{
/// <summary>
/// Extract a diagonal or construct a diagonal array.
/// </summary>
/// <param name="v">
/// If <paramref name="v"/> is 2-D, return a copy of its <paramref name="k"/>-th
/// diagonal. If <paramref name="v"/> is 1-D, return a 2-D array with
/// <paramref name="v"/> on the <paramref name="k"/>-th diagonal.
/// </param>
/// <param name="k">
/// Diagonal in question. Use <c>k > 0</c> for diagonals above the main diagonal,
/// and <c>k < 0</c> for diagonals below the main diagonal.
/// </param>
/// <returns>The extracted diagonal or constructed diagonal array.</returns>
/// <exception cref="ArgumentException">
/// <paramref name="v"/> is not 1- or 2-dimensional —
/// <c>Input must be 1- or 2-d.</c> (NumPy's <c>ValueError</c>).
/// </exception>
/// <remarks>
/// https://numpy.org/doc/stable/reference/generated/numpy.diag.html
/// <para>
/// <b>The two branches differ in view-ness</b>, exactly as in NumPy:
/// the 1-D branch <em>constructs</em> a fresh, writeable, C-contiguous
/// <c>(n, n)</c> array (<c>n = v.size + |k|</c>); the 2-D branch delegates to
/// <see cref="diagonal"/> and therefore returns a <b>read-only view</b> that shares
/// storage with <paramref name="v"/> — despite the NumPy docstring's talk of "a
/// copy" (probed against 2.4.2: <c>np.shares_memory</c> is True and
/// <c>flags.writeable</c> is False).
/// </para>
/// </remarks>
[NDScoped]
public static NDArray diag(NDArray v, int k = 0)
{
if (v is null) throw new ArgumentNullException(nameof(v));
switch (v.ndim)
{
case 1:
return DiagonalEmbed(v, k);
case 2:
return diagonal(v, k);
default:
// NumPy: raise ValueError("Input must be 1- or 2-d.")
throw new ArgumentException("Input must be 1- or 2-d.");
}
}
/// <summary>
/// Create a two-dimensional array with the flattened input as a diagonal.
/// </summary>
/// <param name="v">Input data, which is flattened (in C order) and set as the <paramref name="k"/>-th diagonal of the output.</param>
/// <param name="k">
/// Diagonal to set; 0, the default, corresponds to the "main" diagonal, a positive
/// (negative) <paramref name="k"/> giving the number of the diagonal above (below)
/// the main.
/// </param>
/// <returns>
/// The 2-D output array of shape <c>(n, n)</c> where <c>n = v.size + |k|</c>.
/// Always a freshly allocated, C-contiguous, writeable array.
/// </returns>
/// <remarks>
/// https://numpy.org/doc/stable/reference/generated/numpy.diagflat.html
/// <para>
/// Unlike <see cref="diag"/>, <c>diagflat</c> accepts <b>any</b> dimensionality —
/// the input is raveled first, so a 0-d scalar produces a <c>(1, 1)</c> array and a
/// 3-D input is flattened in C order. Ravel order is <b>logical</b> C order, so an
/// F-contiguous or strided input is read in row-major index order, not memory order
/// (probed against NumPy 2.4.2).
/// </para>
/// </remarks>
[NDScoped]
public static NDArray diagflat(NDArray v, int k = 0)
{
if (v is null) throw new ArgumentNullException(nameof(v));
return DiagonalEmbed(v.ravel(), k);
}
/// <summary>
/// Shared back-end of <see cref="diag"/>'s 1-D branch and <see cref="diagflat"/>:
/// allocate an <c>(n, n)</c> zero matrix and drop <paramref name="v1d"/> onto its
/// <paramref name="k"/>-th diagonal.
/// </summary>
/// <remarks>
/// NumPy writes the values through a strided flat slice
/// (<c>res.flat[i::n+1] = v</c>). NumSharp does the structurally identical thing but
/// without an element loop: it <see cref="Backends.Unmanaged.UnmanagedStorage.Alias(Shape)"/>es
/// a <b>writeable</b> length-<c>v.size</c>, stride-<c>(n+1)</c> view onto the fresh
/// zero buffer and lets the ordinary copy machinery fill it. The view is internal and
/// never escapes — it is a write target, not a result — which is why it does not carry
/// <see cref="diagonal"/>'s read-only contract.
/// </remarks>
[NDScopedCovered] // only callers: [NDScoped] diag / diagflat (the AOT-fallback alias view is scope-reclaimed)
private static NDArray DiagonalEmbed(NDArray v1d, int k)
{
long s = v1d.size;
long absK = System.Math.Abs((long)k);
long n = s + absK;
if (n > int.MaxValue)
throw new ArgumentException(
$"array is too big; `arr.size * arr.itemsize` is larger than the maximum possible size (n={n}).");
var res = np.zeros(Shape.Matrix((int)n, (int)n), v1d.typecode);
if (s == 0)
return res;
// Flat index of the first element of the k-th diagonal, then a constant
// stride of n+1 walks it — the same arithmetic NumPy's flat-slice uses.
long start = k >= 0 ? k : absK * n;
// Scatter v1d straight down the diagonal via the IL strided-store kernel — no
// aliased view, and none of NDIter.Copy's per-strided-write overhead (measured on a
// 3162² construct: 3.5 ms via SetData vs 2.4 ms via the kernel; the win grows with the
// diagonal length). v1d is 1-D, so its single stride (0-d guarded to 0) addresses any
// layout, including a strided/negative-stride input.
long srcStride = v1d.ndim >= 1 ? v1d.Shape.strides[0] : 0;
if (!TryDiagonalScatter(res, start, n + 1, v1d, v1d.Shape.offset, srcStride, s))
{
// Fallback: alias the diagonal as a 1-D view and route through the copy machinery.
var diagShape = new Shape(new[] {s}, new[] {n + 1}, start, res.Shape.BufferSize);
var target = new NDArray(res.Storage.Alias(diagShape)) {TensorEngine = res.TensorEngine};
target.SetData(v1d);
}
return res;
}
}
}