ipynb: add a simple example of rt-app execution on Juno

This provides a first really simple example of usage of devlib and wlgen
to configure and run an rt-app instance on a Juno board.
The trace collected is also visualised using the TRAPpy plotter.

Signed-off-by: Patrick Bellasi <patrick.bellasi@arm.com>
diff --git a/ipynb/simple_rtapp.ipynb b/ipynb/simple_rtapp.ipynb
new file mode 100644
index 0000000..15004e2
--- /dev/null
+++ b/ipynb/simple_rtapp.ipynb
@@ -0,0 +1,723 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "import json\n",
+    "import os\n",
+    "\n",
+    "import devlib\n",
+    "from wlgen import RTA\n",
+    "\n",
+    "import trappy"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "# Configure logging for this session\n",
+    "import logging\n",
+    "reload(logging)\n",
+    "logging.basicConfig(\n",
+    "    format='%(asctime)-9s %(levelname)-8s: %(message)s',\n",
+    "    level=logging.DEBUG,\n",
+    "    datefmt='%I:%M:%S')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Global configuration"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "# Host side results folder\n",
+    "RESULTS_DIR = '/tmp/schedtest'\n",
+    "\n",
+    "# Taerget side temporary folder\n",
+    "TARGET_DIR = '/root/schedtest'\n",
+    "\n",
+    "# List of tools to install on the target system\n",
+    "TOOLS = [\"rt-app\", \"trace-cmd\", \"taskset\"]\n",
+    "\n",
+    "# FTrace Configuration\n",
+    "FTRACE_EVENTS = ['sched_switch', 'cpu_frequency']\n",
+    "FTRACE_BUFFZISE = 10240\n",
+    "\n",
+    "# HWMon Configuration\n",
+    "HWMON_CONF = {\n",
+    "    'sites' : [ 'a53', 'a57' ],\n",
+    "    'kinds' : [ 'energy' ]\n",
+    "}\n",
+    "\n",
+    "# List of modules to enable\n",
+    "MODULES = ['bl', 'cpufreq', 'hwmon']"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Target configuration"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "01:44:18  DEBUG   : Installing module bl\n",
+      "01:44:19  DEBUG   : Installing module cpufreq\n",
+      "01:44:19  DEBUG   : Installing module hwmon\n"
+     ]
+    }
+   ],
+   "source": [
+    "target = devlib.LinuxTarget(\n",
+    "    connection_settings={\n",
+    "        'host'     : '192.168.0.10',\n",
+    "        'username' : 'root',\n",
+    "        'password' : ''\n",
+    "    },\n",
+    "    load_default_modules=False,\n",
+    "    modules=MODULES,\n",
+    "    working_directory=TARGET_DIR\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "01:44:47  INFO    : Target ABI: arm64, CPus: ['A53', 'A57', 'A57', 'A53', 'A53', 'A53']\n"
+     ]
+    }
+   ],
+   "source": [
+    "logging.info(\"Target ABI: %s, CPus: %s\",\n",
+    "             target.abi,\n",
+    "             target.cpuinfo.cpu_names)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "01:44:47  INFO    : Copy tools required on target\n"
+     ]
+    }
+   ],
+   "source": [
+    "logging.info('Copy tools required on target')\n",
+    "tools_to_install = []\n",
+    "for tool in TOOLS:\n",
+    "    # deploy scripts or...\n",
+    "    binary = '../tools/scripts/{}'.format(tool)\n",
+    "    if not os.path.isfile(binary):\n",
+    "        # ... binary tool matching the target ABI\n",
+    "        binary = '../tools/{}/{}'.format(target.abi, tool)\n",
+    "    tools_to_install.append(binary)\n",
+    "target.setup(tools_to_install)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# FTrace configuration"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "01:44:50  INFO    : FTrace configured to collect events:\n",
+      "01:44:50  INFO    :   ['sched_switch', 'cpu_frequency']\n"
+     ]
+    }
+   ],
+   "source": [
+    "ftrace = None\n",
+    "if len(FTRACE_EVENTS):\n",
+    "    ftrace = devlib.FtraceCollector(\n",
+    "        target,\n",
+    "        events = FTRACE_EVENTS,\n",
+    "        buffer_size = FTRACE_BUFFZISE,\n",
+    "        autoreport = False,\n",
+    "        autoview = False\n",
+    "    )\n",
+    "    logging.info('FTrace configured to collect events:')\n",
+    "    logging.info('  %s', FTRACE_EVENTS)\n",
+    "else:\n",
+    "    logging.info('FTrace collection disabled by configuration')\n",
+    "    \n",
+    "def ftrace_start():\n",
+    "    if ftrace is None:\n",
+    "        return\n",
+    "    ftrace.start()\n",
+    "    logging.info('FTrace STARTED')\n",
+    "    \n",
+    "def ftrace_stop():\n",
+    "    if ftrace is None:\n",
+    "        return\n",
+    "    ftrace.stop()\n",
+    "    logging.info('FTrace STOPPED')\n",
+    "    ftrace.get_trace(RESULTS_DIR + '/trace.dat')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Energy Meter configuration"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "01:44:51  DEBUG   : Discovering available HWMON sensors...\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_amp/curr1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_amp/curr1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_power/power1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_power/power1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_energy/energy1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_energy/energy1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_energy/energy1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_energy/energy1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_amp/curr1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_amp/curr1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_volt/in1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_volt/in1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_volt/in1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_volt/in1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_power/power1\n",
+      "01:44:51  DEBUG   : \tAdding sensor v2m_juno_power/power1\n",
+      "01:44:51  INFO    : Channels selected for energy sampling:\n",
+      "01:44:51  INFO    :   [CHAN(v2m_juno_energy/energy1, a53_energy), CHAN(v2m_juno_energy/energy1, a57_energy)]\n"
+     ]
+    }
+   ],
+   "source": [
+    "hwmon = None\n",
+    "if 'hwmon' in MODULES:\n",
+    "    hwmon = devlib.HwmonInstrument(target)\n",
+    "    hwmon.reset(**HWMON_CONF)\n",
+    "    logging.info('Channels selected for energy sampling:')\n",
+    "    logging.info('  %s', str(hwmon.active_channels))\n",
+    "else:\n",
+    "    logging.info('Energy sampling disabled by configuration')\n",
+    "    \n",
+    "# The contained for sampled energy values\n",
+    "energy_reading = {}\n",
+    "\n",
+    "def hwmon_reset():\n",
+    "    energy_reading = {}\n",
+    "\n",
+    "# Simple function to compute energy-deltas among consecutive readings\n",
+    "def hwmon_sample():\n",
+    "    if hwmon is None:\n",
+    "        return\n",
+    "    samples = hwmon.take_measurement()\n",
+    "    logging.debug('Measure: %s', samples)\n",
+    "    for s in samples:\n",
+    "        label = s.channel.label\\\n",
+    "                    .replace('_energy', '')\\\n",
+    "                    .replace(\" \", \"_\")\n",
+    "        value = s.value\n",
+    "        logging.debug('Update %s: %s',\n",
+    "                      label, value)\n",
+    "\n",
+    "        if label not in energy_reading:\n",
+    "            energy_reading[label] = {\n",
+    "                 'last'  : value,\n",
+    "                 'delta' : 0,\n",
+    "                 'total' : 0\n",
+    "                }\n",
+    "            logging.debug('Initialize %s: %s',\n",
+    "                          label, energy_reading[label])\n",
+    "            continue\n",
+    "\n",
+    "        last  = energy_reading[label]['last']\n",
+    "        delta = value - last\n",
+    "        total = energy_reading[label]['total']\n",
+    "\n",
+    "        energy_reading[label]['last']  = value\n",
+    "        energy_reading[label]['delta'] = delta\n",
+    "        energy_reading[label]['total'] = total + delta\n",
+    "        \n",
+    "        logging.debug('Update %s: %s',\n",
+    "                          label, energy_reading[label])\n",
+    "\n",
+    "    return energy_reading\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Workload configuration"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "# RTApp calibration\n",
+    "\n",
+    "# Uncomment the following line to calibrate RTApp the first time\n",
+    "# target_calibration = RTA.calibrate(target)\n",
+    "\n",
+    "# Provide a pre-defined calibration\n",
+    "target_calibration = {0: 353, 1: 138, 2: 138, 3: 353, 4: 354, 5: 360}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "01:44:51  ERROR   : Assuming taskset is preinstalled\n",
+      "01:44:51  INFO    : Setup new workload simple\n",
+      "01:44:51  DEBUG   : Setup step [postrun] callback to [__postrun] function\n",
+      "01:44:51  DEBUG   : Configuring a profile-based workload...\n",
+      "01:44:51  DEBUG   : ref on big cpu: 1\n",
+      "01:44:51  INFO    : Workload duration defined by longest task\n",
+      "01:44:51  INFO    : ------------------------\n",
+      "01:44:51  INFO    : task [task1], SCHED_OTHER:\n",
+      "01:44:51  INFO    :  | loops count: 1\n",
+      "01:44:51  INFO    :  + phase_000001: duration 5.000000 [s] (50 loops)\n",
+      "01:44:51  INFO    :  |  period   100000 [us], duty_cycle  20 %\n",
+      "01:44:51  INFO    :  |  run_time  20000 [us], sleep_time  80000 [us]\n"
+     ]
+    }
+   ],
+   "source": [
+    "rtapp = RTA(target, 'simple', calibration=target_calibration)\n",
+    "rtapp.conf(\n",
+    "    kind='profile',\n",
+    "    params={\n",
+    "        'task1': RTA.periodic(\n",
+    "            period_ms=100,\n",
+    "            duty_cycle_pct=20,\n",
+    "            duration_s=5)\n",
+    "    },\n",
+    "    run_dir=TARGET_DIR\n",
+    ");"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Workload execution"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "01:44:51  INFO    : Create output folder\n"
+     ]
+    }
+   ],
+   "source": [
+    "logging.info('Create output folder')\n",
+    "os.system('mkdir {}'.format(RESULTS_DIR));"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "01:44:58  INFO    : FTrace STARTED\n",
+      "01:44:59  DEBUG   : Measure: [a53_energy: 11588.717062 joules, a57_energy: 16230.437033 joules]\n",
+      "01:44:59  DEBUG   : Update a53: 11588.717062\n",
+      "01:44:59  DEBUG   : Initialize a53: {'total': 0, 'last': 11588.717062, 'delta': 0}\n",
+      "01:44:59  DEBUG   : Update a57: 16230.437033\n",
+      "01:44:59  DEBUG   : Initialize a57: {'total': 0, 'last': 16230.437033, 'delta': 0}\n",
+      "01:44:59  INFO    : RTApp STARTING...\n",
+      "01:44:59  INFO    : Executor [start]: /root/schedtest/bin/rt-app /root/schedtest/simple_00.json\n",
+      "01:45:04  DEBUG   : Callback [postrun]...\n",
+      "01:45:04  DEBUG   : Pulling logfiles to [/tmp/schedtest]...\n",
+      "01:45:04  DEBUG   : Pulling JSON to [/tmp/schedtest]...\n",
+      "01:45:04  DEBUG   : Saving output on [/tmp/schedtest/output.log]...\n",
+      "01:45:04  INFO    : Executor [end]: /root/schedtest/bin/rt-app /root/schedtest/simple_00.json\n",
+      "01:45:05  DEBUG   : Measure: [a53_energy: 11589.953522 joules, a57_energy: 16240.57781 joules]\n",
+      "01:45:05  DEBUG   : Update a53: 11589.953522\n",
+      "01:45:05  DEBUG   : Update a53: {'total': 1.2364600000000792, 'last': 11589.953522, 'delta': 1.2364600000000792}\n",
+      "01:45:05  DEBUG   : Update a57: 16240.57781\n",
+      "01:45:05  DEBUG   : Update a57: {'total': 10.140777000000526, 'last': 16240.57781, 'delta': 10.140777000000526}\n",
+      "01:45:06  INFO    : FTrace STOPPED\n"
+     ]
+    }
+   ],
+   "source": [
+    "hwmon_reset()\n",
+    "\n",
+    "ftrace_start()\n",
+    "hwmon_sample()\n",
+    "\n",
+    "logging.info('RTApp STARTING...')\n",
+    "rtapp.run(out_dir=RESULTS_DIR)\n",
+    "\n",
+    "nrg = hwmon_sample();\n",
+    "ftrace_stop()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "01:45:09  INFO    : Energy: {\n",
+      "    \"a53\": {\n",
+      "        \"total\": 1.2364600000000792,\n",
+      "        \"last\": 11589.953522,\n",
+      "        \"delta\": 1.2364600000000792\n",
+      "    },\n",
+      "    \"a57\": {\n",
+      "        \"total\": 10.140777000000526,\n",
+      "        \"last\": 16240.57781,\n",
+      "        \"delta\": 10.140777000000526\n",
+      "    }\n",
+      "}\n"
+     ]
+    }
+   ],
+   "source": [
+    "logging.info('Energy: %s',\n",
+    "    json.dumps(nrg,\n",
+    "                indent=4,\n",
+    "                separators=(',', ': ')))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Trace inspection"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<style>\n",
+       "/*\n",
+       "\n",
+       " *    Copyright 2015-2015 ARM Limited\n",
+       "\n",
+       " *\n",
+       "\n",
+       " * Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+       "\n",
+       " * you may not use this file except in compliance with the License.\n",
+       "\n",
+       " * You may obtain a copy of the License at\n",
+       "\n",
+       " *\n",
+       "\n",
+       " *     http://www.apache.org/licenses/LICENSE-2.0\n",
+       "\n",
+       " *\n",
+       "\n",
+       " * Unless required by applicable law or agreed to in writing, software\n",
+       "\n",
+       " * distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+       "\n",
+       " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+       "\n",
+       " * See the License for the specific language governing permissions and\n",
+       "\n",
+       " * limitations under the License.\n",
+       "\n",
+       " */\n",
+       "\n",
+       "\n",
+       "\n",
+       ".d3-tip {\n",
+       "\n",
+       "  line-height: 1;\n",
+       "\n",
+       "  padding: 12px;\n",
+       "\n",
+       "  background: rgba(0, 0, 0, 0.6);\n",
+       "\n",
+       "  color: #fff;\n",
+       "\n",
+       "  border-radius: 2px;\n",
+       "\n",
+       "  position: absolute !important;\n",
+       "\n",
+       "  z-index: 99999;\n",
+       "\n",
+       "}\n",
+       "\n",
+       "\n",
+       "\n",
+       ".d3-tip:after {\n",
+       "\n",
+       "  box-sizing: border-box;\n",
+       "\n",
+       "  pointer-events: none;\n",
+       "\n",
+       "  display: inline;\n",
+       "\n",
+       "  font-size: 10px;\n",
+       "\n",
+       "  width: 100%;\n",
+       "\n",
+       "  line-height: 1;\n",
+       "\n",
+       "  color: rgba(0, 0, 0, 0.6);\n",
+       "\n",
+       "  content: \"\\25BC\";\n",
+       "\n",
+       "  position: absolute !important;\n",
+       "\n",
+       "  z-index: 99999;\n",
+       "\n",
+       "  text-align: center;\n",
+       "\n",
+       "}\n",
+       "\n",
+       "\n",
+       "\n",
+       ".d3-tip.n:after {\n",
+       "\n",
+       "  margin: -1px 0 0 0;\n",
+       "\n",
+       "  top: 100%;\n",
+       "\n",
+       "  left: 0;\n",
+       "\n",
+       "}\n",
+       "\n",
+       "\n",
+       "\n",
+       ".contextRect {\n",
+       "\n",
+       "  fill: lightgray;\n",
+       "\n",
+       "  fill-opacity: 0.5;\n",
+       "\n",
+       "  stroke: black;\n",
+       "\n",
+       "  stroke-width: 1;\n",
+       "\n",
+       "  stroke-opacity: 1;\n",
+       "\n",
+       "  pointer-events: none;\n",
+       "\n",
+       "  shape-rendering: crispEdges;\n",
+       "\n",
+       "}\n",
+       "\n",
+       "\n",
+       "\n",
+       ".chart {\n",
+       "\n",
+       "  shape-rendering: crispEdges;\n",
+       "\n",
+       "}\n",
+       "\n",
+       "\n",
+       "\n",
+       ".mini text {\n",
+       "\n",
+       "  font: 9px sans-serif;\n",
+       "\n",
+       "}\n",
+       "\n",
+       "\n",
+       "\n",
+       ".main text {\n",
+       "\n",
+       "  font: 12px sans-serif;\n",
+       "\n",
+       "}\n",
+       "\n",
+       "\n",
+       "\n",
+       ".axis line, .axis path {\n",
+       "\n",
+       "  stroke: black;\n",
+       "\n",
+       "}\n",
+       "\n",
+       "\n",
+       "\n",
+       ".miniItem {\n",
+       "\n",
+       "  stroke-width: 8;\n",
+       "\n",
+       "}\n",
+       "\n",
+       "\n",
+       "\n",
+       ".brush .extent {\n",
+       "\n",
+       "\n",
+       "\n",
+       "  stroke: #000;\n",
+       "\n",
+       "  fill-opacity: .125;\n",
+       "\n",
+       "  shape-rendering: crispEdges;\n",
+       "\n",
+       "}\n",
+       "\n",
+       "</style>\n",
+       "<div id=\"fig_2c57a58c5b13455cad0d583885641163\" class=\"eventplot\">\n",
+       "        <script>\n",
+       "            var req = require.config( {\n",
+       "\n",
+       "                paths: {\n",
+       "\n",
+       "                    \"EventPlot\": '/nbextensions/plotter_scripts/EventPlot/EventPlot',\n",
+       "                    \"d3-tip\": '/nbextensions/plotter_scripts/EventPlot/d3.tip.v0.6.3',\n",
+       "                    \"d3-plotter\": '/nbextensions/plotter_scripts/EventPlot/d3.v3.min'\n",
+       "                },\n",
+       "                shim: {\n",
+       "                    \"d3-plotter\" : {\n",
+       "                        \"exports\" : \"d3\"\n",
+       "                    },\n",
+       "                    \"d3-tip\": [\"d3-plotter\"],\n",
+       "                    \"EventPlot\": {\n",
+       "\n",
+       "                        \"deps\": [\"d3-tip\", \"d3-plotter\" ],\n",
+       "                        \"exports\":  \"EventPlot\"\n",
+       "                    }\n",
+       "                }\n",
+       "            });\n",
+       "            req([\"require\", \"EventPlot\"], function() {\n",
+       "               EventPlot.generate('fig_2c57a58c5b13455cad0d583885641163', '/nbextensions/');\n",
+       "            });\n",
+       "        </script>\n",
+       "        </div>"
+      ],
+      "text/plain": [
+       "<IPython.core.display.HTML object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "trappy.plotter.plot_trace(RESULTS_DIR)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 2",
+   "language": "python",
+   "name": "python2"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 2
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython2",
+   "version": "2.7.9"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}