Switch to unified view

a b/Code/All Qiskit, PennyLane QML Nov 23/36a1 GAN A100 Light.gpu 10.10s kkawchak.ipynb
1
{
2
  "cells": [
3
    {
4
      "cell_type": "code",
5
      "execution_count": 19,
6
      "metadata": {
7
        "colab": {
8
          "base_uri": "https://localhost:8080/",
9
          "height": 0
10
        },
11
        "id": "URenBt8iB4_G",
12
        "outputId": "55500867-9b1c-44de-8414-838d3a0c02c8"
13
      },
14
      "outputs": [
15
        {
16
          "output_type": "stream",
17
          "name": "stdout",
18
          "text": [
19
            "Time in seconds since beginning of run: 1700611267.2426076\n",
20
            "Wed Nov 22 00:01:07 2023\n"
21
          ]
22
        }
23
      ],
24
      "source": [
25
        "# This cell is added by sphinx-gallery\n",
26
        "# It can be customized to whatever you like\n",
27
        "%matplotlib inline\n",
28
        "# !pip install pennylane pennylane-lightning-gpu custatevec-cu11 --upgrade\n",
29
        "# !pip install pennylane-cirq\n",
30
        "# !pip install tensorflow==2.8.1\n",
31
        "# !pip install qsimcirq\n",
32
        "import time\n",
33
        "seconds = time.time()\n",
34
        "print(\"Time in seconds since beginning of run:\", seconds)\n",
35
        "local_time = time.ctime(seconds)\n",
36
        "print(local_time)"
37
      ]
38
    },
39
    {
40
      "cell_type": "markdown",
41
      "metadata": {
42
        "id": "1xodSdeDB4_G"
43
      },
44
      "source": [
45
        "Quantum generative adversarial networks with Cirq + TensorFlow {#quantum_GAN}\n",
46
        "==============================================================\n",
47
        "\n",
48
        "::: {.meta}\n",
49
        ":property=\\\"og:description\\\": This demo constructs and trains a Quantum\n",
50
        "Generative Adversarial Network (QGAN) using PennyLane, Cirq, and\n",
51
        "TensorFlow. :property=\\\"og:image\\\":\n",
52
        "<https://pennylane.ai/qml/_images/qgan3.png>\n",
53
        ":::\n",
54
        "\n",
55
        "*Author: Nathan Killoran --- Posted: 11 October 2019. Last updated: 30\n",
56
        "January 2023.*\n",
57
        "\n",
58
        "This demo constructs a Quantum Generative Adversarial Network (QGAN)\n",
59
        "([Lloyd and Weedbrook\n",
60
        "(2018)](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.121.040502),\n",
61
        "[Dallaire-Demers and Killoran\n",
62
        "(2018)](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.98.012324))\n",
63
        "using two subcircuits, a *generator* and a *discriminator*. The\n",
64
        "generator attempts to generate synthetic quantum data to match a pattern\n",
65
        "of \\\"real\\\" data, while the discriminator tries to discern real data\n",
66
        "from fake data (see image below). The gradient of the discriminator's\n",
67
        "output provides a training signal for the generator to improve its fake\n",
68
        "generated data.\n",
69
        "\n",
70
        "|\n",
71
        "\n",
72
        "![](../demonstrations/QGAN/qgan.png){.align-center width=\"75.0%\"}\n",
73
        "\n",
74
        "|\n"
75
      ]
76
    },
77
    {
78
      "cell_type": "markdown",
79
      "metadata": {
80
        "id": "Q8AMvcT-B4_H"
81
      },
82
      "source": [
83
        "Using Cirq + TensorFlow\n",
84
        "=======================\n",
85
        "\n",
86
        "PennyLane allows us to mix and match quantum devices and classical\n",
87
        "machine learning software. For this demo, we will link together\n",
88
        "Google\\'s [Cirq](https://cirq.readthedocs.io/en/stable/) and\n",
89
        "[TensorFlow](https://www.tensorflow.org/) libraries.\n",
90
        "\n",
91
        "We begin by importing PennyLane, NumPy, and TensorFlow.\n"
92
      ]
93
    },
94
    {
95
      "cell_type": "code",
96
      "execution_count": 20,
97
      "metadata": {
98
        "id": "jZnEogOMB4_H"
99
      },
100
      "outputs": [],
101
      "source": [
102
        "import numpy as np\n",
103
        "import pennylane as qml\n",
104
        "import tensorflow as tf"
105
      ]
106
    },
107
    {
108
      "cell_type": "markdown",
109
      "metadata": {
110
        "id": "VE_Ig3zoB4_H"
111
      },
112
      "source": [
113
        "We also declare a 3-qubit simulator device running in Cirq.\n"
114
      ]
115
    },
116
    {
117
      "cell_type": "code",
118
      "execution_count": 21,
119
      "metadata": {
120
        "id": "qgDmLFu5B4_I"
121
      },
122
      "outputs": [],
123
      "source": [
124
        "dev = qml.device('lightning.gpu', wires=3)"
125
      ]
126
    },
127
    {
128
      "cell_type": "markdown",
129
      "metadata": {
130
        "id": "_6MqJwPsB4_I"
131
      },
132
      "source": [
133
        "Generator and Discriminator\n",
134
        "===========================\n",
135
        "\n",
136
        "In classical GANs, the starting point is to draw samples either from\n",
137
        "some \\\"real data\\\" distribution, or from the generator, and feed them to\n",
138
        "the discriminator. In this QGAN example, we will use a quantum circuit\n",
139
        "to generate the real data.\n",
140
        "\n",
141
        "For this simple example, our real data will be a qubit that has been\n",
142
        "rotated (from the starting state $\\left|0\\right\\rangle$) to some\n",
143
        "arbitrary, but fixed, state.\n"
144
      ]
145
    },
146
    {
147
      "cell_type": "code",
148
      "execution_count": 22,
149
      "metadata": {
150
        "id": "gjBLrnOnB4_I"
151
      },
152
      "outputs": [],
153
      "source": [
154
        "def real(angles, **kwargs):\n",
155
        "    qml.Hadamard(wires=0)\n",
156
        "    qml.Rot(*angles, wires=0)"
157
      ]
158
    },
159
    {
160
      "cell_type": "markdown",
161
      "metadata": {
162
        "id": "bPLhFqDEB4_I"
163
      },
164
      "source": [
165
        "For the generator and discriminator, we will choose the same basic\n",
166
        "circuit structure, but acting on different wires.\n",
167
        "\n",
168
        "Both the real data circuit and the generator will output on wire 0,\n",
169
        "which will be connected as an input to the discriminator. Wire 1 is\n",
170
        "provided as a workspace for the generator, while the discriminator's\n",
171
        "output will be on wire 2.\n"
172
      ]
173
    },
174
    {
175
      "cell_type": "code",
176
      "execution_count": 23,
177
      "metadata": {
178
        "id": "UoOK7QA9B4_I"
179
      },
180
      "outputs": [],
181
      "source": [
182
        "def generator(w, **kwargs):\n",
183
        "    qml.Hadamard(wires=0)\n",
184
        "    qml.RX(w[0], wires=0)\n",
185
        "    qml.RX(w[1], wires=1)\n",
186
        "    qml.RY(w[2], wires=0)\n",
187
        "    qml.RY(w[3], wires=1)\n",
188
        "    qml.RZ(w[4], wires=0)\n",
189
        "    qml.RZ(w[5], wires=1)\n",
190
        "    qml.CNOT(wires=[0, 1])\n",
191
        "    qml.RX(w[6], wires=0)\n",
192
        "    qml.RY(w[7], wires=0)\n",
193
        "    qml.RZ(w[8], wires=0)\n",
194
        "\n",
195
        "\n",
196
        "def discriminator(w):\n",
197
        "    qml.Hadamard(wires=0)\n",
198
        "    qml.RX(w[0], wires=0)\n",
199
        "    qml.RX(w[1], wires=2)\n",
200
        "    qml.RY(w[2], wires=0)\n",
201
        "    qml.RY(w[3], wires=2)\n",
202
        "    qml.RZ(w[4], wires=0)\n",
203
        "    qml.RZ(w[5], wires=2)\n",
204
        "    qml.CNOT(wires=[0, 2])\n",
205
        "    qml.RX(w[6], wires=2)\n",
206
        "    qml.RY(w[7], wires=2)\n",
207
        "    qml.RZ(w[8], wires=2)"
208
      ]
209
    },
210
    {
211
      "cell_type": "markdown",
212
      "metadata": {
213
        "id": "BA6j3YglB4_I"
214
      },
215
      "source": [
216
        "We create two QNodes. One where the real data source is wired up to the\n",
217
        "discriminator, and one where the generator is connected to the\n",
218
        "discriminator. In order to pass TensorFlow Variables into the quantum\n",
219
        "circuits, we specify the `\"tf\"` interface.\n"
220
      ]
221
    },
222
    {
223
      "cell_type": "code",
224
      "execution_count": 24,
225
      "metadata": {
226
        "id": "OLk5Bxs9B4_I"
227
      },
228
      "outputs": [],
229
      "source": [
230
        "@qml.qnode(dev, interface=\"tf\")\n",
231
        "def real_disc_circuit(phi, theta, omega, disc_weights):\n",
232
        "    real([phi, theta, omega])\n",
233
        "    discriminator(disc_weights)\n",
234
        "    return qml.expval(qml.PauliZ(2))\n",
235
        "\n",
236
        "\n",
237
        "@qml.qnode(dev, interface=\"tf\")\n",
238
        "def gen_disc_circuit(gen_weights, disc_weights):\n",
239
        "    generator(gen_weights)\n",
240
        "    discriminator(disc_weights)\n",
241
        "    return qml.expval(qml.PauliZ(2))"
242
      ]
243
    },
244
    {
245
      "cell_type": "markdown",
246
      "metadata": {
247
        "id": "VbnRyFpjB4_I"
248
      },
249
      "source": [
250
        "QGAN cost functions\n",
251
        "===================\n",
252
        "\n",
253
        "There are two cost functions of interest, corresponding to the two\n",
254
        "stages of QGAN training. These cost functions are built from two pieces:\n",
255
        "the first piece is the probability that the discriminator correctly\n",
256
        "classifies real data as real. The second piece is the probability that\n",
257
        "the discriminator classifies fake data (i.e., a state prepared by the\n",
258
        "generator) as real.\n",
259
        "\n",
260
        "The discriminator is trained to maximize the probability of correctly\n",
261
        "classifying real data, while minimizing the probability of mistakenly\n",
262
        "classifying fake data.\n",
263
        "\n",
264
        "$$Cost_D = \\mathrm{Pr}(real|\\mathrm{fake}) - \\mathrm{Pr}(real|\\mathrm{real})$$\n",
265
        "\n",
266
        "The generator is trained to maximize the probability that the\n",
267
        "discriminator accepts fake data as real.\n",
268
        "\n",
269
        "$$Cost_G = - \\mathrm{Pr}(real|\\mathrm{fake})$$\n"
270
      ]
271
    },
272
    {
273
      "cell_type": "code",
274
      "execution_count": 25,
275
      "metadata": {
276
        "id": "qZOU-2G1B4_I"
277
      },
278
      "outputs": [],
279
      "source": [
280
        "def prob_real_true(disc_weights):\n",
281
        "    true_disc_output = real_disc_circuit(phi, theta, omega, disc_weights)\n",
282
        "    # convert to probability\n",
283
        "    prob_real_true = (true_disc_output + 1) / 2\n",
284
        "    return prob_real_true\n",
285
        "\n",
286
        "\n",
287
        "def prob_fake_true(gen_weights, disc_weights):\n",
288
        "    fake_disc_output = gen_disc_circuit(gen_weights, disc_weights)\n",
289
        "    # convert to probability\n",
290
        "    prob_fake_true = (fake_disc_output + 1) / 2\n",
291
        "    return prob_fake_true\n",
292
        "\n",
293
        "\n",
294
        "def disc_cost(disc_weights):\n",
295
        "    cost = prob_fake_true(gen_weights, disc_weights) - prob_real_true(disc_weights)\n",
296
        "    return cost\n",
297
        "\n",
298
        "\n",
299
        "def gen_cost(gen_weights):\n",
300
        "    return -prob_fake_true(gen_weights, disc_weights)"
301
      ]
302
    },
303
    {
304
      "cell_type": "markdown",
305
      "metadata": {
306
        "id": "uf3SAVEMB4_J"
307
      },
308
      "source": [
309
        "Training the QGAN\n",
310
        "=================\n",
311
        "\n",
312
        "We initialize the fixed angles of the \\\"real data\\\" circuit, as well as\n",
313
        "the initial parameters for both generator and discriminator. These are\n",
314
        "chosen so that the generator initially prepares a state on wire 0 that\n",
315
        "is very close to the $\\left| 1 \\right\\rangle$ state.\n"
316
      ]
317
    },
318
    {
319
      "cell_type": "code",
320
      "execution_count": 26,
321
      "metadata": {
322
        "id": "IWGiPa4dB4_J"
323
      },
324
      "outputs": [],
325
      "source": [
326
        "phi = np.pi / 6\n",
327
        "theta = np.pi / 2\n",
328
        "omega = np.pi / 7\n",
329
        "np.random.seed(0)\n",
330
        "eps = 1e-2\n",
331
        "init_gen_weights = np.array([np.pi] + [0] * 8) + \\\n",
332
        "                   np.random.normal(scale=eps, size=(9,))\n",
333
        "init_disc_weights = np.random.normal(size=(9,))\n",
334
        "\n",
335
        "gen_weights = tf.Variable(init_gen_weights)\n",
336
        "disc_weights = tf.Variable(init_disc_weights)"
337
      ]
338
    },
339
    {
340
      "cell_type": "markdown",
341
      "metadata": {
342
        "id": "NVz7yaXGB4_J"
343
      },
344
      "source": [
345
        "We begin by creating the optimizer:\n"
346
      ]
347
    },
348
    {
349
      "cell_type": "code",
350
      "execution_count": 27,
351
      "metadata": {
352
        "id": "8XQoiYjiB4_J"
353
      },
354
      "outputs": [],
355
      "source": [
356
        "opt = tf.keras.optimizers.SGD(0.4)"
357
      ]
358
    },
359
    {
360
      "cell_type": "markdown",
361
      "metadata": {
362
        "id": "BrUfmFDWB4_J"
363
      },
364
      "source": [
365
        "In the first stage of training, we optimize the discriminator while\n",
366
        "keeping the generator parameters fixed.\n"
367
      ]
368
    },
369
    {
370
      "cell_type": "code",
371
      "execution_count": 28,
372
      "metadata": {
373
        "colab": {
374
          "base_uri": "https://localhost:8080/",
375
          "height": 0
376
        },
377
        "id": "6_diImNYB4_J",
378
        "outputId": "dc877ef3-2fb8-4d16-b2b9-680ae547815e"
379
      },
380
      "outputs": [
381
        {
382
          "output_type": "stream",
383
          "name": "stdout",
384
          "text": [
385
            "Step 0: cost = -0.05727697679577842\n",
386
            "Step 5: cost = -0.2634812508449048\n",
387
            "Step 10: cost = -0.4273918853317506\n",
388
            "Step 15: cost = -0.47261597484185947\n",
389
            "Step 20: cost = -0.4840689974053044\n",
390
            "Step 25: cost = -0.48946413443470116\n",
391
            "Step 30: cost = -0.4928187788475247\n",
392
            "Step 35: cost = -0.4949493282586438\n",
393
            "Step 40: cost = -0.49627038768697207\n",
394
            "Step 45: cost = -0.4970720262026469\n"
395
          ]
396
        }
397
      ],
398
      "source": [
399
        "cost = lambda: disc_cost(disc_weights)\n",
400
        "\n",
401
        "for step in range(50):\n",
402
        "    opt.minimize(cost, disc_weights)\n",
403
        "    if step % 5 == 0:\n",
404
        "        cost_val = cost().numpy()\n",
405
        "        print(\"Step {}: cost = {}\".format(step, cost_val))"
406
      ]
407
    },
408
    {
409
      "cell_type": "markdown",
410
      "metadata": {
411
        "id": "dbm3F7-IB4_J"
412
      },
413
      "source": [
414
        "At the discriminator's optimum, the probability for the discriminator to\n",
415
        "correctly classify the real data should be close to one.\n"
416
      ]
417
    },
418
    {
419
      "cell_type": "code",
420
      "execution_count": 29,
421
      "metadata": {
422
        "colab": {
423
          "base_uri": "https://localhost:8080/",
424
          "height": 0
425
        },
426
        "id": "URMw-4uBB4_J",
427
        "outputId": "7ae5da96-8568-4153-d0cd-6d59221a84a2"
428
      },
429
      "outputs": [
430
        {
431
          "output_type": "stream",
432
          "name": "stdout",
433
          "text": [
434
            "Prob(real classified as real):  0.9985872751209892\n"
435
          ]
436
        }
437
      ],
438
      "source": [
439
        "print(\"Prob(real classified as real): \", prob_real_true(disc_weights).numpy())"
440
      ]
441
    },
442
    {
443
      "cell_type": "markdown",
444
      "metadata": {
445
        "id": "7GO0YwksB4_J"
446
      },
447
      "source": [
448
        "For comparison, we check how the discriminator classifies the\n",
449
        "generator's (still unoptimized) fake data:\n"
450
      ]
451
    },
452
    {
453
      "cell_type": "code",
454
      "execution_count": 30,
455
      "metadata": {
456
        "colab": {
457
          "base_uri": "https://localhost:8080/",
458
          "height": 0
459
        },
460
        "id": "b4pWtXVHB4_J",
461
        "outputId": "7a121edf-6a5e-4641-8bac-8439950ee10b"
462
      },
463
      "outputs": [
464
        {
465
          "output_type": "stream",
466
          "name": "stdout",
467
          "text": [
468
            "Prob(fake classified as real):  0.5011127803383656\n"
469
          ]
470
        }
471
      ],
472
      "source": [
473
        "print(\"Prob(fake classified as real): \", prob_fake_true(gen_weights, disc_weights).numpy())"
474
      ]
475
    },
476
    {
477
      "cell_type": "markdown",
478
      "metadata": {
479
        "id": "JGr9atT9B4_J"
480
      },
481
      "source": [
482
        "In the adversarial game we now have to train the generator to better\n",
483
        "fool the discriminator. For this demo, we only perform one stage of the\n",
484
        "game. For more complex models, we would continue training the models in\n",
485
        "an alternating fashion until we reach the optimum point of the\n",
486
        "two-player adversarial game.\n"
487
      ]
488
    },
489
    {
490
      "cell_type": "code",
491
      "execution_count": 31,
492
      "metadata": {
493
        "colab": {
494
          "base_uri": "https://localhost:8080/",
495
          "height": 0
496
        },
497
        "id": "NVrGCFK5B4_J",
498
        "outputId": "39db9092-333e-46f1-bb2d-2d005b37ce50"
499
      },
500
      "outputs": [
501
        {
502
          "output_type": "stream",
503
          "name": "stdout",
504
          "text": [
505
            "Step 0: cost = -0.5833387118384104\n",
506
            "Step 5: cost = -0.8915733598437307\n",
507
            "Step 10: cost = -0.9784243532819915\n",
508
            "Step 15: cost = -0.9946483809432042\n",
509
            "Step 20: cost = -0.9984996426172494\n",
510
            "Step 25: cost = -0.9995638464006635\n",
511
            "Step 30: cost = -0.9998717844534688\n",
512
            "Step 35: cost = -0.9999621462112331\n",
513
            "Step 40: cost = -0.999988801241847\n",
514
            "Step 45: cost = -0.9999966825023898\n"
515
          ]
516
        }
517
      ],
518
      "source": [
519
        "cost = lambda: gen_cost(gen_weights)\n",
520
        "\n",
521
        "for step in range(50):\n",
522
        "    opt.minimize(cost, gen_weights)\n",
523
        "    if step % 5 == 0:\n",
524
        "        cost_val = cost().numpy()\n",
525
        "        print(\"Step {}: cost = {}\".format(step, cost_val))"
526
      ]
527
    },
528
    {
529
      "cell_type": "markdown",
530
      "metadata": {
531
        "id": "0K59ud11B4_J"
532
      },
533
      "source": [
534
        "At the optimum of the generator, the probability for the discriminator\n",
535
        "to be fooled should be close to 1.\n"
536
      ]
537
    },
538
    {
539
      "cell_type": "code",
540
      "execution_count": 32,
541
      "metadata": {
542
        "colab": {
543
          "base_uri": "https://localhost:8080/",
544
          "height": 0
545
        },
546
        "id": "4_3Co4HYB4_J",
547
        "outputId": "194fbb89-5379-43a7-a4b8-f765d2d2881e"
548
      },
549
      "outputs": [
550
        {
551
          "output_type": "stream",
552
          "name": "stdout",
553
          "text": [
554
            "Prob(fake classified as real):  0.9999987450417567\n"
555
          ]
556
        }
557
      ],
558
      "source": [
559
        "print(\"Prob(fake classified as real): \", prob_fake_true(gen_weights, disc_weights).numpy())"
560
      ]
561
    },
562
    {
563
      "cell_type": "markdown",
564
      "metadata": {
565
        "id": "uJR4z50uB4_J"
566
      },
567
      "source": [
568
        "At the joint optimum the discriminator cost will be close to zero,\n",
569
        "indicating that the discriminator assigns equal probability to both real\n",
570
        "and generated data.\n"
571
      ]
572
    },
573
    {
574
      "cell_type": "code",
575
      "execution_count": 33,
576
      "metadata": {
577
        "colab": {
578
          "base_uri": "https://localhost:8080/",
579
          "height": 0
580
        },
581
        "id": "byWE_IY1B4_J",
582
        "outputId": "27ddbb17-239c-47f4-d22c-7621b0f5b62e"
583
      },
584
      "outputs": [
585
        {
586
          "output_type": "stream",
587
          "name": "stdout",
588
          "text": [
589
            "Discriminator cost:  0.0014114699207674608\n"
590
          ]
591
        }
592
      ],
593
      "source": [
594
        "print(\"Discriminator cost: \", disc_cost(disc_weights).numpy())"
595
      ]
596
    },
597
    {
598
      "cell_type": "markdown",
599
      "metadata": {
600
        "id": "Ubhr8ZSsB4_J"
601
      },
602
      "source": [
603
        "The generator has successfully learned how to simulate the real data\n",
604
        "enough to fool the discriminator.\n",
605
        "\n",
606
        "Let\\'s conclude by comparing the states of the real data circuit and the\n",
607
        "generator. We expect the generator to have learned to be in a state that\n",
608
        "is very close to the one prepared in the real data circuit. An easy way\n",
609
        "to access the state of the first qubit is through its [Bloch\n",
610
        "sphere](https://en.wikipedia.org/wiki/Bloch_sphere) representation:\n"
611
      ]
612
    },
613
    {
614
      "cell_type": "code",
615
      "execution_count": 34,
616
      "metadata": {
617
        "colab": {
618
          "base_uri": "https://localhost:8080/",
619
          "height": 0
620
        },
621
        "id": "QZ2TkvG4B4_J",
622
        "outputId": "e7baea95-a783-472c-d4b6-a71e4813d232"
623
      },
624
      "outputs": [
625
        {
626
          "output_type": "stream",
627
          "name": "stdout",
628
          "text": [
629
            "Real Bloch vector: [<tf.Tensor: shape=(), dtype=float64, numpy=-0.21694186955877895>, <tf.Tensor: shape=(), dtype=float64, numpy=0.4504844339512096>, <tf.Tensor: shape=(), dtype=float64, numpy=-0.8660254037844386>]\n",
630
            "Generator Bloch vector: [<tf.Tensor: shape=(), dtype=float64, numpy=-0.2840466575634104>, <tf.Tensor: shape=(), dtype=float64, numpy=0.4189322684453221>, <tf.Tensor: shape=(), dtype=float64, numpy=-0.8624441484467279>]\n"
631
          ]
632
        }
633
      ],
634
      "source": [
635
        "obs = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)]\n",
636
        "\n",
637
        "@qml.qnode(dev, interface=\"tf\")\n",
638
        "def bloch_vector_real(angles):\n",
639
        "    real(angles)\n",
640
        "    return [qml.expval(o) for o in obs]\n",
641
        "\n",
642
        "@qml.qnode(dev, interface=\"tf\")\n",
643
        "def bloch_vector_generator(angles):\n",
644
        "    generator(angles)\n",
645
        "    return [qml.expval(o) for o in obs]\n",
646
        "\n",
647
        "print(f\"Real Bloch vector: {bloch_vector_real([phi, theta, omega])}\")\n",
648
        "print(f\"Generator Bloch vector: {bloch_vector_generator(gen_weights)}\")"
649
      ]
650
    },
651
    {
652
      "cell_type": "markdown",
653
      "metadata": {
654
        "id": "zAlOXLekB4_K"
655
      },
656
      "source": [
657
        "About the author\n",
658
        "================\n"
659
      ]
660
    },
661
    {
662
      "cell_type": "code",
663
      "source": [
664
        "seconds = time.time()\n",
665
        "print(\"Time in seconds since end of run:\", seconds)\n",
666
        "local_time = time.ctime(seconds)\n",
667
        "print(local_time)"
668
      ],
669
      "metadata": {
670
        "colab": {
671
          "base_uri": "https://localhost:8080/",
672
          "height": 0
673
        },
674
        "id": "lypK7PwdDujD",
675
        "outputId": "13d3f482-54c1-4dc9-c98f-ab9a44f2c9ab"
676
      },
677
      "execution_count": 35,
678
      "outputs": [
679
        {
680
          "output_type": "stream",
681
          "name": "stdout",
682
          "text": [
683
            "Time in seconds since end of run: 1700611277.3444774\n",
684
            "Wed Nov 22 00:01:17 2023\n"
685
          ]
686
        }
687
      ]
688
    }
689
  ],
690
  "metadata": {
691
    "kernelspec": {
692
      "display_name": "Python 3",
693
      "name": "python3"
694
    },
695
    "language_info": {
696
      "codemirror_mode": {
697
        "name": "ipython",
698
        "version": 3
699
      },
700
      "file_extension": ".py",
701
      "mimetype": "text/x-python",
702
      "name": "python",
703
      "nbconvert_exporter": "python",
704
      "pygments_lexer": "ipython3",
705
      "version": "3.9.17"
706
    },
707
    "colab": {
708
      "provenance": [],
709
      "machine_shape": "hm",
710
      "gpuType": "A100"
711
    },
712
    "accelerator": "GPU"
713
  },
714
  "nbformat": 4,
715
  "nbformat_minor": 0
716
}