aboutsummaryrefslogtreecommitdiff
path: root/insane-shortest-paths.ipynb
blob: ed97770205e966429a8f88efc7681d4f01768228 (plain)
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 45,
   "execution_state": "idle",
   "id": "86ce5f44-94f6-43b0-a0d1-091b8134ffb6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total number of parameters: 7072\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import random\n",
    "from collections import deque\n",
    "\n",
    "# torch.manual_seed(33)\n",
    "# random.seed(33)\n",
    "\n",
    "# Configuration\n",
    "NVTXS = 8\n",
    "MAXDIST = NVTXS + 1\n",
    "AVGDEG = 2\n",
    "SEQLEN = NVTXS + 1\n",
    "HIDDENDIM = 4 * NVTXS + 2\n",
    "START_REACH = NVTXS + 1\n",
    "START_OUT = 2 * NVTXS + 1\n",
    "START_SELF = 3 * NVTXS + 1\n",
    "SRC_FLAG_IDX = START_SELF\n",
    "ANS_FLAG_IDX = 0\n",
    "NOTANS_FLAG_IDX = -1\n",
    "\n",
    "# Determine device\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "\n",
    "def random_graph(device):\n",
    "    \"\"\"Generate a random graph tensor.\"\"\"\n",
    "    data = torch.zeros((SEQLEN, HIDDENDIM), device=device)\n",
    "    \n",
    "    # Mark self vertices\n",
    "    for i in range(1, NVTXS + 1):\n",
    "        data[i, START_SELF - 1 + i] = 1\n",
    "\n",
    "    # Create adjacency list\n",
    "    adj_list = [set() for _ in range(SEQLEN)]\n",
    "    indices = [random.randint(1, NVTXS) for _ in range(AVGDEG * NVTXS)]\n",
    "    \n",
    "    for i in range(0, len(indices), 2):\n",
    "        u = indices[i]\n",
    "        v = indices[i + 1]\n",
    "        if u != v:\n",
    "            # Bidirectional connections\n",
    "            data[v, u] = 1\n",
    "            data[u, v] = 1\n",
    "            data[v, NVTXS + u] = 1\n",
    "            data[u, NVTXS + v] = 1\n",
    "            adj_list[u].add(v)\n",
    "            adj_list[v].add(u)\n",
    "\n",
    "    # Set flags\n",
    "    data[0, ANS_FLAG_IDX] = 1\n",
    "    data[1:, NOTANS_FLAG_IDX] = 1\n",
    "    data[0, START_REACH:START_REACH + NVTXS] = 1\n",
    "    return data, adj_list\n",
    "\n",
    "def SSSP(G):\n",
    "    \"\"\"Single Source Shortest Path algorithm.\"\"\"\n",
    "    dist = [MAXDIST for _ in G]\n",
    "    dist[1] = 0\n",
    "    frontier = deque([1])\n",
    "    while frontier:\n",
    "        vtx = frontier.popleft()\n",
    "        for x in G[vtx]:\n",
    "            if dist[x] == MAXDIST:\n",
    "                dist[x] = 1 + dist[vtx]\n",
    "                frontier.append(x)\n",
    "                if x == 2:\n",
    "                    return dist[2]\n",
    "    return MAXDIST\n",
    "\n",
    "def mkbatch(size):\n",
    "    \"\"\"Create a batch of graph data.\"\"\"\n",
    "    graphs = []\n",
    "    distances = []\n",
    "\n",
    "    for _ in range(size):\n",
    "        data, adj_list = random_graph(device)\n",
    "        dist = SSSP(adj_list)\n",
    "        graphs.append(data)\n",
    "        distances.append(dist)\n",
    "\n",
    "    data = torch.stack(graphs)\n",
    "    labels = torch.tensor(distances, dtype=torch.float32, device=device)\n",
    "    return data, labels\n",
    "    \n",
    "BIG,SUPABIG,MED,CURSE = 12,30,7,5\n",
    "\n",
    "class SillyTransformer(nn.Module):\n",
    "    def __init__(self, device):\n",
    "        super().__init__()\n",
    "        self.device = device\n",
    "\n",
    "        with torch.no_grad():\n",
    "            # Initialize weight parameters with specific configurations\n",
    "            self.mostKs = nn.ParameterList()\n",
    "            self.mostQs = nn.ParameterList()\n",
    "            self.mostVs = nn.ParameterList()\n",
    "            for head in range(1, NVTXS + 1):\n",
    "                Q = nn.Parameter(torch.zeros((2, HIDDENDIM), device=device))\n",
    "                Q[0, START_REACH - 1 + head] = SUPABIG\n",
    "                Q[1, NOTANS_FLAG_IDX] = 1\n",
    "                K = nn.Parameter(torch.zeros((2, HIDDENDIM), device=device))\n",
    "                K[0, head] = 1\n",
    "                K[1, ANS_FLAG_IDX] = BIG\n",
    "\n",
    "                V = nn.Parameter(torch.zeros((NVTXS, HIDDENDIM), device=device))\n",
    "                for i in range(NVTXS):\n",
    "                    V[i, START_SELF + i] = 1\n",
    "\n",
    "                self.mostKs.append(K)\n",
    "                self.mostQs.append(Q)\n",
    "                self.mostVs.append(V)\n",
    "            self.weirdKs = nn.ParameterList()\n",
    "            self.weirdQs = nn.ParameterList()\n",
    "            self.weirdVs = nn.ParameterList()\n",
    "            for layer in range(NVTXS):\n",
    "                K = nn.Parameter(torch.zeros((3, HIDDENDIM), device=device))\n",
    "                K[0, NOTANS_FLAG_IDX] = -BIG\n",
    "                K[0, SRC_FLAG_IDX] = BIG+SUPABIG\n",
    "                K[1, NOTANS_FLAG_IDX] = -SUPABIG\n",
    "                K[1, NVTXS + 2] = BIG+SUPABIG\n",
    "                K[1, ANS_FLAG_IDX] = -BIG-SUPABIG\n",
    "                K[2, ANS_FLAG_IDX] = MED\n",
    "\n",
    "                Q = nn.Parameter(torch.zeros((3, HIDDENDIM), device=device))\n",
    "                Q[:, ANS_FLAG_IDX] = 1\n",
    "\n",
    "                V = nn.Parameter(torch.zeros((NVTXS, HIDDENDIM), device=device))\n",
    "                V[layer, SRC_FLAG_IDX] = 1\n",
    "\n",
    "                self.weirdKs.append(K)\n",
    "                self.weirdQs.append(Q)\n",
    "                self.weirdVs.append(V)\n",
    "\n",
    "    def forward(self, src):\n",
    "        for layer in range(NVTXS):\n",
    "            allKs = [self.weirdKs[layer]] + [x for x in self.mostKs]\n",
    "            allQs = [self.weirdQs[layer]] + [x for x in self.mostQs]\n",
    "            allVs = [self.weirdVs[layer]] + [x for x in self.mostVs]\n",
    "            head_outputs = []\n",
    "            \n",
    "            for (K, Q, V) in zip(allKs, allQs, allVs):\n",
    "                ksrc = torch.matmul(src, K.unsqueeze(0).transpose(-2, -1))\n",
    "                qsrc = torch.matmul(src, Q.unsqueeze(0).transpose(-2, -1))\n",
    "                vsrc = torch.matmul(src, V.unsqueeze(0).transpose(-2, -1))\n",
    "\n",
    "                scores = torch.matmul(qsrc, ksrc.transpose(-2, -1))\n",
    "                attention_weights = torch.softmax(scores, dim=-1)\n",
    "                head_output = torch.matmul(attention_weights, vsrc)\n",
    "                head_outputs.append(head_output)\n",
    "\n",
    "            new_reaches = sum(head_outputs[1:])\n",
    "            BSZ = new_reaches.shape[0]\n",
    "\n",
    "            nodelta_nbrs = torch.zeros((BSZ, SEQLEN, NVTXS + 1), device=self.device)\n",
    "            morepadlol = torch.zeros((BSZ, SEQLEN, 1 + NVTXS), device=self.device)\n",
    "\n",
    "            src = src + torch.cat((nodelta_nbrs, new_reaches, head_outputs[0], morepadlol), dim=2)\n",
    "            src[:, :, START_REACH:START_REACH + NVTXS] = 2 * torch.sigmoid(src[:, :, START_REACH:START_REACH + NVTXS] * CURSE) - 1\n",
    "\n",
    "        canreach = src[:, 0, START_OUT:START_OUT + NVTXS]\n",
    "        final_output = 1 + torch.sum(1 - canreach, dim=1)\n",
    "        return final_output\n",
    "\n",
    "model = SillyTransformer(device).to(device)\n",
    "params = sum(p.numel() for p in model.parameters())\n",
    "print(f\"Total number of parameters: {params}\")\n",
    "\n",
    "def destroy_rand_weights(model):\n",
    "    weight_lists = [model.mostKs, model.mostQs, model.mostVs, \n",
    "                    model.weirdKs, model.weirdQs, model.weirdVs]\n",
    "    random_list = random.choice(weight_lists)\n",
    "    random_matrix = random.choice(random_list)\n",
    "    random_matrix.data = torch.randn_like(random_matrix)\n",
    "\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=1e-6)\n",
    "loss_fn = nn.MSELoss()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "execution_state": "idle",
   "id": "a9dd76f4-96f2-47b5-9bb9-a32a1b478dd4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch [0/10000], Loss: 0.4030\n",
      "Epoch [10/10000], Loss: 0.3534\n",
      "Epoch [20/10000], Loss: 0.3482\n",
      "Epoch [30/10000], Loss: 0.3803\n",
      "Epoch [40/10000], Loss: 0.3565\n",
      "Epoch [50/10000], Loss: 0.3746\n",
      "Epoch [60/10000], Loss: 0.3738\n",
      "Epoch [70/10000], Loss: 0.3184\n",
      "Epoch [80/10000], Loss: 0.3618\n",
      "Epoch [90/10000], Loss: 0.3509\n",
      "Epoch [100/10000], Loss: 0.3325\n",
      "Epoch [110/10000], Loss: 0.3196\n",
      "Epoch [120/10000], Loss: 0.3198\n",
      "Epoch [130/10000], Loss: 0.3047\n",
      "Epoch [140/10000], Loss: 0.3318\n",
      "Epoch [150/10000], Loss: 0.2962\n",
      "Epoch [160/10000], Loss: 0.3227\n",
      "Epoch [170/10000], Loss: 0.3037\n",
      "Epoch [180/10000], Loss: 0.3056\n",
      "Epoch [190/10000], Loss: 0.2926\n",
      "Epoch [200/10000], Loss: 0.2875\n",
      "Epoch [210/10000], Loss: 0.2778\n",
      "Epoch [220/10000], Loss: 0.2771\n",
      "Epoch [230/10000], Loss: 0.2859\n",
      "Epoch [240/10000], Loss: 0.2520\n",
      "Epoch [250/10000], Loss: 0.2974\n",
      "Epoch [260/10000], Loss: 0.2615\n",
      "Epoch [270/10000], Loss: 0.2589\n",
      "Epoch [280/10000], Loss: 0.2376\n",
      "Epoch [290/10000], Loss: 0.2455\n",
      "Epoch [300/10000], Loss: 0.2594\n",
      "Epoch [310/10000], Loss: 0.2397\n",
      "Epoch [320/10000], Loss: 0.2433\n",
      "Epoch [330/10000], Loss: 0.2434\n",
      "Epoch [340/10000], Loss: 0.2549\n",
      "Epoch [350/10000], Loss: 0.2190\n",
      "Epoch [360/10000], Loss: 0.2415\n",
      "Epoch [370/10000], Loss: 0.2392\n",
      "Epoch [380/10000], Loss: 0.2123\n",
      "Epoch [390/10000], Loss: 0.2555\n",
      "Epoch [400/10000], Loss: 0.2274\n",
      "Epoch [410/10000], Loss: 0.2227\n",
      "Epoch [420/10000], Loss: 0.2207\n",
      "Epoch [430/10000], Loss: 0.2249\n",
      "Epoch [440/10000], Loss: 0.2331\n",
      "Epoch [450/10000], Loss: 0.2155\n",
      "Epoch [460/10000], Loss: 0.2313\n",
      "Epoch [470/10000], Loss: 0.2363\n",
      "Epoch [480/10000], Loss: 0.2311\n",
      "Epoch [490/10000], Loss: 0.2117\n",
      "Epoch [500/10000], Loss: 0.2094\n",
      "Epoch [510/10000], Loss: 0.2217\n",
      "Epoch [520/10000], Loss: 0.2094\n",
      "Epoch [530/10000], Loss: 0.2054\n",
      "Epoch [540/10000], Loss: 0.2094\n",
      "Epoch [550/10000], Loss: 0.1928\n",
      "Epoch [560/10000], Loss: 0.2073\n",
      "Epoch [570/10000], Loss: 0.2034\n",
      "Epoch [580/10000], Loss: 0.2261\n",
      "Epoch [590/10000], Loss: 0.1980\n",
      "Epoch [600/10000], Loss: 0.2031\n",
      "Epoch [610/10000], Loss: 0.2049\n",
      "Epoch [620/10000], Loss: 0.1951\n",
      "Epoch [630/10000], Loss: 0.2012\n",
      "Epoch [640/10000], Loss: 0.2006\n",
      "Epoch [650/10000], Loss: 0.1909\n",
      "Epoch [660/10000], Loss: 0.2079\n",
      "Epoch [670/10000], Loss: 0.1896\n",
      "Epoch [680/10000], Loss: 0.1930\n",
      "Epoch [690/10000], Loss: 0.1852\n",
      "Epoch [700/10000], Loss: 0.1879\n",
      "Epoch [710/10000], Loss: 0.1957\n",
      "Epoch [720/10000], Loss: 0.1922\n",
      "Epoch [730/10000], Loss: 0.1952\n",
      "Epoch [740/10000], Loss: 0.1932\n",
      "Epoch [750/10000], Loss: 0.1937\n",
      "Epoch [760/10000], Loss: 0.1909\n",
      "Epoch [770/10000], Loss: 0.1811\n",
      "Epoch [780/10000], Loss: 0.1784\n",
      "Epoch [790/10000], Loss: 0.1765\n",
      "Epoch [800/10000], Loss: 0.1725\n",
      "Epoch [810/10000], Loss: 0.1711\n",
      "Epoch [820/10000], Loss: 0.1913\n",
      "Epoch [830/10000], Loss: 0.1795\n",
      "Epoch [840/10000], Loss: 0.1721\n",
      "Epoch [850/10000], Loss: 0.1716\n",
      "Epoch [860/10000], Loss: 0.1808\n",
      "Epoch [870/10000], Loss: 0.1842\n",
      "Epoch [880/10000], Loss: 0.1605\n",
      "Epoch [890/10000], Loss: 0.1767\n",
      "Epoch [900/10000], Loss: 0.1724\n",
      "Epoch [910/10000], Loss: 0.1687\n",
      "Epoch [920/10000], Loss: 0.1662\n",
      "Epoch [930/10000], Loss: 0.1783\n",
      "Epoch [940/10000], Loss: 0.1801\n",
      "Epoch [950/10000], Loss: 0.1731\n",
      "Epoch [960/10000], Loss: 0.1670\n",
      "Epoch [970/10000], Loss: 0.1626\n",
      "Epoch [980/10000], Loss: 0.1687\n",
      "Epoch [990/10000], Loss: 0.1548\n",
      "Epoch [1000/10000], Loss: 0.1635\n",
      "Epoch [1010/10000], Loss: 0.1692\n",
      "Epoch [1020/10000], Loss: 0.1564\n",
      "Epoch [1030/10000], Loss: 0.1635\n",
      "Epoch [1040/10000], Loss: 0.1594\n",
      "Epoch [1050/10000], Loss: 0.1605\n",
      "Epoch [1060/10000], Loss: 0.1643\n",
      "Epoch [1070/10000], Loss: 0.1619\n",
      "Epoch [1080/10000], Loss: 0.1670\n",
      "Epoch [1090/10000], Loss: 0.1602\n",
      "Epoch [1100/10000], Loss: 0.1623\n",
      "Epoch [1110/10000], Loss: 0.1625\n",
      "Epoch [1120/10000], Loss: 0.1628\n",
      "Epoch [1130/10000], Loss: 0.1542\n",
      "Epoch [1140/10000], Loss: 0.1581\n",
      "Epoch [1150/10000], Loss: 0.1667\n",
      "Epoch [1160/10000], Loss: 0.1659\n",
      "Epoch [1170/10000], Loss: 0.1515\n",
      "Epoch [1180/10000], Loss: 0.1621\n",
      "Epoch [1190/10000], Loss: 0.1620\n",
      "Epoch [1200/10000], Loss: 0.1561\n",
      "Epoch [1210/10000], Loss: 0.1584\n",
      "Epoch [1220/10000], Loss: 0.1494\n",
      "Epoch [1230/10000], Loss: 0.1625\n",
      "Epoch [1240/10000], Loss: 0.1562\n",
      "Epoch [1250/10000], Loss: 0.1560\n",
      "Epoch [1260/10000], Loss: 0.1485\n",
      "Epoch [1270/10000], Loss: 0.1491\n",
      "Epoch [1280/10000], Loss: 0.1459\n",
      "Epoch [1290/10000], Loss: 0.1521\n",
      "Epoch [1300/10000], Loss: 0.1548\n",
      "Epoch [1310/10000], Loss: 0.1527\n",
      "Epoch [1320/10000], Loss: 0.1468\n",
      "Epoch [1330/10000], Loss: 0.1465\n",
      "Epoch [1340/10000], Loss: 0.1499\n",
      "Epoch [1350/10000], Loss: 0.1423\n",
      "Epoch [1360/10000], Loss: 0.1479\n",
      "Epoch [1370/10000], Loss: 0.1544\n",
      "Epoch [1380/10000], Loss: 0.1528\n",
      "Epoch [1390/10000], Loss: 0.1450\n",
      "Epoch [1400/10000], Loss: 0.1491\n",
      "Epoch [1410/10000], Loss: 0.1430\n",
      "Epoch [1420/10000], Loss: 0.1388\n",
      "Epoch [1430/10000], Loss: 0.1387\n",
      "Epoch [1440/10000], Loss: 0.1479\n",
      "Epoch [1450/10000], Loss: 0.1378\n",
      "Epoch [1460/10000], Loss: 0.1456\n",
      "Epoch [1470/10000], Loss: 0.1418\n",
      "Epoch [1480/10000], Loss: 0.1327\n",
      "Epoch [1490/10000], Loss: 0.1418\n",
      "Epoch [1500/10000], Loss: 0.1419\n",
      "Epoch [1510/10000], Loss: 0.1322\n",
      "Epoch [1520/10000], Loss: 0.1420\n",
      "Epoch [1530/10000], Loss: 0.1405\n",
      "Epoch [1540/10000], Loss: 0.1316\n",
      "Epoch [1550/10000], Loss: 0.1314\n",
      "Epoch [1560/10000], Loss: 0.1367\n",
      "Epoch [1570/10000], Loss: 0.1345\n",
      "Epoch [1580/10000], Loss: 0.1335\n",
      "Epoch [1590/10000], Loss: 0.1371\n",
      "Epoch [1600/10000], Loss: 0.1398\n",
      "Epoch [1610/10000], Loss: 0.1316\n",
      "Epoch [1620/10000], Loss: 0.1366\n",
      "Epoch [1630/10000], Loss: 0.1347\n",
      "Epoch [1640/10000], Loss: 0.1343\n",
      "Epoch [1650/10000], Loss: 0.1297\n",
      "Epoch [1660/10000], Loss: 0.1329\n",
      "Epoch [1670/10000], Loss: 0.1342\n",
      "Epoch [1680/10000], Loss: 0.1327\n",
      "Epoch [1690/10000], Loss: 0.1301\n",
      "Epoch [1700/10000], Loss: 0.1358\n",
      "Epoch [1710/10000], Loss: 0.1292\n",
      "Epoch [1720/10000], Loss: 0.1234\n",
      "Epoch [1730/10000], Loss: 0.1244\n",
      "Epoch [1740/10000], Loss: 0.1280\n",
      "Epoch [1750/10000], Loss: 0.1277\n",
      "Epoch [1760/10000], Loss: 0.1272\n",
      "Epoch [1770/10000], Loss: 0.1267\n",
      "Epoch [1780/10000], Loss: 0.1274\n",
      "Epoch [1790/10000], Loss: 0.1208\n",
      "Epoch [1800/10000], Loss: 0.1227\n",
      "Epoch [1810/10000], Loss: 0.1185\n",
      "Epoch [1820/10000], Loss: 0.1233\n",
      "Epoch [1830/10000], Loss: 0.1268\n",
      "Epoch [1840/10000], Loss: 0.1213\n",
      "Epoch [1850/10000], Loss: 0.1167\n",
      "Epoch [1860/10000], Loss: 0.1199\n",
      "Epoch [1870/10000], Loss: 0.1213\n",
      "Epoch [1880/10000], Loss: 0.1182\n",
      "Epoch [1890/10000], Loss: 0.1177\n",
      "Epoch [1900/10000], Loss: 0.1193\n",
      "Epoch [1910/10000], Loss: 0.1166\n",
      "Epoch [1920/10000], Loss: 0.1286\n",
      "Epoch [1930/10000], Loss: 0.1201\n",
      "Epoch [1940/10000], Loss: 0.1207\n",
      "Epoch [1950/10000], Loss: 0.1253\n",
      "Epoch [1960/10000], Loss: 0.1095\n",
      "Epoch [1970/10000], Loss: 0.1168\n",
      "Epoch [1980/10000], Loss: 0.1202\n",
      "Epoch [1990/10000], Loss: 0.1193\n",
      "Epoch [2000/10000], Loss: 0.1030\n",
      "Epoch [2010/10000], Loss: 0.1196\n",
      "Epoch [2020/10000], Loss: 0.1178\n",
      "Epoch [2030/10000], Loss: 0.1162\n",
      "Epoch [2040/10000], Loss: 0.1181\n",
      "Epoch [2050/10000], Loss: 0.1083\n",
      "Epoch [2060/10000], Loss: 0.1107\n",
      "Epoch [2070/10000], Loss: 0.1101\n",
      "Epoch [2080/10000], Loss: 0.1220\n",
      "Epoch [2090/10000], Loss: 0.1143\n",
      "Epoch [2100/10000], Loss: 0.1138\n",
      "Epoch [2110/10000], Loss: 0.1162\n",
      "Epoch [2120/10000], Loss: 0.1172\n",
      "Epoch [2130/10000], Loss: 0.1067\n",
      "Epoch [2140/10000], Loss: 0.1121\n",
      "Epoch [2150/10000], Loss: 0.1150\n",
      "Epoch [2160/10000], Loss: 0.1172\n",
      "Epoch [2170/10000], Loss: 0.1084\n",
      "Epoch [2180/10000], Loss: 0.1103\n",
      "Epoch [2190/10000], Loss: 0.1059\n",
      "Epoch [2200/10000], Loss: 0.1156\n",
      "Epoch [2210/10000], Loss: 0.1053\n",
      "Epoch [2220/10000], Loss: 0.1055\n",
      "Epoch [2230/10000], Loss: 0.1160\n",
      "Epoch [2240/10000], Loss: 0.1009\n",
      "Epoch [2250/10000], Loss: 0.1030\n",
      "Epoch [2260/10000], Loss: 0.1079\n",
      "Epoch [2270/10000], Loss: 0.1008\n",
      "Epoch [2280/10000], Loss: 0.1152\n",
      "Epoch [2290/10000], Loss: 0.0997\n",
      "Epoch [2300/10000], Loss: 0.1003\n",
      "Epoch [2310/10000], Loss: 0.0990\n",
      "Epoch [2320/10000], Loss: 0.1073\n",
      "Epoch [2330/10000], Loss: 0.1062\n",
      "Epoch [2340/10000], Loss: 0.0993\n",
      "Epoch [2350/10000], Loss: 0.1045\n",
      "Epoch [2360/10000], Loss: 0.1106\n",
      "Epoch [2370/10000], Loss: 0.1167\n",
      "Epoch [2380/10000], Loss: 0.1008\n",
      "Epoch [2390/10000], Loss: 0.1025\n",
      "Epoch [2400/10000], Loss: 0.0958\n",
      "Epoch [2410/10000], Loss: 0.0966\n",
      "Epoch [2420/10000], Loss: 0.1066\n",
      "Epoch [2430/10000], Loss: 0.1135\n",
      "Epoch [2440/10000], Loss: 0.1117\n",
      "Epoch [2450/10000], Loss: 0.1046\n",
      "Epoch [2460/10000], Loss: 0.1019\n",
      "Epoch [2470/10000], Loss: 0.1012\n",
      "Epoch [2480/10000], Loss: 0.0993\n",
      "Epoch [2490/10000], Loss: 0.1014\n",
      "Epoch [2500/10000], Loss: 0.1037\n",
      "Epoch [2510/10000], Loss: 0.1085\n",
      "Epoch [2520/10000], Loss: 0.1081\n",
      "Epoch [2530/10000], Loss: 0.1021\n",
      "Epoch [2540/10000], Loss: 0.0989\n",
      "Epoch [2550/10000], Loss: 0.1006\n",
      "Epoch [2560/10000], Loss: 0.0941\n",
      "Epoch [2570/10000], Loss: 0.0911\n",
      "Epoch [2580/10000], Loss: 0.1020\n",
      "Epoch [2590/10000], Loss: 0.0937\n",
      "Epoch [2600/10000], Loss: 0.1063\n",
      "Epoch [2610/10000], Loss: 0.1030\n",
      "Epoch [2620/10000], Loss: 0.0890\n",
      "Epoch [2630/10000], Loss: 0.0973\n",
      "Epoch [2640/10000], Loss: 0.0938\n",
      "Epoch [2650/10000], Loss: 0.1019\n",
      "Epoch [2660/10000], Loss: 0.1008\n",
      "Epoch [2670/10000], Loss: 0.1037\n",
      "Epoch [2680/10000], Loss: 0.0887\n",
      "Epoch [2690/10000], Loss: 0.0953\n",
      "Epoch [2700/10000], Loss: 0.0997\n",
      "Epoch [2710/10000], Loss: 0.1033\n",
      "Epoch [2720/10000], Loss: 0.0901\n",
      "Epoch [2730/10000], Loss: 0.1019\n",
      "Epoch [2740/10000], Loss: 0.0908\n",
      "Epoch [2750/10000], Loss: 0.0960\n",
      "Epoch [2760/10000], Loss: 0.0952\n",
      "Epoch [2770/10000], Loss: 0.1047\n",
      "Epoch [2780/10000], Loss: 0.0878\n",
      "Epoch [2790/10000], Loss: 0.1007\n",
      "Epoch [2800/10000], Loss: 0.0876\n",
      "Epoch [2810/10000], Loss: 0.0936\n",
      "Epoch [2820/10000], Loss: 0.0989\n",
      "Epoch [2830/10000], Loss: 0.0906\n",
      "Epoch [2840/10000], Loss: 0.0951\n",
      "Epoch [2850/10000], Loss: 0.0913\n",
      "Epoch [2860/10000], Loss: 0.0993\n",
      "Epoch [2870/10000], Loss: 0.0904\n",
      "Epoch [2880/10000], Loss: 0.0974\n",
      "Epoch [2890/10000], Loss: 0.0882\n",
      "Epoch [2900/10000], Loss: 0.0912\n",
      "Epoch [2910/10000], Loss: 0.1034\n",
      "Epoch [2920/10000], Loss: 0.0918\n",
      "Epoch [2930/10000], Loss: 0.0898\n",
      "Epoch [2940/10000], Loss: 0.0914\n",
      "Epoch [2950/10000], Loss: 0.0858\n",
      "Epoch [2960/10000], Loss: 0.0940\n",
      "Epoch [2970/10000], Loss: 0.0834\n",
      "Epoch [2980/10000], Loss: 0.0952\n",
      "Epoch [2990/10000], Loss: 0.1028\n",
      "Epoch [3000/10000], Loss: 0.1005\n",
      "Epoch [3010/10000], Loss: 0.0724\n",
      "Epoch [3020/10000], Loss: 0.1007\n",
      "Epoch [3030/10000], Loss: 0.0883\n",
      "Epoch [3040/10000], Loss: 0.0877\n",
      "Epoch [3050/10000], Loss: 0.0902\n",
      "Epoch [3060/10000], Loss: 0.0882\n",
      "Epoch [3070/10000], Loss: 0.0935\n",
      "Epoch [3080/10000], Loss: 0.1021\n",
      "Epoch [3090/10000], Loss: 0.0936\n",
      "Epoch [3100/10000], Loss: 0.0822\n",
      "Epoch [3110/10000], Loss: 0.0839\n",
      "Epoch [3120/10000], Loss: 0.0907\n",
      "Epoch [3130/10000], Loss: 0.0872\n",
      "Epoch [3140/10000], Loss: 0.0820\n",
      "Epoch [3150/10000], Loss: 0.0804\n",
      "Epoch [3160/10000], Loss: 0.0847\n",
      "Epoch [3170/10000], Loss: 0.0791\n",
      "Epoch [3180/10000], Loss: 0.0934\n",
      "Epoch [3190/10000], Loss: 0.0854\n",
      "Epoch [3200/10000], Loss: 0.0892\n",
      "Epoch [3210/10000], Loss: 0.0869\n",
      "Epoch [3220/10000], Loss: 0.0952\n",
      "Epoch [3230/10000], Loss: 0.0943\n",
      "Epoch [3240/10000], Loss: 0.0885\n",
      "Epoch [3250/10000], Loss: 0.0763\n",
      "Epoch [3260/10000], Loss: 0.0804\n",
      "Epoch [3270/10000], Loss: 0.0832\n",
      "Epoch [3280/10000], Loss: 0.0862\n",
      "Epoch [3290/10000], Loss: 0.0826\n",
      "Epoch [3300/10000], Loss: 0.0783\n",
      "Epoch [3310/10000], Loss: 0.0882\n",
      "Epoch [3320/10000], Loss: 0.0827\n",
      "Epoch [3330/10000], Loss: 0.0819\n",
      "Epoch [3340/10000], Loss: 0.0835\n",
      "Epoch [3350/10000], Loss: 0.0885\n",
      "Epoch [3360/10000], Loss: 0.0873\n",
      "Epoch [3370/10000], Loss: 0.0872\n",
      "Epoch [3380/10000], Loss: 0.0854\n",
      "Epoch [3390/10000], Loss: 0.0862\n",
      "Epoch [3400/10000], Loss: 0.0872\n",
      "Epoch [3410/10000], Loss: 0.0908\n",
      "Epoch [3420/10000], Loss: 0.0865\n",
      "Epoch [3430/10000], Loss: 0.0842\n",
      "Epoch [3440/10000], Loss: 0.0770\n",
      "Epoch [3450/10000], Loss: 0.0866\n",
      "Epoch [3460/10000], Loss: 0.0848\n",
      "Epoch [3470/10000], Loss: 0.0885\n",
      "Epoch [3480/10000], Loss: 0.0770\n",
      "Epoch [3490/10000], Loss: 0.0871\n",
      "Epoch [3500/10000], Loss: 0.0807\n",
      "Epoch [3510/10000], Loss: 0.0751\n",
      "Epoch [3520/10000], Loss: 0.0766\n",
      "Epoch [3530/10000], Loss: 0.0763\n",
      "Epoch [3540/10000], Loss: 0.0727\n",
      "Epoch [3550/10000], Loss: 0.0829\n",
      "Epoch [3560/10000], Loss: 0.0791\n",
      "Epoch [3570/10000], Loss: 0.0770\n",
      "Epoch [3580/10000], Loss: 0.0850\n",
      "Epoch [3590/10000], Loss: 0.0774\n",
      "Epoch [3600/10000], Loss: 0.0766\n",
      "Epoch [3610/10000], Loss: 0.0726\n",
      "Epoch [3620/10000], Loss: 0.0750\n",
      "Epoch [3630/10000], Loss: 0.0723\n",
      "Epoch [3640/10000], Loss: 0.0769\n",
      "Epoch [3650/10000], Loss: 0.0825\n",
      "Epoch [3660/10000], Loss: 0.0734\n",
      "Epoch [3670/10000], Loss: 0.0700\n",
      "Epoch [3680/10000], Loss: 0.0803\n",
      "Epoch [3690/10000], Loss: 0.0784\n",
      "Epoch [3700/10000], Loss: 0.0819\n",
      "Epoch [3710/10000], Loss: 0.0697\n",
      "Epoch [3720/10000], Loss: 0.0818\n",
      "Epoch [3730/10000], Loss: 0.0698\n",
      "Epoch [3740/10000], Loss: 0.0672\n",
      "Epoch [3750/10000], Loss: 0.0778\n",
      "Epoch [3760/10000], Loss: 0.0663\n",
      "Epoch [3770/10000], Loss: 0.0721\n",
      "Epoch [3780/10000], Loss: 0.0773\n",
      "Epoch [3790/10000], Loss: 0.0671\n",
      "Epoch [3800/10000], Loss: 0.0692\n",
      "Epoch [3810/10000], Loss: 0.0719\n",
      "Epoch [3820/10000], Loss: 0.0676\n",
      "Epoch [3830/10000], Loss: 0.0747\n",
      "Epoch [3840/10000], Loss: 0.0712\n",
      "Epoch [3850/10000], Loss: 0.0696\n",
      "Epoch [3860/10000], Loss: 0.0689\n",
      "Epoch [3870/10000], Loss: 0.0797\n",
      "Epoch [3880/10000], Loss: 0.0600\n",
      "Epoch [3890/10000], Loss: 0.0755\n",
      "Epoch [3900/10000], Loss: 0.0715\n",
      "Epoch [3910/10000], Loss: 0.0741\n",
      "Epoch [3920/10000], Loss: 0.0755\n",
      "Epoch [3930/10000], Loss: 0.0634\n",
      "Epoch [3940/10000], Loss: 0.0695\n",
      "Epoch [3950/10000], Loss: 0.0682\n",
      "Epoch [3960/10000], Loss: 0.0688\n",
      "Epoch [3970/10000], Loss: 0.0794\n",
      "Epoch [3980/10000], Loss: 0.0741\n",
      "Epoch [3990/10000], Loss: 0.0751\n",
      "Epoch [4000/10000], Loss: 0.0680\n",
      "Epoch [4010/10000], Loss: 0.0723\n",
      "Epoch [4020/10000], Loss: 0.0605\n",
      "Epoch [4030/10000], Loss: 0.0654\n",
      "Epoch [4040/10000], Loss: 0.0722\n",
      "Epoch [4050/10000], Loss: 0.0748\n",
      "Epoch [4060/10000], Loss: 0.0674\n",
      "Epoch [4070/10000], Loss: 0.0652\n",
      "Epoch [4080/10000], Loss: 0.0621\n",
      "Epoch [4090/10000], Loss: 0.0638\n",
      "Epoch [4100/10000], Loss: 0.0700\n",
      "Epoch [4110/10000], Loss: 0.0682\n",
      "Epoch [4120/10000], Loss: 0.0722\n",
      "Epoch [4130/10000], Loss: 0.0689\n",
      "Epoch [4140/10000], Loss: 0.0708\n",
      "Epoch [4150/10000], Loss: 0.0624\n",
      "Epoch [4160/10000], Loss: 0.0670\n",
      "Epoch [4170/10000], Loss: 0.0706\n",
      "Epoch [4180/10000], Loss: 0.0649\n",
      "Epoch [4190/10000], Loss: 0.0571\n",
      "Epoch [4200/10000], Loss: 0.0610\n",
      "Epoch [4210/10000], Loss: 0.0668\n",
      "Epoch [4220/10000], Loss: 0.0699\n",
      "Epoch [4230/10000], Loss: 0.0606\n",
      "Epoch [4240/10000], Loss: 0.0695\n",
      "Epoch [4250/10000], Loss: 0.0627\n",
      "Epoch [4260/10000], Loss: 0.0583\n",
      "Epoch [4270/10000], Loss: 0.0583\n",
      "Epoch [4280/10000], Loss: 0.0695\n",
      "Epoch [4290/10000], Loss: 0.0615\n",
      "Epoch [4300/10000], Loss: 0.0634\n",
      "Epoch [4310/10000], Loss: 0.0678\n",
      "Epoch [4320/10000], Loss: 0.0624\n",
      "Epoch [4330/10000], Loss: 0.0684\n",
      "Epoch [4340/10000], Loss: 0.0639\n",
      "Epoch [4350/10000], Loss: 0.0642\n",
      "Epoch [4360/10000], Loss: 0.0638\n",
      "Epoch [4370/10000], Loss: 0.0575\n",
      "Epoch [4380/10000], Loss: 0.0615\n",
      "Epoch [4390/10000], Loss: 0.0763\n",
      "Epoch [4400/10000], Loss: 0.0676\n",
      "Epoch [4410/10000], Loss: 0.0716\n",
      "Epoch [4420/10000], Loss: 0.0634\n",
      "Epoch [4430/10000], Loss: 0.0600\n",
      "Epoch [4440/10000], Loss: 0.0663\n",
      "Epoch [4450/10000], Loss: 0.0662\n",
      "Epoch [4460/10000], Loss: 0.0553\n",
      "Epoch [4470/10000], Loss: 0.0603\n",
      "Epoch [4480/10000], Loss: 0.0583\n",
      "Epoch [4490/10000], Loss: 0.0590\n",
      "Epoch [4500/10000], Loss: 0.0634\n",
      "Epoch [4510/10000], Loss: 0.0639\n",
      "Epoch [4520/10000], Loss: 0.0596\n",
      "Epoch [4530/10000], Loss: 0.0670\n",
      "Epoch [4540/10000], Loss: 0.0605\n",
      "Epoch [4550/10000], Loss: 0.0548\n",
      "Epoch [4560/10000], Loss: 0.0680\n",
      "Epoch [4570/10000], Loss: 0.0663\n",
      "Epoch [4580/10000], Loss: 0.0672\n",
      "Epoch [4590/10000], Loss: 0.0727\n",
      "Epoch [4600/10000], Loss: 0.0669\n",
      "Epoch [4610/10000], Loss: 0.0651\n",
      "Epoch [4620/10000], Loss: 0.0619\n",
      "Epoch [4630/10000], Loss: 0.0664\n",
      "Epoch [4640/10000], Loss: 0.0580\n",
      "Epoch [4650/10000], Loss: 0.0690\n",
      "Epoch [4660/10000], Loss: 0.0539\n",
      "Epoch [4670/10000], Loss: 0.0584\n",
      "Epoch [4680/10000], Loss: 0.0636\n",
      "Epoch [4690/10000], Loss: 0.0631\n",
      "Epoch [4700/10000], Loss: 0.0730\n",
      "Epoch [4710/10000], Loss: 0.0631\n",
      "Epoch [4720/10000], Loss: 0.0496\n",
      "Epoch [4730/10000], Loss: 0.0663\n",
      "Epoch [4740/10000], Loss: 0.0571\n",
      "Epoch [4750/10000], Loss: 0.0634\n",
      "Epoch [4760/10000], Loss: 0.0647\n",
      "Epoch [4770/10000], Loss: 0.0679\n",
      "Epoch [4780/10000], Loss: 0.0580\n",
      "Epoch [4790/10000], Loss: 0.0614\n",
      "Epoch [4800/10000], Loss: 0.0570\n",
      "Epoch [4810/10000], Loss: 0.0679\n",
      "Epoch [4820/10000], Loss: 0.0531\n",
      "Epoch [4830/10000], Loss: 0.0569\n",
      "Epoch [4840/10000], Loss: 0.0690\n",
      "Epoch [4850/10000], Loss: 0.0675\n",
      "Epoch [4860/10000], Loss: 0.0644\n",
      "Epoch [4870/10000], Loss: 0.0585\n",
      "Epoch [4880/10000], Loss: 0.0539\n",
      "Epoch [4890/10000], Loss: 0.0619\n",
      "Epoch [4900/10000], Loss: 0.0610\n",
      "Epoch [4910/10000], Loss: 0.0623\n",
      "Epoch [4920/10000], Loss: 0.0625\n",
      "Epoch [4930/10000], Loss: 0.0591\n",
      "Epoch [4940/10000], Loss: 0.0648\n",
      "Epoch [4950/10000], Loss: 0.0549\n",
      "Epoch [4960/10000], Loss: 0.0677\n",
      "Epoch [4970/10000], Loss: 0.0737\n",
      "Epoch [4980/10000], Loss: 0.0610\n",
      "Epoch [4990/10000], Loss: 0.0603\n",
      "Epoch [5000/10000], Loss: 0.0615\n",
      "Epoch [5010/10000], Loss: 0.0562\n",
      "Epoch [5020/10000], Loss: 0.0525\n",
      "Epoch [5030/10000], Loss: 0.0663\n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[47], line 7\u001b[0m\n\u001b[1;32m      5\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m epoch \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(num_epochs):\n\u001b[1;32m      6\u001b[0m     model\u001b[38;5;241m.\u001b[39mtrain()\n\u001b[0;32m----> 7\u001b[0m     data, labels \u001b[38;5;241m=\u001b[39m \u001b[43mmkbatch\u001b[49m\u001b[43m(\u001b[49m\u001b[43mbatch_size\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m      8\u001b[0m     outputs \u001b[38;5;241m=\u001b[39m model(data)\n\u001b[1;32m      9\u001b[0m     loss \u001b[38;5;241m=\u001b[39m loss_fn(outputs, labels)\n",
      "Cell \u001b[0;32mIn[45], line 78\u001b[0m, in \u001b[0;36mmkbatch\u001b[0;34m(size)\u001b[0m\n\u001b[1;32m     75\u001b[0m distances \u001b[38;5;241m=\u001b[39m []\n\u001b[1;32m     77\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m _ \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(size):\n\u001b[0;32m---> 78\u001b[0m     data, adj_list \u001b[38;5;241m=\u001b[39m \u001b[43mrandom_graph\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdevice\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m     79\u001b[0m     dist \u001b[38;5;241m=\u001b[39m SSSP(adj_list)\n\u001b[1;32m     80\u001b[0m     graphs\u001b[38;5;241m.\u001b[39mappend(data)\n",
      "Cell \u001b[0;32mIn[45], line 48\u001b[0m, in \u001b[0;36mrandom_graph\u001b[0;34m(device)\u001b[0m\n\u001b[1;32m     46\u001b[0m         data[v, NVTXS \u001b[38;5;241m+\u001b[39m u] \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m     47\u001b[0m         data[u, NVTXS \u001b[38;5;241m+\u001b[39m v] \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[0;32m---> 48\u001b[0m         \u001b[43madj_list\u001b[49m\u001b[43m[\u001b[49m\u001b[43mu\u001b[49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43madd\u001b[49m\u001b[43m(\u001b[49m\u001b[43mv\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m     49\u001b[0m         adj_list[v]\u001b[38;5;241m.\u001b[39madd(u)\n\u001b[1;32m     51\u001b[0m \u001b[38;5;66;03m# Set flags\u001b[39;00m\n",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "destroy_rand_weights(model)\n",
    "num_epochs = 10000\n",
    "batch_size = 1<<9\n",
    "train_err = []\n",
    "for epoch in range(num_epochs):\n",
    "    model.train()\n",
    "    data, labels = mkbatch(batch_size)\n",
    "    outputs = model(data)\n",
    "    loss = loss_fn(outputs, labels)\n",
    "    optimizer.zero_grad()\n",
    "    loss.backward()\n",
    "    optimizer.step()\n",
    "    train_err.append(loss.item())\n",
    "    if epoch % 10 == 0:\n",
    "        print(f\"Epoch [{epoch}/{num_epochs}], Loss: {loss.item():.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "execution_state": "idle",
   "id": "dcbdebf6-5c9f-4491-a442-9271d2ba5696",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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iuVv3rw5jh8/juTkZM2nT58+eO6555CcnFxiq82VK1dw8+ZN+Pj4GPV+iIiIyDIo/gosJiYGUVFRaN++PTp27Ih58+YhJycH0dHRAIBhw4ahbt26iI+Ph6OjI1q3bq1zfs2aNQFAk56dnY0ZM2ZgwIAB8Pb2xvnz5zFlyhQ0btwYYWFhCtwhERERmRvFA6DIyEjcuHED06ZNQ0pKCgIDA5GQkKDpGH3p0qXHjuUvzNbWFseOHcPKlStx584d+Pr6okePHpg5cyZfcxERERFgDvMAmaPMzEy4ubkhIyODo8CIiIgsRHl+v81iKQwiIiIiU2IARERERFaHARARERFZHQZAREREZHUYABEREZHVYQBEREREVocBkInduwdw4gEiIiJlMQAyoRs3gOrVAT3LkREREZEJMQAyoW++kd+7dildEyIiIuvGAIiIiIisDgMgIiIisjoMgIiIiMjqMAAiIiIiq8MAiIiIiKwOAyAiIiKyOgyATIgTIBIREZkHBkBERERkdRgAERERkdVhAERERERWhwEQERERWR0GQERERGR1GAARERGR1WEAZEIcBk9ERGQeGAARERGR1WEARERERFaHARARERFZHQZAREREZHUYABEREZHVYQBEREREVocBkEL++kvpGhAREVkvBkAKadQIuH1b6VoQERFZJwZACmrcWOkaEBERWScGQCZ0967u/q1bStWEiIjIuplFALRo0SL4+/vD0dERwcHBOHDgQJnOW79+PVQqFfr166eTLoTAtGnT4OPjAycnJ4SGhuLs2bNGqn3ZzZundA2IiIgI5hAAbdiwATExMYiLi8ORI0fQrl07hIWFIS0trdTzLl68iEmTJqFz587Fjs2ZMwfz58/HkiVLsH//ftSoUQNhYWG4f/++Ee/k8WwUf9pEREQEcwiA5s6di9deew3R0dFo2bIllixZgurVq2PZsmUlnpOfn48hQ4ZgxowZCAgI0DkmhMC8efPwzjvvoG/fvmjbti1WrVqFa9euYcuWLSa4o5KpVIpenoiIiB5RNADKy8vD4cOHERoaqq2QjQ1CQ0ORlJRU4nn//ve/4enpiREjRhQ7duHCBaSkpOiU6ebmhuDg4BLLzM3NRWZmps7HGNgCREREZB4U/UlOT09Hfn4+vLy8dNK9vLyQkpKi95zffvsNX375JZYuXar3eMF55SkzPj4ebm5u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3dJc/+TJk9ixYwdOnTqFxYsXw8PDw8RPgYgqxChrzBMRVVJUVJSwtbUVNWrU0Pm8//77QgghAIhRo0bpnBMcHCxGjx4thBDi888/F+7u7iI7O1tz/Pvvvxc2NjYiJSVFCCGEr6+vePvtt0usAwDxzjvvaPazs7MFALFjxw4hhBC9e/cW0dHRBr5zIjIF9gEiIrP13HPPYfHixTpptWrV0myHhIToHAsJCUFycjIA4NSpU2jXrh1q1KihOf70009DrVbjzJkzUKlUuHbtGrp161ZqHdq2bavZrlGjBlxdXZGWlgYAGD16NAYMGIAjR46gR48e6NevH5566qlK3jURmQIDICIyWzVq1Cj2SspQnJycypSvWrVqOvsqlQpqtRoAEBERgb///hvbt2/Hzp070a1bN4wdOxYffvihUepMRIbDPkBEZLH27dtXbL9FixYAgBYtWuD3339HTk6O5vjevXthY2ODZs2awcXFBf7+/khMTKxUHerUqYOoqCisWbMG8+bNw+eff16p8ojINNgCRERmKzc3FykpKTppdnZ2mo7GGzduRPv27fHMM8/gq6++woEDB/Dll18CAIYMGYK4uDhERUVh+vTpuHHjBsaPH4+hQ4fCy8sLADB9+nSMGjUKnp6eiIiIQFZWFvbu3Yvx48eXqX7Tpk1DUFAQWrVqhdzcXGzbtk0TgBGReWMARERmKyEhAT4+PjppzZo1w+nTp4FHI7TWr1+PMWPGwMfHB+vWrUPLli0BANWrV8cPP/yAN954Ax06dED16tUxYMAAzJ07V1NWVFQU7t+/j48//hiTJk2Ch4cHBg4cWOb62dvbIzY2FhcvXoSTkxM6d+6M9evXG+z+ich4VEKOdCAisigqlQqbN29Gv379lK4KEVkg9gEiIiIiq8MAiIiIiKwO+wARkUXi23siqgy2ABEREZHVYQBEREREVocBEBEREVkdBkBERERkdRgAERERkdVhAERERERWhwEQERERWR0GQERERGR1/h+D2qqc9L0DcQAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.suptitle('MSE vs Epochs')\n",
    "plt.plot(train_err, label='Train', color='blue')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('MSE')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "execution_state": "idle",
   "id": "30893731-9991-4df9-b6c6-380010569ee1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAvQAAAJOCAYAAADYjZMFAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABSyElEQVR4nO3deXQUZfb/8U8nkA5bwhpCICSArLIT4YvIJgFkG1FH2ZQAgqMTFI2ooEBYlLiBuCCrAjoy4AY6giAkLIOCIAiCCrIKAyQBWRICJNBdvz+E/hkTIB27ekm/X+fUOemnq566VXr05ubWUxbDMAwBAAAA8EkBng4AAAAAQOGR0AMAAAA+jIQeAAAA8GEk9AAAAIAPI6EHAAAAfBgJPQAAAODDSOgBAAAAH0ZCDwAAAPgwEnoAAADAh5HQA/Bpe/fuVZcuXRQaGiqLxaKlS5e6dP5Dhw7JYrFo/vz5Lp3Xl3Xo0EEdOnTwdBgAgCtI6AH8Zfv379c//vEP1axZU8HBwQoJCVGbNm30+uuv68KFC6aeOy4uTjt37tQLL7yg999/XzExMaaez50GDRoki8WikJCQfO/j3r17ZbFYZLFY9Oqrrzo9/7FjxzR+/Hht377dRREDADyhmKcDAODbli1bpnvvvVdWq1UDBw5Uw4YNlZOTow0bNuipp57Sjz/+qNmzZ5ty7gsXLmjjxo167rnnNHz4cFPOERUVpQsXLqh48eKmzH8jxYoV0/nz5/Wf//xH9913X67vPvjgAwUHB+vixYuFmvvYsWOaMGGCoqOj1bRp0wIf99VXXxXqfAAAc5DQAyi0gwcPqm/fvoqKilJKSoqqVKni+C4+Pl779u3TsmXLTDv/iRMnJElly5Y17RwWi0XBwcGmzX8jVqtVbdq00b///e88Cf3ChQvVo0cPffLJJ26J5fz58ypZsqSCgoLccj4AQMHQcgOg0F5++WWdO3dO77zzTq5k/qqbbrpJI0aMcHy+fPmyJk2apFq1aslqtSo6OlrPPvussrOzcx0XHR2tnj17asOGDWrZsqWCg4NVs2ZNvffee459xo8fr6ioKEnSU089JYvFoujoaOlKq8rVn/9o/PjxslgsucZWrVql2267TWXLllXp0qVVt25dPfvss47vr9VDn5KSorZt26pUqVIqW7as7rzzTv3888/5nm/fvn0aNGiQypYtq9DQUA0ePFjnz58v8H3u37+/vvzyS505c8YxtmXLFu3du1f9+/fPs/+pU6c0cuRINWrUSKVLl1ZISIi6deumHTt2OPZZu3atbrnlFknS4MGDHa07V6+zQ4cOatiwobZu3ap27dqpZMmSjvvy5x76uLg4BQcH57n+rl27qly5cjp27FiBrxUA4DwSegCF9p///Ec1a9bUrbfeWqD9hw4dqnHjxql58+Z67bXX1L59eyUlJalv37559t23b5/+/ve/q3PnzpoyZYrKlSunQYMG6ccff5Qk3X333XrttdckSf369dP777+vadOmORX/jz/+qJ49eyo7O1sTJ07UlClT9Le//U1ff/31dY9bvXq1unbtqvT0dI0fP14JCQn65ptv1KZNGx06dCjP/vfdd58yMzOVlJSk++67T/Pnz9eECRMKHOfdd98ti8WiTz/91DG2cOFC1atXT82bN8+z/4EDB7R06VL17NlTU6dO1VNPPaWdO3eqffv2juS6fv36mjhxoiTpoYce0vvvv6/3339f7dq1c8zz22+/qVu3bmratKmmTZumjh075hvf66+/rkqVKikuLk42m02SNGvWLH311Vd68803FRERUeBrBQAUggEAhXD27FlDknHnnXcWaP/t27cbkoyhQ4fmGh85cqQhyUhJSXGMRUVFGZKM9evXO8bS09MNq9VqPPnkk46xgwcPGpKMV155JdeccXFxRlRUVJ4YEhMTjT/+Z++1114zJBknTpy4ZtxXzzFv3jzHWNOmTY2wsDDjt99+c4zt2LHDCAgIMAYOHJjnfEOGDMk151133WVUqFDhmuf843WUKlXKMAzD+Pvf/2506tTJMAzDsNlsRnh4uDFhwoR878HFixcNm82W5zqsVqsxceJEx9iWLVvyXNtV7du3NyQZM2fOzPe79u3b5xpbuXKlIcl4/vnnjQMHDhilS5c2evfufcNrBAD8dVToARRKRkaGJKlMmTIF2n/58uWSpISEhFzjTz75pHTl4do/atCggdq2bev4XKlSJdWtW1cHDhz4y7FfdbX3/rPPPpPdbi/QMcePH9f27ds1aNAglS9f3jHeuHFjde7c2XGdf/Twww/n+ty2bVv99ttvjntYEP3799fatWuVmpqqlJQUpaam5ttuoyt99wEBv//n3Waz6bfffnO0E23btq3A57RarRo8eHCB9u3SpYv+8Y9/aOLEibr77rsVHBysWbNmFfhcAIDCI6EHUCghISGSpMzMzALt/+uvvyogIEA33XRTrvHw8HCVLVtWv/76a67x6tWr55mjXLlyOn369F+K+4/69OmjNm3aaOjQoapcubL69u2rDz/88LrJ/dU469atm+e7+vXr6+TJk8rKyso1/udrKVeunCQ5dS3du3dXmTJltHjxYn3wwQe65ZZb8tzLq+x2u1577TXVrl1bVqtVFStWVKVKlfTDDz/o7NmzBT5n1apVnXoA9tVXX1X58uW1fft2vfHGGwoLCyvwsQCAwiOhB1AoISEhioiI0K5du5w67s8PpV5LYGBgvuOGYRT6HFf7u68qUaKE1q9fr9WrV+uBBx7QDz/8oD59+qhz58559v0r/sq1XGW1WnX33XdrwYIFWrJkyTWr85I0efJkJSQkqF27dvrXv/6llStXatWqVbr55psL/JcIXbk/zvj++++Vnp4uSdq5c6dTxwIACo+EHkCh9ezZU/v379fGjRtvuG9UVJTsdrv27t2bazwtLU1nzpxxrFjjCuXKlcu1IsxVf/4rgCQFBASoU6dOmjp1qn766Se98MILSklJ0Zo1a655HZK0Z8+ePN/t3r1bFStWVKlSpVxyHX/Wv39/ff/998rMzMz3QeKrPv74Y3Xs2FHvvPOO+vbtqy5duig2NjbPPSnoL1cFkZWVpcGDB6tBgwZ66KGH9PLLL2vLli0umx8AcG0k9AAK7emnn1apUqU0dOhQpaWl5fl+//79ev3116UrLSOS8qxEM3XqVElSjx49XBZXrVq1dPbsWf3www+OsePHj2vJkiW59jt16lSeY6++YOnPS2leVaVKFTVt2lQLFizIlSDv2rVLX331leM6zdCxY0dNmjRJb731lsLDw6+5X2BgYJ7q/0cffaSjR4/mGrv6i0d+v/w465lnntHhw4e1YMECTZ06VdHR0YqLi7vmfQQAuA4vlgJQaLVq1dLChQvVp08f1a9fP9ebYr/55ht99NFHGjRokCSpSZMmiouL0+zZs3XmzBm1b99emzdv1oIFC9S7d+9rLolYGH379tUzzzyju+66S4899pjOnz+vGTNmqE6dOrkeCp04caLWr1+vHj16KCoqSunp6Xr77bdVrVo13Xbbbdec/5VXXlG3bt3UunVrPfjgg7pw4YLefPNNhYaGavz48S67jj8LCAjQ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p7Zt2+qpp57Sq6++qqCgIOnKLwV/9PXXX+vtt99W9+7dJUlHjhzRyZMnnYqrcePGWrBggS5dupQn8a9cubIiIiJ04MABDRgwwKl5AQDei4QeQJFw++2365VXXtF7772n1q1b61//+pd27drlaKcpU6aMRo4cqSeeeEJ2u1233Xabzp49q6+//lohISHX7R8/f/68UlNTdfnyZf3vf//TkiVL9Nprr+mRRx5Rx44d8z1m3LhxatGihW6++WZlZ2friy++UP369SVJYWFhKlGihFasWKFq1aopODhYoaGhql27tt5//33FxMQoIyNDTz311HUr7vkZPny43nzzTfXt21ejR49WaGioNm3apJYtW6pu3bqaMGGCHnvsMYWGhuqOO+5Qdna2vvvuO50+fVoJCQlOnQsA4B1Y5QZAkdC1a1eNHTtWTz/9tG655RZlZmZq4MCBufaZNGmSxo4dq6SkJNWvX1933HGHli1b5lhG8lrmzJmjKlWqqFatWrr77rv1008/afHixddd7jEoKEijR49W48aN1a5dOwUGBmrRokWSpGLFiumNN97QrFmzFBERoTvvvFOS9M477+j06dNq3ry5HnjgAT322GMKCwtz6j5UqFBBKSkpOnfunNq3b68WLVpozpw5jmr90KFDNXfuXM2bN0+NGjVS+/btNX/+/BveAwCA97IYf246BQAAAOAzqNADAAAAPoyEHgAAAPBhJPQAAACADyOhBwAAAHwYCT0AAADgw0joAQAAAB9GQg8AAAD4MBJ6AAAAwIeR0AMAAAA+jIQeAAAA8GEk9AAAAIAPI6EHAAAAfNj/A5Vw+hJb4IT1AAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 800x600 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "batch_src, batch_labels = map(lambda x: x.to(device), mkbatch(1<<10))\n",
    "model.eval()\n",
    "with torch.no_grad():\n",
    "    output = model(batch_src)\n",
    "\n",
    "# Flatten the arrays to 1D\n",
    "x = batch_labels.detach().to(torch.float16).cpu().numpy().flatten()\n",
    "y = output.detach().to(torch.float16).cpu().numpy().flatten()\n",
    "\n",
    "# Define the number of vertices and number of bins per dimension\n",
    "bins_y = 5 * NVTXS  # 10 * nvtxs for y-bin size\n",
    "\n",
    "# Initialize the 2D array (matrix) to store the counts\n",
    "count_matrix = np.zeros((NVTXS, bins_y), dtype=float)\n",
    "\n",
    "# Process the data: Map x to rows and floor(y*10) to columns\n",
    "for xi, yi in zip(x, y):\n",
    "    row = int(xi)  # Use integer value of x for row index\n",
    "    col = int(np.floor(yi * 5))  # Map y values to column by flooring and scaling by 10\n",
    "    if 0 <= row < NVTXS and 0 <= col < bins_y:  # Ensure valid indices\n",
    "        count_matrix[row, col] += 1\n",
    "\n",
    "# Transpose the matrix\n",
    "count_matrix = count_matrix.T\n",
    "\n",
    "# column_sums = count_matrix.sum(axis=0)  # Sum of each column\n",
    "# count_matrix = np.divide(count_matrix, column_sums, where=column_sums >.001)  # Avoid division by zero\n",
    "for i in range(count_matrix.shape[1]):\n",
    "    if np.sum(count_matrix[:,i])>1:\n",
    "        count_matrix[:,i] = count_matrix[:,i] / np.sum(count_matrix[:,i])\n",
    "    \n",
    "# Plot the heatmap\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.imshow(count_matrix, cmap='viridis', origin='lower', interpolation='nearest', aspect='auto')\n",
    "\n",
    "# Set the labels and title\n",
    "plt.ylabel('Predicted Distance')\n",
    "plt.yticks([i*5 for i in range(8)], [i for i in range(8)])\n",
    "plt.xlabel('True Distance')\n",
    "plt.title('Confusion Matrix')\n",
    "\n",
    "# Add a colorbar for reference\n",
    "plt.colorbar(label='Count')\n",
    "\n",
    "# Show the plot\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}