1+ from typing import Literal , cast
2+
13import torch
24from absl .testing import absltest
35from torch_geometric .data import HeteroData
@@ -50,6 +52,19 @@ def create_empty_hetero_data() -> HeteroData:
5052 return data
5153
5254
55+ def create_directed_chain_data () -> HeteroData :
56+ """Create a directed chain 0 -> 1 -> 2 for direction tests."""
57+ data = HeteroData ()
58+ data ["user" ].x = torch .randn (3 , 4 )
59+ data ["user" , "to" , "user" ].edge_index = torch .tensor (
60+ [
61+ [0 , 1 ],
62+ [1 , 2 ],
63+ ]
64+ )
65+ return data
66+
67+
5368class TestAddHeteroRandomWalkEncodings (TestCase ):
5469 """Tests for AddHeteroRandomWalkEncodings (consolidated PE and SE in single pass)."""
5570
@@ -267,6 +282,45 @@ def test_forward_undirected(self):
267282 self .assertTrue (result .hop_distance .is_sparse_csr )
268283 self .assertEqual (result .hop_distance .shape , (5 , 5 ))
269284
285+ def test_forward_sampling_direction_defaults_to_out (self ):
286+ """Out hop distances preserve existing directed reachability."""
287+ data = create_directed_chain_data ()
288+ transform = AddHeteroHopDistanceEncoding (h_max = 2 )
289+
290+ result = transform (data )
291+ dense = result .hop_distance .to_dense ()
292+
293+ self .assertEqual (dense [0 , 1 ].item (), 1.0 )
294+ self .assertEqual (dense [0 , 2 ].item (), 2.0 )
295+ self .assertEqual (dense [2 , 1 ].item (), 0.0 )
296+ self .assertEqual (dense [2 , 0 ].item (), 0.0 )
297+
298+ def test_forward_sampling_direction_in_reverses_reachability (self ):
299+ """In hop distances are computed over reversed graph edges."""
300+ data = create_directed_chain_data ()
301+ transform = AddHeteroHopDistanceEncoding (
302+ h_max = 2 ,
303+ sampling_direction = "in" ,
304+ )
305+
306+ result = transform (data )
307+ dense = result .hop_distance .to_dense ()
308+
309+ self .assertEqual (dense [2 , 1 ].item (), 1.0 )
310+ self .assertEqual (dense [2 , 0 ].item (), 2.0 )
311+ self .assertEqual (dense [0 , 1 ].item (), 0.0 )
312+ self .assertEqual (dense [0 , 2 ].item (), 0.0 )
313+
314+ def test_sampling_direction_rejects_invalid_value (self ):
315+ with self .assertRaisesRegex (ValueError , "sampling_direction" ):
316+ AddHeteroHopDistanceEncoding (
317+ h_max = 2 ,
318+ sampling_direction = cast (
319+ Literal ["in" , "out" ],
320+ "sideways" ,
321+ ),
322+ )
323+
270324 def test_forward_empty_graph (self ):
271325 """Test forward pass with empty graph."""
272326 data = create_empty_hetero_data ()
@@ -283,7 +337,21 @@ def test_forward_empty_graph(self):
283337 def test_repr (self ):
284338 """Test string representation."""
285339 transform = AddHeteroHopDistanceEncoding (h_max = 5 )
286- self .assertEqual (repr (transform ), "AddHeteroHopDistanceEncoding(h_max=5)" )
340+ self .assertEqual (
341+ repr (transform ),
342+ "AddHeteroHopDistanceEncoding(h_max=5, sampling_direction='out')" ,
343+ )
344+
345+ def test_repr_in_sampling_direction (self ):
346+ """Test string representation with non-default direction."""
347+ transform = AddHeteroHopDistanceEncoding (
348+ h_max = 5 ,
349+ sampling_direction = "in" ,
350+ )
351+ self .assertEqual (
352+ repr (transform ),
353+ "AddHeteroHopDistanceEncoding(h_max=5, sampling_direction='in')" ,
354+ )
287355
288356
289357if __name__ == "__main__" :
0 commit comments