rsnumpy 1.0.3__tar.gz → 1.0.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/.github/workflows/ci.yml +7 -5
  2. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/.github/workflows/release.yml +0 -30
  3. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/.gitignore +3 -0
  4. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/Cargo.lock +11 -44
  5. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/Cargo.toml +12 -7
  6. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/PKG-INFO +13 -2
  7. rsnumpy-1.0.6/build_wheel.sh +155 -0
  8. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/pyproject.toml +15 -2
  9. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/python/rsnumpy/__init__.py +67 -9
  10. rsnumpy-1.0.6/rust-toolchain.toml +2 -0
  11. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/src/indexing.rs +23 -6
  12. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/src/lib.rs +139 -0
  13. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/test/run_test.py +0 -7
  14. rsnumpy-1.0.3/build_wheel.sh +0 -93
  15. rsnumpy-1.0.3/plots.py +0 -1086
  16. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/LICENSE +0 -0
  17. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/README.md +0 -0
  18. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/RELEASE_NOTES.md +0 -0
  19. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/python/rsnumpy/array_methods.py +0 -0
  20. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/python/rsnumpy/array_ops.py +0 -0
  21. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/python/rsnumpy/char.py +0 -0
  22. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/python/rsnumpy/io.py +0 -0
  23. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/python/rsnumpy/linalg/__init__.py +0 -0
  24. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/python/rsnumpy/math_functions.py +0 -0
  25. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/python/rsnumpy/matlib.py +0 -0
  26. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/python/rsnumpy/polynomial/__init__.py +0 -0
  27. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/python/rsnumpy/random/__init__.py +0 -0
  28. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/python/rsnumpy/statistics.py +0 -0
  29. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/src/fft.rs +0 -0
  30. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/src/linalg.rs +0 -0
  31. {rsnumpy-1.0.3 → rsnumpy-1.0.6}/src/random.rs +0 -0
@@ -2,9 +2,9 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [main, master, dev, development]
5
+ branches: [main, master, dev, main-dev]
6
6
  pull_request:
7
- branches: [main, master, dev, development]
7
+ branches: [main, master, dev, main-dev]
8
8
 
9
9
  # 同一分支/PR 的新推送会取消仍在进行的旧运行,节省 CI 时间。
10
10
  concurrency:
@@ -26,6 +26,11 @@ jobs:
26
26
  matrix:
27
27
  os: [ubuntu-latest, windows-latest, macos-latest]
28
28
  python-version: ["3.10", "3.13", "3.14"]
29
+ include:
30
+ - os: ubuntu-latest
31
+ python-version: "3.10"
32
+ - os: windows-latest
33
+ python-version: "3.10"
29
34
  steps:
30
35
  - uses: actions/checkout@v4
31
36
 
@@ -57,9 +62,6 @@ jobs:
57
62
  - name: Install built wheel
58
63
  run: python -m pip install --no-index --find-links dist rsnumpy
59
64
 
60
- - name: Install test dependencies
61
- run: python -m pip install numpy
62
-
63
65
  - name: Run smoke test
64
66
  run: python test/run_test.py
65
67
 
@@ -25,16 +25,6 @@ jobs:
25
25
  steps:
26
26
  - uses: actions/checkout@v4
27
27
 
28
- - name: Cache Rust build artifacts
29
- uses: actions/cache@v4
30
- with:
31
- path: |
32
- ~/.cargo/registry
33
- ~/.cargo/git
34
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
35
- restore-keys: |
36
- ${{ runner.os }}-cargo-
37
-
38
28
  - name: Build wheels (${{ matrix.target }})
39
29
  uses: PyO3/maturin-action@v1
40
30
  with:
@@ -57,16 +47,6 @@ jobs:
57
47
  steps:
58
48
  - uses: actions/checkout@v4
59
49
 
60
- - name: Cache Rust build artifacts
61
- uses: actions/cache@v4
62
- with:
63
- path: |
64
- ~/.cargo/registry
65
- ~/.cargo/git
66
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
67
- restore-keys: |
68
- ${{ runner.os }}-cargo-
69
-
70
50
  - uses: actions/setup-python@v5
71
51
  with:
72
52
  python-version: ${{ matrix.python-version }}
@@ -95,16 +75,6 @@ jobs:
95
75
  steps:
96
76
  - uses: actions/checkout@v4
97
77
 
98
- - name: Cache Rust build artifacts
99
- uses: actions/cache@v4
100
- with:
101
- path: |
102
- ~/.cargo/registry
103
- ~/.cargo/git
104
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
105
- restore-keys: |
106
- ${{ runner.os }}-cargo-
107
-
108
78
  - uses: actions/setup-python@v5
109
79
  with:
110
80
  python-version: ${{ matrix.python-version }}
@@ -39,6 +39,9 @@ push*.sh
39
39
  main*.py
40
40
  debug*.py
41
41
  check*.py
42
+ test2*.py
43
+ map*.jpeg
44
+ *.png
42
45
 
43
46
  # Environment
44
47
  .venv
@@ -61,9 +61,9 @@ dependencies = [
61
61
 
62
62
  [[package]]
63
63
  name = "cc"
64
- version = "1.2.65"
64
+ version = "1.2.66"
65
65
  source = "registry+https://github.com/rust-lang/crates.io-index"
66
- checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
66
+ checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
67
67
  dependencies = [
68
68
  "find-msvc-tools",
69
69
  "jobserver",
@@ -260,18 +260,6 @@ dependencies = [
260
260
  "slab",
261
261
  ]
262
262
 
263
- [[package]]
264
- name = "getrandom"
265
- version = "0.3.4"
266
- source = "registry+https://github.com/rust-lang/crates.io-index"
267
- checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
268
- dependencies = [
269
- "cfg-if",
270
- "libc",
271
- "r-efi 5.3.0",
272
- "wasip2",
273
- ]
274
-
275
263
  [[package]]
276
264
  name = "getrandom"
277
265
  version = "0.4.3"
@@ -281,7 +269,7 @@ dependencies = [
281
269
  "cfg-if",
282
270
  "js-sys",
283
271
  "libc",
284
- "r-efi 6.0.0",
272
+ "r-efi",
285
273
  "rand_core",
286
274
  "wasm-bindgen",
287
275
  ]
@@ -337,11 +325,11 @@ dependencies = [
337
325
 
338
326
  [[package]]
339
327
  name = "jobserver"
340
- version = "0.1.34"
328
+ version = "0.1.35"
341
329
  source = "registry+https://github.com/rust-lang/crates.io-index"
342
- checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
330
+ checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
343
331
  dependencies = [
344
- "getrandom 0.3.4",
332
+ "getrandom",
345
333
  "libc",
346
334
  ]
347
335
 
@@ -382,9 +370,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
382
370
 
383
371
  [[package]]
384
372
  name = "lzma-rust2"
385
- version = "0.16.4"
373
+ version = "0.16.5"
386
374
  source = "registry+https://github.com/rust-lang/crates.io-index"
387
- checksum = "ce716bf1a316f47a280fc76295f6495b5bea4752bca01c3b3885e101b1c23c02"
375
+ checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b"
388
376
  dependencies = [
389
377
  "sha2",
390
378
  ]
@@ -607,12 +595,6 @@ dependencies = [
607
595
  "proc-macro2",
608
596
  ]
609
597
 
610
- [[package]]
611
- name = "r-efi"
612
- version = "5.3.0"
613
- source = "registry+https://github.com/rust-lang/crates.io-index"
614
- checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
615
-
616
598
  [[package]]
617
599
  name = "r-efi"
618
600
  version = "6.0.0"
@@ -626,7 +608,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
626
608
  checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
627
609
  dependencies = [
628
610
  "chacha20",
629
- "getrandom 0.4.3",
611
+ "getrandom",
630
612
  "rand_core",
631
613
  ]
632
614
 
@@ -674,7 +656,7 @@ dependencies = [
674
656
 
675
657
  [[package]]
676
658
  name = "rsnumpy"
677
- version = "1.0.3"
659
+ version = "1.0.6"
678
660
  dependencies = [
679
661
  "ndarray",
680
662
  "num-complex",
@@ -868,15 +850,6 @@ version = "1.0.24"
868
850
  source = "registry+https://github.com/rust-lang/crates.io-index"
869
851
  checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
870
852
 
871
- [[package]]
872
- name = "wasip2"
873
- version = "1.0.4+wasi-0.2.12"
874
- source = "registry+https://github.com/rust-lang/crates.io-index"
875
- checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
876
- dependencies = [
877
- "wit-bindgen",
878
- ]
879
-
880
853
  [[package]]
881
854
  name = "wasm-bindgen"
882
855
  version = "0.2.126"
@@ -922,12 +895,6 @@ dependencies = [
922
895
  "unicode-ident",
923
896
  ]
924
897
 
925
- [[package]]
926
- name = "wit-bindgen"
927
- version = "0.57.1"
928
- source = "registry+https://github.com/rust-lang/crates.io-index"
929
- checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
930
-
931
898
  [[package]]
932
899
  name = "zeroize"
933
900
  version = "1.9.0"
@@ -946,7 +913,7 @@ dependencies = [
946
913
  "crc32fast",
947
914
  "deflate64",
948
915
  "flate2",
949
- "getrandom 0.4.3",
916
+ "getrandom",
950
917
  "hmac",
951
918
  "indexmap",
952
919
  "lzma-rust2",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rsnumpy"
3
- version = "1.0.3"
3
+ version = "1.0.6"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -9,7 +9,7 @@ name = "rsnumpy"
9
9
  crate-type = ["cdylib"]
10
10
 
11
11
  [dependencies]
12
- pyo3 = { version = "0.29.0", features = ["extension-module", "abi3-py38"] }
12
+ pyo3 = { version = "0.29.0", features = ["extension-module"] }
13
13
  ndarray = { version = "0.17.2", features = ["rayon", "serde"] }
14
14
  rayon = "1.12.0"
15
15
  rand = "0.10.2"
@@ -29,9 +29,14 @@ blas = ["ndarray/blas"]
29
29
  opt-level = 3
30
30
  lto = "fat"
31
31
  codegen-units = 1
32
- panic = "abort"
33
- strip = true
32
+ strip = "symbols"
34
33
 
35
- [profile.bench]
36
- opt-level = 3
37
- debug = false
34
+ [profile.dev]
35
+ opt-level = 0
36
+ debug = true
37
+
38
+ [lints.clippy]
39
+ too_many_arguments = "allow"
40
+ type_complexity = "allow"
41
+ cast_precision_loss = "allow"
42
+ cast_sign_loss = "allow"
@@ -1,10 +1,21 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rsnumpy
3
- Version: 1.0.3
4
- Classifier: License :: OSI Approved :: MIT License
3
+ Version: 1.0.6
5
4
  Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Programming Language :: Python :: 3 :: Only
7
+ Classifier: Programming Language :: Python :: 3.10
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
6
12
  Classifier: Programming Language :: Python :: Implementation :: CPython
7
13
  Classifier: Programming Language :: Python :: Implementation :: PyPy
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Topic :: Scientific/Engineering :: Visualization
8
19
  License-File: LICENSE
9
20
  Summary: A Rust-powered NumPy-compatible array library.
10
21
  Author: YJ-Niu
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Build Rust lib into Python wheel using maturin
5
+ # Usage: ./build_wheel.sh [--release|--debug] [--out-dir DIR] [--python PYTHON_EXEC]
6
+ #
7
+ # 跨平台:macOS / Linux 直接运行;Windows 请在 Git Bash 或 WSL 中运行
8
+ # (venv 布局会自动切换为 Scripts/python.exe)。
9
+
10
+ # ========== 从 Cargo.toml 读取元信息 ==========
11
+ NAME=$(grep -m1 '^name = ' Cargo.toml | sed 's/name = "\(.*\)"/\1/')
12
+ VERSION=$(grep -m1 '^version = ' Cargo.toml | sed 's/version = "\(.*\)"/\1/')
13
+
14
+ echo "Project: $NAME, Version: $VERSION"
15
+
16
+ # ========== 同步版本到 pyproject.toml ==========
17
+ sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
18
+ rm -f pyproject.toml.bak
19
+ echo " -> pyproject.toml version updated to $VERSION"
20
+
21
+ # ========== 同步版本到 python/rsplotlib/__init__.py ==========
22
+ sed -i.bak "s/^__version__ = \".*\"/__version__ = \"$VERSION\"/" python/rsplotlib/__init__.py 2>/dev/null || true
23
+ rm -f python/rsplotlib/__init__.py.bak
24
+ echo " -> python/rsplotlib/__init__.py __version__ updated to $VERSION"
25
+
26
+ # ========== 检查 cargo 是否可用(maturin 构建需要) ==========
27
+ if ! command -v cargo >/dev/null 2>&1; then
28
+ echo "Error: 'cargo' not found in PATH." >&2
29
+ echo "Please install Rust and Cargo from https://rustup.rs/" >&2
30
+ echo "After installation, run: source \$HOME/.cargo/env" >&2
31
+ exit 1
32
+ fi
33
+
34
+ # ========== 依据 rust-toolchain.toml 准备工具链与 clippy 组件 ==========
35
+ # 项目用 rust-toolchain.toml 固定了 Rust channel;rustup 会自动据此选择工具链,
36
+ # 但构建前的 clippy 检查依赖 clippy 组件,全新环境未必已安装,这里显式补齐。
37
+ if [[ -f rust-toolchain.toml ]]; then
38
+ RUST_CHANNEL=$(grep -m1 '^channel *= *' rust-toolchain.toml | sed -E 's/.*"([^"]+)".*/\1/')
39
+ if [[ -n "${RUST_CHANNEL:-}" ]]; then
40
+ echo "Pinned Rust toolchain (rust-toolchain.toml): $RUST_CHANNEL"
41
+ if command -v rustup >/dev/null 2>&1; then
42
+ # 幂等:已安装则为空操作;离线且缺失时失败被忽略,交由后续 clippy 步骤明确报错。
43
+ rustup toolchain install "$RUST_CHANNEL" >/dev/null 2>&1 || true
44
+ rustup component add clippy --toolchain "$RUST_CHANNEL" >/dev/null 2>&1 || true
45
+ else
46
+ echo " -> rustup not found; using the cargo/clippy already on PATH." >&2
47
+ fi
48
+ fi
49
+ fi
50
+
51
+ RELEASE=true
52
+ OUT_DIR="wheelhouse"
53
+
54
+ # 如果 wheelhouse 目录存在, 则删除
55
+ if [[ -d "$OUT_DIR" ]]; then
56
+ rm -rf "$OUT_DIR"
57
+ fi
58
+
59
+ # Default python executable; may be overridden by --python or positional arg
60
+ PYTHON_EXEC="python"
61
+ # Flag set when the user explicitly provided a Python executable
62
+ PYTHON_EXEC_SET=""
63
+
64
+ # ========== 平台探测:Windows(Git Bash/MSYS/Cygwin) 的 venv 是 Scripts/ 且解释器带 .exe ==========
65
+ case "$(uname -s 2>/dev/null || echo unknown)" in
66
+ MINGW*|MSYS*|CYGWIN*) VENV_BIN=".venv/Scripts"; VENV_PY=".venv/Scripts/python.exe" ;;
67
+ *) VENV_BIN=".venv/bin"; VENV_PY=".venv/bin/python" ;;
68
+ esac
69
+
70
+ # 若存在本地 venv 则激活(让其中的 maturin 进入 PATH);不存在也不报错。
71
+ if [[ -f "$VENV_BIN/activate" ]]; then
72
+ # shellcheck source=/dev/null
73
+ source "$VENV_BIN/activate"
74
+ fi
75
+ while [[ $# -gt 0 ]]; do
76
+ case $1 in
77
+ --release) RELEASE=true; shift ;;
78
+ --debug) RELEASE=false; shift ;;
79
+ --out-dir) OUT_DIR="$2"; shift 2 ;;
80
+ --python) PYTHON_EXEC="$2"; PYTHON_EXEC_SET=1; shift 2 ;;
81
+ *) PYTHON_EXEC="$1"; PYTHON_EXEC_SET=1; shift ;;
82
+ esac
83
+ done
84
+
85
+ # If a local .venv exists and user didn't explicitly set Python, prefer it
86
+ if [[ -f "$VENV_PY" && -z "$PYTHON_EXEC_SET" ]]; then
87
+ PYTHON_EXEC="$VENV_PY"
88
+ fi
89
+
90
+ # Ensure chosen Python executable exists or is runnable
91
+ if ! command -v "$PYTHON_EXEC" >/dev/null 2>&1 && [[ ! -x "$PYTHON_EXEC" && ! -f "$PYTHON_EXEC" ]]; then
92
+ echo "Error: Python executable '$PYTHON_EXEC' not found or not executable." >&2
93
+ exit 1
94
+ fi
95
+
96
+ # Prefer a maturin on PATH, otherwise try running maturin via the chosen Python
97
+ if command -v maturin >/dev/null 2>&1; then
98
+ MATURIN_MODE="path"
99
+ elif "$PYTHON_EXEC" -c "import maturin" >/dev/null 2>&1; then
100
+ MATURIN_MODE="python-module"
101
+ else
102
+ echo "Error: maturin not found. Install it in your chosen Python (e.g. '$PYTHON_EXEC -m pip install maturin')." >&2
103
+ exit 1
104
+ fi
105
+
106
+ # ========== 构建前的 Fmt 静态检查(-check:任何错误都当作错误) ==========
107
+ echo "Running fmt checks (cargo fmt --all -- --check) ..."
108
+ if ! cargo fmt --all -- --check; then
109
+ echo "Error: fmt checks failed. Fix the warnings above before building." >&2
110
+ exit 1
111
+ fi
112
+ echo " -> fmt checks passed."
113
+
114
+ # ========== 构建前的 Clippy 静态检查(-D warnings:任何告警都当作错误) ==========
115
+ echo "Running clippy checks (cargo clippy --all-targets -- -D warnings) ..."
116
+ if ! cargo clippy --all-targets -- -D warnings; then
117
+ echo "Error: clippy checks failed. Fix the warnings above before building." >&2
118
+ exit 1
119
+ fi
120
+ echo " -> clippy checks passed."
121
+
122
+ BUILD_ARGS=()
123
+ if $RELEASE; then BUILD_ARGS+=(--release); else BUILD_ARGS+=(--debug); fi
124
+
125
+ mkdir -p "$OUT_DIR"
126
+ echo "Building wheel into $OUT_DIR using $PYTHON_EXEC (release=$RELEASE)"
127
+ if [[ "$MATURIN_MODE" == "path" ]]; then
128
+ maturin build "${BUILD_ARGS[@]}" -o "$OUT_DIR" -i "$PYTHON_EXEC"
129
+ else
130
+ # run maturin as a module under the chosen Python
131
+ "$PYTHON_EXEC" -m maturin build "${BUILD_ARGS[@]}" -o "$OUT_DIR" -i "$PYTHON_EXEC"
132
+ fi
133
+
134
+ # Locate the built wheel and install it into the local venv
135
+ WHEEL=$(ls -t "$OUT_DIR"/*.whl 2>/dev/null | head -n1 || true)
136
+ if [[ -n "$WHEEL" && -f "$WHEEL" ]]; then
137
+ echo "Wheel built: $WHEEL"
138
+ if [[ -f "$VENV_PY" ]]; then
139
+ echo "Installing into .venv ..."
140
+ # --no-deps:本地 wheel 已含全部绑定代码,无需重新解析依赖。
141
+ # 优先用 uv(显式 --python 指向本 venv,避免依赖是否已 activate);无 uv 时回退到 pip。
142
+ if command -v uv >/dev/null 2>&1; then
143
+ uv pip install --python "$VENV_PY" --reinstall --no-deps "$WHEEL"
144
+ else
145
+ "$VENV_PY" -m pip install --force-reinstall --no-deps "$WHEEL"
146
+ fi
147
+ echo "Installed into .venv successfully."
148
+ else
149
+ echo "Warning: .venv not found; skip install. Wheels available in $OUT_DIR" >&2
150
+ fi
151
+ else
152
+ echo "Warning: no .whl file found in $OUT_DIR; skip install." >&2
153
+ fi
154
+
155
+ echo "Done."
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "rsnumpy"
7
- version = "1.0.3"
7
+ version = "1.0.6"
8
8
  requires-python = ">=3.10"
9
9
  description = "A Rust-powered NumPy-compatible array library."
10
10
  readme = "README.md"
@@ -13,12 +13,25 @@ authors = [
13
13
  { name = "YJ-Niu" },
14
14
  ]
15
15
  classifiers = [
16
- "License :: OSI Approved :: MIT License",
17
16
  "Programming Language :: Rust",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
18
24
  "Programming Language :: Python :: Implementation :: CPython",
19
25
  "Programming Language :: Python :: Implementation :: PyPy",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Operating System :: MacOS",
28
+ "Operating System :: Microsoft :: Windows",
29
+ "Operating System :: POSIX :: Linux",
30
+ "Topic :: Scientific/Engineering :: Visualization",
20
31
  ]
21
32
 
33
+ dependencies = []
34
+
22
35
  [tool.maturin]
23
36
  module-name = "rsnumpy._core"
24
37
  python-source = "python"
@@ -12,7 +12,6 @@ Examples:
12
12
  2.0
13
13
  """
14
14
 
15
- import builtins
16
15
  import rsnumpy._core as _core
17
16
  from rsnumpy._core import ndarray_iter as NdArrayIter
18
17
  # ========== 子模块导入和函数挂载 ==========
@@ -29,7 +28,7 @@ from .random import random_module as _random_module
29
28
  from . import char as _char_module
30
29
  from . import matlib as _matlib_module
31
30
 
32
- __version__ = "1.0.3"
31
+ __version__ = "1.0.5"
33
32
 
34
33
  class ArrayFlags:
35
34
  """数组内存布局信息,与 NumPy 的 np.ndarray.flags 兼容。"""
@@ -119,6 +118,9 @@ class ndarray:
119
118
  if _raw_data is not None:
120
119
  # 原始数据存储:用一个虚拟 Rust 数组占位
121
120
  self._array = _core.zeros((len(data),) if isinstance(data, (list, tuple)) else (1,))
121
+ elif not isinstance(data, (list, tuple)) and hasattr(data, '__array_interface__'):
122
+ # 实现数组接口协议的对象(如 PIL 图像)→ 由 Rust 底层解码缓冲区
123
+ self._array, _dtype = _from_array_interface(data)
122
124
  else:
123
125
  if isinstance(data, tuple):
124
126
  data = list(data)
@@ -126,6 +128,11 @@ class ndarray:
126
128
  # 不规则列表(子列表长度不同),存储为原始数据
127
129
  self._raw_data = data
128
130
  self._array = _core.zeros((len(data),))
131
+ elif _has_string(data):
132
+ # 字符串数据 → 存储为原始 Python 数据
133
+ self._raw_data = list(data) if isinstance(data, (list, tuple)) else [data]
134
+ self._array = _core.zeros((len(self._raw_data),))
135
+ _dtype = "string_"
129
136
  elif _has_complex(data):
130
137
  # 包含复数 → 存储为 _complex_data
131
138
  flat = _flatten_data(data)
@@ -274,7 +281,7 @@ class ndarray:
274
281
  # 展开省略号(...),补充完整切片以匹配数组维度
275
282
  if isinstance(key, tuple):
276
283
  new_key = []
277
- ellipsis_count = builtins.sum(1 for k in key if k is Ellipsis)
284
+ ellipsis_count = _core.isum([1 for k in key if k is Ellipsis])
278
285
  if ellipsis_count > 0:
279
286
  non_ellipsis = [k for k in key if k is not Ellipsis]
280
287
  fill = self.ndim - len(non_ellipsis)
@@ -311,7 +318,7 @@ class ndarray:
311
318
  key = (_ndarray_to_index_list(key)
312
319
  if hasattr(key, '_array') else key,)
313
320
  if isinstance(key, tuple):
314
- _core.setitem_multi(self._array, key, list(self.shape), value)
321
+ _core.setitem_multi(self._array, key, list(self.shape), _setitem_value(value))
315
322
  else:
316
323
  self._array[key] = value
317
324
 
@@ -916,7 +923,7 @@ class _float64:
916
923
  def _format_float_repr_1d(values):
917
924
  """格式化 1D float64 数组的 repr,带逗号和对齐(匹配 NumPy)。"""
918
925
  fmt = [format_float_scalar(v) for v in values]
919
- max_w = builtins.max(len(f) for f in fmt) if fmt else 0
926
+ max_w = _core.imax([len(f) for f in fmt]) if fmt else 0
920
927
  parts = [f.rjust(max_w) for f in fmt]
921
928
  return "[" + ", ".join(parts) + "]"
922
929
 
@@ -1198,6 +1205,57 @@ def _flatten_data(data):
1198
1205
  return flat
1199
1206
 
1200
1207
 
1208
+ def _has_string(data):
1209
+ """检查(展平后的)数据中是否包含字符串。"""
1210
+ flat, _ = _flatten_check(data)
1211
+ return any(isinstance(v, str) for v in flat)
1212
+
1213
+
1214
+ def _setitem_value(value):
1215
+ """规范化赋值右值供 Rust setitem_multi 使用:
1216
+ 标量原样返回;ndarray 或嵌套列表展平为 C 序浮点列表(逐元素赋值)。"""
1217
+ if _is_ndarray(value):
1218
+ return [float(v) for v in _flatten_data(value._array.tolist())]
1219
+ if value.__class__.__name__ == 'ndarray' and hasattr(value, 'tolist'):
1220
+ return [float(v) for v in _flatten_data(value.tolist())]
1221
+ if isinstance(value, (list, tuple)):
1222
+ return [float(v) for v in _flatten_data(value)]
1223
+ return value
1224
+
1225
+
1226
+ # 数组接口协议 typestr 的 (kind, itemsize) → rsnumpy dtype 名称
1227
+ _ARRAY_INTERFACE_DTYPE = {
1228
+ ('b', 1): 'bool',
1229
+ ('u', 1): 'uint8', ('i', 1): 'int8',
1230
+ ('u', 2): 'uint16', ('i', 2): 'int16',
1231
+ ('u', 4): 'uint32', ('i', 4): 'int32',
1232
+ ('u', 8): 'uint64', ('i', 8): 'int64',
1233
+ ('f', 4): 'float32', ('f', 8): 'float64',
1234
+ }
1235
+
1236
+
1237
+ def _from_array_interface(obj):
1238
+ """将实现数组接口协议的对象(如 PIL 图像)解码为 (Rust 数组, dtype)。
1239
+
1240
+ 缓冲区的字节解码由 Rust 底层 `_core.from_buffer_typed` 完成。
1241
+ """
1242
+ ai = obj.__array_interface__
1243
+ shape = tuple(ai['shape'])
1244
+ typestr = ai['typestr']
1245
+ data = ai['data']
1246
+ if isinstance(data, tuple):
1247
+ # (指针, 只读标志) 形式,无法直接读取 → 退回到 tobytes()
1248
+ data = obj.tobytes()
1249
+ elif not isinstance(data, (bytes, bytearray)):
1250
+ data = bytes(data)
1251
+ kind, itemsize = typestr[1], int(typestr[2:])
1252
+ dtype = _ARRAY_INTERFACE_DTYPE.get((kind, itemsize))
1253
+ if dtype is None:
1254
+ raise TypeError(f"Unsupported array interface type: {typestr}")
1255
+ arr = _core.from_buffer_typed(bytes(data), typestr, list(shape))
1256
+ return arr, dtype
1257
+
1258
+
1201
1259
  def _is_rectangular(data):
1202
1260
  """检查嵌套列表是否为矩形(所有子列表长度相同)。"""
1203
1261
  if not isinstance(data, (list, tuple)):
@@ -1843,12 +1901,12 @@ def nditer(a, order='C', op_flags=None, flags=None):
1843
1901
  arrays = [ndarray(x) for x in a]
1844
1902
  # 计算广播形状:每个维度取最大值
1845
1903
  all_shapes = [x.shape for x in arrays]
1846
- max_ndim = builtins.max(len(s) for s in all_shapes)
1904
+ max_ndim = _core.imax([len(s) for s in all_shapes])
1847
1905
  padded = []
1848
1906
  for s in all_shapes:
1849
1907
  pad = [1] * (max_ndim - len(s)) + list(s)
1850
1908
  padded.append(pad)
1851
- bcast_shape = tuple(builtins.max(p[i] for p in padded) for i in range(max_ndim))
1909
+ bcast_shape = tuple(_core.imax([p[i] for p in padded]) for i in range(max_ndim))
1852
1910
  # 广播每个数组到统一形状
1853
1911
  bcast_arrays = []
1854
1912
  for arr in arrays:
@@ -1909,7 +1967,7 @@ def nditer(a, order='C', op_flags=None, flags=None):
1909
1967
  def _iter_f_order(dim, coords):
1910
1968
  if dim < 0:
1911
1969
  # coords 是反向顺序 [last_dim, ..., first_dim],需反转
1912
- idx = builtins.sum(c * s for c, s in zip(reversed(coords), c_strides))
1970
+ idx = _core.isum([c * s for c, s in zip(reversed(coords), c_strides)])
1913
1971
  if 0 <= idx < size:
1914
1972
  val = flat_arr[idx].item()
1915
1973
  yield _to_val(val, idx)
@@ -1960,7 +2018,7 @@ def iscomplex(x):
1960
2018
 
1961
2019
  def _broadcast_shape(*shapes):
1962
2020
  """计算多个数组的广播形状。"""
1963
- max_ndim = builtins.max(len(s) for s in shapes)
2021
+ max_ndim = _core.imax([len(s) for s in shapes])
1964
2022
  result = []
1965
2023
  for i in range(max_ndim):
1966
2024
  dims = set()
@@ -0,0 +1,2 @@
1
+ [toolchain]
2
+ channel = "stable"