diff --git a/experiments/test_notebook.ipynb b/experiments/test_notebook.ipynb
index 33cfffacf3c5efe3c7a2472c4d336291b7d28ba9..79c037183cbdeb4de5aaf458080b6e84f1e77b8f 100644
--- a/experiments/test_notebook.ipynb
+++ b/experiments/test_notebook.ipynb
@@ -26,7 +26,7 @@
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "/tmp/ipykernel_5670/4095267831.py:5: DeprecationWarning: Importing display from IPython.core.display is deprecated since IPython 7.14, please import from IPython display\n",
+      "/tmp/ipykernel_6532/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,8 +60,287 @@
     },
     {
      "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\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": ""
+      "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));"
      },
      "metadata": {},
      "output_type": "display_data"
@@ -106,6 +385,47 @@
     "# Simulating the planner <a name=\"simulation\"></a>"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "id": "aff526c3",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'/host_home/Repos/nuplan-devkit/experiments'"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%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",
@@ -116,7 +436,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 5,
    "id": "11b08c6d",
    "metadata": {},
    "outputs": [],
@@ -135,7 +455,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/sacardoz/checkpoints/pdm_offset_checkpoint.ckpt'\n",
+    "ckpt_dir = '/home/ehdykhne/Repos/nuplan-devkit/experiments/pretrained_checkpoints/pdm_offset_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",
@@ -156,6 +476,7 @@
     "    #f'observation.checkpoint_path={ckpt_dir}',\n",
     "    'worker=sequential',\n",
     "    '+occlusion=true',\n",
+    "    '+occlusion.manager_type=complete_shadow', #options: [range, complete_shadow]\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",
     "    *DATASET_PARAMS,\n",
     "])"
@@ -171,16 +492,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 6,
    "id": "161cc166",
-   "metadata": {},
+   "metadata": {
+    "scrolled": true
+   },
    "outputs": [
     {
      "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"
@@ -190,32 +511,32 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "2023-11-28 11:02:20,269 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/worker_pool_builder.py:19}  Building WorkerPool...\n",
-      "2023-11-28 11:02:20,270 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/utils/multithreading/worker_pool.py:101}  Worker: Sequential\n",
-      "2023-11-28 11:02:20,270 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/utils/multithreading/worker_pool.py:102}  Number of nodes: 1\n",
+      "2023-11-29 11:53:16,287 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/worker_pool_builder.py:19}  Building WorkerPool...\n",
+      "2023-11-29 11:53:16,288 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/utils/multithreading/worker_pool.py:101}  Worker: Sequential\n",
+      "2023-11-29 11:53:16,288 INFO {/home/ehdykhne/Repos/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-11-28 11:02:20,270 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/worker_pool_builder.py:27}  Building WorkerPool...DONE!\n",
-      "2023-11-28 11:02:20,270 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:32}  Building experiment folders...\n",
-      "2023-11-28 11:02:20,270 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:35}  \n",
+      "2023-11-29 11:53:16,288 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/worker_pool_builder.py:27}  Building WorkerPool...DONE!\n",
+      "2023-11-29 11:53:16,288 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:32}  Building experiment folders...\n",
+      "2023-11-29 11:53:16,288 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:35}  \n",
       "\n",
-      "\tFolder where all results are stored: /home/sacardoz/nuplan/exp/exp/simulation/closed_loop_reactive_agents/2023.11.28.11.02.19\n",
+      "\tFolder where all results are stored: /home/ehdykhne/Repos/Datasets/nuplan/exp/exp/simulation/closed_loop_reactive_agents/2023.11.29.11.53.16\n",
       "\n",
-      "2023-11-28 11:02:20,271 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:70}  Building experiment folders...DONE!\n",
-      "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_callback_builder.py:52}  Building AbstractCallback...\n",
-      "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_callback_builder.py:68}  Building AbstractCallback: 0...DONE!\n",
-      "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:49}  Building simulations...\n",
-      "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:55}  Extracting scenarios...\n",
-      "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/common/utils/distributed_scenario_filter.py:83}  Building Scenarios in mode DistributedMode.SINGLE_NODE\n",
-      "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_building_builder.py:18}  Building AbstractScenarioBuilder...\n",
-      "2023-11-28 11:02:20,284 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_building_builder.py:21}  Building AbstractScenarioBuilder...DONE!\n",
-      "2023-11-28 11:02:20,284 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_filter_builder.py:35}  Building ScenarioFilter...\n",
-      "2023-11-28 11:02:20,285 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_filter_builder.py:44}  Building ScenarioFilter...DONE!\n",
-      "2023-11-28 11:02:20,302 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:76}  Building metric engines...\n",
-      "2023-11-28 11:02:20,327 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:78}  Building metric engines...DONE\n",
-      "2023-11-28 11:02:20,327 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:82}  Building simulations from 1 scenarios...\n",
-      "2023-11-28 11:02:20,786 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:142}  Building simulations...DONE!\n"
+      "2023-11-29 11:53:16,309 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:70}  Building experiment folders...DONE!\n",
+      "2023-11-29 11:53:16,309 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_callback_builder.py:52}  Building AbstractCallback...\n",
+      "2023-11-29 11:53:16,309 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_callback_builder.py:68}  Building AbstractCallback: 0...DONE!\n",
+      "2023-11-29 11:53:16,309 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:49}  Building simulations...\n",
+      "2023-11-29 11:53:16,310 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:55}  Extracting scenarios...\n",
+      "2023-11-29 11:53:16,310 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/common/utils/distributed_scenario_filter.py:83}  Building Scenarios in mode DistributedMode.SINGLE_NODE\n",
+      "2023-11-29 11:53:16,310 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/scenario_building_builder.py:18}  Building AbstractScenarioBuilder...\n",
+      "2023-11-29 11:53:16,321 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/scenario_building_builder.py:21}  Building AbstractScenarioBuilder...DONE!\n",
+      "2023-11-29 11:53:16,321 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/scenario_filter_builder.py:35}  Building ScenarioFilter...\n",
+      "2023-11-29 11:53:16,322 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/scenario_filter_builder.py:44}  Building ScenarioFilter...DONE!\n",
+      "2023-11-29 11:53:16,515 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:76}  Building metric engines...\n",
+      "2023-11-29 11:53:16,539 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:78}  Building metric engines...DONE\n",
+      "2023-11-29 11:53:16,539 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:82}  Building simulations from 1 scenarios...\n",
+      "2023-11-29 11:53:16,954 INFO {/home/ehdykhne/Repos/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:142}  Building simulations...DONE!\n"
      ]
     }
    ],
@@ -229,7 +550,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 7,
    "id": "223284d4",
    "metadata": {},
    "outputs": [],
@@ -239,7 +560,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 8,
    "id": "90b79421",
    "metadata": {},
    "outputs": [
@@ -249,6 +570,182 @@
      "text": [
       "                                                                                      \r"
      ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "elapsed time: 0.07004618644714355\n",
+      "elapsed time: 0.07483673095703125\n",
+      "elapsed time: 0.07236838340759277\n",
+      "elapsed time: 0.07217693328857422\n",
+      "elapsed time: 0.10412955284118652\n",
+      "elapsed time: 0.07252717018127441\n",
+      "elapsed time: 0.07221794128417969\n",
+      "elapsed time: 0.07209014892578125\n",
+      "elapsed time: 0.0690145492553711\n",
+      "elapsed time: 0.06903672218322754\n",
+      "elapsed time: 0.06894278526306152\n",
+      "elapsed time: 0.06661295890808105\n",
+      "elapsed time: 0.06461930274963379\n",
+      "elapsed time: 0.08367776870727539\n",
+      "elapsed time: 0.09180307388305664\n",
+      "elapsed time: 0.06936907768249512\n",
+      "elapsed time: 0.06967496871948242\n",
+      "elapsed time: 0.06659340858459473\n",
+      "elapsed time: 0.0688934326171875\n",
+      "elapsed time: 0.07156205177307129\n",
+      "elapsed time: 0.0704042911529541\n",
+      "elapsed time: 0.06720423698425293\n",
+      "elapsed time: 0.06779098510742188\n",
+      "elapsed time: 0.06732940673828125\n",
+      "elapsed time: 0.06793570518493652\n",
+      "elapsed time: 0.06827068328857422\n",
+      "elapsed time: 0.06803774833679199\n",
+      "elapsed time: 0.06943106651306152\n",
+      "elapsed time: 0.07204318046569824\n",
+      "elapsed time: 0.07504653930664062\n",
+      "elapsed time: 0.07936668395996094\n",
+      "elapsed time: 0.07835721969604492\n",
+      "elapsed time: 0.08042740821838379\n",
+      "elapsed time: 0.08030986785888672\n",
+      "elapsed time: 0.08157157897949219\n",
+      "elapsed time: 0.0802454948425293\n",
+      "elapsed time: 0.0751650333404541\n",
+      "elapsed time: 0.07476520538330078\n",
+      "elapsed time: 0.06846976280212402\n",
+      "elapsed time: 0.07239532470703125\n",
+      "elapsed time: 0.0754702091217041\n",
+      "elapsed time: 0.07381010055541992\n",
+      "elapsed time: 0.07812881469726562\n",
+      "elapsed time: 0.0744931697845459\n",
+      "elapsed time: 0.07428240776062012\n",
+      "elapsed time: 0.07447385787963867\n",
+      "elapsed time: 0.07498693466186523\n",
+      "elapsed time: 0.07564187049865723\n",
+      "elapsed time: 0.07411766052246094\n",
+      "elapsed time: 0.07452106475830078\n",
+      "elapsed time: 0.07535028457641602\n",
+      "elapsed time: 0.07478833198547363\n",
+      "elapsed time: 0.07393765449523926\n",
+      "elapsed time: 0.07467913627624512\n",
+      "elapsed time: 0.07434272766113281\n",
+      "elapsed time: 0.07504987716674805\n",
+      "elapsed time: 0.07368898391723633\n",
+      "elapsed time: 0.07052135467529297\n",
+      "elapsed time: 0.07123780250549316\n",
+      "elapsed time: 0.06909584999084473\n",
+      "elapsed time: 0.0709383487701416\n",
+      "elapsed time: 0.07035303115844727\n",
+      "elapsed time: 0.06994056701660156\n",
+      "elapsed time: 0.07037758827209473\n",
+      "elapsed time: 0.06873893737792969\n",
+      "elapsed time: 0.06872749328613281\n",
+      "elapsed time: 0.07295823097229004\n",
+      "elapsed time: 0.0730447769165039\n",
+      "elapsed time: 0.07594704627990723\n",
+      "elapsed time: 0.07653594017028809\n",
+      "elapsed time: 0.07710099220275879\n",
+      "elapsed time: 0.0853586196899414\n",
+      "elapsed time: 0.09077739715576172\n",
+      "elapsed time: 0.0913994312286377\n",
+      "elapsed time: 0.09149646759033203\n",
+      "elapsed time: 0.09023475646972656\n",
+      "elapsed time: 0.0727548599243164\n",
+      "elapsed time: 0.08517217636108398\n",
+      "elapsed time: 0.06823277473449707\n",
+      "elapsed time: 0.06686687469482422\n",
+      "elapsed time: 0.0766451358795166\n",
+      "elapsed time: 0.07756519317626953\n",
+      "elapsed time: 0.06811785697937012\n",
+      "elapsed time: 0.06695938110351562\n",
+      "elapsed time: 0.07274198532104492\n",
+      "elapsed time: 0.07442855834960938\n",
+      "elapsed time: 0.07276296615600586\n",
+      "elapsed time: 0.07159733772277832\n",
+      "elapsed time: 0.06434440612792969\n",
+      "elapsed time: 0.0637660026550293\n",
+      "elapsed time: 0.06423211097717285\n",
+      "elapsed time: 0.06087660789489746\n",
+      "elapsed time: 0.06029963493347168\n",
+      "elapsed time: 0.060274362564086914\n",
+      "elapsed time: 0.05926346778869629\n",
+      "elapsed time: 0.05600380897521973\n",
+      "elapsed time: 0.054741621017456055\n",
+      "elapsed time: 0.05558180809020996\n",
+      "elapsed time: 0.050803422927856445\n",
+      "elapsed time: 0.04188847541809082\n",
+      "elapsed time: 0.04085540771484375\n",
+      "elapsed time: 0.03948569297790527\n",
+      "elapsed time: 0.03525233268737793\n",
+      "elapsed time: 0.034883737564086914\n",
+      "elapsed time: 0.03830385208129883\n",
+      "elapsed time: 0.03892111778259277\n",
+      "elapsed time: 0.03910946846008301\n",
+      "elapsed time: 0.03990888595581055\n",
+      "elapsed time: 0.03977513313293457\n",
+      "elapsed time: 0.03940868377685547\n",
+      "elapsed time: 0.03820657730102539\n",
+      "elapsed time: 0.037245988845825195\n",
+      "elapsed time: 0.03798174858093262\n",
+      "elapsed time: 0.03704357147216797\n",
+      "elapsed time: 0.03214216232299805\n",
+      "elapsed time: 0.0314328670501709\n",
+      "elapsed time: 0.03196096420288086\n",
+      "elapsed time: 0.03226613998413086\n",
+      "elapsed time: 0.03210568428039551\n",
+      "elapsed time: 0.03227066993713379\n",
+      "elapsed time: 0.03103947639465332\n",
+      "elapsed time: 0.030683040618896484\n",
+      "elapsed time: 0.031124114990234375\n",
+      "elapsed time: 0.0313417911529541\n",
+      "elapsed time: 0.031717777252197266\n",
+      "elapsed time: 0.03174924850463867\n",
+      "elapsed time: 0.03109598159790039\n",
+      "elapsed time: 0.030297040939331055\n",
+      "elapsed time: 0.030659198760986328\n",
+      "elapsed time: 0.02944207191467285\n",
+      "elapsed time: 0.028339862823486328\n",
+      "elapsed time: 0.028562068939208984\n",
+      "elapsed time: 0.027463912963867188\n",
+      "elapsed time: 0.027869462966918945\n",
+      "elapsed time: 0.02405858039855957\n",
+      "elapsed time: 0.023447751998901367\n",
+      "elapsed time: 0.022365093231201172\n",
+      "elapsed time: 0.021931171417236328\n",
+      "elapsed time: 0.022172212600708008\n",
+      "elapsed time: 0.02304244041442871\n",
+      "elapsed time: 0.023903608322143555\n",
+      "elapsed time: 0.024321794509887695\n",
+      "elapsed time: 0.024337291717529297\n",
+      "elapsed time: 0.02479100227355957\n",
+      "elapsed time: 0.024851083755493164\n",
+      "elapsed time: 0.02486586570739746\n",
+      "elapsed time: 0.025527000427246094\n",
+      "elapsed time: 0.02471184730529785\n",
+      "elapsed time: 0.025118589401245117\n",
+      "elapsed time: 0.024780750274658203\n",
+      "elapsed time: 0.024580955505371094\n",
+      "elapsed time: 0.026010513305664062\n",
+      "elapsed time: 0.02849745750427246\n",
+      "elapsed time: 0.025658369064331055\n",
+      "elapsed time: 0.026093006134033203\n",
+      "elapsed time: 0.026526212692260742\n",
+      "elapsed time: 0.026284217834472656\n",
+      "elapsed time: 0.025562286376953125\n",
+      "elapsed time: 0.025296449661254883\n",
+      "elapsed time: 0.025045394897460938\n",
+      "elapsed time: 0.02499985694885254\n",
+      "elapsed time: 0.024728775024414062\n",
+      "elapsed time: 0.024486541748046875\n",
+      "elapsed time: 0.0250551700592041\n",
+      "elapsed time: 0.024932384490966797\n",
+      "elapsed time: 0.024564743041992188\n",
+      "elapsed time: 0.025130271911621094\n",
+      "elapsed time: 0.024932146072387695\n",
+      "elapsed time: 0.025365114212036133\n",
+      "elapsed time: 0.02483391761779785\n"
+     ]
     }
    ],
    "source": [
@@ -285,15 +782,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 9,
    "id": "e6c22f5f",
-   "metadata": {},
+   "metadata": {
+    "scrolled": true
+   },
    "outputs": [
     {
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "INFO:bokeh.server.server:Starting Bokeh server version 2.4.3 (running on Tornado 6.3.3)\n",
+      "INFO:bokeh.server.server:Starting Bokeh server version 2.4.3 (running on Tornado 6.2)\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"
      ]
@@ -306,7 +805,7 @@
        "  (function() {\n",
        "    const xhr = new XMLHttpRequest()\n",
        "    xhr.responseType = 'blob';\n",
-       "    xhr.open('GET', \"http://localhost:8888/autoload.js?bokeh-autoload-element=1003&bokeh-absolute-url=http://localhost:8888&resources=none\", true);\n",
+       "    xhr.open('GET', \"http://localhost:5006/autoload.js?bokeh-autoload-element=1003&bokeh-absolute-url=http://localhost:5006&resources=none\", true);\n",
        "    xhr.onload = function (event) {\n",
        "      const script = document.createElement('script');\n",
        "      const src = URL.createObjectURL(event.target.response);\n",
@@ -320,7 +819,7 @@
      },
      "metadata": {
       "application/vnd.bokehjs_exec.v0+json": {
-       "server_id": "09119f1b4c254247b9a8a2c9feb5a3bb"
+       "server_id": "07e9a7f5faa6442b9802b3b9bdd4f578"
       }
      },
      "output_type": "display_data"
@@ -329,23 +828,60 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "2023-11-28 11:03:23,128 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-11-28 11:03:23,129 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-11-28 11:03:23,129 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-11-28 11:03:23,129 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-11-28 11:03:23,130 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-11-28 11:03:23,132 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/simulation_tile.py:172}  Minimum frame time=0.017 s\n"
+      "2023-11-29 11:56:39,656 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-11-29 11:56:39,656 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-11-29 11:56:39,657 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-11-29 11:56:39,657 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-11-29 11:56:39,657 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-11-29 11:56:39,658 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-11-29 11:56:39,659 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, 41.79it/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-11-29 11:56:41,822 INFO {/opt/conda/lib/python3.9/site-packages/tornado/web.py:2271}  200 GET / (10.40.110.52) 2176.07ms\n",
+      "2023-11-29 11:56:41,827 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.110.52) 2176.07ms\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "2023-11-29 11:56:42,805 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-11-29 11:56:42,805 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-11-29 11:56:42,806 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-11-29 11:56:42,806 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-11-29 11:56:42,807 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-11-29 11:56:42,807 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-11-29 11:56:42,809 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,  7.03it/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:8888&resources=none (::1) 959.86ms\n",
+      "Rendering a scenario: 100%|██████████| 1/1 [00:00<00:00, 23.66it/s]\n",
+      "WARNING:bokeh.core.validation.check:W-1000 (MISSING_RENDERERS): Plot has no renderers: Figure(id='3822', ...)\n",
       "INFO:bokeh.server.views.ws:WebSocket connection opened\n",
-      "INFO:tornado.access:101 GET /ws?id=7cadc218-0d10-4ad2-90c5-86e1be250248&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.62ms\n",
+      "INFO:tornado.access:200 GET / (10.40.110.52) 816.47ms\n",
+      "INFO:tornado.access:101 GET /ws (10.40.110.52) 821.70ms\n",
       "INFO:bokeh.server.views.ws:ServerConnection created\n"
      ]
     },
@@ -353,21 +889,37 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "2023-11-28 11:03:24,076 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:8888&resources=none (::1) 959.86ms\n",
-      "2023-11-28 11:03:24,083 INFO {/home/sacardoz/nuplan-devkit/tutorials/utils/tutorial_utils.py:267}  Done rendering!\n",
-      "2023-11-28 11:03:24,084 INFO {/home/sacardoz/miniconda3/envs/nuplan/lib/python3.9/site-packages/tornado/web.py:2344}  101 GET /ws?id=7cadc218-0d10-4ad2-90c5-86e1be250248&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.62ms\n"
+      "2023-11-29 11:56:43,620 INFO {/opt/conda/lib/python3.9/site-packages/tornado/web.py:2271}  200 GET / (10.40.110.52) 816.47ms\n",
+      "2023-11-29 11:56:43,625 INFO {/opt/conda/lib/python3.9/site-packages/tornado/web.py:2271}  101 GET /ws (10.40.110.52) 821.70ms\n",
+      "2023-11-29 11:56:43,705 INFO {/home/ehdykhne/Repos/nuplan-devkit/tutorials/utils/tutorial_utils.py:267}  Done rendering!\n"
      ]
     }
    ],
    "source": [
     "from tutorials.utils.tutorial_utils import visualize_history\n",
-    "visualize_history(runner.simulation._history, runner.scenario, bokeh_port=8888)"
+    "visualize_history(runner.simulation._history, runner.scenario, bokeh_port=5006)"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "887a51e2",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e0a12035",
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "nuplan",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   },
@@ -381,7 +933,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.18"
+   "version": "3.9.16"
   }
  },
  "nbformat": 4,
diff --git a/nuplan/planning/script/builders/occlusion_manager_builder.py b/nuplan/planning/script/builders/occlusion_manager_builder.py
index 2b75cc59d7d4c7b922ced2a140268924e04f6d21..2a1b2ae0869496f4a045e8eaf64367434a0898fb 100644
--- a/nuplan/planning/script/builders/occlusion_manager_builder.py
+++ b/nuplan/planning/script/builders/occlusion_manager_builder.py
@@ -4,6 +4,8 @@ from omegaconf import DictConfig
 from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario
 from nuplan.planning.simulation.occlusion.abstract_occlusion_manager import AbstractOcclusionManager
 from nuplan.planning.simulation.occlusion.range_occlusion_manager import RangeOcclusionManager
+from nuplan.planning.simulation.occlusion.complete_shadow_occlusion_manager import CompleteShadowOcclusionManager
+
 
 def build_occlusion_manager(occlusion_cfg: DictConfig, scenario: AbstractScenario) -> AbstractOcclusionManager:
     """
@@ -12,7 +14,13 @@ def build_occlusion_manager(occlusion_cfg: DictConfig, scenario: AbstractScenari
     :param scenario: scenario
     :return occlusion_cfg
     """
-    # Placeholder
-    occlusion_manager: AbstractOcclusionManager = RangeOcclusionManager(scenario)
+    occlusion_manager: AbstractOcclusionManager
+    if occlusion_cfg.manager_type == 'range': #masks everyone further away than a set threshold
+        occlusion_manager = RangeOcclusionManager(scenario)
+    elif occlusion_cfg.manager_type == 'complete_shadow': #masks everyone who is is completely occluded
+        occlusion_manager = CompleteShadowOcclusionManager(scenario)
+    else:
+        raise ValueError(f"Invalid manager_type selected. Got {occlusion_cfg.manager_type}")
+
 
     return occlusion_manager
diff --git a/nuplan/planning/simulation/occlusion/complete_shadow_occlusion_manager.py b/nuplan/planning/simulation/occlusion/complete_shadow_occlusion_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e08e0b2545db103206bd3a141a99b696822c4c9
--- /dev/null
+++ b/nuplan/planning/simulation/occlusion/complete_shadow_occlusion_manager.py
@@ -0,0 +1,99 @@
+from nuplan.common.actor_state.ego_state import EgoState
+from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario
+from nuplan.planning.simulation.observation.observation_type import DetectionsTracks
+from nuplan.planning.simulation.occlusion.abstract_occlusion_manager import AbstractOcclusionManager
+from nuplan.common.actor_state.agent_state import AgentState
+from nuplan.common.actor_state.tracked_objects_types import AGENT_TYPES, STATIC_OBJECT_TYPES, TrackedObjectType
+from shapely.geometry import Polygon, Point
+from shapely.ops import unary_union
+from typing import List
+from collections import deque 
+import math
+import time
+
+
+class CompleteShadowOcclusionManager(AbstractOcclusionManager):
+    """
+    Range occlusion manager. Occludes all objects outside of a given
+    range of the ego.
+    """
+
+    def __init__(
+        self,
+        scenario: AbstractScenario,
+        horizon_threshold: float = 4800, # meters since that is how far a standing human can see unblocked before the curvature of the earth cuts your line of sight
+        min_rad: float = 0.035 # minimum radians that the vehicle must take up to be observed (0.035 = aprox 2 degrees)
+    ):
+        super().__init__(scenario)
+        self.horizon_threshold = horizon_threshold
+        self.min_rad = min_rad
+    def _compute_visible_agents(self, ego_state: EgoState, observations: DetectionsTracks) -> set:
+        """
+        Returns set of track tokens that represents the observations visible to the ego
+        at this time step.
+        """
+
+        # Visible track token set
+
+        return self._determine_occlusions(ego_state.agent, observations.tracked_objects.tracked_objects)
+    
+    def _determine_occlusions(self, observer: AgentState, targets:List[AgentState]) -> set:
+        start = time.time()
+        not_occluded = set() # Visible track token set
+        
+        shadow_polys = deque([])
+        for target in targets: #first we construct shadows
+            if target.tracked_object_type == TrackedObjectType.VEHICLE or target.tracked_object_type == TrackedObjectType.EGO:
+                corners_list = target.box.all_corners() #Return 4 corners of oriented box (FL, RL, RR, FR) Point2D
+                corners = []
+                for corner in corners_list:
+                    corners.append((corner.x - observer.center.x, corner.y - observer.center.y)) #we shift the corners and move them to a different data structure we can play with
+                horizon_points = []
+                range_to_ego = ((target.center.x - observer.center.x)**2 + (target.center.y - observer.center.y)**2)**0.5
+                shadow_range_from_ego = max(self.horizon_threshold, range_to_ego) #if horizon_threshold is big enough, we dont even really need to check which is the max. we can just always take the horizon threshold
+                for corner in corners:
+                    shadow_range_multiplier = shadow_range_from_ego / (corner[0]**2 + corner[1]**2)**0.5
+                    horizon_points.append((corner[0]*shadow_range_multiplier, corner[1]*shadow_range_multiplier))
+                
+                shadow_polygon1 = Polygon([corners[0], horizon_points[0], horizon_points[2], corners[2]])#FL and RR gives one diagonal
+                shadow_polygon2 = Polygon([corners[1], horizon_points[1], horizon_points[3], corners[3]])#RL and FR gives the second diagonal
+                shadow_polys.append(shadow_polygon1)
+                shadow_polys.append(shadow_polygon2)
+                
+        combined_shadow_poly = unary_union(shadow_polys)
+        
+        observer_origin = Point((0,0))
+        for target in targets: #now we check if each target is contained within the shadows
+            corners_list = target.box.all_corners() #Return 4 corners of oriented box (FL, RL, RR, FR) Point2D
+            corners = []
+            for corner in corners_list:
+                corners.append((corner.x - observer.center.x, corner.y - observer.center.y)) #we shift the corners and move them to a different data structure we can play with
+            target_poly = Polygon(corners)
+            
+            diff_poly = target_poly.difference(combined_shadow_poly)
+            
+            hull = unary_union([diff_poly, observer_origin]).convex_hull
+            if isinstance(hull, Polygon):
+                neighbor_1, neighbor_2 = self._get_two_neighbors((0,0), hull)
+                radians = abs(math.atan2(neighbor_1.x*neighbor_2.y - neighbor_1.y*neighbor_2.x, neighbor_1.x*neighbor_2.x + neighbor_1.y*neighbor_2.y ))
+                if radians > self.min_rad:
+                    not_occluded.add(target.metadata.track_token)
+        print('elapsed time:', time.time() - start)
+        return not_occluded
+    
+    def _get_two_neighbors(self, point, polygon) -> List[Point]:
+        """retrieve the two neighboring points of point in the polygon
+        :point: a tuple representing a point of the polygon
+        :polygon: a shapely Polygon
+        return: a tuple of the two points immediately neighbors of point 
+        """
+        points = list(polygon.exterior.coords)
+        ndx = points.index(point)
+        two_neighbors = [points[(ndx-1)%len(points)], points[(ndx+1)%len(points)]]
+        if two_neighbors[0] == point:
+            two_neighbors[0] = points[(ndx-2)%len(points)]
+        if two_neighbors[1] == point:
+            two_neighbors[1] = points[(ndx+2)%len(points)]
+        two_neighbors[0] = Point(two_neighbors[0])
+        two_neighbors[1] = Point(two_neighbors[1])
+        return two_neighbors
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index b60f549208d0cf546f77f41fc765ae35893e11fc..d0601c28675efb45368006d6807f33f0f95c61bb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -46,7 +46,7 @@ SQLAlchemy==1.4.27  # older versions don't work with some table definitions
 sympy  # Use for symbolic algebra
 testbook  # Used in testing jupyter notebooks
 tornado  # Used in nuboard.py
-threadpoolctl==3.2.0s
+threadpoolctl==3.2.0
 tqdm  # Used widely
 typer # Used for cli
 ujson  # Used in serialiation_callback.py