ggml-ot 0.9.1__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.
ggml_ot-0.9.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Damin Kühn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ggml_ot-0.9.1/PKG-INFO ADDED
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.3
2
+ Name: ggml-ot
3
+ Version: 0.9.1
4
+ Summary: Global Ground Metric Learning
5
+ Author: Damin Kuehn
6
+ Author-email: kuehn@cs.rwth-aachen.de
7
+ Requires-Python: >=3.11
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Framework :: Sphinx
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Intended Audience :: Healthcare Industry
13
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
14
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
15
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
16
+ Requires-Dist: anndata (>=0.11.4,<0.12.0)
17
+ Requires-Dist: matplotlib (>=3.10.1,<4.0.0)
18
+ Requires-Dist: numpy (>=2.2.4,<3.0.0)
19
+ Requires-Dist: pandas (>=2.2.3,<3.0.0)
20
+ Requires-Dist: pot (>=0.9.5,<0.10.0)
21
+ Requires-Dist: pydiffmap (>=0.2.0.1,<0.3.0.0)
22
+ Requires-Dist: scanpy (>=1.11.1,<2.0.0)
23
+ Requires-Dist: scikit-learn (>=1.1,<1.6)
24
+ Requires-Dist: scipy (>=1.15.2,<2.0.0)
25
+ Requires-Dist: seaborn (>=0.13.2,<0.14.0)
26
+ Requires-Dist: torch (>=2.6.0,<3.0.0)
27
+ Requires-Dist: tqdm (>=4.67.1,<5.0.0)
28
+ Requires-Dist: umap-learn (>=0.5.7,<0.6.0)
29
+ Project-URL: Homepage, https://github.com/DaminK/ggml-ot
30
+ Project-URL: Issues, https://github.com/DaminK/ggml-ot/issues
31
+ Description-Content-Type: text/markdown
32
+
33
+ # GGML-OT
34
+
35
+ <img src="https://github.com/user-attachments/assets/5c522e9b-8eb3-4b0a-8ae5-bb8671eba3d6" width="300" />
36
+
37
+ ## Abstract
38
+
39
+ Optimal transport (OT) provides a robust framework for comparing probability distributions.
40
+ Its effectiveness is significantly influenced by the choice of the underlying ground metric.
41
+ Traditionally, the ground metric has either been (i) predefined, e.g. as a Euclidean metric, or (ii) learned in a supervised way, by utilizing labeled data to learn a suitable ground metric for enhanced task-specific performance.
42
+ While predefined metrics often do not account for the inherent structure and varying significance of different features in the data, existing supervised ground metric learning methods often fail to generalize across multiple classes or are limited to distributions with shared supports.
43
+ To address this issue, this paper introduces a novel approach for learning metrics for arbitrary distributions over a shared metric space.
44
+ Our method differentiates elements like a global metric, but requires only class labels on a distribution-level for training akin a ground metric.
45
+ The resulting learned global ground metric enables more accurate OT distances, which can significantly improve clustering and classification tasks. It can create task-specific shared embeddings across elements of different distributions including unseen data.
46
+
47
+ ## Installation
48
+
49
+ ### Via pip
50
+
51
+ ```terminal
52
+ pip install ggml-ot
53
+ ```
54
+
55
+ ### Manual
56
+
57
+ ```terminal
58
+ git clone https://github.com/DaminK/ggml-ot
59
+ cd ggml-ot
60
+ pip install poetry
61
+ poetry lock && poetry install
62
+ ```
63
+
64
+ ### Development installation
65
+
66
+ ```terminal
67
+ git clone https://github.com/DaminK/ggml-ot
68
+ cd ggml-ot
69
+ pip install poetry
70
+ peotry lock && poetry install --with dev
71
+ pre-commit install
72
+ ```
73
+
74
+ ## Citation
75
+
76
+ If you use this code in your research, please cite the following paper:
77
+ > Global Ground Metric Learning with Applications to scRNA data
78
+ >
79
+ > Damin Kuehn and Michael T. Schaub, Department of Computer Science RWTH Aachen
80
+ >
81
+ > Published at AISTATS2025 (DOI will follow)
82
+
@@ -0,0 +1,49 @@
1
+ # GGML-OT
2
+
3
+ <img src="https://github.com/user-attachments/assets/5c522e9b-8eb3-4b0a-8ae5-bb8671eba3d6" width="300" />
4
+
5
+ ## Abstract
6
+
7
+ Optimal transport (OT) provides a robust framework for comparing probability distributions.
8
+ Its effectiveness is significantly influenced by the choice of the underlying ground metric.
9
+ Traditionally, the ground metric has either been (i) predefined, e.g. as a Euclidean metric, or (ii) learned in a supervised way, by utilizing labeled data to learn a suitable ground metric for enhanced task-specific performance.
10
+ While predefined metrics often do not account for the inherent structure and varying significance of different features in the data, existing supervised ground metric learning methods often fail to generalize across multiple classes or are limited to distributions with shared supports.
11
+ To address this issue, this paper introduces a novel approach for learning metrics for arbitrary distributions over a shared metric space.
12
+ Our method differentiates elements like a global metric, but requires only class labels on a distribution-level for training akin a ground metric.
13
+ The resulting learned global ground metric enables more accurate OT distances, which can significantly improve clustering and classification tasks. It can create task-specific shared embeddings across elements of different distributions including unseen data.
14
+
15
+ ## Installation
16
+
17
+ ### Via pip
18
+
19
+ ```terminal
20
+ pip install ggml-ot
21
+ ```
22
+
23
+ ### Manual
24
+
25
+ ```terminal
26
+ git clone https://github.com/DaminK/ggml-ot
27
+ cd ggml-ot
28
+ pip install poetry
29
+ poetry lock && poetry install
30
+ ```
31
+
32
+ ### Development installation
33
+
34
+ ```terminal
35
+ git clone https://github.com/DaminK/ggml-ot
36
+ cd ggml-ot
37
+ pip install poetry
38
+ peotry lock && poetry install --with dev
39
+ pre-commit install
40
+ ```
41
+
42
+ ## Citation
43
+
44
+ If you use this code in your research, please cite the following paper:
45
+ > Global Ground Metric Learning with Applications to scRNA data
46
+ >
47
+ > Damin Kuehn and Michael T. Schaub, Department of Computer Science RWTH Aachen
48
+ >
49
+ > Published at AISTATS2025 (DOI will follow)
@@ -0,0 +1,78 @@
1
+ [project]
2
+ name = "ggml-ot"
3
+ authors = [{ name = "Damin Kuehn", email = "kuehn@cs.rwth-aachen.de" }]
4
+ readme = "README.md"
5
+ requires-python = ">=3.11"
6
+ classifiers = [
7
+ "Programming Language :: Python :: 3",
8
+ "Development Status :: 5 - Production/Stable",
9
+ "Framework :: Sphinx",
10
+ "Intended Audience :: Science/Research",
11
+ "Intended Audience :: Healthcare Industry",
12
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
13
+ "Topic :: Scientific/Engineering :: Information Analysis",
14
+ "Topic :: Scientific/Engineering :: Medical Science Apps.",
15
+ ]
16
+ dependencies = [
17
+ "tqdm (>=4.67.1,<5.0.0)",
18
+ "scipy (>=1.15.2,<2.0.0)",
19
+ "matplotlib (>=3.10.1,<4.0.0)",
20
+ "seaborn (>=0.13.2,<0.14.0)",
21
+ "umap-learn (>=0.5.7,<0.6.0)",
22
+ "numpy (>=2.2.4,<3.0.0)",
23
+ "pandas (>=2.2.3,<3.0.0)",
24
+ "pot (>=0.9.5,<0.10.0)",
25
+ "pydiffmap (>=0.2.0.1,<0.3.0.0)",
26
+ "anndata (>=0.11.4,<0.12.0)",
27
+ "torch (>=2.6.0,<3.0.0)",
28
+ "scanpy (>=1.11.1,<2.0.0)",
29
+ "scikit-learn (>=1.1,<1.6)",
30
+ ]
31
+ [project.urls]
32
+ Homepage = "https://github.com/DaminK/ggml-ot"
33
+ Issues = "https://github.com/DaminK/ggml-ot/issues"
34
+
35
+ [tool.poetry]
36
+ name = "ggml-ot"
37
+ version = "0.9.1"
38
+ description = "Global Ground Metric Learning"
39
+ authors = ["Damin Kuehn <damin.kuehn@rwth-aachen.de>"]
40
+
41
+ [tool.poetry.group.dev]
42
+ optional = true
43
+
44
+ [tool.poetry.group.dev.dependencies]
45
+ pre-commit = "^3.2.1"
46
+ ipykernel = "^6.29.5"
47
+
48
+ [tool.poetry.group.docs]
49
+ optional = true
50
+
51
+ [tool.poetry.group.docs.dependencies]
52
+ sphinx = "^8.2.3"
53
+ sphinx-autorun = "^2.0.0"
54
+ sphinx-rtd-theme = "^3.0.2"
55
+ myst-parser = "^4.0.1"
56
+
57
+ [tool.poetry.group.test]
58
+ optional = true
59
+
60
+ [tool.poetry.group.test.dependencies]
61
+ pytest = "^8.3.5"
62
+ pytest-cov = "^6.1.1"
63
+ ruff = "^0.11.6"
64
+
65
+ [tool.ruff]
66
+ select = ["E4", "E7", "E9", "F"]
67
+ task-tags = ["TODO", "FIXME"]
68
+ output-format = "github"
69
+ respect-gitignore = true
70
+
71
+ [tool.ruff.format]
72
+ quote-style = "double"
73
+ docstring-code-format = true
74
+
75
+ [build-system]
76
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
77
+ build-backend = "poetry.core.masonry.api"
78
+
@@ -0,0 +1,9 @@
1
+ from .data import scRNA_Dataset, synthetic_Dataset
2
+ from .plot import (
3
+ plot_emb,
4
+ plot_heatmap,
5
+ plot_clustermap,
6
+ plot_distribution,
7
+ plot_ellipses,
8
+ )
9
+ from .ggml import ggml
@@ -0,0 +1,2 @@
1
+ from .knn import *
2
+ from .pivoted_chol import *
@@ -0,0 +1,258 @@
1
+ from sklearn.metrics import confusion_matrix
2
+ from sklearn.model_selection import StratifiedShuffleSplit, GroupShuffleSplit
3
+ from sklearn.neighbors import KNeighborsClassifier
4
+ from sklearn.metrics import silhouette_score
5
+ from sklearn.metrics.cluster import adjusted_rand_score
6
+
7
+ import numpy as np
8
+ import seaborn as sns
9
+ import numpy.typing as npt
10
+ import matplotlib.pyplot as plt
11
+ from IPython.display import display
12
+ import warnings
13
+
14
+
15
+ def ShuffleSplit(
16
+ labels,
17
+ n_splits=10,
18
+ train_size=0.4,
19
+ test_size=0.4,
20
+ validation_size=0.2,
21
+ distribution_labels=None,
22
+ ):
23
+ if validation_size > 0:
24
+ # draw validation inds in test split and later split into two test sets
25
+ test_size = test_size + validation_size
26
+ if n_splits > 0:
27
+ if distribution_labels is None:
28
+ sss = StratifiedShuffleSplit(
29
+ n_splits=n_splits,
30
+ test_size=test_size,
31
+ train_size=train_size,
32
+ random_state=0,
33
+ )
34
+ train_test_inds = sss.split(np.zeros(len(labels)), labels)
35
+ else:
36
+ # split patients into test and train,should be stratified
37
+ # distribution_labels
38
+
39
+ # unique_distribution_labels = np.unique(distribution_labels)
40
+ # distribution_train_test_inds = sss.split(np.zeros(len(unique_distribution_labels)), unique_distribution_labels)
41
+
42
+ gss = GroupShuffleSplit(
43
+ n_splits=n_splits,
44
+ test_size=test_size,
45
+ train_size=train_size,
46
+ random_state=0,
47
+ )
48
+ print('shapes')
49
+ print(np.zeros(len(labels)).shape)
50
+ print(labels.shape)
51
+ print(distribution_labels.shape)
52
+ train_test_inds = gss.split(
53
+ np.zeros(len(labels)), labels, distribution_labels
54
+ )
55
+
56
+ # train_test_inds = []
57
+ # for i, (train_distribution_index, test_distribution_index) in enumerate(distribution_train_test_inds):
58
+ # train_label_ind = [distribution_labels == unique_distribution_labels[train_distribution_index]]
59
+ # test_label_ind = [distribution_labels == unique_distribution_labels[test_distribution_index]]
60
+ # #for each patient split, do test and train splits between cells from both splits
61
+ # train = sss.split(np.zeros(len(labels)), labels)
62
+ # test =
63
+ else:
64
+ # Train = Test
65
+ train_test_inds = [(np.arange(len(labels)), np.arange(len(labels)))]
66
+
67
+ if validation_size > 0:
68
+ test_size = test_size - validation_size
69
+
70
+ return train_test_inds
71
+
72
+
73
+ def knn_from_dists(
74
+ dists,
75
+ labels,
76
+ n_splits=20,
77
+ distribution_labels=None,
78
+ n_neighbors=5,
79
+ weights=None,
80
+ test_size=0.2,
81
+ train_size=None,
82
+ ):
83
+ predicted_labels, true_labels, test_indices, scores = [], [], [], []
84
+
85
+ train_test_inds = ShuffleSplit(
86
+ labels,
87
+ n_splits,
88
+ train_size,
89
+ test_size,
90
+ validation_size=0,
91
+ distribution_labels=distribution_labels,
92
+ )
93
+ # , np.arange(len(labels)))
94
+
95
+ for i, (train_index, test_index) in enumerate(train_test_inds):
96
+ print(train_index)
97
+ print(test_index)
98
+ train_dists = dists[np.ix_(train_index, train_index)]
99
+ test_to_train_dists = dists[np.ix_(test_index, train_index)]
100
+
101
+ neigh = KNeighborsClassifier(
102
+ n_neighbors=n_neighbors, metric='precomputed', weights=weights
103
+ )
104
+ neigh.fit(train_dists, [labels[t] for t in train_index])
105
+
106
+ predicted_labels.append(neigh.predict(test_to_train_dists))
107
+ true_labels.append(np.asarray([labels[t] for t in test_index]))
108
+ scores.append(neigh.score(test_to_train_dists, true_labels[-1]))
109
+ test_indices.append(test_index)
110
+ # ari.append(adjusted_rand_score(predicted_labels[-1],true_labels[-1]))
111
+
112
+ ari = adjusted_rand_score(
113
+ np.concatenate(true_labels), np.concatenate(predicted_labels)
114
+ )
115
+
116
+ return predicted_labels, true_labels, scores, ari, test_indices
117
+
118
+
119
+ def silhouette_score_wrapper(dists, labels):
120
+ # wrapper function as fill diagonal is only available as inplace operation. It is needed to catch cases where due to numerical errors the distance of a graph to itself may be very close to zero, but not zero which is required by sklearn silhoute score method
121
+ zero_dia_dists = np.copy(dists)
122
+ np.fill_diagonal(zero_dia_dists, 0)
123
+ return silhouette_score(zero_dia_dists, labels, metric='precomputed')
124
+
125
+
126
+ def compute_dists(Graphs, Graphs2=None, method='TiedOT'):
127
+ dist, plan = methods[method](Graphs, Graphs2)
128
+ dist[dist < 0] = 0
129
+ return dist
130
+
131
+
132
+ def get_dist_precomputed(precomputed_dists, ind1, ind2):
133
+ return precomputed_dists[ind1, :][:, ind2]
134
+
135
+
136
+ def plot_1split(predicted, true, title=None, ax=None):
137
+ annot_labels_ind = np.unique(true, return_index=True)[1]
138
+ annot_labels = true[annot_labels_ind]
139
+ # ind
140
+ cf_matrix = confusion_matrix(true, predicted, labels=annot_labels)
141
+ if ax is None:
142
+ plt.figure()
143
+ ax = sns.heatmap(
144
+ cf_matrix,
145
+ annot=True, # fmt='.0',
146
+ cmap='Blues',
147
+ xticklabels=annot_labels,
148
+ yticklabels=annot_labels,
149
+ ax=ax,
150
+ fmt='g',
151
+ )
152
+ ax.set(xlabel='Predicted Label', ylabel='True Label')
153
+ ax.set_title(title)
154
+
155
+
156
+ def plot_table(df, tranpose=False):
157
+ format_df = df
158
+ format_df.set_index('method', inplace=True)
159
+ if tranpose:
160
+ format_df = format_df.transpose()
161
+ display(format_df)
162
+ print(
163
+ format_df.to_latex(
164
+ index=True,
165
+ # formatters={"name": str.upper},
166
+ float_format='{:.2f}'.format,
167
+ )
168
+ )
169
+
170
+
171
+ def VI(
172
+ labels1: npt.NDArray[np.int32],
173
+ labels2: npt.NDArray[np.int32],
174
+ torch: bool = True,
175
+ device: str = 'cpu',
176
+ return_split_merge: bool = False,
177
+ ):
178
+ """
179
+ Calculates the Variation of Information between two clusterings.
180
+
181
+ Arguments:
182
+ labels1: flat int32 array of labels for the first clustering
183
+ labels2: flat int32 array of labels for the second clustering
184
+ torch: whether to use torch, default:True
185
+ device: device to use for torch, default:"cpu"
186
+ return_split_merge: whether to return split and merge terms, default:False
187
+
188
+ Returns:
189
+ vi: variation of information
190
+ vi_split: split term of variation of information
191
+ vi_merge: merge term of variation of information
192
+ splitters(optional): labels of labels2 which are split by labels1. splitters[i,0] is the contribution of the i-th splitter to the VI and splitters[i,1] is the corresponding label of the splitter
193
+ mergers(optional): labels of labels1 which are merging labels from labels2. mergers[i,0] is the contribution of the i-th merger to the VI and mergers[i,1] is the corresponding label of the merger
194
+ """
195
+ if labels1.ndim > 1 or labels2.ndim > 1:
196
+ warnings.warn(
197
+ f"Inputs of shape {labels1.shape}, {labels2.shape} are not one-dimensional -- inputs will be flattened."
198
+ )
199
+ labels1 = labels1.flatten()
200
+ labels2 = labels2.flatten()
201
+
202
+ if torch:
203
+ return VI_torch(
204
+ labels1, labels2, device=device, return_split_merge=return_split_merge
205
+ )
206
+ else:
207
+ return VI_np(labels1, labels2, return_split_merge=return_split_merge)
208
+
209
+
210
+ def VI_np(labels1, labels2, return_split_merge=False):
211
+ assert len(labels2) == len(labels1)
212
+ size = len(labels2)
213
+
214
+ mutual_labels = (labels1.astype(np.uint64) << 32) + labels2.astype(np.uint64)
215
+
216
+ sm_unique, sm_inverse, sm_counts = np.unique(
217
+ labels2, return_inverse=True, return_counts=True
218
+ )
219
+ fm_unique, fm_inverse, fm_counts = np.unique(
220
+ labels1, return_inverse=True, return_counts=True
221
+ )
222
+ _, mutual_inverse, mutual_counts = np.unique(
223
+ mutual_labels, return_inverse=True, return_counts=True
224
+ )
225
+
226
+ terms_mutual = -np.log(mutual_counts / size) * mutual_counts / size
227
+ terms_mutual_per_count = (
228
+ terms_mutual[mutual_inverse] / mutual_counts[mutual_inverse]
229
+ )
230
+ terms_sm = -np.log(sm_counts / size) * sm_counts / size
231
+ terms_fm = -np.log(fm_counts / size) * fm_counts / size
232
+ if not return_split_merge:
233
+ terms_mutual_sum = np.sum(terms_mutual_per_count)
234
+ vi_split = terms_mutual_sum - terms_sm.sum()
235
+ vi_merge = terms_mutual_sum - terms_fm.sum()
236
+ vi = vi_split + vi_merge
237
+ return vi, vi_split, vi_merge
238
+
239
+ vi_split_each = np.zeros(len(sm_unique))
240
+ np.add.at(vi_split_each, sm_inverse, terms_mutual_per_count)
241
+ vi_split_each -= terms_sm
242
+ vi_merge_each = np.zeros(len(fm_unique))
243
+ np.add.at(vi_merge_each, fm_inverse, terms_mutual_per_count)
244
+ vi_merge_each -= terms_fm
245
+
246
+ vi_split = np.sum(vi_split_each)
247
+ vi_merge = np.sum(vi_merge_each)
248
+ vi = vi_split + vi_merge
249
+
250
+ i_splitters = np.argsort(vi_split_each)[::-1]
251
+ i_mergers = np.argsort(vi_merge_each)[::-1]
252
+
253
+ vi_split_sorted = vi_split_each[i_splitters]
254
+ vi_merge_sorted = vi_merge_each[i_mergers]
255
+
256
+ splitters = np.stack([vi_split_sorted, sm_unique[i_splitters]], axis=1)
257
+ mergers = np.stack([vi_merge_sorted, fm_unique[i_mergers]], axis=1)
258
+ return vi, vi_split, vi_merge, splitters, mergers
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # pivoted_chol.py Author "Nathan Wycoff <nathanbrwycoff@gmail.com>" Date 01.14.2020
4
+
5
+ ## A pivoted cholesky function for kernel functions
6
+ import numpy as np
7
+
8
+
9
+ def pivoted_chol(get_diag, get_row, M, err_tol=1e-6):
10
+ """
11
+ A simple python function which computes the Pivoted Cholesky decomposition/approximation of positive semi-definite operator. Only diagonal elements and select rows of that operator's matrix represenation are required.
12
+
13
+ get_diag - A function which takes no arguments and returns the diagonal of the matrix when called.
14
+ get_row - A function which takes 1 integer argument and returns the desired row (zero indexed).
15
+ M - The maximum rank of the approximate decomposition; an integer.
16
+ err_tol - The maximum error tolerance, that is difference between the approximate decomposition and true matrix, allowed. Note that this is in the Trace norm, not the spectral or frobenius norm.
17
+
18
+ Returns: R, an upper triangular matrix of column dimension equal to the target matrix. It's row dimension will be at most M, but may be less if the termination condition was acceptably low error rather than max iters reached.
19
+ """
20
+
21
+ d = np.copy(get_diag())
22
+ N = len(d)
23
+
24
+ pi = list(range(N))
25
+
26
+ R = np.zeros([M, N])
27
+
28
+ err = np.sum(np.abs(d))
29
+
30
+ m = 0
31
+ while (m < M) and (err > err_tol):
32
+ i = m + np.argmax([d[pi[j]] for j in range(m, N)])
33
+
34
+ tmp = pi[m]
35
+ pi[m] = pi[i]
36
+ pi[i] = tmp
37
+
38
+ R[m, pi[m]] = np.sqrt(d[pi[m]])
39
+ Apim = get_row(pi[m])
40
+ for i in range(m + 1, N):
41
+ if m > 0:
42
+ ip = np.inner(R[:m, pi[m]], R[:m, pi[i]])
43
+ else:
44
+ ip = 0
45
+ R[m, pi[i]] = (Apim[pi[i]] - ip) / R[m, pi[m]]
46
+ d[pi[i]] -= pow(R[m, pi[i]], 2)
47
+
48
+ err = np.sum([d[pi[i]] for i in range(m + 1, N)])
49
+ m += 1
50
+
51
+ R = R[:m, :]
52
+
53
+ return R
54
+
55
+
56
+ # Example:
57
+ N = 10
58
+ S = np.random.normal(size=[N, N])
59
+ A = np.matmul(S.T, S)
60
+
61
+ # Important to copy as diag may not be writable depending on np version
62
+ get_diag = lambda: np.diag(A).copy()
63
+ get_row = lambda i: A[i, :]
64
+ R = pivoted_chol(get_diag, get_row, M=N)
65
+
66
+ # R.T @ R is numerically equal to A.
67
+ print(np.linalg.norm(np.matmul(R.T, R) - A))
@@ -0,0 +1,3 @@
1
+ from .scRNA import * # noqa
2
+ from .synth import * # noqa
3
+ from .util import * # noqa