{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "\n", "# Time Evolution of Maxwell's Equations & Constraints in Flat Spacetime and Cartesian Coordinates\n", "\n", "## Authors: Terrence Pierre Jacques, Zachariah Etienne and Ian Ruchlin\n", "\n", "### This module constructs the evolution equations for Maxwell's equations as symbolic (SymPy) expressions, for an electromagnetic field in vacuum, as defined in [Tutorial-VacuumMaxwell_formulation_Cartesian](Tutorial-VacuumMaxwell_formulation_Cartesian.ipynb).\n", "\n", "**Notebook Status:** Validated \n", "\n", "**Validation Notes:** All expressions generated in this module have been validated.\n", "\n", "### NRPy+ Source Code for this module: [Maxwell/VacuumMaxwell_Flat_Evol_Cartesian.py](../edit/Maxwell/VacuumMaxwell_Flat_Evol_Cartesian.py)\n", "\n", "[comment]: <> (Introduction: TODO)\n", "\n", "This tutorial takes the expressions defined in [Tutorial-VacuumMaxwell_formulation_Cartesian](Tutorial-VacuumMaxwell_formulation_Cartesian.ipynb), and constructs them using NRPy+ in SymPy expressions." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Table of Contents\n", "$$\\label{toc}$$\n", "\n", "1. [Step 1](#initializenrpy): Initialize needed Python/NRPy+ modules\n", "1. [Step 2](#system1): System 1\n", " 1. [Step 2.a](#system1_rhs): Construct evolution equations\n", " 1. [Step 2.b](#system1_val): Code Validation\n", "1. [Step 3](#system2): System 2\n", " 1. [Step 3.a](#system2_rhs): Construct evolution equations\n", " 1. [Step 3.b](#system2_val): Code Validation\n", "1. [Step 4](#latex_pdf_output): Output this notebook to $\\LaTeX$-formatted PDF file" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Step 1: Initialize needed Python/NRPy+ modules \\[Back to [top](#toc)\\]\n", "\n", "$$\\label{initializenrpy}$$" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:46.283741Z", "iopub.status.busy": "2021-03-07T17:18:46.282357Z", "iopub.status.idle": "2021-03-07T17:18:46.626517Z", "shell.execute_reply": "2021-03-07T17:18:46.624947Z" } }, "outputs": [], "source": [ "# Import needed Python modules\n", "import NRPy_param_funcs as par # NRPy+: Parameter interface\n", "import indexedexp as ixp # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support\n", "import grid as gri # NRPy+: Functions having to do with numerical grids\n", "\n", "#Step 0: Set the spatial dimension parameter to 3.\n", "par.set_parval_from_str(\"grid::DIM\", 3)\n", "DIM = par.parval_from_str(\"grid::DIM\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Step 2: System I \\[Back to [top](#toc)\\]\n", "$$\\label{system1}$$\n", "\n", "\n", "\n", "## Step 2.a: Construct evolution equations \\[Back to [top](#toc)\\]\n", "$$\\label{system1_rhs}$$\n", "\n", "Following our derivations in [Tutorial-VacuumMaxwell_formulation_Cartesian](Tutorial-VacuumMaxwell_formulation_Cartesian.ipynb), we construct the system of equations for System I, in flat space and cartesian coordinates:\n", "\n", "\\begin{align}\n", "\\partial_t A^i &= -E^i - \\partial^i \\varphi, \\\\\n", "\\partial_t E^i &= \\partial^i \\partial_j A^j - \\partial_j \\partial^j A^i, \\\\\n", "\\partial_t \\varphi &= -\\partial_i A^i,\n", "\\end{align}" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:46.640662Z", "iopub.status.busy": "2021-03-07T17:18:46.639972Z", "iopub.status.idle": "2021-03-07T17:18:46.643096Z", "shell.execute_reply": "2021-03-07T17:18:46.642537Z" } }, "outputs": [], "source": [ "# Register gridfunctions that are needed as input.\n", "\n", "# Declare the rank-1 indexed expressions E_{i}, A_{i},\n", "# and \\partial_{i} \\psi, that are to be evolved in time.\n", "# Derivative variables like these must have an underscore\n", "# in them, so the finite difference module can parse\n", "# the variable name properly.\n", "\n", "# E_i\n", "EU = ixp.register_gridfunctions_for_single_rank1(\"EVOL\", \"EU\")\n", "\n", "# A_i, _AD is unused\n", "_AU = ixp.register_gridfunctions_for_single_rank1(\"EVOL\", \"AU\")\n", "\n", "# \\psi is a scalar function that is time evolved\n", "# _psi is unused\n", "_psi = gri.register_gridfunctions(\"EVOL\", [\"psi\"])\n", "\n", "# \\partial_i \\psi\n", "psi_dD = ixp.declarerank1(\"psi_dD\")\n", "\n", "# \\partial_k ( A_i ) --> rank two tensor\n", "AU_dD = ixp.declarerank2(\"AU_dD\", \"nosym\")\n", "\n", "# \\partial_k partial_m ( A_i ) --> rank three tensor\n", "AU_dDD = ixp.declarerank3(\"AU_dDD\", \"sym12\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\\begin{align}\n", "\\partial_t A^i &= -E^i - \\partial^i \\varphi, \\\\\n", "\\partial_t E^i &= \\partial^i \\partial_j A^j - \\partial_j \\partial^j A^i, \\\\\n", "\\partial_t \\varphi &= -\\partial_i A^i,\n", "\\end{align}" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:46.652659Z", "iopub.status.busy": "2021-03-07T17:18:46.652026Z", "iopub.status.idle": "2021-03-07T17:18:46.695917Z", "shell.execute_reply": "2021-03-07T17:18:46.696401Z" } }, "outputs": [], "source": [ "# \\partial_t \\psi = -\\partial_i A_i\n", "psi_rhs = 0\n", "\n", "# \\partial_t A_i = E_i - \\partial_i \\psi\n", "ArhsU = ixp.zerorank1()\n", "\n", "# \\partial_t E_i = -\\partial_j^2 A_i + \\partial_j \\partial_i A_j\n", "ErhsU = ixp.zerorank1()\n", "\n", "# RHSs - equations 10 and 11 from https://arxiv.org/abs/gr-qc/0201051\n", "for i in range(DIM):\n", " ArhsU[i] = -EU[i] - psi_dD[i]\n", " psi_rhs += -AU_dD[i][i]\n", " for j in range(DIM):\n", " ErhsU[i] += -AU_dDD[i][j][j] + AU_dDD[j][j][i]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Constraint:\n", "\n", "\\begin{align}\n", "\\mathcal{C} \\equiv \\partial_i E^i\n", "\\end{align}" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:46.701977Z", "iopub.status.busy": "2021-03-07T17:18:46.701330Z", "iopub.status.idle": "2021-03-07T17:18:46.703484Z", "shell.execute_reply": "2021-03-07T17:18:46.703946Z" } }, "outputs": [], "source": [ "# \\partial_k ( E^i ) --> rank two tensor\n", "EU_dD = ixp.declarerank2(\"EU_dD\", \"nosym\")\n", "\n", "# C = \\partial_i E^i\n", "C = EU_dD[0][0] + EU_dD[1][1] + EU_dD[2][2]" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:46.715265Z", "iopub.status.busy": "2021-03-07T17:18:46.714431Z", "iopub.status.idle": "2021-03-07T17:18:46.718545Z", "shell.execute_reply": "2021-03-07T17:18:46.719052Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "C = EU_dD00 + EU_dD11 + EU_dD22\n", "ArhsU[0] = -EU0 - psi_dD0\n", "ArhsU[1] = -EU1 - psi_dD1\n", "ArhsU[2] = -EU2 - psi_dD2\n", "ErhsU[0] = -AU_dDD011 - AU_dDD022 + AU_dDD101 + AU_dDD202\n", "ErhsU[1] = AU_dDD001 - AU_dDD100 - AU_dDD122 + AU_dDD212\n", "ErhsU[2] = AU_dDD002 + AU_dDD112 - AU_dDD200 - AU_dDD211\n", "psi_rhs = -AU_dD00 - AU_dD11 - AU_dD22\n" ] } ], "source": [ "print('C = ', C)\n", "print('ArhsU[0] = ', ArhsU[0])\n", "print('ArhsU[1] = ', ArhsU[1])\n", "print('ArhsU[2] = ', ArhsU[2])\n", "print('ErhsU[0] = ', ErhsU[0])\n", "print('ErhsU[1] = ', ErhsU[1])\n", "print('ErhsU[2] = ', ErhsU[2])\n", "print('psi_rhs = ', psi_rhs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "## Step 2.b: NRPy+ Module Code Validation \\[Back to [top](#toc)\\]\n", "$$\\label{system1_val}$$\n", "\n", "Here, as a code validation check, we verify agreement in the SymPy expressions for the RHSs of Maxwell's equations (in System I) between\n", "1. this tutorial and \n", "2. the NRPy+ [Maxwell/VacuumMaxwell_Flat_Evol_Cartesian.py](../edit/Maxwell/VacuumMaxwell_Flat_Evol_Cartesian.py) module.\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:46.726534Z", "iopub.status.busy": "2021-03-07T17:18:46.725819Z", "iopub.status.idle": "2021-03-07T17:18:47.162628Z", "shell.execute_reply": "2021-03-07T17:18:47.163113Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Currently using System_I RHSs \n", "\n", "Consistency check between Tutorial-VacuumMaxwell_Cartesian_RHSs tutorial and NRPy+ module: ALL SHOULD BE ZERO.\n", "C - mwevol.C = 0\n", "psi_rhs - mwevol.psi_rhs = 0\n", "ArhsU[0] - mwevol.ArhsU[0] = 0\n", "ErhsU[0] - mwevol.ErhsU[0] = 0\n", "ArhsU[1] - mwevol.ArhsU[1] = 0\n", "ErhsU[1] - mwevol.ErhsU[1] = 0\n", "ArhsU[2] - mwevol.ArhsU[2] = 0\n", "ErhsU[2] - mwevol.ErhsU[2] = 0\n" ] } ], "source": [ "# Reset the list of gridfunctions, as registering a gridfunction\n", "# twice will spawn an error.\n", "gri.glb_gridfcs_list = []\n", "\n", "# Call the VacuumMaxwellRHSs() function from within the\n", "# Maxwell/VacuumMaxwell_Flat_Evol_Cartesian.py module,\n", "# which should do exactly the same as the steps above.\n", "\n", "# Set which system to use, which are defined in Maxwell/InitialData.py\n", "par.initialize_param(par.glb_param(\"char\", \"Maxwell.InitialData\",\"System_to_use\",\"System_I\"))\n", "\n", "import Maxwell.VacuumMaxwell_Flat_Evol_Cartesian as mwevol\n", "mwevol.VacuumMaxwellRHSs()\n", "\n", "print(\"Consistency check between Tutorial-VacuumMaxwell_Cartesian_RHSs tutorial and NRPy+ module: ALL SHOULD BE ZERO.\")\n", "\n", "print(\"C - mwevol.C = \" + str(C - mwevol.C))\n", "print(\"psi_rhs - mwevol.psi_rhs = \" + str(psi_rhs - mwevol.psi_rhs))\n", "for i in range(DIM):\n", "\n", " print(\"ArhsU[\"+str(i)+\"] - mwevol.ArhsU[\"+str(i)+\"] = \" + str(ArhsU[i] - mwevol.ArhsU[i]))\n", " print(\"ErhsU[\"+str(i)+\"] - mwevol.ErhsU[\"+str(i)+\"] = \" + str(ErhsU[i] - mwevol.ErhsU[i]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Step 3: System II \\[Back to [top](#toc)\\]\n", "$$\\label{system2}$$\n", "\n", "\n", "\n", "## Step 3.a: Construct evolution equations \\[Back to [top](#toc)\\]\n", "$$\\label{system2_rhs}$$\n", "\n", "Next, again following [Tutorial-VacuumMaxwell_formulation_Cartesian](Tutorial-VacuumMaxwell_formulation_Cartesian.ipynb), we construct the system of equations for System II, in flat space and cartesian coordinates:\n", "\n", "\\begin{align}\n", "\\partial_t A^i &= -E^i - \\partial^i \\varphi, \\\\\n", "\\partial_t E^i &= \\partial^i \\Gamma - \\partial_j \\partial^j A^i, \\\\\n", "\\partial_t \\Gamma &= - \\partial_i \\partial^i \\varphi, \\\\\n", "\\partial_t \\varphi &= -\\Gamma,\n", "\\end{align}" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:47.173619Z", "iopub.status.busy": "2021-03-07T17:18:47.172518Z", "iopub.status.idle": "2021-03-07T17:18:47.176231Z", "shell.execute_reply": "2021-03-07T17:18:47.177083Z" } }, "outputs": [], "source": [ "# We inherit here all of the definitions from System I, above\n", "\n", "# Register the scalar auxiliary variable \\Gamma\n", "Gamma = gri.register_gridfunctions(\"EVOL\", [\"Gamma\"])\n", "\n", "# Declare the ordinary gradient \\partial_{i} \\Gamma\n", "Gamma_dD = ixp.declarerank1(\"Gamma_dD\")\n", "\n", "# partial_i \\partial_j \\psi\n", "psi_dDD = ixp.declarerank2(\"psi_dDD\", \"sym01\")\n", "\n", "psi_rhs = -Gamma" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\\begin{align}\n", "\\partial_t A^i &= -E^i - \\partial^i \\varphi, \\\\\n", "\\partial_t E^i &= \\partial^i \\Gamma - \\partial_j \\partial^j A^i, \\\\\n", "\\partial_t \\Gamma &= - \\partial_i \\partial^i \\varphi, \\\\\n", "\\partial_t \\varphi &= -\\Gamma,\n", "\\end{align}" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:47.190722Z", "iopub.status.busy": "2021-03-07T17:18:47.189928Z", "iopub.status.idle": "2021-03-07T17:18:47.192137Z", "shell.execute_reply": "2021-03-07T17:18:47.192609Z" } }, "outputs": [], "source": [ "# Equation 15 https://arxiv.org/abs/gr-qc/0201051\n", "# \\partial_t \\Gamma = -\\partial_i^2 \\psi\n", "Gamma_rhs = 0\n", "\n", "# \\partial_t A_i = -E_i - \\partial_i \\psi\n", "ArhsU = ixp.zerorank1()\n", "\n", "# \\partial_t E_i = -\\partial_j^2 A_i + \\partial_i \\Gamma\n", "ErhsU = ixp.zerorank1()\n", "\n", "# RHSs - equations 10 and 14 https://arxiv.org/abs/gr-qc/0201051\n", "for i in range(DIM):\n", " ArhsU[i] = -EU[i] - psi_dD[i]\n", " Gamma_rhs -= psi_dDD[i][i]\n", " ErhsU[i] += Gamma_dD[i]\n", " for j in range(DIM):\n", " ErhsU[i] -= AU_dDD[i][j][j]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Constraints:\n", "\n", "\\begin{align}\n", "\\mathcal{G} &\\equiv \\Gamma - \\partial_i A^i, \\\\\n", "\\mathcal{C} &\\equiv \\partial_i E^i.\n", "\\end{align}" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:47.200830Z", "iopub.status.busy": "2021-03-07T17:18:47.199752Z", "iopub.status.idle": "2021-03-07T17:18:47.204835Z", "shell.execute_reply": "2021-03-07T17:18:47.205386Z" } }, "outputs": [], "source": [ "# \\partial_k ( E^i ) --> rank two tensor\n", "EU_dD = ixp.declarerank2(\"EU_dD\", \"nosym\")\n", "\n", "# C = \\partial_i E^i\n", "C = EU_dD[0][0] + EU_dD[1][1] + EU_dD[2][2]\n", "\n", "# G = \\Gamma - \\partial_i A^i\n", "G = Gamma - AU_dD[0][0] - AU_dD[1][1] - AU_dD[2][2]" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:47.216491Z", "iopub.status.busy": "2021-03-07T17:18:47.215710Z", "iopub.status.idle": "2021-03-07T17:18:47.221227Z", "shell.execute_reply": "2021-03-07T17:18:47.220653Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "C = EU_dD00 + EU_dD11 + EU_dD22\n", "G = -AU_dD00 - AU_dD11 - AU_dD22 + Gamma\n", "ArhsU[0] = -EU0 - psi_dD0\n", "ArhsU[1] = -EU1 - psi_dD1\n", "ArhsU[2] = -EU2 - psi_dD2\n", "ErhsU[0] = -AU_dDD000 - AU_dDD011 - AU_dDD022 + Gamma_dD0\n", "ErhsU[1] = -AU_dDD100 - AU_dDD111 - AU_dDD122 + Gamma_dD1\n", "ErhsU[2] = -AU_dDD200 - AU_dDD211 - AU_dDD222 + Gamma_dD2\n", "psi_rhs = -Gamma\n", "Gamma_rhs = -psi_dDD00 - psi_dDD11 - psi_dDD22\n" ] } ], "source": [ "print('C = ', C)\n", "print('G = ', G)\n", "print('ArhsU[0] = ', ArhsU[0])\n", "print('ArhsU[1] = ', ArhsU[1])\n", "print('ArhsU[2] = ', ArhsU[2])\n", "print('ErhsU[0] = ', ErhsU[0])\n", "print('ErhsU[1] = ', ErhsU[1])\n", "print('ErhsU[2] = ', ErhsU[2])\n", "print('psi_rhs = ', psi_rhs)\n", "print('Gamma_rhs =', Gamma_rhs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "## Step 3.b: NRPy+ Module Code Validation \\[Back to [top](#toc)\\]\n", "$$\\label{system2_val}$$\n", "\n", "Here, as a code validation check, we verify agreement in the SymPy expressions for the RHSs of Maxwell's equations (in System II) between\n", "1. this tutorial and \n", "2. the NRPy+ [Maxwell/VacuumMaxwell_Flat_Evol_Cartesian.py](../edit/Maxwell/VacuumMaxwell_Flat_Evol_Cartesian.py) module." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:47.290416Z", "iopub.status.busy": "2021-03-07T17:18:47.254631Z", "iopub.status.idle": "2021-03-07T17:18:47.299468Z", "shell.execute_reply": "2021-03-07T17:18:47.299993Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Currently using System_II RHSs \n", "\n", "Consistency check between Tutorial-VacuumMaxwell_Cartesian_RHSs tutorial and NRPy+ module: ALL SHOULD BE ZERO.\n", "C - mwevol.C = 0\n", "G - mwevol.G = 0\n", "psi_rhs - mwevol.psi_rhs = 0\n", "Gamma_rhs - mwevol.Gamma_rhs = 0\n", "ArhsU[0] - mwevol.ArhsU[0] = 0\n", "ErhsU[0] - mwevol.ErhsU[0] = 0\n", "ArhsU[1] - mwevol.ArhsU[1] = 0\n", "ErhsU[1] - mwevol.ErhsU[1] = 0\n", "ArhsU[2] - mwevol.ArhsU[2] = 0\n", "ErhsU[2] - mwevol.ErhsU[2] = 0\n" ] } ], "source": [ "# Reset the list of gridfunctions, as registering a gridfunction\n", "# twice will spawn an error.\n", "gri.glb_gridfcs_list = []\n", "\n", "# Call the VacuumMaxwellRHSs() function from within the\n", "# Maxwell/VacuumMaxwell_Flat_Evol_Cartesian.py module,\n", "# which should do exactly the same as the steps above.\n", "\n", "par.set_paramsvals_value(\"Maxwell.InitialData::System_to_use=System_II\")\n", "mwevol.VacuumMaxwellRHSs()\n", "\n", "print(\"Consistency check between Tutorial-VacuumMaxwell_Cartesian_RHSs tutorial and NRPy+ module: ALL SHOULD BE ZERO.\")\n", "\n", "print(\"C - mwevol.C = \" + str(C - mwevol.C))\n", "print(\"G - mwevol.G = \" + str(G - mwevol.G))\n", "print(\"psi_rhs - mwevol.psi_rhs = \" + str(psi_rhs - mwevol.psi_rhs))\n", "print(\"Gamma_rhs - mwevol.Gamma_rhs = \" + str(Gamma_rhs - mwevol.Gamma_rhs))\n", "for i in range(DIM):\n", "\n", " print(\"ArhsU[\"+str(i)+\"] - mwevol.ArhsU[\"+str(i)+\"] = \" + str(ArhsU[i] - mwevol.ArhsU[i]))\n", " print(\"ErhsU[\"+str(i)+\"] - mwevol.ErhsU[\"+str(i)+\"] = \" + str(ErhsU[i] - mwevol.ErhsU[i]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Step 4: Output this notebook to $\\LaTeX$-formatted PDF file \\[Back to [top](#toc)\\]\n", "$$\\label{latex_pdf_output}$$\n", "\n", "The following code cell converts this Jupyter notebook into a proper, clickable $\\LaTeX$-formatted PDF file. After the cell is successfully run, the generated PDF may be found in the root NRPy+ tutorial directory, with filename\n", "[Tutorial-VacuumMaxwell_Cartesian_RHSs.pdf](Tutorial-VacuumMaxwell_Cartesian_RHSs.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2021-03-07T17:18:47.304599Z", "iopub.status.busy": "2021-03-07T17:18:47.303930Z", "iopub.status.idle": "2021-03-07T17:18:51.178598Z", "shell.execute_reply": "2021-03-07T17:18:51.179321Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created Tutorial-VacuumMaxwell_Cartesian_RHSs.tex, and compiled LaTeX file\n", " to PDF file Tutorial-VacuumMaxwell_Cartesian_RHSs.pdf\n" ] } ], "source": [ "import cmdline_helper as cmd # NRPy+: Multi-platform Python command-line interface\n", "cmd.output_Jupyter_notebook_to_LaTeXed_PDF(\"Tutorial-VacuumMaxwell_Cartesian_RHSs\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.8.2" } }, "nbformat": 4, "nbformat_minor": 2 }