aspect-data 0.0.3__tar.gz → 0.0.4__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.
Files changed (31) hide show
  1. {aspect_data-0.0.3 → aspect_data-0.0.4}/PKG-INFO +17 -3
  2. {aspect_data-0.0.3 → aspect_data-0.0.4}/README.md +11 -0
  3. aspect_data-0.0.4/aspect/collate/__init__.py +7 -0
  4. aspect_data-0.0.4/aspect/collate/base.py +42 -0
  5. aspect_data-0.0.4/aspect/collate/chemprop.py +115 -0
  6. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/data.py +11 -10
  7. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/typing.py +7 -3
  8. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect_data.egg-info/PKG-INFO +17 -3
  9. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect_data.egg-info/SOURCES.txt +6 -1
  10. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect_data.egg-info/requires.txt +6 -2
  11. {aspect_data-0.0.3 → aspect_data-0.0.4}/pyproject.toml +5 -4
  12. aspect_data-0.0.4/test/test_collate.py +78 -0
  13. aspect_data-0.0.4/test/test_collate_chemprop.py +104 -0
  14. {aspect_data-0.0.3 → aspect_data-0.0.4}/LICENSE +0 -0
  15. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/__init__.py +0 -0
  16. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/checkpoint_utils.py +0 -0
  17. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/cli_module/__init__.py +0 -0
  18. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/cli_module/cli.py +0 -0
  19. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/cli_module/featurize.py +0 -0
  20. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/cli_module/io.py +0 -0
  21. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/io.py +0 -0
  22. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/package_data.py +0 -0
  23. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/transform/__init__.py +0 -0
  24. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/transform/base.py +0 -0
  25. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/transform/deep_functions.py +0 -0
  26. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/transform/functions.py +0 -0
  27. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect/transform/registry.py +0 -0
  28. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect_data.egg-info/dependency_links.txt +0 -0
  29. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect_data.egg-info/entry_points.txt +0 -0
  30. {aspect_data-0.0.3 → aspect_data-0.0.4}/aspect_data.egg-info/top_level.txt +0 -0
  31. {aspect_data-0.0.3 → aspect_data-0.0.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspect-data
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: General purpose, chemistry, and taxonomic featurization of tabular data with caching.
5
5
  Author-email: Eachan Johnson <eachan.johnson@crick.ac.uk>
6
6
  License: MIT License
@@ -48,14 +48,17 @@ Requires-Dist: platformdirs>=4.4.0
48
48
  Requires-Dist: tqdm
49
49
  Provides-Extra: chem
50
50
  Requires-Dist: schemist>=0.0.6; extra == "chem"
51
+ Provides-Extra: torch
52
+ Requires-Dist: torch>=2.4; extra == "torch"
51
53
  Provides-Extra: chemprop
54
+ Requires-Dist: aspect-data[chem,torch]; extra == "chemprop"
52
55
  Requires-Dist: chemprop>=2.1.0; extra == "chemprop"
53
56
  Provides-Extra: deep
54
57
  Requires-Dist: transformers; extra == "deep"
55
58
  Provides-Extra: bio
56
- Requires-Dist: vectome>=0.0.4; extra == "bio"
59
+ Requires-Dist: vectome>=0.0.8; extra == "bio"
57
60
  Provides-Extra: dev
58
- Requires-Dist: aspect-data[bio,chem]; extra == "dev"
61
+ Requires-Dist: aspect-data[bio,chemprop]; extra == "dev"
59
62
  Requires-Dist: pytest>=7.4; extra == "dev"
60
63
  Requires-Dist: flake8; extra == "dev"
61
64
  Dynamic: license-file
@@ -66,6 +69,8 @@ Dynamic: license-file
66
69
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aspect-data)
67
70
  ![PyPI](https://img.shields.io/pypi/v/aspect-data)
68
71
 
72
+ <img src="docs/source/_static/logo.svg" width="200">
73
+
69
74
  **aspect** is a lightweight featurisation layer for tabular machine-learning data.
70
75
 
71
76
  It lets you define independent feature pipelines for different input columns, then apply them to dictionaries, Pandas data frames, local files, Hugging Face datasets, or saved dataset checkpoints.
@@ -83,6 +88,15 @@ input table
83
88
  output dataset with named feature columns
84
89
  ```
85
90
 
91
+ Aspect datasets store Arrow-compatible feature representations.
92
+
93
+ Before model execution, an optional collator converts stored examples into
94
+ runtime representations. A runtime batch is always a mapping from feature
95
+ column names to batched values. Values may be tensors or structured mappings.
96
+
97
+ Downstream model libraries should treat runtime batch values as opaque and
98
+ should not perform dataset preprocessing or deserialization.
99
+
86
100
  ---
87
101
 
88
102
  ## Contents
@@ -4,6 +4,8 @@
4
4
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aspect-data)
5
5
  ![PyPI](https://img.shields.io/pypi/v/aspect-data)
6
6
 
7
+ <img src="docs/source/_static/logo.svg" width="200">
8
+
7
9
  **aspect** is a lightweight featurisation layer for tabular machine-learning data.
8
10
 
9
11
  It lets you define independent feature pipelines for different input columns, then apply them to dictionaries, Pandas data frames, local files, Hugging Face datasets, or saved dataset checkpoints.
@@ -21,6 +23,15 @@ input table
21
23
  output dataset with named feature columns
22
24
  ```
23
25
 
26
+ Aspect datasets store Arrow-compatible feature representations.
27
+
28
+ Before model execution, an optional collator converts stored examples into
29
+ runtime representations. A runtime batch is always a mapping from feature
30
+ column names to batched values. Values may be tensors or structured mappings.
31
+
32
+ Downstream model libraries should treat runtime batch values as opaque and
33
+ should not perform dataset preprocessing or deserialization.
34
+
24
35
  ---
25
36
 
26
37
  ## Contents
@@ -0,0 +1,7 @@
1
+ from .base import ColumnCollator
2
+ from .chemprop import chemprop_collate
3
+
4
+ __all__ = [
5
+ "ColumnCollator",
6
+ "chemprop_collate",
7
+ ]
@@ -0,0 +1,42 @@
1
+ """Specialised column collation."""
2
+
3
+ from collections.abc import Callable, Mapping, Iterable
4
+ from typing import Any, TypeAlias
5
+
6
+ from ..typing import Batch
7
+
8
+
9
+ CollateFn: TypeAlias = Callable[[Iterable[Any]], Any]
10
+
11
+
12
+ class ColumnCollator:
13
+ """Override collation for selected dataset columns."""
14
+
15
+ def __init__(
16
+ self,
17
+ collators: Mapping[str, CollateFn] | None = None,
18
+ ):
19
+ self.collators = collators or {}
20
+
21
+ def __call__(
22
+ self,
23
+ rows: Batch,
24
+ ) -> dict[str, ...]:
25
+ from torch.utils.data import default_collate
26
+
27
+ out = {}
28
+ if not rows:
29
+ return out
30
+
31
+ for column in rows[0]:
32
+ values = [
33
+ row[column]
34
+ for row in rows
35
+ ]
36
+
37
+ if column in self.collators:
38
+ out[column] = self.collators[column](values)
39
+ else:
40
+ out[column] = default_collate(values)
41
+
42
+ return out
@@ -0,0 +1,115 @@
1
+ """Chemprop collation helpers."""
2
+
3
+ from collections.abc import Mapping, Iterable
4
+
5
+ from chemprop.data import MolGraph
6
+ from chemprop.data.collate import BatchMolGraph
7
+ import numpy as np
8
+ import torch
9
+ from torch import Tensor
10
+
11
+ from ..typing import Datum
12
+
13
+
14
+ def _as_tensor(x) -> Tensor:
15
+ """Convert an value to a tensor."""
16
+ if x is None:
17
+ return None
18
+ if torch.is_tensor(x):
19
+ return x
20
+ return torch.as_tensor(x)
21
+
22
+
23
+ def _as_numpy(x) -> np.ndarray:
24
+ """Convert an value to a tensor."""
25
+ if x is None:
26
+ return None
27
+ if isinstance(x, np.ndarray):
28
+ return x
29
+ return np.asarray(x)
30
+
31
+
32
+ def _molgraph_from_mapping(
33
+ x: Mapping[str, ...]
34
+ ) -> MolGraph:
35
+ """Reconstruct a Chemprop MolGraph from a cached mapping."""
36
+ return MolGraph(
37
+ V=_as_numpy(x["V"]),
38
+ E=_as_numpy(x["E"]),
39
+ edge_index=_as_numpy(x["edge_index"]).astype(np.int32),
40
+ rev_edge_index=_as_numpy(x["rev_edge_index"]).astype(np.int32),
41
+ )
42
+
43
+
44
+ def _collate_optional(
45
+ values: Iterable[...],
46
+ *,
47
+ stack: bool,
48
+ ):
49
+ """Collate an optional Chemprop feature."""
50
+ if all(value is None for value in values):
51
+ return None
52
+
53
+ if any(value is None for value in values):
54
+ raise ValueError(
55
+ "Cannot collate Chemprop feature containing a mixture "
56
+ "of None and non-None values."
57
+ )
58
+
59
+ tensors = [
60
+ _as_tensor(value)
61
+ for value in values
62
+ ]
63
+
64
+ if stack:
65
+ return torch.stack(tensors, dim=0)
66
+
67
+ return torch.cat(tensors, dim=0)
68
+
69
+
70
+ def chemprop_collate(
71
+ values: Iterable[Datum],
72
+ ) -> dict[str, ...]:
73
+ """Collate cached Chemprop features into a runtime model input.
74
+
75
+ Each input value is expected to have the form::
76
+
77
+ {
78
+ "bmg": {
79
+ "V": ...,
80
+ "E": ...,
81
+ "edge_index": ...,
82
+ "rev_edge_index": ...,
83
+ },
84
+ "V_d": ...,
85
+ "X_d": ...,
86
+ }
87
+
88
+ Returns
89
+ =======
90
+ dict
91
+ Runtime representation suitable for ``ChempropEncoder(**batch)``.
92
+
93
+ """
94
+ if len(values) == 0:
95
+ raise ValueError("Cannot collate an empty Chemprop batch.")
96
+
97
+ molgraphs = [
98
+ _molgraph_from_mapping(value["bmg"])
99
+ for value in values
100
+ ]
101
+ bmg = BatchMolGraph(molgraphs)
102
+
103
+ V_d = _collate_optional(
104
+ [value.get("V_d") for value in values],
105
+ stack=False,
106
+ )
107
+ X_d = _collate_optional(
108
+ [value.get("X_d") for value in values],
109
+ stack=True,
110
+ )
111
+ return {
112
+ "bmg": bmg,
113
+ "V_d": V_d,
114
+ "X_d": X_d,
115
+ }
@@ -399,17 +399,17 @@ class DataPipeline:
399
399
 
400
400
  @staticmethod
401
401
  def _unsqueeze(
402
- x: Mapping[str, ArrayLike]
402
+ x: Mapping[str, ArrayLike],
403
+ columns: Optional[Iterable[str]] = None
403
404
  ) -> Dict[str, np.ndarray]:
404
- def _unsqueeze(x, columns=None):
405
- columns = columns or x.keys()
406
- for key in columns:
407
- vals = x[key]
408
- if not isinstance(vals, dict):
409
- vals = np.asarray(x[key])
410
- if vals.ndim == 1 and np.issubdtype(vals.dtype, np.number):
411
- x[key] = vals[:, None]
412
- return x
405
+ columns = columns or x.keys()
406
+ for key in columns:
407
+ vals = x[key]
408
+ if not isinstance(vals, dict):
409
+ vals = np.asarray(x[key])
410
+ if vals.ndim == 1 and np.issubdtype(vals.dtype, np.number):
411
+ x[key] = vals[:, None]
412
+ return x
413
413
 
414
414
  def __call__(
415
415
  self,
@@ -477,6 +477,7 @@ class DataPipeline:
477
477
  .select_columns(all_output_columns)
478
478
  .map(
479
479
  self._unsqueeze,
480
+ fn_kwargs={"columns": output_columns},
480
481
  batched=True,
481
482
  batch_size=batch_size,
482
483
  desc="Unsqueezing",
@@ -1,6 +1,7 @@
1
1
  """Custom types."""
2
2
 
3
- from typing import TYPE_CHECKING, Any, Iterable, Mapping, Union
3
+ from typing import TYPE_CHECKING, Any, TypeAlias, Union
4
+ from collections.abc import Iterable, Mapping
4
5
 
5
6
  from datasets import Dataset, IterableDataset
6
7
  from pandas import DataFrame
@@ -11,14 +12,14 @@ if TYPE_CHECKING:
11
12
  else:
12
13
  ColumnTransform = Any
13
14
 
14
- DataLike = Union[
15
+ DataLike: TypeAlias = Union[
15
16
  str,
16
17
  DataFrame,
17
18
  Mapping[str, ArrayLike],
18
19
  Dataset,
19
20
  IterableDataset
20
21
  ]
21
- FeatureLike = Union[
22
+ FeatureLike: TypeAlias = Union[
22
23
  str,
23
24
  Mapping[str, ColumnTransform],
24
25
  Mapping[str, Mapping[str, Any]],
@@ -29,3 +30,6 @@ FeatureLike = Union[
29
30
  ]]
30
31
  ]
31
32
  StrOrIterableOfStr = Union[str, Iterable[str]]
33
+
34
+ Datum: TypeAlias = Mapping[str, Any]
35
+ Batch: TypeAlias = Iterable[Mapping[str, Any]]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspect-data
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: General purpose, chemistry, and taxonomic featurization of tabular data with caching.
5
5
  Author-email: Eachan Johnson <eachan.johnson@crick.ac.uk>
6
6
  License: MIT License
@@ -48,14 +48,17 @@ Requires-Dist: platformdirs>=4.4.0
48
48
  Requires-Dist: tqdm
49
49
  Provides-Extra: chem
50
50
  Requires-Dist: schemist>=0.0.6; extra == "chem"
51
+ Provides-Extra: torch
52
+ Requires-Dist: torch>=2.4; extra == "torch"
51
53
  Provides-Extra: chemprop
54
+ Requires-Dist: aspect-data[chem,torch]; extra == "chemprop"
52
55
  Requires-Dist: chemprop>=2.1.0; extra == "chemprop"
53
56
  Provides-Extra: deep
54
57
  Requires-Dist: transformers; extra == "deep"
55
58
  Provides-Extra: bio
56
- Requires-Dist: vectome>=0.0.4; extra == "bio"
59
+ Requires-Dist: vectome>=0.0.8; extra == "bio"
57
60
  Provides-Extra: dev
58
- Requires-Dist: aspect-data[bio,chem]; extra == "dev"
61
+ Requires-Dist: aspect-data[bio,chemprop]; extra == "dev"
59
62
  Requires-Dist: pytest>=7.4; extra == "dev"
60
63
  Requires-Dist: flake8; extra == "dev"
61
64
  Dynamic: license-file
@@ -66,6 +69,8 @@ Dynamic: license-file
66
69
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aspect-data)
67
70
  ![PyPI](https://img.shields.io/pypi/v/aspect-data)
68
71
 
72
+ <img src="docs/source/_static/logo.svg" width="200">
73
+
69
74
  **aspect** is a lightweight featurisation layer for tabular machine-learning data.
70
75
 
71
76
  It lets you define independent feature pipelines for different input columns, then apply them to dictionaries, Pandas data frames, local files, Hugging Face datasets, or saved dataset checkpoints.
@@ -83,6 +88,15 @@ input table
83
88
  output dataset with named feature columns
84
89
  ```
85
90
 
91
+ Aspect datasets store Arrow-compatible feature representations.
92
+
93
+ Before model execution, an optional collator converts stored examples into
94
+ runtime representations. A runtime batch is always a mapping from feature
95
+ column names to batched values. Values may be tensors or structured mappings.
96
+
97
+ Downstream model libraries should treat runtime batch values as opaque and
98
+ should not perform dataset preprocessing or deserialization.
99
+
86
100
  ---
87
101
 
88
102
  ## Contents
@@ -11,6 +11,9 @@ aspect/cli_module/__init__.py
11
11
  aspect/cli_module/cli.py
12
12
  aspect/cli_module/featurize.py
13
13
  aspect/cli_module/io.py
14
+ aspect/collate/__init__.py
15
+ aspect/collate/base.py
16
+ aspect/collate/chemprop.py
14
17
  aspect/transform/__init__.py
15
18
  aspect/transform/base.py
16
19
  aspect/transform/deep_functions.py
@@ -21,4 +24,6 @@ aspect_data.egg-info/SOURCES.txt
21
24
  aspect_data.egg-info/dependency_links.txt
22
25
  aspect_data.egg-info/entry_points.txt
23
26
  aspect_data.egg-info/requires.txt
24
- aspect_data.egg-info/top_level.txt
27
+ aspect_data.egg-info/top_level.txt
28
+ test/test_collate.py
29
+ test/test_collate_chemprop.py
@@ -7,18 +7,22 @@ platformdirs>=4.4.0
7
7
  tqdm
8
8
 
9
9
  [bio]
10
- vectome>=0.0.4
10
+ vectome>=0.0.8
11
11
 
12
12
  [chem]
13
13
  schemist>=0.0.6
14
14
 
15
15
  [chemprop]
16
+ aspect-data[chem,torch]
16
17
  chemprop>=2.1.0
17
18
 
18
19
  [deep]
19
20
  transformers
20
21
 
21
22
  [dev]
22
- aspect-data[bio,chem]
23
+ aspect-data[bio,chemprop]
23
24
  pytest>=7.4
24
25
  flake8
26
+
27
+ [torch]
28
+ torch>=2.4
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aspect-data"
3
- version = "0.0.3"
3
+ version = "0.0.4"
4
4
  authors = [
5
5
  { name="Eachan Johnson", email="eachan.johnson@crick.ac.uk" },
6
6
  ]
@@ -44,10 +44,11 @@ dependencies = [
44
44
 
45
45
  [project.optional-dependencies]
46
46
  chem = ["schemist>=0.0.6"]
47
- chemprop = ["chemprop>=2.1.0"]
47
+ torch = ["torch>=2.4"]
48
+ chemprop = ["aspect-data[torch,chem]", "chemprop>=2.1.0"]
48
49
  deep = ["transformers"]
49
- bio = ["vectome>=0.0.4"]
50
- dev = ["aspect-data[chem,bio]", "pytest>=7.4", "flake8"]
50
+ bio = ["vectome>=0.0.8"]
51
+ dev = ["aspect-data[chemprop,bio]", "pytest>=7.4", "flake8"]
51
52
 
52
53
  [project.urls]
53
54
  "Homepage" = "https://github.com/scbirlab/aspect"
@@ -0,0 +1,78 @@
1
+
2
+ from aspect.collate import ColumnCollator
3
+
4
+ def test_column_collator_default():
5
+ import torch
6
+
7
+ collator = ColumnCollator()
8
+
9
+ batch = collator([
10
+ {
11
+ "x": torch.tensor([1., 2.]),
12
+ "y": torch.tensor(1.),
13
+ },
14
+ {
15
+ "x": torch.tensor([3., 4.]),
16
+ "y": torch.tensor(2.),
17
+ },
18
+ ])
19
+
20
+ assert batch["x"].shape == (2, 2)
21
+ assert batch["y"].shape == (2,)
22
+
23
+
24
+ def test_column_collator_override():
25
+ collator = ColumnCollator(
26
+ collators={
27
+ "special": lambda values: {
28
+ "values": values,
29
+ }
30
+ }
31
+ )
32
+
33
+ batch = collator([
34
+ {
35
+ "x": 1,
36
+ "special": "a",
37
+ },
38
+ {
39
+ "x": 2,
40
+ "special": "b",
41
+ },
42
+ ])
43
+
44
+ assert batch["special"] == {
45
+ "values": ["a", "b"],
46
+ }
47
+
48
+
49
+ def test_column_collator_mixed():
50
+ import torch
51
+
52
+ collator = ColumnCollator(
53
+ collators={
54
+ "graph": lambda values: {
55
+ "graph_batch": values,
56
+ }
57
+ }
58
+ )
59
+
60
+ batch = collator([
61
+ {
62
+ "fp": torch.tensor([1., 2.]),
63
+ "graph": {"id": 1},
64
+ "target": torch.tensor(0.),
65
+ },
66
+ {
67
+ "fp": torch.tensor([3., 4.]),
68
+ "graph": {"id": 2},
69
+ "target": torch.tensor(1.),
70
+ },
71
+ ])
72
+
73
+ assert batch["fp"].shape == (2, 2)
74
+ assert batch["target"].shape == (2,)
75
+ assert batch["graph"]["graph_batch"] == [
76
+ {"id": 1},
77
+ {"id": 2},
78
+ ]
@@ -0,0 +1,104 @@
1
+ """Tests for Chemprop collation."""
2
+
3
+ from chemprop.data.collate import BatchMolGraph
4
+ from chemprop.featurizers import SimpleMoleculeMolGraphFeaturizer
5
+ import pytest
6
+ from rdkit import Chem
7
+ import torch
8
+
9
+ from aspect.collate.chemprop import chemprop_collate
10
+
11
+
12
+ def _cached_molgraph(smiles: str) -> dict:
13
+ """Create an HF-like serialized MolGraph."""
14
+ featurizer = SimpleMoleculeMolGraphFeaturizer()
15
+ molgraph = featurizer(
16
+ Chem.MolFromSmiles(smiles)
17
+ )
18
+
19
+ return {
20
+ "V": molgraph.V.tolist(),
21
+ "E": molgraph.E.tolist(),
22
+ "edge_index": molgraph.edge_index.tolist(),
23
+ "rev_edge_index": molgraph.rev_edge_index.tolist(),
24
+ }
25
+
26
+
27
+ def test_chemprop_collate_graphs():
28
+ smiles = ["CCO", "CCN", "CCC"]
29
+ values = [
30
+ {
31
+ "bmg": _cached_molgraph(s),
32
+ "V_d": None,
33
+ "X_d": None,
34
+ }
35
+ for s in smiles
36
+ ]
37
+ batch = chemprop_collate(values)
38
+
39
+ assert set(batch) == {
40
+ "bmg",
41
+ "V_d",
42
+ "X_d",
43
+ }
44
+ assert isinstance(
45
+ batch["bmg"],
46
+ BatchMolGraph,
47
+ )
48
+
49
+ assert batch["V_d"] is None
50
+ assert batch["X_d"] is None
51
+
52
+ assert batch["bmg"].V.ndim == 2
53
+ assert batch["bmg"].edge_index.ndim == 2
54
+
55
+ assert torch.unique(
56
+ batch["bmg"].batch
57
+ ).numel() == 3
58
+
59
+
60
+ def test_chemprop_collate_X_d():
61
+ values = [
62
+ {
63
+ "bmg": _cached_molgraph("CCO"),
64
+ "V_d": None,
65
+ "X_d": [1.0, 2.0],
66
+ },
67
+ {
68
+ "bmg": _cached_molgraph("CCN"),
69
+ "V_d": None,
70
+ "X_d": [3.0, 4.0],
71
+ },
72
+ ]
73
+
74
+ batch = chemprop_collate(values)
75
+
76
+ assert batch["X_d"].shape == (2, 2)
77
+ assert torch.equal(
78
+ batch["X_d"],
79
+ torch.tensor([
80
+ [1., 2.],
81
+ [3., 4.],
82
+ ]),
83
+ )
84
+
85
+
86
+ def test_chemprop_collate_rejects_mixed_optional_values():
87
+ values = [
88
+ {
89
+ "bmg": _cached_molgraph("CCO"),
90
+ "V_d": None,
91
+ "X_d": [1.0, 2.0],
92
+ },
93
+ {
94
+ "bmg": _cached_molgraph("CCN"),
95
+ "V_d": None,
96
+ "X_d": None,
97
+ },
98
+ ]
99
+
100
+ with pytest.raises(
101
+ ValueError,
102
+ match="mixture of None and non-None",
103
+ ):
104
+ chemprop_collate(values)
File without changes
File without changes
File without changes