|
1 | 1 | import unittest |
2 | | -from collections.abc import Mapping |
| 2 | +from collections.abc import Mapping, MutableMapping |
3 | 3 |
|
4 | 4 | import torch |
5 | 5 | import torch.multiprocessing as mp |
@@ -398,6 +398,31 @@ def _run_cora_supervised_node_classification( |
398 | 398 | shutdown_rpc() |
399 | 399 |
|
400 | 400 |
|
| 401 | +def _run_featureless_edge_ids_absent( |
| 402 | + _, |
| 403 | + dataset: DistDataset, |
| 404 | + holder: MutableMapping, |
| 405 | +): |
| 406 | + # A featureless dataset should sample with with_edge=False, so GLT never |
| 407 | + # attaches sampled edge ids (``data.edge``) to the produced batches. |
| 408 | + create_test_process_group() |
| 409 | + loader = DistNeighborLoader( |
| 410 | + dataset=dataset, |
| 411 | + num_neighbors=[2, 2], |
| 412 | + pin_memory_device=torch.device("cpu"), |
| 413 | + ) |
| 414 | + count = 0 |
| 415 | + edge_ids_absent = True |
| 416 | + for datum in loader: |
| 417 | + assert isinstance(datum, Data) |
| 418 | + if getattr(datum, "edge", None) is not None: |
| 419 | + edge_ids_absent = False |
| 420 | + count += 1 |
| 421 | + holder["edge_ids_absent"] = edge_ids_absent |
| 422 | + holder["count"] = count |
| 423 | + shutdown_rpc() |
| 424 | + |
| 425 | + |
401 | 426 | class DistributedNeighborLoaderTest(TestCase): |
402 | 427 | def setUp(self): |
403 | 428 | super().setUp() |
@@ -861,5 +886,93 @@ def test_distributed_neighbor_loader_invalid_inputs_colocated( |
861 | 886 | DistNeighborLoader(**kwargs) |
862 | 887 |
|
863 | 888 |
|
| 889 | +class WithEdgeDerivationTest(TestCase): |
| 890 | + """Regression coverage for deriving ``with_edge`` from edge-feature presence.""" |
| 891 | + |
| 892 | + def test_config_with_edge_false_when_no_edge_features(self) -> None: |
| 893 | + from gigl.distributed.base_dist_loader import BaseDistLoader |
| 894 | + from gigl.distributed.utils.neighborloader import DatasetSchema |
| 895 | + |
| 896 | + schema = DatasetSchema( |
| 897 | + is_homogeneous_with_labeled_edge_type=False, |
| 898 | + edge_types=None, |
| 899 | + node_feature_info=None, |
| 900 | + edge_feature_info=None, # no edge features |
| 901 | + edge_dir="out", |
| 902 | + ) |
| 903 | + config = BaseDistLoader.create_sampling_config( |
| 904 | + num_neighbors=[2, 2], dataset_schema=schema |
| 905 | + ) |
| 906 | + self.assertFalse(config.with_edge) |
| 907 | + |
| 908 | + def test_config_with_edge_true_when_edge_features_present(self) -> None: |
| 909 | + from gigl.distributed.base_dist_loader import BaseDistLoader |
| 910 | + from gigl.distributed.utils.neighborloader import DatasetSchema |
| 911 | + from gigl.types.graph import FeatureInfo |
| 912 | + |
| 913 | + schema = DatasetSchema( |
| 914 | + is_homogeneous_with_labeled_edge_type=False, |
| 915 | + edge_types=None, |
| 916 | + node_feature_info=None, |
| 917 | + edge_feature_info=FeatureInfo(dim=4, dtype=torch.float32), |
| 918 | + edge_dir="out", |
| 919 | + ) |
| 920 | + config = BaseDistLoader.create_sampling_config( |
| 921 | + num_neighbors=[2, 2], dataset_schema=schema |
| 922 | + ) |
| 923 | + self.assertTrue(config.with_edge) |
| 924 | + |
| 925 | + def test_config_with_edge_true_for_heterogeneous_edge_feature_dict(self) -> None: |
| 926 | + from gigl.distributed.base_dist_loader import BaseDistLoader |
| 927 | + from gigl.distributed.utils.neighborloader import DatasetSchema |
| 928 | + from gigl.types.graph import FeatureInfo |
| 929 | + |
| 930 | + schema = DatasetSchema( |
| 931 | + is_homogeneous_with_labeled_edge_type=False, |
| 932 | + edge_types=[_USER_TO_STORY, _STORY_TO_USER], |
| 933 | + node_feature_info=None, |
| 934 | + edge_feature_info={ |
| 935 | + _USER_TO_STORY: FeatureInfo(dim=4, dtype=torch.float32), |
| 936 | + }, |
| 937 | + edge_dir="out", |
| 938 | + ) |
| 939 | + config = BaseDistLoader.create_sampling_config( |
| 940 | + num_neighbors=[2, 2], dataset_schema=schema |
| 941 | + ) |
| 942 | + self.assertTrue(config.with_edge) |
| 943 | + |
| 944 | + def test_featureless_dataset_produces_batches_without_edge_ids(self) -> None: |
| 945 | + # Homogeneous dataset with node features but no edge features: the loader |
| 946 | + # must still yield batches, and none carry sampled edge ids. |
| 947 | + partition_output = PartitionOutput( |
| 948 | + node_partition_book=torch.zeros(5), |
| 949 | + edge_partition_book=torch.zeros(5), |
| 950 | + partitioned_edge_index=GraphPartitionData( |
| 951 | + edge_index=torch.tensor([[0, 1, 2, 3, 4], [1, 2, 3, 4, 0]]), |
| 952 | + edge_ids=None, |
| 953 | + ), |
| 954 | + partitioned_node_features=FeaturePartitionData( |
| 955 | + feats=torch.zeros(5, 2), ids=torch.arange(5) |
| 956 | + ), |
| 957 | + partitioned_edge_features=None, |
| 958 | + partitioned_positive_labels=None, |
| 959 | + partitioned_negative_labels=None, |
| 960 | + partitioned_node_labels=None, |
| 961 | + ) |
| 962 | + dataset = DistDataset(rank=0, world_size=1, edge_dir="out") |
| 963 | + dataset.build(partition_output=partition_output) |
| 964 | + |
| 965 | + manager = mp.Manager() |
| 966 | + holder: MutableMapping = manager.dict() |
| 967 | + proc = mp.spawn( |
| 968 | + fn=_run_featureless_edge_ids_absent, |
| 969 | + args=(dataset, holder), |
| 970 | + join=False, |
| 971 | + ) |
| 972 | + proc.join(timeout=120) |
| 973 | + self.assertGreater(holder["count"], 0) |
| 974 | + self.assertTrue(holder["edge_ids_absent"]) |
| 975 | + |
| 976 | + |
864 | 977 | if __name__ == "__main__": |
865 | 978 | absltest.main() |
0 commit comments