From 0a9c8f066ea06c9415bf4548b8cd2c5f17ed7a34 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Sun, 17 Dec 2023 13:16:03 -0600 Subject: [PATCH 1/6] (Logics):add high performance logical AND function with axis and keepdim support also test Upgrade to .NET 8. --- src/NumSharp.Core/Logic/np.all.cs | 141 +++++++++++++++--- .../Logic/np_all_axis_Test.cs | 82 ++++++++++ 2 files changed, 203 insertions(+), 20 deletions(-) create mode 100644 test/NumSharp.UnitTest/Logic/np_all_axis_Test.cs diff --git a/src/NumSharp.Core/Logic/np.all.cs b/src/NumSharp.Core/Logic/np.all.cs index 59111e9c..d3eea431 100644 --- a/src/NumSharp.Core/Logic/np.all.cs +++ b/src/NumSharp.Core/Logic/np.all.cs @@ -26,23 +26,23 @@ public static bool all(NDArray a) #else #region Compute - switch (a.typecode) - { - case NPTypeCode.Boolean: return _all_linear(a.MakeGeneric()); - case NPTypeCode.Byte: return _all_linear(a.MakeGeneric()); - case NPTypeCode.Int16: return _all_linear(a.MakeGeneric()); - case NPTypeCode.UInt16: return _all_linear(a.MakeGeneric()); - case NPTypeCode.Int32: return _all_linear(a.MakeGeneric()); - case NPTypeCode.UInt32: return _all_linear(a.MakeGeneric()); - case NPTypeCode.Int64: return _all_linear(a.MakeGeneric()); - case NPTypeCode.UInt64: return _all_linear(a.MakeGeneric()); - case NPTypeCode.Char: return _all_linear(a.MakeGeneric()); - case NPTypeCode.Double: return _all_linear(a.MakeGeneric()); - case NPTypeCode.Single: return _all_linear(a.MakeGeneric()); - case NPTypeCode.Decimal: return _all_linear(a.MakeGeneric()); - default: - throw new NotSupportedException(); - } + switch (a.typecode) + { + case NPTypeCode.Boolean: return _all_linear(a.MakeGeneric()); + case NPTypeCode.Byte: return _all_linear(a.MakeGeneric()); + case NPTypeCode.Int16: return _all_linear(a.MakeGeneric()); + case NPTypeCode.UInt16: return _all_linear(a.MakeGeneric()); + case NPTypeCode.Int32: return _all_linear(a.MakeGeneric()); + case NPTypeCode.UInt32: return _all_linear(a.MakeGeneric()); + case NPTypeCode.Int64: return _all_linear(a.MakeGeneric()); + case NPTypeCode.UInt64: return _all_linear(a.MakeGeneric()); + case NPTypeCode.Char: return _all_linear(a.MakeGeneric()); + case NPTypeCode.Double: return _all_linear(a.MakeGeneric()); + case NPTypeCode.Single: return _all_linear(a.MakeGeneric()); + case NPTypeCode.Decimal: return _all_linear(a.MakeGeneric()); + default: + throw new NotSupportedException(); + } #endregion #endif } @@ -51,12 +51,113 @@ public static bool all(NDArray a) /// Test whether all array elements along a given axis evaluate to True. /// /// Input array or object that can be converted to an array. - /// Axis or axes along which a logical OR reduction is performed. The default (axis = None) is to perform a logical OR over all the dimensions of the input array. axis may be negative, in which case it counts from the last to the first axis. + /// Axis or axes along which a logical AND reduction is performed. The default (axis = None) is to perform a logical OR over all the dimensions of the input array. axis may be negative, in which case it counts from the last to the first axis. /// A new boolean or ndarray is returned unless out is specified, in which case a reference to out is returned. /// https://docs.scipy.org/doc/numpy/reference/generated/numpy.all.html - public static NDArray all(NDArray nd, int axis) + public static NDArray all(NDArray nd, int axis, bool keepdims = false) { - throw new NotImplementedException(); //TODO + if (axis < 0) + axis = nd.ndim + axis; + if (axis < 0 || axis >= nd.ndim) + { + throw new ArgumentOutOfRangeException(nameof(axis)); + } + if (nd.ndim == 0) + { + throw new ArgumentException("Can't operate with zero array"); + } + if (nd == null) + { + throw new ArgumentException("Can't operate with null array"); + } + + int[] inputShape = nd.shape; + int[] outputShape = new int[keepdims ? inputShape.Length : inputShape.Length - 1]; + int outputIndex = 0; + for (int i = 0; i < inputShape.Length; i++) + { + if (i != axis) + { + outputShape[outputIndex++] = inputShape[i]; + } + else if (keepdims) + { + outputShape[outputIndex++] = 1; // 保留轴,但长度为1 + } + } + + NDArray resultArray = (NDArray)zeros(outputShape); + Span resultSpan = resultArray.GetData().AsSpan(); + + int axisSize = inputShape[axis]; + + // It help to build an index + int preAxisStride = 1; + for (int i = 0; i < axis; i++) + { + preAxisStride *= inputShape[i]; + } + + int postAxisStride = 1; + for (int i = axis + 1; i < inputShape.Length; i++) + { + postAxisStride *= inputShape[i]; + } + + + // Operate different logic by TypeCode + bool computationSuccess = false; + switch (nd.typecode) + { + case NPTypeCode.Boolean: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Byte: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Int16: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.UInt16: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Int32: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.UInt32: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Int64: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.UInt64: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Char: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Double: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Single: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Decimal: computationSuccess = ComputeAllPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + default: + throw new NotSupportedException($"Type {nd.typecode} is not supported"); + } + + if (!computationSuccess) + { + throw new InvalidOperationException("Failed to compute all() along the specified axis"); + } + + return resultArray; + } + + private static bool ComputeAllPerAxis(NDArray nd, int axis, int preAxisStride, int postAxisStride, int axisSize, Span resultSpan) where T : unmanaged + { + Span inputSpan = nd.GetData().AsSpan(); + + + for (int o = 0; o < resultSpan.Length; o++) + { + int blockIndex = o / postAxisStride; + int inBlockIndex = o % postAxisStride; + int inputStartIndex = blockIndex * axisSize * postAxisStride + inBlockIndex; + + bool currentResult = true; + for (int a = 0; a < axisSize; a++) + { + int inputIndex = inputStartIndex + a * postAxisStride; + if (inputSpan[inputIndex].Equals(default(T))) + { + currentResult = false; + break; + } + } + resultSpan[o] = currentResult; + } + + return true; } private static bool _all_linear(NDArray nd) where T : unmanaged diff --git a/test/NumSharp.UnitTest/Logic/np_all_axis_Test.cs b/test/NumSharp.UnitTest/Logic/np_all_axis_Test.cs new file mode 100644 index 00000000..2da7a608 --- /dev/null +++ b/test/NumSharp.UnitTest/Logic/np_all_axis_Test.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NumSharp; + +namespace NumSharp.UnitTest.Logic +{ + [TestClass] + public class np_all_axis_Test + { + [TestMethod] + public void np_all_axis_2D() + { + // Test array: [[true, false, true], [true, true, true]] + var arr = np.array(new bool[,] { { true, false, true }, { true, true, true } }); + + // Test axis=0 (along columns): should be [true, false, true] (all in each column) + var result_axis0 = np.all(arr, axis: 0); + var expected_axis0 = np.array(new bool[] { true, false, true }); + Assert.IsTrue(np.array_equal(result_axis0, expected_axis0)); + + // Test axis=1 (along rows): should be [false, true] (all in each row) + var result_axis1 = np.all(arr, axis: 1); + var expected_axis1 = np.array(new bool[] { false, true }); + Assert.IsTrue(np.array_equal(result_axis1, expected_axis1)); + } + + [TestMethod] + public void np_all_axis_3D() + { + // Create a 3D array for testing + var arr = np.ones(new int[] { 2, 3, 4 }); // All ones (truthy) + arr[0, 1, 2] = 0; // Add one falsy value + + // Test different axes + var result_axis0 = np.all(arr, axis: 0); // Shape should be (3, 4) + Assert.AreEqual(2, result_axis0.ndim); + Assert.AreEqual(3, result_axis0.shape[0]); + Assert.AreEqual(4, result_axis0.shape[1]); + + var result_axis1 = np.all(arr, axis: 1); // Shape should be (2, 4) + Assert.AreEqual(2, result_axis1.ndim); + Assert.AreEqual(2, result_axis1.shape[0]); + Assert.AreEqual(4, result_axis1.shape[1]); + + var result_axis2 = np.all(arr, axis: 2); // Shape should be (2, 3) + Assert.AreEqual(2, result_axis2.ndim); + Assert.AreEqual(2, result_axis2.shape[0]); + Assert.AreEqual(3, result_axis2.shape[1]); + } + + [TestMethod] + public void np_all_keepdims() + { + var arr = np.array(new bool[,] { { true, false, true }, { true, true, true } }); + + // Test with keepdims=true + var result_keepdims = np.all(arr, axis: 0, keepdims: true); + Assert.AreEqual(2, result_keepdims.ndim); // Should maintain original number of dimensions + Assert.AreEqual(1, result_keepdims.shape[0]); // The reduced axis becomes size 1 + Assert.AreEqual(3, result_keepdims.shape[1]); // Other dimensions remain + + var result_keepdims1 = np.all(arr, axis: 1, keepdims: true); + Assert.AreEqual(2, result_keepdims1.ndim); // Should maintain original number of dimensions + Assert.AreEqual(2, result_keepdims1.shape[0]); // Other dimensions remain + Assert.AreEqual(1, result_keepdims1.shape[1]); // The reduced axis becomes size 1 + } + + [TestMethod] + public void np_all_different_types() + { + // Test with integer array + var int_arr = np.array(new int[,] { { 1, 2, 3 }, { 4, 0, 6 } }); // Contains a zero (falsy value) + var int_result = np.all(int_arr, axis: 1); + // First row: all non-zero -> true, Second row: contains zero -> false + Assert.AreEqual(true, int_result[0]); + Assert.AreEqual(false, int_result[1]); + } + } +} \ No newline at end of file From cc0d0c41d2c97ca8a14a76f64eecebbb7d7ddac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=B8=85=E6=B3=89?= <2664542771@qq.com> Date: Sat, 29 Nov 2025 21:49:00 +0800 Subject: [PATCH 2/6] update np.any function and add py.complex function --- src/NumSharp.Core/Logic/np.all.cs | 2 +- src/NumSharp.Core/Logic/np.any.cs | 106 +++++++++++++++++++- src/NumSharp.Core/Utilities/ArrayConvert.cs | 15 ++- src/NumSharp.Core/Utilities/py.cs | 48 ++++++++- test/NumSharp.UnitTest/Utilities/pyTest.cs | 12 +++ 5 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 test/NumSharp.UnitTest/Utilities/pyTest.cs diff --git a/src/NumSharp.Core/Logic/np.all.cs b/src/NumSharp.Core/Logic/np.all.cs index d3eea431..7691b25a 100644 --- a/src/NumSharp.Core/Logic/np.all.cs +++ b/src/NumSharp.Core/Logic/np.all.cs @@ -82,7 +82,7 @@ public static NDArray all(NDArray nd, int axis, bool keepdims = false) } else if (keepdims) { - outputShape[outputIndex++] = 1; // 保留轴,但长度为1 + outputShape[outputIndex++] = 1; // keep axis but length is one. } } diff --git a/src/NumSharp.Core/Logic/np.any.cs b/src/NumSharp.Core/Logic/np.any.cs index 4dd393e4..3aa0a920 100644 --- a/src/NumSharp.Core/Logic/np.any.cs +++ b/src/NumSharp.Core/Logic/np.any.cs @@ -55,9 +55,111 @@ public static bool any(NDArray a) /// Axis or axes along which a logical OR reduction is performed. The default (axis = None) is to perform a logical OR over all the dimensions of the input array. axis may be negative, in which case it counts from the last to the first axis. /// A new boolean or ndarray is returned unless out is specified, in which case a reference to out is returned. /// https://docs.scipy.org/doc/numpy/reference/generated/numpy.any.html - public static NDArray any(NDArray nd, int axis) + public static NDArray any(NDArray nd, int axis, bool keepdims) { - throw new NotImplementedException(); //TODO + if (axis < 0) + axis = nd.ndim + axis; + if (axis < 0 || axis >= nd.ndim) + { + throw new ArgumentOutOfRangeException(nameof(axis)); + } + if (nd.ndim == 0) + { + throw new ArgumentException("Can't operate with zero array"); + } + if (nd == null) + { + throw new ArgumentException("Can't operate with null array"); + } + + int[] inputShape = nd.shape; + int[] outputShape = new int[keepdims ? inputShape.Length : inputShape.Length - 1]; + int outputIndex = 0; + for (int i = 0; i < inputShape.Length; i++) + { + if (i != axis) + { + outputShape[outputIndex++] = inputShape[i]; + } + else if (keepdims) + { + outputShape[outputIndex++] = 1; // keep axis but length is one. + } + } + + NDArray resultArray = (NDArray)zeros(outputShape); + Span resultSpan = resultArray.GetData().AsSpan(); + + int axisSize = inputShape[axis]; + + // It help to build an index + int preAxisStride = 1; + for (int i = 0; i < axis; i++) + { + preAxisStride *= inputShape[i]; + } + + int postAxisStride = 1; + for (int i = axis + 1; i < inputShape.Length; i++) + { + postAxisStride *= inputShape[i]; + } + + + // Operate different logic by TypeCode + bool computationSuccess = false; + switch (nd.typecode) + { + case NPTypeCode.Boolean: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Byte: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Int16: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.UInt16: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Int32: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.UInt32: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Int64: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.UInt64: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Char: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Double: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Single: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + case NPTypeCode.Decimal: computationSuccess = ComputeAnyPerAxis(nd.MakeGeneric(), axis, preAxisStride, postAxisStride, axisSize, resultSpan); break; + default: + throw new NotSupportedException($"Type {nd.typecode} is not supported"); + } + + + if (!computationSuccess) + { + throw new InvalidOperationException("Failed to compute all() along the specified axis"); + } + + return resultArray; + } + + private static bool ComputeAnyPerAxis(NDArray nd, int axis, int preAxisStride, int postAxisStride, int axisSize, Span resultSpan) where T : unmanaged + { + Span inputSpan = nd.GetData().AsSpan(); + + + for (int o = 0; o < resultSpan.Length; o++) + { + int blockIndex = o / postAxisStride; + int inBlockIndex = o % postAxisStride; + int inputStartIndex = blockIndex * axisSize * postAxisStride + inBlockIndex; + + bool currentResult = true; + for (int a = 0; a < axisSize; a++) + { + int inputIndex = inputStartIndex + a * postAxisStride; + if (inputSpan[inputIndex].Equals(default(T))) + { + currentResult = true; + break; + } + } + resultSpan[o] = currentResult; + } + + return false; } private static bool _any_linear(NDArray nd) where T : unmanaged diff --git a/src/NumSharp.Core/Utilities/ArrayConvert.cs b/src/NumSharp.Core/Utilities/ArrayConvert.cs index 71f4658c..b51f13de 100644 --- a/src/NumSharp.Core/Utilities/ArrayConvert.cs +++ b/src/NumSharp.Core/Utilities/ArrayConvert.cs @@ -1,6 +1,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; using System.Threading.Tasks; using NumSharp.Backends; @@ -4072,12 +4073,13 @@ public static Complex[] ToComplex(Decimal[] sourceArray) Parallel.For(0, length, i => new Complex(Converts.ToDouble(sourceArray[i]), 0d)); return output; } - + /// /// Converts array to a array. /// /// The array to convert /// Converted array of type Complex + /// A string in sourceArray has an invalid complex format [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Complex[] ToComplex(String[] sourceArray) { @@ -4087,7 +4089,16 @@ public static Complex[] ToComplex(String[] sourceArray) var length = sourceArray.Length; var output = new Complex[length]; - Parallel.For(0, length, i => new Complex(Converts.ToDouble(sourceArray[i]), 0d)); + Parallel.For(0, length, i => + { + string input = sourceArray[i]?.Trim() ?? string.Empty; + if (string.IsNullOrEmpty(input)) + { + output[i] = Complex.Zero; // NullString save as zero. + return; + } + var match = py.Complex(sourceArray[i]); + }); return output; } #endif diff --git a/src/NumSharp.Core/Utilities/py.cs b/src/NumSharp.Core/Utilities/py.cs index 927f52f0..3989abad 100644 --- a/src/NumSharp.Core/Utilities/py.cs +++ b/src/NumSharp.Core/Utilities/py.cs @@ -1,4 +1,8 @@ -namespace NumSharp.Utilities +using System; +using System.Numerics; +using System.Text.RegularExpressions; + +namespace NumSharp.Utilities { /// /// Implements Python utility functions that are often used in connection with numpy @@ -12,5 +16,47 @@ public static int[] range(int n) a[i] = i; return a; } + /// + /// 解析单个Python风格的复数字符串为Complex对象 + /// + private static readonly Regex _pythonComplexRegex = new Regex( +@"^(?-?\d+(\.\d+)?)?((?\+|-)?(?\d+(\.\d+)?)?)?j$|^(?-?\d+(\.\d+)?)$", +RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); + public static Complex Complex(string input) + { + var match = _pythonComplexRegex.Match(input); + if (!match.Success) + throw new FormatException($"Invalid Python complex format: '{input}'. Expected format like '10+5j', '3-2j', '4j' or '5'."); + + // 解析仅实部的场景 + if (match.Groups["onlyReal"].Success) + { + double real = double.Parse(match.Groups["onlyReal"].Value); + return new Complex(real, 0); + } + + // 解析实部(默认0) + double realPart = 0; + if (double.TryParse(match.Groups["real"].Value, out double r)) + realPart = r; + + // 解析虚部(处理特殊情况:j / -j / +j) + double imagPart = 0; + string imagStr = match.Groups["imag"].Value; + string imagSign = match.Groups["imagSign"].Value; + + if (string.IsNullOrEmpty(imagStr) && !string.IsNullOrEmpty(input.TrimEnd('j', 'J'))) + { + // 处理仅虚部的情况:j → 1j, -j → -1j, +j → 1j + imagStr = "1"; + } + + if (double.TryParse(imagStr, out double im)) + { + imagPart = im * (imagSign == "-" ? -1 : 1); + } + + return new Complex(realPart, imagPart); + } } } diff --git a/test/NumSharp.UnitTest/Utilities/pyTest.cs b/test/NumSharp.UnitTest/Utilities/pyTest.cs new file mode 100644 index 00000000..304cd814 --- /dev/null +++ b/test/NumSharp.UnitTest/Utilities/pyTest.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NumSharp.UnitTest.Utilities +{ + internal class pyTest + { + } +} From c7cdbdf123441948dd05f436f94f5b72b316326b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=B8=85=E6=B3=89?= <2664542771@qq.com> Date: Sat, 29 Nov 2025 21:54:35 +0800 Subject: [PATCH 3/6] just modify the function name --- src/NumSharp.Core/Utilities/ArrayConvert.cs | 2 +- src/NumSharp.Core/Utilities/py.cs | 2 +- test/NumSharp.UnitTest/Logic/np.any.Test.cs | 108 ++++++++++++++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 test/NumSharp.UnitTest/Logic/np.any.Test.cs diff --git a/src/NumSharp.Core/Utilities/ArrayConvert.cs b/src/NumSharp.Core/Utilities/ArrayConvert.cs index b51f13de..b92e6dbd 100644 --- a/src/NumSharp.Core/Utilities/ArrayConvert.cs +++ b/src/NumSharp.Core/Utilities/ArrayConvert.cs @@ -4097,7 +4097,7 @@ public static Complex[] ToComplex(String[] sourceArray) output[i] = Complex.Zero; // NullString save as zero. return; } - var match = py.Complex(sourceArray[i]); + var match = py.complex(sourceArray[i]); }); return output; } diff --git a/src/NumSharp.Core/Utilities/py.cs b/src/NumSharp.Core/Utilities/py.cs index 3989abad..6bf3219f 100644 --- a/src/NumSharp.Core/Utilities/py.cs +++ b/src/NumSharp.Core/Utilities/py.cs @@ -22,7 +22,7 @@ public static int[] range(int n) private static readonly Regex _pythonComplexRegex = new Regex( @"^(?-?\d+(\.\d+)?)?((?\+|-)?(?\d+(\.\d+)?)?)?j$|^(?-?\d+(\.\d+)?)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); - public static Complex Complex(string input) + public static Complex complex(string input) { var match = _pythonComplexRegex.Match(input); if (!match.Success) diff --git a/test/NumSharp.UnitTest/Logic/np.any.Test.cs b/test/NumSharp.UnitTest/Logic/np.any.Test.cs new file mode 100644 index 00000000..db11390b --- /dev/null +++ b/test/NumSharp.UnitTest/Logic/np.any.Test.cs @@ -0,0 +1,108 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NumSharp; + +namespace NumSharp.UnitTest.Logic +{ + [TestClass] + public class NpAnyTest + { + [TestMethod] + public void Any1DArrayTest() + { + // 测试1维数组 + var arr = np.array(new int[] { 0, 1, 2 }); + var result = np.any(arr, axis: 0, keepdims: false); + Assert.AreEqual(true, result.GetItem(0)); + } + + [TestMethod] + public void Any2DArrayTest() + { + // 测试2维数组 + var arr = np.array(new int[,] { { 0, 0 }, { 1, 0 } }); + var result = np.any(arr, axis: 0, keepdims: false); + var expected = np.array(new bool[] { true, false }); + Assert.IsTrue(Enumerable.SequenceEqual(result.Data(), expected.Data())); + } + + [TestMethod] + public void Any2DArrayWithAxis1Test() + { + // 测试2维数组,axis=1 + var arr = np.array(new int[,] { { 0, 0 }, { 1, 0 } }); + var result = np.any(arr, axis: 1, keepdims: false); + var expected = np.array(new bool[] { false, true }); + Assert.IsTrue(Enumerable.SequenceEqual(result.Data(), expected.Data())); + } + + [TestMethod] + public void AnyWithKeepdimsTest() + { + // 测试keepdims参数 + var arr = np.array(new int[,] { { 0, 0 }, { 1, 0 } }); + var result = np.any(arr, axis: 0, keepdims: true); + // 结果形状应该是 (1, 2) 而不是 (2,) + Assert.AreEqual(2, result.ndim); + Assert.AreEqual(1, result.shape[0]); + Assert.AreEqual(2, result.shape[1]); + } + + [TestMethod] + public void AnyWithNegativeAxisTest() + { + // 测试负轴 + var arr = np.array(new int[,] { { 0, 0 }, { 1, 0 } }); + var result1 = np.any(arr, axis: 1, keepdims: false); // axis=1 + var result2 = np.any(arr, axis: -1, keepdims: false); // axis=-1 (应该是等价的) + Assert.IsTrue(Enumerable.SequenceEqual(result1.Data(), result2.Data())); + } + + [TestMethod] + public void AnyAllZerosTest() + { + // 测试全零数组 + var arr = np.array(new int[,] { { 0, 0 }, { 0, 0 } }); + var result = np.any(arr, axis: 0, keepdims: false); + var expected = np.array(new bool[] { false, false }); + Assert.IsTrue(Enumerable.SequenceEqual(result.Data(), expected.Data())); + } + + [TestMethod] + public void AnyAllNonZerosTest() + { + // 测试全非零数组 + var arr = np.array(new int[,] { { 1, 2 }, { 3, 4 } }); + var result = np.any(arr, axis: 0, keepdims: false); + var expected = np.array(new bool[] { true, true }); + Assert.IsTrue(Enumerable.SequenceEqual(result.Data(), expected.Data())); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AnyInvalidAxisTest() + { + // 测试无效轴 + var arr = np.array(new int[,] { { 0, 1 }, { 2, 3 } }); + np.any(arr, axis: 5, keepdims: false); // 轴5不存在 + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void AnyZeroDimensionalArrayTest() + { + // 测试零维数组 + var arr = np.array(5); // 零维数组 + np.any(arr, axis: 0, keepdims: false); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void AnyNullArrayTest() + { + // 测试空数组 + NDArray arr = null; + np.any(arr, axis: 0, keepdims: false); + } + } +} \ No newline at end of file From 52e48034c09654682223fa324c487f88cee94bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=B8=85=E6=B3=89?= <2664542771@qq.com> Date: Thu, 4 Dec 2025 22:21:17 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E5=88=A9=E7=94=A8np.clip()=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E4=BA=86=E4=BA=8C=E5=88=86=E7=B1=BB=E7=9A=84=E4=BA=A4?= =?UTF-8?q?=E5=8F=89=E7=86=B5=E6=8D=9F=E5=A4=B1=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/NeuralNetwork.NumSharp/Cost/BinaryCrossEntropy.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/NeuralNetwork.NumSharp/Cost/BinaryCrossEntropy.cs b/examples/NeuralNetwork.NumSharp/Cost/BinaryCrossEntropy.cs index dbc68638..a42efc35 100644 --- a/examples/NeuralNetwork.NumSharp/Cost/BinaryCrossEntropy.cs +++ b/examples/NeuralNetwork.NumSharp/Cost/BinaryCrossEntropy.cs @@ -15,8 +15,7 @@ public BinaryCrossEntropy() : base("binary_crossentropy") public override NDArray Forward(NDArray preds, NDArray labels) { //ToDo: np.clip - //var output = Clip(preds, Epsilon, 1 - Epsilon); - var output = preds; + var output = np.clip(preds, Epsilon, 1 - Epsilon); output = np.mean(-(labels * np.log(output) + (1 - labels) * np.log(1 - output))); return output; } @@ -24,8 +23,8 @@ public override NDArray Forward(NDArray preds, NDArray labels) public override NDArray Backward(NDArray preds, NDArray labels) { //ToDo: np.clip - //var output = Clip(preds, Epsilon, 1 - Epsilon); - var output = preds; + var output = np.clip(preds, Epsilon, 1 - Epsilon); + output = preds; return (output - labels) / (output * (1 - output)); } } From 1e53202a7b542a1419ee472bce7e3f357202453b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=B8=85=E6=B3=89?= <2664542771@qq.com> Date: Fri, 5 Dec 2025 21:27:53 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86where=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=EF=BC=8C=E5=A2=9E=E5=8A=A0=E4=BA=86=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docfx_project/articles/intro.md | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/docfx_project/articles/intro.md b/docfx_project/articles/intro.md index 6459c451..3773d894 100644 --- a/docfx_project/articles/intro.md +++ b/docfx_project/articles/intro.md @@ -1,4 +1,4 @@ -# Introduction +# Introduction The following pages are for the users who want to use NumSharp. @@ -61,11 +61,8 @@ Column wise also starts with the element [0,0] but! it stays in the 1st column a The image below (taken from https://en.wikipedia.org/wiki/File:Row_and_column_major_order.svg) shows again the 'algorithm' for storing data from matrix to vector. - ![Row Wise Column Wise](../images/rowWise_ColumnWise.png) - - **N dim tensor** Now we come to the most tricky question - how to store a general n dimensional tensor inside a 1D array. @@ -100,17 +97,3 @@ So fill first dimension, increase next, fill again, etc. also in n dimensional t And this you can imagine as **forward filling layout**. That's it. Now you have enough knowledge about NDArray, NDStorage and Shape. Check the other chapters for a how to use. :) - - - - - - - - - - - - - - From cf3a0c5297b39cf94033561b5ab2baec18fa8ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=B8=85=E6=B3=89?= <2664542771@qq.com> Date: Fri, 5 Dec 2025 21:28:23 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86where=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E7=94=A8=E4=BA=8E=E8=8E=B7=E5=8F=96=E6=8E=A9=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E4=BA=86=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NumSharp_Implementation_Documentation.md | 410 +++++++++++++++++++ src/NumSharp.Core/APIs/np.where.cs | 71 ++++ test/NumSharp.UnitTest/APIs/np.where.Test.cs | 78 ++++ test/NumSharp.UnitTest/Logic/np.any.Test.cs | 8 +- 4 files changed, 564 insertions(+), 3 deletions(-) create mode 100644 NumSharp_Implementation_Documentation.md create mode 100644 src/NumSharp.Core/APIs/np.where.cs create mode 100644 test/NumSharp.UnitTest/APIs/np.where.Test.cs diff --git a/NumSharp_Implementation_Documentation.md b/NumSharp_Implementation_Documentation.md new file mode 100644 index 00000000..c1e24a66 --- /dev/null +++ b/NumSharp_Implementation_Documentation.md @@ -0,0 +1,410 @@ +# NumSharp使用指南及核心组件详解 + +本文档详细介绍了NumSharp数值计算库的核心组件NDArray、Slice和Shape的使用方法,包含创建、修改、访问和操作数组的具体示例。 + +## 目录 + +1. [NDArray基本操作](#ndarray基本操作) +2. [Slice切片操作](#slice切片操作) +3. [Shape形状管理](#shape形状管理) +4. [高级操作示例](#高级操作示例) + +## NDArray基本操作 + +NDArray是NumSharp中的核心数据结构,代表多维同质数组。 + +### 创建NDArray + +#### 从数组创建 +```csharp +using NumSharp; + +// 从C#数组创建 +int[] arr1D = {1, 2, 3, 4, 5}; +NDArray nd1 = new NDArray(arr1D); + +double[,] arr2D = {{1.0, 2.0}, {3.0, 4.0}}; +NDArray nd2 = new NDArray(arr2D); + +// 使用np创建函数 +NDArray zeros = np.zeros(3, 4); // 3x4零矩阵 +NDArray ones = np.ones(2, 3, 4); // 2x3x4全1矩阵 +NDArray range = np.arange(10); // [0, 1, 2, ..., 9] +NDArray linspace = np.linspace(0, 10, 5); // [0, 2.5, 5, 7.5, 10] +``` + +#### 指定数据类型创建 +```csharp +// 指定数据类型和形状创建 +NDArray intArr = new NDArray(typeof(int), new Shape(3, 4)); // 3x4整数数组 +NDArray floatArr = new NDArray(NPTypeCode.Single, new Shape(2, 3)); // 2x3单精度浮点数组 +``` + +### 访问和修改数据 + +#### 获取数组信息 +```csharp +NDArray arr = np.arange(12).reshape(3, 4); + +Console.WriteLine($"数据类型: {arr.dtype}"); // 数据类型: System.Int32 +Console.WriteLine($"形状: [{string.Join(", ", arr.shape)}]"); // 形状: [3, 4] +Console.WriteLine($"维度数: {arr.ndim}"); // 维度数: 2 +Console.WriteLine($"元素总数: {arr.size}"); // 元素总数: 12 +Console.WriteLine($"数据大小: {arr.dtypesize}"); // 数据大小: 4 (字节) +``` + +#### 元素访问 +```csharp +NDArray arr = np.array(new int[] {10, 20, 30, 40}); + +// 访问单个元素 +int val = arr.GetInt32(2); // val = 30 +Console.WriteLine($"第三元素: {val}"); + +// 二维数组访问 +NDArray mat = np.array(new int[,] {{1, 2, 3}, {4, 5, 6}}); +int val2d = mat.GetInt32(1, 2); // val2d = 6 (第二行第三列) +Console.WriteLine($"(1,2)位置值: {val2d}"); + +// 通用访问方法 +var genericVal = mat.GetValue(0, 1); // genericVal = 2 +``` + +#### 元素修改 +```csharp +NDArray arr = np.arange(5); + +// 修改单个元素 +arr.SetValue(99, 2); // 将索引2处的元素改为99 +Console.WriteLine($"修改后数组: {arr}"); + +// 批量修改 +arr.SetData(new int[] {100, 200}, 1, 3); // 修改索引1和3处的值 + +// 修改二维数组 +NDArray mat = np.zeros(2, 3); +mat.SetInt32(42, 0, 1); // 将(0,1)位置的值设为42 +Console.WriteLine($"修改后矩阵:\n{mat}"); +``` + +#### 批量数据操作 +```csharp +// 获取内部数据的引用 +NDArray arr = np.array(new double[] {1.1, 2.2, 3.3}); +ArraySlice data = arr.Data(); +Console.WriteLine($"数据: [{string.Join(", ", data)}]"); + +// 批量设置数据 +NDArray target = new NDArray(typeof(double), 3); +target.SetData(new double[] {5.5, 6.6, 7.7}); +Console.WriteLine($"目标数组: {target}"); +``` + +## Slice切片操作 + +Slice类提供了类似NumPy的切片功能,可以方便地对数组进行部分访问和修改。 + +### 基本切片操作 +```csharp +NDArray arr = np.arange(10); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +// 使用Slice对象 +NDArray slice1 = arr[new Slice(2, 7)]; // [2, 3, 4, 5, 6] - 从索引2到6 +NDArray slice2 = arr[new Slice(null, 5)]; // [0, 1, 2, 3, 4] - 从开始到索引4 +NDArray slice3 = arr[new Slice(3, null)]; // [3, 4, 5, 6, 7, 8, 9] - 从索引3到结束 + +Console.WriteLine($"原数组: {arr}"); +Console.WriteLine($"切片[2:7]: {slice1}"); +Console.WriteLine($"切片[:5]: {slice2}"); +Console.WriteLine($"切片[3:]: {slice3}"); +``` + +### 带步长的切片 +```csharp +NDArray arr = np.arange(10); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +// 步长切片 +NDArray stepSlice = arr[new Slice(0, null, 2)]; // [0, 2, 4, 6, 8] - 步长为2 +NDArray reverseSlice = arr[new Slice(null, null, -1)]; // [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] - 反转 +NDArray reverseStep = arr[new Slice(null, null, -2)]; // [9, 7, 5, 3, 1] - 反转步长为2 + +Console.WriteLine($"步长为2的切片: {stepSlice}"); +Console.WriteLine($"反向切片: {reverseSlice}"); +Console.WriteLine($"反向步长为2的切片: {reverseStep}"); +``` + +### 多维数组切片 +```csharp +// 创建3x4数组 +NDArray mat = np.arange(12).reshape(3, 4); +/* +mat = +[[ 0, 1, 2, 3], + [ 4, 5, 6, 7], + [ 8, 9, 10, 11]] +*/ + +// 行切片 +NDArray rowSlice = mat[new Slice(1, 3), Slice.All]; // 第1到2行 +Console.WriteLine($"第1-2行:\n{rowSlice}"); + +// 列切片 +NDArray colSlice = mat[Slice.All, new Slice(0, 2)]; // 第0到1列 +Console.WriteLine($"第0-1列:\n{colSlice}"); + +// 组合切片 +NDArray subMat = mat[new Slice(0, 2), new Slice(1, 3)]; // 前2行的第1-2列 +Console.WriteLine($"子矩阵:\n{subMat}"); + +// 单个索引(会减少一个维度) +NDArray row = mat[1, Slice.All]; // 第1行,结果是1维数组 +Console.WriteLine($"第1行: {row}"); + +NDArray col = mat[Slice.All, 2]; // 第2列,结果是1维数组 +Console.WriteLine($"第2列: {col}"); +``` + +### 使用字符串切片表示法 +```csharp +NDArray arr = np.arange(10); + +// 使用字符串表示法 +NDArray slice1 = arr["2:7"]; // 等同于 new Slice(2, 7) +NDArray slice2 = arr["::2"]; // 等同于 new Slice(null, null, 2) +NDArray slice3 = arr["::-1"]; // 等同于 new Slice(null, null, -1) + +Console.WriteLine($"字符串表示法切片'2:7': {slice1}"); +Console.WriteLine($"字符串表示法切片'::2': {slice2}"); +Console.WriteLine($"字符串表示法切片'::-1': {slice3}"); +``` + +### 切片修改操作 +```csharp +NDArray arr = np.arange(10); +Console.WriteLine($"原始数组: {arr}"); + +// 修改切片会影响原数组(因为切片是视图) +NDArray slice = arr[new Slice(2, 5)]; +slice.fill(99); // 将切片中的所有元素设置为99 +Console.WriteLine($"修改后的原数组: {arr}"); // [0, 1, 99, 99, 99, 5, 6, 7, 8, 9] + +// 二维数组切片修改 +NDArray mat = np.zeros(3, 4); +mat[new Slice(1, 3), new Slice(1, 3)] = np.ones(2, 2) * 5; +Console.WriteLine($"修改后的矩阵:\n{mat}"); +``` + +## Shape形状管理 + +Shape类管理数组的形状信息,包括维度、大小和内存布局。 + +### Shape基本操作 +```csharp +// 创建Shape对象 +Shape shape1 = new Shape(3, 4); // 3x4形状 +Shape shape2 = new Shape(2, 3, 4); // 2x3x4形状 +Shape shape3 = new Shape(new int[] {5, 6, 7}); // 从数组创建 + +Console.WriteLine($"shape1: {shape1}, 维度: {shape1.NDim}, 大小: {shape1.Size}"); +Console.WriteLine($"shape2: {shape2}, 维度: {shape2.NDim}, 大小: {shape2.Size}"); +Console.WriteLine($"shape3: {shape3}, 维度: {shape3.NDim}, 大小: {shape3.Size}"); + +// Shape的维度访问 +Console.WriteLine($"shape1的第0维: {shape1[0]}"); // 3 +Console.WriteLine($"shape1的第1维: {shape1[1]}"); // 4 +``` + +### Shape转换操作 +```csharp +NDArray arr = np.arange(12); + +// 重塑形状 +NDArray reshaped1 = arr.reshape(3, 4); // 3x4矩阵 +NDArray reshaped2 = arr.reshape(2, 6); // 2x6矩阵 +NDArray reshaped3 = arr.reshape(2, 2, 3); // 2x2x3三维数组 + +Console.WriteLine($"原数组形状: {arr.shape}"); +Console.WriteLine($"重塑为3x4:\n{reshaped1}"); +Console.WriteLine($"重塑为2x6:\n{reshaped2}"); +Console.WriteLine($"重塑为2x2x3:\n{reshaped3}"); + +// 扁平化 +NDArray flat = reshaped1.flatten(); // 转换为一维数组 +Console.WriteLine($"扁平化后: {flat}"); +``` + +### Shape的高级操作 +```csharp +NDArray arr = np.arange(24).reshape(2, 3, 4); + +// 获取子形状 +var (subShape, offset) = arr.Shape.GetSubshape(1); // 获取第二"页"的形状和偏移 +Console.WriteLine($"子形状: {subShape}, 偏移: {offset}"); + +// 获取坐标 +int linearIndex = 10; +int[] coords = arr.Shape.GetCoordinates(linearIndex); +Console.WriteLine($"线性索引{linearIndex}对应的坐标: [{string.Join(", ", coords)}]"); + +// 获取偏移 +int[] testCoords = {1, 2, 3}; +int offsetFromCoords = arr.Shape.GetOffset(testCoords); +Console.WriteLine($"坐标[{string.Join(", ", testCoords)}]对应的偏移: {offsetFromCoords}"); +``` + +## 高级操作示例 + +### 数学运算 +```csharp +// 基本数学运算 +NDArray a = np.array(new double[] {1, 2, 3, 4}); +NDArray b = np.array(new double[] {5, 6, 7, 8}); + +NDArray sum = a + b; // [6, 8, 10, 12] +NDArray product = a * b; // [5, 12, 21, 32] +NDArray subtract = b - a; // [4, 4, 4, 4] +NDArray divide = b / a; // [5, 3, 2.33, 2] + +Console.WriteLine($"a: {a}"); +Console.WriteLine($"b: {b}"); +Console.WriteLine($"a + b: {sum}"); +Console.WriteLine($"a * b: {product}"); + +// 一元运算 +NDArray sqrtA = np.sqrt(a); // [1, 1.41, 1.73, 2] +NDArray squareA = np.square(a); // [1, 4, 9, 16] +NDArray absA = np.abs(a - 2.5); // [1.5, 0.5, 0.5, 1.5] + +Console.WriteLine($"sqrt(a): {sqrtA}"); +Console.WriteLine($"square(a): {squareA}"); +Console.WriteLine($"abs(a - 2.5): {absA}"); +``` + +### 统计操作 +```csharp +NDArray arr = np.array(new double[,] {{1, 2, 3}, {4, 5, 6}}); + +// 基本统计 +double sumAll = np.sum(arr).GetDouble(); // 21 +double meanAll = np.mean(arr).GetDouble(); // 3.5 +double maxAll = np.max(arr).GetDouble(); // 6 +double minAll = np.min(arr).GetDouble(); // 1 + +Console.WriteLine($"总和: {sumAll}, 平均值: {meanAll}"); +Console.WriteLine($"最大值: {maxAll}, 最小值: {minAll}"); + +// 按轴统计 +NDArray sumAxis0 = np.sum(arr, axis: 0); // [5, 7, 9] - 按列求和 +NDArray sumAxis1 = np.sum(arr, axis: 1); // [6, 15] - 按行求和 + +Console.WriteLine($"按列求和: {sumAxis0}"); +Console.WriteLine($"按行求和: {sumAxis1}"); + +// 获取最值索引 +int argMax = np.argmax(arr).GetInt32(); // 5 (最大值6的索引) +int argMin = np.argmin(arr).GetInt32(); // 0 (最小值1的索引) + +Console.WriteLine($"最大值索引: {argMax}, 最小值索引: {argMin}"); +``` + +### 线性代数操作 +```csharp +// 矩阵乘法 +NDArray matA = np.array(new double[,] {{1, 2}, {3, 4}}); +NDArray matB = np.array(new double[,] {{5, 6}, {7, 8}}); + +NDArray matMul = np.dot(matA, matB); +Console.WriteLine($"矩阵A:\n{matA}"); +Console.WriteLine($"矩阵B:\n{matB}"); +Console.WriteLine($"A * B:\n{matMul}"); + +// 转置 +NDArray transposed = matA.T; // 或者使用 np.transpose(matA) +Console.WriteLine($"A的转置:\n{transposed}"); + +// 其他操作 +NDArray reshaped = np.arange(12).reshape(3, 4); +NDArray transposed2D = np.transpose(reshaped); +Console.WriteLine($"原矩阵:\n{reshaped}"); +Console.WriteLine($"转置后:\n{transposed2D}"); +``` + +### 数组操作 +```csharp +// 数组合并 +NDArray arr1 = np.array(new int[] {1, 2, 3}); +NDArray arr2 = np.array(new int[] {4, 5, 6}); + +NDArray hStack = np.hstack(new NDArray[] {arr1, arr2}); // 水平拼接 +NDArray vStack = np.vstack(new NDArray[] {arr1, arr2}); // 垂直拼接 + +Console.WriteLine($"原数组1: {arr1}"); +Console.WriteLine($"原数组2: {arr2}"); +Console.WriteLine($"水平拼接: {hStack}"); +Console.WriteLine($"垂直拼接:\n{vStack}"); + +// 数组复制 +NDArray original = np.array(new int[] {1, 2, 3}); +NDArray cloned = original.Clone(); // 深拷贝 +NDArray view = original.view(); // 视图(共享数据) + +Console.WriteLine($"原数组: {original}"); +Console.WriteLine($"拷贝: {cloned}"); +Console.WriteLine($"视图: {view}"); + +// 类型转换 +NDArray floatArr = original.astype(typeof(float)); +Console.WriteLine($"转换为float: {floatArr}, 类型: {floatArr.dtype}"); +``` + +### 条件操作 +```csharp +NDArray arr = np.array(new int[] {-2, -1, 0, 1, 2}); + +// 条件选择 +NDArray positive = np.where(arr > 0, arr, 0); // 大于0的保持,否则设为0 +NDArray absValues = np.abs(arr); // 绝对值 + +Console.WriteLine($"原数组: {arr}"); +Console.WriteLine($"正数保持: {positive}"); +Console.WriteLine($"绝对值: {absValues}"); + +// 布尔索引(通过条件获取索引) +NDArray condition = arr > 0; +NDArray positiveOnly = arr[condition]; // 只获取正数 +Console.WriteLine($"正数元素: {positiveOnly}"); +``` + +### 排序和搜索 +```csharp +NDArray arr = np.array(new int[] {3, 1, 4, 1, 5, 9, 2, 6, 5}); + +// 排序 +NDArray sorted = np.sort(arr); +NDArray sortedIndices = np.argsort(arr); // 返回排序后的索引 + +Console.WriteLine($"原数组: {arr}"); +Console.WriteLine($"排序后: {sorted}"); +Console.WriteLine($"排序索引: {sortedIndices}"); + +// 唯一值 +NDArray unique = np.unique(arr); +Console.WriteLine($"唯一值: {unique}"); +``` + +### 广播操作 +```csharp +// NumSharp支持广播操作 +NDArray mat = np.array(new double[,] {{1, 2, 3}, {4, 5, 6}}); +NDArray vec = np.array(new double[] {10, 20, 30}); + +// 向量与矩阵的广播加法 +NDArray result = mat + vec; // 每行都加上向量vec +Console.WriteLine($"矩阵:\n{mat}"); +Console.WriteLine($"向量: {vec}"); +Console.WriteLine($"广播加法结果:\n{result}"); + +// 标量操作(自动广播到所有元素) +NDArray scaled = mat * 2.0; +Console.WriteLine($"矩阵乘以2:\n{scaled}"); +``` \ No newline at end of file diff --git a/src/NumSharp.Core/APIs/np.where.cs b/src/NumSharp.Core/APIs/np.where.cs new file mode 100644 index 00000000..4b477457 --- /dev/null +++ b/src/NumSharp.Core/APIs/np.where.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using System.Collections; +using System.Collections.Generic; + +namespace NumSharp +{ + public static partial class np + { + /// + /// NumPy-like where: If only condition is given, return tuple of indices where condition is True. + /// If x and y are given, return elements chosen from x or y depending on condition. + /// Supports NDArray, bool[], IEnumerable<bool>, Array, IEnumerable, etc. + /// + public static NDArray where(NDArray condition, NDArray x, NDArray y) + { + var broadcasted = np.broadcast_arrays(condition, x, y); + var broadcasted_condition = broadcasted[0]; + var broadcasted_x = broadcasted[1]; + var broadcasted_y = broadcasted[2]; + + var cond = broadcasted_condition.MakeGeneric(); + var result = new NDArray(broadcasted_x.dtype, broadcasted_x.shape); + var condSpan = cond.GetData().AsSpan(); + var xArr = broadcasted_x.GetData(); + var yArr = broadcasted_y.GetData(); + var resArr = result.GetData(); + + for (int i = 0; i < condSpan.Length; i++) + { + resArr.SetIndex(i, condSpan[i] ? xArr.GetIndex(i) : yArr.GetIndex(i)); + } + return result; + } + + public static NDArray where(NDArray condition, NDArray x, NDArray y, Type outType) + { + return where(condition, x.astype(outType), y.astype(outType)); + } + + public static NDArray where(NDArray condition, NDArray x, NDArray y, NPTypeCode typeCode) + { + return where(condition, x.astype(typeCode), y.astype(typeCode)); + } + + public static NDArray[] where(NDArray condition) + { + return condition.TensorEngine.NonZero(condition); + } + + public static NDArray[] where(bool[] condition) + { + return where(np.array(condition)); + } + + public static NDArray[] where(IEnumerable condition) + { + return where(np.array(condition.ToArray())); + } + + public static NDArray where(bool[] condition, Array x, Array y) + { + return where(np.array(condition), np.array(x), np.array(y)); + } + + public static NDArray where(IEnumerable condition, IEnumerable x, IEnumerable y) + { + return where(np.array(condition.ToArray()), np.array(x.Cast().ToArray()), np.array(y.Cast().ToArray())); + } + } +} diff --git a/test/NumSharp.UnitTest/APIs/np.where.Test.cs b/test/NumSharp.UnitTest/APIs/np.where.Test.cs new file mode 100644 index 00000000..17608a1d --- /dev/null +++ b/test/NumSharp.UnitTest/APIs/np.where.Test.cs @@ -0,0 +1,78 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NumSharp; + +namespace NumSharp.UnitTest.APIs +{ + [TestClass] + public class NpWhereTest + { + [TestMethod] + public void WhereWithConditionOnly() + { + var condition = np.array(new bool[] { true, false, true, false }); + var result = np.where(condition); + + Assert.AreEqual(1, result.Length); // Should return one array of indices + Assert.AreEqual(2, result[0].size); // Two true values + Assert.IsTrue(NDArray.Equals(result[0], np.array(new int[] { 0, 2 }))); // Indices should be [0, 2] + } + + [TestMethod] + public void WhereWith3Args1D() + { + var condition = np.array(new bool[] { true, false, true, false }); + var x = np.array(new int[] { 1, 2, 3, 4 }); + var y = np.array(new int[] { 10, 20, 30, 40 }); + + var result = np.where(condition, x, y); + + Assert.IsTrue(NDArray.Equals(result, np.array(new int[] { 1, 20, 3, 40 }))); + } + + [TestMethod] + public void WhereWith3Args2D() + { + var condition = np.array(new bool[,] { { true, false }, { true, true } }); + var x = np.array(new int[,] { { 1, 2 }, { 3, 4 } }); + var y = np.array(new int[,] { { 9, 8 }, { 7, 6 } }); + + var result = np.where(condition, x, y); + + Assert.IsTrue(NDArray.Equals(result, np.array(new int[,] { { 1, 8 }, { 3, 4 } }))); + } + + [TestMethod] + public void WhereWithBroadcasting() + { + var x = np.arange(3).reshape(3, 1); + var y = np.arange(4).reshape(1, 4); + var condition = x < y; + + var result = np.where(condition, x, 10 + y); + + // Expected: when x < y, take from x, otherwise from 10 + y + var expected = np.array(new int[,] { + { 10, 0, 0, 0 }, + { 10, 11, 1, 1 }, + { 10, 11, 12, 2 } + }); + + Assert.IsTrue(NDArray.Equals(result, expected)); + } + + [TestMethod] + public void WhereWithScalarBroadcasting() + { + var a = np.array(new int[,] { { 0, 1, 2 }, { 0, 2, 4 }, { 0, 3, 6 } }); + var result = np.where(a < 4, a, -1); + + var expected = np.array(new int[,] { + { 0, 1, 2 }, + { 0, 2, -1 }, + { 0, 3, -1 } + }); + + Assert.IsTrue(NDArray.Equals(result, expected)); + } + } +} diff --git a/test/NumSharp.UnitTest/Logic/np.any.Test.cs b/test/NumSharp.UnitTest/Logic/np.any.Test.cs index db11390b..7cac33a3 100644 --- a/test/NumSharp.UnitTest/Logic/np.any.Test.cs +++ b/test/NumSharp.UnitTest/Logic/np.any.Test.cs @@ -1,5 +1,6 @@ -using System; +using System; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; using NumSharp; namespace NumSharp.UnitTest.Logic @@ -13,7 +14,8 @@ public void Any1DArrayTest() // 测试1维数组 var arr = np.array(new int[] { 0, 1, 2 }); var result = np.any(arr, axis: 0, keepdims: false); - Assert.AreEqual(true, result.GetItem(0)); + //TODO:TEST + Assert.AreEqual(true, result[0]); } [TestMethod] @@ -105,4 +107,4 @@ public void AnyNullArrayTest() np.any(arr, axis: 0, keepdims: false); } } -} \ No newline at end of file +}