{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "\n", "# NRPy+'s Reference Metric Interface\n", "\n", "## Author: Zach Etienne\n", "### Formatting improvements courtesy Brandon Clark\n", "\n", "### NRPy+ Source Code for this module: [reference_metric.py](../edit/reference_metric.py)\n", "\n", "## Introduction:\n", "### Why use a reference metric? Benefits of choosing the best coordinate system for the problem\n", "\n", "When solving a partial differential equation on the computer, it is useful to first pick a coordinate system well-suited to the geometry of the problem. For example, if we are modeling a spherically-symmetric star, it would be hugely wasteful to model the star in 3-dimensional Cartesian coordinates ($x$,$y$,$z$). This is because in Cartesian coordinates, we would need to choose high sampling in all three Cartesian directions. If instead we chose to model the star in spherical coordinates ($r$,$\\theta$,$\\phi$), so long as the star is centered at $r=0$, we would not need to model the star with more than one point in the $\\theta$ and $\\phi$ directions!\n", "\n", "A similar argument holds for stars that are *nearly* spherically symmetric. Such stars may exhibit density distributions that vary slowly in $\\theta$ and $\\phi$ directions (e.g., isolated neutron stars or black holes). In these cases the number of points needed to sample the angular directions will still be much smaller than in the radial direction.\n", "\n", "Thus choice of an appropriate reference metric may directly mitigate the [Curse of Dimensionality](https://en.wikipedia.org/wiki/Curse_of_dimensionality)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Table of Contents\n", "$$\\label{toc}$$\n", "\n", "This notebook is organized as follow\n", "\n", "1. [Step 1](#define_ref_metric): Defining a reference metric, [reference_metric.py](../edit/reference_metric.py)\n", "1. [Step 2](#define_geometric): Defining geometric quantities, **ref_metric__hatted_quantities()**\n", "1. [Step 3](#prescribed_ref_metric): Prescribed reference metrics in [reference_metric.py](../edit/reference_metric.py)\n", " 1. [Step 3.a](#sphericallike): Spherical-like coordinate systems\n", " 1. [Step 3.a.i](#spherical): **reference_metric::CoordSystem = \"Spherical\"**\n", " 1. [Step 3.a.ii](#sinhspherical): **reference_metric::CoordSystem = \"SinhSpherical\"**\n", " 1. [Step 3.a.iii](#sinhsphericalv2): **reference_metric::CoordSystem = \"SinhSphericalv2\"**\n", " 1. [Step 3.b](#cylindricallike): Cylindrical-like coordinate systems\n", " 1. [Step 3.b.i](#cylindrical): **reference_metric::CoordSystem = \"Cylindrical\"**\n", " 1. [Step 3.b.ii](#sinhcylindrical): **reference_metric::CoordSystem = \"SinhCylindrical\"**\n", " 1. [Step 3.b.iii](#sinhcylindricalv2): **reference_metric::CoordSystem = \"SinhCylindricalv2\"**\n", " 1. [Step 3.c](#cartesianlike): Cartesian-like coordinate systems\n", " 1. [Step 3.c.i](#cartesian): **reference_metric::CoordSystem = \"Cartesian\"**\n", " 1. [Step 3.c.ii](#sinhcartesian): **reference_metric::CoordSystem = \"SinhCartesian\"**\n", " 1. [Step 3.d](#prolatespheroidal): Prolate spheroidal coordinates\n", " 1. [Step 3.d.i](#symtp): **reference_metric::CoordSystem = \"SymTP\"**\n", " 1. [Step 3.d.ii](#sinhsymtp): **reference_metric::CoordSystem = \"SinhSymTP\"**\n", "1. [Step 4](#latex_pdf_output): Output this notebook to $\\LaTeX$-formatted PDF file" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Step 1: Defining a reference metric, [reference_metric.py](../edit/reference_metric.py) \$Back to [top](#toc)\$\n", "$$\\label{define_ref_metric}$$\n", "\n", "***Note that currently only orthogonal reference metrics of dimension 3 or fewer are supported. This can be extended if desired.***\n", "\n", "NRPy+ assumes all curvilinear coordinate systems map directly from a uniform, Cartesian numerical grid with coordinates $(x,y,z)$=(xx[0],xx[1],xx[2]). Thus when defining reference metrics, all defined coordinate quantities must be in terms of the xx[] array. As we will see, this adds a great deal of flexibility\n", "\n", "For example, [**reference_metric.py**](../edit/reference_metric.py) requires that the *orthogonal coordinate scale factors* be defined. As described [here](https://en.wikipedia.org/wiki/Curvilinear_coordinates), the $i$th scale factor is the positive root of the metric element $g_{ii}$. In ordinary spherical coordinates $(r,\\theta,\\phi)$, with line element $ds^2 = g_{ij} dx^i dx^j = dr^2+ r^2 d \\theta^2 + r^2 \\sin^2\\theta \\ d\\phi^2$, we would first define\n", "* $r = xx_0$\n", "* $\\theta = xx_1$\n", "* $\\phi = xx_2$,\n", "\n", "so that the scale factors are defined as\n", "* scalefactor_orthog[0] = $1$\n", "* scalefactor_orthog[1] = $r$\n", "* scalefactor_orthog[2] = $r \\sin \\theta$\n", "\n", "Here is the corresponding code:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2021-08-29T17:43:24.258939Z", "iopub.status.busy": "2021-08-29T17:43:24.257833Z", "iopub.status.idle": "2021-08-29T17:43:24.518903Z", "shell.execute_reply": "2021-08-29T17:43:24.519387Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "r*sin(th) = xx0*sin(xx1)\n" ] } ], "source": [ "import sympy as sp # SymPy: The Python computer algebra package upon which NRPy+ depends\n", "import NRPy_param_funcs as par # NRPy+: parameter interface\n", "import reference_metric as rfm # NRPy+: Reference metric support\n", "\n", "r = rfm.xx[0]\n", "th = rfm.xx[1]\n", "ph = rfm.xx[2]\n", "\n", "rfm.scalefactor_orthog[0] = 1\n", "rfm.scalefactor_orthog[1] = r\n", "rfm.scalefactor_orthog[2] = r*sp.sin(th)\n", "\n", "# Notice that the scale factor will be given\n", "# in terms of the fundamental Cartesian\n", "# grid variables, and not {r,th,ph}:\n", "print(\"r*sin(th) = \"+str(rfm.scalefactor_orthog[2]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next suppose we wish to modify our radial coordinate $r(xx_0)$ to be an exponentially increasing function, so that our numerical grid $(xx_0,xx_1,xx_2)$ will map to a spherical grid with radial grid spacing ($\\Delta r$) that *increases* with $r$. Generally we will find it useful to define $r(xx_0)$ to be an odd function, so let's choose\n", "\n", "$$r(xx_0) = a \\sinh(xx_0/s),$$\n", "\n", "where $a$ is an overall radial scaling factor, and $s$ denotes the scale (in units of $xx_0$) over which exponential growth will take place. In our implementation below, note that we use the relation\n", "\n", "$$\\sinh(x) = \\frac{e^x - e^{-x}}{2},$$\n", "\n", "as SymPy finds it easier to evaluate exponentials than hyperbolic trigonometric functions." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2021-08-29T17:43:24.527269Z", "iopub.status.busy": "2021-08-29T17:43:24.526203Z", "iopub.status.idle": "2021-08-29T17:43:24.558309Z", "shell.execute_reply": "2021-08-29T17:43:24.557812Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a*(exp(xx0/s) - exp(-xx0/s))*sin(xx1)/2\n" ] } ], "source": [ "a,s = sp.symbols('a s',positive=True)\n", "xx0_rescaled = rfm.xx[0] / s\n", "r = a*(sp.exp(xx0_rescaled) - sp.exp(-xx0_rescaled))/2\n", "\n", "# Must redefine the scalefactors since 'r' has been updated!\n", "rfm.scalefactor_orthog[0] = 1\n", "rfm.scalefactor_orthog[1] = r\n", "rfm.scalefactor_orthog[2] = r*sp.sin(th)\n", "\n", "print(rfm.scalefactor_orthog[2])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Often we will find it useful to also define the appropriate mappings from (xx[0],xx[1],xx[2]) to Cartesian coordinates (for plotting purposes) and ordinary spherical coordinates (e.g., in case initial data when solving a PDE are naturally written in spherical coordinates). For this purpose, reference_metric.py also declares lists **xx_to_Cart[]** and **xxSph[]**, which in this case are defined as" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2021-08-29T17:43:24.601160Z", "iopub.status.busy": "2021-08-29T17:43:24.600554Z", "iopub.status.idle": "2021-08-29T17:43:24.744970Z", "shell.execute_reply": "2021-08-29T17:43:24.744312Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ⎛xx₀⎞\n", "a⋅sin(xx₁)⋅cos(xx₂)⋅sinh⎜───⎟\n", " ⎝ s ⎠\n" ] } ], "source": [ "rfm.xxSph[0] = r\n", "rfm.xxSph[1] = th\n", "rfm.xxSph[2] = ph\n", "\n", "rfm.xx_to_Cart[0] = r*sp.sin(th)*sp.cos(ph)\n", "rfm.xx_to_Cart[1] = r*sp.sin(th)*sp.sin(ph)\n", "rfm.xx_to_Cart[2] = r*sp.cos(th)\n", "\n", "# Here we show off SymPy's pretty_print()\n", "# and simplify() functions. Nice, no?\n", "sp.pretty_print(sp.simplify(rfm.xx_to_Cart[0]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Step 2: Define geometric quantities, ref_metric__hatted_quantities() \$Back to [top](#toc)\$\n", "$$\\label{define_geometric}$$\n", "\n", "Once scalefactor_orthog[] has been defined, the function **ref_metric__hatted_quantities()** within [reference_metric.py](../edit/reference_metric.py) can be called to define a number of geometric quantities useful for solving PDEs in curvilinear coordinate systems. \n", "\n", "Adopting the notation of [Baumgarte, Montero, Cordero-Carrión, and Müller, PRD 87, 044026 (2012)](https://arxiv.org/abs/1211.6632), geometric quantities related to the reference metric are named \"hatted\" quantities, . For example, the reference metric is defined as $\\hat{g}_{ij}$=ghatDD[i][j]:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2021-08-29T17:43:24.805174Z", "iopub.status.busy": "2021-08-29T17:43:24.768957Z", "iopub.status.idle": "2021-08-29T17:43:25.060544Z", "shell.execute_reply": "2021-08-29T17:43:25.059788Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "⎡1 0 0 ⎤\n", "⎢ ⎥\n", "⎢ 2 ⎥\n", "⎢ ⎛ xx₀ -xx₀ ⎞ ⎥\n", "⎢ ⎜ ─── ─────⎟ ⎥\n", "⎢ 2 ⎜ s s ⎟ ⎥\n", "⎢ a ⋅⎝ℯ - ℯ ⎠ ⎥\n", "⎢0 ─────────────────── 0 ⎥\n", "⎢ 4 ⎥\n", "⎢ ⎥\n", "⎢ 2 ⎥\n", "⎢ ⎛ xx₀ -xx₀ ⎞ ⎥\n", "⎢ ⎜ ─── ─────⎟ ⎥\n", "⎢ 2 ⎜ s s ⎟ 2 ⎥\n", "⎢ a ⋅⎝ℯ - ℯ ⎠ ⋅sin (xx₁)⎥\n", "⎢0 0 ─────────────────────────────⎥\n", "⎣ 4 ⎦\n" ] } ], "source": [ "rfm.ref_metric__hatted_quantities()\n", "\n", "sp.pretty_print(sp.Matrix(rfm.ghatDD))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In addition to $\\hat{g}_{ij}$, **ref_metric__hatted_quantities()** also provides:\n", "* The rescaling \"matrix\" ReDD[i][j], used for separating singular (due to chosen coordinate system) pieces of smooth rank-2 tensor components from the smooth parts, so that the smooth parts can be used within temporal and spatial differential operators.\n", "* Inverse reference metric: $\\hat{g}^{ij}$=ghatUU[i][j].\n", "* Reference metric determinant: $\\det\\left(\\hat{g}_{ij}\\right)$=detgammahat.\n", "* First and second derivatives of the reference metric: $\\hat{g}_{ij,k}$=ghatDD_dD[i][j][k]; $\\hat{g}_{ij,kl}$=ghatDD_dDD[i][j][k][l]\n", "* Christoffel symbols associated with the reference metric, $\\hat{\\Gamma}^i_{jk}$ = GammahatUDD[i][j][k] and their first derivatives $\\hat{\\Gamma}^i_{jk,l}$ = GammahatUDD_dD[i][j][k][l]\n", "\n", "For example, the Christoffel symbol $\\hat{\\Gamma}^{xx_1}_{xx_2 xx_2}=\\hat{\\Gamma}^1_{22}$ is given by GammahatUDD[1][2][2]:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2021-08-29T17:43:25.136848Z", "iopub.status.busy": "2021-08-29T17:43:25.100413Z", "iopub.status.idle": "2021-08-29T17:43:25.139937Z", "shell.execute_reply": "2021-08-29T17:43:25.140830Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-sin(2⋅xx₁) \n", "────────────\n", " 2 \n" ] } ], "source": [ "sp.pretty_print(sp.simplify(rfm.GammahatUDD[1][2][2]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Given the trigonometric identity $2\\sin(x)\\cos(x) = \\sin(2x)$, notice that the above expression is equivalent to Eq. 18 of [Baumgarte, Montero, Cordero-Carrión, and Müller, PRD 87, 044026 (2012)](https://arxiv.org/abs/1211.6632). This is expected since the sinh-radial spherical coordinate system is equivalent to ordinary spherical coordinates in the angular components." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Step 3: Prescribed reference metrics in [reference_metric.py](../edit/reference_metric.py) \$Back to [top](#toc)\$\n", "$$\\label{prescribed_ref_metric}$$\n", "\n", "One need not manually define scale factors or other quantities for reference metrics, as a number of prescribed reference metrics are already defined in [reference_metric.py](../edit/reference_metric.py). These can be accessed by first setting the parameter **reference_metric::CoordSystem** to one of the following, and then calling the function **rfm.reference_metric()**." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2021-08-29T17:43:25.149006Z", "iopub.status.busy": "2021-08-29T17:43:25.148251Z", "iopub.status.idle": "2021-08-29T17:43:25.150399Z", "shell.execute_reply": "2021-08-29T17:43:25.149775Z" } }, "outputs": [], "source": [ "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 0a: Initialize parameters\n", "thismodule = __name__\n", "par.initialize_param(par.glb_param(\"char\", thismodule, \"CoordSystem\", \"Spherical\"))\n", "\n", "# Step 0b: Declare global variables\n", "xx = gri.xx\n", "xx_to_Cart = ixp.zerorank1(DIM=4) # Must be set in terms of xx[]s\n", "Cart_to_xx = ixp.zerorank1(DIM=4) # Must be set in terms of xx[]s\n", "Cartx,Carty,Cartz = sp.symbols(\"Cartx Carty Cartz\", real=True)\n", "Cart = [Cartx,Carty,Cartz]\n", "xxSph = ixp.zerorank1(DIM=4) # Must be set in terms of xx[]s\n", "scalefactor_orthog = ixp.zerorank1(DIM=4) # Must be set in terms of xx[]s\n", "have_already_called_reference_metric_function = False\n", "\n", "\n", "\n", "CoordSystem = par.parval_from_str(\"reference_metric::CoordSystem\")\n", "M_PI,M_SQRT1_2 = par.Cparameters(\"#define\",thismodule,[\"M_PI\",\"M_SQRT1_2\"],\"\")\n", "\n", "global xxmin\n", "global xxmax\n", "\n", "global UnitVectors\n", "UnitVectors = ixp.zerorank2(DIM=3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will find the following plotting function useful for analyzing coordinate systems in which the radial coordinate is rescaled." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2021-08-29T17:43:25.159394Z", "iopub.status.busy": "2021-08-29T17:43:25.158734Z", "iopub.status.idle": "2021-08-29T17:43:25.161122Z", "shell.execute_reply": "2021-08-29T17:43:25.160598Z" } }, "outputs": [], "source": [ "def create_r_of_xx0_plots(CoordSystem, r_of_xx0,rprime_of_xx0):\n", " import matplotlib.pyplot as plt # matplotlib: Python module specializing in plotting capabilities\n", " plt.clf()\n", " Nr = 20\n", " dxx0 = 1.0 / float(Nr)\n", " xx0s = []\n", " rs = []\n", " deltars = []\n", " rprimes = []\n", " for i in range(Nr):\n", " xx0 = (float(i) + 0.5)*dxx0\n", " xx0s.append(xx0)\n", " rs.append( sp.sympify(str(r_of_xx0 ).replace(\"xx0\",str(xx0))))\n", " rprimes.append(sp.sympify(str(rprime_of_xx0).replace(\"xx0\",str(xx0))))\n", " if i>0:\n", " deltars.append(sp.log(rs[i]-rs[i-1],10))\n", " else:\n", " deltars.append(sp.log(2*rs[0],10))\n", "\n", " # fig, ax = plt.subplots()\n", " fig = plt.figure(figsize=(12,12)) # 8 in x 8 in\n", "\n", " ax = fig.add_subplot(221)\n", " ax.set_title('$r(xx_0)$ for '+CoordSystem,fontsize='x-large')\n", " ax.set_xlabel('$xx_0$',fontsize='x-large')\n", " ax.set_ylabel('$r(xx_0)$',fontsize='x-large')\n", " ax.plot(xx0s, rs, 'k.', label='Spacing between\\nadjacent gridpoints')\n", " # legend = ax.legend(loc='lower right', shadow=True, fontsize='x-large')\n", " # legend.get_frame().set_facecolor('C1')\n", "\n", " ax = fig.add_subplot(222)\n", " ax.set_title('Grid spacing for '+CoordSystem,fontsize='x-large')\n", " ax.set_xlabel('$xx_0$',fontsize='x-large')\n", " ax.set_ylabel('$\\log_{10}(\\Delta r)$',fontsize='x-large')\n", " ax.plot(xx0s, deltars, 'k.', label='Spacing between\\nadjacent gridpoints\\nin $r(xx_0)$ plot')\n", " legend = ax.legend(loc='lower right', shadow=True, fontsize='x-large')\n", " legend.get_frame().set_facecolor('C1')\n", "\n", " ax = fig.add_subplot(223)\n", " ax.set_title('$r\\'(xx_0)$ for '+CoordSystem,fontsize='x-large')\n", " ax.set_xlabel('$xx_0$',fontsize='x-large')\n", " ax.set_ylabel('$r\\'(xx_0)$',fontsize='x-large')\n", " ax.plot(xx0s, rprimes, 'k.', label='Nr=96')\n", " # legend = ax.legend(loc='upper left', shadow=True, fontsize='x-large')\n", " # legend.get_frame().set_facecolor('C1')\n", "\n", " plt.tight_layout(pad=2)\n", " plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "## Step 3.a: Spherical-like coordinate systems \$Back to [top](#toc)\$\n", "$$\\label{sphericallike}$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "### Step 3.a.i: **reference_metric::CoordSystem = \"Spherical\"** \$Back to [top](#toc)\$\n", "$$\\label{spherical}$$\n", "\n", "Standard spherical coordinates, with $(r,\\theta,\\phi)=(xx_0,xx_1,xx_2)$" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2021-08-29T17:43:25.174692Z", "iopub.status.busy": "2021-08-29T17:43:25.174084Z", "iopub.status.idle": "2021-08-29T17:43:25.176224Z", "shell.execute_reply": "2021-08-29T17:43:25.175582Z" } }, "outputs": [], "source": [ "if CoordSystem == \"Spherical\":\n", " # Adding assumption real=True can help simplify expressions involving xx[0] & xx[1] below.\n", " xx[0] = sp.symbols(\"xx0\", real=True)\n", " xx[1] = sp.symbols(\"xx1\", real=True)\n", "\n", " RMAX = par.Cparameters(\"REAL\", thismodule, [\"RMAX\"],10.0)\n", " xxmin = [sp.sympify(0), sp.sympify(0), -M_PI]\n", " xxmax = [ RMAX, M_PI, M_PI]\n", "\n", " r = xx[0]\n", " th = xx[1]\n", " ph = xx[2]\n", "\n", " Cart_to_xx[0] = sp.sqrt(Cartx ** 2 + Carty ** 2 + Cartz ** 2)\n", " Cart_to_xx[1] = sp.acos(Cartz / Cart_to_xx[0])\n", " Cart_to_xx[2] = sp.atan2(Carty, Cartx)\n", "\n", " xxSph[0] = r\n", " xxSph[1] = th\n", " xxSph[2] = ph\n", "\n", " # Now define xCart, yCart, and zCart in terms of x0,xx[1],xx[2].\n", " # Note that the relation between r and x0 is not necessarily trivial in SinhSpherical coordinates. See above.\n", " xx_to_Cart[0] = xxSph[0]*sp.sin(xxSph[1])*sp.cos(xxSph[2])\n", " xx_to_Cart[1] = xxSph[0]*sp.sin(xxSph[1])*sp.sin(xxSph[2])\n", " xx_to_Cart[2] = xxSph[0]*sp.cos(xxSph[1])\n", "\n", " scalefactor_orthog[0] = sp.diff(xxSph[0],xx[0])\n", " scalefactor_orthog[1] = xxSph[0]\n", " scalefactor_orthog[2] = xxSph[0]*sp.sin(xxSph[1])\n", "\n", " # Set the unit vectors\n", " UnitVectors = [[ sp.sin(xxSph[1])*sp.cos(xxSph[2]), sp.sin(xxSph[1])*sp.sin(xxSph[2]), sp.cos(xxSph[1])],\n", " [ sp.cos(xxSph[1])*sp.cos(xxSph[2]), sp.cos(xxSph[1])*sp.sin(xxSph[2]), -sp.sin(xxSph[1])],\n", " [ -sp.sin(xxSph[2]), sp.cos(xxSph[2]), sp.sympify(0) ]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's analyze $r(xx_0)$ for **\"Spherical\"** coordinates." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2021-08-29T17:43:25.182737Z", "iopub.status.busy": "2021-08-29T17:43:25.182127Z", "iopub.status.idle": "2021-08-29T17:43:26.187428Z", "shell.execute_reply": "2021-08-29T17:43:26.187924Z" } }, "outputs": [ { "data": { "text/plain": [ "