cellarr-array 0.1.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.

Potentially problematic release.


This version of cellarr-array might be problematic. Click here for more details.

@@ -0,0 +1,230 @@
1
+ from typing import Optional
2
+ from warnings import warn
3
+
4
+ import scipy.sparse as sp
5
+ import tiledb
6
+ import torch
7
+ from torch.utils.data import DataLoader, Dataset
8
+
9
+ from ..core.sparse import SparseCellArray
10
+
11
+ __author__ = "Jayaram Kancherla"
12
+ __copyright__ = "Jayaram Kancherla"
13
+ __license__ = "MIT"
14
+
15
+
16
+ class SparseArrayDataset(Dataset):
17
+ def __init__(
18
+ self,
19
+ array_uri: str,
20
+ attribute_name: str = "data",
21
+ num_rows: Optional[int] = None,
22
+ num_columns: Optional[int] = None,
23
+ sparse_format=sp.csr_matrix,
24
+ cellarr_ctx_config: Optional[dict] = None,
25
+ transform=None,
26
+ ):
27
+ """PyTorch Dataset for sparse TileDB arrays accessed via SparseCellArray.
28
+
29
+ Args:
30
+ array_uri:
31
+ URI of the TileDB sparse array.
32
+
33
+ attribute_name:
34
+ Name of the attribute to read from.
35
+
36
+ num_rows:
37
+ Total number of rows in the dataset.
38
+ If None, will infer from `array.shape[0]`.
39
+
40
+ num_columns:
41
+ The number of columns in the dataset.
42
+ If None, will attempt to infer `from array.shape[1]`.
43
+
44
+ sparse_format:
45
+ Format to return, defaults to csr_matrix.
46
+
47
+ cellarr_ctx_config:
48
+ Optional TileDB context configuration dict for CellArray.
49
+
50
+ transform:
51
+ Optional transform to be applied on a sample.
52
+ """
53
+ self.array_uri = array_uri
54
+ self.attribute_name = attribute_name
55
+ self.sparse_format = sparse_format
56
+ self.cellarr_ctx_config = cellarr_ctx_config
57
+ self.transform = transform
58
+ self.cell_array_instance = None
59
+
60
+ if num_rows is not None and num_columns is not None:
61
+ self._len = num_rows
62
+ self.num_columns = num_columns
63
+ else:
64
+ print(f"Dataset '{array_uri}': num_rows or num_columns not provided. Probing sparse array...")
65
+ init_ctx_config = tiledb.Config(self.cellarr_ctx_config) if self.cellarr_ctx_config else None
66
+ try:
67
+ temp_arr = SparseCellArray(
68
+ uri=self.array_uri,
69
+ attr=self.attribute_name,
70
+ config_or_context=init_ctx_config,
71
+ return_sparse=True,
72
+ sparse_format=self.sparse_format,
73
+ )
74
+
75
+ if temp_arr.ndim == 1:
76
+ self._len = num_rows if num_rows is not None else temp_arr.shape[0]
77
+ self.num_columns = 1
78
+ elif temp_arr.ndim == 2:
79
+ self._len = num_rows if num_rows is not None else temp_arr.shape[0]
80
+ self.num_columns = num_columns if num_columns is not None else temp_arr.shape[1]
81
+ else:
82
+ raise ValueError(f"Array ndim {temp_arr.ndim} not supported.")
83
+
84
+ print(f"Dataset '{array_uri}': Inferred sparse shape. Rows: {self._len}, Columns: {self.num_columns}")
85
+
86
+ except Exception as e:
87
+ if num_rows is None or num_columns is None:
88
+ raise ValueError(
89
+ f"num_rows and num_columns must be provided if inferring sparse array shape fails for '{array_uri}'. Original error: {e}"
90
+ ) from e
91
+ self._len = num_rows if num_rows is not None else 0
92
+ self.num_columns = num_columns if num_columns is not None else 0
93
+ warn(
94
+ f"Falling back to provided or zero dimensions for sparse '{array_uri}' due to inference error: {e}",
95
+ RuntimeWarning,
96
+ )
97
+
98
+ if self.num_columns is None or self.num_columns <= 0 and self._len > 0:
99
+ raise ValueError(
100
+ f"num_columns ({self.num_columns}) is invalid or could not be determined for sparse array '{array_uri}'."
101
+ )
102
+
103
+ if self._len == 0:
104
+ warn(f"SparseDataset for '{array_uri}' has length 0.", RuntimeWarning)
105
+
106
+ def _init_worker_state(self):
107
+ if self.cell_array_instance is None:
108
+ ctx = tiledb.Ctx(self.cellarr_ctx_config) if self.cellarr_ctx_config else None
109
+ self.cell_array_instance = SparseCellArray(
110
+ uri=self.array_uri,
111
+ attr=self.attribute_name,
112
+ mode="r",
113
+ config_or_context=ctx,
114
+ return_sparse=True,
115
+ sparse_coerce=self.sparse_format,
116
+ )
117
+
118
+ def __len__(self):
119
+ return self._len
120
+
121
+ def __getitem__(self, idx):
122
+ if not 0 <= idx < self._len:
123
+ raise IndexError(f"Index {idx} out of bounds for dataset of length {self._len}.")
124
+
125
+ self._init_worker_state()
126
+
127
+ item_slice = (slice(idx, idx + 1), slice(None))
128
+
129
+ scipy_sparse_sample = self.cell_array_instance[item_slice]
130
+
131
+ if self.transform: # e.g., convert to COO for easier collation
132
+ scipy_sparse_sample = self.transform(scipy_sparse_sample)
133
+
134
+ if not isinstance(scipy_sparse_sample, sp.coo_matrix):
135
+ scipy_sparse_sample = scipy_sparse_sample.tocoo()
136
+
137
+ return scipy_sparse_sample
138
+
139
+
140
+ def sparse_coo_collate_fn(batch):
141
+ """Custom collate_fn for a batch of SciPy COO sparse matrices.
142
+
143
+ Converts them into a single batched PyTorch sparse COO tensor.
144
+
145
+ Each item in 'batch' is a SciPy coo_matrix representing one sample.
146
+ """
147
+ all_data = []
148
+ all_row_indices = []
149
+ all_col_indices = []
150
+
151
+ for i, scipy_coo in enumerate(batch):
152
+ if scipy_coo.nnz > 0:
153
+ all_data.append(torch.from_numpy(scipy_coo.data))
154
+ all_row_indices.append(torch.full_like(torch.from_numpy(scipy_coo.row), fill_value=i, dtype=torch.long))
155
+ all_col_indices.append(torch.from_numpy(scipy_coo.col))
156
+
157
+ if not all_data:
158
+ num_columns = batch[0].shape[1] if batch else 0
159
+ return torch.sparse_coo_tensor(torch.empty((2, 0), dtype=torch.long), torch.empty(0), (len(batch), num_columns))
160
+
161
+ data_cat = torch.cat(all_data)
162
+ row_indices_cat = torch.cat(all_row_indices)
163
+ col_indices_cat = torch.cat(all_col_indices)
164
+
165
+ indices = torch.stack([row_indices_cat, col_indices_cat], dim=0)
166
+ num_columns = batch[0].shape[1]
167
+ batch_size = len(batch)
168
+
169
+ sparse_tensor = torch.sparse_coo_tensor(indices, data_cat, (batch_size, num_columns))
170
+ return sparse_tensor
171
+
172
+
173
+ def construct_sparse_array_dataloader(
174
+ array_uri: str,
175
+ attribute_name: str = "data",
176
+ num_rows: Optional[int] = None,
177
+ num_columns: Optional[int] = None,
178
+ batch_size: int = 1000,
179
+ num_workers_dl: int = 2,
180
+ ) -> DataLoader:
181
+ """Construct an instance of `SparseArrayDataset` with PyTorch DataLoader.
182
+
183
+ Args:
184
+ array_uri:
185
+ URI of the TileDB array.
186
+
187
+ attribute_name:
188
+ Name of the attribute to read from.
189
+
190
+ num_rows:
191
+ The total number of rows in the TileDB array.
192
+
193
+ num_columns:
194
+ The total number of columns in the TileDB array.
195
+
196
+ batch_size:
197
+ Number of random samples per batch generated by the dataset.
198
+
199
+ num_workers_dl:
200
+ Number of worker processes for the DataLoader.
201
+ """
202
+ tiledb_ctx_config = {
203
+ "sm.tile_cache_size": 1000 * 1024**2,
204
+ "sm.num_reader_threads": 4,
205
+ }
206
+
207
+ dataset = SparseArrayDataset(
208
+ array_uri=array_uri,
209
+ attribute_name=attribute_name,
210
+ num_rows=num_rows,
211
+ num_columns=num_columns,
212
+ sparse_format=sp.coo_matrix,
213
+ cellarr_ctx_config=tiledb_ctx_config,
214
+ )
215
+
216
+ if len(dataset) == 0:
217
+ print("Dataset is empty, cannot create DataLoader.")
218
+ return
219
+
220
+ dataloader = DataLoader(
221
+ dataset,
222
+ batch_size=batch_size,
223
+ shuffle=True,
224
+ num_workers=num_workers_dl,
225
+ collate_fn=sparse_coo_collate_fn,
226
+ pin_memory=False,
227
+ persistent_workers=True if num_workers_dl > 0 else False,
228
+ )
229
+
230
+ return dataloader
@@ -0,0 +1,26 @@
1
+ import random
2
+
3
+ import numpy as np
4
+
5
+ __author__ = "Jayaram Kancherla"
6
+ __copyright__ = "Jayaram Kancherla"
7
+ __license__ = "MIT"
8
+
9
+
10
+ def seed_worker(worker_id: int):
11
+ """Generate seeds for a PyTorch DataLoader worker.
12
+
13
+ This ensures that if multiple workers are sampling randomly, they use
14
+ different sequences of random numbers.
15
+
16
+ Args:
17
+ worker_id:
18
+ The ID of the worker process.
19
+ """
20
+
21
+ import torch
22
+
23
+ worker_seed = torch.initial_seed() % 2**32
24
+ np.random.seed(worker_seed)
25
+ random.seed(worker_seed)
26
+ # print(f"Worker {worker_id} seeded with {worker_seed}")
@@ -0,0 +1,3 @@
1
+ from .config import CellArrConfig, ConsolidationConfig
2
+ from ..core.helpers import create_cellarray
3
+ # from .mock import generate_tiledb_dense_array, generate_tiledb_sparse_array
@@ -0,0 +1,167 @@
1
+ import shutil
2
+ from typing import Dict, Optional
3
+
4
+ import numpy as np
5
+ import scipy.sparse as sp
6
+ import tiledb
7
+
8
+ from ..core import DenseCellArray, SparseCellArray
9
+ from ..core.helpers import CellArrConfig, create_cellarray
10
+
11
+ __author__ = "Jayaram Kancherla"
12
+ __copyright__ = "Jayaram Kancherla"
13
+ __license__ = "MIT"
14
+
15
+
16
+ def generate_tiledb_dense_array(
17
+ uri: str,
18
+ rows: int,
19
+ cols: int,
20
+ attr_name: str = "data",
21
+ attr_dtype: np.dtype = np.float32,
22
+ chunk_size: int = 1000,
23
+ tiledb_config: Optional[Dict] = None,
24
+ ):
25
+ """Generates a dense TileDB array and fills it with random float32 data.
26
+
27
+ Args:
28
+ uri:
29
+ URI for the new TileDB array.
30
+
31
+ rows:
32
+ Number of rows.
33
+
34
+ cols:
35
+ Number of columns (features).
36
+
37
+ attr_name:
38
+ Name of the attribute.
39
+
40
+ attr_dtype:
41
+ Data type of the attribute.
42
+
43
+ chunk_size:
44
+ Number of rows to write per batch.
45
+
46
+ tiledb_config:
47
+ TileDB context configuration.
48
+ """
49
+ if tiledb.array_exists(uri):
50
+ print(f"Array {uri} already exists. Removing.")
51
+ shutil.rmtree(uri)
52
+
53
+ print(f"Creating dense array at '{uri}' with shape ({rows}, {cols})")
54
+ cfg = CellArrConfig(ctx_config=tiledb_config if tiledb_config else {})
55
+
56
+ create_cellarray(
57
+ uri=uri,
58
+ shape=(rows, cols),
59
+ attr_dtype=attr_dtype,
60
+ sparse=False,
61
+ dim_names=["rows", "cols"],
62
+ attr_name=attr_name,
63
+ # config=cfg
64
+ )
65
+
66
+ ctx = tiledb.Ctx(cfg.ctx_config) if cfg.ctx_config else None
67
+ arr_writer = DenseCellArray(uri=uri, attr=attr_name, mode="w", config_or_context=ctx)
68
+
69
+ print("shape of writer", arr_writer.shape)
70
+
71
+ print(f"Writing data to dense array '{uri}'...")
72
+ for i in range(0, rows, chunk_size):
73
+ end_row = min(i + chunk_size, rows)
74
+ num_chunk_rows = end_row - i
75
+ data_chunk = np.random.rand(num_chunk_rows, cols).astype(attr_dtype)
76
+ print(i, end_row, num_chunk_rows, data_chunk.shape)
77
+ arr_writer.write_batch(data_chunk, start_row=i)
78
+ if (i // chunk_size) % 10 == 0:
79
+ print(f" Dense write: {end_row}/{rows} rows written.")
80
+
81
+ print(f"Finished writing to dense array '{uri}'.")
82
+
83
+
84
+ def generate_tiledb_sparse_array(
85
+ uri: str,
86
+ rows: int,
87
+ cols: int,
88
+ density: float = 0.01,
89
+ attr_name: str = "data",
90
+ attr_dtype: np.dtype = np.float32,
91
+ chunk_size: int = 1000,
92
+ tiledb_config: Optional[Dict] = None,
93
+ sparse_format_to_write="coo",
94
+ ):
95
+ """Generates a sparse TileDB array and fills it with random float32 data.
96
+
97
+ Args:
98
+ uri:
99
+ URI for the new TileDB array.
100
+
101
+ rows:
102
+ Number of rows.
103
+
104
+ cols:
105
+ Number of columns (features).
106
+
107
+ density:
108
+ Density of the sparse matrix.
109
+
110
+ attr_name:
111
+ Name of the attribute.
112
+
113
+ attr_dtype:
114
+ Data type of the attribute.
115
+
116
+ chunk_size:
117
+ Number of rows to generate and write per batch.
118
+
119
+ tiledb_configs:
120
+ TileDB context configuration.
121
+
122
+ sparse_format_to_write:
123
+ Scipy sparse format to use for generating chunks ('coo', 'csr', 'csc').
124
+
125
+ """
126
+ if tiledb.array_exists(uri):
127
+ print(f"Array {uri} already exists. Removing.")
128
+ shutil.rmtree(uri)
129
+
130
+ print(f"Creating sparse array at '{uri}' with shape ({rows}, {cols}), density ~{density}")
131
+ cfg = CellArrConfig(ctx_config=tiledb_config if tiledb_config else {})
132
+ create_cellarray(
133
+ uri=uri,
134
+ shape=(rows, cols),
135
+ attr_dtype=attr_dtype,
136
+ sparse=True,
137
+ dim_names=["rows", "cols"],
138
+ attr_name=attr_name,
139
+ # config=cfg
140
+ )
141
+
142
+ ctx = tiledb.Ctx(cfg.ctx_config) if cfg.ctx_config else None
143
+ arr_writer = SparseCellArray(
144
+ uri=uri,
145
+ attr=attr_name,
146
+ mode="w",
147
+ config_or_context=ctx,
148
+ )
149
+
150
+ print(f"Writing data to sparse array '{uri}'...")
151
+ for i in range(0, rows, chunk_size):
152
+ end_row = min(i + chunk_size, rows)
153
+ num_chunk_rows = end_row - i
154
+ if num_chunk_rows <= 0:
155
+ continue
156
+
157
+ data_chunk_scipy = sp.random(
158
+ num_chunk_rows, cols, density=density, format=sparse_format_to_write, dtype=attr_dtype
159
+ )
160
+
161
+ if data_chunk_scipy.nnz > 0:
162
+ arr_writer.write_batch(data_chunk_scipy, start_row=i)
163
+
164
+ if (i // chunk_size) % 10 == 0:
165
+ print(f" Sparse write: {end_row}/{rows} rows processed for writing.")
166
+
167
+ print(f"Finished writing to sparse array '{uri}'.")
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cellarr-array
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: Base class for handling TileDB backed arrays.
5
5
  Home-page: https://github.com/cellarr/cellarr-array
6
6
  Author: Jayaram Kancherla
7
7
  Author-email: jayaram.kancherla@gmail.com
8
8
  License: MIT
9
9
  Project-URL: Documentation, https://github.com/cellarr/cellarr-array
10
+ Project-URL: Source, https://github.com/cellarr/cellarr-array
10
11
  Platform: any
11
12
  Classifier: Development Status :: 4 - Beta
12
13
  Classifier: Programming Language :: Python
@@ -16,10 +17,14 @@ Requires-Dist: importlib-metadata; python_version < "3.8"
16
17
  Requires-Dist: tiledb
17
18
  Requires-Dist: numpy
18
19
  Requires-Dist: scipy
20
+ Provides-Extra: optional
21
+ Requires-Dist: torch; extra == "optional"
19
22
  Provides-Extra: testing
20
23
  Requires-Dist: setuptools; extra == "testing"
21
24
  Requires-Dist: pytest; extra == "testing"
22
25
  Requires-Dist: pytest-cov; extra == "testing"
26
+ Requires-Dist: pandas; extra == "testing"
27
+ Requires-Dist: torch; extra == "testing"
23
28
  Dynamic: license-file
24
29
 
25
30
  [![PyPI-Server](https://img.shields.io/pypi/v/cellarr-array.svg)](https://pypi.org/project/cellarr-array/)
@@ -0,0 +1,19 @@
1
+ cellarr_array/__init__.py,sha256=vo-WXpnb83eJPitY2PgOaASHSfLqECF_UFM3YcbPTIs,732
2
+ cellarr_array/core/__init__.py,sha256=fvM-FEiDn8TKDbHxhhzp9FXZFNovFwvIUSY6SpLQRdk,98
3
+ cellarr_array/core/base.py,sha256=zzG76-zN2oJ7BHldT9ohQhklD-paOYnY0u7_w1DzXR8,15188
4
+ cellarr_array/core/dense.py,sha256=LODRH4utpKs8xhT79Q2-nRiam_s68_a0qPj0unEM7rg,3940
5
+ cellarr_array/core/helpers.py,sha256=w1yXi7eJiL9D1vzpSp_eQbTVnyKRFp5TBpq1DLj4k9U,8262
6
+ cellarr_array/core/sparse.py,sha256=cBxdN7a-fAsQWT6Kc6EqV_5yuEiZ-mtJzSiETKsxgmA,8814
7
+ cellarr_array/dataloaders/__init__.py,sha256=U-MfwC2K84OIXT75in41fe_wvoxjUC5Krb5zICQn_O8,245
8
+ cellarr_array/dataloaders/denseloader.py,sha256=JYJlbuX5My64iIPW_-nlPFkNIezxL3Z3mkwInS3hH9M,7291
9
+ cellarr_array/dataloaders/iterabledataloader.py,sha256=lR2T1YatyBlDM5Sy_75B7_8ORiWfn3cp4q48Oujwf-c,11916
10
+ cellarr_array/dataloaders/sparseloader.py,sha256=V_eKw-Z_CNxHP8c2BN3sOuuv6RPiWBzRfW1BYLhNaQc,7962
11
+ cellarr_array/dataloaders/utils.py,sha256=buJ87x1YBTt5-nZoy_I5j6ko1lVlHdiGpQCusdLoRLI,600
12
+ cellarr_array/utils/__init__.py,sha256=DM5jeUMbxbRzTu2QCjpLlrTQ5uionF887S_7i6_952U,177
13
+ cellarr_array/utils/config.py,sha256=67zBxpYY9N_v6TMdyljUIZmckbwOBcuLC99aJooGmfA,2917
14
+ cellarr_array/utils/mock.py,sha256=7GyCbtM7u94pm7qhjsPRSO2IWYLmd4UrjyvLnQtMMkc,4579
15
+ cellarr_array-0.3.0.dist-info/licenses/LICENSE.txt,sha256=JUlHIfWcRe_MZop18pQvMIPLKSSPz3XQ06ASHuW5Wh8,1076
16
+ cellarr_array-0.3.0.dist-info/METADATA,sha256=J9LgoIMWYKpXNwLEyhxfsIKLtkUitF-0RACpaqzJy7c,4332
17
+ cellarr_array-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
+ cellarr_array-0.3.0.dist-info/top_level.txt,sha256=oErp0D8ABZV-QPtTiXT8_F2z36Ic7ykuDg_1Y84HLZM,14
19
+ cellarr_array-0.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,11 +0,0 @@
1
- cellarr_array/__init__.py,sha256=iCU5zmXXmTwk-VuwrTdVl5STRAL2xeYpq05fL9_bW6w,781
2
- cellarr_array/cellarray_base.py,sha256=CSYsA_Ra-RcwsyHzwayL-w10EhpbIC3u7ZAbyQMO6ks,13451
3
- cellarr_array/cellarray_dense.py,sha256=skunPy_WyOMuS_3SxcAW_gm8d5FiWeV7ZCQp4HLRUUY,3958
4
- cellarr_array/cellarray_sparse.py,sha256=YYZymvWGDG1c2EeOLMBPP5_u4qM8uhxyWJY6PnFWMVo,9112
5
- cellarr_array/config.py,sha256=67zBxpYY9N_v6TMdyljUIZmckbwOBcuLC99aJooGmfA,2917
6
- cellarr_array/helpers.py,sha256=eIeymmvY4KZ-cAiROo3DcYYzP39NQBj-4Nrba9rrEKQ,6491
7
- cellarr_array-0.1.0.dist-info/licenses/LICENSE.txt,sha256=JUlHIfWcRe_MZop18pQvMIPLKSSPz3XQ06ASHuW5Wh8,1076
8
- cellarr_array-0.1.0.dist-info/METADATA,sha256=ELBRCXkEyxhPeGHlA62i2QIzz7yYlLUSy7bfOe6aAdE,4120
9
- cellarr_array-0.1.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
10
- cellarr_array-0.1.0.dist-info/top_level.txt,sha256=oErp0D8ABZV-QPtTiXT8_F2z36Ic7ykuDg_1Y84HLZM,14
11
- cellarr_array-0.1.0.dist-info/RECORD,,
File without changes