1- from typing import Any , Dict , Literal , Optional , Tuple
1+ from typing import Any , Literal , Optional , Tuple
22
33import torch
44
@@ -28,6 +28,7 @@ def __init__(
2828 src : torch .Tensor ,
2929 dst : torch .Tensor ,
3030 ts : torch .Tensor ,
31+ N : int ,
3132 memory_mode : Literal ['unlimited' , 'fixed' ] = 'unlimited' ,
3233 window_ratio : float = 0.15 ,
3334 pos_prob : float = 1.0 ,
@@ -46,6 +47,7 @@ def __init__(
4647 src (torch.Tensor): Source node IDs of edges used for initialization.
4748 dst (torch.Tensor): Destination node IDs of edges used for initialization.
4849 ts (torch.Tensor): Timestamps of edges used for initialization.
50+ N (int): total number of nodes in the graph.
4951 memory_mode (Literal['unlimited', 'fixed'], optional):
5052 - ``'unlimited'``: Keeps all observed edges in memory.
5153 - ``'fixed'``: Keeps only edges within a sliding window of time.
@@ -83,27 +85,10 @@ def __init__(
8385 self ._window_start = ts .max () - window_ratio * (ts .max () - ts .min ())
8486 self ._window_size = self ._window_end - self ._window_start
8587
86- self .memory : Dict [Tuple [int , int ], int ] = {}
87- # maintain bidirectional linked list with 2 pointers
88- self ._head : Optional [_Event ] = None
89- self ._tail : Optional [_Event ] = None
90-
91- logger .warning (
92- 'EdgeBank will be slow if events are added/updated out of order.'
93- )
88+ self ._memory = torch .zeros ((N , N ), dtype = ts .dtype , device = ts .device )
9489
9590 self .update (src , dst , ts )
9691
97- def _clean_up (self ) -> None :
98- """Clean up edges that are out of window in memory."""
99- while self ._head and self ._head .ts < self ._window_start :
100- curr_event = self ._head
101- if self .memory .get (curr_event .edge , - 1 ) == curr_event .ts :
102- self .memory .pop (curr_event .edge )
103- self ._head = curr_event .right
104- if self ._head == None :
105- self ._tail = None
106-
10792 def update (self , src : torch .Tensor , dst : torch .Tensor , ts : torch .Tensor ) -> None :
10893 """Update EdgeBank memory with a batch of edges.
10994
@@ -119,43 +104,7 @@ def update(self, src: torch.Tensor, dst: torch.Tensor, ts: torch.Tensor) -> None
119104 self ._check_input_data (src , dst , ts )
120105 self ._window_end = torch .max (self ._window_end , ts .max ())
121106 self ._window_start = self ._window_end - self ._window_size
122-
123- if (
124- self ._fixed_memory
125- and self ._head is not None
126- and self ._tail is not None
127- and self ._head .ts < self ._window_start
128- ):
129- self ._clean_up ()
130-
131- for src_ , dst_ , ts_ in zip (src , dst , ts ):
132- src_ , dst_ , ts_ = src_ .item (), dst_ .item (), ts_ .item ()
133- if ts_ >= self ._window_start :
134- self .memory [(src_ , dst_ )] = ts_
135- if self ._head == self ._tail == None :
136- self ._head = self ._tail = _Event ((src_ , dst_ ), ts_ , None , None )
137- elif self ._head is not None and self ._tail is not None :
138- new_event = _Event ((src_ , dst_ ), ts_ , left = None , right = None )
139- curr : _Event | None = self ._tail
140-
141- # This while loop should never run assuming events added in time-ascending order.
142- # When events added out of order, time complexity would be O(n)
143- while curr is not None and ts_ < curr .ts :
144- curr = curr .left
145-
146- if curr == None :
147- new_event .right = self ._head
148- if self ._head is not None :
149- self ._head .left = new_event
150- self ._head = new_event
151- else :
152- new_event .left = curr
153- new_event .right = curr .right # type: ignore[union-attr]
154- if curr .right is not None : # type: ignore[union-attr]
155- curr .right .left = new_event # type: ignore[union-attr]
156- curr .right = new_event # type: ignore[union-attr]
157- if curr == self ._tail :
158- self ._tail = new_event
107+ self ._memory [src , dst ] = torch .maximum (self ._memory [src , dst ], ts )
159108
160109 def __call__ (
161110 self , query_src : torch .Tensor , query_dst : torch .Tensor
@@ -172,15 +121,9 @@ def __call__(
172121 its probability is ``self.pos_prob``.
173122 - Otherwise, the probability is ``0.0``.
174123 """
175- pred = torch .zeros_like (query_src )
176- src_list = query_src .tolist ()
177- dst_list = query_dst .tolist ()
178- for i , (s , d ) in enumerate (zip (src_list , dst_list )):
179- mem_val = self .memory .get ((s , d ))
180- if mem_val is not None :
181- if not self ._fixed_memory or mem_val >= self .window_start :
182- pred [i ] = self .pos_prob
183- return pred
124+ return (
125+ self ._memory [query_src , query_dst ] >= self .window_start
126+ ).float () * self .pos_prob
184127
185128 @property
186129 def window_start (self ) -> int | float :
0 commit comments