py-alpha-lib 0.3.1__tar.gz → 0.3.3__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.
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/Cargo.lock +38 -28
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/Cargo.toml +3 -3
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/PKG-INFO +2 -2
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/rank.rs +46 -13
- py_alpha_lib-0.3.3/alpha-algo/src/join.rs +408 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/lib.rs +2 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/Cargo.toml +1 -1
- py_alpha_lib-0.3.3/py-binding/tests/rank.py +54 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/pyproject.toml +1 -1
- py_alpha_lib-0.3.1/py-binding/tests/rank.py +0 -19
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/LICENSE +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/README.md +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/Cargo.toml +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/build.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/alpha.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/backfill.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/beta.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/context.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/cross.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/drawdown.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/elementwise.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/ema.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/entropy.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/error.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/extremum.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/group.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/ma.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/misc.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/mod.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/moments.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/neutralize.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/quantile.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/returns.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/scan.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/series.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/sharpe.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/skip_nan_window.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/slope.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/split.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/stats.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/stddev.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/sum.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/alpha-algo/src/algo/zscore.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/LICENSE +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/README.md +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/alpha/__init__.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/alpha/algo/__init__.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/alpha/algo/algo.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/alpha/algo/algo_gen.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/alpha/algo.md +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/alpha/context.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/alpha/lang/__init__.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/alpha/lang/__main__.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/alpha/lang/alpha.lark +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/alpha/lang/parser.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/alpha/lang/to_python.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/python/conftest.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/python/tests/test_grammar.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/python/tests/test_rank.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/python/tests/test_ref_v.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/python/tests/test_split.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/python/tests/test_talib.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/python/tests/test_to_python.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/src/algo_bindings.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/src/lib.rs +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/tests/usage.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/py-binding/tests/verify_sumif.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/python/alpha/__init__.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/python/alpha/algo/__init__.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/python/alpha/algo/algo.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/python/alpha/algo/algo_gen.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/python/alpha/algo.md +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/python/alpha/context.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/python/alpha/lang/__init__.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/python/alpha/lang/__main__.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/python/alpha/lang/alpha.lark +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/python/alpha/lang/parser.py +0 -0
- {py_alpha_lib-0.3.1 → py_alpha_lib-0.3.3}/python/alpha/lang/to_python.py +0 -0
|
@@ -4,7 +4,7 @@ version = 4
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "alpha"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.3"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"alpha-algo",
|
|
10
10
|
"anyhow",
|
|
@@ -19,7 +19,7 @@ dependencies = [
|
|
|
19
19
|
|
|
20
20
|
[[package]]
|
|
21
21
|
name = "alpha-algo"
|
|
22
|
-
version = "0.3.
|
|
22
|
+
version = "0.3.3"
|
|
23
23
|
dependencies = [
|
|
24
24
|
"anyhow",
|
|
25
25
|
"mlua",
|
|
@@ -52,7 +52,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
|
|
52
52
|
dependencies = [
|
|
53
53
|
"proc-macro2",
|
|
54
54
|
"quote",
|
|
55
|
-
"syn",
|
|
55
|
+
"syn 2.0.114",
|
|
56
56
|
]
|
|
57
57
|
|
|
58
58
|
[[package]]
|
|
@@ -238,7 +238,7 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
|
238
238
|
|
|
239
239
|
[[package]]
|
|
240
240
|
name = "js-alpha-lib"
|
|
241
|
-
version = "0.3.
|
|
241
|
+
version = "0.3.3"
|
|
242
242
|
dependencies = [
|
|
243
243
|
"alpha-algo",
|
|
244
244
|
"console_error_panic_hook",
|
|
@@ -288,7 +288,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|
|
288
288
|
|
|
289
289
|
[[package]]
|
|
290
290
|
name = "lua-alpha-lib"
|
|
291
|
-
version = "0.3.
|
|
291
|
+
version = "0.3.3"
|
|
292
292
|
dependencies = [
|
|
293
293
|
"alpha-algo",
|
|
294
294
|
"anyhow",
|
|
@@ -345,7 +345,7 @@ dependencies = [
|
|
|
345
345
|
|
|
346
346
|
[[package]]
|
|
347
347
|
name = "mlang"
|
|
348
|
-
version = "0.3.
|
|
348
|
+
version = "0.3.3"
|
|
349
349
|
dependencies = [
|
|
350
350
|
"alpha-algo",
|
|
351
351
|
"anyhow",
|
|
@@ -438,9 +438,9 @@ dependencies = [
|
|
|
438
438
|
|
|
439
439
|
[[package]]
|
|
440
440
|
name = "numpy"
|
|
441
|
-
version = "0.
|
|
441
|
+
version = "0.30.0"
|
|
442
442
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
443
|
-
checksum = "
|
|
443
|
+
checksum = "aa0b9111761885449e894981fa8051dcf69e0bd114aecbd4bcdd830b6be4ed92"
|
|
444
444
|
dependencies = [
|
|
445
445
|
"libc",
|
|
446
446
|
"ndarray",
|
|
@@ -517,7 +517,7 @@ dependencies = [
|
|
|
517
517
|
"pest_meta",
|
|
518
518
|
"proc-macro2",
|
|
519
519
|
"quote",
|
|
520
|
-
"syn",
|
|
520
|
+
"syn 2.0.114",
|
|
521
521
|
]
|
|
522
522
|
|
|
523
523
|
[[package]]
|
|
@@ -568,9 +568,9 @@ dependencies = [
|
|
|
568
568
|
|
|
569
569
|
[[package]]
|
|
570
570
|
name = "pyo3"
|
|
571
|
-
version = "0.
|
|
571
|
+
version = "0.30.1"
|
|
572
572
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
573
|
-
checksum = "
|
|
573
|
+
checksum = "229ca13b8a15bb0d1487b81ab975bfcd97513416499a8312011b6036e42e0de4"
|
|
574
574
|
dependencies = [
|
|
575
575
|
"libc",
|
|
576
576
|
"once_cell",
|
|
@@ -582,18 +582,18 @@ dependencies = [
|
|
|
582
582
|
|
|
583
583
|
[[package]]
|
|
584
584
|
name = "pyo3-build-config"
|
|
585
|
-
version = "0.
|
|
585
|
+
version = "0.30.1"
|
|
586
586
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
587
|
-
checksum = "
|
|
587
|
+
checksum = "70130e2b6dd1ee589a8788026a429561ca5c40fa7a2fa7914c2282eddae24700"
|
|
588
588
|
dependencies = [
|
|
589
589
|
"target-lexicon",
|
|
590
590
|
]
|
|
591
591
|
|
|
592
592
|
[[package]]
|
|
593
593
|
name = "pyo3-ffi"
|
|
594
|
-
version = "0.
|
|
594
|
+
version = "0.30.1"
|
|
595
595
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
596
|
-
checksum = "
|
|
596
|
+
checksum = "5e7ff73c555c286d212d4ce7e5fbda845a973c4f61bd5f160f5f2c3c2a04d1c4"
|
|
597
597
|
dependencies = [
|
|
598
598
|
"libc",
|
|
599
599
|
"pyo3-build-config",
|
|
@@ -601,9 +601,9 @@ dependencies = [
|
|
|
601
601
|
|
|
602
602
|
[[package]]
|
|
603
603
|
name = "pyo3-log"
|
|
604
|
-
version = "0.13.
|
|
604
|
+
version = "0.13.5"
|
|
605
605
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
606
|
-
checksum = "
|
|
606
|
+
checksum = "b91aabf1c0d674a2e3b0b55daab6cef1308ce6f0ad3697ae56f4f68db54c98a1"
|
|
607
607
|
dependencies = [
|
|
608
608
|
"arc-swap",
|
|
609
609
|
"log",
|
|
@@ -612,27 +612,26 @@ dependencies = [
|
|
|
612
612
|
|
|
613
613
|
[[package]]
|
|
614
614
|
name = "pyo3-macros"
|
|
615
|
-
version = "0.
|
|
615
|
+
version = "0.30.1"
|
|
616
616
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
617
|
-
checksum = "
|
|
617
|
+
checksum = "c46d2e5862c7bb8207e3b639f713dda3c499933444784154c11d013c5a691b60"
|
|
618
618
|
dependencies = [
|
|
619
619
|
"proc-macro2",
|
|
620
620
|
"pyo3-macros-backend",
|
|
621
621
|
"quote",
|
|
622
|
-
"syn",
|
|
622
|
+
"syn 3.0.9",
|
|
623
623
|
]
|
|
624
624
|
|
|
625
625
|
[[package]]
|
|
626
626
|
name = "pyo3-macros-backend"
|
|
627
|
-
version = "0.
|
|
627
|
+
version = "0.30.1"
|
|
628
628
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
629
|
-
checksum = "
|
|
629
|
+
checksum = "38b89f066f2ae72a2aef52d1537ca2fcd104440ebfadee06e97aa90738a25b9b"
|
|
630
630
|
dependencies = [
|
|
631
631
|
"heck",
|
|
632
632
|
"proc-macro2",
|
|
633
|
-
"pyo3-build-config",
|
|
634
633
|
"quote",
|
|
635
|
-
"syn",
|
|
634
|
+
"syn 3.0.9",
|
|
636
635
|
]
|
|
637
636
|
|
|
638
637
|
[[package]]
|
|
@@ -733,7 +732,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
|
733
732
|
dependencies = [
|
|
734
733
|
"proc-macro2",
|
|
735
734
|
"quote",
|
|
736
|
-
"syn",
|
|
735
|
+
"syn 2.0.114",
|
|
737
736
|
]
|
|
738
737
|
|
|
739
738
|
[[package]]
|
|
@@ -789,6 +788,17 @@ dependencies = [
|
|
|
789
788
|
"unicode-ident",
|
|
790
789
|
]
|
|
791
790
|
|
|
791
|
+
[[package]]
|
|
792
|
+
name = "syn"
|
|
793
|
+
version = "3.0.9"
|
|
794
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
795
|
+
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
|
796
|
+
dependencies = [
|
|
797
|
+
"proc-macro2",
|
|
798
|
+
"quote",
|
|
799
|
+
"unicode-ident",
|
|
800
|
+
]
|
|
801
|
+
|
|
792
802
|
[[package]]
|
|
793
803
|
name = "target-lexicon"
|
|
794
804
|
version = "0.13.4"
|
|
@@ -812,7 +822,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
|
|
812
822
|
dependencies = [
|
|
813
823
|
"proc-macro2",
|
|
814
824
|
"quote",
|
|
815
|
-
"syn",
|
|
825
|
+
"syn 2.0.114",
|
|
816
826
|
]
|
|
817
827
|
|
|
818
828
|
[[package]]
|
|
@@ -891,7 +901,7 @@ dependencies = [
|
|
|
891
901
|
"bumpalo",
|
|
892
902
|
"proc-macro2",
|
|
893
903
|
"quote",
|
|
894
|
-
"syn",
|
|
904
|
+
"syn 2.0.114",
|
|
895
905
|
"wasm-bindgen-shared",
|
|
896
906
|
]
|
|
897
907
|
|
|
@@ -934,7 +944,7 @@ checksum = "e925354648d2a4d1bf205412e36d520a800280622eef4719678d268e5d40e978"
|
|
|
934
944
|
dependencies = [
|
|
935
945
|
"proc-macro2",
|
|
936
946
|
"quote",
|
|
937
|
-
"syn",
|
|
947
|
+
"syn 2.0.114",
|
|
938
948
|
]
|
|
939
949
|
|
|
940
950
|
[[package]]
|
|
@@ -3,14 +3,14 @@ members = ["alpha-algo", "py-binding"]
|
|
|
3
3
|
resolver = "3"
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.3.
|
|
6
|
+
version = "0.3.3"
|
|
7
7
|
authors = ["ElseJJ"]
|
|
8
8
|
edition = "2024"
|
|
9
9
|
|
|
10
10
|
[workspace.dependencies]
|
|
11
11
|
log = "0.4"
|
|
12
12
|
num-traits = "0.2"
|
|
13
|
-
numpy = "0.
|
|
13
|
+
numpy = "0.30"
|
|
14
14
|
pyo3-log = "0.13"
|
|
15
15
|
rayon = "1.11"
|
|
16
16
|
thiserror = "2"
|
|
@@ -18,7 +18,7 @@ anyhow = "1"
|
|
|
18
18
|
mlua = { version = "0.11", features = ["lua55", "vendored", "anyhow"] }
|
|
19
19
|
|
|
20
20
|
[workspace.dependencies.pyo3]
|
|
21
|
-
version = "0.
|
|
21
|
+
version = "0.30"
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
[profile.wasm]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: py-alpha-lib
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
@@ -9,7 +9,7 @@ License-File: LICENSE
|
|
|
9
9
|
Summary: Alpha Library: A high-performance rolling window calculation library implemented in Rust with Python bindings. Used for financial data analysis and factor research.
|
|
10
10
|
Keywords: financial data analysis,factor research,technical indicator calculation
|
|
11
11
|
Author: ElseJJ
|
|
12
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
13
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
14
14
|
Project-URL: repository, https://github.com/msd-rs/py-alpha-lib
|
|
15
15
|
|
|
@@ -478,25 +478,40 @@ mod tests {
|
|
|
478
478
|
#[test]
|
|
479
479
|
fn test_ta_rank_with_nan() {
|
|
480
480
|
// groups=3, matrix [3,NaN; 1,5; 4,6]
|
|
481
|
-
let
|
|
481
|
+
let n = 32;
|
|
482
|
+
let g = 32;
|
|
483
|
+
let mut input = vec![];
|
|
484
|
+
(0..n).for_each(|i| {
|
|
485
|
+
if i % 2 == 0 {
|
|
486
|
+
(0..g).for_each(|_j| input.push(f64::NAN));
|
|
487
|
+
} else if i % 3 == 0 {
|
|
488
|
+
(0..g).for_each(|_j| input.push(f64::INFINITY));
|
|
489
|
+
} else if i % 5 == 0 {
|
|
490
|
+
(0..g).for_each(|_j| input.push(f64::NEG_INFINITY));
|
|
491
|
+
} else {
|
|
492
|
+
(0..g).for_each(|_j| input.push(i as f64));
|
|
493
|
+
}
|
|
494
|
+
});
|
|
482
495
|
let mut r = vec![0.0; input.len()];
|
|
483
|
-
let ctx = Context::new(0,
|
|
496
|
+
let ctx = Context::new(0, g, 0);
|
|
484
497
|
|
|
485
498
|
ta_cc_rank(&ctx, &mut r, &input).unwrap();
|
|
486
499
|
// j=0: values [3,1,4], all valid, sorted [1,3,4], ranks [2,1,3]
|
|
487
500
|
// j=1: values [NaN,5,6], 2 valid, sorted [5,6,NaN]
|
|
488
501
|
// 5 -> rank 1/2=0.5, 6 -> rank 2/2=1.0, NaN -> NaN
|
|
489
|
-
assert_vec_eq_nan(
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
);
|
|
502
|
+
// assert_vec_eq_nan(
|
|
503
|
+
// &r,
|
|
504
|
+
// &vec![
|
|
505
|
+
// 2.0 / 3.0,
|
|
506
|
+
// f64::NAN,
|
|
507
|
+
// 1.0 / 3.0,
|
|
508
|
+
// 1.0 / 2.0,
|
|
509
|
+
// 3.0 / 3.0,
|
|
510
|
+
// 2.0 / 2.0,
|
|
511
|
+
// ],
|
|
512
|
+
// );
|
|
513
|
+
println!("{:?}", input);
|
|
514
|
+
println!("{:?}", r);
|
|
500
515
|
}
|
|
501
516
|
|
|
502
517
|
#[test]
|
|
@@ -531,6 +546,24 @@ mod tests {
|
|
|
531
546
|
-1.0,
|
|
532
547
|
f64::MAX,
|
|
533
548
|
f64::MIN,
|
|
549
|
+
f64::NAN,
|
|
550
|
+
1.0,
|
|
551
|
+
f64::NAN,
|
|
552
|
+
f64::NEG_INFINITY,
|
|
553
|
+
f64::INFINITY,
|
|
554
|
+
0.0,
|
|
555
|
+
-1.0,
|
|
556
|
+
f64::MAX,
|
|
557
|
+
f64::MIN,
|
|
558
|
+
f64::NAN,
|
|
559
|
+
1.0,
|
|
560
|
+
f64::NAN,
|
|
561
|
+
f64::NEG_INFINITY,
|
|
562
|
+
f64::INFINITY,
|
|
563
|
+
0.0,
|
|
564
|
+
-1.0,
|
|
565
|
+
f64::MAX,
|
|
566
|
+
f64::MIN,
|
|
534
567
|
]
|
|
535
568
|
.into_iter()
|
|
536
569
|
.map(OrderedFloat::from)
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
// Copyright 2026 MSD-RS Project LiJia
|
|
2
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
3
|
+
|
|
4
|
+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
|
5
|
+
pub enum FillMethod {
|
|
6
|
+
#[default]
|
|
7
|
+
Zero,
|
|
8
|
+
Nan,
|
|
9
|
+
Backward,
|
|
10
|
+
Forward,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl From<i32> for FillMethod {
|
|
14
|
+
fn from(value: i32) -> Self {
|
|
15
|
+
match value {
|
|
16
|
+
1 => Self::Nan,
|
|
17
|
+
2 => Self::Backward,
|
|
18
|
+
3 => Self::Forward,
|
|
19
|
+
_ => Self::Zero,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl From<FillMethod> for i32 {
|
|
25
|
+
fn from(value: FillMethod) -> i32 {
|
|
26
|
+
match value {
|
|
27
|
+
FillMethod::Zero => 0,
|
|
28
|
+
FillMethod::Nan => 1,
|
|
29
|
+
FillMethod::Backward => 2,
|
|
30
|
+
FillMethod::Forward => 3,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl From<&str> for FillMethod {
|
|
36
|
+
fn from(value: &str) -> Self {
|
|
37
|
+
match value {
|
|
38
|
+
"nan" => Self::Nan,
|
|
39
|
+
"backward" => Self::Backward,
|
|
40
|
+
"forward" => Self::Forward,
|
|
41
|
+
_ => Self::Zero,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl From<String> for FillMethod {
|
|
47
|
+
fn from(value: String) -> Self {
|
|
48
|
+
Self::from(value.as_str())
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl From<&String> for FillMethod {
|
|
53
|
+
fn from(value: &String) -> Self {
|
|
54
|
+
Self::from(value.as_str())
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
impl From<FillMethod> for &'static str {
|
|
59
|
+
fn from(value: FillMethod) -> Self {
|
|
60
|
+
match value {
|
|
61
|
+
FillMethod::Zero => "zero",
|
|
62
|
+
FillMethod::Nan => "nan",
|
|
63
|
+
FillMethod::Backward => "backward",
|
|
64
|
+
FillMethod::Forward => "forward",
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Join `left` and `right` according to `method`, and return in `result` the indices of `right` that match `left`. Both `left` and `right` are already sorted in ascending order.
|
|
70
|
+
pub fn joined_index<T>(left: &[T], right: &[T], method: FillMethod, result: &mut Vec<usize>)
|
|
71
|
+
where
|
|
72
|
+
T: PartialOrd + PartialEq,
|
|
73
|
+
{
|
|
74
|
+
if result.len() != left.len() {
|
|
75
|
+
result.resize(left.len(), usize::MAX);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if left.is_empty() {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if right.is_empty() {
|
|
83
|
+
result.fill(usize::MAX);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
match method {
|
|
88
|
+
FillMethod::Backward => joined_index_backward(left, right, result),
|
|
89
|
+
FillMethod::Forward => joined_index_forward(left, right, result),
|
|
90
|
+
FillMethod::Zero | FillMethod::Nan => joined_index_exact(left, right, result),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#[inline]
|
|
95
|
+
fn joined_index_backward<T: PartialOrd>(left: &[T], right: &[T], result: &mut [usize]) {
|
|
96
|
+
let right_len = right.len();
|
|
97
|
+
let mut curr: Option<usize> = None;
|
|
98
|
+
|
|
99
|
+
for (x, out) in left.iter().zip(result.iter_mut()) {
|
|
100
|
+
if x.partial_cmp(x).is_none() {
|
|
101
|
+
*out = usize::MAX;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let mut cand = match curr {
|
|
106
|
+
Some(c) if right[c] <= *x => c,
|
|
107
|
+
_ => {
|
|
108
|
+
if right[0] > *x {
|
|
109
|
+
*out = usize::MAX;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
0
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if cand + 1 < right_len && right[cand + 1] <= *x {
|
|
117
|
+
cand += 1;
|
|
118
|
+
if cand + 1 < right_len && right[cand + 1] <= *x {
|
|
119
|
+
cand += 1;
|
|
120
|
+
let mut step = 2usize;
|
|
121
|
+
while cand + step < right_len && right[cand + step] <= *x {
|
|
122
|
+
cand += step;
|
|
123
|
+
step = step.saturating_mul(2);
|
|
124
|
+
}
|
|
125
|
+
let upper = (cand + step).min(right_len);
|
|
126
|
+
let offset = right[cand + 1..upper].partition_point(|r| r <= x);
|
|
127
|
+
cand += offset;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
*out = cand;
|
|
132
|
+
curr = Some(cand);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#[inline]
|
|
137
|
+
fn joined_index_exact<T: PartialOrd + PartialEq>(left: &[T], right: &[T], result: &mut [usize]) {
|
|
138
|
+
let right_len = right.len();
|
|
139
|
+
let mut curr: Option<usize> = None;
|
|
140
|
+
|
|
141
|
+
for (x, out) in left.iter().zip(result.iter_mut()) {
|
|
142
|
+
if x.partial_cmp(x).is_none() {
|
|
143
|
+
*out = usize::MAX;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let mut cand = match curr {
|
|
148
|
+
Some(c) if right[c] <= *x => c,
|
|
149
|
+
_ => {
|
|
150
|
+
if right[0] > *x {
|
|
151
|
+
*out = usize::MAX;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
0
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if cand + 1 < right_len && right[cand + 1] <= *x {
|
|
159
|
+
cand += 1;
|
|
160
|
+
if cand + 1 < right_len && right[cand + 1] <= *x {
|
|
161
|
+
cand += 1;
|
|
162
|
+
let mut step = 2usize;
|
|
163
|
+
while cand + step < right_len && right[cand + step] <= *x {
|
|
164
|
+
cand += step;
|
|
165
|
+
step = step.saturating_mul(2);
|
|
166
|
+
}
|
|
167
|
+
let upper = (cand + step).min(right_len);
|
|
168
|
+
let offset = right[cand + 1..upper].partition_point(|r| r <= x);
|
|
169
|
+
cand += offset;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
curr = Some(cand);
|
|
174
|
+
if right[cand] == *x {
|
|
175
|
+
*out = cand;
|
|
176
|
+
} else {
|
|
177
|
+
*out = usize::MAX;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[inline]
|
|
183
|
+
fn joined_index_forward<T: PartialOrd>(left: &[T], right: &[T], result: &mut [usize]) {
|
|
184
|
+
let right_len = right.len();
|
|
185
|
+
let mut curr = 0usize;
|
|
186
|
+
|
|
187
|
+
for (x, out) in left.iter().zip(result.iter_mut()) {
|
|
188
|
+
if x.partial_cmp(x).is_none() {
|
|
189
|
+
*out = usize::MAX;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if curr >= right_len {
|
|
194
|
+
*out = usize::MAX;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if right[curr] >= *x {
|
|
199
|
+
*out = curr;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if curr + 1 >= right_len {
|
|
204
|
+
curr = right_len;
|
|
205
|
+
*out = usize::MAX;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if right[curr + 1] >= *x {
|
|
209
|
+
curr += 1;
|
|
210
|
+
*out = curr;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if curr + 2 >= right_len {
|
|
215
|
+
curr = right_len;
|
|
216
|
+
*out = usize::MAX;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if right[curr + 2] >= *x {
|
|
220
|
+
curr += 2;
|
|
221
|
+
*out = curr;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let mut step = 4usize;
|
|
226
|
+
curr += 2;
|
|
227
|
+
while curr + step < right_len && right[curr + step] < *x {
|
|
228
|
+
curr += step;
|
|
229
|
+
step = step.saturating_mul(2);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let upper = (curr + step).min(right_len);
|
|
233
|
+
let offset = right[curr + 1..upper].partition_point(|r| r < x);
|
|
234
|
+
let found = curr + 1 + offset;
|
|
235
|
+
|
|
236
|
+
if found < right_len && right[found] >= *x {
|
|
237
|
+
curr = found;
|
|
238
|
+
*out = found;
|
|
239
|
+
} else {
|
|
240
|
+
curr = right_len;
|
|
241
|
+
*out = usize::MAX;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#[cfg(test)]
|
|
247
|
+
mod tests {
|
|
248
|
+
use super::*;
|
|
249
|
+
|
|
250
|
+
#[test]
|
|
251
|
+
fn test_joined_index_empty() {
|
|
252
|
+
let mut res = Vec::new();
|
|
253
|
+
joined_index::<f64>(&[], &[1.0, 2.0], FillMethod::Backward, &mut res);
|
|
254
|
+
assert_eq!(res, Vec::<usize>::new());
|
|
255
|
+
|
|
256
|
+
joined_index::<f64>(&[1.0, 2.0], &[], FillMethod::Backward, &mut res);
|
|
257
|
+
assert_eq!(res, vec![usize::MAX, usize::MAX]);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#[test]
|
|
261
|
+
fn test_joined_index_exact() {
|
|
262
|
+
let left = vec![1.0, 2.0, 2.5, 3.0, 4.0];
|
|
263
|
+
let right = vec![1.0, 2.0, 3.0];
|
|
264
|
+
let mut res = Vec::new();
|
|
265
|
+
joined_index(&left, &right, FillMethod::Zero, &mut res);
|
|
266
|
+
assert_eq!(res, vec![0, 1, usize::MAX, 2, usize::MAX]);
|
|
267
|
+
|
|
268
|
+
joined_index(&left, &right, FillMethod::Nan, &mut res);
|
|
269
|
+
assert_eq!(res, vec![0, 1, usize::MAX, 2, usize::MAX]);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#[test]
|
|
273
|
+
fn test_joined_index_backward() {
|
|
274
|
+
let left = vec![0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5];
|
|
275
|
+
let right = vec![1.0, 2.0, 3.0];
|
|
276
|
+
let mut res = Vec::new();
|
|
277
|
+
joined_index(&left, &right, FillMethod::Backward, &mut res);
|
|
278
|
+
assert_eq!(res, vec![usize::MAX, 0, 0, 1, 1, 2, 2]);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#[test]
|
|
282
|
+
fn test_joined_index_forward() {
|
|
283
|
+
let left = vec![0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5];
|
|
284
|
+
let right = vec![1.0, 2.0, 3.0];
|
|
285
|
+
let mut res = Vec::new();
|
|
286
|
+
joined_index(&left, &right, FillMethod::Forward, &mut res);
|
|
287
|
+
assert_eq!(res, vec![0, 0, 1, 1, 2, 2, usize::MAX]);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
#[test]
|
|
291
|
+
fn test_joined_index_duplicates() {
|
|
292
|
+
let left = vec![2.0];
|
|
293
|
+
let right = vec![1.0, 2.0, 2.0, 2.0, 3.0];
|
|
294
|
+
let mut res = Vec::new();
|
|
295
|
+
|
|
296
|
+
// Backward chooses the last element <= 2.0 -> index 3
|
|
297
|
+
joined_index(&left, &right, FillMethod::Backward, &mut res);
|
|
298
|
+
assert_eq!(res, vec![3]);
|
|
299
|
+
|
|
300
|
+
// Forward chooses the first element >= 2.0 -> index 1
|
|
301
|
+
joined_index(&left, &right, FillMethod::Forward, &mut res);
|
|
302
|
+
assert_eq!(res, vec![1]);
|
|
303
|
+
|
|
304
|
+
// Exact matches the last element == 2.0 -> index 3
|
|
305
|
+
joined_index(&left, &right, FillMethod::Zero, &mut res);
|
|
306
|
+
assert_eq!(res, vec![3]);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
#[test]
|
|
310
|
+
fn test_joined_index_nan() {
|
|
311
|
+
let left = vec![1.0, f64::NAN, 2.0];
|
|
312
|
+
let right = vec![1.0, 2.0];
|
|
313
|
+
let mut res = Vec::new();
|
|
314
|
+
|
|
315
|
+
joined_index(&left, &right, FillMethod::Backward, &mut res);
|
|
316
|
+
assert_eq!(res, vec![0, usize::MAX, 1]);
|
|
317
|
+
|
|
318
|
+
joined_index(&left, &right, FillMethod::Forward, &mut res);
|
|
319
|
+
assert_eq!(res, vec![0, usize::MAX, 1]);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
#[test]
|
|
323
|
+
fn test_joined_index_stress_equivalence() {
|
|
324
|
+
// Stress test comparing against naive binary search
|
|
325
|
+
let left: Vec<i64> = (0..5000).map(|x| x * 3).collect();
|
|
326
|
+
let right: Vec<i64> = (0..10000).map(|x| x * 2).collect();
|
|
327
|
+
|
|
328
|
+
let mut res_backward = Vec::new();
|
|
329
|
+
joined_index(&left, &right, FillMethod::Backward, &mut res_backward);
|
|
330
|
+
|
|
331
|
+
for (i, &x) in left.iter().enumerate() {
|
|
332
|
+
let idx = right.partition_point(|&r| r <= x);
|
|
333
|
+
let expected = if idx > 0 { idx - 1 } else { usize::MAX };
|
|
334
|
+
assert_eq!(res_backward[i], expected, "mismatch at left[{}]={}", i, x);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let mut res_forward = Vec::new();
|
|
338
|
+
joined_index(&left, &right, FillMethod::Forward, &mut res_forward);
|
|
339
|
+
|
|
340
|
+
for (i, &x) in left.iter().enumerate() {
|
|
341
|
+
let idx = right.partition_point(|&r| r < x);
|
|
342
|
+
let expected = if idx < right.len() { idx } else { usize::MAX };
|
|
343
|
+
assert_eq!(res_forward[i], expected, "mismatch at left[{}]={}", i, x);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let mut res_exact = Vec::new();
|
|
347
|
+
joined_index(&left, &right, FillMethod::Zero, &mut res_exact);
|
|
348
|
+
|
|
349
|
+
for (i, &x) in left.iter().enumerate() {
|
|
350
|
+
let idx = right.partition_point(|&r| r <= x);
|
|
351
|
+
let expected = if idx > 0 && right[idx - 1] == x {
|
|
352
|
+
idx - 1
|
|
353
|
+
} else {
|
|
354
|
+
usize::MAX
|
|
355
|
+
};
|
|
356
|
+
assert_eq!(res_exact[i], expected, "mismatch at left[{}]={}", i, x);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
#[test]
|
|
361
|
+
fn test_joined_index_large_random_comparison() {
|
|
362
|
+
use std::time::Instant;
|
|
363
|
+
|
|
364
|
+
// Pseudo-random generator without external deps (LCG)
|
|
365
|
+
let mut rng = 123456789u64;
|
|
366
|
+
let mut rand = || {
|
|
367
|
+
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
|
|
368
|
+
(rng >> 33) as i64
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
let n = 50_000;
|
|
372
|
+
let m = 100_000;
|
|
373
|
+
|
|
374
|
+
let mut left = Vec::with_capacity(n);
|
|
375
|
+
let mut cur = 0i64;
|
|
376
|
+
for _ in 0..n {
|
|
377
|
+
cur += (rand().abs() % 10) + 1;
|
|
378
|
+
left.push(cur);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let mut right = Vec::with_capacity(m);
|
|
382
|
+
cur = 0;
|
|
383
|
+
for _ in 0..m {
|
|
384
|
+
cur += (rand().abs() % 5) + 1;
|
|
385
|
+
right.push(cur);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let t0 = Instant::now();
|
|
389
|
+
let mut res_backward = Vec::new();
|
|
390
|
+
joined_index(&left, &right, FillMethod::Backward, &mut res_backward);
|
|
391
|
+
let elapsed = t0.elapsed();
|
|
392
|
+
println!("joined_index backward 50k x 100k elapsed: {:?}", elapsed);
|
|
393
|
+
|
|
394
|
+
for (i, &x) in left.iter().enumerate() {
|
|
395
|
+
let idx = right.partition_point(|&r| r <= x);
|
|
396
|
+
let expected = if idx > 0 { idx - 1 } else { usize::MAX };
|
|
397
|
+
assert_eq!(res_backward[i], expected);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let mut res_forward = Vec::new();
|
|
401
|
+
joined_index(&left, &right, FillMethod::Forward, &mut res_forward);
|
|
402
|
+
for (i, &x) in left.iter().enumerate() {
|
|
403
|
+
let idx = right.partition_point(|&r| r < x);
|
|
404
|
+
let expected = if idx < right.len() { idx } else { usize::MAX };
|
|
405
|
+
assert_eq!(res_forward[i], expected);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -12,7 +12,7 @@ crate-type = ["cdylib", "rlib"]
|
|
|
12
12
|
doctest = false
|
|
13
13
|
|
|
14
14
|
[dependencies]
|
|
15
|
-
pyo3 = { workspace = true, features = ["abi3"] }
|
|
15
|
+
pyo3 = { workspace = true, features = ["abi3-py312", "abi3t-py315"] }
|
|
16
16
|
pyo3-log.workspace = true
|
|
17
17
|
thiserror.workspace = true
|
|
18
18
|
numpy.workspace = true
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
import alpha
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
df = pd.DataFrame(
|
|
7
|
+
data={
|
|
8
|
+
"Animal": ["cat", "penguin", "dog", "spider", "snake"],
|
|
9
|
+
"Number_legs": [4, 2, 4, 8, 0],
|
|
10
|
+
}
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
def test_log_delta_rank_chain_panics():
|
|
14
|
+
"""模拟修复前 alpha_002 的 LOG→DELTA(2)→RANK 链路即复现线上 panic"""
|
|
15
|
+
rng = np.random.default_rng(17)
|
|
16
|
+
n = 32
|
|
17
|
+
# t=4 目标截面(已验证稳定触发 Rust total-order 校验 panic 的 recipe)
|
|
18
|
+
target = rng.normal(size=n)
|
|
19
|
+
pos = rng.permutation(n)
|
|
20
|
+
nn = max(1, int(n * rng.uniform(0.1, 0.3)))
|
|
21
|
+
target[pos[:nn]] = np.nan
|
|
22
|
+
target[pos[nn % n]] = np.inf
|
|
23
|
+
target[pos[(nn + 1) % n]] = -np.inf
|
|
24
|
+
# 反推成交量,使 delta(log(vol),2) 在 t=4 的截面 == target
|
|
25
|
+
vol = np.full((n, 6), 1000.0)
|
|
26
|
+
nan_m = np.isnan(target)
|
|
27
|
+
posinf_m = np.isposinf(target)
|
|
28
|
+
neginf_m = np.isneginf(target)
|
|
29
|
+
vol[nan_m, 2] = np.nan # 停牌 → delta=NaN
|
|
30
|
+
vol[posinf_m, 2] = 0.0 # LOG(0)=-inf → delta=+inf
|
|
31
|
+
vol[neginf_m, 4] = 0.0 # delta=-inf
|
|
32
|
+
fin_m = ~(nan_m | posinf_m | neginf_m)
|
|
33
|
+
vol[fin_m, 4] = 1000.0 * np.exp(target[fin_m]) # delta=target
|
|
34
|
+
# 算子链路:LOG → DELTA(2) → 截面 RANK(与 ctx.LOG/ctx.DELTA 同语义)
|
|
35
|
+
with np.errstate(divide='ignore', invalid='ignore'):
|
|
36
|
+
log_vol = np.log(vol)
|
|
37
|
+
delta = log_vol[:, 2:] - log_vol[:, :-2]
|
|
38
|
+
# t=4 截面(NaN+±inf 混排);列切片为非连续视图,须连续化(绑定层提取要求 contiguous,
|
|
39
|
+
# 否则抛 ValueError "failed to get slice")
|
|
40
|
+
cross = np.ascontiguousarray(delta[:, 2])
|
|
41
|
+
alpha.set_ctx(groups=n)
|
|
42
|
+
print(len(cross), n)
|
|
43
|
+
r = np.empty_like(cross)
|
|
44
|
+
print(alpha.CC_RANK(cross))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
alpha.set_ctx(groups=5)
|
|
48
|
+
print(alpha.RANK(df["Number_legs"].to_numpy().astype(np.float64), 1))
|
|
49
|
+
print(df["Number_legs"].rank(pct=True).to_numpy().astype(np.float64))
|
|
50
|
+
|
|
51
|
+
alpha.set_ctx(groups=3)
|
|
52
|
+
print(alpha.CC_RANK(np.array([3.0, np.nan, 1.0, np.inf, 4.0, -np.inf]*50)))
|
|
53
|
+
|
|
54
|
+
test_log_delta_rank_chain_panics()
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
|
-
import numpy as np
|
|
3
|
-
import alpha
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
df = pd.DataFrame(
|
|
7
|
-
data={
|
|
8
|
-
"Animal": ["cat", "penguin", "dog", "spider", "snake"],
|
|
9
|
-
"Number_legs": [4, 2, 4, 8, 0],
|
|
10
|
-
}
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
alpha.set_ctx(groups=5)
|
|
15
|
-
print(alpha.RANK(df["Number_legs"].to_numpy().astype(np.float64), 1))
|
|
16
|
-
print(df["Number_legs"].rank(pct=True).to_numpy().astype(np.float64))
|
|
17
|
-
|
|
18
|
-
alpha.set_ctx(groups=3)
|
|
19
|
-
print(alpha.CC_RANK(np.array([3.0, np.nan, 1.0, np.inf, 4.0, -np.inf])))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|