xref: /libCEED/examples/python/tutorial-2-elemrestriction.ipynb (revision edab612303ce5c583510db4ec9520fcda426d3c1)
1*edab6123Sjeremylt{
2*edab6123Sjeremylt "cells": [
3*edab6123Sjeremylt  {
4*edab6123Sjeremylt   "cell_type": "markdown",
5*edab6123Sjeremylt   "metadata": {},
6*edab6123Sjeremylt   "source": [
7*edab6123Sjeremylt    "# libCEED for Python examples\n",
8*edab6123Sjeremylt    "\n",
9*edab6123Sjeremylt    "This is a tutorial to illustrate the main feautures of the Python interface for [libCEED](https://github.com/CEED/libCEED/), the low-level API library for efficient high-order discretization methods developed by the co-design [Center for Efficient Exascale Discretizations](https://ceed.exascaleproject.org/) (CEED) of the [Exascale Computing Project](https://www.exascaleproject.org/) (ECP).\n",
10*edab6123Sjeremylt    "\n",
11*edab6123Sjeremylt    "While libCEED's focus is on high-order finite/spectral element method implementations, the approach is mostly algebraic and thus applicable to other discretizations in factored form, as explained in the [user manual](https://libceed.readthedocs.io/)."
12*edab6123Sjeremylt   ]
13*edab6123Sjeremylt  },
14*edab6123Sjeremylt  {
15*edab6123Sjeremylt   "cell_type": "markdown",
16*edab6123Sjeremylt   "metadata": {},
17*edab6123Sjeremylt   "source": [
18*edab6123Sjeremylt    "## Setting up libCEED for Python\n",
19*edab6123Sjeremylt    "\n",
20*edab6123Sjeremylt    "Install libCEED for Python by running"
21*edab6123Sjeremylt   ]
22*edab6123Sjeremylt  },
23*edab6123Sjeremylt  {
24*edab6123Sjeremylt   "cell_type": "code",
25*edab6123Sjeremylt   "execution_count": null,
26*edab6123Sjeremylt   "metadata": {},
27*edab6123Sjeremylt   "outputs": [],
28*edab6123Sjeremylt   "source": [
29*edab6123Sjeremylt    "! python -m pip install libceed"
30*edab6123Sjeremylt   ]
31*edab6123Sjeremylt  },
32*edab6123Sjeremylt  {
33*edab6123Sjeremylt   "cell_type": "markdown",
34*edab6123Sjeremylt   "metadata": {},
35*edab6123Sjeremylt   "source": [
36*edab6123Sjeremylt    "## CeedElemRestriction\n",
37*edab6123Sjeremylt    "\n",
38*edab6123Sjeremylt    "Here we show some basic examples to illustrate the `libceed.ElemRestriction` class. In libCEED, a `libceed.ElemRestriction` groups the degrees of freedom (dofs) of the local vector according to the different elements they belong to (see [the API documentation](https://libceed.readthedocs.io/en/latest/libCEEDapi.html#finite-element-operator-decomposition)).\n",
39*edab6123Sjeremylt    "\n",
40*edab6123Sjeremylt    "Here we illustrate the simple creation and application of a `libceed.ElemRestriction`, with user provided dof indices"
41*edab6123Sjeremylt   ]
42*edab6123Sjeremylt  },
43*edab6123Sjeremylt  {
44*edab6123Sjeremylt   "cell_type": "code",
45*edab6123Sjeremylt   "execution_count": null,
46*edab6123Sjeremylt   "metadata": {},
47*edab6123Sjeremylt   "outputs": [],
48*edab6123Sjeremylt   "source": [
49*edab6123Sjeremylt    "import libceed\n",
50*edab6123Sjeremylt    "import numpy as np\n",
51*edab6123Sjeremylt    "\n",
52*edab6123Sjeremylt    "# In this 1D example, the dofs are indexed as\n",
53*edab6123Sjeremylt    "#\n",
54*edab6123Sjeremylt    "# Restriction input:\n",
55*edab6123Sjeremylt    "#  x --  x --  x --  x\n",
56*edab6123Sjeremylt    "# 10 -- 11 -- 12 -- 13\n",
57*edab6123Sjeremylt    "#\n",
58*edab6123Sjeremylt    "# Restriction output:\n",
59*edab6123Sjeremylt    "#  x --  x |  x --  x | x --  x\n",
60*edab6123Sjeremylt    "# 10 -- 11 | 11 -- 12 | 12 -- 13\n",
61*edab6123Sjeremylt    "\n",
62*edab6123Sjeremylt    "ceed = libceed.Ceed()\n",
63*edab6123Sjeremylt    "\n",
64*edab6123Sjeremylt    "ne = 3\n",
65*edab6123Sjeremylt    "\n",
66*edab6123Sjeremylt    "x = ceed.Vector(ne+1)\n",
67*edab6123Sjeremylt    "a = np.arange(10, 10 + ne+1, dtype=\"float64\")\n",
68*edab6123Sjeremylt    "x.set_array(a, cmode=libceed.USE_POINTER)\n",
69*edab6123Sjeremylt    "\n",
70*edab6123Sjeremylt    "ind = np.zeros(2*ne, dtype=\"int32\")\n",
71*edab6123Sjeremylt    "for i in range(ne):\n",
72*edab6123Sjeremylt    "  ind[2*i+0] = i\n",
73*edab6123Sjeremylt    "  ind[2*i+1] = i+1\n",
74*edab6123Sjeremylt    "    \n",
75*edab6123Sjeremylt    "r = ceed.ElemRestriction(ne, 2, 1, 1, ne+1, ind, cmode=libceed.USE_POINTER)\n",
76*edab6123Sjeremylt    "\n",
77*edab6123Sjeremylt    "y = ceed.Vector(2*ne)\n",
78*edab6123Sjeremylt    "y.set_value(0)\n",
79*edab6123Sjeremylt    "\n",
80*edab6123Sjeremylt    "r.apply(x, y)\n",
81*edab6123Sjeremylt    "\n",
82*edab6123Sjeremylt    "with y.array_read() as y_array:\n",
83*edab6123Sjeremylt    "  print('y =', y_array)\n"
84*edab6123Sjeremylt   ]
85*edab6123Sjeremylt  },
86*edab6123Sjeremylt  {
87*edab6123Sjeremylt   "cell_type": "markdown",
88*edab6123Sjeremylt   "metadata": {},
89*edab6123Sjeremylt   "source": [
90*edab6123Sjeremylt    "* In the following example, we illustrate how to extract the multiplicity of indices in an element restriction"
91*edab6123Sjeremylt   ]
92*edab6123Sjeremylt  },
93*edab6123Sjeremylt  {
94*edab6123Sjeremylt   "cell_type": "code",
95*edab6123Sjeremylt   "execution_count": null,
96*edab6123Sjeremylt   "metadata": {},
97*edab6123Sjeremylt   "outputs": [],
98*edab6123Sjeremylt   "source": [
99*edab6123Sjeremylt    "# In this 1D example, there are four nodes per element\n",
100*edab6123Sjeremylt    "# \n",
101*edab6123Sjeremylt    "#  x -- o -- o -- x -- o -- o -- x -- o -- o -- x\n",
102*edab6123Sjeremylt    "\n",
103*edab6123Sjeremylt    "ne = 3\n",
104*edab6123Sjeremylt    "\n",
105*edab6123Sjeremylt    "ind = np.zeros(4*ne, dtype=\"int32\")\n",
106*edab6123Sjeremylt    "\n",
107*edab6123Sjeremylt    "for i in range(ne):\n",
108*edab6123Sjeremylt    "  ind[4*i+0] = i*3+0\n",
109*edab6123Sjeremylt    "  ind[4*i+1] = i*3+1\n",
110*edab6123Sjeremylt    "  ind[4*i+2] = i*3+2\n",
111*edab6123Sjeremylt    "  ind[4*i+3] = i*3+3\n",
112*edab6123Sjeremylt    "\n",
113*edab6123Sjeremylt    "r = ceed.ElemRestriction(ne, 4, 1, 1, 3*ne+1, ind, cmode=libceed.USE_POINTER)\n",
114*edab6123Sjeremylt    "\n",
115*edab6123Sjeremylt    "mult = r.get_multiplicity()\n",
116*edab6123Sjeremylt    "\n",
117*edab6123Sjeremylt    "with mult.array_read() as m_array:\n",
118*edab6123Sjeremylt    "  print('mult =', m_array)\n"
119*edab6123Sjeremylt   ]
120*edab6123Sjeremylt  },
121*edab6123Sjeremylt  {
122*edab6123Sjeremylt   "cell_type": "markdown",
123*edab6123Sjeremylt   "metadata": {},
124*edab6123Sjeremylt   "source": [
125*edab6123Sjeremylt    "* In the following example, we illustrate the creation and use of a strided (identity) element restriction. Strided restrictions are typically used for data stored at quadrature points or for vectors stored in the [E-vector](https://libceed.readthedocs.io/en/latest/libCEEDapi.html#finite-element-operator-decomposition) format, such as in Discontinuous Galerkin approximations."
126*edab6123Sjeremylt   ]
127*edab6123Sjeremylt  },
128*edab6123Sjeremylt  {
129*edab6123Sjeremylt   "cell_type": "code",
130*edab6123Sjeremylt   "execution_count": null,
131*edab6123Sjeremylt   "metadata": {},
132*edab6123Sjeremylt   "outputs": [],
133*edab6123Sjeremylt   "source": [
134*edab6123Sjeremylt    "# In this 1D example, the dofs are indexed as\n",
135*edab6123Sjeremylt    "#\n",
136*edab6123Sjeremylt    "# Restriction input:\n",
137*edab6123Sjeremylt    "#   x   --   x   --   x\n",
138*edab6123Sjeremylt    "# 10-11 -- 12-13 -- 14-15\n",
139*edab6123Sjeremylt    "#\n",
140*edab6123Sjeremylt    "# Restriction output:\n",
141*edab6123Sjeremylt    "#  x --  x |  x --  x |  x --  x\n",
142*edab6123Sjeremylt    "# 10 -- 11 | 12 -- 13 | 14 -- 15\n",
143*edab6123Sjeremylt    "\n",
144*edab6123Sjeremylt    "ne = 3\n",
145*edab6123Sjeremylt    "\n",
146*edab6123Sjeremylt    "x = ceed.Vector(2*ne)\n",
147*edab6123Sjeremylt    "a = np.arange(10, 10 + 2*ne, dtype=\"float64\")\n",
148*edab6123Sjeremylt    "x.set_array(a, cmode=libceed.USE_POINTER)\n",
149*edab6123Sjeremylt    "\n",
150*edab6123Sjeremylt    "strides = np.array([1, 2, 2], dtype=\"int32\")\n",
151*edab6123Sjeremylt    "\n",
152*edab6123Sjeremylt    "r = ceed.StridedElemRestriction(ne, 2, 1, 2*ne, strides)\n",
153*edab6123Sjeremylt    "\n",
154*edab6123Sjeremylt    "y = ceed.Vector(2*ne)\n",
155*edab6123Sjeremylt    "y.set_value(0)\n",
156*edab6123Sjeremylt    "\n",
157*edab6123Sjeremylt    "r.apply(x, y)\n",
158*edab6123Sjeremylt    "\n",
159*edab6123Sjeremylt    "with y.array_read() as y_array:\n",
160*edab6123Sjeremylt    "  print('y =', y_array)\n"
161*edab6123Sjeremylt   ]
162*edab6123Sjeremylt  },
163*edab6123Sjeremylt  {
164*edab6123Sjeremylt   "cell_type": "markdown",
165*edab6123Sjeremylt   "metadata": {},
166*edab6123Sjeremylt   "source": [
167*edab6123Sjeremylt    "* In the following example, we illustrate the creation and view of a blocked strided (identity) element restriction"
168*edab6123Sjeremylt   ]
169*edab6123Sjeremylt  },
170*edab6123Sjeremylt  {
171*edab6123Sjeremylt   "cell_type": "code",
172*edab6123Sjeremylt   "execution_count": null,
173*edab6123Sjeremylt   "metadata": {},
174*edab6123Sjeremylt   "outputs": [],
175*edab6123Sjeremylt   "source": [
176*edab6123Sjeremylt    "# In this 1D example, there are three elements (four nodes in total) \n",
177*edab6123Sjeremylt    "# \n",
178*edab6123Sjeremylt    "#  x -- x -- x -- x\n",
179*edab6123Sjeremylt    "\n",
180*edab6123Sjeremylt    "ne = 3\n",
181*edab6123Sjeremylt    "\n",
182*edab6123Sjeremylt    "strides = np.array([1, 2, 2], dtype=\"int32\")\n",
183*edab6123Sjeremylt    "\n",
184*edab6123Sjeremylt    "r = ceed.BlockedStridedElemRestriction(ne, 2, 2, 1, ne+1, strides)\n",
185*edab6123Sjeremylt    "\n",
186*edab6123Sjeremylt    "print(r)"
187*edab6123Sjeremylt   ]
188*edab6123Sjeremylt  },
189*edab6123Sjeremylt  {
190*edab6123Sjeremylt   "cell_type": "markdown",
191*edab6123Sjeremylt   "metadata": {},
192*edab6123Sjeremylt   "source": [
193*edab6123Sjeremylt    "### Advanced topics"
194*edab6123Sjeremylt   ]
195*edab6123Sjeremylt  },
196*edab6123Sjeremylt  {
197*edab6123Sjeremylt   "cell_type": "markdown",
198*edab6123Sjeremylt   "metadata": {},
199*edab6123Sjeremylt   "source": [
200*edab6123Sjeremylt    "* In the following example (intended for backend developers), we illustrate the creation of a blocked element restriction (from an L-vector to an E-vector) and its transpose (inverse operation, from an E-vector to an L-vector)"
201*edab6123Sjeremylt   ]
202*edab6123Sjeremylt  },
203*edab6123Sjeremylt  {
204*edab6123Sjeremylt   "cell_type": "code",
205*edab6123Sjeremylt   "execution_count": null,
206*edab6123Sjeremylt   "metadata": {},
207*edab6123Sjeremylt   "outputs": [],
208*edab6123Sjeremylt   "source": [
209*edab6123Sjeremylt    "# In this 1D example, the dofs are indexed as\n",
210*edab6123Sjeremylt    "# \n",
211*edab6123Sjeremylt    "#  x --  x --  x --  x --  x --  x --  x --  x --  x\n",
212*edab6123Sjeremylt    "# 10 -- 11 -- 12 -- 13 -- 14 -- 15 -- 16 -- 17 -- 18\n",
213*edab6123Sjeremylt    "# \n",
214*edab6123Sjeremylt    "# We block elements into groups of 5:\n",
215*edab6123Sjeremylt    "#  ________________________________________________________________________________________\n",
216*edab6123Sjeremylt    "# |                   block 0:                            |         block 1:               |\n",
217*edab6123Sjeremylt    "# |     e0    |    e1    |    e2    |    e3    |    e4    |    e0    |    e1    |    e2    |\n",
218*edab6123Sjeremylt    "# |                                                       |                                |\n",
219*edab6123Sjeremylt    "# |  x --  x  |  x -- x  |  x -- x  |  x -- x  |  x -- x  |  x -- x  |  x -- x  |  x  -- x |\n",
220*edab6123Sjeremylt    "# | 10 -- 11  | 11   12  | 12   13  | 13   14  | 14   15  | 15   16  | 16   17  |  17   18 |\n",
221*edab6123Sjeremylt    "#\n",
222*edab6123Sjeremylt    "# Intermediate logical representation:\n",
223*edab6123Sjeremylt    "#  ______________________________________________________________________________\n",
224*edab6123Sjeremylt    "# |               block 0:               |               block 1:               |\n",
225*edab6123Sjeremylt    "# |     node0:                 node1:    |     node0:                 node1:    |\n",
226*edab6123Sjeremylt    "# | 10-11-12-13-14        11-12-13-14-15 | 15-16-17- *- *        16-17-18- *- * |\n",
227*edab6123Sjeremylt    "# | e0 e1 e2 e3 e4        e0 e1 e2 e3 e4 | e0 e1 e2 e3 e4        e0 e1 e2 e3 e4 |\n",
228*edab6123Sjeremylt    "#\n",
229*edab6123Sjeremylt    "# Forward restriction output:\n",
230*edab6123Sjeremylt    "#  ______________________________________________________________________________\n",
231*edab6123Sjeremylt    "# |               block 0:               |               block 1:               |\n",
232*edab6123Sjeremylt    "# |     node0:                 node1:    |     node0:                 node1:    |\n",
233*edab6123Sjeremylt    "# | 10-11-12-13-14        11-12-13-14-15 | 15-16-17-17-17        16-17-18-18-18 |\n",
234*edab6123Sjeremylt    "# | e0 e1 e2 e3 e4        e0 e1 e2 e3 e4 | e0 e1 e2 e3 e4        e0 e1 e2 e3 e4 |\n",
235*edab6123Sjeremylt    "\n",
236*edab6123Sjeremylt    "ne = 8\n",
237*edab6123Sjeremylt    "blksize = 5\n",
238*edab6123Sjeremylt    "\n",
239*edab6123Sjeremylt    "x = ceed.Vector(ne+1)\n",
240*edab6123Sjeremylt    "a = np.arange(10, 10 + ne+1, dtype=\"float64\")\n",
241*edab6123Sjeremylt    "x.set_array(a, cmode=libceed.USE_POINTER)\n",
242*edab6123Sjeremylt    "\n",
243*edab6123Sjeremylt    "ind = np.zeros(2*ne, dtype=\"int32\")\n",
244*edab6123Sjeremylt    "for i in range(ne):\n",
245*edab6123Sjeremylt    "  ind[2*i+0] = i\n",
246*edab6123Sjeremylt    "  ind[2*i+1] = i+1\n",
247*edab6123Sjeremylt    "\n",
248*edab6123Sjeremylt    "r = ceed.BlockedElemRestriction(ne, 2, blksize, 1, 1, ne+1, ind,\n",
249*edab6123Sjeremylt    "                                cmode=libceed.USE_POINTER)\n",
250*edab6123Sjeremylt    "\n",
251*edab6123Sjeremylt    "y = ceed.Vector(2*blksize*2)\n",
252*edab6123Sjeremylt    "y.set_value(0)\n",
253*edab6123Sjeremylt    "\n",
254*edab6123Sjeremylt    "r.apply(x, y)\n",
255*edab6123Sjeremylt    "\n",
256*edab6123Sjeremylt    "with y.array_read() as y_array:\n",
257*edab6123Sjeremylt    "  print('y =', y_array)\n",
258*edab6123Sjeremylt    "\n",
259*edab6123Sjeremylt    "x.set_value(0)\n",
260*edab6123Sjeremylt    "r.T.apply(y, x)\n",
261*edab6123Sjeremylt    "\n",
262*edab6123Sjeremylt    "with x.array_read() as x_array:\n",
263*edab6123Sjeremylt    "  print('x =', x_array)"
264*edab6123Sjeremylt   ]
265*edab6123Sjeremylt  },
266*edab6123Sjeremylt  {
267*edab6123Sjeremylt   "cell_type": "markdown",
268*edab6123Sjeremylt   "metadata": {},
269*edab6123Sjeremylt   "source": [
270*edab6123Sjeremylt    "* In the following example (intended for backend developers), we illustrate the creation and application of a blocked element restriction (from an L-vector to an E-vector) and its transpose (inverse operation, from an E-vector to an L-vector)"
271*edab6123Sjeremylt   ]
272*edab6123Sjeremylt  },
273*edab6123Sjeremylt  {
274*edab6123Sjeremylt   "cell_type": "code",
275*edab6123Sjeremylt   "execution_count": null,
276*edab6123Sjeremylt   "metadata": {},
277*edab6123Sjeremylt   "outputs": [],
278*edab6123Sjeremylt   "source": [
279*edab6123Sjeremylt    "# In this 1D example, the dofs are indexed as\n",
280*edab6123Sjeremylt    "# \n",
281*edab6123Sjeremylt    "#  x --  x --  x --  x --  x --  x --  x --  x --  x\n",
282*edab6123Sjeremylt    "# 10 -- 11 -- 12 -- 13 -- 14 -- 15 -- 16 -- 17 -- 18\n",
283*edab6123Sjeremylt    "#\n",
284*edab6123Sjeremylt    "# We block elements into groups of 5:\n",
285*edab6123Sjeremylt    "#  ________________________________________________________________________________________\n",
286*edab6123Sjeremylt    "# |                   block 0:                            |         block 1:               |\n",
287*edab6123Sjeremylt    "# |     e0    |    e1    |    e2    |    e3    |    e4    |    e0    |    e1    |    e2    |\n",
288*edab6123Sjeremylt    "# |                                                       |                                |\n",
289*edab6123Sjeremylt    "# |  x --  x  |  x -- x  |  x -- x  |  x -- x  |  x -- x  |  x -- x  |  x -- x  |  x  -- x |\n",
290*edab6123Sjeremylt    "# | 10 -- 11  | 11   12  | 12   13  | 13   14  | 14   15  | 15   16  | 16   17  |  17   18 |\n",
291*edab6123Sjeremylt    "#\n",
292*edab6123Sjeremylt    "# Intermediate logical representation (extraction of block1 only in this case):\n",
293*edab6123Sjeremylt    "#  _______________________________________\n",
294*edab6123Sjeremylt    "# |               block 1:               |\n",
295*edab6123Sjeremylt    "# |     node0:                 node1:    |\n",
296*edab6123Sjeremylt    "# | 15-16-17- *- *        16-17-18- *- * |\n",
297*edab6123Sjeremylt    "# | e0 e1 e2 e3 e4        e0 e1 e2 e3 e4 |\n",
298*edab6123Sjeremylt    "#\n",
299*edab6123Sjeremylt    "# Forward restriction output:\n",
300*edab6123Sjeremylt    "#  _______________________________________\n",
301*edab6123Sjeremylt    "# |               block 1:               |\n",
302*edab6123Sjeremylt    "# |     node0:                 node1:    |\n",
303*edab6123Sjeremylt    "# | 15-16-17-17-17        16-17-18-18-18 |\n",
304*edab6123Sjeremylt    "# | e0 e1 e2 e3 e4        e0 e1 e2 e3 e4 |\n",
305*edab6123Sjeremylt    "\n",
306*edab6123Sjeremylt    "ne = 8\n",
307*edab6123Sjeremylt    "blksize = 5\n",
308*edab6123Sjeremylt    "\n",
309*edab6123Sjeremylt    "x = ceed.Vector(ne+1)\n",
310*edab6123Sjeremylt    "a = np.arange(10, 10 + ne+1, dtype=\"float64\")\n",
311*edab6123Sjeremylt    "x.set_array(a, cmode=libceed.USE_POINTER)\n",
312*edab6123Sjeremylt    "\n",
313*edab6123Sjeremylt    "ind = np.zeros(2*ne, dtype=\"int32\")\n",
314*edab6123Sjeremylt    "for i in range(ne):\n",
315*edab6123Sjeremylt    "  ind[2*i+0] = i\n",
316*edab6123Sjeremylt    "  ind[2*i+1] = i+1\n",
317*edab6123Sjeremylt    "\n",
318*edab6123Sjeremylt    "r = ceed.BlockedElemRestriction(ne, 2, blksize, 1, 1, ne+1, ind,\n",
319*edab6123Sjeremylt    "                                cmode=libceed.USE_POINTER)\n",
320*edab6123Sjeremylt    "\n",
321*edab6123Sjeremylt    "y = ceed.Vector(blksize*2)\n",
322*edab6123Sjeremylt    "y.set_value(0)\n",
323*edab6123Sjeremylt    "\n",
324*edab6123Sjeremylt    "r.apply_block(1, x, y)\n",
325*edab6123Sjeremylt    "\n",
326*edab6123Sjeremylt    "with y.array_read() as y_array:\n",
327*edab6123Sjeremylt    "  print('y =', y_array)\n",
328*edab6123Sjeremylt    "\n",
329*edab6123Sjeremylt    "x.set_value(0)\n",
330*edab6123Sjeremylt    "r.T.apply_block(1, y, x)\n",
331*edab6123Sjeremylt    "\n",
332*edab6123Sjeremylt    "with x.array_read() as array:\n",
333*edab6123Sjeremylt    "  print('x =', x_array)"
334*edab6123Sjeremylt   ]
335*edab6123Sjeremylt  },
336*edab6123Sjeremylt  {
337*edab6123Sjeremylt   "cell_type": "markdown",
338*edab6123Sjeremylt   "metadata": {},
339*edab6123Sjeremylt   "source": [
340*edab6123Sjeremylt    "Note that the nodes at the boundary between elements have multiplicty 2, while the internal nodes and the outer boundary nodes, have multiplicity 1."
341*edab6123Sjeremylt   ]
342*edab6123Sjeremylt  }
343*edab6123Sjeremylt ],
344*edab6123Sjeremylt "metadata": {
345*edab6123Sjeremylt  "kernelspec": {
346*edab6123Sjeremylt   "display_name": "Python 3",
347*edab6123Sjeremylt   "language": "python",
348*edab6123Sjeremylt   "name": "python3"
349*edab6123Sjeremylt  },
350*edab6123Sjeremylt  "language_info": {
351*edab6123Sjeremylt   "codemirror_mode": {
352*edab6123Sjeremylt    "name": "ipython",
353*edab6123Sjeremylt    "version": 3
354*edab6123Sjeremylt   },
355*edab6123Sjeremylt   "file_extension": ".py",
356*edab6123Sjeremylt   "mimetype": "text/x-python",
357*edab6123Sjeremylt   "name": "python",
358*edab6123Sjeremylt   "nbconvert_exporter": "python",
359*edab6123Sjeremylt   "pygments_lexer": "ipython3",
360*edab6123Sjeremylt   "version": "3.8.5"
361*edab6123Sjeremylt  }
362*edab6123Sjeremylt },
363*edab6123Sjeremylt "nbformat": 4,
364*edab6123Sjeremylt "nbformat_minor": 4
365*edab6123Sjeremylt}
366