diff --git a/experiments/test_notebook.ipynb b/experiments/test_notebook.ipynb
index bac465598e8943cf46f8651c520ecc9a6c7ebd46..1b9515f3202b1b6028f65ae6c4b7b2026653999d 100644
--- a/experiments/test_notebook.ipynb
+++ b/experiments/test_notebook.ipynb
@@ -26,7 +26,7 @@
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "/tmp/ipykernel_2455/4095267831.py:5: DeprecationWarning: Importing display from IPython.core.display is deprecated since IPython 7.14, please import from IPython display\n",
+      "/tmp/ipykernel_8229/4095267831.py:5: DeprecationWarning: Importing display from IPython.core.display is deprecated since IPython 7.14, please import from IPython display\n",
       "  from IPython.core.display import display, HTML\n"
      ]
     }
@@ -60,287 +60,8 @@
     },
     {
      "data": {
-      "application/javascript": [
-       "(function(root) {\n",
-       "  function now() {\n",
-       "    return new Date();\n",
-       "  }\n",
-       "\n",
-       "  const force = true;\n",
-       "\n",
-       "  if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n",
-       "    root._bokeh_onload_callbacks = [];\n",
-       "    root._bokeh_is_loading = undefined;\n",
-       "  }\n",
-       "\n",
-       "const JS_MIME_TYPE = 'application/javascript';\n",
-       "  const HTML_MIME_TYPE = 'text/html';\n",
-       "  const EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n",
-       "  const CLASS_NAME = 'output_bokeh rendered_html';\n",
-       "\n",
-       "  /**\n",
-       "   * Render data to the DOM node\n",
-       "   */\n",
-       "  function render(props, node) {\n",
-       "    const script = document.createElement(\"script\");\n",
-       "    node.appendChild(script);\n",
-       "  }\n",
-       "\n",
-       "  /**\n",
-       "   * Handle when an output is cleared or removed\n",
-       "   */\n",
-       "  function handleClearOutput(event, handle) {\n",
-       "    const cell = handle.cell;\n",
-       "\n",
-       "    const id = cell.output_area._bokeh_element_id;\n",
-       "    const server_id = cell.output_area._bokeh_server_id;\n",
-       "    // Clean up Bokeh references\n",
-       "    if (id != null && id in Bokeh.index) {\n",
-       "      Bokeh.index[id].model.document.clear();\n",
-       "      delete Bokeh.index[id];\n",
-       "    }\n",
-       "\n",
-       "    if (server_id !== undefined) {\n",
-       "      // Clean up Bokeh references\n",
-       "      const cmd_clean = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n",
-       "      cell.notebook.kernel.execute(cmd_clean, {\n",
-       "        iopub: {\n",
-       "          output: function(msg) {\n",
-       "            const id = msg.content.text.trim();\n",
-       "            if (id in Bokeh.index) {\n",
-       "              Bokeh.index[id].model.document.clear();\n",
-       "              delete Bokeh.index[id];\n",
-       "            }\n",
-       "          }\n",
-       "        }\n",
-       "      });\n",
-       "      // Destroy server and session\n",
-       "      const cmd_destroy = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n",
-       "      cell.notebook.kernel.execute(cmd_destroy);\n",
-       "    }\n",
-       "  }\n",
-       "\n",
-       "  /**\n",
-       "   * Handle when a new output is added\n",
-       "   */\n",
-       "  function handleAddOutput(event, handle) {\n",
-       "    const output_area = handle.output_area;\n",
-       "    const output = handle.output;\n",
-       "\n",
-       "    // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n",
-       "    if ((output.output_type != \"display_data\") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {\n",
-       "      return\n",
-       "    }\n",
-       "\n",
-       "    const toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n",
-       "\n",
-       "    if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n",
-       "      toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n",
-       "      // store reference to embed id on output_area\n",
-       "      output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n",
-       "    }\n",
-       "    if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n",
-       "      const bk_div = document.createElement(\"div\");\n",
-       "      bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n",
-       "      const script_attrs = bk_div.children[0].attributes;\n",
-       "      for (let i = 0; i < script_attrs.length; i++) {\n",
-       "        toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n",
-       "        toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent\n",
-       "      }\n",
-       "      // store reference to server id on output_area\n",
-       "      output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n",
-       "    }\n",
-       "  }\n",
-       "\n",
-       "  function register_renderer(events, OutputArea) {\n",
-       "\n",
-       "    function append_mime(data, metadata, element) {\n",
-       "      // create a DOM node to render to\n",
-       "      const toinsert = this.create_output_subarea(\n",
-       "        metadata,\n",
-       "        CLASS_NAME,\n",
-       "        EXEC_MIME_TYPE\n",
-       "      );\n",
-       "      this.keyboard_manager.register_events(toinsert);\n",
-       "      // Render to node\n",
-       "      const props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n",
-       "      render(props, toinsert[toinsert.length - 1]);\n",
-       "      element.append(toinsert);\n",
-       "      return toinsert\n",
-       "    }\n",
-       "\n",
-       "    /* Handle when an output is cleared or removed */\n",
-       "    events.on('clear_output.CodeCell', handleClearOutput);\n",
-       "    events.on('delete.Cell', handleClearOutput);\n",
-       "\n",
-       "    /* Handle when a new output is added */\n",
-       "    events.on('output_added.OutputArea', handleAddOutput);\n",
-       "\n",
-       "    /**\n",
-       "     * Register the mime type and append_mime function with output_area\n",
-       "     */\n",
-       "    OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n",
-       "      /* Is output safe? */\n",
-       "      safe: true,\n",
-       "      /* Index of renderer in `output_area.display_order` */\n",
-       "      index: 0\n",
-       "    });\n",
-       "  }\n",
-       "\n",
-       "  // register the mime type if in Jupyter Notebook environment and previously unregistered\n",
-       "  if (root.Jupyter !== undefined) {\n",
-       "    const events = require('base/js/events');\n",
-       "    const OutputArea = require('notebook/js/outputarea').OutputArea;\n",
-       "\n",
-       "    if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n",
-       "      register_renderer(events, OutputArea);\n",
-       "    }\n",
-       "  }\n",
-       "  if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n",
-       "    root._bokeh_timeout = Date.now() + 5000;\n",
-       "    root._bokeh_failed_load = false;\n",
-       "  }\n",
-       "\n",
-       "  const NB_LOAD_WARNING = {'data': {'text/html':\n",
-       "     \"<div style='background-color: #fdd'>\\n\"+\n",
-       "     \"<p>\\n\"+\n",
-       "     \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n",
-       "     \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n",
-       "     \"</p>\\n\"+\n",
-       "     \"<ul>\\n\"+\n",
-       "     \"<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n\"+\n",
-       "     \"<li>use INLINE resources instead, as so:</li>\\n\"+\n",
-       "     \"</ul>\\n\"+\n",
-       "     \"<code>\\n\"+\n",
-       "     \"from bokeh.resources import INLINE\\n\"+\n",
-       "     \"output_notebook(resources=INLINE)\\n\"+\n",
-       "     \"</code>\\n\"+\n",
-       "     \"</div>\"}};\n",
-       "\n",
-       "  function display_loaded() {\n",
-       "    const el = document.getElementById(\"1001\");\n",
-       "    if (el != null) {\n",
-       "      el.textContent = \"BokehJS is loading...\";\n",
-       "    }\n",
-       "    if (root.Bokeh !== undefined) {\n",
-       "      if (el != null) {\n",
-       "        el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n",
-       "      }\n",
-       "    } else if (Date.now() < root._bokeh_timeout) {\n",
-       "      setTimeout(display_loaded, 100)\n",
-       "    }\n",
-       "  }\n",
-       "\n",
-       "  function run_callbacks() {\n",
-       "    try {\n",
-       "      root._bokeh_onload_callbacks.forEach(function(callback) {\n",
-       "        if (callback != null)\n",
-       "          callback();\n",
-       "      });\n",
-       "    } finally {\n",
-       "      delete root._bokeh_onload_callbacks\n",
-       "    }\n",
-       "    console.debug(\"Bokeh: all callbacks have finished\");\n",
-       "  }\n",
-       "\n",
-       "  function load_libs(css_urls, js_urls, callback) {\n",
-       "    if (css_urls == null) css_urls = [];\n",
-       "    if (js_urls == null) js_urls = [];\n",
-       "\n",
-       "    root._bokeh_onload_callbacks.push(callback);\n",
-       "    if (root._bokeh_is_loading > 0) {\n",
-       "      console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n",
-       "      return null;\n",
-       "    }\n",
-       "    if (js_urls == null || js_urls.length === 0) {\n",
-       "      run_callbacks();\n",
-       "      return null;\n",
-       "    }\n",
-       "    console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n",
-       "    root._bokeh_is_loading = css_urls.length + js_urls.length;\n",
-       "\n",
-       "    function on_load() {\n",
-       "      root._bokeh_is_loading--;\n",
-       "      if (root._bokeh_is_loading === 0) {\n",
-       "        console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n",
-       "        run_callbacks()\n",
-       "      }\n",
-       "    }\n",
-       "\n",
-       "    function on_error(url) {\n",
-       "      console.error(\"failed to load \" + url);\n",
-       "    }\n",
-       "\n",
-       "    for (let i = 0; i < css_urls.length; i++) {\n",
-       "      const url = css_urls[i];\n",
-       "      const element = document.createElement(\"link\");\n",
-       "      element.onload = on_load;\n",
-       "      element.onerror = on_error.bind(null, url);\n",
-       "      element.rel = \"stylesheet\";\n",
-       "      element.type = \"text/css\";\n",
-       "      element.href = url;\n",
-       "      console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n",
-       "      document.body.appendChild(element);\n",
-       "    }\n",
-       "\n",
-       "    for (let i = 0; i < js_urls.length; i++) {\n",
-       "      const url = js_urls[i];\n",
-       "      const element = document.createElement('script');\n",
-       "      element.onload = on_load;\n",
-       "      element.onerror = on_error.bind(null, url);\n",
-       "      element.async = false;\n",
-       "      element.src = url;\n",
-       "      console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
-       "      document.head.appendChild(element);\n",
-       "    }\n",
-       "  };\n",
-       "\n",
-       "  function inject_raw_css(css) {\n",
-       "    const element = document.createElement(\"style\");\n",
-       "    element.appendChild(document.createTextNode(css));\n",
-       "    document.body.appendChild(element);\n",
-       "  }\n",
-       "\n",
-       "  const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.3.min.js\"];\n",
-       "  const css_urls = [];\n",
-       "\n",
-       "  const inline_js = [    function(Bokeh) {\n",
-       "      Bokeh.set_log_level(\"info\");\n",
-       "    },\n",
-       "function(Bokeh) {\n",
-       "    }\n",
-       "  ];\n",
-       "\n",
-       "  function run_inline_js() {\n",
-       "    if (root.Bokeh !== undefined || force === true) {\n",
-       "          for (let i = 0; i < inline_js.length; i++) {\n",
-       "      inline_js[i].call(root, root.Bokeh);\n",
-       "    }\n",
-       "if (force === true) {\n",
-       "        display_loaded();\n",
-       "      }} else if (Date.now() < root._bokeh_timeout) {\n",
-       "      setTimeout(run_inline_js, 100);\n",
-       "    } else if (!root._bokeh_failed_load) {\n",
-       "      console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n",
-       "      root._bokeh_failed_load = true;\n",
-       "    } else if (force !== true) {\n",
-       "      const cell = $(document.getElementById(\"1001\")).parents('.cell').data().cell;\n",
-       "      cell.output_area.append_execute_result(NB_LOAD_WARNING)\n",
-       "    }\n",
-       "  }\n",
-       "\n",
-       "  if (root._bokeh_is_loading === 0) {\n",
-       "    console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n",
-       "    run_inline_js();\n",
-       "  } else {\n",
-       "    load_libs(css_urls, js_urls, function() {\n",
-       "      console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n",
-       "      run_inline_js();\n",
-       "    });\n",
-       "  }\n",
-       "}(window));"
-      ],
-      "application/vnd.bokehjs_load.v0+json": "(function(root) {\n  function now() {\n    return new Date();\n  }\n\n  const force = true;\n\n  if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n    root._bokeh_onload_callbacks = [];\n    root._bokeh_is_loading = undefined;\n  }\n\n\n  if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n    root._bokeh_timeout = Date.now() + 5000;\n    root._bokeh_failed_load = false;\n  }\n\n  const NB_LOAD_WARNING = {'data': {'text/html':\n     \"<div style='background-color: #fdd'>\\n\"+\n     \"<p>\\n\"+\n     \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n     \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n     \"</p>\\n\"+\n     \"<ul>\\n\"+\n     \"<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n\"+\n     \"<li>use INLINE resources instead, as so:</li>\\n\"+\n     \"</ul>\\n\"+\n     \"<code>\\n\"+\n     \"from bokeh.resources import INLINE\\n\"+\n     \"output_notebook(resources=INLINE)\\n\"+\n     \"</code>\\n\"+\n     \"</div>\"}};\n\n  function display_loaded() {\n    const el = document.getElementById(\"1001\");\n    if (el != null) {\n      el.textContent = \"BokehJS is loading...\";\n    }\n    if (root.Bokeh !== undefined) {\n      if (el != null) {\n        el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n      }\n    } else if (Date.now() < root._bokeh_timeout) {\n      setTimeout(display_loaded, 100)\n    }\n  }\n\n  function run_callbacks() {\n    try {\n      root._bokeh_onload_callbacks.forEach(function(callback) {\n        if (callback != null)\n          callback();\n      });\n    } finally {\n      delete root._bokeh_onload_callbacks\n    }\n    console.debug(\"Bokeh: all callbacks have finished\");\n  }\n\n  function load_libs(css_urls, js_urls, callback) {\n    if (css_urls == null) css_urls = [];\n    if (js_urls == null) js_urls = [];\n\n    root._bokeh_onload_callbacks.push(callback);\n    if (root._bokeh_is_loading > 0) {\n      console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n      return null;\n    }\n    if (js_urls == null || js_urls.length === 0) {\n      run_callbacks();\n      return null;\n    }\n    console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n    root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n    function on_load() {\n      root._bokeh_is_loading--;\n      if (root._bokeh_is_loading === 0) {\n        console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n        run_callbacks()\n      }\n    }\n\n    function on_error(url) {\n      console.error(\"failed to load \" + url);\n    }\n\n    for (let i = 0; i < css_urls.length; i++) {\n      const url = css_urls[i];\n      const element = document.createElement(\"link\");\n      element.onload = on_load;\n      element.onerror = on_error.bind(null, url);\n      element.rel = \"stylesheet\";\n      element.type = \"text/css\";\n      element.href = url;\n      console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n      document.body.appendChild(element);\n    }\n\n    for (let i = 0; i < js_urls.length; i++) {\n      const url = js_urls[i];\n      const element = document.createElement('script');\n      element.onload = on_load;\n      element.onerror = on_error.bind(null, url);\n      element.async = false;\n      element.src = url;\n      console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n      document.head.appendChild(element);\n    }\n  };\n\n  function inject_raw_css(css) {\n    const element = document.createElement(\"style\");\n    element.appendChild(document.createTextNode(css));\n    document.body.appendChild(element);\n  }\n\n  const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.3.min.js\"];\n  const css_urls = [];\n\n  const inline_js = [    function(Bokeh) {\n      Bokeh.set_log_level(\"info\");\n    },\nfunction(Bokeh) {\n    }\n  ];\n\n  function run_inline_js() {\n    if (root.Bokeh !== undefined || force === true) {\n          for (let i = 0; i < inline_js.length; i++) {\n      inline_js[i].call(root, root.Bokeh);\n    }\nif (force === true) {\n        display_loaded();\n      }} else if (Date.now() < root._bokeh_timeout) {\n      setTimeout(run_inline_js, 100);\n    } else if (!root._bokeh_failed_load) {\n      console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n      root._bokeh_failed_load = true;\n    } else if (force !== true) {\n      const cell = $(document.getElementById(\"1001\")).parents('.cell').data().cell;\n      cell.output_area.append_execute_result(NB_LOAD_WARNING)\n    }\n  }\n\n  if (root._bokeh_is_loading === 0) {\n    console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n    run_inline_js();\n  } else {\n    load_libs(css_urls, js_urls, function() {\n      console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n      run_inline_js();\n    });\n  }\n}(window));"
+      "application/javascript": "(function(root) {\n  function now() {\n    return new Date();\n  }\n\n  const force = true;\n\n  if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n    root._bokeh_onload_callbacks = [];\n    root._bokeh_is_loading = undefined;\n  }\n\nconst JS_MIME_TYPE = 'application/javascript';\n  const HTML_MIME_TYPE = 'text/html';\n  const EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n  const CLASS_NAME = 'output_bokeh rendered_html';\n\n  /**\n   * Render data to the DOM node\n   */\n  function render(props, node) {\n    const script = document.createElement(\"script\");\n    node.appendChild(script);\n  }\n\n  /**\n   * Handle when an output is cleared or removed\n   */\n  function handleClearOutput(event, handle) {\n    const cell = handle.cell;\n\n    const id = cell.output_area._bokeh_element_id;\n    const server_id = cell.output_area._bokeh_server_id;\n    // Clean up Bokeh references\n    if (id != null && id in Bokeh.index) {\n      Bokeh.index[id].model.document.clear();\n      delete Bokeh.index[id];\n    }\n\n    if (server_id !== undefined) {\n      // Clean up Bokeh references\n      const cmd_clean = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n      cell.notebook.kernel.execute(cmd_clean, {\n        iopub: {\n          output: function(msg) {\n            const id = msg.content.text.trim();\n            if (id in Bokeh.index) {\n              Bokeh.index[id].model.document.clear();\n              delete Bokeh.index[id];\n            }\n          }\n        }\n      });\n      // Destroy server and session\n      const cmd_destroy = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n      cell.notebook.kernel.execute(cmd_destroy);\n    }\n  }\n\n  /**\n   * Handle when a new output is added\n   */\n  function handleAddOutput(event, handle) {\n    const output_area = handle.output_area;\n    const output = handle.output;\n\n    // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n    if ((output.output_type != \"display_data\") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {\n      return\n    }\n\n    const toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n\n    if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n      toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n      // store reference to embed id on output_area\n      output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n    }\n    if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n      const bk_div = document.createElement(\"div\");\n      bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n      const script_attrs = bk_div.children[0].attributes;\n      for (let i = 0; i < script_attrs.length; i++) {\n        toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n        toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent\n      }\n      // store reference to server id on output_area\n      output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n    }\n  }\n\n  function register_renderer(events, OutputArea) {\n\n    function append_mime(data, metadata, element) {\n      // create a DOM node to render to\n      const toinsert = this.create_output_subarea(\n        metadata,\n        CLASS_NAME,\n        EXEC_MIME_TYPE\n      );\n      this.keyboard_manager.register_events(toinsert);\n      // Render to node\n      const props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n      render(props, toinsert[toinsert.length - 1]);\n      element.append(toinsert);\n      return toinsert\n    }\n\n    /* Handle when an output is cleared or removed */\n    events.on('clear_output.CodeCell', handleClearOutput);\n    events.on('delete.Cell', handleClearOutput);\n\n    /* Handle when a new output is added */\n    events.on('output_added.OutputArea', handleAddOutput);\n\n    /**\n     * Register the mime type and append_mime function with output_area\n     */\n    OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n      /* Is output safe? */\n      safe: true,\n      /* Index of renderer in `output_area.display_order` */\n      index: 0\n    });\n  }\n\n  // register the mime type if in Jupyter Notebook environment and previously unregistered\n  if (root.Jupyter !== undefined) {\n    const events = require('base/js/events');\n    const OutputArea = require('notebook/js/outputarea').OutputArea;\n\n    if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n      register_renderer(events, OutputArea);\n    }\n  }\n  if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n    root._bokeh_timeout = Date.now() + 5000;\n    root._bokeh_failed_load = false;\n  }\n\n  const NB_LOAD_WARNING = {'data': {'text/html':\n     \"<div style='background-color: #fdd'>\\n\"+\n     \"<p>\\n\"+\n     \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n     \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n     \"</p>\\n\"+\n     \"<ul>\\n\"+\n     \"<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n\"+\n     \"<li>use INLINE resources instead, as so:</li>\\n\"+\n     \"</ul>\\n\"+\n     \"<code>\\n\"+\n     \"from bokeh.resources import INLINE\\n\"+\n     \"output_notebook(resources=INLINE)\\n\"+\n     \"</code>\\n\"+\n     \"</div>\"}};\n\n  function display_loaded() {\n    const el = document.getElementById(\"1001\");\n    if (el != null) {\n      el.textContent = \"BokehJS is loading...\";\n    }\n    if (root.Bokeh !== undefined) {\n      if (el != null) {\n        el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n      }\n    } else if (Date.now() < root._bokeh_timeout) {\n      setTimeout(display_loaded, 100)\n    }\n  }\n\n  function run_callbacks() {\n    try {\n      root._bokeh_onload_callbacks.forEach(function(callback) {\n        if (callback != null)\n          callback();\n      });\n    } finally {\n      delete root._bokeh_onload_callbacks\n    }\n    console.debug(\"Bokeh: all callbacks have finished\");\n  }\n\n  function load_libs(css_urls, js_urls, callback) {\n    if (css_urls == null) css_urls = [];\n    if (js_urls == null) js_urls = [];\n\n    root._bokeh_onload_callbacks.push(callback);\n    if (root._bokeh_is_loading > 0) {\n      console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n      return null;\n    }\n    if (js_urls == null || js_urls.length === 0) {\n      run_callbacks();\n      return null;\n    }\n    console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n    root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n    function on_load() {\n      root._bokeh_is_loading--;\n      if (root._bokeh_is_loading === 0) {\n        console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n        run_callbacks()\n      }\n    }\n\n    function on_error(url) {\n      console.error(\"failed to load \" + url);\n    }\n\n    for (let i = 0; i < css_urls.length; i++) {\n      const url = css_urls[i];\n      const element = document.createElement(\"link\");\n      element.onload = on_load;\n      element.onerror = on_error.bind(null, url);\n      element.rel = \"stylesheet\";\n      element.type = \"text/css\";\n      element.href = url;\n      console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n      document.body.appendChild(element);\n    }\n\n    for (let i = 0; i < js_urls.length; i++) {\n      const url = js_urls[i];\n      const element = document.createElement('script');\n      element.onload = on_load;\n      element.onerror = on_error.bind(null, url);\n      element.async = false;\n      element.src = url;\n      console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n      document.head.appendChild(element);\n    }\n  };\n\n  function inject_raw_css(css) {\n    const element = document.createElement(\"style\");\n    element.appendChild(document.createTextNode(css));\n    document.body.appendChild(element);\n  }\n\n  const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.3.min.js\"];\n  const css_urls = [];\n\n  const inline_js = [    function(Bokeh) {\n      Bokeh.set_log_level(\"info\");\n    },\nfunction(Bokeh) {\n    }\n  ];\n\n  function run_inline_js() {\n    if (root.Bokeh !== undefined || force === true) {\n          for (let i = 0; i < inline_js.length; i++) {\n      inline_js[i].call(root, root.Bokeh);\n    }\nif (force === true) {\n        display_loaded();\n      }} else if (Date.now() < root._bokeh_timeout) {\n      setTimeout(run_inline_js, 100);\n    } else if (!root._bokeh_failed_load) {\n      console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n      root._bokeh_failed_load = true;\n    } else if (force !== true) {\n      const cell = $(document.getElementById(\"1001\")).parents('.cell').data().cell;\n      cell.output_area.append_execute_result(NB_LOAD_WARNING)\n    }\n  }\n\n  if (root._bokeh_is_loading === 0) {\n    console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n    run_inline_js();\n  } else {\n    load_libs(css_urls, js_urls, function() {\n      console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n      run_inline_js();\n    });\n  }\n}(window));",
+      "application/vnd.bokehjs_load.v0+json": ""
      },
      "metadata": {},
      "output_type": "display_data"
@@ -394,7 +115,7 @@
     {
      "data": {
       "text/plain": [
-       "'/host_home/Repos/nuplan-devkit/experiments'"
+       "'/home/sacardoz/nuplan-devkit/experiments'"
       ]
      },
      "execution_count": 3,
@@ -406,26 +127,6 @@
     "%pwd"
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": 4,
-   "id": "c8b59d7c",
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "env: NUPLAN_DATA_ROOT=/home/ehdykhne/Repos/Datasets/nuplan/dataset\n",
-      "env: NUPLAN_MAPS_ROOT=/home/ehdykhne/Repos/Datasets/nuplan/dataset/maps\n"
-     ]
-    }
-   ],
-   "source": [
-    "%env NUPLAN_DATA_ROOT=/home/ehdykhne/Repos/Datasets/nuplan/dataset\n",
-    "%env NUPLAN_MAPS_ROOT=/home/ehdykhne/Repos/Datasets/nuplan/dataset/maps"
-   ]
-  },
   {
    "cell_type": "markdown",
    "id": "db337ceb",
@@ -436,7 +137,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 4,
    "id": "11b08c6d",
    "metadata": {},
    "outputs": [],
@@ -455,7 +156,7 @@
     "    \"scenario_filter.log_names=['2021.06.14.16.48.02_veh-12_04057_04438']\",\n",
     "    'scenario_filter.limit_total_scenarios=1',  # use 1 total scenarios\n",
     "]\n",
-    "ckpt_dir = '/home/ehdykhne/Repos/nuplan-devkit/experiments/pretrained_checkpoints/pdm_offset_checkpoint.ckpt'\n",
+    "ckpt_dir = '/home/sacardoz/checkpoints/urbandriver_checkpoint.ckpt'\n",
     "#'/home/sacardoz/checkpoints/urbandriver_checkpoint.ckpt'\n",
     "#\"/home/sacardoz/tutorial_vector_framework/training_simple_vector_experiment/train_default_simple_vector/2023.11.23.09.55.21/best_model/epoch.ckpt\"\n",
     "#\"/home/sacardoz/training_raster_experiment/train_default_raster/2023.11.23.07.36.36/best_model/epoch.ckpt\"\n",
@@ -465,21 +166,20 @@
     "\n",
     "# Compose the configuration\n",
     "cfg = hydra.compose(config_name=simulation_hydra_paths.config_name, overrides=[\n",
-    "    '+simulation=closed_loop_reactive_agents',\n",
-    "    #'model=pgm_hybrid_model',\n",
-    "    'planner=pdm_hybrid_planner',\n",
-    "    f\"planner.pdm_hybrid_planner.checkpoint_path={ckpt_dir}\" ,\n",
-    "    #'planner.ml_planner.model_config=${model}',\n",
-    "    #f'planner.ml_planner.checkpoint_path={ckpt_dir}',\n",
-    "    #f'observation=idm_agents_observation',\n",
-    "    #'observation.model_config=${model}',\n",
-    "    #f'observation.checkpoint_path={ckpt_dir}',\n",
+    "    '+simulation=closed_loop_multiagent',\n",
+    "    'model=urban_driver_open_loop_model',\n",
+    "    'planner=ml_planner',\n",
+    "    #f\"planner.pdm_hybrid_planner.checkpoint_path={ckpt_dir}\" ,\n",
+    "    'planner.ml_planner.model_config=${model}',\n",
+    "    f'planner.ml_planner.checkpoint_path={ckpt_dir}',\n",
+    "    'observation.model_config=${model}',\n",
+    "    f'observation.checkpoint_path={ckpt_dir}',\n",
     "    'worker=sequential',\n",
     "    '+occlusion=true',\n",
-    "    '+occlusion.manager_type=wedge', #options: [range, shadow, wedge]\n",
-    "    \"hydra.searchpath=[pkg://tuplan_garage.planning.script.config.common, pkg://tuplan_garage.planning.script.config.simulation, pkg://nuplan.planning.script.config.common, pkg://nuplan.planning.script.experiments]\",\n",
+    "    '+occlusion.manager_type=shadow', #options: [range, shadow, wedge]\n",
+    "    \"hydra.searchpath=[pkg://nuplan_garage.planning.script.config.common, pkg://nuplan_garage.planning.script.config.simulation, pkg://nuplan.planning.script.config.common, pkg://nuplan.planning.script.experiments]\",\n",
     "    *DATASET_PARAMS,\n",
-    "])"
+    "])\n"
    ]
   },
   {
@@ -492,7 +192,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 5,
    "id": "161cc166",
    "metadata": {
     "scrolled": true
@@ -502,6 +202,8 @@
      "name": "stderr",
      "output_type": "stream",
      "text": [
+      "INFO:nuplan.planning.script.utils:Setting default NUPLAN_DATA_ROOT: /home/sacardoz/nuplan/dataset\n",
+      "INFO:nuplan.planning.script.utils:Setting default NUPLAN_EXP_ROOT: /home/sacardoz/nuplan/exp\n",
       "Global seed set to 0\n",
       "INFO:nuplan.planning.script.builders.main_callback_builder:Building MultiMainCallback...\n",
       "INFO:nuplan.planning.script.builders.main_callback_builder:Building MultiMainCallback: 4...DONE!\n"
@@ -511,32 +213,36 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "2023-12-04 23:36:26,261 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/worker_pool_builder.py:19}  Building WorkerPool...\n",
-      "2023-12-04 23:36:26,263 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/utils/multithreading/worker_pool.py:101}  Worker: Sequential\n",
-      "2023-12-04 23:36:26,263 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/utils/multithreading/worker_pool.py:102}  Number of nodes: 1\n",
+      "2023-12-05 02:14:01,684 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/worker_pool_builder.py:19}  Building WorkerPool...\n",
+      "2023-12-05 02:14:01,685 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/utils/multithreading/worker_pool.py:101}  Worker: Sequential\n",
+      "2023-12-05 02:14:01,685 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/utils/multithreading/worker_pool.py:102}  Number of nodes: 1\n",
       "Number of CPUs per node: 1\n",
       "Number of GPUs per node: 0\n",
       "Number of threads across all nodes: 1\n",
-      "2023-12-04 23:36:26,263 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/worker_pool_builder.py:27}  Building WorkerPool...DONE!\n",
-      "2023-12-04 23:36:26,263 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:32}  Building experiment folders...\n",
-      "2023-12-04 23:36:26,263 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:35}  \n",
+      "2023-12-05 02:14:01,685 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/worker_pool_builder.py:27}  Building WorkerPool...DONE!\n",
+      "2023-12-05 02:14:01,685 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:32}  Building experiment folders...\n",
+      "2023-12-05 02:14:01,685 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:35}  \n",
       "\n",
-      "\tFolder where all results are stored: /home/ehdykhne/Repos/Datasets/nuplan/exp/exp/simulation/closed_loop_reactive_agents/2023.12.04.23.36.25\n",
+      "\tFolder where all results are stored: /home/sacardoz/nuplan/exp/exp/simulation/closed_loop_multiagent/2023.12.05.02.14.01\n",
       "\n",
-      "2023-12-04 23:36:26,293 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:70}  Building experiment folders...DONE!\n",
-      "2023-12-04 23:36:26,293 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_callback_builder.py:52}  Building AbstractCallback...\n",
-      "2023-12-04 23:36:26,293 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_callback_builder.py:68}  Building AbstractCallback: 0...DONE!\n",
-      "2023-12-04 23:36:26,293 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:49}  Building simulations...\n",
-      "2023-12-04 23:36:26,293 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:55}  Extracting scenarios...\n",
-      "2023-12-04 23:36:26,294 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/common/utils/distributed_scenario_filter.py:83}  Building Scenarios in mode DistributedMode.SINGLE_NODE\n",
-      "2023-12-04 23:36:26,294 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/scenario_building_builder.py:18}  Building AbstractScenarioBuilder...\n",
-      "2023-12-04 23:36:26,305 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/scenario_building_builder.py:21}  Building AbstractScenarioBuilder...DONE!\n",
-      "2023-12-04 23:36:26,305 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/scenario_filter_builder.py:35}  Building ScenarioFilter...\n",
-      "2023-12-04 23:36:26,306 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/scenario_filter_builder.py:44}  Building ScenarioFilter...DONE!\n",
-      "2023-12-04 23:36:26,381 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:76}  Building metric engines...\n",
-      "2023-12-04 23:36:26,403 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:78}  Building metric engines...DONE\n",
-      "2023-12-04 23:36:26,403 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:82}  Building simulations from 1 scenarios...\n",
-      "2023-12-04 23:36:26,979 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:142}  Building simulations...DONE!\n"
+      "2023-12-05 02:14:01,687 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:70}  Building experiment folders...DONE!\n",
+      "2023-12-05 02:14:01,687 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_callback_builder.py:52}  Building AbstractCallback...\n",
+      "2023-12-05 02:14:01,687 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_callback_builder.py:68}  Building AbstractCallback: 0...DONE!\n",
+      "2023-12-05 02:14:01,687 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:49}  Building simulations...\n",
+      "2023-12-05 02:14:01,687 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:55}  Extracting scenarios...\n",
+      "2023-12-05 02:14:01,687 INFO {/home/sacardoz/nuplan-devkit/nuplan/common/utils/distributed_scenario_filter.py:83}  Building Scenarios in mode DistributedMode.SINGLE_NODE\n",
+      "2023-12-05 02:14:01,687 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_building_builder.py:18}  Building AbstractScenarioBuilder...\n",
+      "2023-12-05 02:14:01,700 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_building_builder.py:21}  Building AbstractScenarioBuilder...DONE!\n",
+      "2023-12-05 02:14:01,700 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_filter_builder.py:35}  Building ScenarioFilter...\n",
+      "2023-12-05 02:14:01,701 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_filter_builder.py:44}  Building ScenarioFilter...DONE!\n",
+      "2023-12-05 02:14:01,719 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:76}  Building metric engines...\n",
+      "2023-12-05 02:14:01,744 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:78}  Building metric engines...DONE\n",
+      "2023-12-05 02:14:01,744 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:82}  Building simulations from 1 scenarios...\n",
+      "2023-12-05 02:14:01,746 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/model_builder.py:18}  Building TorchModuleWrapper...\n",
+      "2023-12-05 02:14:01,787 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/model_builder.py:21}  Building TorchModuleWrapper...DONE!\n",
+      "2023-12-05 02:14:01,912 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/model_builder.py:18}  Building TorchModuleWrapper...\n",
+      "2023-12-05 02:14:01,956 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/model_builder.py:21}  Building TorchModuleWrapper...DONE!\n",
+      "2023-12-05 02:14:02,577 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:142}  Building simulations...DONE!\n"
      ]
     }
    ],
@@ -550,7 +256,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 6,
    "id": "223284d4",
    "metadata": {},
    "outputs": [],
@@ -558,193 +264,186 @@
     "runner = runners[0]"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "907ad940",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from nuplan.common.actor_state.agent import Agent\n",
+    "from nuplan.common.actor_state.oriented_box import OrientedBox\n",
+    "from nuplan.common.actor_state.scene_object import SceneObjectMetadata\n",
+    "from nuplan.common.actor_state.state_representation import StateSE2, StateVector2D\n",
+    "\n",
+    "inserted_agent = Agent(tracked_object_type=TrackedObjectType.VEHICLE,\n",
+    "                       oriented_box=OrientedBox(StateSE2(664433, 3997400, 3 * 3.14 / 2), 5, 2, 2),\n",
+    "                       velocity=StateVector2D(0,0),\n",
+    "                       metadata=SceneObjectMetadata(1623707858950113, \"inserted\", -2, \"inserted\"),\n",
+    "                       angular_velocity=0.0)\n",
+    "\n",
+    "inserted_goal = StateSE2(664433, 3997000, 3 * 3.14 / 2)"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 8,
    "id": "90b79421",
    "metadata": {},
    "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "                                                                                      \r"
-     ]
-    },
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "elapsed time: 0.028219938278198242\n",
-      "elapsed time: 0.02777719497680664\n",
-      "elapsed time: 0.02846693992614746\n",
-      "elapsed time: 0.0270230770111084\n",
-      "elapsed time: 0.027086973190307617\n",
-      "elapsed time: 0.02704000473022461\n",
-      "elapsed time: 0.02687692642211914\n",
-      "elapsed time: 0.02667236328125\n",
-      "elapsed time: 0.027269363403320312\n",
-      "elapsed time: 0.026731491088867188\n",
-      "elapsed time: 0.02642965316772461\n",
-      "elapsed time: 0.02652263641357422\n",
-      "elapsed time: 0.026285171508789062\n",
-      "elapsed time: 0.026721715927124023\n",
-      "elapsed time: 0.02680516242980957\n",
-      "elapsed time: 0.026640892028808594\n",
-      "elapsed time: 0.02759575843811035\n",
-      "elapsed time: 0.02881908416748047\n",
-      "elapsed time: 0.02743077278137207\n",
-      "elapsed time: 0.02760910987854004\n",
-      "elapsed time: 0.026903390884399414\n",
-      "elapsed time: 0.027498245239257812\n",
-      "elapsed time: 0.02749776840209961\n",
-      "elapsed time: 0.027604103088378906\n",
-      "elapsed time: 0.028120994567871094\n",
-      "elapsed time: 0.028057098388671875\n",
-      "elapsed time: 0.02862381935119629\n",
-      "elapsed time: 0.02895808219909668\n",
-      "elapsed time: 0.02965092658996582\n",
-      "elapsed time: 0.02978205680847168\n",
-      "elapsed time: 0.030011653900146484\n",
-      "elapsed time: 0.029865503311157227\n",
-      "elapsed time: 0.03024911880493164\n",
-      "elapsed time: 0.02980661392211914\n",
-      "elapsed time: 0.030503511428833008\n",
-      "elapsed time: 0.0301516056060791\n",
-      "elapsed time: 0.0298004150390625\n",
-      "elapsed time: 0.029525279998779297\n",
-      "elapsed time: 0.029907703399658203\n",
-      "elapsed time: 0.029463529586791992\n",
-      "elapsed time: 0.02914571762084961\n",
-      "elapsed time: 0.0291597843170166\n",
-      "elapsed time: 0.02936530113220215\n",
-      "elapsed time: 0.029200077056884766\n",
-      "elapsed time: 0.0286557674407959\n",
-      "elapsed time: 0.02902507781982422\n",
-      "elapsed time: 0.028758764266967773\n",
-      "elapsed time: 0.02935171127319336\n",
-      "elapsed time: 0.029259204864501953\n",
-      "elapsed time: 0.029033422470092773\n",
-      "elapsed time: 0.028880834579467773\n",
-      "elapsed time: 0.028924226760864258\n",
-      "elapsed time: 0.028826475143432617\n",
-      "elapsed time: 0.028478622436523438\n",
-      "elapsed time: 0.0288240909576416\n",
-      "elapsed time: 0.02877044677734375\n",
-      "elapsed time: 0.028140544891357422\n",
-      "elapsed time: 0.02760028839111328\n",
-      "elapsed time: 0.027898073196411133\n",
-      "elapsed time: 0.02771472930908203\n",
-      "elapsed time: 0.027673721313476562\n",
-      "elapsed time: 0.02758169174194336\n",
-      "elapsed time: 0.027628183364868164\n",
-      "elapsed time: 0.027477741241455078\n",
-      "elapsed time: 0.027070999145507812\n",
-      "elapsed time: 0.026733875274658203\n",
-      "elapsed time: 0.02656245231628418\n",
-      "elapsed time: 0.026247739791870117\n",
-      "elapsed time: 0.02589249610900879\n",
-      "elapsed time: 0.025628089904785156\n",
-      "elapsed time: 0.025936126708984375\n",
-      "elapsed time: 0.0257565975189209\n",
-      "elapsed time: 0.025776147842407227\n",
-      "elapsed time: 0.025105953216552734\n",
-      "elapsed time: 0.024316072463989258\n",
-      "elapsed time: 0.02410602569580078\n",
-      "elapsed time: 0.0240628719329834\n",
-      "elapsed time: 0.023929595947265625\n",
-      "elapsed time: 0.024273395538330078\n",
-      "elapsed time: 0.023852109909057617\n",
-      "elapsed time: 0.023021221160888672\n",
-      "elapsed time: 0.024117708206176758\n",
-      "elapsed time: 0.023128271102905273\n",
-      "elapsed time: 0.02261972427368164\n",
-      "elapsed time: 0.022465229034423828\n",
-      "elapsed time: 0.022220611572265625\n",
-      "elapsed time: 0.021961688995361328\n",
-      "elapsed time: 0.02196502685546875\n",
-      "elapsed time: 0.021490097045898438\n",
-      "elapsed time: 0.02141594886779785\n",
-      "elapsed time: 0.02118968963623047\n",
-      "elapsed time: 0.021246671676635742\n",
-      "elapsed time: 0.020877838134765625\n",
-      "elapsed time: 0.020760774612426758\n",
-      "elapsed time: 0.020011425018310547\n",
-      "elapsed time: 0.020204544067382812\n",
-      "elapsed time: 0.019935131072998047\n",
-      "elapsed time: 0.020002126693725586\n",
-      "elapsed time: 0.020210981369018555\n",
-      "elapsed time: 0.01934981346130371\n",
-      "elapsed time: 0.0188140869140625\n",
-      "elapsed time: 0.01867389678955078\n",
-      "elapsed time: 0.018260478973388672\n",
-      "elapsed time: 0.01837778091430664\n",
-      "elapsed time: 0.018278837203979492\n",
-      "elapsed time: 0.018478870391845703\n",
-      "elapsed time: 0.018354177474975586\n",
-      "elapsed time: 0.01824164390563965\n",
-      "elapsed time: 0.017989397048950195\n",
-      "elapsed time: 0.018170833587646484\n",
-      "elapsed time: 0.017641782760620117\n",
-      "elapsed time: 0.017865657806396484\n",
-      "elapsed time: 0.017589569091796875\n",
-      "elapsed time: 0.01693439483642578\n",
-      "elapsed time: 0.016333580017089844\n",
-      "elapsed time: 0.016154050827026367\n",
-      "elapsed time: 0.01594376564025879\n",
-      "elapsed time: 0.015879392623901367\n",
-      "elapsed time: 0.015716075897216797\n",
-      "elapsed time: 0.01625823974609375\n",
-      "elapsed time: 0.015722990036010742\n",
-      "elapsed time: 0.01554727554321289\n",
-      "elapsed time: 0.015479564666748047\n",
-      "elapsed time: 0.01556706428527832\n",
-      "elapsed time: 0.015661954879760742\n",
-      "elapsed time: 0.01565694808959961\n",
-      "elapsed time: 0.014882326126098633\n",
-      "elapsed time: 0.014642000198364258\n",
-      "elapsed time: 0.014568328857421875\n",
-      "elapsed time: 0.014591693878173828\n",
-      "elapsed time: 0.014154195785522461\n",
-      "elapsed time: 0.014048576354980469\n",
-      "elapsed time: 0.013832807540893555\n",
-      "elapsed time: 0.013761281967163086\n",
-      "elapsed time: 0.013667583465576172\n",
-      "elapsed time: 0.01350092887878418\n",
-      "elapsed time: 0.013176918029785156\n",
-      "elapsed time: 0.013062000274658203\n",
-      "elapsed time: 0.013159036636352539\n",
-      "elapsed time: 0.013329505920410156\n",
-      "elapsed time: 0.013332366943359375\n",
-      "elapsed time: 0.013476133346557617\n",
-      "elapsed time: 0.013322591781616211\n",
-      "elapsed time: 0.01374506950378418\n",
-      "elapsed time: 0.013417720794677734\n",
-      "elapsed time: 0.013437509536743164\n",
-      "elapsed time: 0.013459444046020508\n",
-      "elapsed time: 0.013498783111572266\n",
-      "elapsed time: 0.013631343841552734\n",
-      "elapsed time: 0.013602733612060547\n",
-      "elapsed time: 0.013532161712646484\n",
-      "elapsed time: 0.013321399688720703\n",
-      "elapsed time: 0.013411521911621094\n",
-      "elapsed time: 0.013401508331298828\n",
-      "elapsed time: 0.013605833053588867\n",
-      "elapsed time: 0.013462543487548828\n",
-      "elapsed time: 0.013318061828613281\n",
-      "elapsed time: 0.013405323028564453\n",
-      "elapsed time: 0.013209819793701172\n",
-      "elapsed time: 0.013265848159790039\n",
-      "elapsed time: 0.01320028305053711\n",
-      "elapsed time: 0.013041496276855469\n",
-      "elapsed time: 0.013081550598144531\n",
-      "elapsed time: 0.013251304626464844\n",
-      "elapsed time: 0.0133819580078125\n",
-      "elapsed time: 0.013030290603637695\n",
-      "elapsed time: 0.013294696807861328\n",
-      "elapsed time: 0.013309478759765625\n",
-      "elapsed time: 0.013379335403442383\n",
-      "elapsed time: 0.01327824592590332\n"
+      "SimulationIteration(time_point=TimePoint(time_us=1623707846350127), index=0)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707846450055), index=1)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707846549980), index=2)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707846649908), index=3)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707846749828), index=4)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707846849751), index=5)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707846949678), index=6)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707847049600), index=7)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707847149533), index=8)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707847249494), index=9)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707847349489), index=10)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707847449511), index=11)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707847549545), index=12)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707847649595), index=13)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707847749661), index=14)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707847849740), index=15)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707847949830), index=16)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707848049917), index=17)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707848149996), index=18)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707848250061), index=19)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707848350125), index=20)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707848450201), index=21)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707848550285), index=22)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707848650366), index=23)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707848750437), index=24)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707848850498), index=25)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707848950550), index=26)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707849050609), index=27)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707849150663), index=28)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707849250694), index=29)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707849350712), index=30)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707849450721), index=31)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707849550719), index=32)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707849650719), index=33)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707849750708), index=34)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707849850693), index=35)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707849950680), index=36)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707850050657), index=37)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707850150617), index=38)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707850250579), index=39)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707850350533), index=40)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707850450476), index=41)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707850550409), index=42)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707850650331), index=43)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707850750249), index=44)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707850850165), index=45)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707850950074), index=46)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707851049984), index=47)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707851149906), index=48)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707851249854), index=49)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707851349817), index=50)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707851449795), index=51)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707851549788), index=52)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707851649785), index=53)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707851749789), index=54)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707851849798), index=55)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707851949814), index=56)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707852049839), index=57)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707852149873), index=58)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707852249912), index=59)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707852349958), index=60)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707852450015), index=61)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707852550074), index=62)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707852650134), index=63)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707852750202), index=64)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707852850274), index=65)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707852950341), index=66)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707853050396), index=67)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707853150437), index=68)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707853250455), index=69)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707853350458), index=70)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707853450447), index=71)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707853550423), index=72)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707853650396), index=73)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707853750371), index=74)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707853850348), index=75)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707853950316), index=76)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707854050282), index=77)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707854150246), index=78)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707854250202), index=79)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707854350166), index=80)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707854450127), index=81)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707854550087), index=82)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707854650049), index=83)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707854750013), index=84)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707854849975), index=85)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707854949934), index=86)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707855049897), index=87)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707855149866), index=88)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707855249835), index=89)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707855349806), index=90)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707855449781), index=91)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707855549767), index=92)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707855649761), index=93)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707855749759), index=94)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707855849764), index=95)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707855949781), index=96)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707856049800), index=97)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707856149811), index=98)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707856249820), index=99)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707856349835), index=100)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707856449843), index=101)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707856549842), index=102)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707856649840), index=103)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707856749839), index=104)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707856849830), index=105)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707856949823), index=106)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707857049822), index=107)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707857149827), index=108)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707857249832), index=109)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707857349847), index=110)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707857449869), index=111)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707857549895), index=112)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707857649926), index=113)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707857749959), index=114)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707857849999), index=115)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707857950037), index=116)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707858050070), index=117)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707858150098), index=118)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707858250120), index=119)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707858350135), index=120)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707858450145), index=121)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707858550149), index=122)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707858650146), index=123)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707858750137), index=124)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707858850126), index=125)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707858950113), index=126)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707859050096), index=127)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707859150076), index=128)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707859250064), index=129)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707859350064), index=130)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707859450064), index=131)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707859550063), index=132)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707859650055), index=133)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707859750047), index=134)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707859850041), index=135)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707859950028), index=136)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707860050018), index=137)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707860150002), index=138)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707860249979), index=139)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707860349963), index=140)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707860449960), index=141)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707860549964), index=142)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707860649965), index=143)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707860749961), index=144)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707860849946), index=145)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707860949927), index=146)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707861049906), index=147)\n",
+      "SimulationIteration(time_point=TimePoint(time_us=1623707861149865), index=148)\n"
      ]
     }
    ],
@@ -755,6 +454,10 @@
     "runner._initialize()\n",
     "\n",
     "while runner.simulation.is_simulation_running():\n",
+    "\n",
+    "    if runner.simulation._time_controller.get_iteration().index == 125:\n",
+    "        runner.simulation._observations.add_agent_to_scene(inserted_agent, inserted_goal, iteration.time_point)\n",
+    "\n",
     "    # Execute specific callback\n",
     "    runner.simulation.callback.on_step_start(runner.simulation.setup, runner.planner)\n",
     "\n",
@@ -771,7 +474,7 @@
     "    runner._simulation.callback.on_planner_end(runner.simulation.setup, runner.planner, trajectory)\n",
     "\n",
     "    iteration = runner.simulation._time_controller.get_iteration()\n",
-    "\n",
+    "    print(iteration)\n",
     "    runner.simulation.propagate(trajectory)\n",
     "\n",
     "    # Execute specific callback\n",
@@ -782,7 +485,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": null,
    "id": "e6c22f5f",
    "metadata": {
     "scrolled": true
@@ -792,7 +495,7 @@
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "INFO:bokeh.server.server:Starting Bokeh server version 2.4.3 (running on Tornado 6.2)\n",
+      "INFO:bokeh.server.server:Starting Bokeh server version 2.4.3 (running on Tornado 6.3.3)\n",
       "WARNING:bokeh.server.util:Host wildcard '*' will allow connections originating from multiple (or possibly all) hostnames or IPs. Use non-wildcard values to restrict access explicitly\n",
       "INFO:bokeh.server.tornado:User authentication hooks NOT provided (default user enabled)\n"
      ]
@@ -805,7 +508,7 @@
        "  (function() {\n",
        "    const xhr = new XMLHttpRequest()\n",
        "    xhr.responseType = 'blob';\n",
-       "    xhr.open('GET', \"http://localhost:5006/autoload.js?bokeh-autoload-element=1003&bokeh-absolute-url=http://localhost:5006&resources=none\", true);\n",
+       "    xhr.open('GET', \"http://localhost:5008/autoload.js?bokeh-autoload-element=1003&bokeh-absolute-url=http://localhost:5008&resources=none\", true);\n",
        "    xhr.onload = function (event) {\n",
        "      const script = document.createElement('script');\n",
        "      const src = URL.createObjectURL(event.target.response);\n",
@@ -819,7 +522,7 @@
      },
      "metadata": {
       "application/vnd.bokehjs_exec.v0+json": {
-       "server_id": "e6e966ebbd494438b1ac64effae4c8b2"
+       "server_id": "98f8c05cc9d14e8a9b309f39e6f6f8e6"
       }
      },
      "output_type": "display_data"
@@ -828,59 +531,23 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "2023-12-04 23:37:42,023 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:42,024 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:42,026 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Error creating dataset. Could not read schema from 'pretrained_checkpoints/gc_pgp_checkpoint.ckpt'. Is this a 'parquet' file?: Could not open Parquet input source 'pretrained_checkpoints/gc_pgp_checkpoint.ckpt': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:42,027 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Error creating dataset. Could not read schema from '.ipynb_checkpoints/test_notebook-checkpoint.ipynb'. Is this a 'parquet' file?: Could not open Parquet input source '.ipynb_checkpoints/test_notebook-checkpoint.ipynb': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:42,028 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Error creating dataset. Could not read schema from 'run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml'. Is this a 'parquet' file?: Could not open Parquet input source 'run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:42,028 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:42,032 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/simulation_tile.py:172}  Minimum frame time=0.017 s\n"
-     ]
-    },
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "Rendering a scenario: 100%|██████████| 1/1 [00:00<00:00, 157.64it/s]\n",
-      "WARNING:bokeh.core.validation.check:W-1000 (MISSING_RENDERERS): Plot has no renderers: Figure(id='1005', ...)\n"
-     ]
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "2023-12-04 23:37:44,012 INFO {/opt/conda/lib/python3.9/site-packages/tornado/web.py:2271}  200 GET / (10.40.117.44) 2030.70ms\n",
-      "2023-12-04 23:37:44,023 INFO {/home/ehdykhne/Repos/nuplan-devkit/tutorials/utils/tutorial_utils.py:267}  Done rendering!\n"
-     ]
-    },
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "INFO:tornado.access:200 GET / (10.40.117.44) 2030.70ms\n"
-     ]
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "2023-12-04 23:37:44,370 INFO {/opt/conda/lib/python3.9/site-packages/tornado/web.py:2271}  200 GET /static/js/bokeh-gl.min.js?v=e5df31fd9010eacff0aa72d315264604b5e34972ba445acea6fce98080eecf33acf2d2986126360faaa5852813cffa16f6f6f4889923318300f062497c02da4e (10.40.117.44) 271.87ms\n",
-      "2023-12-04 23:37:44,398 INFO {/opt/conda/lib/python3.9/site-packages/tornado/web.py:2271}  200 GET /static/js/bokeh.min.js?v=3c61e952b808bb7e346ce828a565a5f23aaf7708d034fa9d0906403813355d45bb4e8d8b0b23a93f032c76831d4f0221846f28699c7f5147caa62e0d31668314 (10.40.117.44) 299.84ms\n",
-      "2023-12-04 23:37:44,400 INFO {/opt/conda/lib/python3.9/site-packages/tornado/web.py:2271}  200 GET /static/js/bokeh-widgets.min.js?v=8a1ff6f5aa0d967f4998d275803bbb111d928fd9f605ef9e1f30cfd021df0e77224ee3d13f83edb3a942f6e4ccc569ee5dd8951a8aa6cb600602463b90c65a87 (10.40.117.44) 301.22ms\n",
-      "2023-12-04 23:37:44,435 INFO {/opt/conda/lib/python3.9/site-packages/tornado/web.py:2271}  200 GET /static/js/bokeh-tables.min.js?v=ae2903e57cf57f52819fdf4d938c648982b51c34f73b6e653a0f3bb3c8ab44f338505931ace43eafc1636e215492e2314acf54c54baffb47813b86b4923a7fe0 (10.40.117.44) 37.66ms\n"
+      "2023-12-05 02:24:51,738 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
+      "2023-12-05 02:24:51,739 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
+      "2023-12-05 02:24:51,740 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Error creating dataset. Could not read schema from 'pretrained_checkpoints/gc_pgp_checkpoint.ckpt'. Is this a 'parquet' file?: Could not open Parquet input source 'pretrained_checkpoints/gc_pgp_checkpoint.ckpt': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
+      "2023-12-05 02:24:51,740 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
+      "2023-12-05 02:24:51,740 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Error creating dataset. Could not read schema from 'run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml'. Is this a 'parquet' file?: Could not open Parquet input source 'run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
+      "2023-12-05 02:24:51,742 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/simulation_tile.py:172}  Minimum frame time=0.017 s\n"
      ]
     },
     {
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "INFO:tornado.access:200 GET /static/js/bokeh-gl.min.js?v=e5df31fd9010eacff0aa72d315264604b5e34972ba445acea6fce98080eecf33acf2d2986126360faaa5852813cffa16f6f6f4889923318300f062497c02da4e (10.40.117.44) 271.87ms\n",
-      "INFO:tornado.access:200 GET /static/js/bokeh.min.js?v=3c61e952b808bb7e346ce828a565a5f23aaf7708d034fa9d0906403813355d45bb4e8d8b0b23a93f032c76831d4f0221846f28699c7f5147caa62e0d31668314 (10.40.117.44) 299.84ms\n",
-      "INFO:tornado.access:200 GET /static/js/bokeh-widgets.min.js?v=8a1ff6f5aa0d967f4998d275803bbb111d928fd9f605ef9e1f30cfd021df0e77224ee3d13f83edb3a942f6e4ccc569ee5dd8951a8aa6cb600602463b90c65a87 (10.40.117.44) 301.22ms\n",
-      "INFO:tornado.access:200 GET /static/js/bokeh-tables.min.js?v=ae2903e57cf57f52819fdf4d938c648982b51c34f73b6e653a0f3bb3c8ab44f338505931ace43eafc1636e215492e2314acf54c54baffb47813b86b4923a7fe0 (10.40.117.44) 37.66ms\n",
-      "INFO:tornado.access:200 GET /static/js/bokeh-mathjax.min.js?v=176c36fdbcd8fc1019fc828101a2804081a35baf4018d7f2633cd263156b593aa73112f400112b662daa0590138b74851bc91f1f2a5fbf5416ee8c876c3e0d0c (10.40.117.44) 135.59ms\n",
+      "Rendering a scenario: 100%|██████████| 1/1 [00:00<00:00, 17.42it/s]\n",
+      "WARNING:bokeh.core.validation.check:W-1000 (MISSING_RENDERERS): Plot has no renderers: Figure(id='1005', ...)\n",
+      "INFO:tornado.access:200 GET /autoload.js?bokeh-autoload-element=1003&bokeh-absolute-url=http://localhost:5008&resources=none (::1) 1379.10ms\n",
       "INFO:bokeh.server.views.ws:WebSocket connection opened\n",
-      "INFO:tornado.access:101 GET /ws (10.40.117.44) 1.63ms\n",
+      "INFO:tornado.access:101 GET /ws?id=5633698c-81fb-4686-8a38-bed76da7b3ba&origin=da2f890a-eb18-4637-9199-dd0f06169aef&swVersion=4&extensionId=&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app&purpose=notebookRenderer (::1) 0.61ms\n",
       "INFO:bokeh.server.views.ws:ServerConnection created\n"
      ]
     },
@@ -888,63 +555,16 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "2023-12-04 23:37:45,376 INFO {/opt/conda/lib/python3.9/site-packages/tornado/web.py:2271}  200 GET /static/js/bokeh-mathjax.min.js?v=176c36fdbcd8fc1019fc828101a2804081a35baf4018d7f2633cd263156b593aa73112f400112b662daa0590138b74851bc91f1f2a5fbf5416ee8c876c3e0d0c (10.40.117.44) 135.59ms\n",
-      "2023-12-04 23:37:45,551 INFO {/opt/conda/lib/python3.9/site-packages/tornado/web.py:2271}  101 GET /ws (10.40.117.44) 1.63ms\n",
-      "2023-12-04 23:37:45,664 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:45,666 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:45,666 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Error creating dataset. Could not read schema from 'pretrained_checkpoints/gc_pgp_checkpoint.ckpt'. Is this a 'parquet' file?: Could not open Parquet input source 'pretrained_checkpoints/gc_pgp_checkpoint.ckpt': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:45,667 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Error creating dataset. Could not read schema from '.ipynb_checkpoints/test_notebook-checkpoint.ipynb'. Is this a 'parquet' file?: Could not open Parquet input source '.ipynb_checkpoints/test_notebook-checkpoint.ipynb': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:45,667 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Error creating dataset. Could not read schema from 'run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml'. Is this a 'parquet' file?: Could not open Parquet input source 'run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:45,667 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140}  Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n",
-      "2023-12-04 23:37:45,669 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/nuboard/base/simulation_tile.py:172}  Minimum frame time=0.017 s\n"
-     ]
-    },
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "Rendering a scenario: 100%|██████████| 1/1 [00:00<00:00, 10.45it/s]\n",
-      "WARNING:bokeh.core.validation.check:W-1000 (MISSING_RENDERERS): Plot has no renderers: Figure(id='3857', ...)\n",
-      "INFO:tornado.access:200 GET / (10.40.117.44) 805.62ms\n"
-     ]
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "2023-12-04 23:37:46,467 INFO {/opt/conda/lib/python3.9/site-packages/tornado/web.py:2271}  200 GET / (10.40.117.44) 805.62ms\n",
-      "2023-12-04 23:37:46,513 INFO {/home/ehdykhne/Repos/nuplan-devkit/tutorials/utils/tutorial_utils.py:267}  Done rendering!\n"
+      "2023-12-05 02:24:53,085 INFO {/home/sacardoz/miniconda3/envs/nuplan/lib/python3.9/site-packages/tornado/web.py:2344}  200 GET /autoload.js?bokeh-autoload-element=1003&bokeh-absolute-url=http://localhost:5008&resources=none (::1) 1379.10ms\n",
+      "2023-12-05 02:24:53,096 INFO {/home/sacardoz/nuplan-devkit/tutorials/utils/tutorial_utils.py:267}  Done rendering!\n",
+      "2023-12-05 02:24:53,097 INFO {/home/sacardoz/miniconda3/envs/nuplan/lib/python3.9/site-packages/tornado/web.py:2344}  101 GET /ws?id=5633698c-81fb-4686-8a38-bed76da7b3ba&origin=da2f890a-eb18-4637-9199-dd0f06169aef&swVersion=4&extensionId=&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app&purpose=notebookRenderer (::1) 0.61ms\n"
      ]
     }
    ],
    "source": [
     "from tutorials.utils.tutorial_utils import visualize_history\n",
-    "visualize_history(runner.simulation._history, runner.scenario, bokeh_port=5006)"
+    "visualize_history(runner.simulation._history, runner.scenario, bokeh_port=5008)"
    ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "887a51e2",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "e0a12035",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "67b67b86",
-   "metadata": {},
-   "outputs": [],
-   "source": []
   }
  ],
  "metadata": {
@@ -963,7 +583,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.16"
+   "version": "3.9.18"
   }
  },
  "nbformat": 4,
diff --git a/nuplan/planning/script/config/simulation/default_run_metric_aggregator.yaml b/nuplan/planning/script/config/simulation/default_run_metric_aggregator.yaml
index fdd8aa020519607f56b056ec972afe90676e5a65..443ed41d5fd53e280928b329caaa740e602a1f61 100644
--- a/nuplan/planning/script/config/simulation/default_run_metric_aggregator.yaml
+++ b/nuplan/planning/script/config/simulation/default_run_metric_aggregator.yaml
@@ -13,6 +13,7 @@ defaults:
       - open_loop_boxes_weighted_average
       - closed_loop_nonreactive_agents_weighted_average
       - closed_loop_reactive_agents_weighted_average
+      - closed_loop_multiagent_weighted_average
 
   - override hydra/job_logging: none                            # Disable hydra's logging
   - override hydra/hydra_logging: none                          # Disable hydra's logging
diff --git a/nuplan/planning/script/config/simulation/metric_aggregator/closed_loop_multiagent_weighted_average.yaml b/nuplan/planning/script/config/simulation/metric_aggregator/closed_loop_multiagent_weighted_average.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f490e7db9e22021d6adbf58df7db178cfa2ccfe3
--- /dev/null
+++ b/nuplan/planning/script/config/simulation/metric_aggregator/closed_loop_multiagent_weighted_average.yaml
@@ -0,0 +1,19 @@
+closed_loop_reactive_agents_weighted_average:
+  _target_: nuplan.planning.metrics.aggregator.weighted_average_metric_aggregator.WeightedAverageMetricAggregator
+  name: 'closed_loop_multiagent_weighted_average'
+  metric_weights:  # Below we list the metrics used in the scenario scoring function and their corresponsing weights to calculate a weighted average score for each scenario,
+  # if not specified, the weight is set as default.
+    # metric name : metric weight in the weighted average function
+    ego_progress_along_expert_route: 5.0 # This metric has the highest weight equal to 5.0 in the weighted average function, its base score can take a value in [0,1] depending on the ratio of ego to expert progress
+    time_to_collision_within_bound: 5.0 # This metric has the highest weight equal to 5.0 in the weighted average function, its base score can be 0 or 1 depending on the minimum time to collision threshold
+    speed_limit_compliance: 4.0 # This metric has a weight equal to 4.0 in the weighted average function, its base score can take a value in [0,1] depending on the amount and duration of over-speeding
+    ego_is_comfortable: 2.0 # This metric has the lowest weight equal to 2.0 in the weighted average function, its base score can be 0 or 1 depending on the comfort thresholds on acceleration, jerk and yaw.
+    default: 1.0
+  file_name: closed_loop_multiagent_weighted_average_metrics_${now:${date_format}}
+  # The scenario score is defined as the weighted average score of the metrics listed above, multiplied by the score of the multiple_metrics below.
+  multiple_metrics:
+    - no_ego_at_fault_collisions # This metric score can be 0, 0.5 or 1 depending on whether there is an at fault collision with VRUs, vehicles or objects
+    - drivable_area_compliance # This metric score can be 0 or 1 depending on whether ego drives outside the drivable area
+    - ego_is_making_progress # This metric score can be 0 or 1 depending on whether ego makes progress more than a minimum threshold compared to expert's progress
+    - driving_direction_compliance # This metric score can be 0 or 0.5 or 1 depending on how much ego drives in the opposite direction if any
+  challenge_name: closed_loop_multiagent
diff --git a/nuplan/planning/script/config/simulation/observation/ml_planner_agents_observation.yaml b/nuplan/planning/script/config/simulation/observation/ml_planner_agents_observation.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8f84c52cde0754838620a5600154adaa867534ff
--- /dev/null
+++ b/nuplan/planning/script/config/simulation/observation/ml_planner_agents_observation.yaml
@@ -0,0 +1,5 @@
+_target_: nuplan.planning.simulation.observation.ml_planner_agents.MLPlannerAgents
+_convert_: 'all'
+
+model_config: ???  # Dictionary key from existing planner model config (e.g. reactive_agents_model)
+checkpoint_path: ???  # Path to trained model checkpoint
diff --git a/nuplan/planning/script/experiments/simulation/closed_loop_multiagent.yaml b/nuplan/planning/script/experiments/simulation/closed_loop_multiagent.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5b6d3fa902076cfd3b9053aabe0273b77be88c6d
--- /dev/null
+++ b/nuplan/planning/script/experiments/simulation/closed_loop_multiagent.yaml
@@ -0,0 +1,10 @@
+# @package _global_
+job_name: closed_loop_multiagent
+
+defaults:
+  - override /observation: ml_planner_agents_observation 
+  - override /ego_controller: two_stage_controller
+  - override /planner: ml_planner
+  - override /simulation_metric: simulation_closed_loop_reactive_agents
+  - override /metric_aggregator:
+      - closed_loop_multiagent_weighted_average
diff --git a/nuplan/planning/simulation/observation/ml_planner_agents.py b/nuplan/planning/simulation/observation/ml_planner_agents.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb4fa746dc57aa9182773fa363bae20441454582
--- /dev/null
+++ b/nuplan/planning/simulation/observation/ml_planner_agents.py
@@ -0,0 +1,268 @@
+from collections import deque
+from copy import deepcopy
+from typing import Dict, List, Type
+
+from nuplan.common.actor_state.agent import Agent
+from nuplan.common.actor_state.ego_state import EgoState
+from nuplan.common.actor_state.scene_object import SceneObjectMetadata
+from nuplan.common.actor_state.state_representation import StateSE2, StateVector2D, TimePoint
+from nuplan.common.actor_state.tracked_objects import TrackedObject, TrackedObjects
+from nuplan.common.actor_state.tracked_objects_types import TrackedObjectType
+from nuplan.common.actor_state.vehicle_parameters import VehicleParameters
+
+from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario
+from nuplan.planning.simulation.history.simulation_history_buffer import SimulationHistoryBuffer
+from nuplan.planning.simulation.observation.abstract_observation import AbstractObservation
+from nuplan.planning.simulation.observation.observation_type import Observation
+from nuplan.planning.simulation.observation.observation_type import DetectionsTracks
+from nuplan.planning.simulation.planner.abstract_planner import PlannerInitialization, PlannerInput
+from nuplan.planning.simulation.planner.ml_planner.ml_planner import MLPlanner
+from nuplan.planning.simulation.simulation_time_controller.simulation_iteration import SimulationIteration
+from nuplan.planning.training.modeling.torch_module_wrapper import TorchModuleWrapper
+
+
+OPEN_LOOP_DETECTION_TYPES = [TrackedObjectType.PEDESTRIAN, TrackedObjectType.BICYCLE, \
+                             TrackedObjectType.CZONE_SIGN, TrackedObjectType.BARRIER, \
+                             TrackedObjectType.TRAFFIC_CONE, TrackedObjectType.GENERIC_OBJECT]
+
+class MLPlannerAgents(AbstractObservation):
+    """
+    Simulate agents based on an ML model.
+    """
+
+    def __init__(self, model: TorchModuleWrapper, scenario: AbstractScenario) -> None:
+        """
+        Initializes the MLPlannerAgents class.
+        :param model: Model to use for inference.
+        :param scenario: scenario
+        """
+        self.current_iteration = 0
+        self.model = model
+        self._scenario = scenario
+        self._ego_state_history: Dict = {}
+        self._agent_prescence_threshold = 10
+        self._agents: Dict = None    
+
+    def reset(self) -> None:
+        """Inherited, see superclass."""
+        self.current_iteration = 0
+        self._agents = None
+    
+    def _get_agents(self):
+        """
+        Gets dict of tracked agents, or lazily creates them it 
+        from vehicles at simulation start if it does not exist.
+        """
+
+        if not self._agents:
+            self._agents = {}
+            for agent in self._scenario.initial_tracked_objects.tracked_objects.get_tracked_objects_of_type(TrackedObjectType.VEHICLE):
+
+                
+                # TODO: Support ego controllers - right now just doing perfect tracking.
+                #       Revist whether there is a better way of translating agent states to ego states. 
+                #       Revist whether there is a better way of setting agent goals.
+                #       Filter out impossible/off-road initial detections.
+
+                # Sets agent goal to be it's last known point in the simulation. This results in some strange driving behaviour
+                # if the agent disappears early in a scene.
+                goal = None
+
+                for frame in range(self._scenario.get_number_of_iterations()-1, self._agent_prescence_threshold, -1):
+                    last_scenario_frame = self._scenario.get_tracked_objects_at_iteration(frame)
+                    matched_track = None
+                    for track in last_scenario_frame.tracked_objects.tracked_objects:
+                        if track.metadata.track_token == agent.metadata.track_token:
+                            matched_track = track
+                            break
+                    
+                    if matched_track:
+                        goal = matched_track.center
+                        break
+
+                if goal:
+                    # Estimates ego states from agent state at simulation starts, stores metadata and creates planner for each agent
+                    self._agents[agent.metadata.track_token] = {'ego_state': self._build_ego_state_from_agent(agent, self._scenario.start_time), \
+                                                                'metadata': agent.metadata,
+                                                                'planner': MLPlanner(self.model)}
+                    # Initialize planner.
+                    planner_init = PlannerInitialization(
+                            route_roadblock_ids=self._scenario.get_route_roadblock_ids(),
+                            mission_goal=goal,
+                            map_api=self._scenario.map_api,
+                        )
+                    
+                    self._agents[agent.metadata.track_token]['planner'].initialize(planner_init)
+
+        return self._agents
+
+    def observation_type(self) -> Type[Observation]:
+        """Inherited, see superclass."""
+        return DetectionsTracks  # type: ignore
+
+    def initialize(self) -> None:
+        """Inherited, see superclass."""
+        pass
+            
+    def get_observation(self) -> DetectionsTracks:
+        """Inherited, see superclass."""
+        agents = [self._build_agent_from_ego_state(v['ego_state'], v['metadata']) for v in self._get_agents().values()]
+        open_loop_detections = self._get_open_loop_track_objects(self.current_iteration)
+        open_loop_detections.extend(agents)
+        return DetectionsTracks(tracked_objects=TrackedObjects(open_loop_detections))
+    
+    def update_observation(
+        self, iteration: SimulationIteration, next_iteration: SimulationIteration, history: SimulationHistoryBuffer
+    ) -> None:
+        """Inherited, see superclass."""
+        self.current_iteration = next_iteration.index
+        self.propagate_agents(iteration, next_iteration, history)
+
+    def _get_open_loop_track_objects(self, iteration: int) -> List[TrackedObject]:
+        """
+        Get open-loop tracked objects from scenario.
+        :param iteration: The simulation iteration.
+        :return: A list of TrackedObjects.
+        """
+
+        detections = self._scenario.get_tracked_objects_at_iteration(iteration) 
+        return detections.tracked_objects.get_tracked_objects_of_types(OPEN_LOOP_DETECTION_TYPES) 
+
+    def propagate_agents(self, iteration: SimulationIteration, next_iteration: SimulationIteration, history: SimulationHistoryBuffer):
+        """
+        Propagates agents into next timestep by constructing input for each agent planner, then interpolating new agent
+        states from the predicted trajectory for each from their respective planners. Caches computed agent states.
+        """
+
+        traffic_light_data = list(self._scenario.get_traffic_light_status_at_iteration(iteration.index))
+
+        # TODO: Find way to parallelize.
+        # TODO: Propagate non-ego and lower frequency to improve performance.
+        for agent_token, agent_data in self._agents.items():
+            history_input = self._build_history_input(agent_token, agent_data['ego_state'], history)
+            planner_input = PlannerInput(iteration=iteration, history=history_input, traffic_light_data=traffic_light_data)
+            trajectory = agent_data['planner'].compute_trajectory(planner_input)
+            agent_data['ego_state'] = trajectory.get_state_at_time(next_iteration.time_point)
+            self._ego_state_history[agent_token][next_iteration.time_point] = agent_data['ego_state']
+            
+    def _build_ego_state_from_agent(self, agent: Agent, time_point: TimePoint) -> EgoState:
+        """
+        Builds ego state from corresponding agent state. Since this process is imperfect, it uses cached ego states from the propagation
+        so casting is only required for the beginning agent states for which we have no propagation information.
+        """
+
+        if agent.metadata.track_token in self._ego_state_history:
+            if time_point in self._ego_state_history[agent.metadata.track_token]:
+                return self._ego_state_history[agent.metadata.track_token][time_point]
+        else:
+            self._ego_state_history[agent.metadata.track_token] = {}
+
+        # Most of this is just eyeballed, so there may be a more principled way of setting these values.
+        output = EgoState.build_from_center(
+            center=agent.center,
+            center_velocity_2d=agent.velocity,
+            center_acceleration_2d=StateVector2D(0, 0),
+            tire_steering_angle=0,
+            time_point=time_point,
+            vehicle_parameters=VehicleParameters(
+                        vehicle_name=agent.track_token,
+                        vehicle_type="gen1",
+                        width=agent.box.width,
+                        front_length=agent.box.length * 4 / 5,
+                        rear_length=agent.box.length * 1 / 5,
+                        wheel_base=agent.box.length * 3 / 5,
+                        cog_position_from_rear_axle=agent.box.length * 1.5 / 5,
+                        height=agent.box.height,
+                    ),
+        )
+
+        self._ego_state_history[agent.metadata.track_token][time_point] = output
+        return output
+    
+    def _build_agent_from_ego_state(self, ego_state: EgoState, scene_object_metadata: SceneObjectMetadata) -> Agent:
+        """
+        Builds agent state from corresponding ego state. Unlike the inverse this process is well-defined.
+        """
+
+        agent_state = Agent(
+            metadata=scene_object_metadata,
+            tracked_object_type=TrackedObjectType.VEHICLE,
+            oriented_box=ego_state.car_footprint.oriented_box,
+            velocity=ego_state.dynamic_car_state.center_velocity_2d,
+        )
+        return agent_state
+    
+    def _build_history_input(self, agent_track_token: str, current_state: EgoState, history: SimulationHistoryBuffer) -> SimulationHistoryBuffer:
+        """
+        Builds the planner history input for a given agent. This requires us the interchange the ego states of the actual ego with the 
+        constructed ego states of the agent of interest, and create observations corresponding to the ego in the observation history buffer.
+        """
+        ego_state_buffer = history.ego_state_buffer
+        observation_buffer = history.observation_buffer
+
+        new_observations = []
+        faux_ego_obervations = []
+
+        # Construct a timestep/track_token to observation loopup table for faster proessing
+        track_token_agent_dict = {}
+        for t, observation in enumerate(observation_buffer):
+            track_token_agent_dict[t] = {}
+            for agent in observation.tracked_objects.tracked_objects:
+                track_token_agent_dict[t][agent.metadata.track_token] = agent
+
+        # Loop through history buffer
+        for t, (ego_state, observation) in enumerate(zip(ego_state_buffer, observation_buffer)):
+
+            # Convert actual ego state into agent object
+            ego_agent_object = self._build_agent_from_ego_state(ego_state, \
+                                                                SceneObjectMetadata(token='ego', track_token="ego", track_id=-1, \
+                                                                                    timestamp_us=ego_state.time_us))
+            
+            # Get agent object corresponding to agent from observation buffer. If one does not exist for current timestep, take from the future,
+            # if one does not exist from the future, take the current state. This might occur at the first timestep for observations that have no
+            # logged  history prior to simulation start, or observations inserted mid-simulation.
+            matched_agent = None
+
+            for i in range(t, len(observation_buffer)):
+                if i in track_token_agent_dict and agent_track_token in track_token_agent_dict[i]:
+                    matched_agent = track_token_agent_dict[i][agent_track_token]
+                    break
+                    
+            # Convert agent state to a corresponding "ego state" object, or pull it from cache if already computed.
+            if matched_agent is None:
+                faux_ego_observation = deepcopy(current_state)
+                faux_ego_observation._time_point = ego_state.time_point
+            else:
+                faux_ego_observation = self._build_ego_state_from_agent(matched_agent, ego_state.time_point)
+
+
+            # Rebuild timestep and buffer - creating a new observations object with old ego appended.
+            tracks = [ag for ag in observation.tracked_objects.tracked_objects if ag.metadata.track_token != agent_track_token]
+            tracks.append(ego_agent_object)
+
+            new_observations.append(DetectionsTracks(tracked_objects=TrackedObjects(tracks)))
+            faux_ego_obervations.append(faux_ego_observation)
+    
+
+        output_buffer = SimulationHistoryBuffer(deque(faux_ego_obervations), \
+                            deque(new_observations), \
+                                history.sample_interval)
+
+        return output_buffer
+    
+    def add_agent_to_scene(self, agent: Agent, goal: StateSE2, timepoint_record: TimePoint):
+        """
+        Adds agent to the scene with a given goal during the simulation runtime.
+        """
+        # TODO: Inject IDM agents (and non-ML agents more broadly)
+
+        self._agents[agent.metadata.track_token] = {'ego_state': self._build_ego_state_from_agent(agent, timepoint_record), \
+                                                    'metadata': agent.metadata,
+                                                    'planner': MLPlanner(self.model)}
+        planner_init = PlannerInitialization(
+                route_roadblock_ids=self._scenario.get_route_roadblock_ids(),
+                mission_goal=goal,
+                map_api=self._scenario.map_api,
+            )
+        
+        self._agents[agent.metadata.track_token]['planner'].initialize(planner_init)