multivariate-probit 0.3.0__tar.gz → 0.3.2__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.
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/CITATION.cff +1 -1
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/PKG-INFO +5 -3
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/README.md +4 -2
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/bench.py +2 -2
- multivariate_probit-0.3.2/benchmarks/results.csv +65 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/api.md +3 -3
- multivariate_probit-0.3.2/docs/benchmarks.md +95 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/fiml.md +21 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/ghk.md +5 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/pyproject.toml +1 -1
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/src/multivariate_probit/__init__.py +1 -1
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/src/multivariate_probit/model.py +11 -2
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/src/multivariate_probit/orthant/__init__.py +76 -18
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/tests/test_evaluators.py +53 -0
- multivariate_probit-0.3.0/benchmarks/results.csv +0 -55
- multivariate_probit-0.3.0/docs/benchmarks.md +0 -90
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/.github/ISSUE_TEMPLATE/use-case.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/.gitignore +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/LICENSE +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/benchmarks.ipynb +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_d12_n3000_100_rho0.4_s0.npz +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_d16_n3000_100_rho0.4_s0.npz +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_d3_n3000_100_rho0.4_s0.npz +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_d5_n3000_100_rho0.4_s0.npz +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_d8_n3000_100_rho0.4_s0.npz +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_pairwise_d12_n3000_100_s0.npz +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_pairwise_d16_n3000_100_s0.npz +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_pairwise_d3_n3000_100_s0.npz +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_pairwise_d5_n3000_100_s0.npz +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_pairwise_d8_n3000_100_s0.npz +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/ifm.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/implementation.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/limitations.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/README.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/comparators.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/index-correlation-shortcut.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/log-score-evaluator.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/margin-gaps.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/mulan-multilabel.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/multilabel-benchmarks.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/sparse-label-transfer.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/uci-credit.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/use-cases/README.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/use-cases/claims-fast-track.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/use-cases/content-moderation.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/use-cases/cyber.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/use-cases/fraud-vendor-fusion.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/use-cases/hcc-codes.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/use-cases/property-imagery.md +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/hatch_build.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/src/multivariate_probit/_corr.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/src/multivariate_probit/_mvn.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/src/multivariate_probit/_remote.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/src/multivariate_probit/ifm.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/src/multivariate_probit/inner.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/src/multivariate_probit/linear.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/src/multivariate_probit/results.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/tests/conftest.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/tests/test_calibration.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/tests/test_conditional.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/tests/test_contract.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/tests/test_correlation_projection.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/tests/test_linear.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/tests/test_modal_fit.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/tests/test_rf.py +0 -0
- {multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/tests/test_xgboost.py +0 -0
|
@@ -7,7 +7,7 @@ authors:
|
|
|
7
7
|
given-names: Mark
|
|
8
8
|
orcid: "https://orcid.org/0009-0008-7193-1263"
|
|
9
9
|
version: 0.3.0
|
|
10
|
-
date-released: 2026-10-
|
|
10
|
+
date-released: 2026-10-06
|
|
11
11
|
license: MIT
|
|
12
12
|
repository-code: "https://github.com/sign-of-fourier/multivariate-probit"
|
|
13
13
|
url: "https://quantecarlo.com/multivariate-probit"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: multivariate-probit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Joint probabilities for correlated binary outcomes: P(any), P(all), patterns and conditionals that agree. Any classifier per outcome; scikit-learn-style API.
|
|
5
5
|
Project-URL: Homepage, https://quantecarlo.com/multivariate-probit
|
|
6
6
|
Project-URL: Source, https://github.com/sign-of-fourier/multivariate-probit
|
|
@@ -213,8 +213,10 @@ The backend is chosen with `evaluator=`:
|
|
|
213
213
|
of magnitude faster than SciPy, at roughly the accuracy of 100-draw GHK
|
|
214
214
|
simulation ([studies/ghk.md](https://github.com/sign-of-fourier/multivariate-probit/blob/main/docs/studies/ghk.md)). It ships for CPython 3.11
|
|
215
215
|
and 3.12 on x86-64 Linux (`pip install multivariate-probit[orthant]`); without
|
|
216
|
-
a key it runs d ≤ 3 at `resolution="low"`.
|
|
217
|
-
|
|
216
|
+
a key it runs d ≤ 3 at `resolution="low"`. The key is read from `$ORTHANT_KEY` or
|
|
217
|
+
`~/.orthant/key` at import, or passed in code with
|
|
218
|
+
`from multivariate_probit import orthant; orthant.activate(key)` (on Spark, call it inside
|
|
219
|
+
the function the workers run). Key: [quantecarlo.com/orthant_key](https://quantecarlo.com/orthant_key).
|
|
218
220
|
|
|
219
221
|
Benchmarks against other tools: [docs/benchmarks.md](https://github.com/sign-of-fourier/multivariate-probit/blob/main/docs/benchmarks.md).
|
|
220
222
|
|
|
@@ -149,8 +149,10 @@ The backend is chosen with `evaluator=`:
|
|
|
149
149
|
of magnitude faster than SciPy, at roughly the accuracy of 100-draw GHK
|
|
150
150
|
simulation ([studies/ghk.md](https://github.com/sign-of-fourier/multivariate-probit/blob/main/docs/studies/ghk.md)). It ships for CPython 3.11
|
|
151
151
|
and 3.12 on x86-64 Linux (`pip install multivariate-probit[orthant]`); without
|
|
152
|
-
a key it runs d ≤ 3 at `resolution="low"`.
|
|
153
|
-
|
|
152
|
+
a key it runs d ≤ 3 at `resolution="low"`. The key is read from `$ORTHANT_KEY` or
|
|
153
|
+
`~/.orthant/key` at import, or passed in code with
|
|
154
|
+
`from multivariate_probit import orthant; orthant.activate(key)` (on Spark, call it inside
|
|
155
|
+
the function the workers run). Key: [quantecarlo.com/orthant_key](https://quantecarlo.com/orthant_key).
|
|
154
156
|
|
|
155
157
|
Benchmarks against other tools: [docs/benchmarks.md](https://github.com/sign-of-fourier/multivariate-probit/blob/main/docs/benchmarks.md).
|
|
156
158
|
|
|
@@ -104,8 +104,8 @@ def run_cell(d, n_train=3000, n_test=100, rho=0.4, seed=0, sigma="equi"):
|
|
|
104
104
|
if d <= 6:
|
|
105
105
|
res["mvp quadrature"] = timed(lambda: model.set_params(evaluator="quadrature").joint_proba(Xte, Yte))
|
|
106
106
|
if orthant is not None and (ORTHANT_KEYED or d <= 3):
|
|
107
|
-
r
|
|
108
|
-
|
|
107
|
+
for r in (("high", "highest") if ORTHANT_KEYED else ("low",)):
|
|
108
|
+
res[f"mvp orthant ({r})"] = timed(lambda: model.set_params(evaluator="orthant", resolution=r).joint_proba(Xte, Yte))
|
|
109
109
|
res["SciPy default"] = timed(lambda: model.set_params(evaluator="scipy").joint_proba(Xte, Yte))
|
|
110
110
|
if mvncd_batch is not None:
|
|
111
111
|
st = np.moveaxis(stack, 2, 0)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
sigma,d,N,method,median_err,max_err,s_per_row
|
|
2
|
+
equi,3,100,mvp quadrature,1.3373382605444406e-05,5.564767189014663e-05,7.751391967758536e-05
|
|
3
|
+
equi,3,100,mvp orthant (high),5.190348623980261e-05,0.00019774969627306405,1.484813867136836e-05
|
|
4
|
+
equi,3,100,mvp orthant (highest),1.999845194528005e-06,7.989768001565034e-06,4.311794182285666e-05
|
|
5
|
+
equi,3,100,SciPy default,2.039637077871914e-06,1.1415786741186817e-05,0.005793280370999128
|
|
6
|
+
equi,3,100,pybhatlib ovus,0.00011229346488104974,0.0008524556947959311,8.469653083011508e-05
|
|
7
|
+
equi,3,100,statsmodels,0.028443441360488153,0.09919722660040897,6.613903678953648e-07
|
|
8
|
+
equi,3,100,reference,0.0,0.0,0.2536748156798421
|
|
9
|
+
equi,5,100,mvp quadrature,2.0314513939401896e-05,0.00011324180994054522,0.04333823672030121
|
|
10
|
+
equi,5,100,mvp orthant (high),8.080162918654848e-05,0.00030384074452138865,2.3601381108164788e-05
|
|
11
|
+
equi,5,100,mvp orthant (highest),3.4247200694226265e-06,2.3859122737224236e-05,7.270404836162925e-05
|
|
12
|
+
equi,5,100,SciPy default,1.3427915335623153e-06,7.990427117937626e-06,0.010302326420787721
|
|
13
|
+
equi,5,100,pybhatlib ovus,0.00039917378471633624,0.0019016346577496457,0.00011997280875220894
|
|
14
|
+
equi,5,100,statsmodels,0.024622620503874038,0.12870241315304662,5.677388980984688e-07
|
|
15
|
+
equi,5,100,reference,0.0,0.0,0.5339089114402304
|
|
16
|
+
equi,8,100,mvp orthant (high),7.013787736394328e-05,0.00045342350585386715,4.317081067711115e-05
|
|
17
|
+
equi,8,100,mvp orthant (highest),4.605751738051556e-06,2.9230892634346528e-05,9.955347049981356e-05
|
|
18
|
+
equi,8,100,SciPy default,1.0030036643121978e-06,8.134689687355756e-06,0.01642337770899758
|
|
19
|
+
equi,8,100,pybhatlib ovus,0.0003370945286850363,0.0023083975100191556,0.0001771104708313942
|
|
20
|
+
equi,8,100,statsmodels,0.014453125416131472,0.11538392212916126,9.872694499790668e-07
|
|
21
|
+
equi,8,100,reference,0.0,0.0,1.4400106923800196
|
|
22
|
+
equi,12,100,mvp orthant (high),6.245144142705012e-05,0.0007179989696481925,6.305043119937181e-05
|
|
23
|
+
equi,12,100,mvp orthant (highest),5.63448417025339e-06,4.668168500868153e-05,0.00014906150056049228
|
|
24
|
+
equi,12,100,SciPy default,3.440844550360589e-07,7.96331593577726e-06,0.02377823827089742
|
|
25
|
+
equi,12,100,pybhatlib ovus,0.0002047092197251764,0.0035788660943063266,0.0002432926092296839
|
|
26
|
+
equi,12,100,statsmodels,0.006303004648829415,0.07762653255283464,1.5006307512521743e-06
|
|
27
|
+
equi,12,100,reference,0.0,0.0,1.6423227695701643
|
|
28
|
+
equi,16,100,mvp orthant (high),1.630594965073808e-05,0.0006243813016577615,9.883297141641379e-05
|
|
29
|
+
equi,16,100,mvp orthant (highest),1.1074220600440065e-06,2.9310608489277468e-05,0.00022130653960630297
|
|
30
|
+
equi,16,100,SciPy default,6.853848620851782e-08,4.3920697129634645e-06,0.03263484545052051
|
|
31
|
+
equi,16,100,pybhatlib ovus,1.6800734753088424e-05,0.0011253768512156778,0.00031500173965469
|
|
32
|
+
equi,16,100,statsmodels,0.0014514737962008938,0.05355898580397629,1.4994502998888493e-06
|
|
33
|
+
equi,16,100,reference,0.0,0.0,1.1454083026899025
|
|
34
|
+
pairwise,3,100,mvp quadrature,4.975128970532983e-06,8.276622284841828e-05,7.341289892792702e-05
|
|
35
|
+
pairwise,3,100,mvp orthant (high),0.0006363036604397354,0.002893273763736187,2.0136469975113868e-05
|
|
36
|
+
pairwise,3,100,mvp orthant (highest),0.00011323696696274865,0.0007099285566469216,4.4253640808165074e-05
|
|
37
|
+
pairwise,3,100,SciPy default,1.3494787358958593e-06,2.586364471635072e-05,0.0073384556197561325
|
|
38
|
+
pairwise,3,100,pybhatlib ovus,0.00037654161082817383,0.004713394298326956,8.070134790614248e-05
|
|
39
|
+
pairwise,3,100,statsmodels,0.037060543222106224,0.15997644750429899,4.6383123844861987e-07
|
|
40
|
+
pairwise,3,100,reference,0.0,0.0,0.24052267621998907
|
|
41
|
+
pairwise,5,100,mvp quadrature,5.607947987679163e-06,6.266772635254991e-05,0.04593952419003472
|
|
42
|
+
pairwise,5,100,mvp orthant (high),0.0060661393002539865,0.03370856471106998,2.4112039245665073e-05
|
|
43
|
+
pairwise,5,100,mvp orthant (highest),0.003141591472459862,0.0325453719435822,6.538510089740157e-05
|
|
44
|
+
pairwise,5,100,SciPy default,1.2512113251161167e-06,2.1201235567813725e-05,0.14286554857157172
|
|
45
|
+
pairwise,5,100,pybhatlib ovus,0.0014663128072703105,0.02764332982515838,0.00012095558922737837
|
|
46
|
+
pairwise,5,100,statsmodels,0.052292238612919734,0.2549403651447862,5.610100924968719e-07
|
|
47
|
+
pairwise,5,100,reference,0.0,0.0,1.2560263323498657
|
|
48
|
+
pairwise,8,100,mvp orthant (high),0.0047540425543466,0.03786870554320598,3.998839994892478e-05
|
|
49
|
+
pairwise,8,100,mvp orthant (highest),0.0021531855423506108,0.013414597153998009,9.109209990128875e-05
|
|
50
|
+
pairwise,8,100,SciPy default,2.2860621589921443e-06,3.321167501391109e-05,1.132777205838356
|
|
51
|
+
pairwise,8,100,pybhatlib ovus,0.0015355099588129813,0.010313908094296734,0.00017519862158223986
|
|
52
|
+
pairwise,8,100,statsmodels,0.024908203618945243,0.1426044668035415,8.142902515828609e-07
|
|
53
|
+
pairwise,8,100,reference,0.0,0.0,4.434727118879964
|
|
54
|
+
pairwise,12,100,mvp orthant (high),0.003428055959417961,0.04309079684926198,6.395480129867793e-05
|
|
55
|
+
pairwise,12,100,mvp orthant (highest),0.002445889327158528,0.02153141541464762,0.00015708803897723555
|
|
56
|
+
pairwise,12,100,SciPy default,1.9157011284364617e-06,5.195613406111521e-05,1.5738827282516286
|
|
57
|
+
pairwise,12,100,pybhatlib ovus,0.0007431598443556342,0.01388904489935694,0.0002463792893104255
|
|
58
|
+
pairwise,12,100,statsmodels,0.014263734107914516,0.1854686635860365,1.2842798605561255e-06
|
|
59
|
+
pairwise,12,100,reference,0.0,0.0,9.518902401560045
|
|
60
|
+
pairwise,16,100,mvp orthant (high),0.0037733872849075424,0.04512852480876324,8.360587060451507e-05
|
|
61
|
+
pairwise,16,100,mvp orthant (highest),0.00293222915103126,0.045234964595115376,0.00021089559188112618
|
|
62
|
+
pairwise,16,100,SciPy default,1.7024296601283496e-06,1.0376357636343592e-05,1.7738397101382726
|
|
63
|
+
pairwise,16,100,pybhatlib ovus,0.0009786447220386783,0.015062755675490355,0.0003229634207673371
|
|
64
|
+
pairwise,16,100,statsmodels,0.010651175036494289,0.18479811475521019,1.8178089521825314e-06
|
|
65
|
+
pairwise,16,100,reference,0.0,0.0,19.51646036452992
|
|
@@ -36,7 +36,7 @@ MultivariateProbit(
|
|
|
36
36
|
| `project_correlation` | `True` | Project a pairwise estimate onto the nearest positive-definite correlation matrix. No effect when `dependence="joint"`. |
|
|
37
37
|
| `random_state` | `None` | Controls the cross-fitting split and `sample`. |
|
|
38
38
|
| `evaluator` | `"quadrature"` | Backend for the orthant probabilities behind `dependence="joint"` and every joint query: `"quadrature"`, `"scipy"` or `"orthant"`. See [Evaluators](#evaluators). |
|
|
39
|
-
| `resolution` | `"high"` | Passed to `evaluator="orthant"`; ignored otherwise. |
|
|
39
|
+
| `resolution` | `"high"` | Passed to `evaluator="orthant"`; ignored otherwise. `"high"`, `"highest"` (most accurate, about 2–3× the time of `"high"`: median error under 0.00005 on an equicorrelated Σ against up to 0.0001 at `"high"`, and 0.0001–0.0031 on a near-singular Σ against 0.0006–0.0061; see [benchmarks.md](benchmarks.md)), `"smooth"` (the cost of `"highest"`, continuous in the parameters with no threshold where the method changes, for gradient-based fitting; not uniformly more accurate than `"high"`) or `"low"` (cheapest). |
|
|
40
40
|
| `fitter` | `"ifm"` | `"ifm"`: the local two-stage fit. `"modal"`: full maximum likelihood on a hosted GPU service ([fiml.md](fiml.md)) — `inner="linear"` only, `inner_params` may set only `alpha`; `dependence`, `cv`, `optimizer` and `project_correlation` do not apply. Uploads X, Y and `sample_weight`. An int `random_state` seeds its Sobol draws (default seed 0). |
|
|
41
41
|
| `n_draws` | `512` | Sobol draws per row for `fitter="modal"`; ignored otherwise. |
|
|
42
42
|
|
|
@@ -77,7 +77,7 @@ queries on a pairwise fit still use the chosen backend.
|
|
|
77
77
|
| --- | --- | --- |
|
|
78
78
|
| `"quadrature"` | Genz's recursive conditioning with a fixed Gauss-Legendre rule of order `n_quad` (see [implementation.md](implementation.md)). | Deterministic and accurate. Cost grows as `n_quad ** (d - 2)`, so it becomes impractical well before d = 10. |
|
|
79
79
|
| `"scipy"` | `scipy.stats.multivariate_normal.cdf`, one row at a time. | Reaches any d, but is slow per row and randomised: a `dependence="joint"` fit is not exactly reproducible, and its noisy objective can stall the simplex. |
|
|
80
|
-
| `"orthant"` | An optional compiled package bundled as `multivariate_probit.orthant`. Install with `pip install multivariate-probit[orthant]`. | Built for CPython 3.11 and 3.12 on x86-64 Linux only; elsewhere it raises `ImportError`. Without a key it accepts d ≤ 3 at `resolution="low"` only, and raises `ValueError` outside that. A key is read from `$ORTHANT_KEY` or `~/.orthant/key`; see https://quantecarlo.com/orthant_key. Accuracy and speed against GHK and SciPy: [studies/ghk.md](studies/ghk.md). |
|
|
80
|
+
| `"orthant"` | An optional compiled package bundled as `multivariate_probit.orthant`. Install with `pip install multivariate-probit[orthant]`. | Built for CPython 3.11 and 3.12 on x86-64 Linux only; elsewhere it raises `ImportError`. Without a key it accepts d ≤ 3 at `resolution="low"` only, and raises `ValueError` outside that. A key is read from `$ORTHANT_KEY` or `~/.orthant/key` at import, or passed at any time with `orthant.activate(key)` (returns `"paid"`; a wrong key raises `ValueError`). Each process needs its own key: on Spark, call `activate` inside the function the workers run, or set the variable in the cluster environment; see https://quantecarlo.com/orthant_key. Accuracy and speed against GHK and SciPy: [studies/ghk.md](studies/ghk.md). |
|
|
81
81
|
|
|
82
82
|
For test sets too large for one machine, the same evaluator runs as a hosted
|
|
83
83
|
GPU service, `quantecarlo.orthant_cdf`: about a second per million rows at
|
|
@@ -94,7 +94,7 @@ the
|
|
|
94
94
|
| `correlation_` | (d, d) | The fitted Σ. |
|
|
95
95
|
| `eta_` | (n, d) | The (cross-fitted) latent indices stage two was fitted on; for `fitter="modal"`, the in-sample indices of the fitted model. |
|
|
96
96
|
| `calibration_` | (d, 2) | Intercept and slope of a probit of each outcome on its own fitted index — the calibration slope. See the note below. |
|
|
97
|
-
| `nll_` | float | Negative log-likelihood at the end of the dependence fit. Joint and pairwise fits optimise different objectives, so the values are not comparable across settings. |
|
|
97
|
+
| `nll_` | float | Negative log-likelihood at the end of the dependence fit. Joint and pairwise fits optimise different objectives, so the values are not comparable across settings. For `fitter="modal"`, the simulated joint negative log-likelihood of all margins and Σ. |
|
|
98
98
|
| `optimize_result_` | OptimizeResult or None | The SciPy result for `dependence="joint"`. `None` for `fitter="modal"`. |
|
|
99
99
|
| `stderr_`, `stderr_robust_` | dict | `fitter="modal"` only. Standard errors keyed `"coef"` (d, n_features), `"intercept"` (d,), `"correlation"` (d, d); inverse-Hessian and sandwich. |
|
|
100
100
|
| `cov_params_`, `cov_params_robust_` | (k, k) | `fitter="modal"` only. The matching covariance matrices, ordered as `param_names_`. |
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Benchmarks against other tools
|
|
2
|
+
|
|
3
|
+
**Task.** Score `P(Y = y | x)`, the probability of the pattern each held-out
|
|
4
|
+
row actually has, from one fitted model. Synthetic data: 6 features, linear
|
|
5
|
+
margins, 3,000 training rows, 100 scored rows per d. The model is
|
|
6
|
+
`MultivariateProbit(inner="linear", dependence="pairwise", random_state=0)`.
|
|
7
|
+
Every column scores that same fitted model (η̂, Σ̂), except statsmodels.
|
|
8
|
+
|
|
9
|
+
There are two shapes of true Σ:
|
|
10
|
+
|
|
11
|
+
- **Equicorrelated**, ρ = 0.4: the easy case.
|
|
12
|
+
- **Pairwise-shaped**: a rank-2 factor plus noise, projected to the nearest
|
|
13
|
+
correlation matrix (smallest eigenvalue about 1e-8), the same construction
|
|
14
|
+
as [studies/ghk.md](studies/ghk.md). A fitted pairwise Σ̂ lands on that PSD
|
|
15
|
+
boundary, which is hard for every approximation.
|
|
16
|
+
|
|
17
|
+
**Reference.** SciPy `multivariate_normal.cdf` with `abseps=1e-7`,
|
|
18
|
+
`releps=1e-6`, one row at a time. Error is the absolute difference from it in
|
|
19
|
+
probability: median over rows, maximum in parentheses. Time is wall-clock per
|
|
20
|
+
row after one warm-up call, on a two-core x86-64 Linux machine.
|
|
21
|
+
|
|
22
|
+
Reproduce with [benchmarks/benchmarks.ipynb](../benchmarks/benchmarks.ipynb)
|
|
23
|
+
(logic in `benchmarks/bench.py`; raw numbers in `benchmarks/results.csv`).
|
|
24
|
+
The reference values are cached in `benchmarks/ref_*.npz` together with the
|
|
25
|
+
problem they were computed for, so a rerun skips the slow part and a changed
|
|
26
|
+
problem recomputes. The `orthant` column needs a key in `$ORTHANT_KEY` or
|
|
27
|
+
`~/.orthant/key`.
|
|
28
|
+
|
|
29
|
+
### Equicorrelated Σ (ρ = 0.4)
|
|
30
|
+
|
|
31
|
+
| d | median p | multivariate-probit `orthant` (`resolution="high"`) | multivariate-probit `orthant` (`resolution="highest"`) | multivariate-probit `quadrature` | SciPy, default tolerances | pybhatlib (Bhat OVUS) | statsmodels |
|
|
32
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
33
|
+
| 3 | 0.348 | 1.5e-05 s / 0.0001 (0.0002) | 4.3e-05 s / 0.0000 (0.0000) | 7.8e-05 s / 0.0000 (0.0001) | 5.8e-03 s / 0.0000 (0.0000) | 8.5e-05 s / 0.0001 (0.0009) | 6.6e-07 s / 0.0284 (0.0992) |
|
|
34
|
+
| 5 | 0.154 | 2.4e-05 s / 0.0001 (0.0003) | 7.3e-05 s / 0.0000 (0.0000) | 4.3e-02 s / 0.0000 (0.0001) | 1.0e-02 s / 0.0000 (0.0000) | 1.2e-04 s / 0.0004 (0.0019) | 5.7e-07 s / 0.0246 (0.1287) |
|
|
35
|
+
| 8 | 0.101 | 4.3e-05 s / 0.0001 (0.0005) | 1.0e-04 s / 0.0000 (0.0000) | impractical past d = 7 | 1.6e-02 s / 0.0000 (0.0000) | 1.8e-04 s / 0.0003 (0.0023) | 9.9e-07 s / 0.0145 (0.1154) |
|
|
36
|
+
| 12 | 0.022 | 6.3e-05 s / 0.0001 (0.0007) | 1.5e-04 s / 0.0000 (0.0000) | impractical past d = 7 | 2.4e-02 s / 0.0000 (0.0000) | 2.4e-04 s / 0.0002 (0.0036) | 1.5e-06 s / 0.0063 (0.0776) |
|
|
37
|
+
| 16 | 0.0024 | 9.9e-05 s / 0.0000 (0.0006) | 2.2e-04 s / 0.0000 (0.0000) | impractical past d = 7 | 3.3e-02 s / 0.0000 (0.0000) | 3.2e-04 s / 0.0000 (0.0011) | 1.5e-06 s / 0.0015 (0.0536) |
|
|
38
|
+
|
|
39
|
+
### Pairwise-shaped Σ (near-singular)
|
|
40
|
+
|
|
41
|
+
| d | median p | multivariate-probit `orthant` (`resolution="high"`) | multivariate-probit `orthant` (`resolution="highest"`) | multivariate-probit `quadrature` | SciPy, default tolerances | pybhatlib (Bhat OVUS) | statsmodels |
|
|
42
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
43
|
+
| 3 | 0.431 | 2.0e-05 s / 0.0006 (0.0029) | 4.4e-05 s / 0.0001 (0.0007) | 7.3e-05 s / 0.0000 (0.0001) | 7.3e-03 s / 0.0000 (0.0000) | 8.1e-05 s / 0.0004 (0.0047) | 4.6e-07 s / 0.0371 (0.1600) |
|
|
44
|
+
| 5 | 0.171 | 2.4e-05 s / 0.0061 (0.0337) | 6.5e-05 s / 0.0031 (0.0325) | 4.6e-02 s / 0.0000 (0.0001) | 1.4e-01 s / 0.0000 (0.0000) | 1.2e-04 s / 0.0015 (0.0276) | 5.6e-07 s / 0.0523 (0.2549) |
|
|
45
|
+
| 8 | 0.097 | 4.0e-05 s / 0.0048 (0.0379) | 9.1e-05 s / 0.0022 (0.0134) | impractical past d = 7 | 1.1e+00 s / 0.0000 (0.0000) | 1.8e-04 s / 0.0015 (0.0103) | 8.1e-07 s / 0.0249 (0.1426) |
|
|
46
|
+
| 12 | 0.025 | 6.4e-05 s / 0.0034 (0.0431) | 1.6e-04 s / 0.0024 (0.0215) | impractical past d = 7 | 1.6e+00 s / 0.0000 (0.0001) | 2.5e-04 s / 0.0007 (0.0139) | 1.3e-06 s / 0.0143 (0.1855) |
|
|
47
|
+
| 16 | 0.013 | 8.4e-05 s / 0.0038 (0.0451) | 2.1e-04 s / 0.0029 (0.0452) | impractical past d = 7 | 1.8e+00 s / 0.0000 (0.0000) | 3.2e-04 s / 0.0010 (0.0151) | 1.8e-06 s / 0.0107 (0.1848) |
|
|
48
|
+
|
|
49
|
+
R `mvProbit` was not run (see below).
|
|
50
|
+
|
|
51
|
+
The reference itself took 0.24 to 1.6 s per row on the equicorrelated Σ and
|
|
52
|
+
0.24 to 20 s per row on the pairwise-shaped one. At d = 16 on the
|
|
53
|
+
equicorrelated Σ most pattern probabilities are small (median 0.0024), so the
|
|
54
|
+
maximum error says more than the median there.
|
|
55
|
+
|
|
56
|
+
## Reading the tables
|
|
57
|
+
|
|
58
|
+
- **statsmodels** fits one `Probit` per outcome and has no correlation matrix,
|
|
59
|
+
so its pattern probability is a product of marginals. Its error is the cost
|
|
60
|
+
of ignoring Σ, not of numerical integration, and it does not shrink with
|
|
61
|
+
more data. Its time covers prediction only.
|
|
62
|
+
- **`orthant`** scores all rows in one compiled call.
|
|
63
|
+
- **`resolution="high"`** was the fastest of the integrating methods at every
|
|
64
|
+
d on both Σ shapes: 3 to 6 times faster than pybhatlib, and 330 to 440 times
|
|
65
|
+
faster than SciPy on the equicorrelated Σ. Median error 0.0001 or less on the
|
|
66
|
+
equicorrelated Σ (maximum up to 0.0007, against pybhatlib's 0.0009 to
|
|
67
|
+
0.0036). On the pairwise-shaped Σ, median error 0.0006 to 0.0061, maximum up
|
|
68
|
+
to 0.045, the largest of the integrating methods.
|
|
69
|
+
- **`resolution="highest"`** costs 2.2 to 3.1 times `"high"`'s time and is
|
|
70
|
+
still 1.4 to 2 times faster than pybhatlib. On the equicorrelated Σ its
|
|
71
|
+
median and maximum error are below 0.00005 at every d. On the
|
|
72
|
+
pairwise-shaped Σ its median error is 0.0001 to 0.0031 (`"high"`: 0.0006 to
|
|
73
|
+
0.0061) and its maximum up to 0.045; pybhatlib's median there is 0.0004 to
|
|
74
|
+
0.0015.
|
|
75
|
+
- **pybhatlib** scores with Bhat's analytic approximation (`mvncd_batch`,
|
|
76
|
+
`method="ovus"`), one row at a time in Python. It is 70 to 105 times faster
|
|
77
|
+
than SciPy on the equicorrelated Σ. On the pairwise-shaped Σ its median error
|
|
78
|
+
is 0.0004 to 0.0015 and its maximum 0.0047 to 0.028. pybhatlib also has a PyTorch backend that runs on
|
|
79
|
+
your own GPU, but in pybhatlib 0.4.0 `mvncd` converts its inputs to NumPy
|
|
80
|
+
and `mvncd_batch` loops over rows in Python, so the GPU does not speed up
|
|
81
|
+
this scoring.
|
|
82
|
+
- **SciPy** stays accurate on the pairwise-shaped Σ but slows sharply, to
|
|
83
|
+
1.1 to 1.8 s per row from d = 8. That is 54 to 69 times its time on the
|
|
84
|
+
equicorrelated Σ at the same d.
|
|
85
|
+
- **`quadrature`** matched the reference on both shapes up to d = 5.
|
|
86
|
+
- **R `mvProbit`** was not run: R is not installed on the benchmark machine,
|
|
87
|
+
and the notebook has no R column yet.
|
|
88
|
+
|
|
89
|
+
## What this does not establish
|
|
90
|
+
|
|
91
|
+
- Two Σ shapes, one seed, and 100 rows per d. The pairwise-shaped Σ is one
|
|
92
|
+
construction of a near-singular matrix; see [studies/ghk.md](studies/ghk.md)
|
|
93
|
+
and [studies/comparators.md](studies/comparators.md) for more.
|
|
94
|
+
- Scoring only. Fitting time against full-likelihood estimation is in
|
|
95
|
+
[studies/comparators.md](studies/comparators.md).
|
|
@@ -19,6 +19,27 @@ model.stderr_robust_ # sandwich standard errors
|
|
|
19
19
|
model.predict_proba(X_new) # runs locally, on any evaluator
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
## What changes, and what doesn't
|
|
23
|
+
|
|
24
|
+
Only `fitter="modal"` changes. After `fit`, the model is used exactly as an
|
|
25
|
+
IFM fit is: `transform`, `predict_proba` and its joint queries, `joint_proba`,
|
|
26
|
+
`score`, `sample`, `correlation_`, `inner_models_[j].coef_`, `get_params` /
|
|
27
|
+
`set_params`. Prediction runs locally on the chosen `evaluator`.
|
|
28
|
+
|
|
29
|
+
The differences:
|
|
30
|
+
|
|
31
|
+
- `inner` must be `"linear"`; `inner_params` may set only `alpha`. Anything
|
|
32
|
+
else raises.
|
|
33
|
+
- `dependence`, `cv`, `optimizer` and `project_correlation` do not apply.
|
|
34
|
+
- `fit` uploads X, Y and `sample_weight` and waits for the service (see
|
|
35
|
+
[Cost](#cost)).
|
|
36
|
+
- Extra attributes: `stderr_`, `stderr_robust_`, `cov_params_`,
|
|
37
|
+
`cov_params_robust_`, `param_names_`, `fit_result_`.
|
|
38
|
+
- `eta_` holds in-sample indices (there is no cross-fitting); `nll_` is the
|
|
39
|
+
simulated joint negative log-likelihood; `optimize_result_` is `None`.
|
|
40
|
+
- An int `random_state` seeds the Sobol draws; otherwise the seed is 0, so
|
|
41
|
+
repeated fits agree.
|
|
42
|
+
|
|
22
43
|
## When to use it instead of IFM
|
|
23
44
|
|
|
24
45
|
- You need **standard errors** for the coefficients and for Σ. IFM does not
|
|
@@ -6,6 +6,11 @@ without measuring it. For *scoring* a fitted model, where that objection does
|
|
|
6
6
|
not apply, how does GHK compare with the compiled `orthant` backend in
|
|
7
7
|
accuracy and speed?
|
|
8
8
|
|
|
9
|
+
> **Version note (2026-10).** This study measured the backend shipped in 0.2.1. Later builds integrate
|
|
10
|
+
> `resolution="high"` on a finer rule and add `resolution="highest"`, which is more accurate at a few times the
|
|
11
|
+
> cost. The tables below were not rerun; current numbers for both settings are in
|
|
12
|
+
> [benchmarks.md](../benchmarks.md).
|
|
13
|
+
|
|
9
14
|
## Summary
|
|
10
15
|
|
|
11
16
|
| | accuracy | speed (s per row) |
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "multivariate-probit"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "Joint probabilities for correlated binary outcomes: P(any), P(all), patterns and conditionals that agree. Any classifier per outcome; scikit-learn-style API."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -165,8 +165,17 @@ class MultivariateProbit:
|
|
|
165
165
|
n_quad : int, default=24
|
|
166
166
|
Gauss-Legendre order for ``evaluator="quadrature"``. Lower it if
|
|
167
167
|
fitting with many outcomes gets slow.
|
|
168
|
-
resolution : {"high", "low"}, default="high"
|
|
169
|
-
Passed to ``evaluator="orthant"``; ignored otherwise.
|
|
168
|
+
resolution : {"high", "highest", "smooth", "low"}, default="high"
|
|
169
|
+
Passed to ``evaluator="orthant"``; ignored otherwise. ``"highest"``
|
|
170
|
+
is the most accurate setting, at about 2-3 times the time of
|
|
171
|
+
``"high"``: in docs/benchmarks.md its median error is under 0.00005 on
|
|
172
|
+
an equicorrelated covariance (``"high"``: up to 0.0001) and
|
|
173
|
+
0.0001-0.0031 on a near-singular one (``"high"``: 0.0006-0.0061).
|
|
174
|
+
``"smooth"`` costs the same as ``"highest"`` and is continuous in the
|
|
175
|
+
parameters (no threshold where the method changes, near-duplicates
|
|
176
|
+
included), for gradient-based fitting; it is not uniformly more
|
|
177
|
+
accurate than ``"high"``. ``"low"`` is the cheapest. ``"highest"`` and
|
|
178
|
+
``"smooth"`` are not available in 0.3.0 and earlier.
|
|
170
179
|
optimizer : str, default="Nelder-Mead"
|
|
171
180
|
Passed to ``scipy.optimize.minimize`` for ``dependence="joint"``.
|
|
172
181
|
Derivative-free by design: the orthant likelihood has no convenient
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/src/multivariate_probit/orthant/__init__.py
RENAMED
|
@@ -5,10 +5,17 @@ On import this binds ``orthant.cdf`` to one of two binaries:
|
|
|
5
5
|
* a valid key present -> the paid binary is decrypted in memory and bound;
|
|
6
6
|
full accuracy, unlimited dimension.
|
|
7
7
|
* no key / bad key -> the plaintext free binary is bound (n <= 3,
|
|
8
|
-
resolution="low" only) and a one-time notice is printed to stderr
|
|
8
|
+
resolution="low" only) and a one-time notice is printed to stderr on the
|
|
9
|
+
first call.
|
|
9
10
|
|
|
10
|
-
The key is read from ``$ORTHANT_KEY`` or ``~/.orthant/key
|
|
11
|
-
|
|
11
|
+
The key is read from ``$ORTHANT_KEY`` or ``~/.orthant/key`` at import, or
|
|
12
|
+
passed at any time with ``activate(key)`` (no environment needed, no restart):
|
|
13
|
+
|
|
14
|
+
>>> from multivariate_probit import orthant
|
|
15
|
+
>>> orthant.activate("my-key")
|
|
16
|
+
'paid'
|
|
17
|
+
|
|
18
|
+
Get a key at https://quantecarlo.com/orthant_key
|
|
12
19
|
"""
|
|
13
20
|
import glob
|
|
14
21
|
import importlib.machinery
|
|
@@ -18,7 +25,7 @@ import struct
|
|
|
18
25
|
import sys
|
|
19
26
|
import tempfile
|
|
20
27
|
|
|
21
|
-
__all__ = ["cdf"]
|
|
28
|
+
__all__ = ["cdf", "activate"]
|
|
22
29
|
|
|
23
30
|
_URL = "https://quantecarlo.com/orthant_key"
|
|
24
31
|
_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
@@ -26,6 +33,7 @@ _MAGIC = b"ORT1"
|
|
|
26
33
|
_SCRYPT_N, _SCRYPT_R, _SCRYPT_P = 2 ** 14, 8, 1
|
|
27
34
|
|
|
28
35
|
tier = "free" # set to "paid" when the paid binary is bound
|
|
36
|
+
_notice = None # why we are in free mode; printed once, on the first free call
|
|
29
37
|
|
|
30
38
|
|
|
31
39
|
def _read_key():
|
|
@@ -52,8 +60,12 @@ def _import_extension(data, modname):
|
|
|
52
60
|
fd = os.memfd_create(modname)
|
|
53
61
|
try:
|
|
54
62
|
os.write(fd, data)
|
|
63
|
+
path = "/proc/self/fd/%d" % fd
|
|
64
|
+
# The path has no .so suffix, so the loader must be given
|
|
65
|
+
# explicitly; inferred from the path, the spec would be None.
|
|
55
66
|
spec = importlib.util.spec_from_file_location(
|
|
56
|
-
qualified,
|
|
67
|
+
qualified, path,
|
|
68
|
+
loader=importlib.machinery.ExtensionFileLoader(qualified, path))
|
|
57
69
|
mod = importlib.util.module_from_spec(spec)
|
|
58
70
|
spec.loader.exec_module(mod)
|
|
59
71
|
return mod
|
|
@@ -124,18 +136,55 @@ def _warn(msg):
|
|
|
124
136
|
pass
|
|
125
137
|
|
|
126
138
|
|
|
139
|
+
def _free_cdf(free):
|
|
140
|
+
"""The free binary's cdf, printing the free-mode notice on its first call."""
|
|
141
|
+
def cdf(*args, **kwargs):
|
|
142
|
+
global _notice
|
|
143
|
+
if _notice:
|
|
144
|
+
_warn(_notice)
|
|
145
|
+
_notice = None
|
|
146
|
+
return free(*args, **kwargs)
|
|
147
|
+
cdf.__doc__ = free.__doc__
|
|
148
|
+
return cdf
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def activate(key):
|
|
152
|
+
"""Bind the paid binary with ``key``, at any time; return ``"paid"``.
|
|
153
|
+
|
|
154
|
+
Unlike the import-time lookup this never falls back: a wrong key raises
|
|
155
|
+
``ValueError`` and a binary that cannot load here raises ``ImportError``,
|
|
156
|
+
leaving the current binding unchanged.
|
|
157
|
+
"""
|
|
158
|
+
global tier, _notice
|
|
159
|
+
if not isinstance(key, str) or not key.strip():
|
|
160
|
+
raise ValueError("orthant: activate() needs a non-empty key string")
|
|
161
|
+
try:
|
|
162
|
+
data = _decrypt_paid(key.strip(), _tag())
|
|
163
|
+
except ImportError as e:
|
|
164
|
+
if (e.name or "").startswith("cryptography"):
|
|
165
|
+
raise ImportError("orthant: activate() needs the cryptography package: "
|
|
166
|
+
"pip install cryptography (or multivariate-probit[orthant])",
|
|
167
|
+
name=e.name) from None
|
|
168
|
+
raise
|
|
169
|
+
except Exception as e:
|
|
170
|
+
raise ValueError("orthant: key not accepted by this binary (%s). "
|
|
171
|
+
"Renew or get a key: %s" % (type(e).__name__, _URL)) from None
|
|
172
|
+
globals()["cdf"] = _import_extension(data, "_ocore").cdf
|
|
173
|
+
tier, _notice = "paid", None
|
|
174
|
+
return tier
|
|
175
|
+
|
|
176
|
+
|
|
127
177
|
def _bind():
|
|
128
|
-
"""Pick a binary, bind cdf, and return the tier."""
|
|
178
|
+
"""Pick a binary at import, bind cdf, and return the tier."""
|
|
179
|
+
global _notice
|
|
129
180
|
tag = _tag()
|
|
130
181
|
key = _read_key()
|
|
131
182
|
if key is not None:
|
|
132
183
|
try:
|
|
133
|
-
|
|
184
|
+
data = _decrypt_paid(key, tag)
|
|
134
185
|
except ImportError as e:
|
|
135
|
-
# Not a key problem: a missing dependency or a binary that won't
|
|
136
|
-
# load on this platform. Say so rather than blaming the key.
|
|
137
186
|
notice = ("orthant: key found but the paid binary could not be "
|
|
138
|
-
"
|
|
187
|
+
"decrypted (%s) -- running in FREE mode (n<=3, "
|
|
139
188
|
"resolution='low').\n" % e)
|
|
140
189
|
if (e.name or "").startswith("cryptography"):
|
|
141
190
|
notice += " pip install cryptography\n"
|
|
@@ -144,15 +193,24 @@ def _bind():
|
|
|
144
193
|
"mode (n<=3, resolution='low').\n"
|
|
145
194
|
" Renew or get a key: %s\n" % _URL)
|
|
146
195
|
else:
|
|
147
|
-
|
|
148
|
-
|
|
196
|
+
try:
|
|
197
|
+
globals()["cdf"] = _import_extension(data, "_ocore").cdf
|
|
198
|
+
return "paid"
|
|
199
|
+
except Exception as e:
|
|
200
|
+
# The key was right; loading the binary failed. Say so
|
|
201
|
+
# rather than blaming the key.
|
|
202
|
+
notice = ("orthant: key accepted but the paid binary could not "
|
|
203
|
+
"be loaded (%s: %s) -- running in FREE mode (n<=3, "
|
|
204
|
+
"resolution='low').\n" % (type(e).__name__, e))
|
|
149
205
|
else:
|
|
150
|
-
notice = ("orthant: FREE mode -- n<=3 and resolution='low' only
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
206
|
+
notice = ("orthant: FREE mode -- n<=3 and resolution='low' only. No key "
|
|
207
|
+
"in $ORTHANT_KEY or ~/.orthant/key when this module was "
|
|
208
|
+
"imported;\n"
|
|
209
|
+
" pass one with orthant.activate(key). Get a key: "
|
|
210
|
+
"%s\n" % _URL)
|
|
211
|
+
|
|
212
|
+
globals()["cdf"] = _free_cdf(_load_free(tag).cdf)
|
|
213
|
+
_notice = notice
|
|
156
214
|
return "free"
|
|
157
215
|
|
|
158
216
|
|
|
@@ -156,3 +156,56 @@ def test_orthant_with_key_runs_above_three(tmp_path):
|
|
|
156
156
|
tmp_path=tmp_path,
|
|
157
157
|
)
|
|
158
158
|
assert out.returncode == 0, out.stderr
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@orthant_built
|
|
162
|
+
def test_orthant_activate_rejects_a_wrong_key_and_stays_free(tmp_path):
|
|
163
|
+
out = _run_orthant(
|
|
164
|
+
"""
|
|
165
|
+
from multivariate_probit import orthant
|
|
166
|
+
assert orthant.tier == "free"
|
|
167
|
+
try:
|
|
168
|
+
orthant.activate("not-a-key")
|
|
169
|
+
except ValueError as exc:
|
|
170
|
+
print(exc)
|
|
171
|
+
else:
|
|
172
|
+
raise SystemExit("no error")
|
|
173
|
+
assert orthant.tier == "free"
|
|
174
|
+
eta, Y, corr = problem(3)
|
|
175
|
+
pattern_prob(eta, Y, corr, evaluator="orthant", resolution="low")
|
|
176
|
+
""",
|
|
177
|
+
tmp_path=tmp_path,
|
|
178
|
+
)
|
|
179
|
+
assert out.returncode == 0, out.stderr
|
|
180
|
+
assert "key not accepted" in out.stdout
|
|
181
|
+
assert out.stderr.count("FREE mode") == 1 # the notice comes once, at first use
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@orthant_built
|
|
185
|
+
@pytest.mark.skipif(not os.environ.get("ORTHANT_KEY"), reason="needs $ORTHANT_KEY")
|
|
186
|
+
def test_orthant_activate_after_import_without_environment(tmp_path):
|
|
187
|
+
out = _run_orthant(
|
|
188
|
+
f"""
|
|
189
|
+
from multivariate_probit import orthant
|
|
190
|
+
assert orthant.tier == "free"
|
|
191
|
+
assert orthant.activate({os.environ["ORTHANT_KEY"]!r}) == "paid"
|
|
192
|
+
eta, Y, corr = problem(5)
|
|
193
|
+
got = pattern_prob(eta, Y, corr, evaluator="orthant", resolution="highest")
|
|
194
|
+
ref = pattern_prob(eta, Y, corr)
|
|
195
|
+
assert np.abs(got - ref).max() < 1e-2, np.abs(got - ref).max()
|
|
196
|
+
""",
|
|
197
|
+
tmp_path=tmp_path,
|
|
198
|
+
)
|
|
199
|
+
assert out.returncode == 0, out.stderr
|
|
200
|
+
assert "FREE mode" not in out.stderr
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@orthant_built
|
|
204
|
+
def test_orthant_loads_an_extension_from_memory():
|
|
205
|
+
# The paid binary is loaded from bytes. Where os.memfd_create exists the
|
|
206
|
+
# path has no .so suffix, which once left the spec without a loader.
|
|
207
|
+
from multivariate_probit.orthant import _import_extension, _tag
|
|
208
|
+
|
|
209
|
+
with open(ORTHANT_DIR / f"_ofree.{_tag()}.so", "rb") as f:
|
|
210
|
+
mod = _import_extension(f.read(), "_ofree")
|
|
211
|
+
assert callable(mod.cdf)
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
sigma,d,N,method,median_err,max_err,s_per_row
|
|
2
|
-
equi,3,100,mvp quadrature,1.3373382605444406e-05,5.564767189014663e-05,6.151222012704238e-05
|
|
3
|
-
equi,3,100,mvp orthant (high),5.520783257679379e-05,0.0001977558612211161,9.499299922026694e-06
|
|
4
|
-
equi,3,100,SciPy default,1.7085000221517266e-06,2.872515295393452e-05,0.0042396432999521495
|
|
5
|
-
equi,3,100,pybhatlib ovus,0.00011229346488104974,0.0008524556947959311,6.468125007813797e-05
|
|
6
|
-
equi,3,100,statsmodels,0.028443441360488153,0.09919722660040897,5.445600254461169e-07
|
|
7
|
-
equi,3,100,reference,0.0,0.0,0.2536748156798421
|
|
8
|
-
equi,5,100,mvp quadrature,2.0314513939401896e-05,0.00011324180994054522,0.03316033348994097
|
|
9
|
-
equi,5,100,mvp orthant (high),8.582073115437003e-05,0.0003006023566725635,1.3224330032244325e-05
|
|
10
|
-
equi,5,100,SciPy default,1.3144313745008795e-06,1.410367111304156e-05,0.0065609772101743145
|
|
11
|
-
equi,5,100,pybhatlib ovus,0.00039917378471633624,0.0019016346577496457,9.364616998936981e-05
|
|
12
|
-
equi,5,100,statsmodels,0.024622620503874038,0.12870241315304662,6.427397602237761e-07
|
|
13
|
-
equi,5,100,reference,0.0,0.0,0.5339089114402304
|
|
14
|
-
equi,8,100,mvp orthant (high),7.170267413460912e-05,0.00047284481673742773,2.604458015412092e-05
|
|
15
|
-
equi,8,100,SciPy default,1.0657093905339088e-06,7.46774341275902e-06,0.013163221169961617
|
|
16
|
-
equi,8,100,pybhatlib ovus,0.0003370945286850363,0.0023083975100191556,0.00014096851024078205
|
|
17
|
-
equi,8,100,statsmodels,0.014453125416131472,0.11538392212916126,1.2416898971423507e-06
|
|
18
|
-
equi,8,100,reference,0.0,0.0,1.4400106923800196
|
|
19
|
-
equi,12,100,mvp orthant (high),5.782104286452053e-05,0.0007241255415409431,4.110162000870332e-05
|
|
20
|
-
equi,12,100,SciPy default,3.4970988831498073e-07,4.815527437126987e-06,0.018762066540075464
|
|
21
|
-
equi,12,100,pybhatlib ovus,0.0002047092197251764,0.0035788660943063266,0.00019040222017792984
|
|
22
|
-
equi,12,100,statsmodels,0.006303004648829415,0.07762653255283464,1.182040141429752e-06
|
|
23
|
-
equi,12,100,reference,0.0,0.0,1.6423227695701643
|
|
24
|
-
equi,16,100,mvp orthant (high),1.5439688762976245e-05,0.0006260612582136282,6.588060990907252e-05
|
|
25
|
-
equi,16,100,SciPy default,7.660225022907471e-08,7.498227887592357e-06,0.026153904990060255
|
|
26
|
-
equi,16,100,pybhatlib ovus,1.6800734753088424e-05,0.0011253768512156778,0.00026122707000467924
|
|
27
|
-
equi,16,100,statsmodels,0.0014514737962008938,0.05355898580397629,2.2758700652047993e-06
|
|
28
|
-
equi,16,100,reference,0.0,0.0,1.1454083026899025
|
|
29
|
-
pairwise,3,100,mvp quadrature,4.975128970532983e-06,8.276622284841828e-05,6.013165984768421e-05
|
|
30
|
-
pairwise,3,100,mvp orthant (high),0.0007572622538304392,0.0028934841345368834,9.238499915227294e-06
|
|
31
|
-
pairwise,3,100,SciPy default,1.0863552671880328e-06,1.1356497155445844e-05,0.006485114450042601
|
|
32
|
-
pairwise,3,100,pybhatlib ovus,0.00037654161082817383,0.004713394298326956,6.38040600460954e-05
|
|
33
|
-
pairwise,3,100,statsmodels,0.037060543222106224,0.15997644750429899,4.1702005546540024e-07
|
|
34
|
-
pairwise,3,100,reference,0.0,0.0,0.24052267621998907
|
|
35
|
-
pairwise,5,100,mvp quadrature,5.607947987679163e-06,6.266772635254991e-05,0.03469973466999363
|
|
36
|
-
pairwise,5,100,mvp orthant (high),0.006131866821483319,0.033843011353965835,1.3460909831337631e-05
|
|
37
|
-
pairwise,5,100,SciPy default,2.0348250380219923e-06,8.649425807485955e-06,0.10544797601993196
|
|
38
|
-
pairwise,5,100,pybhatlib ovus,0.0014663128072703105,0.02764332982515838,9.492035984294489e-05
|
|
39
|
-
pairwise,5,100,statsmodels,0.052292238612919734,0.2549403651447862,5.712499842047691e-07
|
|
40
|
-
pairwise,5,100,reference,0.0,0.0,1.2560263323498657
|
|
41
|
-
pairwise,8,100,mvp orthant (high),0.0047540407934710425,0.037869385076046036,2.4516569974366577e-05
|
|
42
|
-
pairwise,8,100,SciPy default,1.8501012514909876e-06,1.4681123618909275e-05,0.8790659057398443
|
|
43
|
-
pairwise,8,100,pybhatlib ovus,0.0015355099588129813,0.010313908094296734,0.00013880397018510847
|
|
44
|
-
pairwise,8,100,statsmodels,0.024908203618945243,0.1426044668035415,9.262599633075297e-07
|
|
45
|
-
pairwise,8,100,reference,0.0,0.0,4.434727118879964
|
|
46
|
-
pairwise,12,100,mvp orthant (high),0.003427530742914099,0.04306180419633249,3.916209010640159e-05
|
|
47
|
-
pairwise,12,100,SciPy default,1.6616917820439989e-06,2.8483187402228438e-05,1.20114108054986
|
|
48
|
-
pairwise,12,100,pybhatlib ovus,0.0007431598443556342,0.01388904489935694,0.00019705019018147142
|
|
49
|
-
pairwise,12,100,statsmodels,0.014263734107914516,0.1854686635860365,1.1522200657054783e-06
|
|
50
|
-
pairwise,12,100,reference,0.0,0.0,9.518902401560045
|
|
51
|
-
pairwise,16,100,mvp orthant (high),0.00374560158932665,0.045287863431270364,5.415496008936316e-05
|
|
52
|
-
pairwise,16,100,SciPy default,1.8019881021374973e-06,9.824805690655108e-06,1.8181418986897915
|
|
53
|
-
pairwise,16,100,pybhatlib ovus,0.0009786447220386783,0.015062755675490355,0.0002470732299843803
|
|
54
|
-
pairwise,16,100,statsmodels,0.010651175036494289,0.18479811475521019,1.4770901179872453e-06
|
|
55
|
-
pairwise,16,100,reference,0.0,0.0,19.51646036452992
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# Benchmarks against other tools
|
|
2
|
-
|
|
3
|
-
**Task.** Score `P(Y = y | x)`, the probability of the pattern each held-out
|
|
4
|
-
row actually has, from one fitted model. Synthetic data: 6 features, linear
|
|
5
|
-
margins, 3,000 training rows, 100 scored rows per d. The model is
|
|
6
|
-
`MultivariateProbit(inner="linear", dependence="pairwise", random_state=0)`.
|
|
7
|
-
Every column scores that same fitted model (η̂, Σ̂), except statsmodels.
|
|
8
|
-
|
|
9
|
-
There are two shapes of true Σ:
|
|
10
|
-
|
|
11
|
-
- **Equicorrelated**, ρ = 0.4: the easy case.
|
|
12
|
-
- **Pairwise-shaped**: a rank-2 factor plus noise, projected to the nearest
|
|
13
|
-
correlation matrix (smallest eigenvalue about 1e-8), the same construction
|
|
14
|
-
as [studies/ghk.md](studies/ghk.md). A fitted pairwise Σ̂ lands on that PSD
|
|
15
|
-
boundary, which is hard for every approximation.
|
|
16
|
-
|
|
17
|
-
**Reference.** SciPy `multivariate_normal.cdf` with `abseps=1e-7`,
|
|
18
|
-
`releps=1e-6`, one row at a time. Error is the absolute difference from it in
|
|
19
|
-
probability: median over rows, maximum in parentheses. Time is wall-clock per
|
|
20
|
-
row after one warm-up call, on a two-core x86-64 Linux machine.
|
|
21
|
-
|
|
22
|
-
Reproduce with [benchmarks/benchmarks.ipynb](../benchmarks/benchmarks.ipynb)
|
|
23
|
-
(logic in `benchmarks/bench.py`; raw numbers in `benchmarks/results.csv`).
|
|
24
|
-
The reference values are cached in `benchmarks/ref_*.npz` together with the
|
|
25
|
-
problem they were computed for, so a rerun skips the slow part and a changed
|
|
26
|
-
problem recomputes. The `orthant` column needs a key in `$ORTHANT_KEY` or
|
|
27
|
-
`~/.orthant/key`.
|
|
28
|
-
|
|
29
|
-
### Equicorrelated Σ (ρ = 0.4)
|
|
30
|
-
|
|
31
|
-
| d | median p | multivariate-probit `orthant` (`resolution="high"`) | multivariate-probit `quadrature` | SciPy, default tolerances | pybhatlib (Bhat OVUS) | statsmodels |
|
|
32
|
-
| --- | --- | --- | --- | --- | --- | --- |
|
|
33
|
-
| 3 | 0.348 | 9.5e-6 s / 0.0001 (0.0002) | 6.2e-5 s / 0.0000 (0.0001) | 4.2e-3 s / 0.0000 (0.0000) | 6.5e-5 s / 0.0001 (0.0009) | 5.4e-7 s / 0.0284 (0.0992) |
|
|
34
|
-
| 5 | 0.154 | 1.3e-5 s / 0.0001 (0.0003) | 3.3e-2 s / 0.0000 (0.0001) | 6.6e-3 s / 0.0000 (0.0000) | 9.4e-5 s / 0.0004 (0.0019) | 6.4e-7 s / 0.0246 (0.1287) |
|
|
35
|
-
| 8 | 0.101 | 2.6e-5 s / 0.0001 (0.0005) | impractical past d = 7 | 1.3e-2 s / 0.0000 (0.0000) | 1.4e-4 s / 0.0003 (0.0023) | 1.2e-6 s / 0.0145 (0.1154) |
|
|
36
|
-
| 12 | 0.022 | 4.1e-5 s / 0.0001 (0.0007) | impractical past d = 7 | 1.9e-2 s / 0.0000 (0.0000) | 1.9e-4 s / 0.0002 (0.0036) | 1.2e-6 s / 0.0063 (0.0776) |
|
|
37
|
-
| 16 | 0.0024 | 6.6e-5 s / 0.0000 (0.0006) | impractical past d = 7 | 2.6e-2 s / 0.0000 (0.0000) | 2.6e-4 s / 0.0000 (0.0011) | 2.3e-6 s / 0.0015 (0.0536) |
|
|
38
|
-
|
|
39
|
-
### Pairwise-shaped Σ (near-singular)
|
|
40
|
-
|
|
41
|
-
| d | median p | multivariate-probit `orthant` (`resolution="high"`) | multivariate-probit `quadrature` | SciPy, default tolerances | pybhatlib (Bhat OVUS) | statsmodels |
|
|
42
|
-
| --- | --- | --- | --- | --- | --- | --- |
|
|
43
|
-
| 3 | 0.431 | 9.2e-6 s / 0.0008 (0.0029) | 6.0e-5 s / 0.0000 (0.0001) | 6.5e-3 s / 0.0000 (0.0000) | 6.4e-5 s / 0.0004 (0.0047) | 4.2e-7 s / 0.0371 (0.1600) |
|
|
44
|
-
| 5 | 0.171 | 1.3e-5 s / 0.0061 (0.0338) | 3.5e-2 s / 0.0000 (0.0001) | 1.1e-1 s / 0.0000 (0.0000) | 9.5e-5 s / 0.0015 (0.0276) | 5.7e-7 s / 0.0523 (0.2549) |
|
|
45
|
-
| 8 | 0.097 | 2.5e-5 s / 0.0048 (0.0379) | impractical past d = 7 | 8.8e-1 s / 0.0000 (0.0000) | 1.4e-4 s / 0.0015 (0.0103) | 9.3e-7 s / 0.0249 (0.1426) |
|
|
46
|
-
| 12 | 0.025 | 3.9e-5 s / 0.0034 (0.0431) | impractical past d = 7 | 1.2e+0 s / 0.0000 (0.0000) | 2.0e-4 s / 0.0007 (0.0139) | 1.2e-6 s / 0.0143 (0.1855) |
|
|
47
|
-
| 16 | 0.013 | 5.4e-5 s / 0.0037 (0.0453) | impractical past d = 7 | 1.8e+0 s / 0.0000 (0.0000) | 2.5e-4 s / 0.0010 (0.0151) | 1.5e-6 s / 0.0107 (0.1848) |
|
|
48
|
-
|
|
49
|
-
R `mvProbit` was not run (see below).
|
|
50
|
-
|
|
51
|
-
The reference itself took 0.24 to 1.6 s per row on the equicorrelated Σ and
|
|
52
|
-
0.24 to 20 s per row on the pairwise-shaped one. At d = 16 on the
|
|
53
|
-
equicorrelated Σ most pattern probabilities are small (median 0.0024), so the
|
|
54
|
-
maximum error says more than the median there.
|
|
55
|
-
|
|
56
|
-
## Reading the tables
|
|
57
|
-
|
|
58
|
-
- **statsmodels** fits one `Probit` per outcome and has no correlation matrix,
|
|
59
|
-
so its pattern probability is a product of marginals. Its error is the cost
|
|
60
|
-
of ignoring Σ, not of numerical integration, and it does not shrink with
|
|
61
|
-
more data. Its time covers prediction only.
|
|
62
|
-
- **`orthant`** scores all rows in one compiled call. It was the fastest of
|
|
63
|
-
the integrating methods at every d on both Σ shapes: 4 to 7 times faster
|
|
64
|
-
than pybhatlib, and 390 to 510 times faster than SciPy on the
|
|
65
|
-
equicorrelated Σ. On the equicorrelated Σ it also had the smallest maximum
|
|
66
|
-
error of the approximations. On the pairwise-shaped Σ it was the *least*
|
|
67
|
-
accurate integrating method: median error 0.0008 to 0.0061, maximum up to
|
|
68
|
-
0.045.
|
|
69
|
-
- **pybhatlib** scores with Bhat's analytic approximation (`mvncd_batch`,
|
|
70
|
-
`method="ovus"`), one row at a time in Python. It is 65 to 100 times faster
|
|
71
|
-
than SciPy on the equicorrelated Σ. On the pairwise-shaped Σ its median error
|
|
72
|
-
(0.0004 to 0.0015) was 2 to 5 times smaller than `orthant`'s, and so was its
|
|
73
|
-
maximum from d = 5 up. pybhatlib also has a PyTorch backend that runs on
|
|
74
|
-
your own GPU, but in pybhatlib 0.4.0 `mvncd` converts its inputs to NumPy
|
|
75
|
-
and `mvncd_batch` loops over rows in Python, so the GPU does not speed up
|
|
76
|
-
this scoring.
|
|
77
|
-
- **SciPy** stays accurate on the pairwise-shaped Σ but slows sharply, to
|
|
78
|
-
0.9 to 1.8 s per row from d = 8. That is 65 to 70 times its time on the
|
|
79
|
-
equicorrelated Σ at the same d.
|
|
80
|
-
- **`quadrature`** matched the reference on both shapes up to d = 5.
|
|
81
|
-
- **R `mvProbit`** was not run: R is not installed on the benchmark machine,
|
|
82
|
-
and the notebook has no R column yet.
|
|
83
|
-
|
|
84
|
-
## What this does not establish
|
|
85
|
-
|
|
86
|
-
- Two Σ shapes, one seed, and 100 rows per d. The pairwise-shaped Σ is one
|
|
87
|
-
construction of a near-singular matrix; see [studies/ghk.md](studies/ghk.md)
|
|
88
|
-
and [studies/comparators.md](studies/comparators.md) for more.
|
|
89
|
-
- Scoring only. Fitting time against full-likelihood estimation is in
|
|
90
|
-
[studies/comparators.md](studies/comparators.md).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_d12_n3000_100_rho0.4_s0.npz
RENAMED
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_d16_n3000_100_rho0.4_s0.npz
RENAMED
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_d3_n3000_100_rho0.4_s0.npz
RENAMED
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_d5_n3000_100_rho0.4_s0.npz
RENAMED
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_d8_n3000_100_rho0.4_s0.npz
RENAMED
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_pairwise_d12_n3000_100_s0.npz
RENAMED
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_pairwise_d16_n3000_100_s0.npz
RENAMED
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_pairwise_d3_n3000_100_s0.npz
RENAMED
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_pairwise_d5_n3000_100_s0.npz
RENAMED
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/benchmarks/ref_pairwise_d8_n3000_100_s0.npz
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/index-correlation-shortcut.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/multilabel-benchmarks.md
RENAMED
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/studies/sparse-label-transfer.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/use-cases/content-moderation.md
RENAMED
|
File without changes
|
|
File without changes
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/docs/use-cases/fraud-vendor-fusion.md
RENAMED
|
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
|
{multivariate_probit-0.3.0 → multivariate_probit-0.3.2}/tests/test_correlation_projection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|