27snd = np.array([1, 2, 3, 4, 2, 3, 4, 3, 4, 4, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3], dtype=
"int64")
28rec = np.array([0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 1, 2, 3, 4, 2, 3, 4, 3, 4, 4], dtype=
"int64")
38torch.set_grad_enabled(
False)
42def get_graph_data_dict(num_nodes, num_edges, NODE_FEATURE_SIZE=2, EDGE_FEATURE_SIZE=2, GLOBAL_FEATURE_SIZE=1):
44 "globals": 10 * np.random.rand(1, GLOBAL_FEATURE_SIZE).astype(np.float32) - 5.0,
45 "nodes": 10 * np.random.rand(num_nodes, NODE_FEATURE_SIZE).astype(np.float32) - 5.0,
46 "edges": 10 * np.random.rand(num_edges, EDGE_FEATURE_SIZE).astype(np.float32) - 5.0,
54def make_mlp_model(num_inputs, with_layer_norm=False):
56 for _
in range(NUM_LAYERS):
57 layers += [nn.Linear(num_inputs, LATENT_SIZE), nn.ReLU()]
58 num_inputs = LATENT_SIZE
60 layers.append(nn.LayerNorm(LATENT_SIZE))
61 return nn.Sequential(*layers)
65class MLPGraphIndependent(nn.Module):
66 def __init__(self, num_node_inputs, num_edge_inputs, num_global_inputs):
68 self.node_fn = make_mlp_model(num_node_inputs)
69 self.edge_fn = make_mlp_model(num_edge_inputs)
70 self.global_fn = make_mlp_model(num_global_inputs)
72 def forward(self, node_data, edge_data, global_data):
73 return self.node_fn(node_data), self.edge_fn(edge_data), self.global_fn(global_data)
80class MLPGraphNetwork(nn.Module):
81 def __init__(self, num_node_inputs, num_edge_inputs, num_global_inputs):
83 self.edge_fn = make_mlp_model(num_edge_inputs + 2 * num_node_inputs + num_global_inputs,
True)
84 self.node_fn = make_mlp_model(LATENT_SIZE + num_node_inputs + num_global_inputs,
True)
85 self.global_fn = make_mlp_model(2 * LATENT_SIZE + num_global_inputs,
True)
87 def forward(self, node_data, edge_data, global_data, receivers, senders):
88 n_nodes = node_data.shape[0]
89 n_edges = edge_data.shape[0]
90 edge_input = torch.cat(
91 [edge_data, node_data[receivers], node_data[senders], global_data.expand(n_edges, -1)], dim=1
93 edge_output = self.edge_fn(edge_input)
95 received_edges = torch.zeros(n_nodes, edge_output.shape[1]).scatter_add(
96 0, receivers.unsqueeze(1).expand(n_edges, edge_output.shape[1]), edge_output
98 node_input = torch.cat([received_edges, node_data, global_data.expand(n_nodes, -1)], dim=1)
99 node_output = self.node_fn(node_input)
100 global_input = torch.cat(
101 [edge_output.sum(0, keepdim=
True), node_output.sum(0, keepdim=
True), global_data], dim=1
103 global_output = self.global_fn(global_input)
104 return node_output, edge_output, global_output
108class EncodeProcessDecode(nn.Module):
111 self._encoder = MLPGraphIndependent(node_size, edge_size, global_size)
112 self._core = MLPGraphNetwork(2 * LATENT_SIZE, 2 * LATENT_SIZE, 2 * LATENT_SIZE)
113 self._decoder = MLPGraphIndependent(LATENT_SIZE, LATENT_SIZE, LATENT_SIZE)
114 self._output_transform = MLPGraphIndependent(LATENT_SIZE, LATENT_SIZE, LATENT_SIZE)
116 def forward(self, node_data, edge_data, global_data, receivers, senders, num_processing_steps):
117 latent = self._encoder(node_data, edge_data, global_data)
120 for _
in range(num_processing_steps):
121 core_input = tuple(torch.cat([a, b], dim=1)
for a, b
in zip(latent0, latent))
122 latent = self._core(*core_input, receivers, senders)
123 decoded_op = self._decoder(*latent)
124 output_ops.append(self._output_transform(*decoded_op))
129ep_model = EncodeProcessDecode()
133sample_indices = (torch.from_numpy(rec), torch.from_numpy(snd))
136def export_component(component, name, num_features):
138 torch.zeros(num_nodes, num_features[0]),
139 torch.zeros(num_edges, num_features[1]),
140 torch.zeros(1, num_features[2]),
142 input_names = [
"node_data",
"edge_data",
"global_data"]
143 if isinstance(component, MLPGraphNetwork):
144 sample_input += sample_indices
145 input_names += [
"receivers",
"senders"]
150 input_names=input_names,
151 output_names=[
"node_output",
"edge_output",
"global_output"],
156export_component(ep_model._encoder,
"gnn_encoder", (node_size, edge_size, global_size))
157export_component(ep_model._core,
"gnn_core", (2 * LATENT_SIZE,) * 3)
158export_component(ep_model._decoder,
"gnn_decoder", (LATENT_SIZE,) * 3)
159export_component(ep_model._output_transform,
"gnn_output_transform", (LATENT_SIZE,) * 3)
162parser = ROOT.TMVA.Experimental.SOFIE.RModelParser_ONNX()
163for name
in [
"gnn_encoder",
"gnn_core",
"gnn_decoder",
"gnn_output_transform"]:
164 model = parser.Parse(name +
".onnx")
166 model.OutputGenerated()
169gen_code =
"""#pragma cling optimize(2)
170#include "gnn_encoder.hxx"
171#include "gnn_core.hxx"
172#include "gnn_decoder.hxx"
173#include "gnn_output_transform.hxx"
175ROOT.gInterpreter.Declare(gen_code)
181 self.encoder_session = ROOT.TMVA_SOFIE_gnn_encoder.Session()
182 self.core_session = ROOT.TMVA_SOFIE_gnn_core.Session()
183 self.decoder_session = ROOT.TMVA_SOFIE_gnn_decoder.Session()
184 self.output_transform_session = ROOT.TMVA_SOFIE_gnn_output_transform.Session()
187 def _as_arrays(result, num_nodes, num_edges):
190 np.asarray(result[0], dtype=np.float32).reshape(num_nodes, -1),
191 np.asarray(result[1], dtype=np.float32).reshape(num_edges, -1),
192 np.asarray(result[2], dtype=np.float32).reshape(1, -1),
195 def infer(self, graphData):
196 n_nodes =
len(graphData[
"nodes"])
197 n_edges =
len(graphData[
"edges"])
200 return np.ascontiguousarray(x, dtype=np.float32)
202 receivers = np.ascontiguousarray(graphData[
"receivers"], dtype=np.int64)
203 senders = np.ascontiguousarray(graphData[
"senders"], dtype=np.int64)
204 i64p = cppyy.ll.cast[
"const int64_t*"]
205 receivers_ptr = i64p(receivers.ctypes.data)
206 senders_ptr = i64p(senders.ctypes.data)
208 latent = self._as_arrays(
209 self.encoder_session.infer(
c(graphData[
"nodes"]),
c(graphData[
"edges"]),
c(graphData[
"globals"])),
214 for _
in range(processing_steps):
215 core_input = tuple(np.concatenate([a, b], axis=1)
for a, b
in zip(latent0, latent))
216 latent = self._as_arrays(
217 self.core_session.infer(
c(core_input[0]),
c(core_input[1]),
c(core_input[2]), receivers_ptr, senders_ptr),
220 decoded = self._as_arrays(
221 self.decoder_session.infer(
c(latent[0]),
c(latent[1]),
c(latent[2])), n_nodes, n_edges
225 self.output_transform_session.infer(
c(decoded[0]),
c(decoded[1]),
c(decoded[2])),
233dataSet = [get_graph_data_dict(num_nodes, num_edges, node_size, edge_size, global_size)
for i
in range(numevts)]
237def RunGNet(graphData):
239 torch.from_numpy(graphData[
"nodes"]),
240 torch.from_numpy(graphData[
"edges"]),
241 torch.from_numpy(graphData[
"globals"]),
242 torch.from_numpy(graphData[
"receivers"]),
243 torch.from_numpy(graphData[
"senders"]),
249hG = ROOT.TH1D(
"hG",
"Result from PyTorch", 20, 1, 0)
251for i
in range(numevts):
252 out = RunGNet(dataSet[i])
253 torchOutput.append([[t.numpy()
for t
in step]
for step
in out])
254 hG.Fill(np.mean(torchOutput[-1][1][2]))
257print(
"elapsed time for ", numevts,
"events = ", end - start)
260hS = ROOT.TH1D(
"hS",
"Result from SOFIE", 20, 1, 0)
264print(
"time to create SOFIE GNN class", start - start0)
266for i
in range(numevts):
267 out = gnn.infer(dataSet[i])
268 sofieOutput.append(out)
269 hS.Fill(np.mean(out[1][2]))
272print(
"elapsed time for ", numevts,
"events = ", end - start)
283hDn = ROOT.TH1D(
"hDn",
"Difference for node data", 40, 1, 0)
284hDe = ROOT.TH1D(
"hDe",
"Difference for edge data", 40, 1, 0)
285hDg = ROOT.TH1D(
"hDg",
"Difference for global data", 40, 1, 0)
288for i
in range(numevts):
289 for hist, j
in [(hDn, 0), (hDe, 1), (hDg, 2)]:
290 difference = sofieOutput[i][1][j] - torchOutput[i][1][j]
291 for value
in difference.flatten():
293 maxDifference = max(maxDifference, np.abs(difference).max())
295print(
"maximum difference between SOFIE and PyTorch = ", maxDifference)
296if maxDifference > 1e-4:
297 raise RuntimeError(
"SOFIE and PyTorch outputs disagree")
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t UChar_t len