scm2cpp-lasso 0.5.0__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.
- scm2cpp_lasso-0.5.0/PKG-INFO +158 -0
- scm2cpp_lasso-0.5.0/README.md +140 -0
- scm2cpp_lasso-0.5.0/pyproject.toml +34 -0
- scm2cpp_lasso-0.5.0/setup.cfg +4 -0
- scm2cpp_lasso-0.5.0/setup.py +63 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso/__init__.py +543 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso/_generated/__init__.py +0 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso/_generated/_loader.py +59 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso/_generated/lasso_cov.cpp +9 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso/_generated/lasso_cov.hpp +114 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso/_generated/lasso_cov_capi.cpp +31 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso/_generated/scm2cpp.hpp +1585 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso/_libfind.py +52 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso/batch_capi.cu +80 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso.egg-info/PKG-INFO +158 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso.egg-info/SOURCES.txt +17 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso.egg-info/dependency_links.txt +1 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso.egg-info/requires.txt +1 -0
- scm2cpp_lasso-0.5.0/src/scm2cpp_lasso.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scm2cpp-lasso
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Lasso by covariance-update coordinate descent, translated from Scheme to C++ by scm2cpp; GPU batch solver included
|
|
5
|
+
Author: Hirotaka Niitsuma
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/niitsuma/scm2cpp
|
|
8
|
+
Project-URL: Source, https://github.com/niitsuma/scm2cpp/tree/master/python/scm2cpp-lasso
|
|
9
|
+
Keywords: lasso,elastic-net,ridge,coordinate-descent,cuda,compiler
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: C++
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: numpy>=1.17
|
|
18
|
+
|
|
19
|
+
# scm2cpp-lasso
|
|
20
|
+
|
|
21
|
+
*[Japanese (README.ja.md)](README.ja.md)*
|
|
22
|
+
|
|
23
|
+
Lasso by coordinate descent over a Gram matrix, with an optional GPU
|
|
24
|
+
path. The solver is Scheme, translated to C++ by
|
|
25
|
+
[scm2cpp](https://github.com/niitsuma/scm2cpp).
|
|
26
|
+
|
|
27
|
+
```console
|
|
28
|
+
$ pip install scm2cpp-lasso
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Installing needs a C++17 compiler and nothing else. If `nvcc` is on
|
|
32
|
+
the path, the batched GPU solver is built as well; if it is not, the
|
|
33
|
+
package installs and works exactly the same, minus that one method.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import numpy as np
|
|
37
|
+
from scm2cpp_lasso import CovLasso, cuda_available
|
|
38
|
+
|
|
39
|
+
model = CovLasso(X, y) # forms X'X and X'y
|
|
40
|
+
lambdas = model.lambda_grid(num=100) # from lambda_max down
|
|
41
|
+
|
|
42
|
+
path = model.fit_path(lambdas) # warm-started, sequential
|
|
43
|
+
grid = model.fit_path_batch(lambdas) # every lambda from zero
|
|
44
|
+
print("GPU:", cuda_available())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The objective is scikit-learn's, with `fit_intercept=False`:
|
|
48
|
+
|
|
49
|
+
(1 / 2 nobs) ||y - X b||^2 + lam ||b||_1
|
|
50
|
+
|
|
51
|
+
Penalties are compared against correlations on the features' own
|
|
52
|
+
scale, so the useful range of `lam` depends on the data.
|
|
53
|
+
`lambda_max()` is the smallest penalty that leaves every coefficient
|
|
54
|
+
at zero, and `lambda_grid()` walks down from it -- the construction
|
|
55
|
+
scikit-learn uses. If your columns differ wildly in scale, standardize
|
|
56
|
+
them first; this solver does not do it for you.
|
|
57
|
+
|
|
58
|
+
## Which method
|
|
59
|
+
|
|
60
|
+
`fit_path` walks a single path, each lambda starting from the previous
|
|
61
|
+
solution -- the descent is exactly resumable, so this costs almost
|
|
62
|
+
nothing per lambda after the first. `fit_path_batch` solves every
|
|
63
|
+
lambda from zero, which is what a cross-validation grid needs, where
|
|
64
|
+
the folds differ and warm starting across lambdas is not on offer; the
|
|
65
|
+
problems are independent, so they go to the GPU together.
|
|
66
|
+
|
|
67
|
+
Over a 400-lambda path at p=200 and 1800 rows, on an RTX 4090 and an
|
|
68
|
+
i9-10900X:
|
|
69
|
+
|
|
70
|
+
| call | time |
|
|
71
|
+
|---------------------------------------|---------|
|
|
72
|
+
| `fit_path` (warm, sequential) | 0.091 s |
|
|
73
|
+
| `fit_path_batch` (GPU) | 0.047 s |
|
|
74
|
+
| `fit_path_batch(force_cpu=True)` | 0.178 s |
|
|
75
|
+
|
|
76
|
+
GPU and CPU agree to 2e-14, and the objectives are within 3e-17 of
|
|
77
|
+
scikit-learn's `lasso_path` on the same grid.
|
|
78
|
+
|
|
79
|
+
## Elastic net and ridge
|
|
80
|
+
|
|
81
|
+
The same Gram matrix serves two more estimators. `fit_path` and
|
|
82
|
+
`fit_path_batch` take `l1_ratio` (scikit-learn's mixing parameter):
|
|
83
|
+
the L2 share of the penalty enters only the update's denominator, so
|
|
84
|
+
the elastic net runs on the identical machinery, GPU path included,
|
|
85
|
+
and `l1_ratio=1` is bit-for-bit the lasso.
|
|
86
|
+
|
|
87
|
+
`CovRidge` is the closed form: one symmetric eigendecomposition,
|
|
88
|
+
then every alpha costs O(p^2), so thousands of alphas cost what one
|
|
89
|
+
does. Its objective matches scikit-learn's `Ridge` with
|
|
90
|
+
`fit_intercept=False` (which, unlike the lasso, is not scaled by the
|
|
91
|
+
number of rows), and agrees with it to machine precision.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
path = model.fit_path(lambdas, l1_ratio=0.5) # elastic net
|
|
95
|
+
ridge = CovRidge(X, y)
|
|
96
|
+
betas = ridge.fit_path(ridge.alpha_grid()) # a whole ridge path
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Logistic regression with L1
|
|
100
|
+
|
|
101
|
+
`CovLogistic` solves L1-penalized logistic regression by
|
|
102
|
+
majorization: the logistic Hessian is bounded by X'X/4, so the
|
|
103
|
+
quadratic term is the same Gram matrix, fixed once, and each outer
|
|
104
|
+
round costs one gradient pass before handing the majorizer to the
|
|
105
|
+
same coordinate descent the lasso uses. Objectives agree with
|
|
106
|
+
scikit-learn's `LogisticRegression(penalty="l1",
|
|
107
|
+
fit_intercept=False)` at `C = 1/(n lam)` to 9e-15.
|
|
108
|
+
|
|
109
|
+
## Group lasso
|
|
110
|
+
|
|
111
|
+
`CovGroupLasso` penalizes whole groups -- `lam * sum_g sqrt(|g|)
|
|
112
|
+
||b_g||` -- so correlated features enter or leave together. Block
|
|
113
|
+
coordinate descent on the same Gram machinery: each block visit is
|
|
114
|
+
one majorized proximal step (the group's Gram block dominated by its
|
|
115
|
+
top eigenvalue, found once), descending monotonically. With size-one
|
|
116
|
+
groups it reduces exactly to the lasso -- verified against sklearn to
|
|
117
|
+
9e-16 -- and at convergence the group KKT conditions hold to 2e-12.
|
|
118
|
+
|
|
119
|
+
## Bootstrap on the GPU
|
|
120
|
+
|
|
121
|
+
`bootstrap` draws pairs-bootstrap resamples and refits them all at
|
|
122
|
+
one lambda. Each resample's Gram matrix is `X' diag(m) X` for its
|
|
123
|
+
multiplicity counts `m` -- one BLAS product -- and because the
|
|
124
|
+
problems are independent, the descents run as one batch: on the GPU,
|
|
125
|
+
one thread per resample, each reading its own Gram matrix.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
betas = model.bootstrap(lam, n_boot=500, seed=0) # (500, p)
|
|
129
|
+
freq = (abs(betas) > 1e-9).mean(axis=0) # selection frequency
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Requires constructing the model from `X, y` (a Gram matrix alone
|
|
133
|
+
cannot be resampled by rows). GPU and CPU agree to machine
|
|
134
|
+
precision.
|
|
135
|
+
|
|
136
|
+
## A design with structure
|
|
137
|
+
|
|
138
|
+
When the design matrix has structure, forming X'X the general way is
|
|
139
|
+
the wrong move. `kernel` exposes the translated functions directly,
|
|
140
|
+
and [`scm2cpp-tfs`](https://pypi.org/project/scm2cpp-tfs/) does exactly
|
|
141
|
+
that for moving-average designs: it builds the Gram matrix from a
|
|
142
|
+
series' prefix sums in O(n p) time, never forming the design. That
|
|
143
|
+
package stands alone -- it carries its own copy of this descent -- so
|
|
144
|
+
neither installs the other.
|
|
145
|
+
|
|
146
|
+
## Where this comes from
|
|
147
|
+
|
|
148
|
+
The C++ this compiles is committed to `python/scm2cpp-lasso/` in the
|
|
149
|
+
scm2cpp repository, translated from
|
|
150
|
+
`examples/kernel-only/lasso-cov.scm`. That repository also derives the
|
|
151
|
+
covariance-update solver automatically from a naive one by finite
|
|
152
|
+
differencing; this package is the derived kernel, packaged. To refresh
|
|
153
|
+
the committed C++ after changing the Scheme, run `regenerate.sh` --
|
|
154
|
+
that step, and only that step, needs Racket.
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT, the same as scm2cpp.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# scm2cpp-lasso
|
|
2
|
+
|
|
3
|
+
*[Japanese (README.ja.md)](README.ja.md)*
|
|
4
|
+
|
|
5
|
+
Lasso by coordinate descent over a Gram matrix, with an optional GPU
|
|
6
|
+
path. The solver is Scheme, translated to C++ by
|
|
7
|
+
[scm2cpp](https://github.com/niitsuma/scm2cpp).
|
|
8
|
+
|
|
9
|
+
```console
|
|
10
|
+
$ pip install scm2cpp-lasso
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Installing needs a C++17 compiler and nothing else. If `nvcc` is on
|
|
14
|
+
the path, the batched GPU solver is built as well; if it is not, the
|
|
15
|
+
package installs and works exactly the same, minus that one method.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import numpy as np
|
|
19
|
+
from scm2cpp_lasso import CovLasso, cuda_available
|
|
20
|
+
|
|
21
|
+
model = CovLasso(X, y) # forms X'X and X'y
|
|
22
|
+
lambdas = model.lambda_grid(num=100) # from lambda_max down
|
|
23
|
+
|
|
24
|
+
path = model.fit_path(lambdas) # warm-started, sequential
|
|
25
|
+
grid = model.fit_path_batch(lambdas) # every lambda from zero
|
|
26
|
+
print("GPU:", cuda_available())
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The objective is scikit-learn's, with `fit_intercept=False`:
|
|
30
|
+
|
|
31
|
+
(1 / 2 nobs) ||y - X b||^2 + lam ||b||_1
|
|
32
|
+
|
|
33
|
+
Penalties are compared against correlations on the features' own
|
|
34
|
+
scale, so the useful range of `lam` depends on the data.
|
|
35
|
+
`lambda_max()` is the smallest penalty that leaves every coefficient
|
|
36
|
+
at zero, and `lambda_grid()` walks down from it -- the construction
|
|
37
|
+
scikit-learn uses. If your columns differ wildly in scale, standardize
|
|
38
|
+
them first; this solver does not do it for you.
|
|
39
|
+
|
|
40
|
+
## Which method
|
|
41
|
+
|
|
42
|
+
`fit_path` walks a single path, each lambda starting from the previous
|
|
43
|
+
solution -- the descent is exactly resumable, so this costs almost
|
|
44
|
+
nothing per lambda after the first. `fit_path_batch` solves every
|
|
45
|
+
lambda from zero, which is what a cross-validation grid needs, where
|
|
46
|
+
the folds differ and warm starting across lambdas is not on offer; the
|
|
47
|
+
problems are independent, so they go to the GPU together.
|
|
48
|
+
|
|
49
|
+
Over a 400-lambda path at p=200 and 1800 rows, on an RTX 4090 and an
|
|
50
|
+
i9-10900X:
|
|
51
|
+
|
|
52
|
+
| call | time |
|
|
53
|
+
|---------------------------------------|---------|
|
|
54
|
+
| `fit_path` (warm, sequential) | 0.091 s |
|
|
55
|
+
| `fit_path_batch` (GPU) | 0.047 s |
|
|
56
|
+
| `fit_path_batch(force_cpu=True)` | 0.178 s |
|
|
57
|
+
|
|
58
|
+
GPU and CPU agree to 2e-14, and the objectives are within 3e-17 of
|
|
59
|
+
scikit-learn's `lasso_path` on the same grid.
|
|
60
|
+
|
|
61
|
+
## Elastic net and ridge
|
|
62
|
+
|
|
63
|
+
The same Gram matrix serves two more estimators. `fit_path` and
|
|
64
|
+
`fit_path_batch` take `l1_ratio` (scikit-learn's mixing parameter):
|
|
65
|
+
the L2 share of the penalty enters only the update's denominator, so
|
|
66
|
+
the elastic net runs on the identical machinery, GPU path included,
|
|
67
|
+
and `l1_ratio=1` is bit-for-bit the lasso.
|
|
68
|
+
|
|
69
|
+
`CovRidge` is the closed form: one symmetric eigendecomposition,
|
|
70
|
+
then every alpha costs O(p^2), so thousands of alphas cost what one
|
|
71
|
+
does. Its objective matches scikit-learn's `Ridge` with
|
|
72
|
+
`fit_intercept=False` (which, unlike the lasso, is not scaled by the
|
|
73
|
+
number of rows), and agrees with it to machine precision.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
path = model.fit_path(lambdas, l1_ratio=0.5) # elastic net
|
|
77
|
+
ridge = CovRidge(X, y)
|
|
78
|
+
betas = ridge.fit_path(ridge.alpha_grid()) # a whole ridge path
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Logistic regression with L1
|
|
82
|
+
|
|
83
|
+
`CovLogistic` solves L1-penalized logistic regression by
|
|
84
|
+
majorization: the logistic Hessian is bounded by X'X/4, so the
|
|
85
|
+
quadratic term is the same Gram matrix, fixed once, and each outer
|
|
86
|
+
round costs one gradient pass before handing the majorizer to the
|
|
87
|
+
same coordinate descent the lasso uses. Objectives agree with
|
|
88
|
+
scikit-learn's `LogisticRegression(penalty="l1",
|
|
89
|
+
fit_intercept=False)` at `C = 1/(n lam)` to 9e-15.
|
|
90
|
+
|
|
91
|
+
## Group lasso
|
|
92
|
+
|
|
93
|
+
`CovGroupLasso` penalizes whole groups -- `lam * sum_g sqrt(|g|)
|
|
94
|
+
||b_g||` -- so correlated features enter or leave together. Block
|
|
95
|
+
coordinate descent on the same Gram machinery: each block visit is
|
|
96
|
+
one majorized proximal step (the group's Gram block dominated by its
|
|
97
|
+
top eigenvalue, found once), descending monotonically. With size-one
|
|
98
|
+
groups it reduces exactly to the lasso -- verified against sklearn to
|
|
99
|
+
9e-16 -- and at convergence the group KKT conditions hold to 2e-12.
|
|
100
|
+
|
|
101
|
+
## Bootstrap on the GPU
|
|
102
|
+
|
|
103
|
+
`bootstrap` draws pairs-bootstrap resamples and refits them all at
|
|
104
|
+
one lambda. Each resample's Gram matrix is `X' diag(m) X` for its
|
|
105
|
+
multiplicity counts `m` -- one BLAS product -- and because the
|
|
106
|
+
problems are independent, the descents run as one batch: on the GPU,
|
|
107
|
+
one thread per resample, each reading its own Gram matrix.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
betas = model.bootstrap(lam, n_boot=500, seed=0) # (500, p)
|
|
111
|
+
freq = (abs(betas) > 1e-9).mean(axis=0) # selection frequency
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Requires constructing the model from `X, y` (a Gram matrix alone
|
|
115
|
+
cannot be resampled by rows). GPU and CPU agree to machine
|
|
116
|
+
precision.
|
|
117
|
+
|
|
118
|
+
## A design with structure
|
|
119
|
+
|
|
120
|
+
When the design matrix has structure, forming X'X the general way is
|
|
121
|
+
the wrong move. `kernel` exposes the translated functions directly,
|
|
122
|
+
and [`scm2cpp-tfs`](https://pypi.org/project/scm2cpp-tfs/) does exactly
|
|
123
|
+
that for moving-average designs: it builds the Gram matrix from a
|
|
124
|
+
series' prefix sums in O(n p) time, never forming the design. That
|
|
125
|
+
package stands alone -- it carries its own copy of this descent -- so
|
|
126
|
+
neither installs the other.
|
|
127
|
+
|
|
128
|
+
## Where this comes from
|
|
129
|
+
|
|
130
|
+
The C++ this compiles is committed to `python/scm2cpp-lasso/` in the
|
|
131
|
+
scm2cpp repository, translated from
|
|
132
|
+
`examples/kernel-only/lasso-cov.scm`. That repository also derives the
|
|
133
|
+
covariance-update solver automatically from a naive one by finite
|
|
134
|
+
differencing; this package is the derived kernel, packaged. To refresh
|
|
135
|
+
the committed C++ after changing the Scheme, run `regenerate.sh` --
|
|
136
|
+
that step, and only that step, needs Racket.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT, the same as scm2cpp.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "numpy"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "scm2cpp-lasso"
|
|
7
|
+
version = "0.5.0"
|
|
8
|
+
description = "Lasso by covariance-update coordinate descent, translated from Scheme to C++ by scm2cpp; GPU batch solver included"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "Hirotaka Niitsuma"}]
|
|
13
|
+
keywords = ["lasso", "elastic-net", "ridge", "coordinate-descent", "cuda", "compiler"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: C++",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
]
|
|
21
|
+
dependencies = ["numpy>=1.17"]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/niitsuma/scm2cpp"
|
|
25
|
+
Source = "https://github.com/niitsuma/scm2cpp/tree/master/python/scm2cpp-lasso"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
package-dir = {"" = "src"}
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.package-data]
|
|
34
|
+
scm2cpp_lasso = ["_generated/*.hpp", "_generated/*.cpp", "*.cu"]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Build the translated kernel, and the CUDA path when nvcc is here.
|
|
2
|
+
|
|
3
|
+
The C++ this compiles is committed, not generated at install time, so
|
|
4
|
+
building the package needs a C++17 compiler and nothing else -- no
|
|
5
|
+
Racket, no scm2cpp. regenerate.sh is how the committed sources are
|
|
6
|
+
refreshed, and it is the only thing that needs the translator.
|
|
7
|
+
|
|
8
|
+
The CUDA library is optional in the strongest sense: no nvcc, no
|
|
9
|
+
device, or a failed nvcc all leave a working CPU package behind, and
|
|
10
|
+
the batch path notices at import and falls back.
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from setuptools import Extension, setup
|
|
19
|
+
from setuptools.command.build_ext import build_ext
|
|
20
|
+
|
|
21
|
+
HERE = Path(__file__).resolve().parent
|
|
22
|
+
# setuptools wants sources and include paths relative to this file and
|
|
23
|
+
# separated by forward slashes, on every platform
|
|
24
|
+
PKG_REL = "src/scm2cpp_lasso"
|
|
25
|
+
GEN_REL = PKG_REL + "/_generated"
|
|
26
|
+
PKG = HERE / "src" / "scm2cpp_lasso"
|
|
27
|
+
GEN = PKG / "_generated"
|
|
28
|
+
|
|
29
|
+
kernel = Extension(
|
|
30
|
+
"scm2cpp_lasso._lasso_cov",
|
|
31
|
+
sources=[GEN_REL + "/lasso_cov_capi.cpp"],
|
|
32
|
+
include_dirs=[GEN_REL],
|
|
33
|
+
extra_compile_args=(["-O2", "-std=c++17"] if os.name != "nt"
|
|
34
|
+
else ["/O2", "/std:c++17"]),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BuildWithCuda(build_ext):
|
|
39
|
+
def build_extensions(self):
|
|
40
|
+
# the kernel is C++ compiled through the usual machinery, so
|
|
41
|
+
# wheels and cross-compilation behave as they always do
|
|
42
|
+
super().build_extensions()
|
|
43
|
+
if os.environ.get("SCM2CPP_NO_CUDA"):
|
|
44
|
+
return
|
|
45
|
+
nvcc = shutil.which("nvcc")
|
|
46
|
+
if not nvcc:
|
|
47
|
+
return
|
|
48
|
+
out = Path(self.build_lib) / "scm2cpp_lasso" / "libscm2cpp_batch.so"
|
|
49
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
cmd = [nvcc, "-O2", "-std=c++17", "-shared",
|
|
51
|
+
"-Xcompiler", "-fPIC",
|
|
52
|
+
"-I", str(PKG), "-I", str(GEN),
|
|
53
|
+
str(PKG / "batch_capi.cu"), "-o", str(out),
|
|
54
|
+
"-Wno-deprecated-gpu-targets", "-diag-suppress", "174"]
|
|
55
|
+
try:
|
|
56
|
+
subprocess.run(cmd, check=True)
|
|
57
|
+
print("scm2cpp_lasso: built the CUDA batch path")
|
|
58
|
+
except (subprocess.CalledProcessError, OSError) as exc:
|
|
59
|
+
print(f"scm2cpp_lasso: no CUDA batch path ({exc}); "
|
|
60
|
+
"the CPU solver is unaffected", file=sys.stderr)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
setup(ext_modules=[kernel], cmdclass={"build_ext": BuildWithCuda})
|