-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathgraphDifferentialEquation.py
More file actions
65 lines (53 loc) · 1.95 KB
/
Copy pathgraphDifferentialEquation.py
File metadata and controls
65 lines (53 loc) · 1.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import torch
import torch.nn as nn
class GDEFunc(nn.Module):
"""
Graph Differential Equation for latent node dynamics:
dz/dt = MLP( GNN(z, edge_index) )
"""
def __init__(self, gnn, latent_dim):
"""
gnn: a GNN module mapping (N, latent_dim) -> (N, latent_dim)
latent_dim: dimension of latent state per node
"""
def __init__(
self,
gnn: nn.Module, # Can be any GNN (GAT, GCN, etc.)
mlp_latent_dim = [5,20], # latent_dimensions of the MLP (input, hidden)
):
"""General GDE function class. To be passed to an ODEBlock"""
super().__init__()
self.gnn = gnn
# small nonlinear MLP to give curvature to the vector field
# this is f_theta in the paper
self.mlp = nn.Sequential(
nn.Linear(mlp_latent_dim[0], mlp_latent_dim[1]),
nn.Tanh(),
nn.Linear(mlp_latent_dim[1], mlp_latent_dim[1]),
nn.Tanh(),
nn.Linear(mlp_latent_dim[1], mlp_latent_dim[0]),
)
# number of function evaluations (NFE)
self.nfe = 0
# will be set by RiTINI before each ODE solve
self.edge_index = None
# last attention info (optional)
self.attention_output = None
def set_graph(self, edge_index):
"""Set edge_index externally before ODE integration."""
self.edge_index = edge_index
def forward(self, t, z):
"""
t: scalar time (ignored — autonomous ODE)
z: (N, latent_dim)
returns dz/dt: (N, latent_dim)
"""
self.nfe += 1
# GNN in latent space
out = self.gnn(z, self.edge_index)
# handle GAT returning (output, (edge_index, attn))
if isinstance(out, tuple):
out, self.attention_output = out
# nonlinear mapping to get dz/dt
dzdt = self.mlp(out)
return dzdt