diff --git a/.gitignore b/.gitignore index 5c191a8a..fb693523 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ __pycache__/ # Exclude Sphinx build artifacts -docs/_build/ +docs/_build/* # Exclude images from research folder only examples/research/**/*.png diff --git a/docs/_build/doctrees/base_module.doctree b/docs/_build/doctrees/base_module.doctree deleted file mode 100644 index 59a7002f..00000000 Binary files a/docs/_build/doctrees/base_module.doctree and /dev/null differ diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle deleted file mode 100644 index 75d20be7..00000000 Binary files a/docs/_build/doctrees/environment.pickle and /dev/null differ diff --git a/docs/_build/doctrees/index.doctree b/docs/_build/doctrees/index.doctree deleted file mode 100644 index 97db2e60..00000000 Binary files a/docs/_build/doctrees/index.doctree and /dev/null differ diff --git a/docs/_build/doctrees/modules.doctree b/docs/_build/doctrees/modules.doctree deleted file mode 100644 index 165e6355..00000000 Binary files a/docs/_build/doctrees/modules.doctree and /dev/null differ diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo deleted file mode 100644 index 22092a3e..00000000 --- a/docs/_build/html/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 26366cb90167276ca848f24876f9a13b -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_build/html/.buildinfo.bak b/docs/_build/html/.buildinfo.bak deleted file mode 100644 index deff1aa9..00000000 --- a/docs/_build/html/.buildinfo.bak +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 97424fb823c1bd731ae02c38dfa1f88a -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_build/html/.doctrees/base_module.doctree b/docs/_build/html/.doctrees/base_module.doctree deleted file mode 100644 index 001befea..00000000 Binary files a/docs/_build/html/.doctrees/base_module.doctree and /dev/null differ diff --git a/docs/_build/html/.doctrees/environment.pickle b/docs/_build/html/.doctrees/environment.pickle deleted file mode 100644 index 6da8337b..00000000 Binary files a/docs/_build/html/.doctrees/environment.pickle and /dev/null differ diff --git a/docs/_build/html/.doctrees/index.doctree b/docs/_build/html/.doctrees/index.doctree deleted file mode 100644 index ed53bfeb..00000000 Binary files a/docs/_build/html/.doctrees/index.doctree and /dev/null differ diff --git a/docs/_build/html/.doctrees/modules.doctree b/docs/_build/html/.doctrees/modules.doctree deleted file mode 100644 index 05dbce7e..00000000 Binary files a/docs/_build/html/.doctrees/modules.doctree and /dev/null differ diff --git a/docs/_build/html/_modules/base_module/doc_test.html b/docs/_build/html/_modules/base_module/doc_test.html deleted file mode 100644 index 743dcee9..00000000 --- a/docs/_build/html/_modules/base_module/doc_test.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - base_module.doc_test — Daphme 0.0.1 documentation - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for base_module.doc_test

-
-[docs] -def hello_world(s="Hello World"): - """ - Args: - s (str, optional): Defaults to "Hello World". - """ - print(s) - return
- - -
-[docs] -def goodbye_world(s="Goodbye World"): - """ - Args: - s (str, optional): Defaults to "Goodbye World". - """ - print(s) - return
- - -if __name__ == '__main__': - hello_world("Hello Garry") -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html deleted file mode 100644 index 08490d16..00000000 --- a/docs/_build/html/_modules/index.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - Overview: module code — NOMAD 0.0.1 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_sources/base_module.rst.txt b/docs/_build/html/_sources/base_module.rst.txt deleted file mode 100644 index 691f36b5..00000000 --- a/docs/_build/html/_sources/base_module.rst.txt +++ /dev/null @@ -1,29 +0,0 @@ -base\_module package -==================== - -Submodules ----------- - -base\_module.doc\_test module ------------------------------ - -.. automodule:: base_module.doc_test - :members: - :undoc-members: - :show-inheritance: - -base\_module.setup module -------------------------- - -.. automodule:: base_module.setup - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: base_module - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/_build/html/_sources/index.rst.txt b/docs/_build/html/_sources/index.rst.txt deleted file mode 100644 index 3332079c..00000000 --- a/docs/_build/html/_sources/index.rst.txt +++ /dev/null @@ -1,114 +0,0 @@ -.. NOMAD documentation master file - -NOMAD: Network for Open Mobility Analysis and Data -=================================================== - -.. image:: https://img.shields.io/badge/python-3.9+-blue.svg - :target: https://www.python.org/downloads/ - :alt: Python 3.9+ - -.. image:: https://img.shields.io/badge/license-MIT-green.svg - :target: https://opensource.org/licenses/MIT - :alt: MIT License - -**NOMAD** is an open-source Python library for end-to-end processing of large-scale GPS mobility data. -Part of the NSF-funded NOMAD research infrastructure project, it provides production-ready tools for -mobility data analysis with seamless scaling from local workstations to Spark clusters. - -NOMAD builds on previous software resources—like scikit-mobility, mobilkit, and trackintel—with the -goal of providing a single, production-ready library that covers the entire processing pipeline in a -form suitable for analysis of massive datasets and to aid in the replicability of existing research. - -All functions are implemented in Python with parallel equivalents in PySpark, enabling the same -analysis notebook to run on a workstation or a Spark cluster without API changes. - -Quick Links ------------ - -* **GitHub**: `github.com/Watts-Lab/nomad `_ -* **Website**: `nomad.seas.upenn.edu `_ -* **Installation**: ``pip install git+https://github.com/Watts-Lab/nomad.git`` - -Installation ------------- - -.. code-block:: bash - - pip install git+https://github.com/Watts-Lab/nomad.git - -.. toctree:: - :maxdepth: 2 - :caption: User Guide - :hidden: - - data_ingestion - filtering - tessellation - stop_detection - visit_attribution - metrics - colocation - aggregation - synthetic_data_generation - -.. toctree:: - :maxdepth: 2 - :caption: Examples - :hidden: - - source/benchmarking_of_stop_detection_algorithms - source/lachesis_demo - source/tadbscan_demo - source/hdbscan_demo - source/grid_based_demo - source/poi_synthetic - source/poi_osm - -.. toctree:: - :maxdepth: 2 - :caption: API Reference - :hidden: - - api_reference - -Modules Overview ----------------- - -.. list-table:: - :header-rows: 1 - :widths: 30 70 - - * - Module - - Description - * - :doc:`data_ingestion` - - Read CSV, Parquet, GeoJSON; validate schemas; return Pandas or Spark DataFrames - * - :doc:`filtering` - - Assess coverage; filter by completeness, geography, time; handle projections - * - :doc:`tessellation` - - Map pings to H3, S2, or custom grids for grid-based algorithms - * - :doc:`stop_detection` - - DBSCAN, HDBSCAN, grid-based, and sequential (Lachesis) algorithms - * - :doc:`visit_attribution` - - Frequency and time-window heuristics for home/workplace inference - * - :doc:`metrics` - - Radius of gyration, travel distance, entropy, and related indicators - * - :doc:`colocation` - - Build proximity graphs from POI visits or spatial-temporal proximity - * - :doc:`aggregation` - - Differential privacy, k-anonymity, debiasing, and post-stratification - * - :doc:`synthetic_data_generation` - - EPR models and point process samplers for trajectory generation - -Community & Support -------------------- - -* **Issues**: Report bugs on `GitHub Issues `_ -* **Contribute**: See our contribution guidelines -* **License**: MIT © University of Pennsylvania 2025 - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/_build/html/_sources/modules.rst.txt b/docs/_build/html/_sources/modules.rst.txt deleted file mode 100644 index 2a58e0c1..00000000 --- a/docs/_build/html/_sources/modules.rst.txt +++ /dev/null @@ -1,7 +0,0 @@ -src -=== - -.. toctree:: - :maxdepth: 4 - - base_module diff --git a/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js b/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js deleted file mode 100644 index 81415803..00000000 --- a/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js +++ /dev/null @@ -1,123 +0,0 @@ -/* Compatability shim for jQuery and underscores.js. - * - * Copyright Sphinx contributors - * Released under the two clause BSD licence - */ - -/** - * small helper function to urldecode strings - * - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL - */ -jQuery.urldecode = function(x) { - if (!x) { - return x - } - return decodeURIComponent(x.replace(/\+/g, ' ')); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} diff --git a/docs/_build/html/_static/basic.css b/docs/_build/html/_static/basic.css deleted file mode 100644 index 7ebbd6d0..00000000 --- a/docs/_build/html/_static/basic.css +++ /dev/null @@ -1,914 +0,0 @@ -/* - * Sphinx stylesheet -- basic theme. - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -div.section::after { - display: block; - content: ''; - clear: left; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox form.search { - overflow: hidden; -} - -div.sphinxsidebar #searchbox input[type="text"] { - float: left; - width: 80%; - padding: 0.25em; - box-sizing: border-box; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - float: left; - width: 20%; - border-left: none; - padding: 0.25em; - box-sizing: border-box; -} - - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin-top: 10px; -} - -ul.search li { - padding: 5px 0; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li p.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body { - min-width: 360px; - max-width: 800px; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -a:visited { - color: #551A8B; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, figure.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, figure.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, figure.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -img.align-default, figure.align-default, .figure.align-default { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-default { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar, -aside.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px; - background-color: #ffe; - width: 40%; - float: right; - clear: right; - overflow-x: auto; -} - -p.sidebar-title { - font-weight: bold; -} - -nav.contents, -aside.topic, -div.admonition, div.topic, blockquote { - clear: left; -} - -/* -- topics ---------------------------------------------------------------- */ - -nav.contents, -aside.topic, -div.topic { - border: 1px solid #ccc; - padding: 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- content of sidebars/topics/admonitions -------------------------------- */ - -div.sidebar > :last-child, -aside.sidebar > :last-child, -nav.contents > :last-child, -aside.topic > :last-child, -div.topic > :last-child, -div.admonition > :last-child { - margin-bottom: 0; -} - -div.sidebar::after, -aside.sidebar::after, -nav.contents::after, -aside.topic::after, -div.topic::after, -div.admonition::after, -blockquote::after { - display: block; - content: ''; - clear: both; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - margin-top: 10px; - margin-bottom: 10px; - border: 0; - border-collapse: collapse; -} - -table.align-center { - margin-left: auto; - margin-right: auto; -} - -table.align-default { - margin-left: auto; - margin-right: auto; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -th > :first-child, -td > :first-child { - margin-top: 0px; -} - -th > :last-child, -td > :last-child { - margin-bottom: 0px; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure, figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption, figcaption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number, -figcaption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text, -figcaption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- hlist styles ---------------------------------------------------------- */ - -table.hlist { - margin: 1em 0; -} - -table.hlist td { - vertical-align: top; -} - -/* -- object description styles --------------------------------------------- */ - -.sig { - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; -} - -.sig-name, code.descname { - background-color: transparent; - font-weight: bold; -} - -.sig-name { - font-size: 1.1em; -} - -code.descname { - font-size: 1.2em; -} - -.sig-prename, code.descclassname { - background-color: transparent; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.sig-param.n { - font-style: italic; -} - -/* C++ specific styling */ - -.sig-inline.c-texpr, -.sig-inline.cpp-texpr { - font-family: unset; -} - -.sig.c .k, .sig.c .kt, -.sig.cpp .k, .sig.cpp .kt { - color: #0033B3; -} - -.sig.c .m, -.sig.cpp .m { - color: #1750EB; -} - -.sig.c .s, .sig.c .sc, -.sig.cpp .s, .sig.cpp .sc { - color: #067D17; -} - - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -:not(li) > ol > li:first-child > :first-child, -:not(li) > ul > li:first-child > :first-child { - margin-top: 0px; -} - -:not(li) > ol > li:last-child > :last-child, -:not(li) > ul > li:last-child > :last-child { - margin-bottom: 0px; -} - -ol.simple ol p, -ol.simple ul p, -ul.simple ol p, -ul.simple ul p { - margin-top: 0; -} - -ol.simple > li:not(:first-child) > p, -ul.simple > li:not(:first-child) > p { - margin-top: 0; -} - -ol.simple p, -ul.simple p { - margin-bottom: 0; -} - -aside.footnote > span, -div.citation > span { - float: left; -} -aside.footnote > span:last-of-type, -div.citation > span:last-of-type { - padding-right: 0.5em; -} -aside.footnote > p { - margin-left: 2em; -} -div.citation > p { - margin-left: 4em; -} -aside.footnote > p:last-of-type, -div.citation > p:last-of-type { - margin-bottom: 0em; -} -aside.footnote > p:last-of-type:after, -div.citation > p:last-of-type:after { - content: ""; - clear: both; -} - -dl.field-list { - display: grid; - grid-template-columns: fit-content(30%) auto; -} - -dl.field-list > dt { - font-weight: bold; - word-break: break-word; - padding-left: 0.5em; - padding-right: 5px; -} - -dl.field-list > dd { - padding-left: 0.5em; - margin-top: 0em; - margin-left: 0em; - margin-bottom: 0em; -} - -dl { - margin-bottom: 15px; -} - -dd > :first-child { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -.sig dd { - margin-top: 0px; - margin-bottom: 0px; -} - -.sig dl { - margin-top: 0px; - margin-bottom: 0px; -} - -dl > dd:last-child, -dl > dd:last-child > :last-child { - margin-bottom: 0; -} - -dt:target, span.highlighted { - background-color: #fbe54e; -} - -rect.highlighted { - fill: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -.classifier:before { - font-style: normal; - margin: 0 0.5em; - content: ":"; - display: inline-block; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -.translated { - background-color: rgba(207, 255, 207, 0.2) -} - -.untranslated { - background-color: rgba(255, 207, 207, 0.2) -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -pre, div[class*="highlight-"] { - clear: both; -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; - white-space: nowrap; -} - -div[class*="highlight-"] { - margin: 1em 0; -} - -td.linenos pre { - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - display: block; -} - -table.highlighttable tbody { - display: block; -} - -table.highlighttable tr { - display: flex; -} - -table.highlighttable td { - margin: 0; - padding: 0; -} - -table.highlighttable td.linenos { - padding-right: 0.5em; -} - -table.highlighttable td.code { - flex: 1; - overflow: hidden; -} - -.highlight .hll { - display: block; -} - -div.highlight pre, -table.highlighttable pre { - margin: 0; -} - -div.code-block-caption + div { - margin-top: 0; -} - -div.code-block-caption { - margin-top: 1em; - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -table.highlighttable td.linenos, -span.linenos, -div.highlight span.gp { /* gp: Generic.Prompt */ - user-select: none; - -webkit-user-select: text; /* Safari fallback only */ - -webkit-user-select: none; /* Chrome/Safari */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* IE10+ */ -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - margin: 1em 0; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: absolute; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/docs/_build/html/_static/css/badge_only.css b/docs/_build/html/_static/css/badge_only.css deleted file mode 100644 index 88ba55b9..00000000 --- a/docs/_build/html/_static/css/badge_only.css +++ /dev/null @@ -1 +0,0 @@ -.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff deleted file mode 100644 index 6cb60000..00000000 Binary files a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 deleted file mode 100644 index 7059e231..00000000 Binary files a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff deleted file mode 100644 index f815f63f..00000000 Binary files a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 deleted file mode 100644 index f2c76e5b..00000000 Binary files a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot b/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca9..00000000 Binary files a/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg b/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845e..00000000 --- a/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,2671 +0,0 @@ - - - - -Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 - By ,,, -Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf b/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2f..00000000 Binary files a/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a4..00000000 Binary files a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc60..00000000 Binary files a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff deleted file mode 100644 index 88ad05b9..00000000 Binary files a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 deleted file mode 100644 index c4e3d804..00000000 Binary files a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold.woff b/docs/_build/html/_static/css/fonts/lato-bold.woff deleted file mode 100644 index c6dff51f..00000000 Binary files a/docs/_build/html/_static/css/fonts/lato-bold.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold.woff2 b/docs/_build/html/_static/css/fonts/lato-bold.woff2 deleted file mode 100644 index bb195043..00000000 Binary files a/docs/_build/html/_static/css/fonts/lato-bold.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff deleted file mode 100644 index 76114bc0..00000000 Binary files a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 deleted file mode 100644 index 3404f37e..00000000 Binary files a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal.woff b/docs/_build/html/_static/css/fonts/lato-normal.woff deleted file mode 100644 index ae1307ff..00000000 Binary files a/docs/_build/html/_static/css/fonts/lato-normal.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal.woff2 b/docs/_build/html/_static/css/fonts/lato-normal.woff2 deleted file mode 100644 index 3bf98433..00000000 Binary files a/docs/_build/html/_static/css/fonts/lato-normal.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/theme.css b/docs/_build/html/_static/css/theme.css deleted file mode 100644 index 0f14f106..00000000 --- a/docs/_build/html/_static/css/theme.css +++ /dev/null @@ -1,4 +0,0 @@ -html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/_build/html/_static/doctools.js b/docs/_build/html/_static/doctools.js deleted file mode 100644 index 0398ebb9..00000000 --- a/docs/_build/html/_static/doctools.js +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Base JavaScript utilities for all Sphinx HTML documentation. - */ -"use strict"; - -const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ - "TEXTAREA", - "INPUT", - "SELECT", - "BUTTON", -]); - -const _ready = (callback) => { - if (document.readyState !== "loading") { - callback(); - } else { - document.addEventListener("DOMContentLoaded", callback); - } -}; - -/** - * Small JavaScript module for the documentation. - */ -const Documentation = { - init: () => { - Documentation.initDomainIndexTable(); - Documentation.initOnKeyListeners(); - }, - - /** - * i18n support - */ - TRANSLATIONS: {}, - PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), - LOCALE: "unknown", - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext: (string) => { - const translated = Documentation.TRANSLATIONS[string]; - switch (typeof translated) { - case "undefined": - return string; // no translation - case "string": - return translated; // translation exists - default: - return translated[0]; // (singular, plural) translation tuple exists - } - }, - - ngettext: (singular, plural, n) => { - const translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated !== "undefined") - return translated[Documentation.PLURAL_EXPR(n)]; - return n === 1 ? singular : plural; - }, - - addTranslations: (catalog) => { - Object.assign(Documentation.TRANSLATIONS, catalog.messages); - Documentation.PLURAL_EXPR = new Function( - "n", - `return (${catalog.plural_expr})` - ); - Documentation.LOCALE = catalog.locale; - }, - - /** - * helper function to focus on search bar - */ - focusSearchBar: () => { - document.querySelectorAll("input[name=q]")[0]?.focus(); - }, - - /** - * Initialise the domain index toggle buttons - */ - initDomainIndexTable: () => { - const toggler = (el) => { - const idNumber = el.id.substr(7); - const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); - if (el.src.substr(-9) === "minus.png") { - el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; - toggledRows.forEach((el) => (el.style.display = "none")); - } else { - el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; - toggledRows.forEach((el) => (el.style.display = "")); - } - }; - - const togglerElements = document.querySelectorAll("img.toggler"); - togglerElements.forEach((el) => - el.addEventListener("click", (event) => toggler(event.currentTarget)) - ); - togglerElements.forEach((el) => (el.style.display = "")); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); - }, - - initOnKeyListeners: () => { - // only install a listener if it is really needed - if ( - !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && - !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS - ) - return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; - // bail with special keys - if (event.altKey || event.ctrlKey || event.metaKey) return; - - if (!event.shiftKey) { - switch (event.key) { - case "ArrowLeft": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const prevLink = document.querySelector('link[rel="prev"]'); - if (prevLink && prevLink.href) { - window.location.href = prevLink.href; - event.preventDefault(); - } - break; - case "ArrowRight": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const nextLink = document.querySelector('link[rel="next"]'); - if (nextLink && nextLink.href) { - window.location.href = nextLink.href; - event.preventDefault(); - } - break; - } - } - - // some keyboard layouts may need Shift to get / - switch (event.key) { - case "/": - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; - Documentation.focusSearchBar(); - event.preventDefault(); - } - }); - }, -}; - -// quick alias for translations -const _ = Documentation.gettext; - -_ready(Documentation.init); diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js deleted file mode 100644 index d1f22919..00000000 --- a/docs/_build/html/_static/documentation_options.js +++ /dev/null @@ -1,13 +0,0 @@ -const DOCUMENTATION_OPTIONS = { - VERSION: '0.0.1', - LANGUAGE: 'en', - COLLAPSE_INDEX: false, - BUILDER: 'html', - FILE_SUFFIX: '.html', - LINK_SUFFIX: '.html', - HAS_SOURCE: true, - SOURCELINK_SUFFIX: '.txt', - NAVIGATION_WITH_KEYS: false, - SHOW_SEARCH_SUMMARY: true, - ENABLE_SEARCH_SHORTCUTS: true, -}; \ No newline at end of file diff --git a/docs/_build/html/_static/file.png b/docs/_build/html/_static/file.png deleted file mode 100644 index a858a410..00000000 Binary files a/docs/_build/html/_static/file.png and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.eot b/docs/_build/html/_static/fonts/Lato/lato-bold.eot deleted file mode 100644 index 3361183a..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bold.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.ttf b/docs/_build/html/_static/fonts/Lato/lato-bold.ttf deleted file mode 100644 index 29f691d5..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bold.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.woff b/docs/_build/html/_static/fonts/Lato/lato-bold.woff deleted file mode 100644 index c6dff51f..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bold.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.woff2 b/docs/_build/html/_static/fonts/Lato/lato-bold.woff2 deleted file mode 100644 index bb195043..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bold.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.eot b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.eot deleted file mode 100644 index 3d415493..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.ttf b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.ttf deleted file mode 100644 index f402040b..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff deleted file mode 100644 index 88ad05b9..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff2 deleted file mode 100644 index c4e3d804..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.eot b/docs/_build/html/_static/fonts/Lato/lato-italic.eot deleted file mode 100644 index 3f826421..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-italic.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.ttf b/docs/_build/html/_static/fonts/Lato/lato-italic.ttf deleted file mode 100644 index b4bfc9b2..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-italic.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.woff b/docs/_build/html/_static/fonts/Lato/lato-italic.woff deleted file mode 100644 index 76114bc0..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-italic.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.woff2 b/docs/_build/html/_static/fonts/Lato/lato-italic.woff2 deleted file mode 100644 index 3404f37e..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-italic.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.eot b/docs/_build/html/_static/fonts/Lato/lato-regular.eot deleted file mode 100644 index 11e3f2a5..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-regular.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.ttf b/docs/_build/html/_static/fonts/Lato/lato-regular.ttf deleted file mode 100644 index 74decd9e..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-regular.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.woff b/docs/_build/html/_static/fonts/Lato/lato-regular.woff deleted file mode 100644 index ae1307ff..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-regular.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.woff2 b/docs/_build/html/_static/fonts/Lato/lato-regular.woff2 deleted file mode 100644 index 3bf98433..00000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-regular.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot deleted file mode 100644 index 79dc8efe..00000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf deleted file mode 100644 index df5d1df2..00000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff deleted file mode 100644 index 6cb60000..00000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 deleted file mode 100644 index 7059e231..00000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot deleted file mode 100644 index 2f7ca78a..00000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf deleted file mode 100644 index eb52a790..00000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff deleted file mode 100644 index f815f63f..00000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 deleted file mode 100644 index f2c76e5b..00000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/jquery.js b/docs/_build/html/_static/jquery.js deleted file mode 100644 index c4c6022f..00000000 --- a/docs/_build/html/_static/jquery.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/docs/_build/html/_static/js/html5shiv.min.js b/docs/_build/html/_static/js/html5shiv.min.js deleted file mode 100644 index cd1c674f..00000000 --- a/docs/_build/html/_static/js/html5shiv.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/** -* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed -*/ -!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/docs/_build/html/_static/js/theme.js b/docs/_build/html/_static/js/theme.js deleted file mode 100644 index 1fddb6ee..00000000 --- a/docs/_build/html/_static/js/theme.js +++ /dev/null @@ -1 +0,0 @@ -!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name)); - - const languagesHTML = ` -
-
Languages
- ${languages - .map( - (translation) => ` -
- ${translation.language.code} -
- `, - ) - .join("\n")} -
- `; - return languagesHTML; - } - - function renderVersions(config) { - if (!config.versions.active.length) { - return ""; - } - const versionsHTML = ` -
-
Versions
- ${config.versions.active - .map( - (version) => ` -
- ${version.slug} -
- `, - ) - .join("\n")} -
- `; - return versionsHTML; - } - - function renderDownloads(config) { - if (!Object.keys(config.versions.current.downloads).length) { - return ""; - } - const downloadsNameDisplay = { - pdf: "PDF", - epub: "Epub", - htmlzip: "HTML", - }; - - const downloadsHTML = ` -
-
Downloads
- ${Object.entries(config.versions.current.downloads) - .map( - ([name, url]) => ` -
- ${downloadsNameDisplay[name]} -
- `, - ) - .join("\n")} -
- `; - return downloadsHTML; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const flyout = ` -
- - Read the Docs - v: ${config.versions.current.slug} - - -
-
- ${renderLanguages(config)} - ${renderVersions(config)} - ${renderDownloads(config)} -
-
On Read the Docs
-
- Project Home -
-
- Builds -
-
- Downloads -
-
-
-
Search
-
-
- -
-
-
-
- - Hosted by Read the Docs - -
-
- `; - - // Inject the generated flyout into the body HTML element. - document.body.insertAdjacentHTML("beforeend", flyout); - - // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. - document - .querySelector("#flyout-search-form") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); - }) -} - -if (themeLanguageSelector || themeVersionSelector) { - function onSelectorSwitch(event) { - const option = event.target.selectedIndex; - const item = event.target.options[option]; - window.location.href = item.dataset.url; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const versionSwitch = document.querySelector( - "div.switch-menus > div.version-switch", - ); - if (themeVersionSelector) { - let versions = config.versions.active; - if (config.versions.current.hidden || config.versions.current.type === "external") { - versions.unshift(config.versions.current); - } - const versionSelect = ` - - `; - - versionSwitch.innerHTML = versionSelect; - versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - - const languageSwitch = document.querySelector( - "div.switch-menus > div.language-switch", - ); - - if (themeLanguageSelector) { - if (config.projects.translations.length) { - // Add the current language to the options on the selector - let languages = config.projects.translations.concat( - config.projects.current, - ); - languages = languages.sort((a, b) => - a.language.name.localeCompare(b.language.name), - ); - - const languageSelect = ` - - `; - - languageSwitch.innerHTML = languageSelect; - languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - else { - languageSwitch.remove(); - } - } - }); -} - -document.addEventListener("readthedocs-addons-data-ready", function (event) { - // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. - document - .querySelector("[role='search'] input") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); -}); \ No newline at end of file diff --git a/docs/_build/html/_static/language_data.js b/docs/_build/html/_static/language_data.js deleted file mode 100644 index c7fe6c6f..00000000 --- a/docs/_build/html/_static/language_data.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - * This script contains the language-specific data used by searchtools.js, - * namely the list of stopwords, stemmer, scorer and splitter. - */ - -var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; - - -/* Non-minified version is copied as a separate JS file, if available */ - -/** - * Porter Stemmer - */ -var Stemmer = function() { - - var step2list = { - ational: 'ate', - tional: 'tion', - enci: 'ence', - anci: 'ance', - izer: 'ize', - bli: 'ble', - alli: 'al', - entli: 'ent', - eli: 'e', - ousli: 'ous', - ization: 'ize', - ation: 'ate', - ator: 'ate', - alism: 'al', - iveness: 'ive', - fulness: 'ful', - ousness: 'ous', - aliti: 'al', - iviti: 'ive', - biliti: 'ble', - logi: 'log' - }; - - var step3list = { - icate: 'ic', - ative: '', - alize: 'al', - iciti: 'ic', - ical: 'ic', - ful: '', - ness: '' - }; - - var c = "[^aeiou]"; // consonant - var v = "[aeiouy]"; // vowel - var C = c + "[^aeiouy]*"; // consonant sequence - var V = v + "[aeiou]*"; // vowel sequence - - var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 - var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 - var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 - var s_v = "^(" + C + ")?" + v; // vowel in stem - - this.stemWord = function (w) { - var stem; - var suffix; - var firstch; - var origword = w; - - if (w.length < 3) - return w; - - var re; - var re2; - var re3; - var re4; - - firstch = w.substr(0,1); - if (firstch == "y") - w = firstch.toUpperCase() + w.substr(1); - - // Step 1a - re = /^(.+?)(ss|i)es$/; - re2 = /^(.+?)([^s])s$/; - - if (re.test(w)) - w = w.replace(re,"$1$2"); - else if (re2.test(w)) - w = w.replace(re2,"$1$2"); - - // Step 1b - re = /^(.+?)eed$/; - re2 = /^(.+?)(ed|ing)$/; - if (re.test(w)) { - var fp = re.exec(w); - re = new RegExp(mgr0); - if (re.test(fp[1])) { - re = /.$/; - w = w.replace(re,""); - } - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - re2 = new RegExp(s_v); - if (re2.test(stem)) { - w = stem; - re2 = /(at|bl|iz)$/; - re3 = new RegExp("([^aeiouylsz])\\1$"); - re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re2.test(w)) - w = w + "e"; - else if (re3.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - else if (re4.test(w)) - w = w + "e"; - } - } - - // Step 1c - re = /^(.+?)y$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(s_v); - if (re.test(stem)) - w = stem + "i"; - } - - // Step 2 - re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step2list[suffix]; - } - - // Step 3 - re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step3list[suffix]; - } - - // Step 4 - re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; - re2 = /^(.+?)(s|t)(ion)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - if (re.test(stem)) - w = stem; - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1] + fp[2]; - re2 = new RegExp(mgr1); - if (re2.test(stem)) - w = stem; - } - - // Step 5 - re = /^(.+?)e$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - re2 = new RegExp(meq1); - re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) - w = stem; - } - re = /ll$/; - re2 = new RegExp(mgr1); - if (re.test(w) && re2.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - - // and turn initial Y back to y - if (firstch == "y") - w = firstch.toLowerCase() + w.substr(1); - return w; - } -} - diff --git a/docs/_build/html/_static/minus.png b/docs/_build/html/_static/minus.png deleted file mode 100644 index d96755fd..00000000 Binary files a/docs/_build/html/_static/minus.png and /dev/null differ diff --git a/docs/_build/html/_static/plus.png b/docs/_build/html/_static/plus.png deleted file mode 100644 index 7107cec9..00000000 Binary files a/docs/_build/html/_static/plus.png and /dev/null differ diff --git a/docs/_build/html/_static/pygments.css b/docs/_build/html/_static/pygments.css deleted file mode 100644 index 84ab3030..00000000 --- a/docs/_build/html/_static/pygments.css +++ /dev/null @@ -1,75 +0,0 @@ -pre { line-height: 125%; } -td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -.highlight .hll { background-color: #ffffcc } -.highlight { background: #f8f8f8; } -.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { color: #008000; font-weight: bold } /* Keyword */ -.highlight .o { color: #666666 } /* Operator */ -.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ -.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #9C6500 } /* Comment.Preproc */ -.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ -.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ -.highlight .gr { color: #E40000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #008400 } /* Generic.Inserted */ -.highlight .go { color: #717171 } /* Generic.Output */ -.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0044DD } /* Generic.Traceback */ -.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #008000 } /* Keyword.Pseudo */ -.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #B00040 } /* Keyword.Type */ -.highlight .m { color: #666666 } /* Literal.Number */ -.highlight .s { color: #BA2121 } /* Literal.String */ -.highlight .na { color: #687822 } /* Name.Attribute */ -.highlight .nb { color: #008000 } /* Name.Builtin */ -.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ -.highlight .no { color: #880000 } /* Name.Constant */ -.highlight .nd { color: #AA22FF } /* Name.Decorator */ -.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #0000FF } /* Name.Function */ -.highlight .nl { color: #767600 } /* Name.Label */ -.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #19177C } /* Name.Variable */ -.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mb { color: #666666 } /* Literal.Number.Bin */ -.highlight .mf { color: #666666 } /* Literal.Number.Float */ -.highlight .mh { color: #666666 } /* Literal.Number.Hex */ -.highlight .mi { color: #666666 } /* Literal.Number.Integer */ -.highlight .mo { color: #666666 } /* Literal.Number.Oct */ -.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ -.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ -.highlight .sc { color: #BA2121 } /* Literal.String.Char */ -.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ -.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ -.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ -.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ -.highlight .sx { color: #008000 } /* Literal.String.Other */ -.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ -.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ -.highlight .ss { color: #19177C } /* Literal.String.Symbol */ -.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ -.highlight .fm { color: #0000FF } /* Name.Function.Magic */ -.highlight .vc { color: #19177C } /* Name.Variable.Class */ -.highlight .vg { color: #19177C } /* Name.Variable.Global */ -.highlight .vi { color: #19177C } /* Name.Variable.Instance */ -.highlight .vm { color: #19177C } /* Name.Variable.Magic */ -.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_build/html/_static/searchtools.js b/docs/_build/html/_static/searchtools.js deleted file mode 100644 index 2c774d17..00000000 --- a/docs/_build/html/_static/searchtools.js +++ /dev/null @@ -1,632 +0,0 @@ -/* - * Sphinx JavaScript utilities for the full-text search. - */ -"use strict"; - -/** - * Simple result scoring code. - */ -if (typeof Scorer === "undefined") { - var Scorer = { - // Implement the following function to further tweak the score for each result - // The function takes a result array [docname, title, anchor, descr, score, filename] - // and returns the new score. - /* - score: result => { - const [docname, title, anchor, descr, score, filename, kind] = result - return score - }, - */ - - // query matches the full name of an object - objNameMatch: 11, - // or matches in the last dotted part of the object name - objPartialMatch: 6, - // Additive scores depending on the priority of the object - objPrio: { - 0: 15, // used to be importantResults - 1: 5, // used to be objectResults - 2: -5, // used to be unimportantResults - }, - // Used when the priority is not in the mapping. - objPrioDefault: 0, - - // query found in title - title: 15, - partialTitle: 7, - // query found in terms - term: 5, - partialTerm: 2, - }; -} - -// Global search result kind enum, used by themes to style search results. -class SearchResultKind { - static get index() { return "index"; } - static get object() { return "object"; } - static get text() { return "text"; } - static get title() { return "title"; } -} - -const _removeChildren = (element) => { - while (element && element.lastChild) element.removeChild(element.lastChild); -}; - -/** - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping - */ -const _escapeRegExp = (string) => - string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string - -const _displayItem = (item, searchTerms, highlightTerms) => { - const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; - const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; - const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; - const contentRoot = document.documentElement.dataset.content_root; - - const [docName, title, anchor, descr, score, _filename, kind] = item; - - let listItem = document.createElement("li"); - // Add a class representing the item's type: - // can be used by a theme's CSS selector for styling - // See SearchResultKind for the class names. - listItem.classList.add(`kind-${kind}`); - let requestUrl; - let linkUrl; - if (docBuilder === "dirhtml") { - // dirhtml builder - let dirname = docName + "/"; - if (dirname.match(/\/index\/$/)) - dirname = dirname.substring(0, dirname.length - 6); - else if (dirname === "index/") dirname = ""; - requestUrl = contentRoot + dirname; - linkUrl = requestUrl; - } else { - // normal html builders - requestUrl = contentRoot + docName + docFileSuffix; - linkUrl = docName + docLinkSuffix; - } - let linkEl = listItem.appendChild(document.createElement("a")); - linkEl.href = linkUrl + anchor; - linkEl.dataset.score = score; - linkEl.innerHTML = title; - if (descr) { - listItem.appendChild(document.createElement("span")).innerHTML = - " (" + descr + ")"; - // highlight search terms in the description - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); - } - else if (showSearchSummary) - fetch(requestUrl) - .then((responseData) => responseData.text()) - .then((data) => { - if (data) - listItem.appendChild( - Search.makeSearchSummary(data, searchTerms, anchor) - ); - // highlight search terms in the summary - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); - }); - Search.output.appendChild(listItem); -}; -const _finishSearch = (resultCount) => { - Search.stopPulse(); - Search.title.innerText = _("Search Results"); - if (!resultCount) - Search.status.innerText = Documentation.gettext( - "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." - ); - else - Search.status.innerText = Documentation.ngettext( - "Search finished, found one page matching the search query.", - "Search finished, found ${resultCount} pages matching the search query.", - resultCount, - ).replace('${resultCount}', resultCount); -}; -const _displayNextItem = ( - results, - resultCount, - searchTerms, - highlightTerms, -) => { - // results left, load the summary and display it - // this is intended to be dynamic (don't sub resultsCount) - if (results.length) { - _displayItem(results.pop(), searchTerms, highlightTerms); - setTimeout( - () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), - 5 - ); - } - // search finished, update title and status message - else _finishSearch(resultCount); -}; -// Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. -// Order the results by score (in opposite order of appearance, since the -// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. -const _orderResultsByScoreThenName = (a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; -}; - -/** - * Default splitQuery function. Can be overridden in ``sphinx.search`` with a - * custom function per language. - * - * The regular expression works by splitting the string on consecutive characters - * that are not Unicode letters, numbers, underscores, or emoji characters. - * This is the same as ``\W+`` in Python, preserving the surrogate pair area. - */ -if (typeof splitQuery === "undefined") { - var splitQuery = (query) => query - .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) - .filter(term => term) // remove remaining empty strings -} - -/** - * Search Module - */ -const Search = { - _index: null, - _queued_query: null, - _pulse_status: -1, - - htmlToText: (htmlString, anchor) => { - const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - for (const removalQuery of [".headerlink", "script", "style"]) { - htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); - } - if (anchor) { - const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); - if (anchorContent) return anchorContent.textContent; - - console.warn( - `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` - ); - } - - // if anchor not specified or not found, fall back to main content - const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent) return docContent.textContent; - - console.warn( - "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." - ); - return ""; - }, - - init: () => { - const query = new URLSearchParams(window.location.search).get("q"); - document - .querySelectorAll('input[name="q"]') - .forEach((el) => (el.value = query)); - if (query) Search.performSearch(query); - }, - - loadIndex: (url) => - (document.body.appendChild(document.createElement("script")).src = url), - - setIndex: (index) => { - Search._index = index; - if (Search._queued_query !== null) { - const query = Search._queued_query; - Search._queued_query = null; - Search.query(query); - } - }, - - hasIndex: () => Search._index !== null, - - deferQuery: (query) => (Search._queued_query = query), - - stopPulse: () => (Search._pulse_status = -1), - - startPulse: () => { - if (Search._pulse_status >= 0) return; - - const pulse = () => { - Search._pulse_status = (Search._pulse_status + 1) % 4; - Search.dots.innerText = ".".repeat(Search._pulse_status); - if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); - }; - pulse(); - }, - - /** - * perform a search for something (or wait until index is loaded) - */ - performSearch: (query) => { - // create the required interface elements - const searchText = document.createElement("h2"); - searchText.textContent = _("Searching"); - const searchSummary = document.createElement("p"); - searchSummary.classList.add("search-summary"); - searchSummary.innerText = ""; - const searchList = document.createElement("ul"); - searchList.setAttribute("role", "list"); - searchList.classList.add("search"); - - const out = document.getElementById("search-results"); - Search.title = out.appendChild(searchText); - Search.dots = Search.title.appendChild(document.createElement("span")); - Search.status = out.appendChild(searchSummary); - Search.output = out.appendChild(searchList); - - const searchProgress = document.getElementById("search-progress"); - // Some themes don't use the search progress node - if (searchProgress) { - searchProgress.innerText = _("Preparing search..."); - } - Search.startPulse(); - - // index already loaded, the browser was quick! - if (Search.hasIndex()) Search.query(query); - else Search.deferQuery(query); - }, - - _parseQuery: (query) => { - // stem the search terms and add them to the correct list - const stemmer = new Stemmer(); - const searchTerms = new Set(); - const excludedTerms = new Set(); - const highlightTerms = new Set(); - const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); - splitQuery(query.trim()).forEach((queryTerm) => { - const queryTermLower = queryTerm.toLowerCase(); - - // maybe skip this "word" - // stopwords array is from language_data.js - if ( - stopwords.indexOf(queryTermLower) !== -1 || - queryTerm.match(/^\d+$/) - ) - return; - - // stem the word - let word = stemmer.stemWord(queryTermLower); - // select the correct list - if (word[0] === "-") excludedTerms.add(word.substr(1)); - else { - searchTerms.add(word); - highlightTerms.add(queryTermLower); - } - }); - - if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js - localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) - } - - // console.debug("SEARCH: searching for:"); - // console.info("required: ", [...searchTerms]); - // console.info("excluded: ", [...excludedTerms]); - - return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; - }, - - /** - * execute search (requires search index to be loaded) - */ - _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - - // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename, kind]. - const normalResults = []; - const nonMainIndexResults = []; - - _removeChildren(document.getElementById("search-progress")); - - const queryLower = query.toLowerCase().trim(); - for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { - for (const [file, id] of foundTitles) { - const score = Math.round(Scorer.title * queryLower.length / title.length); - const boost = titles[file] === title ? 1 : 0; // add a boost for document titles - normalResults.push([ - docNames[file], - titles[file] !== title ? `${titles[file]} > ${title}` : title, - id !== null ? "#" + id : "", - null, - score + boost, - filenames[file], - SearchResultKind.title, - ]); - } - } - } - - // search for explicit entries in index directives - for (const [entry, foundEntries] of Object.entries(indexEntries)) { - if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { - for (const [file, id, isMain] of foundEntries) { - const score = Math.round(100 * queryLower.length / entry.length); - const result = [ - docNames[file], - titles[file], - id ? "#" + id : "", - null, - score, - filenames[file], - SearchResultKind.index, - ]; - if (isMain) { - normalResults.push(result); - } else { - nonMainIndexResults.push(result); - } - } - } - } - - // lookup as object - objectTerms.forEach((term) => - normalResults.push(...Search.performObjectSearch(term, objectTerms)) - ); - - // lookup as search terms in fulltext - normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); - - // let the scorer override scores with a custom scoring function - if (Scorer.score) { - normalResults.forEach((item) => (item[4] = Scorer.score(item))); - nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); - } - - // Sort each group of results by score and then alphabetically by name. - normalResults.sort(_orderResultsByScoreThenName); - nonMainIndexResults.sort(_orderResultsByScoreThenName); - - // Combine the result groups in (reverse) order. - // Non-main index entries are typically arbitrary cross-references, - // so display them after other results. - let results = [...nonMainIndexResults, ...normalResults]; - - // remove duplicate search results - // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept - let seen = new Set(); - results = results.reverse().reduce((acc, result) => { - let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); - if (!seen.has(resultStr)) { - acc.push(result); - seen.add(resultStr); - } - return acc; - }, []); - - return results.reverse(); - }, - - query: (query) => { - const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); - const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); - - // for debugging - //Search.lastresults = results.slice(); // a copy - // console.info("search results:", Search.lastresults); - - // print the results - _displayNextItem(results, results.length, searchTerms, highlightTerms); - }, - - /** - * search for object names - */ - performObjectSearch: (object, objectTerms) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const objects = Search._index.objects; - const objNames = Search._index.objnames; - const titles = Search._index.titles; - - const results = []; - - const objectSearchCallback = (prefix, match) => { - const name = match[4] - const fullname = (prefix ? prefix + "." : "") + name; - const fullnameLower = fullname.toLowerCase(); - if (fullnameLower.indexOf(object) < 0) return; - - let score = 0; - const parts = fullnameLower.split("."); - - // check for different match types: exact matches of full name or - // "last name" (i.e. last dotted part) - if (fullnameLower === object || parts.slice(-1)[0] === object) - score += Scorer.objNameMatch; - else if (parts.slice(-1)[0].indexOf(object) > -1) - score += Scorer.objPartialMatch; // matches in last name - - const objName = objNames[match[1]][2]; - const title = titles[match[0]]; - - // If more than one term searched for, we require other words to be - // found in the name/title/description - const otherTerms = new Set(objectTerms); - otherTerms.delete(object); - if (otherTerms.size > 0) { - const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); - if ( - [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) - ) - return; - } - - let anchor = match[3]; - if (anchor === "") anchor = fullname; - else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; - - const descr = objName + _(", in ") + title; - - // add custom score for some objects according to scorer - if (Scorer.objPrio.hasOwnProperty(match[2])) - score += Scorer.objPrio[match[2]]; - else score += Scorer.objPrioDefault; - - results.push([ - docNames[match[0]], - fullname, - "#" + anchor, - descr, - score, - filenames[match[0]], - SearchResultKind.object, - ]); - }; - Object.keys(objects).forEach((prefix) => - objects[prefix].forEach((array) => - objectSearchCallback(prefix, array) - ) - ); - return results; - }, - - /** - * search for full-text terms in the index - */ - performTermsSearch: (searchTerms, excludedTerms) => { - // prepare search - const terms = Search._index.terms; - const titleTerms = Search._index.titleterms; - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - - const scoreMap = new Map(); - const fileMap = new Map(); - - // perform the search on the required terms - searchTerms.forEach((word) => { - const files = []; - const arr = [ - { files: terms[word], score: Scorer.term }, - { files: titleTerms[word], score: Scorer.title }, - ]; - // add support for partial matches - if (word.length > 2) { - const escapedWord = _escapeRegExp(word); - if (!terms.hasOwnProperty(word)) { - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - } - if (!titleTerms.hasOwnProperty(word)) { - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); - }); - } - } - - // no match but word was a required one - if (arr.every((record) => record.files === undefined)) return; - - // found search word in contents - arr.forEach((record) => { - if (record.files === undefined) return; - - let recordFiles = record.files; - if (recordFiles.length === undefined) recordFiles = [recordFiles]; - files.push(...recordFiles); - - // set score for the word in each file - recordFiles.forEach((file) => { - if (!scoreMap.has(file)) scoreMap.set(file, {}); - scoreMap.get(file)[word] = record.score; - }); - }); - - // create the mapping - files.forEach((file) => { - if (!fileMap.has(file)) fileMap.set(file, [word]); - else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); - }); - }); - - // now check if the files don't contain excluded terms - const results = []; - for (const [file, wordList] of fileMap) { - // check if all requirements are matched - - // as search terms with length < 3 are discarded - const filteredTermCount = [...searchTerms].filter( - (term) => term.length > 2 - ).length; - if ( - wordList.length !== searchTerms.size && - wordList.length !== filteredTermCount - ) - continue; - - // ensure that none of the excluded terms is in the search result - if ( - [...excludedTerms].some( - (term) => - terms[term] === file || - titleTerms[term] === file || - (terms[term] || []).includes(file) || - (titleTerms[term] || []).includes(file) - ) - ) - break; - - // select one (max) score for the file. - const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); - // add result to the result list - results.push([ - docNames[file], - titles[file], - "", - null, - score, - filenames[file], - SearchResultKind.text, - ]); - } - return results; - }, - - /** - * helper function to return a node containing the - * search summary for a given text. keywords is a list - * of stemmed words. - */ - makeSearchSummary: (htmlText, keywords, anchor) => { - const text = Search.htmlToText(htmlText, anchor); - if (text === "") return null; - - const textLower = text.toLowerCase(); - const actualStartPosition = [...keywords] - .map((k) => textLower.indexOf(k.toLowerCase())) - .filter((i) => i > -1) - .slice(-1)[0]; - const startWithContext = Math.max(actualStartPosition - 120, 0); - - const top = startWithContext === 0 ? "" : "..."; - const tail = startWithContext + 240 < text.length ? "..." : ""; - - let summary = document.createElement("p"); - summary.classList.add("context"); - summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; - - return summary; - }, -}; - -_ready(Search.init); diff --git a/docs/_build/html/_static/sphinx_highlight.js b/docs/_build/html/_static/sphinx_highlight.js deleted file mode 100644 index 8a96c69a..00000000 --- a/docs/_build/html/_static/sphinx_highlight.js +++ /dev/null @@ -1,154 +0,0 @@ -/* Highlighting utilities for Sphinx HTML documentation. */ -"use strict"; - -const SPHINX_HIGHLIGHT_ENABLED = true - -/** - * highlight a given string on a node by wrapping it in - * span elements with the given class name. - */ -const _highlight = (node, addItems, text, className) => { - if (node.nodeType === Node.TEXT_NODE) { - const val = node.nodeValue; - const parent = node.parentNode; - const pos = val.toLowerCase().indexOf(text); - if ( - pos >= 0 && - !parent.classList.contains(className) && - !parent.classList.contains("nohighlight") - ) { - let span; - - const closestNode = parent.closest("body, svg, foreignObject"); - const isInSVG = closestNode && closestNode.matches("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.classList.add(className); - } - - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - const rest = document.createTextNode(val.substr(pos + text.length)); - parent.insertBefore( - span, - parent.insertBefore( - rest, - node.nextSibling - ) - ); - node.nodeValue = val.substr(0, pos); - /* There may be more occurrences of search term in this node. So call this - * function recursively on the remaining fragment. - */ - _highlight(rest, addItems, text, className); - - if (isInSVG) { - const rect = document.createElementNS( - "http://www.w3.org/2000/svg", - "rect" - ); - const bbox = parent.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute("class", className); - addItems.push({ parent: parent, target: rect }); - } - } - } else if (node.matches && !node.matches("button, select, textarea")) { - node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); - } -}; -const _highlightText = (thisNode, text, className) => { - let addItems = []; - _highlight(thisNode, addItems, text, className); - addItems.forEach((obj) => - obj.parent.insertAdjacentElement("beforebegin", obj.target) - ); -}; - -/** - * Small JavaScript module for the documentation. - */ -const SphinxHighlight = { - - /** - * highlight the search words provided in localstorage in the text - */ - highlightSearchWords: () => { - if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight - - // get and clear terms from localstorage - const url = new URL(window.location); - const highlight = - localStorage.getItem("sphinx_highlight_terms") - || url.searchParams.get("highlight") - || ""; - localStorage.removeItem("sphinx_highlight_terms") - url.searchParams.delete("highlight"); - window.history.replaceState({}, "", url); - - // get individual terms from highlight string - const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); - if (terms.length === 0) return; // nothing to do - - // There should never be more than one element matching "div.body" - const divBody = document.querySelectorAll("div.body"); - const body = divBody.length ? divBody[0] : document.querySelector("body"); - window.setTimeout(() => { - terms.forEach((term) => _highlightText(body, term, "highlighted")); - }, 10); - - const searchBox = document.getElementById("searchbox"); - if (searchBox === null) return; - searchBox.appendChild( - document - .createRange() - .createContextualFragment( - '" - ) - ); - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords: () => { - document - .querySelectorAll("#searchbox .highlight-link") - .forEach((el) => el.remove()); - document - .querySelectorAll("span.highlighted") - .forEach((el) => el.classList.remove("highlighted")); - localStorage.removeItem("sphinx_highlight_terms") - }, - - initEscapeListener: () => { - // only install a listener if it is really needed - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; - // bail with special keys - if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; - if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { - SphinxHighlight.hideSearchWords(); - event.preventDefault(); - } - }); - }, -}; - -_ready(() => { - /* Do not call highlightSearchWords() when we are on the search page. - * It will highlight words from the *previous* search query. - */ - if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); - SphinxHighlight.initEscapeListener(); -}); diff --git a/docs/_build/html/base_module.html b/docs/_build/html/base_module.html deleted file mode 100644 index d6959ffc..00000000 --- a/docs/_build/html/base_module.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - base_module package — NOMAD 0.0.1 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

base_module package

-
-

Submodules

-
-
-

base_module.doc_test module

-
-
-

base_module.setup module

-
-
-

Module contents

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html deleted file mode 100644 index 9f4b7ef5..00000000 --- a/docs/_build/html/genindex.html +++ /dev/null @@ -1,649 +0,0 @@ - - - - - - - - Index — NOMAD 0.0.1 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - -

Index

- -
- _ - | A - | B - | C - | D - | F - | G - | I - | L - | M - | N - | O - | P - | Q - | R - | S - | T - | W - -
-

_

- - -
- -

A

- - - -
- -

B

- - - -
- -

C

- - - -
- -

D

- - - -
- -

F

- - - -
- -

G

- - - -
- -

I

- - - -
- -

L

- - -
- -

M

- - -
- -

N

- - - -
- -

O

- - -
- -

P

- - - -
- -

Q

- - -
- -

R

- - - -
- -

S

- - - -
- -

T

- - - -
- -

W

- - -
- - - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html deleted file mode 100644 index fa2304b2..00000000 --- a/docs/_build/html/index.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - - - NOMAD: Network for Open Mobility Analysis and Data — NOMAD 0.0.1 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

NOMAD: Network for Open Mobility Analysis and Data

-Python 3.9+ - -MIT License - -

NOMAD is an open-source Python library for end-to-end processing of large-scale GPS mobility data. -Part of the NSF-funded NOMAD research infrastructure project, it provides production-ready tools for -mobility data analysis with seamless scaling from local workstations to Spark clusters.

-

NOMAD builds on previous software resources—like scikit-mobility, mobilkit, and trackintel—with the -goal of providing a single, production-ready library that covers the entire processing pipeline in a -form suitable for analysis of massive datasets and to aid in the replicability of existing research.

-

All functions are implemented in Python with parallel equivalents in PySpark, enabling the same -analysis notebook to run on a workstation or a Spark cluster without API changes.

- -
-

Installation

-
pip install git+https://github.com/Watts-Lab/nomad.git
-
-
-
-
-
-
-
-
-
-
-

Modules Overview

- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Module

Description

Data Ingestion

Read CSV, Parquet, GeoJSON; validate schemas; return Pandas or Spark DataFrames

Filtering & Completeness

Assess coverage; filter by completeness, geography, time; handle projections

Tessellation

Map pings to H3, S2, or custom grids for grid-based algorithms

Stop Detection

DBSCAN, HDBSCAN, grid-based, and sequential (Lachesis) algorithms

Visit Attribution

Frequency and time-window heuristics for home/workplace inference

Mobility Metrics

Radius of gyration, travel distance, entropy, and related indicators

Co-location & Contact Networks

Build proximity graphs from POI visits or spatial-temporal proximity

Aggregation & Debiasing

Differential privacy, k-anonymity, debiasing, and post-stratification

Synthetic Data Generation

EPR models and point process samplers for trajectory generation

-
-
-

Community & Support

-
    -
  • Issues: Report bugs on GitHub Issues

  • -
  • Contribute: See our contribution guidelines

  • -
  • License: MIT © University of Pennsylvania 2025

  • -
-
-
-
-

Indices and tables

- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/modules.html b/docs/_build/html/modules.html deleted file mode 100644 index f5253134..00000000 --- a/docs/_build/html/modules.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - - - - src — NOMAD 0.0.1 documentation - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv deleted file mode 100644 index 29e7fc8b..00000000 Binary files a/docs/_build/html/objects.inv and /dev/null differ diff --git a/docs/_build/html/py-modindex.html b/docs/_build/html/py-modindex.html deleted file mode 100644 index 298133a3..00000000 --- a/docs/_build/html/py-modindex.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - - Python Module Index — NOMAD 0.0.1 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - -

Python Module Index

- -
- n -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
- n
- nomad -
    - nomad.agg -
    - nomad.aggregation -
    - nomad.city_gen -
    - nomad.constants -
    - nomad.contact_estimation -
    - nomad.displacement -
    - nomad.filters -
    - nomad.filters_spark -
    - nomad.generation -
    - nomad.home_attribution -
    - nomad.io -
    - nomad.map_utils -
    - nomad.metrics -
    - nomad.stop_detection -
    - nomad.traj_gen -
    - nomad.visit_attribution -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html deleted file mode 100644 index ea508c0a..00000000 --- a/docs/_build/html/search.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - - Search — NOMAD 0.0.1 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - - - -
- -
- -
-
- -
-
-
-
- - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js deleted file mode 100644 index 3e96b2f6..00000000 --- a/docs/_build/html/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({"alltitles": {"API Reference": [[17, null]], "Aggregation & Debiasing": [[0, null]], "Basic Installation": [[22, "basic-installation"]], "Benchmarking Stop Detection Algorithms": [[26, null]], "Co-location & Contact Networks": [[19, null]], "Community & Support": [[23, "community-support"]], "DBSCAN": [[33, "dbscan"]], "Data Ingestion": [[20, null]], "Download Data by Bounding Box": [[30, "Download-Data-by-Bounding-Box"]], "Download Data by City Name": [[30, "Download-Data-by-City-Name"]], "Downloading places of interest (POI) Data from OSM": [[30, null]], "Examples": [[22, "examples"]], "Filtering & Completeness": [[21, null]], "Generating synthetic places of interest (POI) Data": [[31, null]], "Getting Started": [[22, null]], "Grid-Based Stop Detection": [[27, null]], "Grid-based": [[33, "grid-based"]], "HDBSCAN": [[33, "hdbscan"]], "HDBSCAN Stop Detection": [[28, null]], "Indices and tables": [[23, "indices-and-tables"]], "Installation": [[22, "installation"], [23, "installation"]], "Key Features": [[22, "key-features"]], "Lachesis": [[33, "lachesis"]], "Lachesis Stop Detection": [[29, null]], "License": [[22, "license"]], "Mobility Metrics": [[24, null]], "Module contents": [[18, "module-contents"]], "Modules": [[17, "modules"]], "Modules Overview": [[23, "modules-overview"]], "NOMAD: Network for Open Mobility Analysis and Data": [[23, null]], "Next Steps": [[22, "next-steps"]], "Overall Runtime Comparison": [[26, "Overall-Runtime-Comparison"]], "Overview of stop-detection methods": [[33, "overview-of-stop-detection-methods"]], "Parameters:": [[3, "parameters"]], "Performance Comparison": [[26, "Performance-Comparison"]], "Quick Links": [[23, "quick-links"]], "Quick Start": [[22, "quick-start"]], "Requirements": [[22, "requirements"]], "Returns:": [[3, "returns"]], "Runtime Scalability": [[26, "Runtime-Scalability"]], "Setup": [[26, "Setup"]], "Stop Detection": [[33, null]], "Submodules": [[18, "submodules"]], "Synthetic Data Generation": [[34, null]], "TADBSCAN Stop Detection": [[32, null]], "Tessellation": [[35, null]], "Visit Attribution": [[36, null]], "Visualization": [[26, "Visualization"]], "What is NOMAD?": [[22, "what-is-nomad"]], "With Spark Support": [[22, "with-spark-support"]], "agg": [[1, null]], "aggregation": [[2, null]], "base_module package": [[18, null]], "base_module.doc_test module": [[18, "base-module-doc-test-module"]], "base_module.setup module": [[18, "base-module-setup-module"]], "city_gen": [[3, null]], "constants": [[4, null]], "contact_estimation": [[5, null]], "displacement": [[6, null]], "filters": [[7, null]], "filters_spark": [[8, null]], "generation": [[9, null]], "home_attribution": [[10, null]], "io": [[11, null]], "map_utils": [[12, null]], "metrics": [[13, null]], "src": [[25, null]], "stop_detection": [[14, null]], "traj_gen": [[15, null]], "visit_attribution": [[16, null]]}, "docnames": ["aggregation", "api/nomad.agg", "api/nomad.aggregation", "api/nomad.city_gen", "api/nomad.constants", "api/nomad.contact_estimation", "api/nomad.displacement", "api/nomad.filters", "api/nomad.filters_spark", "api/nomad.generation", "api/nomad.home_attribution", "api/nomad.io", "api/nomad.map_utils", "api/nomad.metrics", "api/nomad.stop_detection", "api/nomad.traj_gen", "api/nomad.visit_attribution", "api_reference", "base_module", "colocation", "data_ingestion", "filtering", "getting_started", "index", "metrics", "modules", "source/benchmarking_of_stop_detection_algorithms", "source/grid_based_demo", "source/hdbscan_demo", "source/lachesis_demo", "source/poi_osm", "source/poi_synthetic", "source/tadbscan_demo", "stop_detection", "synthetic_data_generation", "tessellation", "visit_attribution"], "envversion": {"nbsphinx": 4, "sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.todo": 2, "sphinx.ext.viewcode": 1}, "filenames": ["aggregation.rst", "api\\nomad.agg.rst", "api\\nomad.aggregation.rst", "api\\nomad.city_gen.rst", "api\\nomad.constants.rst", "api\\nomad.contact_estimation.rst", "api\\nomad.displacement.rst", "api\\nomad.filters.rst", "api\\nomad.filters_spark.rst", "api\\nomad.generation.rst", "api\\nomad.home_attribution.rst", "api\\nomad.io.rst", "api\\nomad.map_utils.rst", "api\\nomad.metrics.rst", "api\\nomad.stop_detection.rst", "api\\nomad.traj_gen.rst", "api\\nomad.visit_attribution.rst", "api_reference.rst", "base_module.rst", "colocation.rst", "data_ingestion.rst", "filtering.rst", "getting_started.rst", "index.rst", "metrics.rst", "modules.rst", "source\\benchmarking_of_stop_detection_algorithms.ipynb", "source\\grid_based_demo.ipynb", "source\\hdbscan_demo.ipynb", "source\\lachesis_demo.ipynb", "source\\poi_osm.ipynb", "source\\poi_synthetic.ipynb", "source\\tadbscan_demo.ipynb", "stop_detection.rst", "synthetic_data_generation.rst", "tessellation.rst", "visit_attribution.rst"], "indexentries": {"__init__() (agent method)": [[15, "nomad.traj_gen.Agent.__init__", false]], "__init__() (city method)": [[3, "nomad.city_gen.City.__init__", false]], "__init__() (population method)": [[15, "nomad.traj_gen.Population.__init__", false]], "__init__() (randomcitygenerator method)": [[3, "nomad.city_gen.RandomCityGenerator.__init__", false]], "__init__() (street method)": [[3, "nomad.city_gen.Street.__init__", false]], "add_agent() (population method)": [[15, "nomad.traj_gen.Population.add_agent", false]], "add_building() (city method)": [[3, "id0", false], [3, "nomad.city_gen.City.add_building", false]], "add_buildings_from_gdf() (city method)": [[3, "nomad.city_gen.City.add_buildings_from_gdf", false]], "add_street() (city method)": [[3, "nomad.city_gen.City.add_street", false]], "agent (class in nomad.traj_gen)": [[15, "nomad.traj_gen.Agent", false]], "allowed_buildings() (in module nomad.traj_gen)": [[15, "nomad.traj_gen.allowed_buildings", false]], "blocks_to_mercator() (in module nomad.map_utils)": [[12, "nomad.map_utils.blocks_to_mercator", false]], "buildings (city attribute)": [[3, "nomad.city_gen.City.buildings", false]], "buildings_df (city property)": [[3, "nomad.city_gen.City.buildings_df", false]], "buildings_outline (city attribute)": [[3, "nomad.city_gen.City.buildings_outline", false]], "check_adjacent() (in module nomad.city_gen)": [[3, "nomad.city_gen.check_adjacent", false]], "city (class in nomad.city_gen)": [[3, "nomad.city_gen.City", false]], "city (population attribute)": [[15, "nomad.traj_gen.Population.city", false]], "city_boundary (city attribute)": [[3, "nomad.city_gen.City.city_boundary", false]], "completeness() (in module nomad.filters)": [[7, "nomad.filters.completeness", false]], "compute_candidate_homes() (in module nomad.home_attribution)": [[10, "nomad.home_attribution.compute_candidate_homes", false]], "compute_stop_detection_metrics() (in module nomad.contact_estimation)": [[5, "nomad.contact_estimation.compute_stop_detection_metrics", false]], "compute_visitation_errors() (in module nomad.contact_estimation)": [[5, "nomad.contact_estimation.compute_visitation_errors", false]], "condense_destinations() (in module nomad.traj_gen)": [[15, "nomad.traj_gen.condense_destinations", false]], "coordinates (street attribute)": [[3, "nomad.city_gen.Street.coordinates", false]], "coverage_matrix() (in module nomad.filters)": [[7, "nomad.filters.coverage_matrix", false]], "dimensions (city attribute)": [[3, "nomad.city_gen.City.dimensions", false]], "download_osm_buildings() (in module nomad.map_utils)": [[12, "nomad.map_utils.download_osm_buildings", false]], "download_osm_streets() (in module nomad.map_utils)": [[12, "nomad.map_utils.download_osm_streets", false]], "downsample() (in module nomad.filters)": [[7, "nomad.filters.downsample", false]], "dt (agent attribute)": [[15, "nomad.traj_gen.Agent.dt", false]], "dt (population attribute)": [[15, "nomad.traj_gen.Population.dt", false]], "fill_block() (randomcitygenerator method)": [[3, "nomad.city_gen.RandomCityGenerator.fill_block", false]], "filter_users() (in module nomad.filters_spark)": [[8, "nomad.filters_spark.filter_users", false]], "from_geodataframes() (city class method)": [[3, "nomad.city_gen.City.from_geodataframes", false]], "from_geopackage() (city class method)": [[3, "nomad.city_gen.City.from_geopackage", false]], "from_mercator() (city method)": [[3, "nomad.city_gen.City.from_mercator", false]], "generate_agents() (population method)": [[15, "nomad.traj_gen.Population.generate_agents", false]], "generate_city() (randomcitygenerator method)": [[3, "nomad.city_gen.RandomCityGenerator.generate_city", false]], "generate_streets() (randomcitygenerator method)": [[3, "nomad.city_gen.RandomCityGenerator.generate_streets", false]], "generate_trajectory() (agent method)": [[15, "nomad.traj_gen.Agent.generate_trajectory", false]], "geometry (street attribute)": [[3, "nomad.city_gen.Street.geometry", false]], "get_adjacent_street() (randomcitygenerator method)": [[3, "nomad.city_gen.RandomCityGenerator.get_adjacent_street", false]], "get_block() (city method)": [[3, "id1", false], [3, "nomad.city_gen.City.get_block", false]], "get_block_type() (randomcitygenerator method)": [[3, "nomad.city_gen.RandomCityGenerator.get_block_type", false]], "get_building() (city method)": [[3, "nomad.city_gen.City.get_building", false]], "get_building_coordinates() (city method)": [[3, "nomad.city_gen.City.get_building_coordinates", false]], "get_category_for_subtype() (in module nomad.map_utils)": [[12, "nomad.map_utils.get_category_for_subtype", false]], "get_category_summary() (in module nomad.map_utils)": [[12, "nomad.map_utils.get_category_summary", false]], "get_city_boundary_osm() (in module nomad.map_utils)": [[12, "nomad.map_utils.get_city_boundary_osm", false]], "get_prominent_streets() (in module nomad.map_utils)": [[12, "nomad.map_utils.get_prominent_streets", false]], "get_shortest_path() (city method)": [[3, "nomad.city_gen.City.get_shortest_path", false]], "get_street_graph() (city method)": [[3, "id2", false], [3, "nomad.city_gen.City.get_street_graph", false]], "get_subtype_summary() (in module nomad.map_utils)": [[12, "nomad.map_utils.get_subtype_summary", false]], "gravity (city attribute)": [[3, "nomad.city_gen.City.gravity", false]], "id (street attribute)": [[3, "nomad.city_gen.Street.id", false]], "id_to_door_cell() (city method)": [[3, "nomad.city_gen.City.id_to_door_cell", false]], "is_within() (in module nomad.filters)": [[7, "nomad.filters.is_within", false]], "load() (in module nomad.city_gen)": [[3, "nomad.city_gen.load", false]], "mercator_to_blocks() (in module nomad.map_utils)": [[12, "nomad.map_utils.mercator_to_blocks", false]], "module": [[1, "module-nomad.agg", false], [2, "module-nomad.aggregation", false], [3, "module-nomad.city_gen", false], [4, "module-nomad.constants", false], [5, "module-nomad.contact_estimation", false], [6, "module-nomad.displacement", false], [7, "module-nomad.filters", false], [8, "module-nomad.filters_spark", false], [9, "module-nomad.generation", false], [10, "module-nomad.home_attribution", false], [11, "module-nomad.io", false], [12, "module-nomad.map_utils", false], [13, "module-nomad.metrics", false], [14, "module-nomad.stop_detection", false], [15, "module-nomad.traj_gen", false], [16, "module-nomad.visit_attribution", false]], "nocturnal_stops() (in module nomad.home_attribution)": [[10, "nomad.home_attribution.nocturnal_stops", false]], "nomad.agg": [[1, "module-nomad.agg", false]], "nomad.aggregation": [[2, "module-nomad.aggregation", false]], "nomad.city_gen": [[3, "module-nomad.city_gen", false]], "nomad.constants": [[4, "module-nomad.constants", false]], "nomad.contact_estimation": [[5, "module-nomad.contact_estimation", false]], "nomad.displacement": [[6, "module-nomad.displacement", false]], "nomad.filters": [[7, "module-nomad.filters", false]], "nomad.filters_spark": [[8, "module-nomad.filters_spark", false]], "nomad.generation": [[9, "module-nomad.generation", false]], "nomad.home_attribution": [[10, "module-nomad.home_attribution", false]], "nomad.io": [[11, "module-nomad.io", false]], "nomad.map_utils": [[12, "module-nomad.map_utils", false]], "nomad.metrics": [[13, "module-nomad.metrics", false]], "nomad.stop_detection": [[14, "module-nomad.stop_detection", false]], "nomad.traj_gen": [[15, "module-nomad.traj_gen", false]], "nomad.visit_attribution": [[16, "module-nomad.visit_attribution", false]], "normalize_od() (in module nomad.displacement)": [[6, "nomad.displacement.normalize_od", false]], "normalized_moved() (in module nomad.displacement)": [[6, "nomad.displacement.normalized_moved", false]], "normalized_remained() (in module nomad.displacement)": [[6, "nomad.displacement.normalized_remained", false]], "overlapping_visits() (in module nomad.contact_estimation)": [[5, "nomad.contact_estimation.overlapping_visits", false]], "parse_agent_attr() (in module nomad.traj_gen)": [[15, "nomad.traj_gen.parse_agent_attr", false]], "place_buildings_in_blocks() (randomcitygenerator method)": [[3, "nomad.city_gen.RandomCityGenerator.place_buildings_in_blocks", false]], "plot_city() (city method)": [[3, "id4", false], [3, "nomad.city_gen.City.plot_city", false]], "plot_traj() (agent method)": [[15, "id0", false], [15, "nomad.traj_gen.Agent.plot_traj", false]], "population (class in nomad.traj_gen)": [[15, "nomad.traj_gen.Population", false]], "precision_recall_f1_from_minutes() (in module nomad.contact_estimation)": [[5, "nomad.contact_estimation.precision_recall_f1_from_minutes", false]], "q_filter() (in module nomad.filters)": [[7, "nomad.filters.q_filter", false]], "randomcitygenerator (class in nomad.city_gen)": [[3, "nomad.city_gen.RandomCityGenerator", false]], "remove_overlaps() (in module nomad.map_utils)": [[12, "nomad.map_utils.remove_overlaps", false]], "reproject_to_mercator() (population method)": [[15, "nomad.traj_gen.Population.reproject_to_mercator", false]], "reset_trajectory() (agent method)": [[15, "nomad.traj_gen.Agent.reset_trajectory", false]], "roster (population attribute)": [[15, "nomad.traj_gen.Population.roster", false]], "rotate() (in module nomad.map_utils)": [[12, "nomad.map_utils.rotate", false]], "rotate_streets_to_align() (in module nomad.map_utils)": [[12, "nomad.map_utils.rotate_streets_to_align", false]], "sample_hier_nhpp() (in module nomad.traj_gen)": [[15, "nomad.traj_gen.sample_hier_nhpp", false]], "sample_traj_hier_nhpp() (agent method)": [[15, "nomad.traj_gen.Agent.sample_traj_hier_nhpp", false]], "sample_trajectory() (agent method)": [[15, "nomad.traj_gen.Agent.sample_trajectory", false]], "save() (city method)": [[3, "id3", false], [3, "nomad.city_gen.City.save", false]], "save() (in module nomad.city_gen)": [[3, "nomad.city_gen.save", false]], "save_geopackage() (city method)": [[3, "nomad.city_gen.City.save_geopackage", false]], "save_pop() (population method)": [[15, "nomad.traj_gen.Population.save_pop", false]], "select_home() (in module nomad.home_attribution)": [[10, "nomad.home_attribution.select_home", false]], "set_osmnx_cache_mode() (in module nomad.map_utils)": [[12, "nomad.map_utils.set_osmnx_cache_mode", false]], "shortest_paths (city attribute)": [[3, "nomad.city_gen.City.shortest_paths", false]], "speeds (agent attribute)": [[15, "nomad.traj_gen.Agent.speeds", false]], "still_probs (agent attribute)": [[15, "nomad.traj_gen.Agent.still_probs", false]], "street (class in nomad.city_gen)": [[3, "nomad.city_gen.Street", false]], "street_adjacency_edges() (city method)": [[3, "nomad.city_gen.City.street_adjacency_edges", false]], "street_graph (city attribute)": [[3, "nomad.city_gen.City.street_graph", false]], "streets (city attribute)": [[3, "nomad.city_gen.City.streets", false]], "streets_df (city property)": [[3, "nomad.city_gen.City.streets_df", false]], "to_file() (city method)": [[3, "nomad.city_gen.City.to_file", false]], "to_geodataframes() (city method)": [[3, "nomad.city_gen.City.to_geodataframes", false]], "to_mercator() (city method)": [[3, "nomad.city_gen.City.to_mercator", false]], "to_projection() (in module nomad.filters)": [[7, "nomad.filters.to_projection", false]], "to_projection() (in module nomad.filters_spark)": [[8, "nomad.filters_spark.to_projection", false]], "to_tessellation() (in module nomad.filters)": [[7, "nomad.filters.to_tessellation", false]], "to_timestamp() (in module nomad.filters)": [[7, "nomad.filters.to_timestamp", false]], "to_yyyymmdd() (in module nomad.filters)": [[7, "nomad.filters.to_yyyymmdd", false]], "to_zoned_datetime() (in module nomad.filters)": [[7, "nomad.filters.to_zoned_datetime", false]], "within() (in module nomad.filters)": [[7, "nomad.filters.within", false]]}, "objects": {"nomad": [[1, 0, 0, "-", "agg"], [2, 0, 0, "-", "aggregation"], [3, 0, 0, "-", "city_gen"], [4, 0, 0, "-", "constants"], [5, 0, 0, "-", "contact_estimation"], [6, 0, 0, "-", "displacement"], [7, 0, 0, "-", "filters"], [8, 0, 0, "-", "filters_spark"], [9, 0, 0, "-", "generation"], [10, 0, 0, "-", "home_attribution"], [11, 0, 0, "-", "io"], [12, 0, 0, "-", "map_utils"], [13, 0, 0, "-", "metrics"], [14, 0, 0, "-", "stop_detection"], [15, 0, 0, "-", "traj_gen"], [16, 0, 0, "-", "visit_attribution"]], "nomad.city_gen": [[3, 1, 1, "", "City"], [3, 1, 1, "", "RandomCityGenerator"], [3, 1, 1, "", "Street"], [3, 5, 1, "", "check_adjacent"], [3, 5, 1, "", "load"], [3, 5, 1, "", "save"]], "nomad.city_gen.City": [[3, 2, 1, "", "__init__"], [3, 2, 1, "id0", "add_building"], [3, 2, 1, "", "add_buildings_from_gdf"], [3, 2, 1, "", "add_street"], [3, 3, 1, "", "buildings"], [3, 4, 1, "", "buildings_df"], [3, 3, 1, "", "buildings_outline"], [3, 3, 1, "", "city_boundary"], [3, 3, 1, "", "dimensions"], [3, 2, 1, "", "from_geodataframes"], [3, 2, 1, "", "from_geopackage"], [3, 2, 1, "", "from_mercator"], [3, 2, 1, "id1", "get_block"], [3, 2, 1, "", "get_building"], [3, 2, 1, "", "get_building_coordinates"], [3, 2, 1, "", "get_shortest_path"], [3, 2, 1, "id2", "get_street_graph"], [3, 3, 1, "", "gravity"], [3, 2, 1, "", "id_to_door_cell"], [3, 2, 1, "id4", "plot_city"], [3, 2, 1, "id3", "save"], [3, 2, 1, "", "save_geopackage"], [3, 3, 1, "", "shortest_paths"], [3, 2, 1, "", "street_adjacency_edges"], [3, 3, 1, "", "street_graph"], [3, 3, 1, "", "streets"], [3, 4, 1, "", "streets_df"], [3, 2, 1, "", "to_file"], [3, 2, 1, "", "to_geodataframes"], [3, 2, 1, "", "to_mercator"]], "nomad.city_gen.RandomCityGenerator": [[3, 2, 1, "", "__init__"], [3, 2, 1, "", "fill_block"], [3, 2, 1, "", "generate_city"], [3, 2, 1, "", "generate_streets"], [3, 2, 1, "", "get_adjacent_street"], [3, 2, 1, "", "get_block_type"], [3, 2, 1, "", "place_buildings_in_blocks"]], "nomad.city_gen.Street": [[3, 2, 1, "", "__init__"], [3, 3, 1, "", "coordinates"], [3, 3, 1, "", "geometry"], [3, 3, 1, "", "id"]], "nomad.contact_estimation": [[5, 5, 1, "", "compute_stop_detection_metrics"], [5, 5, 1, "", "compute_visitation_errors"], [5, 5, 1, "", "overlapping_visits"], [5, 5, 1, "", "precision_recall_f1_from_minutes"]], "nomad.displacement": [[6, 5, 1, "", "normalize_od"], [6, 5, 1, "", "normalized_moved"], [6, 5, 1, "", "normalized_remained"]], "nomad.filters": [[7, 5, 1, "", "completeness"], [7, 5, 1, "", "coverage_matrix"], [7, 5, 1, "", "downsample"], [7, 5, 1, "", "is_within"], [7, 5, 1, "", "q_filter"], [7, 5, 1, "", "to_projection"], [7, 5, 1, "", "to_tessellation"], [7, 5, 1, "", "to_timestamp"], [7, 5, 1, "", "to_yyyymmdd"], [7, 5, 1, "", "to_zoned_datetime"], [7, 5, 1, "", "within"]], "nomad.filters_spark": [[8, 5, 1, "", "filter_users"], [8, 5, 1, "", "to_projection"]], "nomad.home_attribution": [[10, 5, 1, "", "compute_candidate_homes"], [10, 5, 1, "", "nocturnal_stops"], [10, 5, 1, "", "select_home"]], "nomad.map_utils": [[12, 5, 1, "", "blocks_to_mercator"], [12, 5, 1, "", "download_osm_buildings"], [12, 5, 1, "", "download_osm_streets"], [12, 5, 1, "", "get_category_for_subtype"], [12, 5, 1, "", "get_category_summary"], [12, 5, 1, "", "get_city_boundary_osm"], [12, 5, 1, "", "get_prominent_streets"], [12, 5, 1, "", "get_subtype_summary"], [12, 5, 1, "", "mercator_to_blocks"], [12, 5, 1, "", "remove_overlaps"], [12, 5, 1, "", "rotate"], [12, 5, 1, "", "rotate_streets_to_align"], [12, 5, 1, "", "set_osmnx_cache_mode"]], "nomad.traj_gen": [[15, 1, 1, "", "Agent"], [15, 1, 1, "", "Population"], [15, 5, 1, "", "allowed_buildings"], [15, 5, 1, "", "condense_destinations"], [15, 5, 1, "", "parse_agent_attr"], [15, 5, 1, "", "sample_hier_nhpp"]], "nomad.traj_gen.Agent": [[15, 2, 1, "", "__init__"], [15, 3, 1, "", "dt"], [15, 2, 1, "", "generate_trajectory"], [15, 2, 1, "id0", "plot_traj"], [15, 2, 1, "", "reset_trajectory"], [15, 2, 1, "", "sample_traj_hier_nhpp"], [15, 2, 1, "", "sample_trajectory"], [15, 3, 1, "", "speeds"], [15, 3, 1, "", "still_probs"]], "nomad.traj_gen.Population": [[15, 2, 1, "", "__init__"], [15, 2, 1, "", "add_agent"], [15, 3, 1, "", "city"], [15, 3, 1, "", "dt"], [15, 2, 1, "", "generate_agents"], [15, 2, 1, "", "reproject_to_mercator"], [15, 3, 1, "", "roster"], [15, 2, 1, "", "save_pop"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "attribute", "Python attribute"], "4": ["py", "property", "Python property"], "5": ["py", "function", "Python function"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:attribute", "4": "py:property", "5": "py:function"}, "terms": {"": [3, 6, 7, 8, 12, 15, 22, 26, 31], "0": [3, 7, 12, 15, 26, 30, 31], "01": [26, 27, 28, 29, 32], "0204081632653061": 15, "03": 32, "05": [], "060303": 26, "070307": 30, "08": 30, "08098": 30, "0e0e0": [], "1": [3, 7, 8, 12, 15, 22, 26, 27, 28, 29, 30, 31, 32], "10": [12, 26, 27, 28, 29, 30, 31, 32], "100": [6, 22], "1004": 30, "101": 30, "104": [], "106": 31, "11": 31, "11749631": 30, "11752856": 30, "11753099": 30, "11758546": 30, "12": [26, 31], "120": [], "13": 31, "14": [26, 30, 31], "14652246706544": 30, "15": [3, 12, 15, 26, 27, 28, 31, 32], "154": 30, "155": 30, "16": 31, "16617": 30, "16631": 30, "17": 31, "1700": 30, "17019": 30, "1704162819": 26, "17178": 30, "172": 30, "18": [30, 31], "180": 30, "18000": 7, "19": [10, 31], "191": 30, "19103": 30, "19104": 30, "19129": 30, "19130": 30, "19375": 30, "19378": 30, "19743": 30, "19745": 30, "19747721789525": 30, "2": [3, 12, 15, 22, 26, 28, 30, 31], "20": [26, 29, 31], "200": 12, "2024": [27, 28, 29, 32], "2025": [22, 23, 27], "2035": 30, "207118": 26, "21": [27, 30, 31], "22": 31, "224": 31, "23": [], "23172": 30, "240": [26, 27, 28, 32], "246": 30, "25": [], "255": [], "2c353c": [], "3": [3, 7, 22, 26, 28, 30, 31, 32], "30": 28, "300": [], "314632": 26, "31779": 30, "31782": 30, "32": 30, "323719": 30, "33003": 30, "3330": 30, "33482": 30, "34": [], "3600": 26, "36042": 30, "37962": 30, "3826530612244898": 15, "3857": [8, 12, 27, 28, 31, 32], "39": 30, "3f": [26, 31], "4": [3, 22, 26, 30, 31, 32], "4075": 30, "4096": [], "42": 3, "4265669": 12, "4265684": 12, "4265699": [3, 12], "4326": [12, 26, 32], "4392976": [3, 12], "4392991": 12, "450": [], "47604": 30, "47605": 30, "47648": 30, "47653": 30, "4766": 30, "48": 26, "5": [3, 7, 15, 26, 27, 28, 29, 30, 31, 32], "50": [], "504672": 26, "51444": 30, "515": 15, "51511": 30, "525774": 30, "5574": 30, "569": 30, "58": 30, "59249": 30, "6": [10, 26, 30, 31], "60": [26, 29], "64045": 30, "64079": 30, "64503": 30, "648": 30, "64825": 30, "656": 31, "68": 15, "6h": 26, "7": [30, 31], "72": 30, "720": [28, 32], "72495": 30, "72523": 30, "75": [15, 30], "7597": 30, "7747": 30, "7932": 30, "8": [15, 31], "80": [], "8192": [], "83": 30, "8601": [], "8928571428571429": 15, "9": [15, 22, 31], "931392279878246": 30, "947": 30, "94717": 30, "949": 30, "94942": 30, "95201": 30, "95204": 30, "953576": 30, "95649": 30, "95652": 30, "962": 30, "96231": 30, "96336810441389": 30, "99": [], "A": [3, 7, 8, 15, 33], "AND": [], "At": [], "But": [], "For": [7, 15, 22], "If": [3, 6, 7, 8, 12, 15], "In": [], "It": [8, 10, 33], "Its": [], "NOT": [], "No": 5, "On": [], "One": 7, "The": [3, 5, 7, 12, 15, 22, 27, 28, 29, 31, 32], "There": [], "These": 15, "To": [0, 7, 19, 20, 21, 24, 33, 34, 35, 36], "__init__": [3, 15], "_add_mani": 31, "_base_cdf": [], "about": [3, 15], "abov": [], "absent": 7, "absolut": [], "abstract": 12, "accept": [7, 31], "access": 30, "accuraci": 15, "across": 31, "act": [], "actual": [7, 15], "ad": [3, 15], "adapt": [32, 33], "add": [3, 6, 15, 31], "add_ag": 15, "add_build": [3, 31], "add_buildings_from_gdf": 3, "add_point": [], "add_street": 3, "addit": [7, 8, 15], "addr": 30, "address": [3, 15, 31], "adjac": 3, "adjust_zoom": [], "admiring_brattain": [27, 28, 29], "affin": 12, "after": [7, 15], "against": [], "agent": [3, 15], "agent_hom": 15, "agent_workplac": 15, "agg_freq": 7, "aggreg": [7, 10, 23], "aid": [22, 23], "algo": 26, "algorithm": [5, 22, 23, 27, 28, 29, 32, 33], "alia": [], "alias": 7, "align": [3, 7, 12], "all": [3, 7, 10, 12, 15, 22, 23], "allow": 15, "allowai": 30, "allowed_build": 15, "along": [], "alpha": [3, 15, 26, 30], "alreadi": [5, 10, 15], "also": [3, 12, 15, 32], "alter": 3, "altern": 27, "although": [], "alwai": [], "amount": 29, "an": [3, 5, 6, 12, 15, 22, 23, 29, 32], "analysi": 22, "ani": [3, 7, 12], "anonym": 23, "anoth": [8, 12], "any_coord": 3, "apart": 30, "api": [22, 23, 30], "appear": 7, "append": [15, 26], "appli": [7, 8, 12], "applic": 3, "approach": [], "appropri": 5, "approxim": [], "ar": [3, 6, 7, 8, 10, 12, 15, 23, 27, 32], "area": 8, "arg": [], "argument": [7, 15], "around": 12, "arrai": 15, "arriv": 15, "artefact": [], "assess": [22, 23], "assign": [3, 7, 26, 28, 32], "assum": 10, "attempt": 8, "attr": 15, "attribut": [15, 22, 23], "augment": [], "author": 12, "auto": [], "autodetect": [], "automat": [15, 17, 33], "avail": 3, "avenu": 30, "avoid": [], "awai": 29, "awar": 33, "ax": [3, 15, 26, 27, 28, 29, 30, 31, 32], "ax1": 26, "ax2": 26, "ax_barcod": [26, 27, 28, 29, 32], "ax_map": 26, "axi": [3, 15, 26], "b": [], "back": [3, 12], "backend": [], "background": [], "background_gdf": [], "bailei": 30, "bar": 26, "barcod": [], "barh": 26, "base": [3, 15, 22, 23, 26, 28, 29, 32], "base_geom_background": [], "base_geom_color": [], "base_geometri": 26, "base_modul": 25, "basic": [], "bbox": [12, 30], "bbox_or_c": 12, "bear": 12, "becaus": [], "been": [], "befor": [], "begin": [], "behav": [], "being": [], "below": 33, "beta_dur": 15, "beta_dur_rang": [], "beta_p": 15, "beta_ping_rang": [], "beta_start": 15, "beta_start_rang": [], "between": [3, 10, 29, 32, 33], "beyond": 10, "binari": [], "black": [15, 26, 30], "blksize": [], "block": [3, 12, 15, 30, 31], "block_i": 3, "block_side_length": [3, 15], "block_siz": 12, "block_typ": 3, "block_x": 3, "blockingioerror": [], "blocks_gdf": 3, "blocks_to_merc": 12, "blue": [28, 29, 30], "bool": [3, 7, 15], "boolean": 7, "border": [], "both": [8, 26], "bound": [3, 7, 8, 12], "boundari": [3, 12], "boundary_multipolygon": 12, "box": [3, 12, 27, 28, 29, 31, 32], "bridg": 30, "broken": [], "btype": 31, "bucket": 7, "budget": 6, "buffer": [27, 28, 32], "buffer_s": [], "bufferediobas": [], "bufferedrandom": [], "bufferedread": [], "bufferedrwpair": [], "bufferedwrit": [], "bug": 23, "build": [3, 12, 15, 22, 23, 30, 31], "building_id": [3, 5, 15], "building_typ": [3, 31], "buildings_df": 3, "buildings_gdf": [3, 15, 31], "buildings_outlin": 3, "buildings_path": 3, "built": 31, "builtin": [], "burst": 15, "by_chunk": 12, "byte": [], "bytearrai": [], "bytesio": [], "c": [], "cach": 12, "cache_mod": 12, "cache_traj": 15, "calcul": [3, 10], "calendar": [], "call": [27, 31], "callabl": 15, "caller": [5, 10], "can": [3, 15, 29], "candid": [], "candidate_hom": 10, "candidate_workplac": [], "cannot": 12, "canon": 15, "carolinechen": [], "carri": [], "carrol": 30, "case": [7, 33], "cast": [], "categor": 12, "categori": [12, 30], "cdf": [], "cdf_function": [], "cell": [], "center": 26, "center_coordin": 12, "centroid": 12, "certain": [], "chain": [], "chang": [22, 23], "charact": [], "characterist": 33, "check_adjac": 3, "choos": [], "chosen": [], "chronolog": [], "chunk": [], "chunk_mil": 12, "circl": [], "citi": [3, 12, 15, 26, 27, 28, 31, 32], "city_boundari": 3, "city_gen": 31, "city_nam": 30, "city_properti": 3, "class": [3, 15], "classmethod": 3, "clean": 12, "client": [], "clip": [10, 12, 30], "clip_spatial_outli": 26, "clip_stays_d": [], "clip_stops_datetim": [], "clip_to_gdf": 12, "close": 31, "closefd": [], "closest": 3, "cluster": [22, 23, 27, 28, 32, 33], "cluster_hierarchi": [], "cluster_id": [], "cluster_label": [], "cluster_st": [], "cluster_stability_df": [], "cmap": 26, "co": [5, 23], "code": 8, "codec": [], "coercion": 10, "col_nam": 15, "collect": [], "color": [3, 15, 26, 30], "colormap": [], "column": [3, 5, 6, 7, 8, 10, 12, 15, 27], "column_stack": [7, 32], "colunmn": [], "com": [22, 23], "combin": [3, 5, 27], "commerci": [27, 30], "common": [7, 27], "compar": 26, "comparison": 33, "complet": [3, 7, 22, 23, 26], "complete_output": [26, 27, 28, 29, 32], "comput": [5, 6, 7, 8, 22, 31, 33], "compute_candidate_hom": 10, "compute_candidate_workplac": [], "compute_cluster_st": [], "compute_radius_of_gyr": 22, "compute_stop_detection_metr": 5, "compute_visitation_error": 5, "concaten": [], "concentr": [], "conceptu": [], "condense_destin": 15, "confident_aryabhata": 32, "conflict": [], "connect": 3, "consecut": [7, 15, 29, 32], "consid": [12, 29, 32], "construct": [3, 28], "constructor": [], "contact": 23, "contain": [3, 7, 8, 12, 15, 17, 22], "content": 25, "contigu": 29, "contribut": [6, 7, 23], "control": [15, 22], "convent": 7, "convers": 7, "convert": [3, 6, 7, 12], "cooki": [], "coord": 3, "coord_i": 3, "coord_x": 3, "coordin": [3, 7, 8, 12, 15, 27, 32], "core": 22, "core_dist": [], "correctli": [], "correspond": [7, 15], "count": [6, 12], "count_night": [], "counterclockwis": 12, "counterpart": [], "cove": 30, "cover": [12, 15, 22, 23], "coverag": 23, "coverage_matrix": 7, "cr": [7, 8, 12], "crash": [], "creat": 3, "creation": [], "criterion": [], "crs_to": [7, 32], "csv": [22, 23, 26], "cumul": [], "current": [], "current_end": 26, "current_idx": [], "custom": 23, "d": [7, 15], "d_graph": [], "dai": [7, 8], "daili": 7, "data": [3, 6, 7, 12, 22, 26, 27, 28, 29, 32, 33], "data_cr": [7, 26, 27, 32], "databas": [], "datafram": [3, 5, 6, 7, 8, 12, 15, 23, 26], "dataset": [22, 23, 26, 27], "date": [7, 27, 28, 29, 32], "date_rang": 26, "datetim": [7, 8, 10, 15, 26], "datetime64": [7, 27], "dawn_hour": 10, "dawn_tim": [], "day_part": [], "daytim": [], "dbscan": [22, 23, 26, 32], "dd": [], "deal": [], "debias": 23, "declar": 31, "decod": [], "dedic": [], "dedupl": 15, "def": 31, "default": [3, 7, 8, 12, 15, 27], "default_buffer_s": [], "default_schema": [], "default_spe": 15, "default_still_prob": 15, "defer": [], "defin": [7, 8, 32], "degre": 12, "delawar": 30, "delimit": [], "delta_roam": [26, 29], "demonstr": 22, "dendogram_scal": [], "densiti": [32, 33], "depend": [22, 29], "deprec": [], "deriv": [15, 22], "descend": [], "describ": [], "descript": 23, "descriptor": [], "design": 22, "desktop": [], "dest_col": 6, "destin": [6, 15], "destination_diari": 15, "detach": [], "detail": 3, "detect": [5, 22, 23], "detect_stop": 22, "determin": [3, 29, 32], "determinist": 33, "dev_i": [27, 28, 29, 32], "dev_x": [27, 28, 29, 32], "develop": 12, "devic": [], "df": [7, 12, 22], "diamet": 26, "diari": 15, "diaries_path": 15, "dict": [3, 5, 7, 8, 12, 15, 31], "dictionari": [3, 5, 7, 8, 15], "diff_privacy_ep": 6, "differ": [22, 27, 28, 32], "differenti": 23, "dimens": [3, 31, 32], "directli": [7, 22], "directori": [], "disabl": [], "disconnect": [], "discret": 15, "disk": 3, "dist_thresh": [26, 28, 32], "distanc": [3, 23, 27, 29, 32], "distance_threshold": 33, "distinct": 8, "distinguish": 33, "distribut": [3, 8, 15, 22], "divid": 12, "do": [3, 10], "doc_test": 25, "docstr": 17, "document": 17, "doe": [], "doesn": [], "don": [], "door": [3, 15, 31], "door_cell_i": 3, "door_cell_x": 3, "door_coord": 3, "door_point": 3, "down": 7, "download": 12, "download_osm_build": [12, 30], "download_osm_street": [12, 30], "downsampl": 7, "dp": 6, "draw": [], "drawn": 15, "drive": 30, "driver": [3, 30], "drop": 10, "dt": 15, "dt_max": [26, 29], "dtype": 7, "dual": 22, "dummi": [], "duplic": 7, "dur_min": [26, 28, 29, 32], "durat": [5, 10, 15, 29], "duration_at_night_fast": [], "dusk_hour": 10, "dusk_tim": [], "dx": 3, "dy": 3, "dynam": 3, "e": [3, 7, 8, 15, 31], "each": [3, 6, 7, 10, 15, 26, 31], "east": [3, 12], "edg": 3, "edge_alpha": [], "edge_cmap": [], "edge_color": [], "edge_onli": [], "edgecolor": 30, "edges_df": 3, "edges_path": 3, "edges_sorted_df": [], "edu": [22, 23], "educ": 30, "effect": [], "effici": 12, "egg": 22, "either": [7, 31], "elaps": 31, "element": 15, "els": 31, "empti": 15, "enabl": 23, "encod": 7, "end": [3, 7, 8, 15, 22, 23, 26], "end_": [], "end_col": [], "end_coord": 3, "end_datetim": 10, "end_tim": [8, 15], "end_timestamp": 26, "enforc": [], "engin": [], "ensur": 3, "entir": [22, 23], "entri": [7, 15, 31], "entropi": 23, "eof": [], "ep": [22, 33], "epoch": 7, "epr": [15, 23], "epr_time_r": 15, "epsg": [8, 12, 26, 27, 28, 31, 32], "epsilon": [], "equal": 30, "equival": [22, 23], "error": [12, 15], "estim": [5, 12], "etc": 5, "euclidean": 27, "evalu": 26, "even": [], "event": 15, "everi": 7, "exact": 6, "exactli": [], "exampl": [7, 12, 26], "except": [], "exclude_categori": 12, "exclus": [], "execut": 26, "exist": [3, 12, 15, 22, 23], "expect": [7, 8, 15], "explicit": 7, "explod": [12, 30], "explode_stop": [], "explor": 15, "exponenti": 15, "express": 7, "extend": [], "extra": [], "extract": 29, "f": [15, 26, 30, 31], "f1": 5, "face": [], "failur": [], "fall": [], "fallback": 3, "fals": [3, 5, 7, 12, 15, 26, 27, 28, 29, 30, 31, 32], "false_east": 12, "false_north": 12, "fast": 33, "faster": [], "favor": [], "featur": 12, "fetch": 12, "field": 15, "fig": [26, 27, 28, 29, 30, 31, 32], "figsiz": [26, 27, 28, 29, 30, 31, 32], "figur": 3, "file": [3, 15], "fileexistserror": [], "fileio": [], "filenam": 3, "fileno": [], "filepath": [], "filepath_root": [27, 28, 29, 32], "filesystem": 15, "fill": [3, 5], "fill_block": 3, "fill_cmap": [], "fill_timestamp_gap": [], "filter": [8, 12, 22, 23, 26, 27, 28, 29, 32], "filter_us": 8, "final": [], "find": [3, 15, 33], "first": [7, 26, 29], "first_tim": [], "fix": [], "fixed_format": [], "flag": 7, "flexibl": 12, "float": [3, 6, 7, 12, 15], "flow": 6, "flush": [], "fmt": 15, "focus": 22, "folder": 22, "follow": [15, 29], "form": [23, 32], "format": [3, 22, 26, 27, 28, 29, 32], "found": [3, 7], "four": 26, "fp": [], "frac_record": [], "frac_us": [], "fraction": [5, 7], "freq": [7, 26], "frequenc": [7, 23, 33], "frequent": [], "friendli": 7, "from": [3, 5, 7, 8, 12, 15, 17, 22, 23, 26, 27, 28, 29, 31, 32], "from_df": [], "from_fil": [], "from_geodatafram": 3, "from_geopackag": 3, "from_merc": 3, "full": [15, 26], "full_path": 15, "full_traj": 15, "fulli": [3, 12], "func": 26, "function": [8, 12, 15, 22, 23], "fund": [22, 23], "fundament": 33, "further": [], "futur": [], "g": [3, 7, 8, 15], "gap": [5, 29], "garden": [3, 30], "garden_c": [12, 26, 27, 28, 30, 32], "garden_city_categori": 30, "gc_data": 26, "gc_data_long": [27, 28, 29, 32], "gc_identifi": [27, 28, 29, 32], "gdf": 12, "gdf_row": 3, "gen_params_rang": [], "gen_params_target_q": [], "gener": [3, 15, 17, 22, 23, 33], "generate_ag": 15, "generate_c": 3, "generate_dest_diari": 15, "generate_street": 3, "generate_trajectori": 15, "geodatafram": [3, 7, 12], "geograph": [27, 32], "geographi": [6, 23], "geohash": [7, 27], "geojson": [3, 22, 23, 26, 27, 28, 30, 32], "geolife_plu": 12, "geom": [3, 31], "geom1": 3, "geom2": 3, "geometri": [3, 8, 12, 27, 28, 29, 30, 31, 32], "geopackag": [3, 31], "geopanda": [3, 22, 26, 27, 28, 30, 32], "geospati": 12, "get": [3, 31], "get_adjacent_street": 3, "get_block": 3, "get_block_typ": 3, "get_build": 3, "get_building_coordin": 3, "get_category_for_subtyp": 12, "get_category_summari": 12, "get_city_boundary_osm": 12, "get_height": 26, "get_i": 26, "get_prominent_street": 12, "get_shortest_path": 3, "get_street_graph": [3, 31], "get_subtype_summari": 12, "get_table_head": [], "getbuff": [], "getpreferredencod": [], "getvalu": [], "git": [22, 23], "github": [22, 23], "give": [], "given": [3, 6, 7, 12, 15, 27], "global": 12, "goal": [22, 23], "good": 33, "govern": 15, "gp": [22, 23, 33], "gpd": [3, 26, 27, 28, 30, 32], "gpkg": [3, 31], "gpkg_path": 3, "granular": 15, "graph": [3, 23, 31], "graviti": 3, "greater": 29, "green": [27, 30], "grei": 30, "grid": [3, 12, 23, 26], "grid_bas": [26, 27], "grid_based_label": [], "grid_based_per_us": [], "grid_resolut": 33, "gridspec_kw": 26, "ground": [5, 15], "group": [], "grouped_data": [], "gt": 30, "guidelin": 23, "gyrat": 23, "h": [3, 7], "h3": [7, 23, 26, 27], "h3_cell": [26, 27], "h3_cell_to_polygon": [], "h3_resolut": [], "ha": [3, 12, 15, 27, 28, 29, 31, 32], "handl": [7, 12, 23], "hariharan": 29, "hash": [], "hashabl": [], "have": [8, 15], "haversin": [27, 32], "hdbscan": [23, 26], "hdbscan_label": 28, "head": 30, "heatmap": [3, 15], "heatmap_ag": 3, "height": 3, "height_ratio": 26, "help": [], "helper": [10, 31], "here": [5, 22], "heterogen": 7, "heurist": [12, 23], "hexagon": [], "hierarch": [15, 33], "hierarchi": 28, "hierarchy_df": [], "higher": 22, "highlight": [], "highwai": [12, 30], "histori": 29, "hit": [], "hold": [], "hollow": [], "home": [3, 10, 15, 22, 23, 31], "home_d": [], "home_ratio": 3, "homes_path": 15, "homogen": 15, "horizont": 15, "hospit": 30, "hour": [7, 26], "hourli": [], "housenumb": 30, "how": [22, 26], "howev": [], "http": [22, 23], "human": 33, "i": [3, 5, 6, 7, 8, 10, 12, 15, 23, 27, 29, 32, 33], "ic2s2": 27, "id": [3, 7, 15], "id_col": [], "id_to_door_cel": 3, "ident": 12, "identifi": [3, 5, 7, 15, 29, 33], "ignor": 7, "immedi": [], "immin": [], "immut": [], "implement": [0, 19, 20, 21, 22, 23, 24, 27, 29, 32, 33, 34, 35, 36], "impli": [], "implicit": [], "import": [12, 22, 26, 27, 28, 29, 30, 31, 32], "includ": [7, 12, 15], "include_schema": [], "include_weekend": [], "incomplet": [], "incorpor": 32, "increas": 26, "increment": 26, "index": [7, 12, 15, 23, 26, 27], "indic": [7, 15], "individu": [3, 5, 7], "inf": [], "infer": [7, 10, 15, 22, 23], "infer_building_typ": [12, 30], "inform": [3, 15, 22], "infrastructur": [22, 23], "ingest": [22, 23], "inherit": [], "initi": [3, 15, 31], "initial_byt": [], "initial_valu": [], "inlin": [27, 28, 29, 32], "inplac": 31, "input": [7, 12, 27], "input_cr": 8, "insid": [7, 8], "insipir": 29, "instead": [3, 7], "int": [3, 6, 7, 8, 15], "int0": 15, "int64": 7, "integ": [7, 27], "intend": [], "intent": [], "inter": 15, "interact": [], "interchang": [], "interfac": [], "intern": [], "interpret": [], "intersect": 10, "interv": 7, "invalid": [3, 7], "invalid_stop": [], "invers": 12, "invok": [], "io": [22, 26, 27, 28, 29, 32], "iobas": [], "is_within": 7, "isatti": [], "isn": [], "iso": [], "issu": 23, "item": 30, "iter": [], "its": 15, "jersei": 30, "join": 15, "join_styl": [27, 28, 32], "junction": 30, "jupyt": 22, "k": [12, 23], "keep": [7, 12, 15], "keep_col_nam": [26, 29], "kei": [3, 7, 12, 15], "kept": [], "keyerror": 7, "keyword": [10, 15], "kind": 3, "known": [], "kwarg": [5, 7, 8, 10, 15, 31], "lab": [22, 23], "label": 26, "label_history_df": [], "lachesi": [23, 26], "lachesis_label": [], "lachesis_per_us": [], "lack": 3, "lambda": 26, "lane": 30, "laplac": 6, "larg": [22, 23], "larger": 26, "largest": [], "last": 7, "last_dat": [], "last_p": 15, "last_tim": [], "lat": [7, 27, 32], "latent": 15, "later": [], "latitud": [8, 26, 32], "layer": 3, "layout": 15, "lead": [], "learn": 22, "least": [7, 8, 27, 29], "leav": [], "left": 5, "legal": [], "legend": 26, "len": [26, 30, 31], "length": [3, 12, 15, 30], "less": [], "librari": [12, 22, 23], "licens": 23, "like": [8, 22, 23, 27], "limit": [], "line": [], "line_buff": [], "linesep": [], "linestr": 30, "linewidth": [26, 30], "list": [3, 6, 15, 31], "listedcolormap": [], "live": [], "load": [3, 22, 27, 28, 29, 32], "loader": [22, 26, 27, 28, 29, 32], "local": [7, 8, 15, 23], "local_t": 15, "localize_from_offset": [], "locat": [3, 5, 10, 15, 22, 23, 27, 29, 33], "location_id": [26, 27], "logic": 7, "lon": [7, 27, 32], "long": [], "longitud": [8, 26, 32], "loop": [], "loss": [], "low": 33, "lower": 7, "lower_quantil": [], "lt": 30, "m": [7, 15], "mai": 7, "main": [], "major": [], "make": [], "manag": 3, "mani": 31, "manual": 3, "manual_street": 3, "map": [3, 5, 7, 8, 15, 23, 30], "map_util": 30, "mark": [], "marker": 26, "mask": [3, 7], "massiv": [22, 23], "match": 7, "match_loc": 5, "matplotlib": [3, 15, 26, 27, 28, 29, 30, 31, 32], "matrix": 7, "matter": [], "max": 26, "max_dist": [], "max_gap": [], "maxim": 28, "maximum": [29, 32], "maxspe": 30, "mean": 15, "meaning": 33, "measur": [7, 26], "medic": 30, "medoid": [], "meet": [], "memori": 3, "mercat": [3, 12, 15], "mercator_to_block": 12, "mere": 10, "merg": 5, "merged_fract": 5, "messag": 15, "meter": [3, 12, 27, 29, 32], "method": [3, 15, 28, 32], "metric": [5, 22, 23], "metrics_df": 22, "might": [], "min": [7, 26], "min_active_dai": 8, "min_cluster_s": [26, 28, 32, 33], "min_dai": 10, "min_dur": 33, "min_dwel": [], "min_pings_per_dai": 8, "min_pt": [26, 28, 32], "min_sampl": [22, 33], "min_week": 10, "minimum": [29, 32], "minut": [5, 7, 15, 29, 32], "mismatch": 7, "miss": [5, 8], "missed_fract": 5, "mit": [22, 23], "mitr": [27, 28, 32], "mix": [], "mixed_timezone_behavior": 15, "mm": [], "mobil": [22, 33], "mobilkit": [22, 23], "mode": 12, "model": [15, 23, 29], "modifi": 15, "modul": [12, 25], "moment": [], "monthli": 7, "more": 22, "most": [7, 12], "move": [3, 6, 29], "movement": 33, "mst": [], "much": [], "multi": [], "multilinestr": 12, "multipl": [3, 7], "multipolygon": [3, 7, 12], "must": [3, 5, 7, 15, 29], "n": [15, 27, 31], "n_ping": 26, "n_row": [], "na": [7, 30], "naiv": 15, "naive_datetime_from_unix_and_offset": [], "naive_dt": [], "name": [3, 5, 7, 8, 10, 12, 15, 26, 27, 33], "name_count": 15, "nan": 30, "nearest": [], "necessari": 10, "need": 8, "neg": 6, "neighbor": [3, 32], "neither": [], "network": [12, 22], "networkx": 22, "never": [], "new": [3, 8, 15, 30], "newlin": [], "next": [], "night": 10, "night_stop": [], "nightli": 10, "nj": 30, "nocturn": 10, "nocturnal_stop": 10, "node": 3, "nois": 6, "nomad": [12, 17, 26, 27, 28, 29, 30, 31, 32, 33], "non": [6, 15, 28], "none": [3, 5, 6, 7, 8, 10, 12, 15], "nor": [], "normalis": 6, "normalize_od": 6, "normalized_mov": 6, "normalized_remain": 6, "north": [3, 12], "note": [], "notebook": [22, 23, 26], "noth": [], "notic": 32, "np": [7, 27, 28, 32], "nsampl": 30, "nsf": [22, 23], "num_night": [], "num_week": [], "num_work_dai": [], "number": [7, 15, 26, 32], "numpi": [3, 15, 22, 27, 28, 32], "o": 26, "object": [3, 7, 15, 29], "observ": 7, "obtain": [], "occupi": 3, "occurr": [], "od": 6, "od_df": [], "off": 12, "offend": [], "offset": [7, 12], "offset_col": 7, "often": [], "oi": 3, "omit": 7, "onc": [], "one": [7, 8, 12, 29], "ones": [], "onewai": 30, "onli": [3, 7, 15, 27, 30], "opac": [], "open": [22, 30], "open_cod": [], "openstreetmap": 12, "oper": 3, "optim": [], "option": [3, 5, 6, 7, 8, 12, 15, 22], "oracle_map": [], "orang": 30, "order": 3, "origin": [6, 8, 12], "origin_col": 6, "oserror": [], "osm": 12, "osm_typ": [12, 30], "osmid": 30, "osmnx": 12, "other": [12, 30], "otherwis": [3, 7], "our": 23, "outer_box": [27, 28, 32], "outgo": 6, "outlier": [], "output": [7, 12, 15], "output_burst": 15, "output_cr": 8, "output_traj_col": [], "outsid": [], "over": 7, "overal": 7, "overlap": [3, 5, 12, 28, 30], "overlapping_visit": 5, "overpass": 30, "overrid": [7, 10], "overridden": [], "own": 7, "ox": 3, "p": 5, "pa": 30, "packag": [17, 25], "pad": [], "pad_short_stop": [], "page": 23, "pair": [3, 8, 15, 27], "panda": [3, 6, 7, 12, 15, 22, 23, 26, 27, 28, 30, 32], "parallel": [22, 23], "param": 6, "paramet": [5, 7, 8, 12, 15, 29, 32, 33], "pareto": 15, "park": [3, 12, 15, 30, 31], "park_ratio": 3, "parquet": [3, 15, 22, 23, 27, 28, 29, 32], "pars": [7, 15, 29], "parse_agent_attr": 15, "parse_d": [], "parser": [], "part": [22, 23], "partit": 15, "partition_bi": [], "partition_col": 15, "pass": 7, "passthrough": [], "passthrough_col": [], "path": [3, 15], "pattern": [3, 33], "pd": [3, 5, 7, 8, 12, 15, 26, 27, 28, 30, 32], "peek": [], "pennsylvania": [22, 23], "per": [6, 7, 10, 15], "percentag": 6, "perf_count": 31, "perform": [5, 10, 22], "period": [7, 33], "permit": 29, "persist": [3, 12, 31], "persist_block": 3, "persist_city_properti": 3, "philadelphia": 30, "philadelphia_build": 30, "philadelphia_street": 30, "pick": [], "pilesgrov": 30, "ping": [7, 8, 15, 23, 26, 29, 32], "pip": [22, 23], "pipe": [], "pipelin": [22, 23], "place": [], "place_buildings_in_block": 3, "plain": 32, "platform": [], "plausibl": [], "plot": [3, 15, 26, 30, 31], "plot_circl": [], "plot_citi": [3, 31], "plot_hexagon": [], "plot_od_map": [], "plot_p": 26, "plot_polygon": [], "plot_popul": 15, "plot_sparse_clust": [], "plot_stop": 26, "plot_stops_barcod": [26, 27, 28, 29, 32], "plot_time_barcod": [26, 27, 28, 29, 32], "plot_traj": 15, "plt": [26, 27, 28, 29, 30, 31, 32], "plu": [], "po": [], "poi": 23, "poi_data": 15, "poi_map": [], "poi_tabl": [], "point": [3, 7, 8, 23, 29, 30], "point_color": [], "point_in_polygon": [], "pointer": [], "points_gdf": [], "points\u00b2": [], "poisson": 15, "polici": [], "poly_cr": 7, "polygon": [3, 7, 8, 12, 30], "popul": [3, 12, 15], "portion": 10, "posit": [7, 15], "possibl": [15, 27], "post": [23, 28, 32], "postcod": 30, "postprocess": [28, 32], "pre": [], "precis": 5, "precision_recall_f1_from_minut": 5, "predefin": 3, "predict": 5, "preferenti": 15, "prepar": [], "presenc": 10, "present": [7, 12], "preserv": 33, "previou": [22, 23], "print": [7, 15, 26, 30, 31], "print_stop": [], "prioriti": 12, "privaci": [6, 23, 33], "probabl": 15, "process": [12, 15, 22, 23, 28, 30, 32, 33], "produc": 5, "product": [22, 23], "profession": 12, "progress": 26, "project": [3, 7, 8, 12, 22, 23, 27, 29], "promin": 12, "propag": [], "proper": [10, 30], "properti": [3, 15], "proport": [3, 7], "protocol": [], "provid": [3, 7, 8, 10, 12, 15, 22, 23, 27], "proxim": 23, "public": [], "purpl": 30, "purpos": 33, "put": [], "py": [], "pyarrow": 15, "pydeck": [], "pyfunc": [], "pyplot": [26, 27, 28, 29, 30, 31, 32], "pyproj": [], "pyspark": [22, 23], "python": [22, 23], "q": 7, "q_filter": 7, "q_rang": [], "q_stat": 7, "qbar": 7, "qualifi": 29, "qualiti": 22, "quantil": [], "queri": 12, "r": 5, "radiu": [15, 23, 26, 28, 33], "rais": [3, 5, 7, 8], "random": 15, "randomcitygener": 3, "rang": 7, "rank": [], "rate": 15, "rather": 15, "raw": [], "rawio": [], "rawiobas": [], "rb": [], "re": [7, 26, 27], "reach": [], "read": [22, 23], "read1": [], "read_csv": [], "read_data": 22, "read_fil": [26, 27, 28, 32], "readabl": [], "readal": [], "reader": [], "readi": [22, 23], "readinto": [], "readinto1": [], "readlin": [], "real": 33, "reassign": [], "recal": 5, "recent": [], "recogn": [], "recomput": 10, "reconfigur": [], "record": 7, "recurs": [], "red": [26, 32], "reduc": 12, "ref": 30, "refer": 8, "regardless": [], "region_gdf": [], "regist": [], "reiniti": 15, "rel": [6, 7], "relat": 23, "relev": [10, 15], "reli": 32, "remain": 6, "remov": [7, 12, 30], "remove_overlap": [12, 28, 30, 32], "repeat": 15, "repeatedli": [], "replac": 15, "replace_sparse_traj": 15, "replic": [22, 23], "report": 23, "repres": [3, 15, 29], "reproduc": [6, 15], "reproject": 15, "reproject_to_merc": 15, "request": 12, "requir": [3, 7, 27, 32], "research": [22, 23], "reset": 15, "reset_trajectori": 15, "resid": 15, "residenti": [22, 30], "resolut": 7, "resolv": 10, "resourc": [22, 23, 31], "respect": [], "result": [5, 7, 12, 26, 29, 30], "results_df": 26, "retail": [3, 15, 30, 31], "retail_ratio": 3, "retain": [7, 8], "retriev": [3, 12], "return": [5, 6, 7, 8, 12, 15, 23], "return_cor": [], "revers": 30, "rgba": [], "right": [5, 31], "riverwalk": 30, "rn": [], "road": 30, "roam": 29, "robust": 7, "rog": [], "rog_spark": [], "roster": 15, "rotat": 12, "rotate_streets_to_align": 12, "rotated_streets_gdf": 12, "rotation_deg": 12, "rotation_degre": 12, "routin": [], "row": [3, 6, 7, 10, 12], "rt": [], "rule": [], "run": [15, 22, 23, 26], "runtim": 33, "runtime_data": 26, "runtime_df": 26, "s2": [7, 23], "s3": 15, "s3f": 15, "s3filesystem": 15, "salem": 30, "salem_build": 30, "salem_street": 30, "same": [15, 22, 23, 27, 32], "sampl": [7, 15, 22, 30, 33], "sample_from_fil": [26, 27, 28, 29, 32], "sample_hier_nhpp": 15, "sample_step": 15, "sample_traj_hier_nhpp": 15, "sample_trajectori": 15, "sample_us": [], "sampler": 23, "satisfi": [], "saturdai": [], "save": [3, 15, 30], "save_geopackag": [3, 31], "save_pop": 15, "scalabl": 22, "scalar": 7, "scale": [3, 12, 15, 22, 23, 26], "scan": [], "schema": [12, 23, 30], "scikit": [22, 23], "sea": [22, 23], "seamless": 23, "search": 23, "second": [7, 26, 27, 32], "section": 17, "see": [6, 23], "seed": [3, 6, 15], "seek": [], "seek_cur": [], "seek_end": [], "seek_set": [], "seekabl": [], "segment": 33, "select": [10, 28, 33], "select_hom": 10, "select_most_stable_clust": [], "select_workplac": [], "self": 15, "semant": 7, "sep": [], "separ": [], "sequenc": 29, "sequenti": [23, 29, 33], "seri": [3, 7, 8], "serv": [], "servic": [12, 30], "session": 8, "set": [5, 6, 12, 15, 32], "set_aspect": 30, "set_axis_off": [], "set_cr": 31, "set_osmnx_cache_mod": 12, "set_titl": [26, 30], "set_xlabel": 26, "set_xlim": [26, 27, 28, 29, 32], "set_ylabel": 26, "setup": 25, "sever": [], "shape": [3, 7, 8, 12, 22, 27, 28, 29, 31, 32], "share": [6, 7], "shift": [], "short": [], "shorter": [], "shortest": 3, "shortest_path": 3, "shorthand": 7, "should": 29, "show": [26, 27, 28, 29, 30, 31, 32], "shown": 33, "shp_plt": [], "shuffl": [], "side": 3, "signatur": [], "significantli": [], "similar": [], "simpl": 22, "simpli": [], "simplifi": 12, "simul": 15, "simulate_traj": 15, "simultan": [], "sinc": 7, "singl": [3, 5, 6, 7, 10, 12, 15, 22, 23], "size": [3, 7, 12, 26, 29], "slice": 10, "slice_datetimes_interval_fast": [], "small": 31, "smallest": [], "snow_engin": [], "so": [7, 15], "socket": [], "softwar": [22, 23], "sole": [], "some": [], "sort": [], "sort_tim": [], "sourc": [3, 5, 6, 7, 8, 10, 12, 15, 22, 23], "spam": [], "span": 7, "spark": [8, 23], "spark_sess": 8, "sparksess": 8, "spars": [15, 33], "sparse_path": 15, "sparse_traj": 15, "sparsifi": 15, "spatial": [7, 8, 12, 22, 23, 26, 27, 32, 33], "specif": [], "specifi": [3, 7, 8, 32], "speed": 15, "spend": 29, "spent": [3, 15], "split": 5, "split_fract": 5, "spring": 30, "spurious": [], "sql": [], "sqlalchemi": [], "st_hdbscan": [26, 28], "st_hdbscan_per_us": [], "stabil": 28, "stagger": [], "stai": [15, 29], "standard": 7, "start": [3, 5, 7, 8, 15, 26], "start_": [], "start_col": [], "start_coord": 3, "start_datetim": 10, "start_tim": 8, "start_timestamp": 5, "stat": [], "state": [15, 30], "statement": [], "static": 15, "stationari": 33, "statist": [7, 10], "step": [12, 15], "step_se": 15, "still": [15, 29], "still_prob": 15, "stop": [5, 10, 22, 23], "stop_color": [27, 28, 29, 32], "stop_data": [], "stop_detect": [22, 26, 27, 28, 29, 32], "stop_tabl": [], "stops_gb": 27, "stops_hdb": 28, "stops_output": 26, "stops_subset": 26, "stops_tabl": 10, "stops_tadb": 32, "store": [3, 15], "str": [3, 5, 6, 7, 8, 15], "str_from_tim": 7, "strategi": [], "stratif": 23, "stream": [], "street": [3, 12, 30, 31], "street_adjacency_edg": 3, "street_graph": 3, "street_spac": 3, "streets_df": 3, "streets_gdf": [3, 12, 31], "streets_path": 3, "strftime": [], "strict": [], "strictli": 10, "string": [3, 7, 15], "stringio": [], "strip": [], "structur": 3, "style": [], "subclass": [], "submodul": 25, "subplot": [26, 27, 28, 29, 30, 31, 32], "subset": [8, 26, 30], "subtyp": [12, 30], "subtype_2": 30, "subtype_3": 30, "suit": [], "suitabl": [7, 22, 23], "sum": 6, "summar": [], "summari": [], "summarize_stop": [], "summarize_stop_grid": [], "sundai": [], "suppli": 5, "support": [8, 27], "suptitl": [28, 29], "surfac": 12, "surviv": 7, "switch": [], "symmetr": [], "syneth": 31, "synthet": [22, 23], "synthetic_poi": 31, "system": 8, "systemat": 3, "t": 26, "t0": [26, 31], "ta": [26, 32], "ta_dbscan": [26, 32], "ta_dbscan_label": 32, "ta_dbscan_per_us": [], "tabl": [5, 6, 15], "table_column": [], "table_column_filt": [], "table_column_uniqu": [], "table_nam": [], "tag": 12, "take": [8, 15], "target": 8, "tc": [27, 28, 29, 32], "team": 12, "tell": [], "temp": 12, "tempor": [22, 23, 26, 27, 32, 33], "temporari": [], "termin": [], "tessel": [23, 33], "test": [], "text": 26, "textio": [], "textiobas": [], "textiowrapp": [], "th": 15, "than": [15, 29], "thei": 8, "them": [8, 12, 32], "thi": [3, 7, 8, 10, 12, 15, 17, 26, 29, 32], "those": [8, 12, 28], "though": [], "threshold": 7, "through": [3, 10, 27], "ti": [], "tick": 15, "tight_layout": [26, 27, 29, 30, 32], "tile": 12, "time": [3, 7, 12, 15, 23, 26, 29, 31, 32, 33], "time_col": 15, "time_thresh": [26, 27, 28, 32], "time_threshold": 33, "time_valu": 7, "time_weight": [], "timedelta": 26, "timefram": 8, "timestamp": [5, 7, 8, 15, 26, 27, 28, 29, 32], "timezon": [7, 8], "timezone_offset": 7, "titl": [27, 32], "to_cr": [27, 28, 32], "to_dict": 30, "to_fil": [3, 30], "to_geodatafram": 3, "to_merc": 3, "to_project": [7, 8, 32], "to_rgba": [], "to_str": 26, "to_tessel": [7, 26, 27], "to_timestamp": 7, "to_yyyymmdd": 7, "to_zoned_datetim": 7, "todo": 8, "togeth": [], "tolist": 12, "too": [], "tool": 23, "top": [], "top_k_destin": [], "top_k_origin": [], "total": [5, 6, 7, 15, 26], "total_bound": [27, 28, 32], "total_dur": [], "total_pr": 5, "total_truth": 5, "toyama": 29, "tp": 5, "trace": 22, "trackintel": [22, 23], "traj": [8, 15, 26, 27, 28, 29, 32], "traj_clean": 26, "traj_col": [5, 7, 8, 10, 15, 27, 28, 29, 32], "traj_from_dest_diari": 15, "traj_subset": 26, "trajectori": [7, 8, 15, 22, 23, 26, 33], "transform": [12, 15, 22], "translat": [], "transpar": 15, "travel": [15, 23], "tree": [], "trigger": [], "trip": [6, 22, 33], "true": [3, 7, 12, 15, 26, 27, 28, 29, 30, 31, 32], "true_visit": 5, "truncat": 12, "truth": [5, 15], "try": [], "ts_seri": [], "tty": [], "tunnel": [12, 30], "tupl": [3, 12, 31], "tutori": 27, "twilight": [], "two": [3, 12, 29, 32], "two_dai": 26, "txt": [], "type": [3, 5, 6, 7, 8, 10, 12, 15, 31], "typic": [], "tz": [15, 27], "tz_offset": 7, "u": 3, "uid": [], "unchang": [], "underli": [], "underneath": [], "understand": 33, "uniform": 33, "uniqu": [3, 5, 7, 26], "unit": [7, 12], "univers": [22, 23, 30], "unix": [7, 27], "unix_t": [27, 28, 29, 32], "unknown": 12, "unless": [], "unlik": 32, "unspecifi": [], "unsupportedoper": [], "until": [3, 15], "untransl": [], "unus": [], "unweight": [], "up": [], "updat": [3, 12, 15], "upenn": [22, 23], "upon": [], "upper": [], "upper_quantil": [], "us": [3, 7, 8, 12, 15, 26, 27, 30, 32, 33], "usabl": [], "use_datetim": [], "use_offset": [], "user": [5, 7, 8, 10, 15, 26, 27, 28, 29, 32], "user_data_hdb": 28, "user_data_tadb": 32, "user_id": [5, 7, 8, 27, 28, 29, 32], "userwarn": [], "usr_polygon": [], "usual": [], "utc": 7, "utc_timestamp": 7, "util": 12, "v": [3, 26], "va": 26, "val": 26, "valid": [12, 22, 23], "valu": [3, 6, 7, 15, 28], "value_count": 30, "valueerror": [3, 5, 7, 8], "vari": [], "variabl": [15, 33], "variat": 33, "variou": [5, 22], "vector": 3, "verbos": [3, 7, 15], "version": [], "via": 7, "view": 7, "visit": [22, 23], "viz": [26, 27, 28, 29, 32], "w": 7, "w_min": [], "wa": [], "wai": [], "walnut": 30, "warn": [7, 26], "watt": [22, 23], "we": [26, 32], "web": [3, 12, 15], "web_mercator_origin": 3, "web_mercator_origin_i": 3, "web_mercator_origin_x": [3, 15], "websit": 23, "week": 7, "weekdai": [], "weekli": 7, "weight": [], "weight_col": 6, "weight_freq": [], "well": 27, "what": 10, "when": [6, 7, 12, 15, 27], "whenc": [], "where": [3, 5, 6, 7, 15, 31], "whether": [3, 15], "which": [3, 7, 15, 27, 29, 33], "while": [], "white": [], "who": 8, "whole": 15, "whose": [3, 6, 7], "width": 3, "window": [7, 10, 23, 26], "within": [3, 7, 8, 12, 15, 29, 32], "without": [3, 22, 23], "wkt": 7, "woodstown": 30, "work": [3, 15, 27, 31, 32], "work_dat": [], "work_end_hour": [], "work_ratio": 3, "work_start_hour": [], "workdai": [], "workday_stop": [], "workplac": [15, 22, 23, 30, 31], "workstat": [22, 23], "would": [], "wrap": [], "wrapper": 30, "writabl": [], "write": 3, "write_through": [], "writeabl": [], "writelin": [], "writer": [], "written": [], "wt": [], "x": [3, 7, 8, 12, 15, 26, 27, 28, 29, 32], "x8": 3, "x_block": 12, "x_mercat": 12, "y": [3, 7, 8, 12, 15, 27, 28, 29, 32], "y8": 3, "ye": [12, 30], "yield": [], "your": 22, "youthful_may": 26, "yyyi": [], "yyyymmdd": 7, "z": 3, "zero": [], "zip": 26, "zone": 7, "zoned_datetime_from_ts_and_offset": [], "zorder": 3, "\u03b5": [6, 15], "\u03b5_x": 15, "\u03b5_y": 15, "\u03c3": 15, "\u03c3\u00b2": 15}, "titles": ["Aggregation & Debiasing", "agg", "aggregation", "city_gen", "constants", "contact_estimation", "displacement", "filters", "filters_spark", "generation", "home_attribution", "io", "map_utils", "metrics", "stop_detection", "traj_gen", "visit_attribution", "API Reference", "base_module package", "Co-location & Contact Networks", "Data Ingestion", "Filtering & Completeness", "Getting Started", "NOMAD: Network for Open Mobility Analysis and Data", "Mobility Metrics", "src", "Benchmarking Stop Detection Algorithms", "Grid-Based Stop Detection", "HDBSCAN Stop Detection", "Lachesis Stop Detection", "Downloading places of interest (POI) Data from OSM", "Generating synthetic places of interest (POI) Data", "TADBSCAN Stop Detection", "Stop Detection", "Synthetic Data Generation", "Tessellation", "Visit Attribution"], "titleterms": {"With": 22, "agg": 1, "aggreg": [0, 2], "algorithm": 26, "analysi": 23, "api": 17, "attribut": 36, "base": [27, 33], "base_modul": 18, "basic": 22, "benchmark": 26, "bound": 30, "box": 30, "citi": 30, "city_gen": 3, "co": 19, "commun": 23, "comparison": 26, "complet": 21, "constant": 4, "contact": 19, "contact_estim": 5, "content": 18, "core": [], "data": [20, 23, 30, 31, 34], "dbscan": 33, "debias": 0, "detect": [26, 27, 28, 29, 32, 33], "displac": 6, "doc_test": 18, "download": 30, "exampl": 22, "featur": 22, "filter": [7, 21], "filters_spark": 8, "from": 30, "gener": [9, 31, 34], "get": 22, "grid": [27, 33], "grid_bas": [], "hdbscan": [28, 33], "home_attribut": 10, "i": 22, "indic": 23, "ingest": 20, "instal": [22, 23], "interest": [30, 31], "io": 11, "kei": 22, "lachesi": [29, 33], "licens": 22, "link": 23, "locat": 19, "map_util": 12, "method": 33, "metric": [13, 24], "mobil": [23, 24], "modul": [17, 18, 23], "name": 30, "network": [19, 23], "next": 22, "nomad": [22, 23], "od": [], "open": 23, "osm": 30, "overal": 26, "overview": [23, 33], "packag": 18, "paramet": 3, "perform": 26, "place": [30, 31], "poi": [30, 31], "postprocess": [], "preprocess": [], "privaci": [], "process": [], "quick": [22, 23], "refer": 17, "requir": 22, "return": 3, "runtim": 26, "scalabl": 26, "sequenti": [], "setup": [18, 26], "snowflak": [], "spark": 22, "sparsiti": [], "src": 25, "start": 22, "step": 22, "stop": [26, 27, 28, 29, 32, 33], "stop_detect": 14, "submodul": 18, "support": [22, 23], "synthet": [31, 34], "tabl": 23, "tadbscan": 32, "tessel": 35, "traj_gen": 15, "util": [], "visit": 36, "visit_attribut": 16, "visual": 26, "viz": [], "what": 22}}) \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 3332079c..f51bcbc7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -56,6 +56,7 @@ Installation :caption: Examples :hidden: + source/ingesting-data source/benchmarking_of_stop_detection_algorithms source/lachesis_demo source/tadbscan_demo diff --git a/docs/source/benchmarking_of_stop_detection_algorithms.ipynb b/docs/source/benchmarking_of_stop_detection_algorithms.ipynb index 747c1e8c..64549ab4 100644 --- a/docs/source/benchmarking_of_stop_detection_algorithms.ipynb +++ b/docs/source/benchmarking_of_stop_detection_algorithms.ipynb @@ -2,73 +2,191 @@ "cells": [ { "cell_type": "markdown", - "id": "92838936", + "id": "67fc810a", "metadata": {}, "source": [ - "# Benchmarking Stop Detection Algorithms\n", - "\n", - "This notebook compares the performance of four stop detection algorithms: **Lachesis**, **TA-DBSCAN**, **Grid-Based**, and **HDBSCAN**. We evaluate both overall runtime on a full trajectory dataset and how runtime scales with increasing data size." + "# Comparing runtimes of different stop detection algorithms on toy datasets" ] }, { "cell_type": "markdown", - "id": "426e43f2", + "id": "f152187c", "metadata": {}, "source": [ - "## Setup" + "Here we compare the runtimes of four different stop detection algorithms: Lachesis, grid-based, temporal DBSCAN, and HDBSCAN." ] }, { "cell_type": "code", "execution_count": 1, - "id": "74f96664", + "id": "474229df", "metadata": { "execution": { - "iopub.execute_input": "2025-10-17T05:59:34.956525Z", - "iopub.status.busy": "2025-10-17T05:59:34.956274Z", - "iopub.status.idle": "2025-10-17T05:59:38.575372Z", - "shell.execute_reply": "2025-10-17T05:59:38.574638Z" + "iopub.execute_input": "2025-11-24T18:33:19.946986Z", + "iopub.status.busy": "2025-11-24T18:33:19.946986Z", + "iopub.status.idle": "2025-11-24T18:33:23.251955Z", + "shell.execute_reply": "2025-11-24T18:33:23.251955Z" } }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Lachesis execution time: 0.02721381187438965 seconds\n", + "TA-DBSCAN execution time: 0.012791156768798828 seconds\n", + "TA-DBSCAN clustering time: 0.009821414947509766 seconds\n", + "TA-DBSCAN post-processing time: 0.0029697418212890625 seconds\n", + "Grid-Based execution time: 0.022524595260620117 seconds\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "HDBSCAN execution time: 0.3206779956817627 seconds\n", + "HDBSCAN clustering time: 0.3206779956817627 seconds\n", + "HDBSCAN post-processing time: 0.0 seconds\n" + ] + } + ], "source": [ - "import time\n", - "import warnings\n", - "import pandas as pd\n", - "import geopandas as gpd\n", + "%matplotlib inline\n", + "import matplotlib\n", + "matplotlib.use('TkAgg')\n", "import matplotlib.pyplot as plt\n", + "plt.ion()\n", + "\n", + "# Imports\n", "import nomad.io.base as loader\n", - "import nomad.filters as filters\n", - "from nomad.stop_detection.viz import plot_pings, plot_stops, plot_time_barcode, plot_stops_barcode, clip_spatial_outliers\n", - "import nomad.stop_detection.lachesis as LACHESIS\n", + "import geopandas as gpd\n", + "from shapely.geometry import box\n", + "import pandas as pd\n", + "import numpy as np\n", + "from nomad.stop_detection.viz import plot_stops_barcode, plot_pings, plot_stops, plot_time_barcode\n", "import nomad.stop_detection.dbscan as DBSCAN\n", + "import nomad.stop_detection.lachesis as LACHESIS\n", "import nomad.stop_detection.grid_based as GRID_BASED\n", "import nomad.stop_detection.hdbscan as HDBSCAN\n", + "import nomad.filters as filters \n", + "import nomad.stop_detection.postprocessing as post\n", + "import time\n", + "from tqdm import tqdm\n", + "\n", + "# Load data\n", + "import nomad.data as data_folder\n", + "from pathlib import Path\n", + "data_dir = Path(data_folder.__file__).parent\n", + "city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet')\n", + "outer_box = box(*city.total_bounds).buffer(15, join_style='mitre')\n", + "\n", + "filepath_root = 'gc_data_long/'\n", + "tc = {\n", + " \"user_id\": \"gc_identifier\",\n", + " \"timestamp\": \"unix_ts\",\n", + " \"x\": \"dev_x\",\n", + " \"y\": \"dev_y\",\n", + " \"ha\":\"ha\",\n", + " \"date\":\"date\"}\n", + "\n", + "users = ['admiring_brattain']\n", + "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc)\n", + "\n", + "# Lachesis (sequential stop detection)\n", + "start_time = time.time()\n", + "stops = LACHESIS.lachesis(traj, delta_roam=20, dt_max = 60, dur_min=5, complete_output=True, keep_col_names=True, traj_cols=tc)\n", + "execution_time_lachesis = time.time() - start_time\n", + "print(f\"Lachesis execution time: {execution_time_lachesis} seconds\")\n", + "\n", + "# Density based stop detection (Temporal DBSCAN)\n", + "start_time = time.time()\n", + "user_data_tadb = traj.assign(cluster=DBSCAN.ta_dbscan_labels(traj, time_thresh=240, dist_thresh=15, min_pts=3, traj_cols=tc))\n", + "clustering_time_tadbscan = time.time() - start_time\n", + "start_time_post = time.time()\n", + "cluster_labels_tadb = post.remove_overlaps(user_data_tadb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3)\n", + "execution_time_tadbscan = time.time() - start_time\n", + "post_time_tadbscan = time.time() - start_time_post\n", + "print(f\"TA-DBSCAN execution time: {execution_time_tadbscan} seconds\")\n", + "print(f\"TA-DBSCAN clustering time: {clustering_time_tadbscan} seconds\")\n", + "print(f\"TA-DBSCAN post-processing time: {post_time_tadbscan} seconds\")\n", + "\n", + "# Grid-based\n", + "start_time = time.time()\n", + "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, traj_cols=tc, data_crs='EPSG:3857')\n", + "stops_gb = GRID_BASED.grid_based(traj, time_thresh=240, complete_output=True, traj_cols=tc, location_id='h3_cell')\n", + "execution_time_grid = time.time() - start_time\n", + "print(f\"Grid-Based execution time: {execution_time_grid} seconds\")\n", "\n", - "city = gpd.read_file(\"../../examples/garden_city.geojson\")\n", - "traj = loader.sample_from_file('../../examples/gc_data/', users=['youthful_mayer'], format='csv')" + "# HDBSCAN\n", + "start_time = time.time()\n", + "user_data_hdb = traj.assign(cluster=HDBSCAN.hdbscan_labels(traj, time_thresh=240, min_pts=3, min_cluster_size=2, traj_cols=tc))\n", + "clustering_time_hdbscan = time.time() - start_time\n", + "start_time_post = time.time()\n", + "cluster_labels_hdb = post.remove_overlaps(user_data_hdb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) \n", + "execution_time_hdbscan = time.time() - start_time\n", + "post_time_hdbscan = time.time() - start_time_post\n", + "print(f\"HDBSCAN execution time: {execution_time_hdbscan} seconds\")\n", + "print(f\"HDBSCAN clustering time: {clustering_time_hdbscan} seconds\")\n", + "print(f\"HDBSCAN post-processing time: {post_time_hdbscan} seconds\")" ] }, { "cell_type": "markdown", - "id": "cb276fd9", + "id": "c88f426d", "metadata": {}, "source": [ - "## Overall Runtime Comparison\n", - "\n", - "We first measure the total execution time for each algorithm on the complete dataset." + "## Summary of Single-User Performance" + ] + }, + { + "cell_type": "markdown", + "id": "6a678431", + "metadata": {}, + "source": [ + "### Lachesis" ] }, { "cell_type": "code", "execution_count": 2, - "id": "19184dee", + "id": "b7480c93", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:23.251955Z", + "iopub.status.busy": "2025-11-24T18:33:23.251955Z", + "iopub.status.idle": "2025-11-24T18:33:23.475346Z", + "shell.execute_reply": "2025-11-24T18:33:23.475346Z" + } + }, + "outputs": [], + "source": [ + "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", + " gridspec_kw={'height_ratios':[10,1]})\n", + "\n", + "gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3')\n", + "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c')\n", + "\n", + "plot_stops(stops, ax=ax_map, cmap='Reds')\n", + "plot_pings(traj, ax=ax_map, s=6, point_color='black', cmap='twilight', traj_cols=tc)\n", + "ax_map.set_axis_off()\n", + "\n", + "plot_time_barcode(traj[tc['timestamp']], ax=ax_barcode, set_xlim=True)\n", + "plot_stops_barcode(stops, ax=ax_barcode, cmap='Reds', set_xlim=False, timestamp='unix_ts')\n", + "\n", + "plt.tight_layout(pad=0.1)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "98cdda1f", "metadata": { "execution": { - "iopub.execute_input": "2025-10-17T05:59:38.579313Z", - "iopub.status.busy": "2025-10-17T05:59:38.578920Z", - "iopub.status.idle": "2025-10-17T05:59:58.677680Z", - "shell.execute_reply": "2025-10-17T05:59:58.676969Z" + "iopub.execute_input": "2025-11-24T18:33:23.475346Z", + "iopub.status.busy": "2025-11-24T18:33:23.475346Z", + "iopub.status.idle": "2025-11-24T18:33:23.482904Z", + "shell.execute_reply": "2025-11-24T18:33:23.482904Z" } }, "outputs": [ @@ -76,189 +194,214 @@ "name": "stdout", "output_type": "stream", "text": [ - " Algorithm Runtime (s)\n", - " Lachesis 1.060303\n", - " TA-DBSCAN 6.314632\n", - "Grid-Based 0.207118\n", - " HDBSCAN 12.504672\n" + "Summary of Single-User Performance\n", + "Lachesis execution time: 0.02721381187438965 seconds\n", + "TA-DBSCAN execution time: 0.012791156768798828 seconds\n", + "Grid-Based execution time: 0.022524595260620117 seconds\n", + "HDBSCAN execution time: 0.3206779956817627 seconds\n" ] } ], "source": [ - "algorithms = [\n", - " ('Lachesis', lambda t: LACHESIS.lachesis(t, delta_roam=20, dt_max=60, dur_min=5, \n", - " complete_output=True, keep_col_names=True,\n", - " latitude=\"latitude\", longitude=\"longitude\")),\n", - " ('TA-DBSCAN', lambda t: DBSCAN.ta_dbscan(t, time_thresh=240, dist_thresh=15, min_pts=3, \n", - " dur_min=5, complete_output=True,\n", - " latitude=\"latitude\", longitude=\"longitude\")),\n", - " ('Grid-Based', lambda t: GRID_BASED.grid_based(\n", - " t.assign(h3_cell=filters.to_tessellation(t, index=\"h3\", res=10, \n", - " latitude='latitude', longitude='longitude', \n", - " data_crs='EPSG:4326')),\n", - " time_thresh=240, complete_output=True, timestamp='timestamp', location_id='h3_cell')),\n", - " ('HDBSCAN', lambda t: HDBSCAN.st_hdbscan(t, time_thresh=240, min_pts=3, min_cluster_size=2, \n", - " dur_min=5, complete_output=True,\n", - " latitude=\"latitude\", longitude=\"longitude\"))\n", - "]\n", - "\n", - "results = []\n", - "for name, func in algorithms:\n", - " t0 = time.time()\n", - " stops_output = func(traj)\n", - " results.append({'Algorithm': name, 'Runtime (s)': time.time() - t0})\n", - " if name == 'Lachesis':\n", - " stops = stops_output\n", - "\n", - "results_df = pd.DataFrame(results)\n", - "print(results_df.to_string(index=False))" - ] - }, - { - "cell_type": "markdown", - "id": "5b150f1e", - "metadata": {}, - "source": [ - "## Visualization\n", - "\n", - "Spatial and temporal visualization of detected stops using the Lachesis algorithm." + "print(\"Summary of Single-User Performance\")\n", + "print(f\"Lachesis execution time: {execution_time_lachesis} seconds\")\n", + "print(f\"TA-DBSCAN execution time: {execution_time_tadbscan} seconds\")\n", + "print(f\"Grid-Based execution time: {execution_time_grid} seconds\")\n", + "print(f\"HDBSCAN execution time: {execution_time_hdbscan} seconds\")" ] }, { "cell_type": "code", - "execution_count": 3, - "id": "f9c852d1", + "execution_count": 4, + "id": "41e9a154", "metadata": { "execution": { - "iopub.execute_input": "2025-10-17T05:59:58.680965Z", - "iopub.status.busy": "2025-10-17T05:59:58.680723Z", - "iopub.status.idle": "2025-10-17T05:59:59.277513Z", - "shell.execute_reply": "2025-10-17T05:59:59.276740Z" + "iopub.execute_input": "2025-11-24T18:33:23.482904Z", + "iopub.status.busy": "2025-11-24T18:33:23.482904Z", + "iopub.status.idle": "2025-11-24T18:33:23.492088Z", + "shell.execute_reply": "2025-11-24T18:33:23.492088Z" } }, "outputs": [ { - "data": { - "image/png": "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FhFRB98YL5fo1nz9TZvnnbN9Fxw1TgZNRO2EexYRUQfz+gP4v/c/RF19251tkvuYXlu4DHUeL59YJGonDJqIiE7DJbpyRzX+seD9NuspfdFHn2FfYbHKm4i6YeeWycnJWPT26+jKEuK775vYiaj9fLbmC/zql/+FTXsKcOtN075VXqu/2oD12/Pw3tJl6Nu3b8N4q9WK8ReOQ1duw+VSY3u/S4/tOHWJoMnhcGD2nfegK8t64x+nexWIqAuy2+0orHDhiRdexYHCQ5g1bUqr70WSs1Sr/70Bf359Puy9+mHuLx/u0PapI9pwCcpm3PrDdl0G23Eyiq9RISI6TWxRdsT3HYQ3l61C7u69mHv7bAwa0M/QvJXVNXg1azHWZucipvcA9Y4/ImpfDJqIiE4jmy0KSf3TsPVACe5++He4ZNx5mHLJJIwaMQzR9uYv3vX7/cg7UIjP1m3Eqq82IGSJRmL/NJh5HxNRh2DQRER0mslluaSUfggGAvhi6178e8t2hPx+9O+bgl6JCTCbTKhx1aKkvAImswVBsx1xKakq4CKijsOgiYiok7BYrejVR788FwwGUe31oLLcpYblbFJc3zSVhohOD+59RESdkHQdYImNO92rQURNsJ8mIiIiIgMYNBEREREZwKCJiIiIyAAGTUREREQGMGgiIiIiMoBBExEREZEBDJqIiIiIDGDQRERERGQAgyYiIiIiAxg0ERERERnAoImIiIjIAAZNRERERAaYNE3TjCQkIiIi6sl4pomIiIjIAAZNRERERAYwaCIiIiIygEETERERkQEMmoiIiIgMYNBEREREZACDJiIiIiIDGDQRERERGcCgiYiIiMgABk1EREREBjBoIiIiIjKAQRMRERGRAQyaiIiIiAxg0ERERERkAIMmIiIiIgMYNBEREREZwKCJiIiIyAAGTUREREQGMGgiIiIiMoBBExEREZEBDJqIiIiIDGDQRERERGQAgyYiIiIiAxg0ERERERnAoImIiIjIAAZNRERERAYwaCIiIiIygEETERERkQEMmoiIiIgMYNBEREREZACDJiIiIiIDGDQRERERGcCgiYiIiMgABk1EREREBjBoIiIiIjKAQRMRERGRAQyaiIiIiAxg0ERERERkAIMmIiIiIgMYNBEREREZwKCJiIiIyAAGTUREREQGMGgiIiIiMoBBExEREZEBDJqIiIiIDGDQRERERGQAgyYiIiIiAxg0ERERERnAoImIiIjIAAZNRERERAYwaCIiIiIygEETERERkQEMmoiIiIgMYNBEREREZACDJiIiIiIDGDQRERERGcCgiYiIiMgABk1EREREBjBoIiIiIjKAQRMRERGRAQyaiIiIiAxg0ERERERkAIMmIiIiIgMYNBEREREZwKCJiIiIyAAGTUREREQGMGgiIiIiMoBBExEREZEBDJqIiIiIDGDQRERERGQAgyYiIiIiAxg0ERERERnAoImIiIjIAAZNRERERAYwaCIiIiIygEETERERkQEMmoiIiIgMYNBEREREZACDJiIiIiIDGDQRERERGcCgiYiIiMgAq5FEoVAIhw8fRkJCAkwmk5FZiIiIiNqcpmlwuVxITU2F2WzufEGTBExpaWntvzZEREREBhQVFWHw4MHodEGTnGGKrGBiYiIQCgL1Ln2ipun/5ASUBiDgB0wWfUALQZ8QThqXCNRW6mnMVsBs0ucVcb0BVzkQOZMlywiF9LQWW+N4yVfGq7wlnQYEAyryRMADWKIAiyw/klyfrvIzW/R8QgHAXQdERetpJQ/Jb2AGULwbMJmBY0Wv4eWahp0HU3TcqZQ3nYBWXwtt7ya9Xgj5XtLOgnZgq/59yPci31MoBPNZE2CK0eul5nYhtHNdQz7mUZkwxSa0zN/tRGjbF43pxlyq6kco93MgKPUt2LL+RMh0v1f/VHVe0tqOXU9EwAc4q4Eou57eKvlaAZ8HcFYBsu4WC8yX36LqdGj1Ar1uq/XwA1Y7EPQD1RVAXBLM1/0IpuQ++nY4yhFa8Vp4vzPBdOlN0D5b3LBo0+Xf04cj2xEMwvy9e2Dq3a+xLKqOILTwZX2drFYgEIDmqweCoYb5LLPmwpTSHx1NqyxF8J2/hPfdIBAIAna7PtHr0cfZo/VhT72qD5rsm44KIClFbwuCGhAdo6cJ+GGZ8WME5v8dsNkAW1TjsupqoR0pAZJT9PZL8pG8I2n8PmieemgBKRcrtEAAkL81TV+m1IGAD5qUm9UKE0yw3/tzuF96DprPj8CRIzAPTIUprmV9bEHql8+HxKefgzVtCAJFB+H49S8a1iVU50YopMFktSHk96mvyhQTC1OkbOSrdrngP1QE66DBMMfHQ/N6ofn9GPj8XxA1ZAh8Bw+g+IEHYLLZms2nyiKctu9DD6H093+AKcqm51lbpzd9moagLN9mw/BX/qaKa++9P1Vp0n77MPIf+S08JUcQNaA/zDYbRr36sirTbXf/RKUZ8+rLap7cu3+CoM8Hd0kp7AMHYNyb/0L8sDNaXU9qC/Yj+8f3whwVLh+fDxf+8+9qGV/fdY9aX80fQED2dZsVF732KhLCy3EW7McXP7xb/X3p66+qtJ/+8G5YbHqZBP1eXPn6q0gaPkwNV+cXYNWdMl1fVtDvw5Q3XlXL+vD2H6txU9/6pxp+/wcyLH+ZcNP//VNNW3TbXWoZcqVmxrx/oXc4356oMr8AC269C2Z7FAJeH8r3H0DykCGwh+MMEfB54amtRUCT3c6mDtt3Zr2OvuFyczqd6kROJDbpdEFT5JKcBEwNQZPNpMdDkYAjckBTAZElfGAJjxdSgSVoMvnCgZA1nE5vgBCfKC1g40FIBTqSn08/4EhaIeMk6FF5m/RGXoImmeazALYY/SAQIesqy5OGVsZLPn6ffjeXXdJG6WlkeYkJQE1c47odLZzOlJjIoKkdaDYztPhw+Yvwd9IwTv7JuGAAZvkOIkGT1YRQXKxedySgkmnHCpqsaJFOBU0yLhI0RcU0Lr8pWa7Hon9KfVVpowGrfmBpQQ7mEsRLGklvj9XTeqyA3w3INplMMEudk6ApNlpfL6mbQQsQE6fXW68LiIlW6aTeqe0IehCKkfQS8AdhSoiHFmPXg51gk2Gp38Jb32x+lYffjVAkjRwIJDiok30pBMhBKBiEJaH5PB1F89UhGB3eHgmSJGiKi9cnmmQdmwxD9skQNGln6mxAbIwe2EjQFB9OU18HS3w8AvYoIDYOJglkw0ImDVqVFYiJUYGjal/i42EKB2Wa1wMtFIBmDsFkj0FIvldzY9Bkio6B5tb06RKkBQKwx8fBarNBs1jgt1pgiY0Lf88n2W6PDZrmQmJCAqyJiQgkJCAgwU14WyXQUEFTdAxCWkh9VRIYmaOjGw82oSB8FgtssbHq+wvZbAi59DyjEhPhS0iA02ptMZ8qi0jauDjUSppYvY0LeLzNgyarVeUnbW58uK2VeeItVljMZtijo9UxQ9JIorhImvA8MhwK/zCNtljU+PhTqGfmhATEWaywxenr6Q+GGpdhscJksyIQcutBk0Vf54TIchISEBvez9U8mqaGo2S/lEOJM6jGq+OdlE04vXy3wutsXFZM03zUsAUWmw1Bvz+ctz5OAsmQ34+E+MZ8eyJffIL63u2xcfCZLXCazIiLjUFsk33E57YC7noVNEXFxKgfCccqt9NxuxBvBCciIiIygEETERERkQEMmoiIiIgMYNBEREREZACDJiIiIiIDGDQRERERGcCgiYiIiMgABk1EREREBjBoIiIiImqrHsGP5TvjL8Jll2Qic8IE1Tv3nJm3YP7CxXhj3jsYmZGBvH37MHL4MLzwpz+o9HMf/A32HDioevguOVKGrV98oo9/6BFkjh8H2OPxzP/8AZdlTsQLf3gSc3/zuOp1N29fPkZmDMcLzzzZsOy5jzyFtes34tdz78W67G+QOfZczL7hWsx96jl8kbMFuR+/j/lLP8ScG6eq9NfceR9GDE1D5rixeg/lwQDeXLgUH732V8xf8ZHqBfgPf38TudkbMPf3/4u9B4tVb+d3fG8a1m3eihce/W/MX/Ex5ky9Uk1/8bX/O9Vio+OYO3cu/vrHZzB/5adYt3UXMs87Ry/7l19B1kef671fmy1qnHxfppT38OLLf8eUKVMw4oyhyNucjTuuv0rVmTcffh5nnj0KmZmZKu85c+bo+f/P08j65EvMnnIZ7n/+VVw0rQrr1m/ApAGxWLd1t/T7i8xzz1F1ZN22XdhbdBgr//dJZH28Fm9+sBp3TL4I67bvwd7iUoxI7Ye/PnAXrv3l0xgxeKBaTuY5Z6rP2VdehKw1G7BuyzZkjhqh9wgur2cwWzB70rm49tk3ccfki1Ud+/7kOZi/6D38e+Fi/PWuW5C1brPq5fvNf2/R5/N5UVJbj18H++L7P75X5T9/8Xt4489vYuUvf4T7314BrN2NL1avxq+uuxRvfrUJH02eiazs7Vh3oBSZGelYt6cAL878WUNZz58/H5qrGv9+92NknjlM9bw9+4JRyNq0S/Xwv77wCCYNHQhz7BKs37YTL7zwQofWhfnvLcEzz76FzQ/9EA8sXau/dcBmw6QzBmF93n5VLpNGDG32GpVZY0bg/AVf4MFLz1fD64vK8GVxOR68dJwqQ/OHqxDctR/ryxzIHDoYs849Cwtyd2Nd/kHA5cTzUwfg3bxCbCgpx8ShaWp5G4pKMHFAH6zfXwRNC2HSoIHQ/D5sKKnExH7J2HCkEhMHDcC8XfnIiI/FsxeNw02frsPK+4FFB4sxPW0gfl1wCH8cMgwPfrkB+TVOfP/MjGbb+nVpGf54yUQs3lugXmHydXEJLl+6DF/n7UXI5cSYosOqJ3KVtuiQ+nz6grF4/3Apsh01+F3mJDyyYSPG9euLm4adgd/mbsO+snLMjIqGucqh8sw5XIK4xx6DJTERQWcNRpaU4qaEBCw9cAA3Dh2qPsX1AwZg6rr1eGj1ajhKS3FDRgaWHzqEUF0tFpceUWmG2KNxXnIyPrnjdix/8y38IX8vzk1Mwn8A+NPB/QjWuWEJ+lTdTnn8cTz/2ON4fv8+Ne+05ctUj9kv79yGwdHR2FRZgfOCXkwI10nZT1vjxjvvwITKcnyybxeuTR2MDwoP4mfLl2HDpk0oOViA0Um9sL2qEoekV3mTGeOfeBxRSUlq3rHDh2Nx4X6cFZ+AlU88jvFjz8df9+7EExdMxKt5u7CtqgKu5cuxpSAfeXl5uOWaa7G5qgI2Ty0u7jcQ66oqsOaJJ1Re+w/tR6nXg8Dy5Wr7/pK/GwNiY1HqdiPniSfwh98+hpzqSpgsFmjBIGwrluOen/8nerJX9u/BA+deiG+qyrGy3oEpVeW4KDkZ2aXFKKhxIBgIYCDMate/IC4Oy0sK8ckP78Sar77sukHT9l27UFZRjoOFsiNretC06D2s+fd65G7fCUdNjfqMBE0Lly5X44Tf72/IZ9GyD1B4qFi9zmTH7jyUV1SqoGnR8pWqC36HswZbd+1tFjQtWvERyiqrkLX0A2zcvBWFhw6roGnxR5+hrMqh0mQtawya1mZvwtbde1FYUhZ+91wIa7/J1dOtWKWWs2NfgRpe/PEaOJy16m97VBSyt+5UQVPWB3rQJNNfPNVCo+NauHChCpqyPvoM2Tt2o7CkFNnbdkEO11mr1ujBrsmkxqkvzBqlgqY1a9Ygd0svOBxVsMtrMjQNazduwdbtO3Dw4EGVtzTGKv8mQdPiz9ahqF7DxpwcHMxIR/YuvWEvLHeoZWXvzIPDpdeDrNVfYu2WHbBbzMjenQ9HrRtbC4pU0LQ2dye2yoFX5i2raBY0Ze/eh8JKvc7r7z00q6Bp7Z4DsMsrNyRokvwXvYeNG3L1oGm9BEshrN2pr49sqj8URNbiJQ1Bk/y9Nk8/0C3O2QFszUdZRSWyNm7D2jx9XbKydyD7YAkKq6qRnV/UrM7KAUoCiY1bdqOwpk69zkKCpgWbdqvlZReVorCqBqYyPzZuye3woCnrvfexo7RS375t4XIwmVDocCL74GG1joW1Hn28vPpE01TQtKPKhQU7CtRw9uEKlLs9WLAtT3+1TM0qaIf2I7u0EkUutx40bctDduFh1R48D+DdvYXIOVKJwno/TGYzsg8fUcvMOawHDIfcPmihIL4pc6Cozo1vKhwo8vjwVVkltjuceBZQf6v1PnhYBU3LK2vwRwBLCw6ixuuD/aj3Gn5TVqEHTfv2q/WQ4SPLliF723a13vusZvXaErGprEIdlJ+W/ErKsLnGid8BWH6wEIfq6lTQ9MHhEji9PsQUH9bfsxcMYktlFcwffthw0B4D4KaRI7DsYKEKmuQzEjTl1dVh4erV8JQdUUHTiuJiFSjmOPV6nGepQ0nAj/X789XwJ5XlKPF6VdD0maNKlY/Jr3831pUrVdD0WZW+X9QvX6E+t7hqsM9dh+pgAM7qKjXuVIKmrzZuhDs2Drk11YgyW7DN5cTC5SvwzdZc+KprUO73YbfLiVp57Y68eH7lSlUGIn/0OVjvqES5z4uDK1eisLgYO2v196muKz+CGr8Pi1Ysx+YdO+BwOGAOBHDEUQlzrUUFTTLvoY9WqvTemmq4g0EsXrFcL6M6F4q99agLBFDx0cpw0FQFk9kELaShhEET9tXpZb3JUY4KBLHZUYGLMBI5ZYdxQN7ZqQEVNjvkDbMXYDB2uBzwZW9EZ8DLc0REREQGMGgiIiIiMoBBExEREZEBDJqIiIiIDGDQRERERGQAgyYiIiIiAxg0ERERERnAoImIiIjIAAZNRERERAYwaCIiIiIygEETERERkQEMmoiIiIgMYNBEREREZACDJiIiIiIDGDQRERERGcCgiYiIiMgABk1EREREBjBoIiIiIjKAQRMRERGRAQyaiIiIiAxg0ERERERkAIMmIiIiIgMYNBEREREZwKCJiIiIyAAGTUREREQGMGgiIiIiMoBBExEREZEBDJqIiIiIDGDQRERERGQAgyYiIiIiAxg0ERERERnAoImIiIjIAAZNRERERAYwaCIiIiIywIpTdM7ZZ+OySzKROWECoIXUuDkzbobX48HIjAzk7duHkcOHNaS/5cbrsefAQSDgQ8mRsobxM264DpnjxwH2eOwvyMdlmRP18ddfC2ga8vblY2TG8GbLnjHtGqxdvxGzb7wO6akDkDn2XDV++jVX4IucLerv2TdMbUh/2YXnY8TQNGSOGwuYzEAwAG99vZ5u2hQgFMKBQ4f1PK6+HHsPFgMmYPZ1V6v8Vbrrrm6YTm3vlltuUZ+zr7kC6akDkXneOUgf0E8fN+VywGxR/9IH9FXflylloJp2+eWXY8QZQ5G3ORuzr75M1RmvPR5nnj0KmZmZLfO/6hL1Of2KTFw0bTrSBw/GpAGxSO/XB0AImeeeo+pIev8+2Fuk14nZky+B1+vF7O9OQnrf3thbXIoRqfq6XXbuKIwYrK9L5jlnNixv9uUTkd4rDpmjRqh1gs2ub4PMc+ZQzL74AlXHVNoZNyNNq9P/nnQeEArCG9T0+XxelNTWY/b07zXmPf178BTm69sxbjQwOANfrF6N2ePHwOvz62kuHI30vinIzEhHelJ8s7KeM2cONFc10twVyDxzGGDR12vW+WcBIQ3pKb0waehAmC++DkNGjERHm33zTdifu0nfvjEZap1gs2HSGYOQHh+tymXSiKF6Yk+9qg9idO8EzBo9TA2nJ8Thy+JyzBozUpWhOfMqBL9chbTevZA5dLC+vWNGIi3WDricanjmiHSkx8dg4tA0tby0pHhMHNAHadE2aFoIkwYNhOb3IS0mBhP7JSMtNhoTBw1QZZ4RH6vyuLhfir7eQ1LV5/UpSerzxmFDkF/jxPSMM5pt6+D4OD19xhnQ/H4MirLh8htuwNCzzkbI5cSYnbkw2WNUmlRL42/cGwf2Q2q0XV/GkHSM69dX/X1d6kDsKyvH9YNSYY6NVXmmWq2I++4VsCQmIuiswchNm1XaG4akN/sUI+PicMvkyXA4atTwtEGDEKqrhSeol/EQezTOS06GddTZaviqlL44N1HfxiuSeyNY54YlIV7V7ZSrJuvje8u+BUy7fho0AOU532BwdDQ2VVbgvF69G+pka108fjwmlJQhYDbhygED4fH5cMv105A2KBUlqz7B6KRe6GO24JDXo/bp8ddei6gkfV3HDh8O07YdOCs+AWMvnojxY8/H4ewcNS2zb39sq6rAjGnXI2PUKOTl5WH6Nddic34hbLHRKs2k5BT4Lpmk/t6/chVKvR5Mn3a92r6DX+dgQGwsSt1uXHTNtSrNuF69YbJYoAWDmDTtevR0GXEJ6vP85L44UuPE2GS9jozrl4re9hgEAwEMhFnt+mJ0QjKiRze2r6eTSdOkZT4xp9OJpKQk1NTUIDExUTXqqHfpjb5slQRNEozIZ8CvHxy0JuOFpkGLSwJcFXoaizWcLqSnjU8BnGWAOZw+GNAbw4APsEY1HHDUuFAgnLcJkJ05GFCVFT43YIsBrE1iQVlXWV4wqI+XfPw+wO0CpDGSvCWNLG/QSKBoZ+O6HS2czpRxAUzRemNHbUerr4W2J1svfyHfyZBR0Aq26OPkn4wLBmAelQlTjL7jaW4XQtu+0OtOKATzmEthik1omb/bidCWzxrTnXeFqnuhTZ/o9UO+36iYxuU3Jcv1uPVPqa8qbTRgtR17Y+RgXlOhp5H09lg9reThKAPie6n6a54sBwsNoY/m6esldTPoB2Li9HpbUQLE9YL5pnthStYPjJqjDKElf9fraCgI0xUzoH2cpQc/wSBMk2fqw+GDLbz1MM/8GUy9+zeWRWUpQvP/oqexRanlanVOfX+KilL5WG77L5j66D8aOpJWUYLgm3/St0cOeIEgEG5kodZRhhP14doatc6atDPlJUBKf2gBqSMakKAfIFFfB8ucnyLw1gtAbBxMUXqwIUK1TmiFB4C+AwCZT9qXhCSY7PrBUfN6oDmroQVCKngJyfca0NssTYL36Bhobje0oP635GG//yG4//yMmu4/dAiWIcNgDh+sT7jdHg+0WheSnvsbrOlDESg8gKqf/xSm8LYHHVUIhTSYomMRctepr8qc3BvmaH1dRaCmGr59+2AbNhyWpCSEPB6EXC6kvvwPRA0dCt+B/Th0zz0wJyQ0m0+VRThtv9/+FocfewLmeD3YDlTJcmWTNQRl+TYbRrz5mmpz99z+Q5VmyNNPYu9/P4T6wyWwp6fBZDJhzLw3pGpjy213qjTnzXtDzbPptjsRCoZQW1iI6NSBmLBwPuKb/MA2qja/ABu+fztsiXr5+J0uTHznLbWMdXN+AJPNikCdGwG5pGKz4bKseUgIL8eZX4DPZt2m/r5iwTy1batm3YYoOb7JocTpxJQF85AU/sFevS8fH8y8DfbwsrxOF657d55a1tIZt6pxNy56Ww0vnP59WGw2BP1+3LL4HbX7Z02fo9Yh5Pdj9uL5SBnR/ERAT1KxNx/zbp4De2IifB4PSnbtQd+zRiI2Obkhjc9dj9qKCgQ0ICouDiG/D3cvXYB+4XJrEZN0IF6eIyIiIjKAQRMRERGRAQyaiIiIiAxg0ERERERkAIMmIiIiIgMYNBEREREZwKCJiIiIyAAGTUREREQGMGgiIiIiaqvXqEQ6DZdeOJVIj+D6RP2f9A4uyaQXY5P0ph3uETzyrggZEzQBta5wr+HS67ZJn1flaQNc0su4qXEZqkdw6T3c1jhe8lXd0+rd+qseyaVHcMkn4AEskr5Jb94yXvUuHtR7UJZ8pEdxdx3gC+ppI72aO11AbZ3ei3mkZ/Kmwss1OZ0wybzU9j2CS/lLvRDyvThd4XHmxl7npTdv+Q78WmOP4HXuhnzUNOlK9lg9gh+VTvUILuMiPYJ7As3rT4RM93v1T1Xng4A3cOx6IqQne7f0Zh15HYrUNSvg8wD1HunaWi3HLHVOegSXtFK31Xr4Adk26Rm83guYPSqdyaL3ZK05XQhJHmq/M8HkqoUm6cIahn2BhnVX81vDPYRLHi7JQ9KEe8oPBKD5ZPtCgF+fz+JywRSlvx6kI8m6BT3e8L4rPfpLmdfqE73SY3qTYY9P1QfpfRvyChl3vd4WSI/gpnCagB+W2loEZF71ShZf47Lq6qDJ9sprlaTKSD61tfpyhPSU7vOrHsElT9XbeJMewSHNQMCvegSXcSaYYK+tg9vvV/MFAkGY3XUwRd6McCJSv/x+mFwuWJ1OBFwuuPx+oE7fjpA/oPcIrtUjFAior8pUWwuTpAmT15j4g0FY3W6YzWZoXq9aZ6fLhSinEz7JMxBoMZ8qi3Da6Lo61EoaaSNVZ/iB5j2Cm0wqPykuSSecMk8wAI/0gu7xqN6vJY0kqoukCc8jw0GfD+5QCMFgUI0PRY4trVDrcqFO3g5Qp69nKBhoXIYcE9T3EtR7BDeH1zm8HPnbHWyyXpqmhr3S1qhdRs/LdFT6o6fLsuqb5qOGw2+YgL5MhMdJ2UrZuWpdsJ3C9nYXrloXPMEgfO46tU96tRDq3PUINHkTQ8DnhScUVM2nv75eHbZlvujI9xH+NPBCk9PzGpVDhw4hLS2tY9aIiIiI6CSKiooweLD+LslOFTSFQiEcPnwYCQkJKlI+ngsvvBDZ2dmGFtyatJ0lb4luJXiUL8ro+246w3q3Z949pUxak7a1ZdJZtrE982aZtMQy6dgy6Szb2Jr0PaV9PZUy2blzJ84880x1RrXTXZ6TlTISzVksFsNfbGvSdqa8haRtj3VhmXz79WivvFu7Hq0pk86yje2dt2CZtMQy6Zgy6UzbyPb126cfNGhQhwdMgjeCExEREXV00HTfffe1S9rOlHdrdZb1Zpl07Hq0RmfZxvbOu73Wo7XpO1Pe7bUerU3fmfLuLOvRWfJurc6y3vd1ojJpU3JPExnj8Xi0xx57TH2SjmXSEsukJZZJSyyTllgmzbE8Ol+ZGLoRnIiIiKin4z1NRERERAYwaCIiIiIygEETERERkQEMmoiIiIgMYNBEREREZEC3D5r+9re/YejQoYiOjsaECROwcePGZtNLS0vxgx/8AAMGDEBcXBzOP/98LF682FDe69evV72YXnfddS2mHThwQL1yRqYXFxc3m1ZSUgKr1aqmS7rOVi5VVVWYO3eu6qI+JiYG6enpuP/++1FTU3PCPC+//HK1TfJP8h01ahReeumlhulvvPGGmnb22We3mHfhwoVqmqxTd6ord955Z0OZREVFISMjA08++SQC4ZeYrlmzRk1LTk6GxyMv7W0krxSIzNtd6kjE/Pnz1b5xrL5ZumqZ9NT2hG1JS2xLunF7onVjWVlZWlRUlPbaa69pO3bs0O6++26tV69e2pEjRxrSXHXVVdqFF16off3111p+fr721FNPaWazWdu0adNJ87/rrru0Bx54QIuPj9eKi4ubTdu/f7905aClpaVpv//975tNe+aZZ7T09HQ1XdJ1tnLZtm2bdvPNN2vLli3T9u3bp3366afaiBEjtOnTp58w38suu0zlVVJSospS+tKQbXznnXfU9Ndff12Li4vTevfura1bt67ZvFOmTFFlMmTIEK071ZU77rhDu+aaa1SZHDhwQHvppZc0k8nUUCc+//zzhnoSKaeIe++9t6GedJc6EnHllVdqv/rVr7Tk5GStvr6+2bSuWiY9sT1hW9IS25Lu3Z5066Bp/Pjx2n333dcwHAwGtdTUVNXIRMiO99ZbbzWbT3bEV1999YR5u1wu1bjt3r1bmzVrlvb0008fs5F75JFH1Jff1MiRI7VHH330tAVNRsrlaO+++66q9H6//4QNnTT6Tcm2z549u6GhS0pK0n72s59pP/7xjxvSFBUVaXa7XVX609XQtVddkYbuxhtvbDZOGsyJEyc226GlnkyePLkhjdvtVmUVqSfdpY6IgoICLSYmRquurtYmTJigvf32282md+Uy6WntCduSltiWdO/2pNtenvP5fPjmm28wefLkhnHycj8ZltPgEZmZmViwYIE6PRgKhZCVlaVO4cnp4RN59913cdZZZ6nTibfddhtee+01KfUW6W644QY4HA589dVXalg+Zfj6669HZy6Xo8lpUnmZolwGaA051SrLbOpHP/qRKj+3291wqv2aa65B//790R3ripEykVP1X375JQoLC9WwnKqXU9ly2r671ZHXX39dXYJKSkpS+86//vWvY6brimXSk9oTtiUtsS3p/u1Jtw2aKioqEAwGW+w8MizXkyNkh/P7/UhJSYHdbse9996LJUuWqOvFJyJfjHxBQnZS+YLXrl3bIp3NZmtoBIV8yrCM78zlcvQ8Tz31FO655x7Dy5FlzJs3D1u3bsUVV1zRbNrYsWMxbNgwLFq0SB0YpKGTxq+71pUI2dbVq1dj1apVLcqkX79+uPbaa1VZROrJ6SqT9qwjcoCQbYzsO7Nnz1YH/v3797dI2xXLpCe1J2xLWmJb0v3bk24bNBn16KOPorq6WlXAnJwc/OIXv8DMmTOxbdu2486zZ88edRPbnDlz1LBEwrNmzTpuhCtfjNycKBVEPk9nBW4tp9Oponi5EfPxxx8/aXq5WTM+Pl79Arr77rvx85//HD/5yU9apJMykF8IcmCoq6vD1KlT0R3rilixYoUqE7kBUnZaqSvHKkspE9mhCwoK1C+wW2+9Fd2tjnzyySfNvu8+ffrgqquuaggCunqZsD05PrYljdiWdN32pHXnR7sQKTy5m/7IkSPNxsuwPLEg8vPz8eKLL2L79u0YPXq0GnfuueeqU3hyp/8rr7xyzLylMZMnFlJTU5tF/vKLQfKT04RNjRkzRp16l0ZRnvY455xzsGXLFnTWcolwuVzqV29CQoL6FWTk16xUwocfflg1dAMHDlSnYY+X7sEHH1Q7hZw2be2p+q5SV8R3v/tdvPzyy+qJF6kzx9tWaQTll9Vdd92lLrfIr9DuVkdk35FLElI/mv5alLMITzzxRIv60pXKpKe1J2xLWmJb0v3bk257pkkq1QUXXIBPP/20WWHK8KRJk9Rw5Dr40QUrX7CkPRZp3N566y0899xzqqGK/MvNzVWVWB59PF6EK48+nu5fhUbKJRLtX3311Sr9smXL1C8bI6SBl1PMgwYNOm4jJ3r37q3uz5Bfh12hTE6lrkTII8VSJvIY7YkadJl2++23n/Z60l51pLKyEkuXLlX3bzTddzZv3qzuy/n444+7dJn0tPaEbUlLbEt6QHuidWPymKM8SfHGG29oO3fu1O655x71mGNpaama7vP5tIyMDO2SSy5Rj37Ko47PPvuseozzgw8+OGaeS5YsUXf0y536R3vwwQe1cePGNXvaZfPmzWpYngAoLy9veBJAxp/OLgdOVC41NTXqKYQxY8aoMpFHXCP/AoFAq554aSryxEvTpxcqKioahv/85z+f1seE27quHO+Jl2M92eFwONSw1+tV9SQUCjXUt9P1eH1b1xH5fgcOHNiwbU3NnDlTmzFjRpcuk57YnrAtaYltSfduT7p10CReeOEF1Q+DNEzy2OOGDRuaTc/Ly1P9Q/Tr10+LjY3VvvOd77R4FLSpadOmaVOnTj3mNNkBpPBzc3NbNHJHO51B08nKJVLJjvXvROvb2obuaKezoWuPunIqDd3RTmdD19Z1RBrEn/70p8ectmDBArUcacy6apn01PaEbUlLbEu6b3tikv9O7RwVERERUc/Rbe9pIiIiImpLDJqIiIiIDGDQRERERGQAgyYiIiIiAxg0ERERERnAoImIiIjIAAZNRERERAYwaCIiIiIygEETERERkQEMmoiIiIgMYNBEREREhJP7f8qDKdHxyAyGAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stdout", + "output_type": "stream", + "text": [ + "Runtime Disaggregation\n", + "Lachesis clustering time: 0.02721381187438965 seconds\n", + "--------------------------------\n", + "TA-DBSCAN clustering time: 0.009821414947509766 seconds\n", + "TA-DBSCAN post-processing time: 0.0029697418212890625 seconds\n", + "--------------------------------\n", + "Grid-Based clustering time: 0.022524595260620117 seconds\n", + "--------------------------------\n", + "HDBSCAN clustering time: 0.3206779956817627 seconds\n", + "HDBSCAN post-processing time: 0.0 seconds\n" + ] } ], "source": [ - "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6, 6.5),\n", - " gridspec_kw={'height_ratios': [10, 1]})\n", - "\n", - "two_days = 1704162819 + 3600*48\n", - "traj_subset = traj[traj['timestamp'] <= two_days]\n", - "stops_subset = stops[stops['end_timestamp'] <= two_days]\n", - "traj_clean = clip_spatial_outliers(traj_subset, latitude='latitude', longitude='longitude')\n", - "\n", - "plot_pings(traj_clean, ax=ax_map, color='black', s=1.5, alpha=0.3, \n", - " base_geometry=city, latitude='latitude', longitude='longitude')\n", - "plot_stops(stops_subset, ax=ax_map, cmap='Reds', base_geometry=city,\n", - " latitude='latitude', longitude='longitude', radius=stops_subset[\"diameter\"]/2)\n", - "plot_time_barcode(traj_subset['timestamp'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_subset, ax=ax_barcode, cmap='Reds', set_xlim=False, timestamp='timestamp')\n", - "plt.tight_layout()\n", - "plt.show()" + "print(\"Runtime Disaggregation\")\n", + "print(f\"Lachesis clustering time: {execution_time_lachesis} seconds\")\n", + "print(\"--------------------------------\")\n", + "print(f\"TA-DBSCAN clustering time: {clustering_time_tadbscan} seconds\")\n", + "print(f\"TA-DBSCAN post-processing time: {post_time_tadbscan} seconds\")\n", + "print(\"--------------------------------\")\n", + "print(f\"Grid-Based clustering time: {execution_time_grid} seconds\")\n", + "print(\"--------------------------------\")\n", + "print(f\"HDBSCAN clustering time: {clustering_time_hdbscan} seconds\")\n", + "print(f\"HDBSCAN post-processing time: {post_time_hdbscan} seconds\")" ] }, { "cell_type": "markdown", - "id": "235e25d5", + "id": "5c9ee070", "metadata": {}, "source": [ - "## Runtime Scalability\n", - "\n", - "We measure how runtime scales with dataset size by running each algorithm on progressively larger time windows (6-hour increments)." + "## Pings vs Runtime" ] }, { "cell_type": "code", - "execution_count": 4, - "id": "947ce6c3", + "execution_count": 5, + "id": "62ed6a42", "metadata": { "execution": { - "iopub.execute_input": "2025-10-17T05:59:59.280183Z", - "iopub.status.busy": "2025-10-17T05:59:59.279974Z", - "iopub.status.idle": "2025-10-17T06:09:24.164218Z", - "shell.execute_reply": "2025-10-17T06:09:24.162167Z" + "iopub.execute_input": "2025-11-24T18:33:23.492088Z", + "iopub.status.busy": "2025-11-24T18:33:23.492088Z", + "iopub.status.idle": "2025-11-24T18:33:23.520075Z", + "shell.execute_reply": "2025-11-24T18:33:23.520075Z" } }, "outputs": [], "source": [ - "runtime_data = []\n", - "for current_end in pd.date_range(start=traj['datetime'].min() + pd.Timedelta(hours=6),\n", - " end=traj['datetime'].max(),\n", - " freq='6h'):\n", - " window = traj[traj['datetime'] <= current_end]\n", - " n_pings = len(window)\n", - " \n", - " for name, func in algorithms:\n", - " t0 = time.time()\n", - " func(window)\n", - " runtime_data.append({'Algorithm': name, 'n_pings': n_pings, 'runtime': time.time() - t0})\n", + "traj = loader.sample_from_file(filepath_root, frac_users=0.1, format='parquet', traj_cols=tc, seed=10)\n", "\n", - "runtime_df = pd.DataFrame(runtime_data)" - ] - }, - { - "cell_type": "markdown", - "id": "07203969", - "metadata": {}, - "source": [ - "## Performance Comparison" + "# H3 cells for grid_based stop detection method\n", + "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, traj_cols=tc, data_crs='EPSG:3857')\n", + "pings_per_user = traj['gc_identifier'].value_counts()" ] }, { "cell_type": "code", - "execution_count": 5, - "id": "570b6103", + "execution_count": 6, + "id": "baafc0b8", "metadata": { "execution": { - "iopub.execute_input": "2025-10-17T06:09:24.247662Z", - "iopub.status.busy": "2025-10-17T06:09:24.247376Z", - "iopub.status.idle": "2025-10-17T06:09:24.624693Z", - "shell.execute_reply": "2025-10-17T06:09:24.623659Z" + "iopub.execute_input": "2025-11-24T18:33:23.520075Z", + "iopub.status.busy": "2025-11-24T18:33:23.520075Z", + "iopub.status.idle": "2025-11-24T18:33:24.093854Z", + "shell.execute_reply": "2025-11-24T18:33:24.093854Z" } }, "outputs": [ { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAAHqCAYAAAB/bWzAAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Qd4U9X7B/A3nXS3QAd7I7IRxA0oCKiAGzco+OMvTlwsF6CyRAT3QnCggltQEUQ2yBJFQZS9aaF07yb5P9/T3jRJ0zZtk+Ym/X6eJ/bm3pubk3tLPXnve95jMJvNZiEiIiIiIiIiIiIiXfDzdAOIiIiIiIiIiIiIqASDtkREREREREREREQ6wqAtERERERERERERkY4waEtERERERERERESkIwzaEhEREREREREREekIg7ZEREREREREREREOsKgLREREREREREREZGOMGhLREREREREREREpCMM2hIRERERERERERHpCIO2RFTrrV69WgwGg/qpufvuu6V58+birRYsWKA+06FDh0RPjh49KnXq1JENGza45fiTJk1Sn9sariOuJ5WvoKBAmjRpIm+++aanm0JEREQ6gD4V+lbk/fT63YCIysegLRFV265du+TOO++URo0aSXBwsDRs2FDuuOMOtd7X9enTR3WAtEdISIh07txZ5syZIyaTye3vP3XqVPn222/FW0yZMkUuuOACueSSSyzrEFC1PofWj2XLltVY26zfNyAgQOrWrSvdu3eXRx55RHbv3l3l42ZnZ6svPNY3BTxp48aNqj2pqak26wMDA+Wxxx6TF198UXJzcz3WPiIiotoYTLPug6BPjf7R8ePH3f7+P/74IwOzDvr1fn5+EhkZKeecc47cddddsmLFimodGzfFca1dLT8/X+bOnSvdunVT7Y2OjpYOHTrIqFGjZM+ePS5/PyKqWQE1/H5E5GO+/vprue2221SAa+TIkdKiRQt1B3fevHny5Zdfyueffy7XX3+9+LLGjRvLtGnT1PKZM2fk008/lUcffVROnz6tAmDuDtredNNNct1119msR+fy1ltvVUF0vcD5+PDDD9XDHtr5/vvvl1rfpUsXqUlXXnmlDBs2TMxms6Slpcmff/6p2ouO9owZM1RQsypB28mTJ1u+DOghaIv24MsgOvbW7rnnHhk/frz6HR4xYoTH2khERFTb4MY2+tG4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BGzoT06ZMkX13S6++GL566+/VDkCbZRbedBPRZkI9P9QRgt9TPT9cA4weS0C/mWVrcBIr927d6t6tt99953NNpw33LxHfWB8LmR9Y9JbfF70GxEsRv8YGc/onxKRG5mJiKpp586d5ttuu83coEEDc2BgoDkhIUE9/+uvv8p8zYoVK3BL2WwwGMxHjx51uM/+/fvNw4YNU8fDcRs1amQeNGiQ+csvv7TsM3/+fHWcrVu3lnp9SkqK+Z577jHXr1/fHB4ebh4wYIB5z5495mbNmpmHDx9u2W/VqlXqGPipwXbsV5HevXubO3To4HDb6tWr1XGfe+459Tw7O9s8cuRIc1RUlDkiIsI8dOhQc1JSks0+gGWsO336tM3xtM968OBByzp8nl69eplDQkLUNu1zOdoXn+eaa64p1U7s98ADD9isw+uw/qWXXqr0NSlLYmKiOSAgwPzxxx/brEebw8LCyn0tzsWNN95oDg0NNcfExJj/7//+z/z333+rNuKz2p87a/bXuyx4nfbw8/MzR0dHm7t162Z+5JFHzLt27Sq1v8lkMk+dOlUdPzg4WO27dOlSh787GzduNHfv3t0cFBRkc72PHTtmvv7669V74ffi5ptvNp84ccJmn7y8PPOTTz5p7tKli/q9wbnC8ptvvlmqTTt27DDfcMMN5nr16qk2oR34PVu5cqXNfs8//7y6dvic1r8nqampqo3vv/9+heeLiIiIqq+8vqzRaDS3atVKPQoLC9W6Tz75xNyyZUv1/+uuXbuaf/7551J9j7L6cWDf78RxH3roIXNsbKzql1v3o5zto5bVl3PUT87PzzfPmDFDrUdfBf069JEmT55sTktLc+qcPfXUU6odrVu3LrXt999/V99DmjZtqo4fFxen+qrbtm2r8Lhor3V/EN8f2rRpY77zzjvNy5cvd/ia77//3ty5c2dznTp1zM2bN1ef7YMPPijVDz916pTqh6Mvh214L8jNzTU//vjj6nsU+vOXXHKJedOmTWq7tk95fevp06er/fB69LNxPq+44opSfXP77wa4Ztaf1fph329+99131TVC+9D+Tp06mceOHav6rETkXgb8x51BYSIiIg2yQpDNinrHpD/IyEDmL7JJWLuMiIiIiIjIcxi0JSKiGoO6WygRgAm4MCSO9AMlGjAcDpOdYLIzIiIiIiIi8hwGbYmIiIiIiIiIiIh0xM/TDSAiIiIiIiIiIiKiEgzaEhEREREREREREekIg7ZEREREREREREREOsKgLREREREREREREZGOBHi6AVTCZDLJiRMnJCIiQgwGg6ebQ0REROQVMK9uRkaGNGzYUPz8mJNQU9h3JSIiInJf35VBWx1Bp7dJkyaebgYRERGRVzp69Kg0btzY082oNdh3JSIiInJf35VBWx1BlgIcPnxYoqOjPd0cn84KOX36tMTGxjIbx414nmsOz3XN4HmuGTzPNceXznV6eroKHmp9KaoZ2vnGF47IyEif/N3yVbxG+sbro2+8PvrG66NvvD6V67syaKsj2rAydHqtO77k+j8Subm56hzX5j8S7sbzXHN4rmsGz3PN4HmuOb54rjlEXx99V1/83fI1vEb6xuujb7w++sbro2+8PpXru/IMEREREREREREREekIg7ZEREREREREREREOsKgLREREREREREREZGOsKYtERERERG5jdFolIKCAk83g+xqCuKaoK6gu2oKBgYGir+/v1uOTUREVBswaEtERERERC5nNpvl5MmTkpqa6ummkINrg8BtRkaGWyfwi46OloSEBE4SSEREVAUM2hIRERERkcslJiZKWlqaxMXFSWhoKAN3OgvaFhYWSkBAgFuuC46fnZ0tSUlJ6nmDBg1c/h5ERES+jkFbIiIiIiJyKWRxIsM2Pj5e6tWr5+nmUA0HbSEkJET9ROAWgXuWSiAiIqocTkRGREREREQur2MLyLCl2ku7/qxpTEREVHkM2hIRERERkVuwJELtxutPRERUdQzaEhEREREREREREekIg7ZEREREREQ1oE+fPjJmzBi3HX/BggUSHR3ttuMTERFRzeFEZEREREREpEtGk1m2HDwrSRm5EhdRR3q2qCv+fu4dcn/33XerSdS+/fZb8Ta33HKLXH311Z5uBhEReZDZaJTsbdul8PRpCYiNldAe3cXAySC9EoO2RERERESkO8v+PimTl+yWk2m5lnUNourIc4Pby8CODTzaNr0KCQlRDyIiqp3Sly+XxKnTpPDUKcu6gIQEiZ84QSL79/do26jyWB6BiIiIiIh0F7Ad/cnvNgFbOJWWq9ZjuyfMnj1bOnXqJGFhYdKkSRO5//77JTMz02afDRs2qDIIoaGhEhMTIwMGDJCUlBTLdpPJJGPHjpW6detKQkKCTJo0yeb1yPK99957JTY2ViIjI+WKK66QP//807Idy5dffrlERESo7d27d5dt27Y5LI9Q3r5EROR7Advjj4yxCdhCYWKiWo/t5F0YtCUiIiIiIl2VRECGrdnBNm0dtmO/mubn5yevvvqq7Nq1Sz788EP59ddfVQBW88cff0jfvn2lffv2smnTJlm/fr0MHjxYjEajZR+8DkHfzZs3y8yZM2XKlCmyYsUKy/abb75ZkpKS5KeffpLt27fLeeedp4559uxZtf2OO+6Qxo0by9atW9X28ePHS2BgoMP2VmZfIiLy7pIIyLAVs4P/Nxavw3bsR96D5RGIiIiIiMjtBr+2Xk5n5FW4X16hUVKyC8rcjq+eyMDt8cIKCQ6ouEZfbESwLHnoUnEF60nEmjdvLi+88ILcd9998uabb6p1CML26NHD8hw6dOhgc4zOnTvLc889p5bbtGkjr7/+uqxcuVKuvPJKFeTdsmWLCtoGBwerfWbNmqXq63755ZcyatQoOXLkiDz55JPSrl07yzHKUta+Zkdf6omIyGupGrZ2GbY2zGa1HfuFXdCzJptG1cCgrQ51nbxcJDjM083wWX5ilnNjzPJPikFM4t6JLGoznueaw3NdM3ieawbPs++c60PTr3H5Mcm7IWB7Kt223EF1FAV2yw7uusMvv/wi06ZNkz179kh6eroUFhZKbm6uZGdnq3IIyLRFpmx5ELS11qBBAxWk1coZoNxCvXr1bPbJycmR/fv3q+XHHntMlU/4+OOPpV+/fur9WrVq5fC9KrMvERF5L0w65sr9SB9YHoGIiIiIiNwOGa8JkXUqfMSEOjd8H/s5czy8ryscOnRIBg0apIKuX331lSo38MYbb6ht+fn56qczk4DZlycwGAyqzi0gYIsgLoK/1o9///1XZcwCauCiPMM111yjyjOgFMM333zj8L0qsy8REXmvgNhYl+5H+sBMWyIiIiIicjtnSxSgVu2lM35Vk445GsSPvPCEqDqyftwV4u9Xcxn5CNIiuPryyy+r2rawePFim30Q0EWpg8mTJ1fpPVC/9tSpUxIQEKDKL5Slbdu26vHoo4/KbbfdJvPnz5frr7/e6X2vu+66KrWPiIj0KbRHdwlISFCTjjmsa2swSEB8vNqPvAczbYmIiIiISDcQiH1ucHu1bB+S1Z5juzsDtmlpaaWyXevXry8FBQXy2muvyYEDB1TJgbffftvmdRMmTFCTft1///2yc+dOVUbhrbfekjNnzjj1vihhcNFFF6mg6vLly1V278aNG+Wpp56Sbdu2qTIJDz74oKxevVoOHz4sGzZsUO937rnnljpWZfYlIiLvZvD3l/iJE8rYWPT/S2zHfuQ9GLQlIiIiIiJdGdixgbx153kqo9YanmM9trsTAp3dunWzeSBIO3v2bJkxY4Z07NhRFi5cqOrbWkNGK4KtqE3bs2dPFYD97rvvVOasM1Aq4ccff5RevXrJPffco4536623qqBrfHy8+Pv7S3JysgwbNkxtGzp0qFx11VUOM3srsy8REXm/yP79pdHcOeIXGWmzHhm2WI/t5F0MZk4dqhuYzCAqKkqajVnEicjciJPc1Aye55rDc10zeJ5rBs9zzfGlici0PhSyIyPtvqhQzZ93lBA4fvy4ZGRkSMuWLaVOHdvAa2WgVMKWg2clKSNX4iLqSM8WdWu0JIKvwldATKKGYDICxe6CSdoOHjwoLVq0qNbvQW2Df0OYnC4uLs5SioP0g9dH33h9RE6/+aacefU1tRx87rnS4ssvdJNhy+tTub4ra9oSEREREZEuIUB7Uat6nm4GERGR1zClpVmW/aOidBOwpcqrvWFtIiIiIiIiIiIiH2JMTfV0E8hFGLQlIiIiIiIiIiLyAYUM2voMBm2JiIiIiIiIiIh8ADNtfQeDtkRERERERERERD7AmMKgra9g0JaIiIiIiIiIiMgHMNPWdzBoS0RERERERERE5OXMhYViSk/3dDPIRRi0JSIiIiIiIiIi8nLGtDRPN4FciEFbIiIiIiIiIiIiL8fSCL6FQVsiIiIiIiIiIiIvx6Ctb2HQloiIiIiI9MlkFDm4TuSvL4t+4rmbGAyGch+TJk2y7NuuXTsJDg6WU6dOVXjcQ4cO2RwnIiJCOnToIA888IDs3bvXZt8FCxbY7BseHi7du3eXr7/+2ma/NWvWyBVXXCF169aV0NBQadOmjQwfPlzy8/Mt+5jNZnn33XflggsuUMeJjo6WHj16yJw5cyQ7O9vmeMeOHZOgoCDp2LFjmeemTp06cvjwYZv11113ndx9990VngMiIqoZDNr6FgZtiYiIiIhIf3Z/LzKno8iHg0S+Gln0E8+x3g1OnjxpeSCwGRkZabPuiSeeUPutX79ecnJy5KabbpIPP/zQ6eP/8ssv6jh//vmnTJ06Vf755x/p0qWLrFy50mY/6/fdsWOHDBgwQIYOHSr//vuv2r57924ZOHCgCsCuXbtW/vrrL3nttddU0NVoLAlq33XXXTJmzBi59tprZdWqVfLHH3/IM888I999950sX768VLAY75Geni6bN28uM3D77LPPVuqcEhFRzTKmpHi6CeRCAa48GBERERERUbUhMLt4GPJFbdennyxaP/QjkfZDXPqWCQkJluWoqCgVpLRep5k3b57cfvvt0rt3b3nkkUdk3LhxTh2/Xr16luO1bNlSBg8eLH379pWRI0fK/v37xd/fX22zfl/8fOGFF2TWrFmyc+dOOeecc1TAFetnzpxpOXarVq1UIFezePFiWbhwoXz77bcqaKtp3ry5DBkyRNKsJqpBRu78+fPlzTfflMaNG6vPh+xcew8++KDMnj1bnnzyyTIzcomIyLOYaetbmGlLRERERET6gRIIy8aVDtgqxeuWjXdrqYSyZGRkyBdffCF33nmnXHnllSr4uW7duiody8/PTwV9UXJg+/btDvdB5qyWzXveeeepnwjYIgsXWbZlQcAWAV7rgK0GQWEEpTXIwkW5hH79+qnP9fnnn0tWVlap111yySUyaNAgGT9+fJU+LxERuR+Dtr6FmbZEREREROR+7/QWyUyqeL/CPJGc5HJ2MIukHxd5qY1IQHDFxwuPE/m/NeIKCGiifixq0sKtt96qMlMvu+yyKh0PtXG1urc9e/ZUywgEowYtoAxDYGCgqk2LbFq4+eab5eeff1aZvgjgXnjhhSpjd9iwYaq0AqBWLoK2zkD78TmQ6YsMWmQBIzDtqFbttGnTpHPnzipQXdXPTERE7lPIoK1PYaYtERERERG5HwK2GScqfpQbsLWC/Zw5njOBYid98MEHKhtVg2UEOJGBC1dddZUKuOKhBXbLg9IEWvarBhOVof4sHqhpi/q39913nyxZskRtR3AV5QwweRhKJDRq1Ejtg/dDBq71cSuSmpqqJjmz/0wI5DrSvn17FRxmti0RkT4ZUxi09SXMtCUiIiIiIvdDxqszKsy0LRZSz/lMWxfABGC//fabbNmyxaaOLUoYIAP3f//7n7z//vsqOxaQIVsRTEYGLVq0sCmb0Lp1a8tzZLaiju2MGTNUHVwNgrWYbAyP559/Xtq2bStvv/22TJ48WS3v2bOnwvf/9NNPJTc316aGLQK+JpNJ/vvvP3Uce9rxUS+XiIj0heURfAuDtkRERERE5H7OlihArdo5HYsmHXNY19YgEtlQZMxfIn5Fk3fVBGSf9urVS9544w2b9ch6xTYEbRFIdRYCo6+++qoK2Hbr1q3cfZFdqwWDHYmJiZEGDRpYatFiojSUPPjuu+9K1bVFUBYlGMLCwlTm8OOPP16qFML999+vtk2fPr3UezVp0kRNSjZx4kRLyQYiItIHBm19C8sjEBERERGRfiAQO3BG8ZOSsgE2zwdOr9GAbUFBgXz88cdy2223qbqv1o97771XNm/eLLt27Sr3GMnJyXLq1Ck5cOCAfP/992riL2TtIuCLoKx1UBX74XHw4EFVzxY1bLXg6zvvvCOjR49W2bf79+9X74vMX/zUMnGHDh0qt9xyi2ovSids27ZNTXi2dOlS9b6YfAzlF37//XfVfvvPhNdhArTCwkKHn2XChAly4sQJ+eWXX1x6nomIqHqMKSmebgK5EIO2RERERESkL+2HiAz9SCSyge16ZNhiPbbXIARZEXS9/vrrS20799xz1aOsOrAaBEuRDdupUydVExav2blzp1x++eU2+6Wnp6v98MA+L7/8skyZMkWeeuoptR0TlmVmZqo6t6hjiwnJULYB5QqwrNXIRemD2bNnW9ajzMKkSZNU8HfAgAGyYMECVaNWmwzNGj5nUlKS/Pjjjw4/S926dVWgGKUViIhIH3DTz5iW5ulmkAsZzM5WqSe3QwctKipKmo1ZJBIc5unm+Cw/Mcu5MWb5J8UgplLZG+QqPM81h+e6ZvA81wyeZ98514emXyM13YfCkO/IyMgae9/arqzzjmH/x48fV5NztWzZUurUqVP1N0GphMMbRTITRcLjRZpdXKMZtr4KXwGRRRsQEGAzCZqrIaiLbGGUgKjW70Etg39DCJrHxcWpGsekL7w++labr48xPV3+61lSoxxCL7xQmi2YL3pRm69PVfqurGlLRERERET6hABti8s83QoiIiLdYz1b31N7w9pEREREREREREQ+gPVsfQ+DtkRERERERERERF6Mmba+h0FbIiIiIiIiIiIiL8agre9h0JaIiIiIyMdMmzZNzj//fImIiFCTfVx33XXy77//lpok6oEHHpB69epJeHi43HjjjZKYmOixNhMREVHVMWjrexi0JSIiIiK3Wbt2rQwePFgaNmyoZqn/9ttvLdsKCgpk3Lhx0qlTJwkLC1P7DBs2TE6cOFHuMSdNmqSOpT0w+649ZwKS1sfQHp9//rn4gjVr1qjP/9tvv8mKFSvUue7fv79kZWVZ9nn00UdlyZIl8sUXX6j9cd5vuOEGj7abiIiIqqaQNW19DoO2REREROQ2CBJ26dJF3njjjVLbsrOz5ffff5dnnnlG/fz6669VNuiQIUMqPG6HDh3k5MmT6vHff/+V2u5sQHL+/PmW4+CBjFRfsGzZMrn77rvVecL5X7BggRw5ckS2b9+utqelpcm8efNk9uzZcsUVV0j37t3Vudi4caMK9BIREZF3Yaat73Fb0BadREed3tWrV6sshtTUVMsyHn5+fipLolu3bjJ27FjVaa4oo+Kyyy5TnXBr33zzjVx44YVqO4aDoaM6ZswYm33y8/Nl5syZqgMbGhoq9evXl0suuUR1VJGFYG3Tpk3i7+8v11xzTanPcujQIdUWDDnLyMiw2da1a1fVZiIiIqLa7KqrrpIXXnhBrr/++lLb0F9DFujQoUPlnHPOUX24119/XQUWEWAsT0BAgCQkJKhHfHy8zbbKBCSjo6Mtx8GjTp06lm1//vmnXH755apPGRkZqY6zbds28UY4J1C3bl31E+cY/d5+/fpZ9mnXrp00bdpU9X+JiIjIuxhTi/5fT74jQHQAGRXoCKenp6ssCwRU0dFGUBfD5TQIwP7yyy9q+ezZszJr1iwZNGiQHDt2THX6V65cKbfccou8+OKLKkMDAdXdu3erLwPWAdsBAwaoTvjzzz+vgrV4b3TgcTwEjRFw1aAdDz30kPqJDA0M27OHgC1eO3nyZLefKyIiIiJfhuAi+nAIppZn7969ql+GIGuPHj1stlUUkERwWIMSAvfee6+0bNlS7rvvPrnnnnvU+8Mdd9yh+oZvvfWWuon/xx9/SGBgoHgbk8mkkhjQ7+3YsaNad+rUKQkKCip1nhEAxzZH8vLy1EODvrt2fDys389sNqtl/NSWSV+sr5E73wMP+98RKp/2b4jnTJ94ffStNl8fY8rZ0it1di5q8/Wx5uzn10XQFpmqWpZD27Zt5dprr1Ud5NGjR8v69etLZVQAfk6ZMkVlTWBIHCZawBA4dEaffPJJy2twPOuM3zlz5qjaasiSwHto0FG/+eabVVBXk5mZKYsWLVL7ovOKYWUTJ04s1X4EdZHJgU4/PgsRERERVR7q0KLG7W233aZuqpflggsuUP0yZOdidNazzz5ruZGO1zkbkERfEpm4GHm1fPlyuf/++1X/7+GHH1bbke2LfiUCvtCmTRvxRuij/v333zb96qpObuYoSeH06dPq2ll/EcG1wJeywsJC9agtMArwyiuvlKSkpDJvPHz00Ufy+OOPq/PmKbg2RqNRLWs3KVz1+azh2uP3ITk52StveHgKzhluYOE6YUQq6Quvj77V5uuTd+ZMqXX5Bfnqb7Ze1ObrY81+tL6ug7b2QkJCVKYDapHhl8tRIBR3+RGwRWcBHXYtkPvpp5+qTqmWRWBv4cKFKuvCOmCrQUfCujOxePFi1UnH8e+8806VoTBhwoRSHRt8sUA2Lzr+GNLnrLKyFYiIiIhqG2TGokwCOvHIbK2o5IKmc+fOcu6550qzZs1UmawHH3zQ6fdELV0N+oaov/vSSy9ZgraPPfaYysL9+OOPVf8RN/hbtWol3gTnY+nSpSppoXHjxpb16DcjWQEly6yDb5isTUuSsId+MM6Jdd+1SZMmEhsbaxNkxxcyBOsQAEfSBR5VZTQZ5fek3+V0zmmJDYmV8+LOE38/f3E3BPcRpP7xxx8to/pat26tsq+HDx+uAv2OoHwbRudhAryygqHal9Tyzov1F1lkeSOrHJPpoU3BwcHiKpUNpKIt4Ox1xT74LDgf1qVHqHz4N4TfH/zbqs1BDb3i9dG32nx9MjKLJhv1i4gQU3FQMCgwSFfJhbX5+lhz9v+Jbg3aooOI2XqtaXd0K6JlNKBurPYL9tdff1mOh4krUF8MmbBaJxEZr+vWrVMlFdBxx9A3zJKLzpXWucFQuj59+jjVBpREQLAWBg4cqO4G4O6y/evxCzd9+nQ1MzICzc525svKViAiIiKqjQHbw4cPy6+//lpulq0jWtDxwIEDVQ5Iahm8KJ+Fm+roO2J+gttvv11++OEH+emnn+S5556Tzz//3GF9Xr1B8Bt9YwSyUXKsRYsWNttRnxcBO5QXQzBQK1mG7OKLLrrI4TFxThwFDPGly/6Llxaw1OajqIpfDv8i07dMl8TsRMu6+NB4Gd9zvPRrVlL6wtXwe4TRe/jdmTp1qvpugc+N7yLvvvuuCn47miwPv8fYr0GDBuUe3/rclAcJKvgOguOitBtKd+C7EH5HXfH74Ww7qnNdrecvqc1fzquC503feH30rTZeHzWCongiMv+YGEvQVorPhZ7Uxutjz9nP7tYzhIkbUPvL+vH+++879VqttpJ1ZwAZr9pxUKsM5ROQ8aBNCBEWFqY61fv27ZOnn35adWow9Khnz54qyGt93Iqg07plyxaVRavdJUa9XARyHUGd3EsvvdQmY6MiyFZAIFh7HD161OnXEhEREflSwBY31jF3ATLyKgsZnaAFZK0DkpqKApKAPmZMTIxNYBKltnBTHuUTbrjhBhVI85aSCJ988okahYZEB2SO4pGTk6O2I3N05MiRKnN21apVqm+NoCDOj3XNX09BwPax1Y/ZBGwhKTtJrcd2d0GZDPT98R0Dv5vI5EYpNZRww3cNJGpo31OQFY4ALr6HYF4N60mXNSjlgXrKyM5FwB+lApyhlY9DNjPm8cD7Y/4Pzf79+9U6lP3A9x6Ui9Pm/9C8+eabqqwHMnqw30033WST7YQkEgT0MdIRkzR/+eWXNq9HpjH+DWA7vtshoYaIiPTHnJMj5uKR3P5OlK8h7+DWTFt0XjCMyBqGFznjn3/+UT+bN29uWYfaZNbHwzC2b7/9VtWpRadUg0xXPDCc7amnnlIdDWTkoiOK5T179lT4/gjOYliX9cRjCPiiE48SCOjo2kO2LTq61jV1y1NWtgIRERGRr0BAFTfUNQcPHlTB0bp166qMRASREIjCCC2MyNJqzmI7+n7Qt29fFezSSh888cQTKnCGkVUYio7+HmgBKeuAJI6DzF1knVoHJDEXAjJv8RwBLZS6QlYljg0IbqJPh2MiqIU+7NatWy1ZqXqnlZiwHyGGoPPdd9+tll955RWV6YHPhOxiJCEgyOdpKImADFuzlE62wDqDGGTGlhlyeZPLXV4qAQFVBOjxu4DvMo5YJ5UgGxvfAfB9BIFeLdtbs3nzZvW7iOAo5tlYtmyZytiuLMzhgSx07dpp/7auvvpqFSzGdwrUysW/C9ygQJAYQWeU+kB5j4svvlhN5IxRiZoZM2bIZ599Jm+//bYK7KKEBkYZYshq7969VUIJblTgBsCoUaPU8ZAQQ0RE+qNl2YJ/dOl4FXknXda0RScZQ4969eqlOg0V1VXSMgYcQdAXd7VRowwwxA2Tie3YsaNUXVtkemAondbpefnll1V5BWvobKFzg5q79pDRi47N+PHjK/mJiYiIiHwTAj3I0NNoNVFRFxQBr++//14979q1q83rkP2pBRyRUXjGanINBFAxGgoBNvQVUdYA6tevb9mnooAkMnHfeOMNlUWLG/NIDMDEsv/73/8sfUwcf9iwYSq4i2Ojn+ctpa2cGV2GYDXOAR414Zalt8iZnNKTpNjLN+ZLal7Jl09HgdtT2aekz+I+EuRfFNgvT/2Q+rJo0CKn2ogbDDh32pwZlmPUr2+ZbA1BTAQ8te8WSAzR2Adt586dq0ocjB07Vj1HAsnGjRtV8LYi+B3H7yESSfA7jGxbjNTTIDMWDw3KJqAcBv5N4QYHMssReMbrkG2Nmxza9x8cD58BNysQ0AVkE2OyunfeeUcFbRH4RyIMvhMBzglKRGifnYiI9Bq0Zaatr9BF0BaTjaEThNnTMDRr5syZqmP+9ddf2+yHDouWfYF9kT27e/duNcswoOOPMgi444xOCYYlvfrqqyoYi1lOAZOJYVgTMjbQsUFJA3Ri8IUCHRBk2GLYT0pKirorbp9Ri44/9nEUtAXc6e7QoUO1JlwgIiIi8hUIvJYXQHQmuGg/JBt1Za1hQiz7fmNFAUkE0vAoC7J8caOeXAcBW5Q3cJXyAruuhrJpKCeAuTKsJxLu0aNHhaMH7WsgI+NbC9oisNq+fXvLNiSX4KHdeMAEeMhARzAZNzzuuusuy+8/Mm3x/QffbU6ePKm+KyGZBccEfP/BdyIEY7Xfd7QFCS04Hr432SeoIIFFC+yi7doNEeu2ExGRvoO2ATExHm0LuY4uIou4a4thRqjFhE4FOg/olNhPFLFr1y5LYX90NnDnF3eAkQEBuCOMjrmWEYGaZOh0YIiTdrccWbS4o4xOEO4iYwgcjoVaVRg+1LFjR1WXFh0kRyUQELRFUHnnzp0OJ8nA3fMRI0aoTGEiIiIiIirJeHVGRZm2mujgaKczbZ2FjGt8L0GJAWv4jgKo7WqtrBIKzkIpNpQL0aCchwbfhbTScPgug6QVZN++8MILaj2+x+B7zaxZs9RztA3lPBB4BSSmoPQI6uzi+9Czzz6rgrwo86HVgUZZEkysZo3l24iIvE9hSoplmZm2vsNtQVsU3K8o26KizAtr6GDgUR4MvbMeflcWdERQwqCsMgaocVYWlECwbrOj9iMYjAcRERERERVxtkQBatoO+GqAysp1VNcWNW3jQ+Nl2Y3LXF7TFhPhIUMVc1igDnJ1g7JIDEFdW2u//fabZRmj8+znACkLSiWAVhpuw4YNqsatlsmLQKx9VjqOj2QUPFBLF5OboTYunuM7EbJy7eseW7ddK1/iqO1ERKQfLI/gm3SRaUtERERERAQIxI7vOV4eW/2YCtBaB27xHMb1HOfygK0GtY8vueQSVfoASSOdO3dW9ZGRoYoJjbt37+70sTCSD8dCNuy1114rP//8s1P1bAGl3lAaDmUZ9u7dK1OmTFGj+hBMBUwehrIgmHwM2cEYLYh9NciiRY1dzBOCEYg//vij2o6sXWThop4zRjciCQUl49LS0lQgGKMJUXMa5eBQzxYT8mGCZ5SxKysxh4iIPItBW9/k5+kGEBERERERWevXrJ/M7jNb4kLjbNYjwxbrsd1dUIINkxYjGxUTf2GyLwRwX3vtNVWSAPNiOOvCCy+U9957T01IhuOgTMHTTz/t1GsxwRlKw6F8AcoiYN6Mn376yTJ3BibOQzAWE4khcIvJ9s477zzL65FVi6DuFVdcoQK9b7/9tqrTjOMAJtVDW6ZNm6a2o+Yt6uO2aNFCbW/atKl89dVX8u2336q24/VTp06t5NkkIqKaYEyxCtqypq3PMJidrU9AbodJNFBHt9mYRSLB1RuKRWXzE7OcG2OWf1IMYirO1iDX43muOTzXNYPnuWbwPPvOuT40/Rqp6T4UMgUdzTlANXvekc15/PhxVYMVtWAxKVxVoVTC70m/y+ns0xIbGivnxZ3ntgzb2gRfATFxGQLAyNJ1F0w2ffDgQRUIrs7vQW2Df0OYrDsuLk5leZO+8ProW229PsefeFLSly5Vyy2+/UYOXldUOif0wgul2YL5ohe19fpUte/K8ghERERERKRLCNCen3C+p5tBRESkayyP4Jtqb1ibiIiIiIiIiIjIV4K2fn7iHxHh6eaQizBoS0RERERERERE5KWMKSnqp39UlArckm/glSQiIiIiIiIiIvLyTFuWRvAtDNoSERERERERERF5IXN+vpiystQyg7a+hUFbIiIiIiIiIiIiL2RMS7Ms+8fEeLQt5FoM2hIREREREREREXmhwuJ6tsBMW9/CoC0REREREREREZEX17MFBm19C4O2REREREREREREXohBW9/FoC0REREREREREZEXMqZYBW1jGLT1JQzaEhERERGRLpmNRsnavEXSlv6gfuK5u919991y3XXXlVq/evVqMRgMkpqaalnGw8/PT6KioqRbt24yduxYOXnypM3rJk2aZNkXD+x72WWXyZo1a2z2++abb+TCCy9U2yMiIqRDhw4yZswYm33y8/Nl5syZ0qVLFwkNDZX69evLJZdcIvPnz5eCggKbfTdt2iT+/v5yzTXXlPoshw4dkqCgIImPj5eMjAybbV27dlVtJiIi78BMW9/FoC0REREREelO+vLlsq9vPzkyfLiceOIJ9RPPsV4v/v33Xzlx4oRs3bpVxo0bJ7/88ot07NhR/vrrL5v9EIBFMBcPBFPbtGkjgwYNkrTiGb9Xrlwpt9xyi9x4442yZcsW2b59u7z44os2gVgEbAcMGCDTp0+XUaNGycaNG9W+DzzwgLz22muya9cum/ecN2+ePPTQQ7J27VrVRkcQsJ01a5Zbzg0REdV80DaAQVufEuDpBhAREREREVlDYPb4I2NEzGab9YWJiUXr586RyP79xdPi4uIkOjpaEhISpG3btnLttdeqjNvRo0fL+vXrLfsFBASofQA/p0yZorJj//vvPzn//PNlyZIlKmP2ySeftLwGx7PO+J0zZ44KwG7btk29h6Zly5Zy8803q6CuJjMzUxYtWqT2PXXqlCxYsEAmTpxYqv0PPvigzJ49WwV+8VmIiMj7MNPWdzHTloiIiIiIdAMlEBKnTisVsC3aWLQO22uiVEJlhYSEyH333ScbNmyQpKQkh/vk5eWpgC2Cveecc44lkItM2b///rvMYy9cuFD69etnE7DVBAYGSlhYmOX54sWLpV27dur4d955p3zwwQdidnA+b7vtNmndurUKIhMRkXcypqRYlv1jYjzaFnItZtoSEREREZHbHbzxJik8c6bC/Uz5+WKy+gJaitkshadOyX+XXiZ+QUEVHi+gfn1p8dWXlWrr0qVLJTw83Gad0ckgMYKlWt1YLXsV5RK042VnZ6uatciEjYyMVOtQxmDdunXSqVMnadasmapt279/f7njjjskODhY7bN3717p06ePU21AaQQEa2HgwIGqDANq6Nq/HjV2UW5h8ODB8uijj0qrVq2cOj4REek00zYqSsyFhR5tD7kOg7ZEREREROR2CNiivIGrILBrEve4/PLL5a233rJZt3nzZksgtDxaRisCohpkvH7//feWOrII2KKkwapVq6RHjx4qS/aHH36Q/fv3q3W//fabPP744zJ37lxVAxeTjjnKlC2rzi5q3WJiM600A+rlIpDrKOiLOrmXXnqpPPPMM/Lpp5869R5ERKS/oK1feLgYAgMZtPUhDNoSEREREZHbIePVGRVm2hbzi4lxOtO2shBERdkAa8eOHXPqtf/884/62bx5c8u6oKAgm+OhxMG3336r6tR+8sknlvXIdMXj3nvvlaeeekrVtUWA95577lHLe/bsqfD9EZwtLCyUhg0bWtYh4IuM3ddff12ioqJKvQbZthdddJFNTV0iIvKuoC1LI/geBm2JiIiIiMjtnC1RgFq1+/r2K8rKdZRdajBIQHy8tF75ixj8/UVPcnJy5N1335VevXpJbGxsufv6+/ur/cuCoC8ybLOystTz22+/XU0mtmPHjlJ1bQsKCtREZAjMfvTRR/Lyyy+r8grWMKnZZ599pmru2uvZs6fccMMNMn78+Ep+YiIi8iT8P9OYlqaWOQmZ72HQloiIiIiIdAOB2PiJE+T4I2NUgNYmcFtccgDb9RCwxWRjubm5quTB9u3bZebMmXLmzBn5+uuvbfZD5uupU6dsyiPs3r1bxo0bp9ZNmjRJ1bq9+uqrVU3b1NRUefXVV1Uw9sorr1T7jBkzRpVQ6Nu3rzz//POqpAFq427btk1mzJihMmxRRzclJUVGjhxZKqP2xhtvVPs4CtrCiy++KB06dFDlFIiIyDsY09Mt/59k0Nb3+Hm6AURERERERNYi+/eXRnPnqIxaa3iO9diuB6hVizIE3bt3VyUG+vXrJ3///be0b9/eZr9du3ZJgwYN1KNr166yePFiVTN32LBhanvv3r3lwIED6jkmMrvqqqtUkHf58uXqPQBZtCtWrJCxY8fKO++8oyYrO//881Vw9+GHH5aOHTuqoCza4KgEAoK2CPDu3LnT4WdB+YURI0aoIDQREXnhJGQM2voc3kYlIiIiIiLdQWA2om9fyd62XQpPn5aA2FgJ7dHd7Rm2CxYscLgek3hpk4FZL1cEWbR4VDTxGR4VQeAWJQzKKmOwZMmSMl+LEgham/ET5RTss2oRDMaDiIi8gzHFKmgbw6Ctr2HQloiIiIiIdAkB2rALenq6GURERLrETFvfxvIIREREREREREREXoZBW9/GoC0REREREREREZEXB20DGLT1OQzaEhEREREREREReRljSopl2T8mxqNtIddj0JaIiIiIiNzC2cm6yDfx+hMRuRfLI/g2Bm2JiIiIiMil/P391c/s7GxPN4U8SLv+gYGBnm4KEZFPYtDWtwV4ugFERERERORb/Pz8JDo6WpKSktTz0NBQMRgMnm4WWWXAFhYWSkBAgFuuC46PgC2uP34PtCA+ERG5FoO2vo1BWyIiIiIicrn4+HgVENQCt6QfCKqaTCYVXHdnMB0B24SEBLcdn4iotjOmFtW0NdSpI34hIZ5uDrkYg7Y69Mdz/VUHh9wDHVR8eYiLi1MdVXIPnueaw3NdM3ieawbPc83huSZ3QzCwQYMG6nesoKDA080hu3//ycnJUq9ePbf9+0dJBGbYEhG5V2Fxpi2zbH0Tg7ZEREREROQ2CNwxeKe/oC2CqnWQmcWbNkREXjtqwpiappYZtPVN/D80ERERERERERGRFzFlZYkUj2Txj2HQ1hcxaEtERERERERERORFOAmZ72PQloiIiIiIiIiIyIsYUxi09XUM2hIREREREREREXkRZtr6PgZtiYiIiIiIiIiIvIgxNcWyHBAT49G2kHswaEtERERERERERORFWB7B9zFoS0RERERERERE5EVYHsH3MWhLRERERERERETkrUFblkfwSQzaEhEREREREREReWlNW2ba+iYGbYmIiIiIiIiIiLwIyyP4PgZtiYiIiIiIiIiIvEihFrQNCBC/8HBPN4fcgEFbIiIiIiIiIiIiL2JMSbVk2RoMBk83h9yAQVsiIiIiIiIiIiIvLI/gHx3l6aaQmzBoS0RERERERERE5CVMeXlizslRy6xn67sYtCUiIiIiIiIiIvISnISsdmDQloiIiIiIiIiIyEsYU1IsywExMR5tC7kPg7ZERERERERERERegpm2tUOApxtApXWdvFwkOKzM7YemX1Oj7SEiIiIiIiIiIn1g0LZ2YKYtERERERERERGRVwZtWR7BVzFoS0RERERERERE5IU1bf1jmGnrqxi0JSIiIiIiIiIi8hIsj1A7MGhLRERERERERETkJRi0rR0YtCUiIiIiIiIiIvIShTblEVjT1lcxaEtEREREREREROQljKlpRQsGg/hHRnq6OeQmDNoSERERERERERF5WXkEv8hIMfj7e7o55CYM2hIREREREREREXlZ0NY/OsrTTSE3YtCWiIiIiIiIiIjIC5gLC8WUnq6WA6JZz9aXMWhLRERERERERETkBYxpxfVsVaZttEfbQu7FoC0REREREREREZEXlUYA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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stderr", + "output_type": "stream", + "text": [ + "\r", + " 0%| | 0/4 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 2, + "id": "4fea8a03", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:32:16.169725Z", + "iopub.status.busy": "2025-11-24T18:32:16.169725Z", + "iopub.status.idle": "2025-11-24T18:32:16.492839Z", + "shell.execute_reply": "2025-11-24T18:32:16.492839Z" } - ], + }, + "outputs": [], "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", + "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", + " gridspec_kw={'height_ratios':[10,1]})\n", + "\n", + "gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3')\n", + "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c')\n", + "\n", + "plot_hexagons(stops_gb, ax=ax_map, color='cluster', cmap='Greens', location_id='h3_cell', data_crs='EPSG:3857')\n", + "plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc)\n", + "ax_map.set_axis_off()\n", "\n", "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_gb, ax=ax_barcode, stop_color='green', set_xlim=False, timestamp='unix_ts')\n", - "plt.title(\"Grid-Based stops\")\n", - "plt.tight_layout()\n", + "plot_stops_barcode(stops_gb, ax=ax_barcode, cmap='Greens', set_xlim=False, timestamp='unix_ts')\n", + "\n", + "plt.tight_layout(pad=0.1)\n", "plt.show()" ] } @@ -106,7 +115,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.0" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/docs/source/hdbscan_demo.ipynb b/docs/source/hdbscan_demo.ipynb index 4437d09a..b7c6c4e4 100644 --- a/docs/source/hdbscan_demo.ipynb +++ b/docs/source/hdbscan_demo.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "92838936", + "id": "4f16fcbe", "metadata": {}, "source": [ "# HDBSCAN Stop Detection" @@ -10,7 +10,7 @@ }, { "cell_type": "markdown", - "id": "cb276fd9", + "id": "f69ed80f", "metadata": {}, "source": [ "The HDBSCAN algorithm constructs a hierarchy of non-overlapping clusters from different radius values and selects those that maximize stability." @@ -18,73 +18,79 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "19184dee", - "metadata": {}, + "execution_count": 1, + "id": "3561532d", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:32:30.624504Z", + "iopub.status.busy": "2025-11-24T18:32:30.624504Z", + "iopub.status.idle": "2025-11-24T18:32:33.741073Z", + "shell.execute_reply": "2025-11-24T18:32:33.740043Z" + } + }, "outputs": [], "source": [ "%matplotlib inline\n", + "import matplotlib\n", + "matplotlib.use('TkAgg')\n", + "import matplotlib.pyplot as plt\n", + "plt.ion()\n", "\n", "# Imports\n", "import nomad.io.base as loader\n", "import geopandas as gpd\n", "from shapely.geometry import box\n", - "import pandas as pd\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode\n", + "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode, plot_stops, plot_pings\n", "import nomad.stop_detection.hdbscan as HDBSCAN\n", - "import nomad.stop_detection.postprocessing as post\n", "\n", "# Load data\n", - "city = gpd.read_file(\"garden_city.geojson\").to_crs('EPSG:3857')\n", - "outer_box = box(*city.total_bounds).buffer(15, join_style='mitre')\n", + "import nomad.data as data_folder\n", + "from pathlib import Path\n", + "data_dir = Path(data_folder.__file__).parent\n", + "city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet')\n", + "outer_box = box(*city.total_bounds)\n", "\n", "filepath_root = 'gc_data_long/'\n", - "tc = {\n", - " \"user_id\": \"gc_identifier\",\n", - " \"timestamp\": \"unix_ts\",\n", - " \"x\": \"dev_x\",\n", - " \"y\": \"dev_y\",\n", - " \"ha\":\"ha\",\n", - " \"date\":\"date\"}\n", + "tc = {\"user_id\": \"gc_identifier\", \"x\": \"dev_x\", \"y\": \"dev_y\", \"timestamp\": \"unix_ts\"}\n", "\n", "users = ['admiring_brattain']\n", - "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','==', '2024-01-01'), traj_cols=tc)\n", + "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc)\n", "\n", - "user_data_hdb = traj.assign(cluster=HDBSCAN.hdbscan_labels(traj, time_thresh=240, min_pts=3, min_cluster_size=2, traj_cols=tc))\n", "stops_hdb = HDBSCAN.st_hdbscan(traj,\n", " time_thresh=720,\n", - " dist_thresh=15,\n", " min_pts=3,\n", " complete_output=True,\n", - " traj_cols=tc)\n", - "stops_hdb[\"cluster\"] = post.remove_overlaps(user_data_hdb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) " + " traj_cols=tc) " ] }, { "cell_type": "code", - "execution_count": 30, - "id": "fa70719e", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxoAAACuCAYAAACx83usAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAm8klEQVR4nO3deXgV9b3H8c8hZCMbEAgQE8ImBCKLhYKCNMQAslMBQ0B2awsUBaUqyhYoiiC3LsXYp/ey+RhZvKKyKJWYsJXlgkChorgQCCk7GghgzPa7f9BzyuGc5CRhJBDer+fJI+d3fjPzm5nvjPlk5syxGWOMAAAAAMBCVSp6AAAAAAAqH4IGAAAAAMsRNAAAAABYjqABAAAAwHIEDQAAAACWI2gAAAAAsBxBAwAAAIDlCBoAAAAALEfQAAAAAGA5ggZwi1q6dKlsNpv27Nnj9v0+ffqoQYMGTm0NGjSQzWaTzWZTlSpVFBISoubNm2vEiBH69NNP3c7H3t/+ExAQoObNm2vWrFm6fPmyU9/Lly9r3rx5at26tYKDgxUUFKTGjRsrISFBmzdvdpn3kSNHNGHCBDVt2lT+/v6qVq2aYmJiNG3aNP3rX/9yO54BAwbIZrNpwoQJbt/ftGmTY6w7duxweX/UqFEKDAx0O21ZJScna+nSpZbM61bTpUsXdenSxfH6ypUrSkpK0qZNm1z6JiUlyWaz6dy5czdvgDfoxIkTSkpK0v79+yt6KLcN+7HlrgYAoDyqVvQAAFirU6dOWrBggSTp0qVLOnz4sFasWKGHHnpIAwcO1PLly+Xt7e00zaBBgzR58mTHNJs3b9bs2bN14MABvf/++5KkwsJCde/eXQcPHtQzzzyj9u3bS5K++eYbrV27Vlu3blVsbKxjnuvWrVNiYqJq1aqlCRMm6N5775XNZtPBgwe1ePFirV+/Xvv27XMax5kzZ7Ru3TpJUkpKihYsWCA/P79i1/XZZ5/V1q1bb3CLFS85OVm1atXSqFGjfrZlVJTk5GSn11euXNGsWbMkySmA3K5OnDihWbNmqUGDBmrTpk1FD+e28Itf/EI7duxQixYtKnooACoJggZQyVSvXl333Xef43XXrl31+9//XklJSZo1a5amTZumefPmOU1Tp04dl2mOHTumlJQU5ebmys/PT1u2bNH27du1ePFijR492tH3oYce0oQJE1RUVORoy8jIUGJiopo2bar09HSFhIQ43nvwwQf15JNP6oMPPnAZ+9tvv638/Hz17t1b69ev1+rVqzV06FC369mjRw9t2LBBa9euVd++fcu+oe5w/DJpvR9//FH+/v4VPYxyCw4OdjoPAMCN4tYp4A6RlJSkmJgYLVy4ULm5uR77h4SEyGazycvLS5J0/vx5SVK9evXc9q9S5T+nkz/96U+6fPmykpOTnUKGnc1m04ABA1zaFy9erDp16mjZsmXy9/fX4sWLix3fqFGj1KJFCz3//PMqLCz0uD7XO3LkiBITExUeHi5fX1/VqVNH8fHxjlttGjRooC+++EKbN2923Kp17a1qmZmZGjZsmMLCwuTr66vmzZvrv/7rv5wC19GjR2Wz2TR//ny9+OKLql+/vvz8/NSuXTt99tlnTuM5e/asfvvb3yoyMlK+vr6qXbu2OnXqpNTU1GLX4YsvvpDNZtN7773naPv8889ls9kUExPj1Ldfv35q27at4/W1t04dPXpUtWvXliTNmjXLsb7XX8k5ffq0hgwZopCQENWpU0djxozRhQsXPG7rLl266J577tHWrVt13333yd/fX3fddZemT5/usu++//57jR8/XnfddZd8fHzUqFEjTZ06VT/99JNTv/fee08dOnRQSEiIqlWrpkaNGmnMmDGSrt4C9Mtf/lKSNHr0aMf6JCUllThO+y1i+/bt04ABAxQcHKyQkBANGzZMZ8+ederboEED9enTR6tXr9a9994rPz8/xxWhf/7zn+rfv79q1KghPz8/tWnTRsuWLXNZXnZ2tiZPnqxGjRrJ19dXYWFh6tWrl7766itHn7y8PM2ZM0fR0dGOuhg9erTLeNLS0tSlSxeFhobK399f9evX18CBA3XlyhVHn7feekutW7dWYGCggoKCFB0drRdeeMHxvrtbp+y3In777bfq1auXAgMDFRkZqcmTJ7vsk6ysLA0aNEhBQUGqXr26Hn30Ue3evVs2m63S3oIIoGRc0QBucYWFhSooKHBpN8aUeV59+/bVyy+/rD179uiBBx5wmpd9GfZbp5YtW6bExETHbVbt2rWTt7e3Jk6cqBkzZujBBx8sNnR8+umnLldJPNm+fbu+/PJLPfPMMwoNDdXAgQOVkpKijIwMNWzY0KW/l5eX5s6dq/79+2vZsmWOXzJLq1evXiosLNT8+fNVv359nTt3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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 2, + "id": "ca45c6c3", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:32:33.744689Z", + "iopub.status.busy": "2025-11-24T18:32:33.743614Z", + "iopub.status.idle": "2025-11-24T18:32:34.044704Z", + "shell.execute_reply": "2025-11-24T18:32:34.044704Z" } - ], + }, + "outputs": [], "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", + "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", + " gridspec_kw={'height_ratios':[10,1]})\n", + "\n", + "gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3')\n", + "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c')\n", + "\n", + "plot_stops(stops_hdb, ax=ax_map, cmap='Blues')\n", + "plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc)\n", + "ax_map.set_axis_off()\n", "\n", "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_hdb, ax=ax_barcode, stop_color='blue', set_xlim=False, timestamp='unix_ts')\n", - "fig.suptitle(\"HDBSCAN stops with post-processing\")\n", + "plot_stops_barcode(stops_hdb, ax=ax_barcode, cmap='Blues', set_xlim=False, timestamp='unix_ts')\n", + "\n", + "plt.tight_layout(pad=0.1)\n", "plt.show()" ] } @@ -105,7 +111,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/docs/source/ingesting-data.ipynb b/docs/source/ingesting-data.ipynb new file mode 100644 index 00000000..0f7b355e --- /dev/null +++ b/docs/source/ingesting-data.ipynb @@ -0,0 +1,507 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "460ff464-7812-41fb-bc5b-bc4f24e16499", + "metadata": { + "id": "460ff464-7812-41fb-bc5b-bc4f24e16499" + }, + "source": [ + "# **Loading and Sampling Trajectory Data**\n", + "\n", + "## Getting started\n", + "\n", + "Real-world mobility files vary widely in structure and formatting:\n", + "- e.g. **Timestamps** may be **UNIX** integers or **ISO-formatted strings**\n", + "- May have **timezones**, e.g. -05:00, Z, (GMT+01), -3600\n", + "- Coordinates might be **projected** or **geographical**\n", + "- Files may be a flat **CSV**, or **partitioned Parquets**, local or **in S3**.\n", + "\n", + "`nomad.io` is here to help." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "ca448248-3077-4e67-ad81-6d1ba1b170db", + "metadata": { + "executionInfo": { + "elapsed": 3404, + "status": "ok", + "timestamp": 1753083319439, + "user": { + "displayName": "Thomas Li", + "userId": "03526318197962168317" + }, + "user_tz": -120 + }, + "id": "ca448248-3077-4e67-ad81-6d1ba1b170db" + }, + "outputs": [], + "source": [ + "from nomad.io import base as loader\n", + "import pandas as pd\n", + "import geopandas as gpd" + ] + }, + { + "cell_type": "markdown", + "id": "c78e81f2-bcf3-4cc5-8c26-b6111484df73", + "metadata": { + "id": "c78e81f2-bcf3-4cc5-8c26-b6111484df73" + }, + "source": [ + "## Typical data ingestion ( `pandas`, `geopandas`) vs `nomad` `io` utilities" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "904bf840-4253-41e3-a1d3-d54874072613", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 206 + }, + "executionInfo": { + "elapsed": 849, + "status": "ok", + "timestamp": 1753083322765, + "user": { + "displayName": "Thomas Li", + "userId": "03526318197962168317" + }, + "user_tz": -120 + }, + "id": "904bf840-4253-41e3-a1d3-d54874072613", + "outputId": "9dd16f0f-7e96-4aaa-ade3-c431698bafbc" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
identifierdevice_londevice_latunix_timestamplocal_datetimedateha
0cocky_stallman-38.31880236.66989417041144352024-01-01 09:07:15-04:002024-01-018.492856
1cocky_stallman-38.31876536.66990517041147532024-01-01 09:12:33-04:002024-01-0111.336772
2cocky_stallman-38.31862736.66985617041147922024-01-01 09:13:12-04:002024-01-0118.436612
3cocky_stallman-38.31866136.66992017041149892024-01-01 09:16:29-04:002024-01-0127.370737
4cocky_stallman-38.31860236.66982317041151952024-01-01 09:19:55-04:002024-01-0112.506606
\n", + "
" + ], + "text/plain": [ + " identifier device_lon device_lat unix_timestamp \\\n", + "0 cocky_stallman -38.318802 36.669894 1704114435 \n", + "1 cocky_stallman -38.318765 36.669905 1704114753 \n", + "2 cocky_stallman -38.318627 36.669856 1704114792 \n", + "3 cocky_stallman -38.318661 36.669920 1704114989 \n", + "4 cocky_stallman -38.318602 36.669823 1704115195 \n", + "\n", + " local_datetime date ha \n", + "0 2024-01-01 09:07:15-04:00 2024-01-01 8.492856 \n", + "1 2024-01-01 09:12:33-04:00 2024-01-01 11.336772 \n", + "2 2024-01-01 09:13:12-04:00 2024-01-01 18.436612 \n", + "3 2024-01-01 09:16:29-04:00 2024-01-01 27.370737 \n", + "4 2024-01-01 09:19:55-04:00 2024-01-01 12.506606 " + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = pd.read_csv(\"../../tutorials/IC2S2-2025/IC2S2-2025/gc_data.csv\")\n", + "city = gpd.read_file(\"../../tutorials/IC2S2-2025/IC2S2-2025/garden_city.geojson\")\n", + "\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "de10f93d", + "metadata": {}, + "source": [ + "## `nomad.io` — facilitates type casting and default names\n", + "\n", + "`nomad.io.base.from_file` is basically a `pandas` / `pyarrow` wrapper, trying to simplify the formatting of canonical variables\n", + "\n", + "- dates and datetimes in **ISO format** are cast to `pandas.datetime64`\n", + "- **unix timestamps** are cast to integers and **reformatted to seconds**.\n", + "- **user identifiers** are cast to strings\n", + "- **partition folders** can be read as columns (Hive)\n", + "- **timezone handling** parses ISO datetime strings (with or without timezones)" + ] + }, + { + "cell_type": "markdown", + "id": "03b7bf33-48a1-4d75-bd95-ae05fb7f9357", + "metadata": { + "id": "03b7bf33-48a1-4d75-bd95-ae05fb7f9357" + }, + "source": [ + "Don't read partitioned data with a for loop! `nomad`'s `from_file` wraps `PyArrow`'s file readers maintaning the same signature." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "b33de9d2-ee49-46ac-96a1-56784674d40c", + "metadata": { + "id": "b33de9d2-ee49-46ac-96a1-56784674d40c" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "user_id object\n", + "longitude float64\n", + "latitude float64\n", + "timestamp Int64\n", + "datetime datetime64[ns]\n", + "ha float64\n", + "date object\n", + "tz_offset Int64\n", + "dtype: object\n" + ] + } + ], + "source": [ + "# For the partitioned dataset\n", + "traj_cols = {\"user_id\": \"user_id\",\n", + " \"timestamp\": \"timestamp\",\n", + " \"latitude\": \"latitude\",\n", + " \"longitude\": \"longitude\",\n", + " \"datetime\": \"datetime\",\n", + " \"date\": \"date\"}\n", + "\n", + "file_path = \"../../tutorials/IC2S2-2025/IC2S2-2025/gc_data/\" # partitioned\n", + "\n", + "\n", + "df = loader.from_file(file_path, format=\"csv\", traj_cols=traj_cols, parse_dates=True)\n", + "print(df.dtypes)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "8ca7f977", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Canonical column names in nomad\n" + ] + }, + { + "data": { + "text/plain": [ + "{'user_id': 'user_id',\n", + " 'latitude': 'latitude',\n", + " 'longitude': 'longitude',\n", + " 'datetime': 'datetime',\n", + " 'start_datetime': 'start_datetime',\n", + " 'end_datetime': 'end_datetime',\n", + " 'start_timestamp': 'start_timestamp',\n", + " 'end_timestamp': 'end_timestamp',\n", + " 'timestamp': 'timestamp',\n", + " 'date': 'date',\n", + " 'utc_date': 'date',\n", + " 'x': 'x',\n", + " 'y': 'y',\n", + " 'geohash': 'geohash',\n", + " 'tz_offset': 'tz_offset',\n", + " 'duration': 'duration',\n", + " 'ha': 'ha',\n", + " 'h3_cell': 'h3_cell',\n", + " 'location_id': 'location_id'}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from nomad.constants import DEFAULT_SCHEMA\n", + "print(\"Canonical column names in nomad\")\n", + "DEFAULT_SCHEMA" + ] + }, + { + "cell_type": "markdown", + "id": "cf4fbb1f", + "metadata": {}, + "source": [ + "```from_file``` automatically detects and reads Parquet files (single or partitioned directories) using ```PyArrow```'s dataset API, applying the same validation, type casting, and timezone handling as for CSV inputs." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "da9e025d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "uid object\n", + "timestamp Int64\n", + "latitude float64\n", + "longitude float64\n", + "date object\n", + "dtype: object\n" + ] + } + ], + "source": [ + "traj_cols = {\"user_id\": \"uid\",\n", + " \"timestamp\": \"timestamp\",\n", + " \"latitude\": \"latitude\",\n", + " \"longitude\": \"longitude\",\n", + " \"date\": \"date\"}\n", + "\n", + "file_path = \"../../nomad/data/partitioned_parquet/\" # partitioned\n", + "\n", + "df = loader.from_file(file_path, format=\"parquet\", traj_cols=traj_cols, parse_dates=True)\n", + "print(df.dtypes)" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + }, + "nbdime-conflicts": { + "local_diff": [ + { + "diff": [ + { + "diff": [ + { + "key": 0, + "op": "addrange", + "valuelist": [ + "conda_py_310_env" + ] + }, + { + "key": 0, + "length": 1, + "op": "removerange" + } + ], + "key": "display_name", + "op": "patch" + }, + { + "diff": [ + { + "key": 0, + "op": "addrange", + "valuelist": [ + "conda_py_310_env" + ] + }, + { + "key": 0, + "length": 1, + "op": "removerange" + } + ], + "key": "name", + "op": "patch" + } + ], + "key": "kernelspec", + "op": "patch" + }, + { + "diff": [ + { + "diff": [ + { + "diff": [ + { + "key": 5, + "op": "addrange", + "valuelist": "6" + }, + { + "key": 5, + "length": 2, + "op": "removerange" + } + ], + "key": 0, + "op": "patch" + } + ], + "key": "version", + "op": "patch" + } + ], + "key": "language_info", + "op": "patch" + } + ], + "remote_diff": [ + { + "diff": [ + { + "diff": [ + { + "key": 0, + "op": "addrange", + "valuelist": [ + "Python 3 (ipykernel)" + ] + }, + { + "key": 0, + "length": 1, + "op": "removerange" + } + ], + "key": "display_name", + "op": "patch" + }, + { + "diff": [ + { + "key": 0, + "op": "addrange", + "valuelist": [ + "python3" + ] + }, + { + "key": 0, + "length": 1, + "op": "removerange" + } + ], + "key": "name", + "op": "patch" + } + ], + "key": "kernelspec", + "op": "patch" + }, + { + "diff": [ + { + "diff": [ + { + "key": 0, + "length": 1, + "op": "removerange" + } + ], + "key": "version", + "op": "patch" + } + ], + "key": "language_info", + "op": "patch" + } + ] + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/lachesis_demo.ipynb b/docs/source/lachesis_demo.ipynb index a46d838b..cb9a8992 100644 --- a/docs/source/lachesis_demo.ipynb +++ b/docs/source/lachesis_demo.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "92838936", + "id": "05c3afed", "metadata": {}, "source": [ "# Lachesis Stop Detection" @@ -10,7 +10,7 @@ }, { "cell_type": "markdown", - "id": "cb276fd9", + "id": "bca8605a", "metadata": {}, "source": [ "The first stop detection algorithm implemented in ```nomad``` is a sequential algorithm insipired by the one in _Project Lachesis: Parsing and Modeling Location Histories_ (Hariharan & Toyama). This algorithm for extracting stays is dependent on two parameters: the roaming distance and the stay duration. \n", @@ -28,32 +28,43 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "19184dee", - "metadata": {}, + "execution_count": 1, + "id": "7f0b2bb1", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:32:46.921799Z", + "iopub.status.busy": "2025-11-24T18:32:46.921799Z", + "iopub.status.idle": "2025-11-24T18:32:51.000857Z", + "shell.execute_reply": "2025-11-24T18:32:51.000857Z" + } + }, "outputs": [], "source": [ "%matplotlib inline\n", + "import matplotlib\n", + "matplotlib.use('TkAgg')\n", + "import matplotlib.pyplot as plt\n", + "plt.ion()\n", "\n", "# Imports\n", "import nomad.io.base as loader\n", + "import geopandas as gpd\n", "from shapely.geometry import box\n", - "import matplotlib.pyplot as plt\n", - "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode\n", + "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode, plot_stops, plot_pings\n", "import nomad.stop_detection.lachesis as LACHESIS\n", + "import nomad.data as data_folder\n", + "from pathlib import Path\n", "\n", "# Load data\n", + "data_dir = Path(data_folder.__file__).parent\n", + "city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet')\n", + "outer_box = box(*city.total_bounds)\n", + "\n", "filepath_root = 'gc_data_long/'\n", - "tc = {\n", - " \"user_id\": \"gc_identifier\",\n", - " \"timestamp\": \"unix_ts\",\n", - " \"x\": \"dev_x\",\n", - " \"y\": \"dev_y\",\n", - " \"ha\":\"ha\",\n", - " \"date\":\"date\"}\n", + "tc = {\"user_id\": \"gc_identifier\", \"x\": \"dev_x\", \"y\": \"dev_y\", \"timestamp\": \"unix_ts\"}\n", "\n", "users = ['admiring_brattain']\n", - "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','==', '2024-01-01'), traj_cols=tc)\n", + "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc)\n", "\n", "# Lachesis (sequential stop detection)\n", "stops = LACHESIS.lachesis(traj, delta_roam=20, dt_max = 60, dur_min=5, complete_output=True, keep_col_names=True, traj_cols=tc)" @@ -61,28 +72,32 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "570b6103", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 2, + "id": "86273598", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:32:51.005525Z", + "iopub.status.busy": "2025-11-24T18:32:51.000857Z", + "iopub.status.idle": "2025-11-24T18:32:51.355886Z", + "shell.execute_reply": "2025-11-24T18:32:51.355274Z" } - ], + }, + "outputs": [], "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", + "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", + " gridspec_kw={'height_ratios':[10,1]})\n", + "\n", + "gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3')\n", + "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c')\n", + "\n", + "plot_stops(stops, ax=ax_map, cmap='Blues')\n", + "plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc)\n", + "ax_map.set_axis_off()\n", + "\n", + "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", + "plot_stops_barcode(stops, ax=ax_barcode, cmap='Blues', set_xlim=False, timestamp='unix_ts')\n", "\n", - "plot_time_barcode(traj[tc['timestamp']], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops, ax=ax_barcode, stop_color='blue', set_xlim=False, timestamp='unix_ts')\n", - "fig.suptitle(\"Lachesis stops\")\n", - "plt.tight_layout()\n", + "plt.tight_layout(pad=0.1)\n", "plt.show()" ] } @@ -103,7 +118,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/docs/source/tadbscan_demo.ipynb b/docs/source/tadbscan_demo.ipynb index 6e28d6b4..c4f5747b 100644 --- a/docs/source/tadbscan_demo.ipynb +++ b/docs/source/tadbscan_demo.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "92838936", + "id": "7c76d1d8", "metadata": {}, "source": [ "# TADBSCAN Stop Detection" @@ -10,7 +10,7 @@ }, { "cell_type": "markdown", - "id": "cb276fd9", + "id": "f29a96ce", "metadata": {}, "source": [ "The second stop detection algorithm implemented in ```nomad``` is an adaptation of DBSCAN. Unlike in plain DBSCAN, we also incorporate the time dimension to determine if two pings are \"neighbors\". This implementation relies on 3 parameters\n", @@ -24,79 +24,81 @@ }, { "cell_type": "code", - "execution_count": 4, - "id": "19184dee", - "metadata": {}, + "execution_count": 1, + "id": "1e62c25a", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:03.240035Z", + "iopub.status.busy": "2025-11-24T18:33:03.240035Z", + "iopub.status.idle": "2025-11-24T18:33:05.816985Z", + "shell.execute_reply": "2025-11-24T18:33:05.816985Z" + } + }, "outputs": [], "source": [ "%matplotlib inline\n", + "import matplotlib\n", + "matplotlib.use('TkAgg')\n", + "import matplotlib.pyplot as plt\n", + "plt.ion()\n", "\n", "# Imports\n", "import nomad.io.base as loader\n", "import geopandas as gpd\n", "from shapely.geometry import box\n", - "import pandas as pd\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode\n", + "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode, plot_stops, plot_pings\n", "import nomad.stop_detection.dbscan as DBSCAN\n", - "import nomad.filters as filters \n", - "import nomad.stop_detection.postprocessing as post\n", "\n", "# Load data\n", - "city = gpd.read_file(\"garden_city.geojson\").to_crs('EPSG:3857')\n", - "outer_box = box(*city.total_bounds).buffer(15, join_style='mitre')\n", + "import nomad.data as data_folder\n", + "from pathlib import Path\n", + "data_dir = Path(data_folder.__file__).parent\n", + "city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet')\n", + "outer_box = box(*city.total_bounds)\n", "\n", "filepath_root = 'gc_data_long/'\n", - "tc = {\n", - " \"user_id\": \"gc_identifier\",\n", - " \"timestamp\": \"unix_ts\",\n", - " \"x\": \"dev_x\",\n", - " \"y\": \"dev_y\",\n", - " \"ha\":\"ha\",\n", - " \"date\":\"date\"}\n", + "tc = {\"user_id\": \"gc_identifier\", \"x\": \"dev_x\", \"y\": \"dev_y\", \"timestamp\": \"unix_ts\"}\n", "\n", "# Density based stop detection (Temporal DBSCAN)\n", - "users = ['confident_aryabhata']\n", - "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','<=', '2024-01-03'), traj_cols=tc)\n", - "traj[['longitude','latitude']] = np.column_stack(\n", - " filters.to_projection(traj, x='dev_x', y='dev_y', data_crs='EPSG:3857', crs_to='EPSG:4326')\n", - ")\n", + "users = ['admiring_brattain']\n", + "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc)\n", "\n", - "user_data_tadb = traj.assign(cluster=DBSCAN.ta_dbscan_labels(traj, time_thresh=240, dist_thresh=15, min_pts=3, traj_cols=tc))\n", "stops_tadb = DBSCAN.ta_dbscan(traj,\n", " time_thresh=720,\n", " dist_thresh=15,\n", " min_pts=3,\n", " complete_output=True,\n", - " traj_cols=tc)\n", - "stops_tadb[\"cluster\"] = post.remove_overlaps(user_data_tadb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) " + " traj_cols=tc) " ] }, { "cell_type": "code", - "execution_count": 5, - "id": "2159107b", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA9oAAACMCAYAAACDKNEwAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAp9ElEQVR4nO3deXxU1d3H8e9kyGQhCxACJRC2qGwxWFbBUnaRTQXUAsoiqPUpYkFUtC4Ea4uIIlgU9DEsigZEAQUECgjiEhAr2AcBl0oEhCgGSSIQkOQ8f9C5zJpMwmQI8nm/XvOCOXPuOeeee+Y385u5c2MzxhgBAAAAAICgCDvfAwAAAAAA4NeERBsAAAAAgCAi0QYAAAAAIIhItAEAAAAACCISbQAAAAAAgohEGwAAAACAICLRBgAAAAAgiEi0AQAAAAAIIhJtAAAAAACCiEQbAMrAZrMFdNu0aZO1zbPPPiubzabU1NQy9bVp0ya3Nh0OhxITE3XVVVfpoYce0rfffuu1zfz5873GkpiYqC5dumjlypVe9Xfv3q1hw4apcePGioyMVM2aNdWqVSvdddddys/P96q/YsUK9e/fX7Vr15bD4VCNGjXUvXt3vfrqq/rll1+86v/444+KiIiQzWbTJ5984nM/R44cKZvNphYtWqioqMjrcZvNprvuuiuQKSvRwYMHlZ6erh07dpxzW5VNdna2bDab5s+fb5V99NFHSk9P19GjR73qN2zYUP369QvdAIPgnXfeUXp6+vkexgVl5MiRatiw4fkeBgBclEi0AaAMsrKy3G59+vRRVFSUV3mrVq2sbebOnStJ+vzzz7V169Yy9/n3v/9dWVlZ2rhxozIyMtSlSxfNnTtXzZo106uvvupzm3nz5ikrK0sfffSRXnzxRdntdvXv318rVqyw6mzfvl2tW7fWrl279Oijj2rNmjWaM2eO+vbtq7Vr1+rIkSNWXWOMbr31Vl177bUqLi7W9OnTtX79ei1YsEAtW7bUn/70Jz3//PNe43jllVd06tQpSVJGRkaJ+7lr1y63RDHYDh48qMmTJ/8qE+06deooKytLffv2tco++ugjTZ482WeifSF65513NHny5PM9jAvKI488omXLlp3vYQDARanK+R4AAFxIrrzySrf7iYmJCgsL8yp3+uSTT/TZZ5+pb9++WrVqlTIyMtS+ffsy9XnppZe6tX/ttddqwoQJ6tGjh0aOHKm0tDRdfvnlbtukpqaqTZs21v1rrrlG1atXV2Zmpvr37y9JmjFjhsLCwrRp0ybFxsZadW+44Qb99a9/lTHGKps2bZrmz5+vyZMn69FHH3Xrq3///rr//vv19ddfe4197ty5qlWrlho0aKDMzExNnz5dUVFRXvWqVq2qVq1aadKkSRo6dKjPOvAvIiLC7xpE+fzyyy+y2WyqUuXCfauUkpJyvocAABctvtEGgArk/Bb3iSeeUMeOHbVo0SIdP378nNutUaOGXnjhBZ0+fVrPPPNMqfUjIyPlcDgUHh5uleXm5iouLk4xMTE+t7HZbJLOJBxTp05V06ZN9cgjj/is+5vf/Ea/+93v3Mq2bt2qnTt3atiwYbr99tuVl5enN9980+8Yp06dqu+++04zZ84sdX98WbJkidq3b6/4+HhFR0ercePGGjVqlKQzp+G3bdtWknTrrbdap9W7nor89ttvq0OHDoqOjlZsbKx69uyprKwstz7S09Nls9m0fft2DRw4UHFxcYqPj9ctt9yiw4cPu9V999131aVLFyUkJCgqKkr169fXoEGDSjz+9913n+Lj491OoR87dqxsNpumTZtmleXm5iosLEz/+Mc/JHmfOp6enq777rtPktSoUSOfP2mQpDVr1qhVq1aKiopS06ZNrbMvSuLs68knn9Tf/vY31a9fX5GRkWrTpo02bNjgVf+DDz5Q9+7dFRsbq+joaHXs2FGrVq1yq3P8+HHde++9atSokSIjI1WjRg21adNGmZmZks6cAv3cc89Jcv/5RnZ2doljdZ4iv2zZMqWlpSkyMlKNGzfWs88+61bP+TONV155RRMmTFDdunUVERFhfXg0d+5ctWzZ0hrbgAEDtHv3bq/+tm7dqv79+yshIUGRkZFKSUnRuHHj3Op89dVXGjp0qGrVqqWIiAg1a9bM2jen4uJiPf7442rSpImioqJUrVo1paWluT03Dh8+rDvuuEPJycmKiIiwflayfv16q46vU8edP8V45ZVX1KxZM0VHR6tly5Y+f1ry1ltvKS0tTREREWrcuLFmzpxpPQcAACUj0QaACnLixAllZmaqbdu2Sk1N1ahRo1RQUKAlS5YEpf22bduqTp062rx5s9djRUVFOn36tH755RcdOHBA48aN07FjxzR06FCrTocOHXTo0CHdfPPNeu+993TixAmf/XzyySc6cuSIrrvuujK9wXZ+yDBq1CgNHjxY0dHRJZ4+3qFDBw0YMEBTp051O209EFlZWfrDH/6gxo0ba9GiRVq1apUeffRRnT59WpLUqlUrzZs3T5L08MMPW6f433bbbZKk1157Tdddd53i4uKUmZmpjIwM/fTTT+rSpYs++OADr/4GDBigSy65RG+88YbS09O1fPly9erVy/qdenZ2tvr27SuHw6G5c+dqzZo1euKJJ1S1alXrVHpfevToofz8fH388cdW2fr16xUVFaV169ZZZRs2bJAxRj169PDZzm233aaxY8dKkpYuXerzJw2fffaZJkyYoPHjx1sJ1ejRo32uJ19mzZqlNWvWaMaMGVq4cKHCwsLUu3dvtw8n3nvvPXXr1k15eXnKyMhQZmamYmNj1b9/fy1evNiqd88992j27Nm6++67tWbNGr3yyiu68cYblZubK+nMKdA33HCDJPefb9SpU6fUce7YsUPjxo3T+PHjtWzZMnXs2FF//vOf9dRTT3nVffDBB7Vv3z7NmTNHK1asUK1atTRlyhSNHj1aLVq00NKlSzVz5kz9+9//VocOHfTVV19Z265du1adOnXSvn37NH36dK1evVoPP/ywvv/+e6vOrl271LZtW+3cuVNPP/20Vq5cqb59++ruu+92Oy3+ySefVHp6uoYMGaJVq1Zp8eLFGj16tNvPAIYNG6bly5fr0Ucf1T//+U+99NJL6tGjhzVnJVm1apVmzZqlxx57TG+++ab14cE333xj1VmzZo0GDhyohIQELV68WE8++aQyMzO1YMGCUtsHAEgyAIByGzFihKlatarPx15++WUjycyZM8cYY0xBQYGJiYkxnTp1CqjtjRs3GklmyZIlfuu0b9/eREVFWffnzZtnJHndIiIizPPPP++2bWFhobn++uutOna73fz2t781Dz30kPnhhx+seosWLXLbj0AcO3bMxMXFmSuvvNIqGzFihLHZbObrr792q+s6h3v27DF2u91MmDDBelySGTNmTIn9PfXUU0aSOXr0qN8627ZtM5LMvHnz3MqLiopMUlKSufzyy01RUZFVXlBQYGrVqmU6duxolU2aNMlIMuPHj3dr49VXXzWSzMKFC40xxrzxxhtGktmxY0eJ4/Z07Ngx43A4zGOPPWaMMebAgQNGkpk4caKJiooyhYWFxhhjbr/9dpOUlGRtt3fvXq99mzZtmpFk9u7d69VPgwYNTGRkpPn222+tshMnTpgaNWqYP/7xjyWO0dlXUlKSOXHihFWen59vatSoYXr06GGVXXnllaZWrVqmoKDAKjt9+rRJTU019erVM8XFxcYYY1JTU831119fYr9jxowxZX3b0qBBA2Oz2byOQ8+ePU1cXJw5duyYMebsc+33v/+9W72ffvrJREVFmT59+riV79u3z0RERJihQ4daZSkpKSYlJcVtTjz16tXL1KtXz+Tl5bmV33XXXSYyMtIcOXLEGGNMv379zBVXXFHivsXExJhx48aVWGfEiBGmQYMGbmWSTO3atU1+fr5VlpOTY8LCwsyUKVOssrZt25rk5GRz8uRJq6ygoMAkJCSU+TgAwMWIb7QBoIJkZGQoKipKgwcPliTFxMToxhtv1Pvvv+/2Tdjp06fdbsblt9Gl8Vf35Zdf1rZt27Rt2zatXr1aI0aM0JgxYzRr1iyrTkREhJYtW6Zdu3bpmWee0eDBg3X48GH97W9/U7NmzfTFF1+Uc8+l119/Xfn5+dap29KZb7aNMdY3y740adJEo0eP1qxZs7Rv376A+3OeFn7TTTfp9ddf13fffRfwtl988YUOHjyoYcOGKSzs7MtiTEyMBg0apC1btnid7n3zzTe73b/ppptUpUoVbdy4UZJ0xRVXyOFw6I477tCCBQvcviksSXR0tDp06GCd/rtu3TpVq1ZN9913n06dOmV9u75+/Xq/32YH6oorrlD9+vWt+5GRkbrssst8Xs3el4EDByoyMtK67/ymevPmzSoqKtKxY8e0detW3XDDDW4/T7Db7Ro2bJgOHDhgrbF27dpp9erVeuCBB7Rp0ya/Z1f4Ulxc7Pb88bxyfYsWLdSyZUu3sqFDhyo/P1+ffvqpW/mgQYPc7mdlZenEiRMaOXKkW3lycrK6detmnSr/5Zdf6j//+Y9Gjx7tNieuCgsLtWHDBg0YMEDR0dFuY+7Tp48KCwu1ZcsWaz4+++wz/elPf9LatWt9/gWAdu3aaf78+Xr88ce1ZcsWn1f996dr165u12WoXbu2atWqZR37Y8eO6ZNPPtH1118vh8Nh1YuJibGu8QAAKBmJNgBUgK+//lqbN29W3759ZYzR0aNHdfToUev0V+dvYbOzsxUeHu52e++99wLuZ9++fUpKSvIqb9asmdq0aaM2bdrommuu0QsvvKCrr75a999/v9dVqJs1a6Zx48Zp4cKF1mmvubm51u+xncnY3r17Ax5XRkaGIiMjdc0111j7npaWpoYNG2r+/Pk+/4yXU3p6uux2u9/fg/vy+9//XsuXL9fp06c1fPhw1atXT6mpqdZvfEviPNXW12nISUlJKi4u1k8//eRW/pvf/MbtfpUqVZSQkGC1lZKSovXr16tWrVoaM2aMUlJSlJKSEtDvz3v06KEtW7bo2LFjWr9+vbp166aEhAS1bt1a69ev1969e7V3795zTrQTEhK8yiIiIgJOcj3nwFl26tQp/fzzz/rpp59kjPE7r9LZuX/22Wc1ceJELV++XF27dlWNGjV0/fXXu30g5c+oUaPcnj/du3cPaJyu/Tt5jrW0teF83Pn7/Hr16vkdZ25urk6fPq1//OMfXs/5Pn36SDrz5/CkM6ewP/XUU9qyZYt69+6thIQEde/e3e1P5C1evFgjRozQSy+9pA4dOqhGjRoaPny4cnJy/I7BqbRj7zx2tWvX9qrnqwwA4I1EGwAqwNy5c2WM0RtvvKHq1atbN+efX1qwYIGKioqUlJRkffPsvLVu3TqgPj7++GPl5OSoS5cuAdVPS0vTiRMn9OWXX/qtY7PZNH78eFWrVk07d+6UJLVp00Y1atTQW2+9FdC37V9++aU++OADFRYWqn79+m77n52dre+++05r1671u32dOnWsxP/f//53QPsmSdddd502bNigvLw8bdq0SfXq1dPQoUO9LmjmyZl0HDp0yOuxgwcPKiwsTNWrV3cr90xmTp8+rdzcXLcEplOnTlqxYoXy8vK0ZcsWdejQQePGjdOiRYtKHE/37t116tQpbd68WRs2bFDPnj2t8nXr1lm/1fZMKEPNV0KXk5Mjh8OhmJgYVa9eXWFhYX7nVZJq1qwp6cxV5ydPnqw9e/YoJydHs2fP1pYtWwL69jQ9Pd3t+fPCCy8ENE7JO+H0vAZBaWvDOf7ExERJ0oEDB/yOs3r16rLb7Ro5cqTXc955cybcVapU0T333KNPP/1UR44cUWZmpvbv369evXpZZ1fUrFlTM2bMUHZ2tr799ltNmTJFS5cu9fr2vTyqV68um83m9vtyp0ASeQAAiTYABF1RUZEWLFiglJQUbdy40es2YcIEHTp0SKtXr5bD4bC+eXbeXE/p9OfIkSO68847FR4ervHjxwc0Luffj3YmBb6SB+lMApGfn2996xgeHq6JEydqz549+utf/+pzmx9++EEffvihpLMXQfvf//1fr31/5513FB4eXurVrSdOnKgaNWrogQceCGjfXEVERKhz586aOnWqpDN/L9xZLsnrG9smTZqobt26eu2119w+SDh27JjefPNN60rkrjz/fvnrr7+u06dP+/zQw263q3379taVpT1PV/bUrl07xcXFacaMGcrJybES7R49emj79u16/fXX1bx5c59nMnjOg6/9DZalS5eqsLDQul9QUKAVK1aoU6dOstvtqlq1qtq3b6+lS5e6jaG4uFgLFy5UvXr1dNlll3m1W7t2bY0cOVJDhgzRF198YSWW/vanYcOGbs+fJk2auD3++eef67PPPnMre+211xQbG+t2cThfOnTooKioKC1cuNCt/MCBA3r33XetDzsuu+wypaSkaO7cuTp58qTPtqKjo9W1a1dt375daWlpXs/7Nm3a+PymuVq1arrhhhs0ZswYHTlyxOeV1uvXr6+77rpLPXv2LHV9BaJq1apq06aNli9f7nbxvp9//tnn1ckBAN4u3D8OCQCV1OrVq3Xw4EFNnTrVZ+KVmpqqWbNmKSMjQ/369Su1va+++kpbtmxRcXGxcnNztXXrVmVkZCg/P18vv/yyWrRo4bXNzp07rStu5+bmaunSpVq3bp0GDBigRo0aSZLuuOMOHT16VIMGDVJqaqrsdrv27NmjZ555RmFhYZo4caLV3n333afdu3dr0qRJ+vjjjzV06FAlJycrLy9Pmzdv1osvvqjJkyerffv2evnll9WsWTPrit6e+vfvr7fffluHDx+2kn5PcXFxeuihhwL+EOHRRx/VgQMH1L17d9WrV09Hjx7VzJkzFR4ers6dO0s6czp3VFSUXn31VTVr1kwxMTFKSkpSUlKSnnzySd18883q16+f/vjHP+rkyZOaNm2ajh49qieeeMKrv6VLl6pKlSrq2bOnPv/8cz3yyCNq2bKlbrrpJknSnDlz9O6776pv376qX7++CgsLrQ8XSjvl2263q3PnzlqxYoUaNWpk/S3kq666ShEREdqwYYPuvvvuUufE+bfVZ86cqREjRig8PFxNmjQJ6IOcQNjtdvXs2VP33HOPiouLNXXqVOXn57tdPXvKlCnq2bOnunbtqnvvvVcOh0PPP/+8du7cqczMTOsb5Pbt26tfv35KS0tT9erVtXv3br3yyituH3I492fq1Knq3bu37Ha70tLS3H5D7EtSUpKuvfZapaenq06dOlq4cKHWrVunqVOnen2A4qlatWp65JFH9Je//EXDhw/XkCFDlJubq8mTJysyMlKTJk2y6j733HPq37+/rrzySo0fP17169fXvn37tHbtWuuDmZkzZ+p3v/udOnXqpP/5n/9Rw4YNVVBQoK+//lorVqzQu+++K+nMcyQ1NVVt2rRRYmKivv32W82YMUMNGjTQpZdeqry8PHXt2lVDhw5V06ZNFRsbq23btllXCg+Gxx57TH379lWvXr305z//WUVFRZo2bZpiYmLK/FcBAOCidN4uwwYAvwK+rjp+/fXXG4fD4Xblbk+DBw82VapUMTk5OX7rOK+E7LxVqVLFJCQkmA4dOpi//OUvJjs722sbX1cdj4+PN1dccYWZPn26ddVqY4xZu3atGTVqlGnevLmJj483VapUMXXq1DEDBw40WVlZPsf01ltvmb59+5rExERTpUoVU716ddO1a1czZ84cc/LkSbN8+XIjycyYMcPvfq1Zs8ZIMk8//bTfOTTGmJMnT5pGjRoFdNXxlStXmt69e5u6desah8NhatWqZfr06WPef/99t3qZmZmmadOmJjw83EgykyZNsh5bvny5ad++vYmMjDRVq1Y13bt3Nx9++KHb9s6rjv/rX/8y/fv3NzExMSY2NtYMGTLEfP/991a9rKwsM2DAANOgQQMTERFhEhISTOfOnc3bb79d4n44zZw500gyt99+u1t5z549jSSvdnxdddwYYx588EGTlJRkwsLCjCSzceNGY8yZq3H37dvXq9/OnTubzp07lzg2Z19Tp041kydPNvXq1TMOh8P89re/NWvXrvWq//7775tu3bqZqlWrmqioKHPllVeaFStWuNV54IEHTJs2bUz16tVNRESEady4sRk/frz58ccfrTonT540t912m0lMTDQ2m83vFdVdOffzjTfeMC1atDAOh8M0bNjQTJ8+3a1eaVf4f+mll0xaWppxOBwmPj7eXHfddebzzz/3qpeVlWV69+5t4uPjTUREhElJSfG6Qv3evXvNqFGjTN26dU14eLhJTEw0HTt2NI8//rhV5+mnnzYdO3Y0NWvWNA6Hw9SvX9+MHj3aes4XFhaaO++806SlpZm4uDgTFRVl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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 2, + "id": "df942a2c", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:05.816985Z", + "iopub.status.busy": "2025-11-24T18:33:05.816985Z", + "iopub.status.idle": "2025-11-24T18:33:06.001663Z", + "shell.execute_reply": "2025-11-24T18:33:06.001511Z" } - ], + }, + "outputs": [], "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", + "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", + " gridspec_kw={'height_ratios':[10,1]})\n", + "\n", + "gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3')\n", + "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c')\n", + "\n", + "plot_stops(stops_tadb, ax=ax_map, cmap='Reds')\n", + "plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc)\n", + "ax_map.set_axis_off()\n", "\n", "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_tadb, ax=ax_barcode, stop_color='red', set_xlim=False, timestamp='unix_ts')\n", - "plt.title(\"TA-DBSCAN stops with post-processing\")\n", - "plt.tight_layout()\n", + "plot_stops_barcode(stops_tadb, ax=ax_barcode, cmap='Reds', set_xlim=False, timestamp='unix_ts')\n", + "\n", + "plt.tight_layout(pad=0.1)\n", "plt.show()" ] } @@ -117,7 +119,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/examples/benchmarking_of_stop_detection_algorithms.ipynb b/examples/benchmarking_of_stop_detection_algorithms.ipynb index 6097165d..64549ab4 100644 --- a/examples/benchmarking_of_stop_detection_algorithms.ipynb +++ b/examples/benchmarking_of_stop_detection_algorithms.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "92838936", + "id": "67fc810a", "metadata": {}, "source": [ "# Comparing runtimes of different stop detection algorithms on toy datasets" @@ -10,7 +10,7 @@ }, { "cell_type": "markdown", - "id": "cb276fd9", + "id": "f152187c", "metadata": {}, "source": [ "Here we compare the runtimes of four different stop detection algorithms: Lachesis, grid-based, temporal DBSCAN, and HDBSCAN." @@ -18,28 +18,44 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "19184dee", - "metadata": {}, + "execution_count": 1, + "id": "474229df", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:19.946986Z", + "iopub.status.busy": "2025-11-24T18:33:19.946986Z", + "iopub.status.idle": "2025-11-24T18:33:23.251955Z", + "shell.execute_reply": "2025-11-24T18:33:23.251955Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Lachesis execution time: 0.06618499755859375 seconds\n", - "TA-DBSCAN execution time: 0.13709473609924316 seconds\n", - "TA-DBSCAN pre-processing time: 0.06432580947875977 seconds\n", - "TA-DBSCAN clustering time: 0.11364507675170898 seconds\n", - "TA-DBSCAN post-processing time: 0.02340412139892578 seconds\n", - "Grid-Based execution time: 0.16486215591430664 seconds\n", - "HDBSCAN execution time: 2.95216703414917 seconds\n", - "HDBSCAN clustering time: 2.930790901184082 seconds\n", - "HDBSCAN post-processing time: 0.02130913734436035 seconds\n" + "Lachesis execution time: 0.02721381187438965 seconds\n", + "TA-DBSCAN execution time: 0.012791156768798828 seconds\n", + "TA-DBSCAN clustering time: 0.009821414947509766 seconds\n", + "TA-DBSCAN post-processing time: 0.0029697418212890625 seconds\n", + "Grid-Based execution time: 0.022524595260620117 seconds\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "HDBSCAN execution time: 0.3206779956817627 seconds\n", + "HDBSCAN clustering time: 0.3206779956817627 seconds\n", + "HDBSCAN post-processing time: 0.0 seconds\n" ] } ], "source": [ "%matplotlib inline\n", + "import matplotlib\n", + "matplotlib.use('TkAgg')\n", + "import matplotlib.pyplot as plt\n", + "plt.ion()\n", "\n", "# Imports\n", "import nomad.io.base as loader\n", @@ -47,9 +63,7 @@ "from shapely.geometry import box\n", "import pandas as pd\n", "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import shapely.plotting as shp_plt\n", - "from nomad.stop_detection.viz import adjust_zoom, plot_stops_barcode, plot_pings, plot_stops, plot_time_barcode\n", + "from nomad.stop_detection.viz import plot_stops_barcode, plot_pings, plot_stops, plot_time_barcode\n", "import nomad.stop_detection.dbscan as DBSCAN\n", "import nomad.stop_detection.lachesis as LACHESIS\n", "import nomad.stop_detection.grid_based as GRID_BASED\n", @@ -60,10 +74,13 @@ "from tqdm import tqdm\n", "\n", "# Load data\n", - "city = gpd.read_file(\"garden_city.geojson\").to_crs('EPSG:3857')\n", + "import nomad.data as data_folder\n", + "from pathlib import Path\n", + "data_dir = Path(data_folder.__file__).parent\n", + "city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet')\n", "outer_box = box(*city.total_bounds).buffer(15, join_style='mitre')\n", "\n", - "filepath_root = '../tutorials/IC2S2-2025/gc_data_long/'\n", + "filepath_root = 'gc_data_long/'\n", "tc = {\n", " \"user_id\": \"gc_identifier\",\n", " \"timestamp\": \"unix_ts\",\n", @@ -73,7 +90,7 @@ " \"date\":\"date\"}\n", "\n", "users = ['admiring_brattain']\n", - "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','==', '2024-01-01'), traj_cols=tc)\n", + "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc)\n", "\n", "# Lachesis (sequential stop detection)\n", "start_time = time.time()\n", @@ -82,30 +99,21 @@ "print(f\"Lachesis execution time: {execution_time_lachesis} seconds\")\n", "\n", "# Density based stop detection (Temporal DBSCAN)\n", - "start_time_pre_tadbscan = time.time()\n", - "users = ['confident_aryabhata']\n", - "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','<=', '2024-01-03'), traj_cols=tc)\n", - "traj[['longitude','latitude']] = np.column_stack(\n", - " filters.to_projection(traj, x='dev_x', y='dev_y', data_crs='EPSG:3857', crs_to='EPSG:4326')\n", - ")\n", - "time_pre_tadbscan = time.time() - start_time_pre_tadbscan\n", - "\n", "start_time = time.time()\n", "user_data_tadb = traj.assign(cluster=DBSCAN.ta_dbscan_labels(traj, time_thresh=240, dist_thresh=15, min_pts=3, traj_cols=tc))\n", "clustering_time_tadbscan = time.time() - start_time\n", "start_time_post = time.time()\n", - "stops_tadb = post.remove_overlaps(user_data_tadb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3)\n", + "cluster_labels_tadb = post.remove_overlaps(user_data_tadb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3)\n", "execution_time_tadbscan = time.time() - start_time\n", "post_time_tadbscan = time.time() - start_time_post\n", "print(f\"TA-DBSCAN execution time: {execution_time_tadbscan} seconds\")\n", - "print(f\"TA-DBSCAN pre-processing time: {time_pre_tadbscan} seconds\")\n", "print(f\"TA-DBSCAN clustering time: {clustering_time_tadbscan} seconds\")\n", "print(f\"TA-DBSCAN post-processing time: {post_time_tadbscan} seconds\")\n", "\n", "# Grid-based\n", "start_time = time.time()\n", - "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, x='dev_x', y='dev_y', data_crs='EPSG:3857')\n", - "stops_gb = GRID_BASED.grid_based(traj, time_thresh=240, complete_output=True, timestamp='unix_ts', location_id='h3_cell')\n", + "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, traj_cols=tc, data_crs='EPSG:3857')\n", + "stops_gb = GRID_BASED.grid_based(traj, time_thresh=240, complete_output=True, traj_cols=tc, location_id='h3_cell')\n", "execution_time_grid = time.time() - start_time\n", "print(f\"Grid-Based execution time: {execution_time_grid} seconds\")\n", "\n", @@ -114,7 +122,7 @@ "user_data_hdb = traj.assign(cluster=HDBSCAN.hdbscan_labels(traj, time_thresh=240, min_pts=3, min_cluster_size=2, traj_cols=tc))\n", "clustering_time_hdbscan = time.time() - start_time\n", "start_time_post = time.time()\n", - "stops_hdb = post.remove_overlaps(user_data_hdb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) \n", + "cluster_labels_hdb = post.remove_overlaps(user_data_hdb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) \n", "execution_time_hdbscan = time.time() - start_time\n", "post_time_hdbscan = time.time() - start_time_post\n", "print(f\"HDBSCAN execution time: {execution_time_hdbscan} seconds\")\n", @@ -124,7 +132,7 @@ }, { "cell_type": "markdown", - "id": "5b150f1e", + "id": "c88f426d", "metadata": {}, "source": [ "## Summary of Single-User Performance" @@ -132,7 +140,7 @@ }, { "cell_type": "markdown", - "id": "07203969", + "id": "6a678431", "metadata": {}, "source": [ "### Lachesis" @@ -140,177 +148,57 @@ }, { "cell_type": "code", - "execution_count": 7, - "id": "570b6103", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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Nh7lA0WLcw9wxkQ/9whpE0QymdTnPvc5XX311YddTPbGG2/UO9/5TknS61//evX29mrVqlX7Pc7n8+05mCXppptu0jvf+U75fL78PwGgSHHuAc6YDOceIW0CyGQyeutb3yrbtvWTn/zkkI9du3atnn76aV199dWSJK/Xq6uuuko33njjAR9/zTXX6E9/+pN27typP/3pT7rmmmvyXj9QrDj3AGdMlnPP61jLyIuhA3XLli16+OGHR/TbRDab1dSpU/d8zbZtBQIB/ehHP1Jpaelejz/qqKPU0NCgq6++WosWLdLSpUu1Zs2a8XgqQFHh3AOcMZnOPXrSitjQgbp+/Xo9+OCDqqysPOTjs9msfvOb3+i73/2u1qxZs+fPiy++qKlTp+qWW2454HbXXHONHn30UX6TB3bj3AOcMdnOPXrSXKq/v18bNmzY8//NmzdrzZo1qqio0IwZM5TJZPSWt7xFzz//vO6++27lcjnt3LlTklRRUSG/37/fPu+++251d3fr2muv3e83hze/+c268cYb9aEPfWi/7T7wgQ/oyiuvPOzATGAi4NwDnMG5dwAFvZcUI/bII4/Ykvb78573vMe27X/dLnygP4888sgB93nppZfaF1988QG/99RTT9mS7BdffHHPvl944YUDPvaFF15gGgBMWJx7gDM49/Zn2PYB7kEFAACAoxiTBgAA4EKENAAAABcipAEAALgQIQ0AAMCFCGkAAAAuREgDAABwIUIaAACACxHSAAAAXIiQBgAA4EKENAAAABcipAEAALgQIQ0AAMCF/j/YVMXrVVyP4QAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 2, + "id": "b7480c93", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:23.251955Z", + "iopub.status.busy": "2025-11-24T18:33:23.251955Z", + "iopub.status.idle": "2025-11-24T18:33:23.475346Z", + "shell.execute_reply": "2025-11-24T18:33:23.475346Z" } - ], + }, + "outputs": [], "source": [ "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", " gridspec_kw={'height_ratios':[10,1]})\n", "\n", - "shp_plt.plot_polygon(outer_box, ax=ax_map, add_points=False, color='#0e0e0e')\n", - "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#2c353c')\n", + "gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3')\n", + "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c')\n", "\n", - "plot_stops(stops, ax=ax_map, cmap='Reds', x='x', y='y')\n", + "plot_stops(stops, ax=ax_map, cmap='Reds')\n", "plot_pings(traj, ax=ax_map, s=6, point_color='black', cmap='twilight', traj_cols=tc)\n", - "\n", - "adjust_zoom(stops['x'], stops['y'], buffer=1.4, ax=ax_map)\n", "ax_map.set_axis_off()\n", "\n", "plot_time_barcode(traj[tc['timestamp']], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops, ax=ax_barcode, cmap='Reds', set_xlim=False, x='x', y='y', timestamp='unix_ts')\n", + "plot_stops_barcode(stops, ax=ax_barcode, cmap='Reds', set_xlim=False, timestamp='unix_ts')\n", "\n", "plt.tight_layout(pad=0.1)\n", "plt.show()" ] }, - { - "cell_type": "markdown", - "id": "d286bd02", - "metadata": {}, - "source": [ - "### TADBSCAN" - ] - }, { "cell_type": "code", - "execution_count": 20, - "id": "2159107b", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/carolinechen/Desktop/cs/nomad/nomad/io/base.py:104: UserWarning: Trajectory column 'unix_ts' specified for 'timestamp' not found in DataFrame.\n", - " warnings.warn(f\"Trajectory column '{value}' specified for '{key}' not found in DataFrame.\")\n" - ] - }, - { - "data": { - "image/png": "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a4eFhenNN9/U3//+d9WsWVPdunXTW2+9pVOnTpXYl65du+rkyZNKS0vTN998I4fDoc6dO3sk4ykpKerSpYtPf0ujfv36PmVVqlQJ+EOCksbANWbNmjXzqdeiRQudPXtWly5dknR9685737l8+XKx/YyKilLt2rV9zq1v1KiRT9sZGRlF9t99GQ8dOqRmzZp5XC/B28GDB5WZmanY2FjVqFHD43Hx4kVrX+vevbuGDBmiV199VdWrV9egQYN8bg/49NNPq2nTpurbt6/i4uKsD88CERcX53N3AO/1npGRocaNG/vUK+1dFwAAhUi6AcAmGzdu1MmTJ7V06VI1adLEegwfPlySrG98v/nmG9WuXdvjcfTo0YDm4bpwVSAHw3FxcWrWrJm2bNnid3rFihXVtWtXzZkzRy+//LLOnz+vv//975Kk5s2bS5J+/PHHgPqVnJysK1euaNasWR7L/txzz0lSQN927969W3/9618Dmp8kDR8+XIcPH9Z7772nOnXqaObMmWrVqpW1DDfaM888owMHDuhPf/qTwsPDNX36dLVo0UK7du0q9nWub1W3bNmilJQU3XvvvYqMjLSS7osXL2rXrl0e31Jfi6CgIL/lpfmgo6xcz7rz3nc+/fTTa+qD3VcqLygoUGxsrPULFO/Ha6+9Jqnwm/Fly5bp22+/1eTJk3X8+HGNGzdO7du3tz5ki42N1e7du7V69WoNHDhQmzZtUt++fYu8M4C78rTeAeBOQdINADZJTExUbGyskpOTfR4JCQlauXKlLl++rLZt2/ocgPu70Jg/rnuDe/8stij5+fnWgXtx7rvvPkmFPxGWpPj4eEnSJ598EtB8EhMT1bp1a7/L/vDDD2vJkiXFvn7kyJG6++679eqrr5YqGahdu7aefvpprVq1Sunp6apWrZreeOMNa3pR9/9u0KCBDh48qIKCAo/y/fv3W9PdHTx40KeNAwcO+FwgrXHjxnr++ef1xRdfaO/evcrNzdWsWbOKXYb69eurfv36SklJUUpKipVcd+vWTUeOHFFycrKcTqe6detWbDt23evcpaQxcI1ZamqqT739+/erevXqioyMtMqudd157zve+4J3Py9evKiTJ08GdO/yBg0aFNl/92Vs3LixUlNTi/w5uKvOuXPn1KVLF+tXKO6Ptm3betS///779cYbb2jHjh1KTEzUTz/9pKVLl1rTQ0NDFR8fr7lz5+rQoUOaOHGiFi9erLS0tBKXK5DlPnTokM++VxZtA8CdiKQbAGxw+fJlrVixQgMGDNDQoUN9HpMnT1ZWVpZWr16tKlWq+ByAB3J/3SVLlujDDz9U586d1atXrxLrHzhwQKmpqR4H966fuHtzne/q+mlt586d9eijj+rDDz/0e651bm6u/umf/kmSdPToUW3ZskXDhw/3u+xjx45VWlqatm3bVmRf3b/tdl05vThOp1OZmZkeZbGxsapTp47Hz3IjIyN96klSv379dOrUKY9vSfPz8/Xee+8pKipK3bt396i/atUqHT9+3Hq+fft2bdu2zbr6c3Z2tnJycjxe07hxY1WqVMmjP0Xp2rWrNm7cqO3bt1tJd7t27VSpUiX9+7//uyIiItS+ffti23AltL/++muJ87sWJY1B7dq11a5dOy1atMijD3v37tUXX3yhfv36Sbr+dee979SuXdtj+vz58z2S4Xnz5ik/P9/qZ3H69eun7du369tvv7XKLl26pPnz56thw4Zq2bKlpMIr1Z89e1Zz5szxacOVuA4fPlxOp1Ovv/66T538/HxrjM6fP++T7LZr106SrPE4d+6cx/QKFSpYV7sPZPsqSZ8+fXT8+HGPfS8nJ0cffPDBdbcNAHcibhkGADZYvXq1srKyPC5E5O7+++9XjRo1lJiY6HGv5aIsW7ZMUVFRys3N1fHjx7Vu3Tp9/fXXatu2rZKTk33q5+fnW99KFxQU6MiRI/rzn/+sgoICvfLKK1a9QYMGqVGjRoqPj1fjxo116dIlbdiwQX/729/UoUMH6xtuqfAWUb1799bgwYMVHx+vXr16KTIyUgcPHtTSpUt18uRJvf3221qyZImMMUUue79+/RQcHKzExER16tSpyGV+8skn9frrr2v37t0ljk9WVpbi4uI0dOhQtW3bVlFRUdqwYYO+//57j2+W27dvr08//VTPPfecOnTooKioKMXHx2vChAl6//33NWbMGP3www9q2LChli1bpq+//lqzZ8/2OWf+7rvv1oMPPqg//OEPunLlimbPnq1q1arphRdekFT4AUevXr00fPhwtWzZUsHBwVq5cqVOnz6tESNGlLg8Xbt2VWJiohwOh/Vz86CgID3wwANat26devTo4ffe6u7atWunoKAgvfnmm8rMzFRYWJgeeughj/uvX4+SxkCSZs6cqb59+6pz584aP368dcuwypUra8aMGZKuf92VJDc311oXqampmjt3rh588MEit093f/zjH5WUlKS+fftq6tSpqlq1qhYtWqT09HQtX77cOqd+1KhRWrx4sZ577jnrgxLXvvT0009r0KBB6t69uyZOnKg//elP2r17t3r37q2QkBAdPHhQycnJevfddzV06FAtWrRIc+fO1WOPPabGjRsrKytLH3zwgaKjo60PKn73u9/p//7v//TQQw8pLi5OGRkZeu+999SuXTuP28Jdq4kTJ2rOnDlKSEjQtGnTVLt2bSUmJlofBtr9KwoAuO3cvAunA8Dtwd8tw+Lj4014eLi5dOlSka8bM2aMCQkJMWfPni2yjut2Wa5HeHi4iYuLMwMGDDALFy40OTk5Pq/xd8uw6Oho06tXL7NhwwaPuklJSWbEiBGmcePGJiIiwoSHh5uWLVual156ybpNlrvs7Gzz9ttvmw4dOpioqCgTGhpqmjRpYqZMmWLS0tKMMca0adPG1K9fv9gx69Gjh4mNjTV5eXketwzz5rrtk0q4ZdiVK1fMP//zP5u2bduaSpUqmcjISNO2bVszd+5cj3oXL140TzzxhImJiTGSPG5Bdfr0aTN27FhTvXp1Exoaatq0aWM++ugjj9e793XWrFmmXr16JiwszHTt2tXj9l1nz541kyZNMs2bNzeRkZGmcuXKplOnTuYvf/lLsePi8tNPPxlJpkWLFh7l//Zv/2YkmenTp/u8xvuWYcYY88EHH5i77rrLBAUFedzqqUGDBqZ///4+bXTv3t1079692L4FOgYuGzZsMF26dDEREREmOjraxMfHm3379lnTy2Ld+ePadr766iszYcIEU6VKFRMVFWWefPJJj1uYFTcexhhz6NAhM3ToUBMTE2PCw8NNx44dzZo1a3zqZWdnm5deesk0atTIhISEmFq1apmhQ4eaQ4cOedSbP3++ad++vYmIiDCVKlUybdq0MS+88II5ceKEMcaYnTt3moSEBFO/fn0TFhZmYmNjzYABA8yOHTusNpYtW2Z69+5tYmNjTWhoqKlfv76ZOHGiOXnypFWnqFuGtWrVyqfvo0eP9hnPw4cPm/79+5uIiAhTo0YN8/zzz5vly5cbSea7777zP+gAAL8cxnDlDAAAAnHkyBE1atRIM2fOtH5Of6e5Vcbg448/1tixY/X9999b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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", - "\n", - "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_tadb, ax=ax_barcode, stop_color='red', set_xlim=False, timestamp='unix_ts')\n", - "plt.title(\"TA-DBSCAN stops with post-processing\")\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "9c2b278a", - "metadata": {}, - "source": [ - "### Grid-Based" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "62555a1b", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAACMCAYAAABh9MpJAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8ekN5oAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAf0UlEQVR4nO3deXRU9f3/8dckk8m+sCSWyFYwrCL8vojSooKagobFVhFMwUKpHkQE7TmtonXDys+D39qjglCkgFplEUQPxpZzsA0uHJUlRI9VItqEqoAQCFkJ2T6/P/jd68xkJpkslwzx+ThnDsy9n/v5vO/nfu7n3ndmcxljjAAAAAAAQLuL6OgAAAAAAADorEi6AQAAAABwCEk3AAAAAAAOIekGAAAAAMAhJN0AAAAAADiEpBsAAAAAAIeQdAMAAAAA4BCSbgAAAAAAHELSDQAAAACAQ0i6AQA/GLNnz1bfvn2bLVdUVCSXy6UXXnjB8Zg6St++fTV79uyODgMAgE6PpBsAEPYKCwt11113acCAAYqLi1NcXJyGDBmi+fPn65NPPumwuHbu3CmXy+Xz6Nq1q0aPHq1XXnmlw+I6lz777DM9+uijKioq6uhQAAAIS+6ODgAAgKbk5ORo+vTpcrvdmjFjhoYPH66IiAgdOHBAW7du1cqVK1VYWKg+ffo0W9fq1avV0NDQ7jEuXLhQo0aNkiSdOHFCmzZt0syZM3Xq1CnNnz+/3dsLJ5999pkWL16scePGhfQuAgAAfmhIugEAYeurr77SLbfcoj59+uif//ynevTo4bN+6dKlWrFihSIimn7jVmVlpeLj4xUVFeVInFdeeaWmTp1qP583b5769eun9evXd/qkGwAANI23lwMAwtaTTz6pyspKrVu3rlHCLUlut1sLFy5Ur1697GWzZ89WQkKCvvrqK2VlZSkxMVEzZsyw1/m/Gnvq1CnNnj1bycnJSklJ0axZs3Tq1Kk2xe3xeNSlSxe53b5/2163bp2uueYapaWlKTo6WkOGDNHKlSsbbb93715NmDBB3bt3V2xsrH784x9rzpw5PmUaGhr09NNPa+jQoYqJidEFF1yguXPnqqSkxKecMUaPP/64evbsqbi4OF199dX697//HfK+bNy4USNHjlRiYqKSkpI0bNgwPfPMM5KkF154QTfffLMk6eqrr7bfYr9z5057+xUrVmjo0KGKjo5Wenq65s+f36h/x40bp4svvlj79u3TT3/6U3uf//KXvzSKZ9myZRo6dKji4uLUpUsXXXrppVq/fn3I+wMAwLnGK90AgLCVk5Ojiy66SJdffnmLtqurq9OECRN0xRVX6E9/+pPi4uICljPG6IYbbtD777+vO+64Q4MHD9brr7+uWbNmtai98vJyFRcXS5JOnjyp9evX69NPP9WaNWt8yq1cuVJDhw7VlClT5Ha79eabb+rOO+9UQ0OD/Yr4sWPHNH78eKWmpmrRokVKSUlRUVGRtm7d6lPX3Llz9cILL+jXv/61Fi5cqMLCQi1fvlz79+/Xrl277Ff1H374YT3++OPKyspSVlaW8vLyNH78eNXU1DS7Xzt27FB2drauvfZaLV26VJL0+eefa9euXbr77rt11VVXaeHChXr22Wf1wAMPaPDgwZJk//voo49q8eLFyszM1Lx581RQUKCVK1dqz549PjFKUklJibKysjRt2jRlZ2fr1Vdf1bx58+TxeOw/OKxevVoLFy7U1KlTdffdd6u6ulqffPKJPvroI/3yl79s0TEDAOCcMQAAhKHS0lIjyfz85z9vtK6kpMQcP37cflRVVdnrZs2aZSSZRYsWNdpu1qxZpk+fPvbzN954w0gyTz75pL2srq7OXHnllUaSWbduXZMx5ubmGkmNHhEREWbJkiWNynvHaZkwYYLp16+f/fz11183ksyePXuCtvvee+8ZSeaVV17xWb59+3af5ceOHTMej8dMnDjRNDQ02OUeeOABI8nMmjWryf27++67TVJSkqmrqwtaZvPmzUaSyc3N9VlutT1+/HhTX19vL1++fLmRZNauXWsvGzt2rJFknnrqKXvZmTNnzIgRI0xaWpqpqakxxhhzww03mKFDhzYZMwAA4Ya3lwMAwlJZWZkkKSEhodG6cePGKTU11X4899xzjcrMmzev2Tb+/ve/y+12+5SNjIzUggULWhTrww8/rB07dmjHjh3atGmTsrOz9Yc//MF+G7YlNjbW/n9paamKi4s1duxY/ec//1FpaakkKSUlRdLZV/lra2sDtrd582YlJyfrZz/7mYqLi+3HyJEjlZCQoNzcXEnS22+/rZqaGi1YsEAul8ve/p577glpv1JSUlRZWakdO3aE2hU2q+177rnH5zP3t99+u5KSkvTWW2/5lHe73Zo7d6793OPxaO7cuTp27Jj27dtnx/PNN99oz549LY4HAICOQtINAAhLiYmJkqSKiopG61atWqUdO3bo5ZdfDrit2+1Wz549m23j0KFD6tGjR6PEfuDAgT7PT58+raNHj/o8vA0bNkyZmZnKzMzUtGnT9PLLL2vSpElatGiRjh8/bpfbtWuXMjMzFR8fr5SUFKWmpuqBBx6QJDvpHjt2rG666SYtXrxY3bt31w033KB169bpzJkzdj0HDx5UaWmp0tLSfP74kJqaqoqKCh07dszeP0nKyMjwiTc1NVVdunRptn/uvPNODRgwQNdff7169uypOXPmaPv27c1u5922f196PB7169fPXm9JT09XfHy8z7IBAwZIkv1zZPfdd58SEhJ02WWXKSMjQ/Pnz9euXbtCigcAgI5C0g0ACEvJycnq0aOHPv3000brLr/8cmVmZmrMmDEBt42Ojm72G81bYtOmTerRo4fPoznXXnutqqurtXv3bklnv4n92muvVXFxsf785z/rrbfe0o4dO/Tb3/5WkuyfMnO5XNqyZYs++OAD3XXXXfr22281Z84cjRw50v4DRENDg9LS0uxX1/0fjz32WLvsd1pamvLz87Vt2zZNmTJFubm5uv7661v8mff2MnjwYBUUFGjjxo264oor9Nprr+mKK67QI4880iHxAAAQCr5IDQAQtiZOnKi//vWv2r17ty677LJ2r9/6KbKKigqfV7sLCgp8yk2YMKHFb7Guq6uT9P0r9W+++abOnDmjbdu2qXfv3nY5663g/kaPHq3Ro0dryZIlWr9+vWbMmKGNGzfqtttuU//+/fX2229rzJgxPm9ZD7R/0tlXxvv162cvP378eKNvOQ/G4/Fo8uTJmjx5shoaGnTnnXdq1apVeuihh3TRRRf5vG09UNsFBQU+bdfU1KiwsFCZmZk+5Q8fPmz/tJvliy++kCSfb5yPj4/X9OnTNX36dNXU1OjGG2/UkiVLdP/99ysmJiakfQIA4FzilW4AQNi69957FRcXpzlz5ui7775rtN4Y06b6s7KyVFdX5/OzXfX19Vq2bJlPuR49ethvH7cezcnJyZEkDR8+XNLZz4r7x1xaWqp169b5bFdSUtJov0aMGCFJ9lvMp02bpvr6ev3xj39s1G5dXZ39k1yZmZmKiorSsmXLfOp8+umnm41fkk6cOOHzPCIiQpdccolPLFaS7P8zYJmZ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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", - "\n", - "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_gb, ax=ax_barcode, stop_color='green', set_xlim=False, timestamp='unix_ts')\n", - "plt.title(\"Grid-Based stops\")\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "779c39db", - "metadata": {}, - "source": [ - "### HDBSCAN" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "fa70719e", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/carolinechen/Desktop/cs/nomad/nomad/io/base.py:104: UserWarning: Trajectory column 'unix_ts' specified for 'timestamp' not found in DataFrame.\n", - " warnings.warn(f\"Trajectory column '{value}' specified for '{key}' not found in DataFrame.\")\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAACYCAYAAAD5s4rEAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8ekN5oAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAnYklEQVR4nO3deXwV5b3H8e/JSggJYQlLIILsS9ikrmFr1VJkq4JAKIKA4ALc1qr1VmuBVqrgdaHghhctXiFCVIpCvb1yAQEFBBEU2QQJVZAQIIRAEpKcPPeP3BnOluQQMiTEz/v1Oq/kzDzzrHOemd9ZZlzGGCMAAAAAAFDpQqq6AgAAAAAA1FQE3QAAAAAAOISgGwAAAAAAhxB0AwAAAADgEIJuAAAAAAAcQtANAAAAAIBDCLoBAAAAAHAIQTcAAAAAAA4h6AYAAAAAwCEE3QAAXAHWrVsnl8uldevWBZ32nXfecb5iqDJ33323WrZsWdXVAACUg6AbACrR3/72N7lcLm3bti3g+n79+ikpKclrWcuWLeVyueRyuRQSEqK4uDh16dJFkydP1pYtWwLmY6W3HtHR0erUqZOefPJJ5ebmeqUtKCjQ3Llz1aNHD8XGxiouLk6dO3fW5MmTtXfvXr+8Dx48qHvvvVetWrVSrVq1FBsbq+TkZM2dO1d5eXl+6d1utxISEuRyufThhx8GrO+MGTPkcrnUuHFjv/pZfTBo0KCA216M3NxczZgxI6jAtCZYsmSJXnjhhaquRqX4sY0dAODHI6yqKwAAkLp3766HHnpIkpSTk6M9e/YoLS1Nr732mh588EE999xzftvceuutGjt2rCTp7Nmz2rBhg5544gnt3LlTaWlpdrphw4bpww8/VEpKiiZNmqTCwkLt3btXK1eu1E033aQOHTrYaVetWqU777xTkZGRGjt2rJKSklRQUKCNGzfqkUce0ddff60FCxZ41WPNmjX64Ycf1LJlSy1evFgDBgwotZ3Hjx/Xyy+/bLe1suXm5mrmzJmSSt7gqEn69OmjvLw8RURE2MuWLFmiXbt26Te/+U3VVayS1OSxc8prr72m4uLiqq4GAKAcBN0AUA00a9ZMY8aM8Vo2e/ZsjR49Ws8//7zatm2r+++/32t9u3btvLa57777VFBQoPfee0/5+fmqVauWtm7dqpUrV2rWrFl67LHHvLafP3++Tp8+bT8/dOiQRo0apRYtWmjNmjVq2rSpvW7KlCk6cOCAVq1a5Vf3t956S9dcc43GjRunxx57TOfOnVN0dHTAdnbv3l3PPPOMHnjgAUVFRQXdP5BCQkJUq1atqq5GjZGfn6+IiAiFhFy5X/oLDw+v6ioAAIJw5R5pAKCGi4qK0n/913+pfv36mjVrlowx5W7TpEkTuVwuhYWVvKd68OBBSVJycrJf2tDQUDVo0MB+PmfOHJ09e1YLFy70Crgtbdq00a9//WuvZXl5eVq+fLlGjRqlESNGKC8vTytWrCi1fn/84x+VkZGhl19+udy2BLJt2zb1799fDRs2VFRUlK6++mpNmDBBkpSenq74+HhJ0syZM+2v3s+YMcPefs2aNerdu7eio6MVFxenoUOHas+ePV5lWF+F37t3r0aMGKHY2Fg1aNBAv/71r5Wfn++V9qOPPlKvXr0UFxenOnXqqH379n5vbvi64447dM0113gtGzx4sFwul95//3172ZYtW7y+su/7m+5+/fpp1apVOnz4sN1W39/3FhcXa9asWWrevLlq1aqlm2++WQcOHCi7ky+yD4qKivTnP/9ZrVu3VmRkpFq2bKnHHntM58+f90p3qWMXiPVzjc8//1w33XSTne8rr7zilc7qu7ffflt/+MMf1KxZM9WuXVtnzpyRJKWlpalnz56KiopSw4YNNWbMGB05csSvPKs/4uPjFRUVpfbt2+vxxx/3SnPkyBFNmDBBjRs3VmRkpDp37qzXX3/dL6958+apc+fOql27turVq6ef/OQnWrJkib0+JydHv/nNb9SyZUtFRkaqUaNGuvXWW7V9+3Y7je9vutPT0+VyufQf//EfWrBggT0m1157rbZu3epXh7S0NHXq1Em1atVSUlKSli9fzu/EAcABfNINAA7Izs7WiRMn/JYXFhZeVD516tTR7bffroULF2r37t3q3LmzvS4/P98u49y5c/rkk0+0aNEijR492g66W7RoIUlavHixkpOT7eWBfPDBB2rVqpVuuummoOv3/vvv6+zZsxo1apSaNGmifv36afHixRo9enTA9L1799bPfvYzzZkzR/fff/9Ffdp9/Phx/fznP1d8fLz+/d//XXFxcUpPT9d7770nSYqPj9fLL7+s+++/X7fffrvuuOMOSVLXrl0lSatXr9aAAQPUqlUrzZgxQ3l5eZo3b56Sk5O1fft2v0BjxIgRatmypZ566ilt3rxZf/3rX5WVlaU333xTkvT1119r0KBB6tq1q/70pz8pMjJSBw4c0CeffFJmO3r37q0VK1bozJkzio2NlTFGn3zyiUJCQrRhwwYNGTJEkrRhwwaFhIQEfMNEkh5//HFlZ2fr+++/1/PPPy+pZH/x9PTTTyskJEQPP/ywsrOzNWfOHP3qV78q9VoBvsrrA0m65557tGjRIg0fPlwPPfSQtmzZoqeeekp79uzR8uXLJV362JUlKytLt912m0aMGKGUlBQtW7ZM999/vyIiIuyg3vLnP/9ZERERevjhh3X+/HlFRETob3/7m8aPH69rr71WTz31lDIyMjR37lx98skn+uKLLxQXFydJ+vLLL9W7d2+Fh4dr8uTJatmypQ4ePKgPPvhAs2bNkiRlZGTohhtukMvl0tSpUxUfH68PP/xQEydO1JkzZ+yfAbz22mv6t3/7Nw0fPtx+I+PLL7/Uli1b7NfOfffdp3feeUdTp05Vp06ddPLkSW3cuFF79uzxe9PG15IlS5STk6N7771XLpdLc+bM0R133KFvv/3W/nR81apVGjlypLp06aKnnnpKWVlZmjhxopo1a1ZunwMALpIBAFSaN954w0gq89G5c2evbVq0aGEGDhxYap7PP/+8kWRWrFhhLyst71/+8pcmPz/fTldcXGz69u1rJJnGjRublJQU8+KLL5rDhw97lZGdnW0kmaFDh15UewcNGmSSk5Pt5wsWLDBhYWHm+PHjXummT59uJJnMzEzz8ccfG0nmueeeC7oPjDFm+fLlRpLZunVrqWkyMzONJDN9+nS/dd27dzeNGjUyJ0+etJft3LnThISEmLFjx/rVdciQIV7bP/DAA0aS2blzpzHmwrhkZmaWWW9fW7duNZLMP/7xD2OMMV9++aWRZO68805z/fXX2+mGDBlievToYT9fu3atkWTWrl1rLxs4cKBp0aKFXxlW2o4dO5rz58/by+fOnWskma+++qrMOgbbBzt27DCSzD333OOV7uGHHzaSzJo1a4wxlz52pbH27WeffdZedv78eXusCwoKjDEX+qNVq1YmNzfXTltQUGAaNWpkkpKSTF5enr185cqVRpL54x//aC/r06ePiYmJ8XvtFBcX2/9PnDjRNG3a1Jw4ccIrzahRo0zdunXtsocOHeo3D/iqW7eumTJlSplpxo0b5zX+hw4dMpJMgwYNzKlTp+zlK1asMJLMBx98YC/r0qWLad68ucnJybGXrVu3zkgKuE8BACqOr5cDgANefPFFffTRR36PYD6582V9epmTk+O1fOjQoXa+K1as0O9//3v993//t0aPHm1/Fd3lcumf//ynnnzySdWrV0+pqamaMmWKWrRooZEjR9q/6ba+ZhsTExN0vU6ePKl//vOfSklJsZcNGzZMLpdLy5YtK3W7Pn366Kc//anmzJkT8GropbE+cVy5cuVFf2Pghx9+0I4dO3T33Xerfv369vKuXbvq1ltv1T/+8Q+/baZMmeL1fNq0aZJkp7Xqs2LFiou6mFWPHj1Up04drV+/XlLJJ9rNmzfX2LFjtX37duXm5soYo40bN6p3794X1U5f48eP97rwmpXft99+G9T25fWB9fe3v/2tVzrrQnnWNQAuZezKExYWpnvvvdd+HhERoXvvvVfHjx/X559/7pV23LhxXt+u2LZtm44fP64HHnjA6/fyAwcOVIcOHez6Z2Zmav369ZowYYKuuuoqrzxdLpckyRijd999V4MHD5YxRidOnLAf/fv3V3Z2tv3V8Li4OH3//fcBv/JtiYuL05YtW3T06NGL7pORI0eqXr169nPfcT969Ki++uorjR071uvbEX379lWXLl0uujwAQNkIugHAAdddd51uueUWv4fniXCwzp49K8k/IG7evLmd75AhQ/SXv/xFTz75pN577z2tXLnSThcZGanHH39ce/bs0dGjR5WamqobbrhBy5Yt09SpUyVJsbGxkvwD+7IsXbpUhYWF6tGjhw4cOKADBw7o1KlTuv7667V48eIyt50xY4aOHTvm99vbsvTt21fDhg3TzJkz1bBhQw0dOlRvvPGG32+HAzl8+LAkqX379n7rOnbsqBMnTujcuXNey9u2bev1vHXr1goJCVF6erqkksAmOTlZ99xzjxo3bqxRo0Zp2bJl5QbgoaGhuvHGG7VhwwZJJUF379691atXL7ndbm3evFm7d+/WqVOnLjno9g0Qrf0vKysrqO3L64PDhw8rJCREbdq08UrXpEkTxcXF2f1+KWOXl5enY8eOeT08JSQk+F24r127dpJk19Ny9dVXez0va7/o0KGDvd4KVn1v9+cpMzNTp0+f1oIFCxQfH+/1GD9+vKSSr9lL0qOPPqo6derouuuuU9u2bTVl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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 3, + "id": "98cdda1f", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:23.475346Z", + "iopub.status.busy": "2025-11-24T18:33:23.475346Z", + "iopub.status.idle": "2025-11-24T18:33:23.482904Z", + "shell.execute_reply": "2025-11-24T18:33:23.482904Z" } - ], - "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", - "\n", - "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_hdb, ax=ax_barcode, stop_color='blue', set_xlim=False, timestamp='unix_ts')\n", - "fig.suptitle(\"HDBSCAN stops with post-processing\")\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "c6351f6e", - "metadata": {}, + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Summary of Single-User Performance\n", - "Lachesis execution time: 0.06618499755859375 seconds\n", - "TA-DBSCAN execution time: 0.13709473609924316 seconds\n", - "Grid-Based execution time: 0.16486215591430664 seconds\n", - "HDBSCAN execution time: 2.95216703414917 seconds\n" + "Lachesis execution time: 0.02721381187438965 seconds\n", + "TA-DBSCAN execution time: 0.012791156768798828 seconds\n", + "Grid-Based execution time: 0.022524595260620117 seconds\n", + "HDBSCAN execution time: 0.3206779956817627 seconds\n" ] } ], @@ -324,25 +212,31 @@ }, { "cell_type": "code", - "execution_count": 9, - "id": "d0431a3c", - "metadata": {}, + "execution_count": 4, + "id": "41e9a154", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:23.482904Z", + "iopub.status.busy": "2025-11-24T18:33:23.482904Z", + "iopub.status.idle": "2025-11-24T18:33:23.492088Z", + "shell.execute_reply": "2025-11-24T18:33:23.492088Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Runtime Disaggregation\n", - "Lachesis clustering time: 0.06618499755859375 seconds\n", + "Lachesis clustering time: 0.02721381187438965 seconds\n", "--------------------------------\n", - "TA-DBSCAN pre-processing time: 0.06432580947875977 seconds\n", - "TA-DBSCAN clustering time: 0.11364507675170898 seconds\n", - "TA-DBSCAN post-processing time: 0.02340412139892578 seconds\n", + "TA-DBSCAN clustering time: 0.009821414947509766 seconds\n", + "TA-DBSCAN post-processing time: 0.0029697418212890625 seconds\n", "--------------------------------\n", - "Grid-Based clustering time: 0.16486215591430664 seconds\n", + "Grid-Based clustering time: 0.022524595260620117 seconds\n", "--------------------------------\n", - "HDBSCAN clustering time: 2.930790901184082 seconds\n", - "HDBSCAN post-processing time: 0.02130913734436035 seconds\n" + "HDBSCAN clustering time: 0.3206779956817627 seconds\n", + "HDBSCAN post-processing time: 0.0 seconds\n" ] } ], @@ -350,7 +244,6 @@ "print(\"Runtime Disaggregation\")\n", "print(f\"Lachesis clustering time: {execution_time_lachesis} seconds\")\n", "print(\"--------------------------------\")\n", - "print(f\"TA-DBSCAN pre-processing time: {time_pre_tadbscan} seconds\")\n", "print(f\"TA-DBSCAN clustering time: {clustering_time_tadbscan} seconds\")\n", "print(f\"TA-DBSCAN post-processing time: {post_time_tadbscan} seconds\")\n", "print(\"--------------------------------\")\n", @@ -362,7 +255,7 @@ }, { "cell_type": "markdown", - "id": "2de448bd", + "id": "5c9ee070", "metadata": {}, "source": [ "## Pings vs Runtime" @@ -370,36 +263,83 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "dc99ecd5", - "metadata": {}, + "execution_count": 5, + "id": "62ed6a42", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:23.492088Z", + "iopub.status.busy": "2025-11-24T18:33:23.492088Z", + "iopub.status.idle": "2025-11-24T18:33:23.520075Z", + "shell.execute_reply": "2025-11-24T18:33:23.520075Z" + } + }, "outputs": [], "source": [ - "traj = loader.sample_from_file(filepath_root, frac_users=0.1, format='parquet', traj_cols=tc, seed=10) # try frac_users = 0.1\n", + "traj = loader.sample_from_file(filepath_root, frac_users=0.1, format='parquet', traj_cols=tc, seed=10)\n", "\n", "# H3 cells for grid_based stop detection method\n", - "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, x='dev_x', y='dev_y', data_crs='EPSG:3857')\n", + "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, traj_cols=tc, data_crs='EPSG:3857')\n", "pings_per_user = traj['gc_identifier'].value_counts()" ] }, { "cell_type": "code", - "execution_count": 12, - "id": "4609ebe7", - "metadata": {}, + "execution_count": 6, + "id": "baafc0b8", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:23.520075Z", + "iopub.status.busy": "2025-11-24T18:33:23.520075Z", + "iopub.status.idle": "2025-11-24T18:33:24.093854Z", + "shell.execute_reply": "2025-11-24T18:33:24.093854Z" + } + }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - " 0%| | 0/35 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 7, + "id": "8e8546e8", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:24.093854Z", + "iopub.status.busy": "2025-11-24T18:33:24.093854Z", + "iopub.status.idle": "2025-11-24T18:33:24.338240Z", + "shell.execute_reply": "2025-11-24T18:33:24.338240Z" } - ], + }, + "outputs": [], "source": [ "import seaborn as sns\n", "\n", @@ -487,13 +400,16 @@ "palette = dict(zip(algos, sns.color_palette(n_colors=len(algos))))\n", "\n", "fig, ax = plt.subplots(figsize=(5, 5))\n", - "sns.scatterplot(data=metrics, x='n_pings', y='execution_time', hue='algo', ax=ax)\n", + "sns.scatterplot(data=results, x='n_pings', y='execution_time', hue='algo', ax=ax)\n", "ax.set_title('n_pings vs execution_time')\n", "plt.show()" ] } ], "metadata": { + "jupytext": { + "formats": "ipynb,py:percent" + }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", @@ -509,7 +425,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.0" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/examples/benchmarking_of_stop_detection_algorithms.py b/examples/benchmarking_of_stop_detection_algorithms.py new file mode 100644 index 00000000..5972b6a1 --- /dev/null +++ b/examples/benchmarking_of_stop_detection_algorithms.py @@ -0,0 +1,198 @@ +# --- +# jupyter: +# jupytext: +# formats: ipynb,py:percent +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: Python 3 (ipykernel) +# language: python +# name: python3 +# --- + +# %% [markdown] +# # Comparing runtimes of different stop detection algorithms on toy datasets + +# %% [markdown] +# Here we compare the runtimes of four different stop detection algorithms: Lachesis, grid-based, temporal DBSCAN, and HDBSCAN. + +# %% +# %matplotlib inline +import matplotlib +matplotlib.use('TkAgg') +import matplotlib.pyplot as plt +plt.ion() + +# Imports +import nomad.io.base as loader +import geopandas as gpd +from shapely.geometry import box +import pandas as pd +import numpy as np +from nomad.stop_detection.viz import plot_stops_barcode, plot_pings, plot_stops, plot_time_barcode +import nomad.stop_detection.dbscan as DBSCAN +import nomad.stop_detection.lachesis as LACHESIS +import nomad.stop_detection.grid_based as GRID_BASED +import nomad.stop_detection.hdbscan as HDBSCAN +import nomad.filters as filters +import nomad.stop_detection.postprocessing as post +import time +from tqdm import tqdm + +# Load data +import nomad.data as data_folder +from pathlib import Path +data_dir = Path(data_folder.__file__).parent +city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet') +outer_box = box(*city.total_bounds).buffer(15, join_style='mitre') + +filepath_root = 'gc_data_long/' +tc = { + "user_id": "gc_identifier", + "timestamp": "unix_ts", + "x": "dev_x", + "y": "dev_y", + "ha":"ha", + "date":"date"} + +users = ['admiring_brattain'] +traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc) + +# Lachesis (sequential stop detection) +start_time = time.time() +stops = LACHESIS.lachesis(traj, delta_roam=20, dt_max = 60, dur_min=5, complete_output=True, keep_col_names=True, traj_cols=tc) +execution_time_lachesis = time.time() - start_time +print(f"Lachesis execution time: {execution_time_lachesis} seconds") + +# Density based stop detection (Temporal DBSCAN) +start_time = time.time() +user_data_tadb = traj.assign(cluster=DBSCAN.ta_dbscan_labels(traj, time_thresh=240, dist_thresh=15, min_pts=3, traj_cols=tc)) +clustering_time_tadbscan = time.time() - start_time +start_time_post = time.time() +cluster_labels_tadb = post.remove_overlaps(user_data_tadb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) +execution_time_tadbscan = time.time() - start_time +post_time_tadbscan = time.time() - start_time_post +print(f"TA-DBSCAN execution time: {execution_time_tadbscan} seconds") +print(f"TA-DBSCAN clustering time: {clustering_time_tadbscan} seconds") +print(f"TA-DBSCAN post-processing time: {post_time_tadbscan} seconds") + +# Grid-based +start_time = time.time() +traj['h3_cell'] = filters.to_tessellation(traj, index="h3", res=10, traj_cols=tc, data_crs='EPSG:3857') +stops_gb = GRID_BASED.grid_based(traj, time_thresh=240, complete_output=True, traj_cols=tc, location_id='h3_cell') +execution_time_grid = time.time() - start_time +print(f"Grid-Based execution time: {execution_time_grid} seconds") + +# HDBSCAN +start_time = time.time() +user_data_hdb = traj.assign(cluster=HDBSCAN.hdbscan_labels(traj, time_thresh=240, min_pts=3, min_cluster_size=2, traj_cols=tc)) +clustering_time_hdbscan = time.time() - start_time +start_time_post = time.time() +cluster_labels_hdb = post.remove_overlaps(user_data_hdb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) +execution_time_hdbscan = time.time() - start_time +post_time_hdbscan = time.time() - start_time_post +print(f"HDBSCAN execution time: {execution_time_hdbscan} seconds") +print(f"HDBSCAN clustering time: {clustering_time_hdbscan} seconds") +print(f"HDBSCAN post-processing time: {post_time_hdbscan} seconds") + +# %% [markdown] +# ## Summary of Single-User Performance + +# %% [markdown] +# ### Lachesis + +# %% +fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5), + gridspec_kw={'height_ratios':[10,1]}) + +gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3') +city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c') + +plot_stops(stops, ax=ax_map, cmap='Reds') +plot_pings(traj, ax=ax_map, s=6, point_color='black', cmap='twilight', traj_cols=tc) +ax_map.set_axis_off() + +plot_time_barcode(traj[tc['timestamp']], ax=ax_barcode, set_xlim=True) +plot_stops_barcode(stops, ax=ax_barcode, cmap='Reds', set_xlim=False, timestamp='unix_ts') + +plt.tight_layout(pad=0.1) +plt.show() + +# %% +print("Summary of Single-User Performance") +print(f"Lachesis execution time: {execution_time_lachesis} seconds") +print(f"TA-DBSCAN execution time: {execution_time_tadbscan} seconds") +print(f"Grid-Based execution time: {execution_time_grid} seconds") +print(f"HDBSCAN execution time: {execution_time_hdbscan} seconds") + +# %% +print("Runtime Disaggregation") +print(f"Lachesis clustering time: {execution_time_lachesis} seconds") +print("--------------------------------") +print(f"TA-DBSCAN clustering time: {clustering_time_tadbscan} seconds") +print(f"TA-DBSCAN post-processing time: {post_time_tadbscan} seconds") +print("--------------------------------") +print(f"Grid-Based clustering time: {execution_time_grid} seconds") +print("--------------------------------") +print(f"HDBSCAN clustering time: {clustering_time_hdbscan} seconds") +print(f"HDBSCAN post-processing time: {post_time_hdbscan} seconds") + +# %% [markdown] +# ## Pings vs Runtime + +# %% +traj = loader.sample_from_file(filepath_root, frac_users=0.1, format='parquet', traj_cols=tc, seed=10) + +# H3 cells for grid_based stop detection method +traj['h3_cell'] = filters.to_tessellation(traj, index="h3", res=10, traj_cols=tc, data_crs='EPSG:3857') +pings_per_user = traj['gc_identifier'].value_counts() + +# %% +# Approximately 5 minutes for 40 users +results = [] +for user, n_pings in tqdm(pings_per_user.items(), total=len(pings_per_user)): + user_data = traj.query("gc_identifier == @user") + + # For location based + start_time = time.time() + stops_gb = GRID_BASED.grid_based(user_data, time_thresh=240, complete_output=True, traj_cols=tc, location_id='h3_cell') + execution_time = time.time() - start_time + results += [pd.Series({'user':user, 'algo':'grid_based', 'execution_time':execution_time, 'n_pings':n_pings})] + + # For Lachesis + start_time = time.time() + stops_lac = LACHESIS.lachesis(user_data, delta_roam=30, dt_max=240, complete_output=True, traj_cols=tc) + execution_time = time.time() - start_time + results += [pd.Series({'user':user, 'algo':'lachesis', 'execution_time':execution_time, 'n_pings':n_pings})] + + # For TADbscan + start_time = time.time() + user_data_tadb = user_data.assign(cluster=DBSCAN.ta_dbscan_labels(user_data, time_thresh=240, dist_thresh=15, min_pts=3, traj_cols=tc)) + # - post-processing + stops_tadb = post.remove_overlaps(user_data_tadb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) + execution_time = time.time() - start_time + results += [pd.Series({'user':user, 'algo':'tadbscan', 'execution_time':execution_time, 'n_pings':n_pings})] + + # For HDBSCAN + start_time = time.time() + user_data_hdb = user_data.assign(cluster=HDBSCAN.hdbscan_labels(user_data, time_thresh=240, min_pts=3, min_cluster_size=2, traj_cols=tc)) + # - post-processing + stops_hdb = post.remove_overlaps(user_data_hdb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) + execution_time = time.time() - start_time + results += [pd.Series({'user':user, 'algo':'hdbscan', 'execution_time':execution_time, 'n_pings':n_pings})] + +results = pd.DataFrame(results) + +# %% +import seaborn as sns + +algos = ['grid_based', 'lachesis', 'tadbscan', 'hdbscan'] +palette = dict(zip(algos, sns.color_palette(n_colors=len(algos)))) + +fig, ax = plt.subplots(figsize=(5, 5)) +sns.scatterplot(data=results, x='n_pings', y='execution_time', hue='algo', ax=ax) +ax.set_title('n_pings vs execution_time') +plt.show() diff --git a/examples/generate_synthetic_pois.ipynb b/examples/generate_synthetic_pois.ipynb index c9e8d88c..1fec4f56 100644 --- a/examples/generate_synthetic_pois.ipynb +++ b/examples/generate_synthetic_pois.ipynb @@ -2,8 +2,8 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, - "id": "0eb89ec7-0bed-48fe-9005-d54c14695ff7", + "execution_count": null, + "id": "e7c495ca", "metadata": {}, "outputs": [], "source": [ @@ -15,36 +15,10 @@ }, { "cell_type": "code", - "execution_count": 3, - "id": "71e396a0-5d62-4183-9ae0-5b15c8ca9853", + "execution_count": null, + "id": "e96465b0", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\pacob\\Desktop\\Brain\\Code Development\\nomad\\nomad\\city_gen.py:271: FutureWarning: The behavior of DataFrame concatenation with empty or all-NA entries is deprecated. In a future version, this will no longer exclude empty or all-NA columns when determining the result dtypes. To retain the old behavior, exclude the relevant entries before the concat operation.\n", - " self.buildings_gdf = pd.concat([self.buildings_gdf, new_row], axis=0)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "City built and street graph computed in 0.952s; buildings=106 streets=224\n" - ] - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# Initialize city and time the build\n", "t0 = time.perf_counter()\n", @@ -73,37 +47,37 @@ "city.add_building('home', (8, 10), blocks=[(7, 10), (7, 9)])\n", "\n", "# add workplaces\n", - "city.add_building('work', (3, 4), blocks=[(4, 4), (4, 5)])\n", - "city.add_building('work', (5, 3), blocks=[(5, 4), (5, 5)])\n", - "city.add_building('work', (6, 6), geom=box(6, 4, 8, 6))\n", - "city.add_building('work', (8, 6), geom=box(8, 4, 10, 6))\n", - "city.add_building('work', (12, 6), geom=box(11, 5, 14, 6))\n", - "city.add_building('work', (12, 3), geom=box(11, 4, 14, 5))\n", - "city.add_building('work', (15, 3), geom=box(14, 4, 17, 6))\n", - "city.add_building('work', (18, 4), geom=box(17, 4, 18, 6))\n", - "city.add_building('work', (18, 6), geom=box(16, 6, 18, 8))\n", - "city.add_building('work', (15, 9), geom=box(16, 8, 17, 10))\n", - "city.add_building('work', (18, 8), geom=box(17, 8, 18, 10))\n", - "city.add_building('work', (18, 10), geom=box(16, 10, 18, 12))\n", - "city.add_building('work', (18, 13), geom=box(16, 13, 18, 15))\n", - "city.add_building('work', (18, 15), geom=box(16, 15, 18, 16))\n", - "city.add_building('work', (15, 15), geom=box(15, 16, 18, 17))\n", - "city.add_building('work', (14, 15), blocks=[(14, 16)])\n", - "city.add_building('work', (16, 18), geom=box(16, 17, 18, 18))\n", - "city.add_building('work', (15, 18), geom=box(14, 17, 16, 18))\n", - "city.add_building('work', (13, 18), geom=box(12, 16, 14, 18))\n", - "city.add_building('work', (11, 18), geom=box(10, 17, 12, 18))\n", - "city.add_building('work', (11, 15), geom=box(10, 16, 12, 17))\n", - "city.add_building('work', (8, 18), geom=box(7, 16, 9, 18))\n", - "city.add_building('work', (6, 18), geom=box(5, 17, 7, 18))\n", - "city.add_building('work', (6, 15), geom=box(5, 16, 7, 17))\n", - "city.add_building('work', (3, 16), blocks=[(4, 16), (4, 17)])\n", - "city.add_building('work', (3, 13), geom=box(4, 13, 6, 16))\n", - "city.add_building('work', (6, 12), geom=box(4, 12, 6, 13))\n", - "city.add_building('work', (3, 10), blocks=[(4, 9), (4, 10)])\n", - "city.add_building('work', (6, 9), blocks=[(5, 9), (5, 10)])\n", - "city.add_building('work', (6, 8), blocks=[(4, 8), (5, 8)])\n", - "city.add_building('work', (3, 6), geom=box(4, 6, 6, 8))\n", + "city.add_building('workplace', (3, 4), blocks=[(4, 4), (4, 5)])\n", + "city.add_building('workplace', (5, 3), blocks=[(5, 4), (5, 5)])\n", + "city.add_building('workplace', (6, 6), geom=box(6, 4, 8, 6))\n", + "city.add_building('workplace', (8, 6), geom=box(8, 4, 10, 6))\n", + "city.add_building('workplace', (12, 6), geom=box(11, 5, 14, 6))\n", + "city.add_building('workplace', (12, 3), geom=box(11, 4, 14, 5))\n", + "city.add_building('workplace', (15, 3), geom=box(14, 4, 17, 6))\n", + "city.add_building('workplace', (18, 4), geom=box(17, 4, 18, 6))\n", + "city.add_building('workplace', (18, 6), geom=box(16, 6, 18, 8))\n", + "city.add_building('workplace', (15, 9), geom=box(16, 8, 17, 10))\n", + "city.add_building('workplace', (18, 8), geom=box(17, 8, 18, 10))\n", + "city.add_building('workplace', (18, 10), geom=box(16, 10, 18, 12))\n", + "city.add_building('workplace', (18, 13), geom=box(16, 13, 18, 15))\n", + "city.add_building('workplace', (18, 15), geom=box(16, 15, 18, 16))\n", + "city.add_building('workplace', (15, 15), geom=box(15, 16, 18, 17))\n", + "city.add_building('workplace', (14, 15), blocks=[(14, 16)])\n", + "city.add_building('workplace', (16, 18), geom=box(16, 17, 18, 18))\n", + "city.add_building('workplace', (15, 18), geom=box(14, 17, 16, 18))\n", + "city.add_building('workplace', (13, 18), geom=box(12, 16, 14, 18))\n", + "city.add_building('workplace', (11, 18), geom=box(10, 17, 12, 18))\n", + "city.add_building('workplace', (11, 15), geom=box(10, 16, 12, 17))\n", + "city.add_building('workplace', (8, 18), geom=box(7, 16, 9, 18))\n", + "city.add_building('workplace', (6, 18), geom=box(5, 17, 7, 18))\n", + "city.add_building('workplace', (6, 15), geom=box(5, 16, 7, 17))\n", + "city.add_building('workplace', (3, 16), blocks=[(4, 16), (4, 17)])\n", + "city.add_building('workplace', (3, 13), geom=box(4, 13, 6, 16))\n", + "city.add_building('workplace', (6, 12), geom=box(4, 12, 6, 13))\n", + "city.add_building('workplace', (3, 10), blocks=[(4, 9), (4, 10)])\n", + "city.add_building('workplace', (6, 9), blocks=[(5, 9), (5, 10)])\n", + "city.add_building('workplace', (6, 8), blocks=[(4, 8), (5, 8)])\n", + "city.add_building('workplace', (3, 6), geom=box(4, 6, 6, 8))\n", "\n", "# add retail places\n", "city.add_building('retail', (0, 1), geom=box(1, 1, 3, 3))\n", @@ -167,7 +141,7 @@ "print(f\"City built and street graph computed in {elapsed:.3f}s; buildings={len(city.buildings_gdf)} streets={len(city.streets_gdf)}\")\n", "\n", "# Persist as GeoPackage\n", - "city.save_geopackage('synthetic_pois.gpkg')\n", + "city.save_geopackage('garden-city.gpkg')\n", "\n", "# Plot a city\n", "fig, ax = plt.subplots(figsize=(6, 6))\n", @@ -179,7 +153,7 @@ }, { "cell_type": "markdown", - "id": "4b65abc5-4e5f-4376-88ab-998caca51d57", + "id": "12f6be66", "metadata": {}, "source": [ "## Plotting a shortest path" @@ -187,8 +161,8 @@ }, { "cell_type": "code", - "execution_count": 30, - "id": "c599d0f0-5b20-4627-a015-daa7cc3e35d3", + "execution_count": null, + "id": "c8f01501", "metadata": {}, "outputs": [], "source": [ @@ -207,18 +181,6 @@ "display_name": "Python (nomad repo venv)", "language": "python", "name": "nomad-repo-venv" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.3" } }, "nbformat": 4, diff --git a/examples/generate_synthetic_trajectories.ipynb b/examples/generate_synthetic_trajectories.ipynb index 1ee4a1cc..2fd681c3 100644 --- a/examples/generate_synthetic_trajectories.ipynb +++ b/examples/generate_synthetic_trajectories.ipynb @@ -1,856 +1,247 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "2ccfc0c4", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "from datetime import datetime, timedelta\n", - "from zoneinfo import ZoneInfo\n", - "import matplotlib.pyplot as plt\n", - "plt.style.use('seaborn-v0_8-muted')\n", - "from matplotlib import cm\n", - "import geopandas as gpd\n", - "\n", - "from pyproj import Transformer\n", - "from concurrent.futures import ProcessPoolExecutor\n", - "import concurrent.futures\n", - "import multiprocessing\n", - "from multiprocessing import Pool\n", - "from functools import partial\n", - "import numpy.random as npr\n", - "import matplotlib.dates as mdates\n", - "from itertools import product\n", - "import copy\n", - "import pickle\n", - "from tqdm import tqdm\n", - "\n", - "import nomad.io.base as loader\n", - "import nomad.city_gen as cg\n", - "from nomad.city_gen import City, Building\n", - "import nomad.traj_gen as tg\n", - "from nomad.traj_gen import Agent, Population\n", - "import nomad.stop_detection.ta_dbscan as DBSCAN\n", - "import nomad.stop_detection.lachesis as Lachesis\n", - "from nomad.generation.sparsity import gen_params_target_q" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3fd6c945", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# garden city\n", - "\n", - "city_geojson = gpd.read_file('garden_city.geojson')\n", - "\n", - "city = cg.load('garden-city.pkl')" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "3e5ee980", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# synthetic philly\n", - "\n", - "city_geojson = gpd.read_file('philly.geojson')\n", - "\n", - "s3 = boto3.client('s3', region_name=\"us-east-2\")\n", - "pickle_buffer = io.BytesIO()\n", - "s3.download_fileobj(\"synthetic-philly\", \"philadelphia-city.pkl\", pickle_buffer)\n", - "pickle_buffer.seek(0)\n", - "city = pickle.load(pickle_buffer)" - ] - }, { "cell_type": "markdown", - "id": "eb7675c1", + "id": "10e2517a", "metadata": {}, "source": [ - "### Generate N agents" - ] - }, - { - "cell_type": "markdown", - "id": "b76a575b", - "metadata": {}, - "source": [ - "The following code maps our Garden City coordinates to a location in the Atlantic Ocean (Atlantis?)." + "# Synthetic Trajectory Generation with Nomad\n", + "\n", + "This notebook demonstrates how to generate realistic synthetic human mobility trajectories." ] }, { "cell_type": "code", "execution_count": null, - "id": "e21366a2", - "metadata": { - "tags": [] - }, + "id": "58d68e64", + "metadata": {}, "outputs": [], "source": [ - "def garden_city_to_lat_long(agent, sparse_traj=True, full_traj=False, diaries=True):\n", - " def project_city_blocks_to_web_mercator(df):\n", - " \"\"\"Convert (x, y) from 15m block units to Web Mercator meters via affine shift and projection.\"\"\"\n", - " transformer = Transformer.from_crs(\"EPSG:3857\", \"EPSG:4326\", always_xy=True)\n", - " df['x'] = 15 * df['x'] - 4265699\n", - " df['y'] = 15 * df['y'] + 4392976\n", - " if 'ha' in df:\n", - " df['ha'] = 15 * df['ha']\n", - " df['longitude'], df['latitude'] = transformer.transform(df['x'].values, df['y'].values)\n", - " df['date'] = df['datetime'].dt.date\n", - " return df\n", - "\n", - " def finalize(df):\n", - " front = ['user_id', 'timestamp', 'longitude', 'latitude', 'x', 'y', 'date']\n", - " cols = [col for col in front if col in df] + [col for col in df.columns if col not in front]\n", - " return df[cols].rename(columns={'user_id': 'user_id', 'timestamp': 'timestamp'}).reset_index(drop=True)\n", + "import pandas as pd\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "plt.style.use('default')\n", + "import time\n", + "import os\n", + "from joblib import Parallel, delayed\n", "\n", - " if sparse_traj:\n", - " agent.sparse_traj = finalize(project_city_blocks_to_web_mercator(agent.sparse_traj))\n", - " if full_traj:\n", - " agent.trajectory = finalize(project_city_blocks_to_web_mercator(agent.trajectory))\n", - " \n", - " if diaries:\n", - " diary = agent.diary.copy()\n", - " xs = []\n", - " ys = []\n", - " for loc in diary[\"location\"]:\n", - " if loc is None:\n", - " xs.append(None)\n", - " ys.append(None)\n", - " else:\n", - " pt = agent.city.buildings[loc].geometry.centroid\n", - " xs.append(pt.x)\n", - " ys.append(pt.y)\n", - " diary[\"x\"] = xs\n", - " diary[\"y\"] = ys\n", - " agent.diary = finalize(project_city_blocks_to_web_mercator(diary))" + "from nomad.city_gen import City\n", + "from nomad.traj_gen import Agent, Population\n", + "from nomad.stop_detection.viz import plot_pings, plot_time_barcode" ] }, { "cell_type": "code", - "execution_count": 22, - "id": "3d0fe233", - "metadata": { - "tags": [] - }, + "execution_count": null, + "id": "9860e901", + "metadata": {}, "outputs": [], "source": [ - "def philly_to_lat_long(agent, sparse_traj=True, full_traj=False, diaries=True):\n", - " def project_point_to_web_mercator(x, y):\n", - " \"\"\"\n", - " Project a fractional (x, y) block coord to Web Mercator using affine interpolation.\n", - " philly_grid_map is the grid_map produced by RealCityGenerator in virtual_philly.ipynb\n", - " import it into this notebook through pkl\n", - " \"\"\"\n", - " i, j = int(math.floor(x)), int(math.floor(y))\n", - " poly = philly_grid_map.get((i, j))\n", - "\n", - " if poly is None:\n", - " raise ValueError(f\"No polygon found at grid cell ({i}, {j})\")\n", - "\n", - " # Bounds of the 1x1 block polygon in EPSG:3857\n", - " minx, miny, maxx, maxy = poly.bounds\n", + "city = City.from_geopackage('garden-city.gpkg', edges_path='garden-city-edges.parquet')\n", + "city._build_hub_network(hub_size=16)\n", + "city.compute_gravity(exponent=2.0)\n", "\n", - " dx = x - i\n", - " dy = y - j\n", - "\n", - " X = minx + dx * (maxx - minx)\n", - " Y = miny + dy * (maxy - miny)\n", - "\n", - " return X, Y\n", - "\n", - " def apply_projection_to_df(df):\n", - " \"\"\"Apply Web Mercator projection to a DataFrame with 'x' and 'y' columns.\"\"\"\n", - " def safe_project(row):\n", - " try:\n", - " return project_point_to_web_mercator(row['x'], row['y'])\n", - " except Exception:\n", - " return (None, None)\n", - "\n", - " projected = df.apply(safe_project, axis=1)\n", - " df[['x', 'y']] = pd.DataFrame(projected.tolist(), index=df.index)\n", - "\n", - " transformer = Transformer.from_crs(\"EPSG:3857\", \"EPSG:4326\", always_xy=True)\n", - " if 'ha' in df:\n", - " df['ha'] = 10 * df['ha'] # 10 because thats the sidelength of a block\n", - " df['longitude'], df['latitude'] = transformer.transform(df['x'].values, df['y'].values)\n", - " df['date'] = df['datetime'].dt.date\n", - " return df\n", - "\n", - " def finalize(df):\n", - " front = ['identifier', 'timestamp', 'longitude', 'latitude', 'x', 'y', 'date']\n", - " cols = [col for col in front if col in df] + [col for col in df.columns if col not in front]\n", - " return df[cols].rename(columns={'identifier': 'uid', 'timestamp': 'timestamp'}).reset_index(drop=True)\n", - "\n", - " if sparse_traj:\n", - " agent.sparse_traj = finalize(apply_projection_to_df(agent.sparse_traj))\n", - " if full_traj:\n", - " agent.trajectory = finalize(apply_projection_to_df(agent.trajectory))\n", - "\n", - " if diaries:\n", - " diary = agent.diary.copy()\n", - " xs = []\n", - " ys = []\n", - " for loc in diary[\"location\"]:\n", - " if loc is None:\n", - " xs.append(None)\n", - " ys.append(None)\n", - " else:\n", - " pt = agent.city.buildings[loc].geometry.centroid\n", - " xs.append(pt.x)\n", - " ys.append(pt.y)\n", - " diary[\"x\"] = xs\n", - " diary[\"y\"] = ys\n", - " agent.diary = finalize(apply_projection_to_df(diary))" + "print(f\"City: {city.name}\")\n", + "print(f\"Dimensions: {city.dimensions}\")\n", + "print(f\"Buildings: {len(city.buildings_gdf)}\")" ] }, { "cell_type": "markdown", - "id": "eb8c0db6", + "id": "9ff8ddec", "metadata": {}, "source": [ - "## Simple trajectory generation\n", + "## Part 1: Effect of Sampling Parameters on Sparsity\n", "\n", - "For simple trajectory generation tasks that don't require too much computation power and can be done on a personal laptop, the following code generates a trajectory for each agent and saves it to a csv." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "82f2112f", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Generating trajectories: 100%|██████████| 1/1 [00:28<00:00, 28.22s/it]\n" - ] - } - ], - "source": [ - "def generate_trajectory_data(agent, seed_trajectory=None, seed_sparsity=None):\n", - " beta_params = gen_params_target_q(q_range=(0.3, 0.9), seed=seed_sparsity)\n", - " rng = npr.default_rng(seed_sparsity)\n", - " ha_sample = rng.uniform(11.5/15, 1)\n", - "\n", - " agent.reset_trajectory()\n", - " agent.generate_trajectory(\n", - " datetime = \"2024-01-01T07:00 -04:00\",\n", - " end_time = pd.Timestamp('2024-01-31T09:00 -04:00'),\n", - " seed=seed_trajectory,\n", - " dt=1)\n", - "\n", - " agent.sample_trajectory(\n", - " **beta_params,\n", - " seed=seed_sparsity,\n", - " ha=ha_sample,\n", - " replace_sparse_traj=True)\n", - "\n", - " philly_to_lat_long(agent, sparse_traj=True, full_traj=False)\n", - " agent.reset_trajectory(trajectory = False, sparse = False, diary = False)\n", - " return None\n", - "\n", - "# Generate trajectories with progress bar\n", - "N = 1\n", - "population = Population(city)\n", - "population.generate_agents(N=N, seed=250, name_count=2)\n", - "\n", - "for i, agent in enumerate(tqdm(population.roster.values(), desc=\"Generating trajectories\")):\n", - " generate_trajectory_data(agent, seed_trajectory=i, seed_sparsity=i)" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "cda6fba3", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "agent.sparse_traj.to_csv(\"philly_sparse_traj.csv\", index=False)\n", - "agent.trajectory.to_csv(\"philly_full_traj.csv\", index=False)" + "Generate 3 agents with 2-day trajectories, varying beta_duration and beta_start \n", + "to show their effect on sparsity (q = observed points / ground truth points)." ] }, { "cell_type": "code", "execution_count": null, - "id": "8f47ea1c", + "id": "b6336cc1", "metadata": {}, "outputs": [], "source": [ - "def generate_agent_trajectory(agent_id, agent, seed):\n", + "np.random.seed(42)\n", + "population = Population(city)\n", + "population.generate_agents(N=3, seed=42, name_count=2)\n", "\n", - " beta_params = gen_params_target_q(q_range=(0.4, 0.85), seed=seed)\n", + "# Vary beta_duration and beta_start to target different sparsity levels\n", + "sampling_params = [\n", + " {'beta_ping': 5, 'beta_start': 100, 'beta_durations': 60}, \n", + " {'beta_ping': 5, 'beta_start': 250, 'beta_durations': 150}, \n", + " {'beta_ping': 5, 'beta_start': 400, 'beta_durations': 240} \n", + "]\n", "\n", + "# Generate 2-day trajectories for quick visualization\n", + "for i, (agent_id, agent) in enumerate(population.roster.items()):\n", " agent.generate_trajectory(\n", - " datetime = \"2024-01-01T08:00 -04:00\",\n", - " end_time = pd.Timestamp('2024-01-21T08:30:00 -04:00'),\n", - " seed=1,\n", - " dt=0.25)\n", - " print('finished generating trajectory')\n", + " datetime=pd.Timestamp(\"2024-01-01T07:00-04:00\"),\n", + " end_time=pd.Timestamp(\"2024-01-03T07:00-04:00\"),\n", + " seed=i\n", + " )\n", + "\n", " agent.sample_trajectory(\n", - " **beta_params,\n", - " seed=seed,\n", - " ha=13/15, # <<<<<<\n", - " replace_sparse_traj=True)\n", + " **sampling_params[i],\n", + " replace_sparse_traj=True,\n", + " seed=i\n", + " )\n", " \n", - " garden_city_to_lat_long(agent,\n", - " sparse_traj=True,\n", - " full_traj=False)\n", - " agent.reset_trajectory(trajectory = True, sparse = False, diary = False)\n", - " \n", - " return agent_id, copy.deepcopy(agent)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d0f8a946", - "metadata": {}, - "outputs": [], - "source": [ - "population = Population(city)\n", - "population.generate_agents(N=2, seed=2, name_count=2)" + " q = len(agent.sparse_traj) / len(agent.trajectory)\n", + " print(f\"Agent {i}: q={q:.3f}, beta_start={sampling_params[i]['beta_start']}, \"\n", + " f\"beta_dur={sampling_params[i]['beta_durations']}\")" ] }, { "cell_type": "code", "execution_count": null, - "id": "4946528f", + "id": "0e4b20fa", "metadata": { - "scrolled": true + "lines_to_next_cell": 1 }, "outputs": [], "source": [ - "%%time \n", - "# time for 2 weeks of data at dt = 0.25\n", - "agent_1 = list(population.roster.values())[1]\n", - "generate_agent_trajectory(agent_1, 10, 6)\n", - "agent_1.sparse_traj.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fbb51dfd", - "metadata": {}, - "outputs": [], - "source": [ - "def generate_trajectory_data(agent, seed_trajectory=None, seed_sparsity=None, use_datetime=None, use_lon_lat=True):\n", - " beta_params = gen_params_ranges(seed=seed_sparsity)\n", - " rng = npr.default_rng(seed_sparsity)\n", - " ha_sample = rng.uniform(11.5/15, 16.5/15)\n", - "\n", - " agent.reset_trajectory()\n", - " agent.generate_trajectory(\n", - " datetime = \"2024-01-01T07:00 -04:00\",\n", - " end_time = pd.Timestamp('2024-01-15T09:00:00 -04:00'),\n", - " seed=seed_trajectory,\n", - " dt=1)\n", - "\n", - " agent.sample_trajectory(\n", - " **beta_params,\n", - " seed=seed_sparsity,\n", - " ha=ha_sample,\n", - " replace_sparse_traj=True)\n", - "\n", - " garden_city_to_lat_long(agent, sparse_traj=True, full_traj=False, use_datetime=use_datetime, use_lon_lat=use_lon_lat)\n", - " agent.reset_trajectory(trajectory = True, sparse = False, diary = False)\n", - " return None\n", - "\n", - "# Generate trajectories with progress bar\n", - "N = 100 \n", - "population = Population(city)\n", - "population.generate_agents(N=N, seed=314, name_count=2)\n", + "fig, axes = plt.subplots(2, 3, figsize=(15, 10), \n", + " gridspec_kw={'height_ratios': [10, 1]})\n", "\n", - "for i, agent in enumerate(tqdm(population.roster.values(), desc=\"Generating trajectories\")):\n", - " generate_trajectory_data(agent, seed_trajectory=i, seed_sparsity=i, use_datetime=None, use_lon_lat=True)\n", - " #agent.sparse_traj.rename(columns={'uid': 'identifier', 'timestamp': 'unix_timestamp', 'latitude':'device_lat', 'longitude':'device_lon', 'datetime':'local_datetime'}, inplace=True)\n", - " agent.sparse_traj.rename(columns={'uid': 'user_id'}, inplace=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ed6116df", - "metadata": {}, - "outputs": [], - "source": [ - "agent_id, agent = [(agent_id, agent) for agent_id, agent in population.roster.items()][0]\n", - "agent_id" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9724ff27", - "metadata": {}, - "outputs": [], - "source": [ - "def generate_trajectory_data(agent_id, agent, seed):\n", - " agent.reset_trajectory()\n", + "for i, (agent_id, agent) in enumerate(population.roster.items()):\n", + " ax_map = axes[0, i]\n", + " ax_barcode = axes[1, i]\n", " \n", - " agent.generate_trajectory(\n", - " local_timestamp=\"2024-01-01T06:00:00 +02:00\",\n", - " end_time=pd.Timestamp(\"2024-01-20T12:00:00 +02:00\"),\n", - " seed=105,\n", - " dt=1)\n", - "\n", - " beta_duration = npr.uniform(25, 170)\n", - " beta_start = max(npr.uniform(25, 520), beta_duration)\n", - " beta_ping = min(npr.uniform(3, 15), beta_duration//2)\n", - "\n", - " agent.sample_trajectory(\n", - " beta_start=beta_start,\n", - " beta_durations=beta_duration,\n", - " beta_ping=beta_ping,\n", - " seed=seed,\n", - " replace_sparse_traj=True)\n", - "\n", - " garden_city_to_lat_long(agent, sparse_traj=True, full_traj=False)\n", - " return None\n", + " city.plot_city(ax=ax_map, doors=False, address=False)\n", + " \n", + " traj = agent.sparse_traj\n", + " plot_pings(traj, ax=ax_map, s=15, point_color='red', \n", + " x='x', y='y', timestamp='timestamp')\n", + " \n", + " plot_time_barcode(traj['timestamp'], ax=ax_barcode, set_xlim=True)\n", + " \n", + " q = len(traj) / len(agent.trajectory)\n", + " ax_map.set_title(f\"Agent {i}: {len(traj)} obs (q={q:.2f})\\n\"\n", + " f\"beta_start={sampling_params[i]['beta_start']}, \"\n", + " f\"beta_dur={sampling_params[i]['beta_durations']}\")\n", + " ax_map.set_axis_off()\n", "\n", - "# Generate trajectories with progress bar\n", - "for agent_id, agent in tqdm(population.roster.items(), desc=\"Generating trajectories\"):\n", - " generate_trajectory_data(agent_id, agent, seed=150)" + "plt.tight_layout()\n", + "plt.savefig('data/trajectories_visualization.png', dpi=150, bbox_inches='tight')\n", + "plt.show()" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "957367ef", + "cell_type": "markdown", + "id": "dc7b266a", "metadata": {}, - "outputs": [], "source": [ - "# dataset no 1\n", - "traj_cols = {\n", - " \"user_id\": \"identifier\",\n", - " \"timestamp\": \"unix_timestamp\",\n", - " \"latitude\": \"device_lat\",\n", - " \"longitude\": \"device_lon\",\n", - " \"datetime\": \"local_datetime\"}\n", - "# Save only sparse trajectories and diaries\n", - "population.save_pop(\n", - " sparse_path=\"output/gc_data.csv\",\n", - " diaries_path=None,\n", - " partition_cols=None,\n", - " traj_cols=traj_cols,\n", - " fmt=\"csv\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8b8b2f21", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# dataset no 2\n", - "traj_cols = {\n", - " \"user_id\": \"user_id\",\n", - " \"timestamp\": \"timestamp\",\n", - " \"latitude\": \"latitude\",\n", - " \"longitude\": \"longitude\",\n", - " \"datetime\": \"datetime\"}\n", - "# Save only sparse trajectories and diaries\n", - "population.save_pop(\n", - " sparse_path=\"output/gc_data/\",\n", - " diaries_path=None,\n", - " partition_cols=['date'],\n", - " traj_cols=traj_cols,\n", - " fmt=\"csv\"\n", - ")" + "## Part 2: Parallel Generation at Scale\n", + "\n", + "Generate trajectories for 15 users using parallelization." ] }, { "cell_type": "code", "execution_count": null, - "id": "3c870944", + "id": "5238c745", "metadata": { - "scrolled": true + "lines_to_next_cell": 1 }, "outputs": [], "source": [ - "sparse_df = loader.from_file(\"output/gc_data/\", format=\"csv\", traj_cols=traj_cols,\n", - " parse_dates=True)" - ] - }, - { - "cell_type": "markdown", - "id": "340dab62", - "metadata": {}, - "source": [ - "## Generate dataset 3 for tutorial" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1b22e370", - "metadata": {}, - "outputs": [], - "source": [ - "def generate_trajectory_data(agent, seed_trajectory=None, seed_sparsity=None, use_datetime=None, use_lon_lat=True):\n", - " beta_params = gen_params_target_q(q_range=(0.2, 0.8), beta_dur_range=(25, 180), beta_ping_range=(1.5, 6), seed=seed_sparsity)\n", - " rng = npr.default_rng(seed_sparsity)\n", - " ha_sample = rng.uniform(11.5/15, 16.5/15)\n", - "\n", - " agent.reset_trajectory()\n", + "def generate_agent_trajectory(args):\n", + " \"\"\"Worker function for parallel generation.\"\"\"\n", + " identifier, home, work, seed = args\n", + " \n", + " city = City.from_geopackage('garden-city.gpkg', edges_path='garden-city-edges.parquet')\n", + " city._build_hub_network(hub_size=16)\n", + " city.compute_gravity(exponent=2.0)\n", + " agent = Agent(identifier=identifier, city=city, home=home, workplace=work)\n", + " \n", " agent.generate_trajectory(\n", - " datetime = \"2024-01-01T07:00 -04:00\",\n", - " end_time = pd.Timestamp('2024-01-21T09:00:00 -04:00'),\n", - " seed=seed_trajectory,\n", - " dt=0.15)\n", + " datetime=pd.Timestamp(\"2024-01-01T07:00-04:00\"),\n", + " end_time=pd.Timestamp(\"2024-01-08T07:00-04:00\"),\n", + " seed=seed\n", + " )\n", "\n", " agent.sample_trajectory(\n", - " **beta_params,\n", - " seed=seed_sparsity,\n", - " ha=ha_sample,\n", - " replace_sparse_traj=True)\n", - "\n", - " garden_city_to_lat_long(agent, sparse_traj=True, full_traj=False, use_datetime=use_datetime, use_lon_lat=use_lon_lat)\n", - " agent.reset_trajectory(trajectory = True, sparse = False, diary = False)\n", - " return None\n", - "\n", - "# Generate trajectories with progress bar\n", - "N = 350 \n", - "population = Population(city)\n", - "population.generate_agents(N=N, seed=5, name_count=2)\n", - "\n", - "for i, agent in enumerate(tqdm(population.roster.values(), desc=\"Generating trajectories\")):\n", - " if i == 0:\n", - " continue\n", - " generate_trajectory_data(agent, seed_trajectory=i, seed_sparsity=i, use_datetime=False, use_lon_lat=False)\n", - " agent.sparse_traj.rename(columns={'uid': 'gc_identifier', 'timestamp': 'unix_ts', 'x':'dev_x', 'y':'dev_y'}, inplace=True)" + " beta_ping=5,\n", + " replace_sparse_traj=True,\n", + " seed=seed\n", + " )\n", + " \n", + " sparse_df = agent.sparse_traj.copy()\n", + " sparse_df['user_id'] = identifier\n", + " return sparse_df" ] }, { "cell_type": "code", "execution_count": null, - "id": "f71d0544", + "id": "aa1b6112", "metadata": {}, "outputs": [], "source": [ - "# Make data for agent 0\n", - "start_time = pd.date_range(start='2024-01-01T07:00 -04:00', periods=4, freq='60min')\n", - "tz_offset = loader._offset_seconds_from_ts(start_time[0])\n", - "unix_timestamp = [int(t.timestamp()) for t in start_time]\n", - "duration = [60]*4 # in minutes\n", - "location = ['h-x13-y11'] * 1 + ['h-x13-y9'] * 1 + ['w-x18-y10'] * 1 + ['w-x18-y8'] * 1\n", - "\n", - "destinations = pd.DataFrame(\n", - " {\"datetime\":start_time,\n", - " \"timestamp\":unix_timestamp,\n", - " \"duration\":duration,\n", - " \"location\":location}\n", - " )\n", - "destinations = condense_destinations(destinations)\n", - "\n", - "agent_0 = list(population.roster.values())[0]\n", - "\n", - "rng = npr.default_rng(0)\n", - "ha_sample = rng.uniform(11.5/15, 16.5/15)\n", - "\n", - "agent_0.reset_trajectory()\n", - "agent_0.generate_trajectory(destination_diary=destinations, seed=0, dt=0.15)\n", + "np.random.seed(100)\n", + "n_agents = 15\n", + "homes = city.buildings_gdf[city.buildings_gdf['building_type'] == 'home']['id'].tolist()\n", + "workplaces = city.buildings_gdf[city.buildings_gdf['building_type'] == 'workplace']['id'].tolist()\n", "\n", - "agent_0.sample_trajectory(\n", - " beta_ping=2,\n", - " beta_start=None,\n", - " beta_durations=None,\n", - " seed=0,\n", - " ha=ha_sample,\n", - " replace_sparse_traj=True)\n", - "\n", - "garden_city_to_lat_long(agent_0, sparse_traj=True, full_traj=False, use_datetime=False, use_lon_lat=False)\n", - "agent_0.reset_trajectory(trajectory = True, sparse = False, diary = False)\n", - "agent_0.sparse_traj.rename(columns={'uid': 'gc_identifier', 'timestamp': 'unix_ts', 'x':'dev_x', 'y':'dev_y'}, inplace=True)" + "agent_params = [\n", + " (f'agent_{i:04d}', \n", + " np.random.choice(homes),\n", + " np.random.choice(workplaces),\n", + " i)\n", + " for i in range(n_agents)\n", + "]" ] }, { "cell_type": "code", "execution_count": null, - "id": "aff38e9c", + "id": "a0364bc9", "metadata": {}, "outputs": [], "source": [ - "# dataset no 3\n", - "traj_cols = {\n", - " \"user_id\": \"gc_identifier\",\n", - " \"timestamp\": \"unix_ts\",\n", - " \"x\": \"dev_x\",\n", - " \"y\": \"dev_y\"}\n", - "# Save only sparse trajectories and diaries\n", - "population.save_pop(\n", - " sparse_path=\"output/gc_data_long/\",\n", - " diaries_path=None,\n", - " partition_cols=['date'],\n", - " traj_cols=traj_cols,\n", - " fmt=\"parquet\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "5c2af855", - "metadata": {}, - "source": [ - "For larger trajectory generation tasks that require a lot of compute power, we can parallelize the trajectory generation using the following code. We generate ground-truth trajectories in agent-month \"chunks\", sparsify each chunk, then reset the ground-truth trajectory field to lessen the memory usage. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "41b6d757", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# Using parallel processing (e.g., using a cluster)\n", - "%%time\n", + "print(f\"Generating {n_agents} agents in parallel...\")\n", + "start_time = time.time()\n", "\n", - "def generate_agent_trajectory(agent_id, agent, seed):\n", - " \n", - " beta_duration = npr.uniform(15, 180)\n", - " beta_start = max(npr.uniform(60, 1200), beta_duration*3)\n", - " beta_ping = npr.uniform(1.5, 30)\n", - " \n", - " param = (beta_start, beta_duration, beta_ping)\n", - " \n", - " for month in range(1,13):\n", - " days = calendar.monthrange(2024, month)[1]\n", - " population_n.generate_trajectory(agent, \n", - " T=datetime(2024, month, days, hour=23, minute=59), \n", - " seed=seed)\n", - " \n", - " agent.sample_traj_hier_nhpp(*param, \n", - " seed=seed,\n", - " reset_traj=True)\n", - " \n", - " garden_city_to_lat_long(agent,\n", - " sparse_traj=True,\n", - " full_traj=False)\n", - " \n", - " return agent_id, copy.deepcopy(agent)\n", - "\n", - "manager = multiprocessing.Manager()\n", - "shared_roster = manager.dict(population_n.roster)\n", - "\n", - "start = 6001 # 12001 # can modify\n", - "end = 12001 # 18001 # can modify\n", - "roster = dict(population_n.roster)\n", - "batch = islice(roster.items(), start, end)\n", - "\n", - "with ProcessPoolExecutor() as executor:\n", - " with tqdm(total=(end-start), desc=\"Processing agents\") as pbar:\n", - " futures = [\n", - " executor.submit(generate_agent_trajectory, agent_id, agent, i+15000)\n", - " for i, (agent_id, agent) in enumerate(batch, start=start)\n", - " ]\n", - " results = []\n", - " for future in futures:\n", - " results.append(future.result())\n", - " pbar.update(1)\n", + "results = Parallel(n_jobs=-1, verbose=10)(\n", + " delayed(generate_agent_trajectory)(params) for params in agent_params\n", + ")\n", "\n", - "for agent_id, agent in results:\n", - " population_n.roster[agent_id] = agent" - ] - }, - { - "cell_type": "markdown", - "id": "68ea90d9", - "metadata": {}, - "source": [ - "This code saves the generated trajectories in a parquet file, using the date as the partition column. " + "generation_time = time.time() - start_time\n", + "print(f\"Generated {n_agents} agents in {generation_time:.2f}s ({generation_time/n_agents:.2f}s per agent)\")" ] }, { "cell_type": "code", "execution_count": null, - "id": "a8d9885f", - "metadata": { - "tags": [] - }, + "id": "e8a48448", + "metadata": {}, "outputs": [], "source": [ - "partition_cols = {\n", - " 'sparse_traj': ['date'],\n", - " 'diaries': ['identifier']\n", - "}\n", + "all_trajectories = pd.concat(results, ignore_index=True)\n", + "all_trajectories = city.to_mercator(all_trajectories)\n", + "all_trajectories['date'] = pd.to_datetime(all_trajectories['datetime']).dt.date\n", "\n", - "roster = dict(islice(population_n.roster.items(), start, end))\n", + "output_path = 'data/trajectories_15_users'\n", + "for date, group in all_trajectories.groupby('date'):\n", + " os.makedirs(f'{output_path}/date={str(date)}', exist_ok=True)\n", + " group.to_parquet(f'{output_path}/date={str(date)}/data.parquet', index=False)\n", "\n", - "population.save_pop(bucket=\"synthetic-raw-data\",\n", - " prefix=f\"agents-{start+15000}-{end+15000-1}/\",\n", - " save_full_traj=False,\n", - " save_sparse_traj=True,\n", - " save_homes=True,\n", - " save_diaries=True,\n", - " partition_cols=partition_cols,\n", - " roster=roster)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bee94160", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# Load the Parquet files\n", - "s3_path = \"s3://synthetic-raw-data/agents-1-1001/sparse_trajectories.parquet/\"\n", - "df1 = pd.read_parquet(s3_path)\n", - "s3_path = \"s3://synthetic-raw-data/agents-1001-2000/sparse_trajectories.parquet/\"\n", - "df2 = pd.read_parquet(s3_path)" + "print(f\"Saved {len(all_trajectories):,} records to {output_path}/\")" ] } ], "metadata": { "jupytext": { - "formats": "ipynb,auto:percent" + "formats": "ipynb,py:percent" }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.0" - }, - "nbdime-conflicts": { - "local_diff": [ - { - "diff": [ - { - "diff": [ - { - "key": 0, - "op": "addrange", - "valuelist": [ - "Python 3 (ipykernel)" - ] - }, - { - "key": 0, - "length": 1, - "op": "removerange" - } - ], - "key": "display_name", - "op": "patch" - } - ], - "key": "kernelspec", - "op": "patch" - }, - { - "diff": [ - { - "diff": [ - { - "key": 0, - "length": 1, - "op": "removerange" - } - ], - "key": "version", - "op": "patch" - } - ], - "key": "language_info", - "op": "patch" - } - ], - "remote_diff": [ - { - "diff": [ - { - "diff": [ - { - "key": 0, - "op": "addrange", - "valuelist": [ - "Python 3.10 (daphme)" - ] - }, - { - "key": 0, - "length": 1, - "op": "removerange" - } - ], - "key": "display_name", - "op": "patch" - } - ], - "key": "kernelspec", - "op": "patch" - }, - { - "diff": [ - { - "diff": [ - { - "diff": [ - { - "key": 3, - "op": "addrange", - "valuelist": "0" - }, - { - "key": 3, - "length": 1, - "op": "removerange" - } - ], - "key": 0, - "op": "patch" - } - ], - "key": "version", - "op": "patch" - } - ], - "key": "language_info", - "op": "patch" - } - ] - }, - "toc-autonumbering": false + } }, "nbformat": 4, "nbformat_minor": 5 diff --git a/examples/grid_based_demo.ipynb b/examples/grid_based_demo.ipynb index 03022d0a..90114911 100644 --- a/examples/grid_based_demo.ipynb +++ b/examples/grid_based_demo.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "92838936", + "id": "f56b531d", "metadata": {}, "source": [ "# Grid-Based Stop Detection" @@ -10,7 +10,7 @@ }, { "cell_type": "markdown", - "id": "cb276fd9", + "id": "9cfdb26e", "metadata": {}, "source": [ "The stop detection algorithms implemented in `nomad` support different combinations of input formats that are common in commercial datasets, detecting default names when possible\n", @@ -23,69 +23,78 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "19184dee", - "metadata": {}, + "execution_count": 1, + "id": "24b50a14", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:32:11.959806Z", + "iopub.status.busy": "2025-11-24T18:32:11.959806Z", + "iopub.status.idle": "2025-11-24T18:32:16.169725Z", + "shell.execute_reply": "2025-11-24T18:32:16.169725Z" + } + }, "outputs": [], "source": [ "%matplotlib inline\n", + "import matplotlib\n", + "matplotlib.use('TkAgg') # Non-blocking backend\n", + "import matplotlib.pyplot as plt\n", + "plt.ion() # Interactive mode\n", "\n", "# Imports\n", "import nomad.io.base as loader\n", "import geopandas as gpd\n", "from shapely.geometry import box\n", - "import pandas as pd\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode\n", + "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode, plot_hexagons, plot_pings\n", "import nomad.stop_detection.grid_based as GRID_BASED\n", "import nomad.filters as filters \n", "\n", "# Load data\n", - "city = gpd.read_file(\"garden_city.geojson\").to_crs('EPSG:3857')\n", - "outer_box = box(*city.total_bounds).buffer(15, join_style='mitre')\n", + "import nomad.data as data_folder\n", + "from pathlib import Path\n", + "data_dir = Path(data_folder.__file__).parent\n", + "city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet')\n", + "outer_box = box(*city.total_bounds)\n", "\n", - "filepath_root = '../tutorials/IC2S2-2025/gc_data_long/'\n", - "tc = {\n", - " \"user_id\": \"gc_identifier\",\n", - " \"timestamp\": \"unix_ts\",\n", - " \"x\": \"dev_x\",\n", - " \"y\": \"dev_y\",\n", - " \"ha\":\"ha\",\n", - " \"date\":\"date\"}\n", + "filepath_root = 'gc_data_long/'\n", + "tc = {\"user_id\": \"gc_identifier\", \"x\": \"dev_x\", \"y\": \"dev_y\", \"timestamp\": \"unix_ts\"}\n", "\n", "users = ['admiring_brattain']\n", - "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','==', '2024-01-01'), traj_cols=tc)\n", + "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc)\n", "\n", - "# Grid-based\n", - "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, x='dev_x', y='dev_y', data_crs='EPSG:3857')\n", - "stops_gb = GRID_BASED.grid_based(traj, time_thresh=240, complete_output=True, timestamp='unix_ts', location_id='h3_cell')" + "# Grid-based - data is in Web Mercator (EPSG:3857) projected coordinates\n", + "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, traj_cols=tc, data_crs='EPSG:3857')\n", + "stops_gb = GRID_BASED.grid_based(traj, time_thresh=240, complete_output=True, location_id='h3_cell', traj_cols=tc)" ] }, { "cell_type": "code", - "execution_count": 21, - "id": "62555a1b", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAACMCAYAAABh9MpJAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8ekN5oAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAf0UlEQVR4nO3deXRU9f3/8dckk8m+sCSWyFYwrCL8vojSooKagobFVhFMwUKpHkQE7TmtonXDys+D39qjglCkgFplEUQPxpZzsA0uHJUlRI9VItqEqoAQCFkJ2T6/P/jd68xkJpkslwzx+ThnDsy9n/v5vO/nfu7n3ndmcxljjAAAAAAAQLuL6OgAAAAAAADorEi6AQAAAABwCEk3AAAAAAAOIekGAAAAAMAhJN0AAAAAADiEpBsAAAAAAIeQdAMAAAAA4BCSbgAAAAAAHELSDQAAAACAQ0i6AQA/GLNnz1bfvn2bLVdUVCSXy6UXXnjB8Zg6St++fTV79uyODgMAgE6PpBsAEPYKCwt11113acCAAYqLi1NcXJyGDBmi+fPn65NPPumwuHbu3CmXy+Xz6Nq1q0aPHq1XXnmlw+I6lz777DM9+uijKioq6uhQAAAIS+6ODgAAgKbk5ORo+vTpcrvdmjFjhoYPH66IiAgdOHBAW7du1cqVK1VYWKg+ffo0W9fq1avV0NDQ7jEuXLhQo0aNkiSdOHFCmzZt0syZM3Xq1CnNnz+/3dsLJ5999pkWL16scePGhfQuAgAAfmhIugEAYeurr77SLbfcoj59+uif//ynevTo4bN+6dKlWrFihSIimn7jVmVlpeLj4xUVFeVInFdeeaWmTp1qP583b5769eun9evXd/qkGwAANI23lwMAwtaTTz6pyspKrVu3rlHCLUlut1sLFy5Ur1697GWzZ89WQkKCvvrqK2VlZSkxMVEzZsyw1/m/Gnvq1CnNnj1bycnJSklJ0axZs3Tq1Kk2xe3xeNSlSxe53b5/2163bp2uueYapaWlKTo6WkOGDNHKlSsbbb93715NmDBB3bt3V2xsrH784x9rzpw5PmUaGhr09NNPa+jQoYqJidEFF1yguXPnqqSkxKecMUaPP/64evbsqbi4OF199dX697//HfK+bNy4USNHjlRiYqKSkpI0bNgwPfPMM5KkF154QTfffLMk6eqrr7bfYr9z5057+xUrVmjo0KGKjo5Wenq65s+f36h/x40bp4svvlj79u3TT3/6U3uf//KXvzSKZ9myZRo6dKji4uLUpUsXXXrppVq/fn3I+wMAwLnGK90AgLCVk5Ojiy66SJdffnmLtqurq9OECRN0xRVX6E9/+pPi4uICljPG6IYbbtD777+vO+64Q4MHD9brr7+uWbNmtai98vJyFRcXS5JOnjyp9evX69NPP9WaNWt8yq1cuVJDhw7VlClT5Ha79eabb+rOO+9UQ0OD/Yr4sWPHNH78eKWmpmrRokVKSUlRUVGRtm7d6lPX3Llz9cILL+jXv/61Fi5cqMLCQi1fvlz79+/Xrl277Ff1H374YT3++OPKyspSVlaW8vLyNH78eNXU1DS7Xzt27FB2drauvfZaLV26VJL0+eefa9euXbr77rt11VVXaeHChXr22Wf1wAMPaPDgwZJk//voo49q8eLFyszM1Lx581RQUKCVK1dqz549PjFKUklJibKysjRt2jRlZ2fr1Vdf1bx58+TxeOw/OKxevVoLFy7U1KlTdffdd6u6ulqffPKJPvroI/3yl79s0TEDAOCcMQAAhKHS0lIjyfz85z9vtK6kpMQcP37cflRVVdnrZs2aZSSZRYsWNdpu1qxZpk+fPvbzN954w0gyTz75pL2srq7OXHnllUaSWbduXZMx5ubmGkmNHhEREWbJkiWNynvHaZkwYYLp16+f/fz11183ksyePXuCtvvee+8ZSeaVV17xWb59+3af5ceOHTMej8dMnDjRNDQ02OUeeOABI8nMmjWryf27++67TVJSkqmrqwtaZvPmzUaSyc3N9VlutT1+/HhTX19vL1++fLmRZNauXWsvGzt2rJFknnrqKXvZmTNnzIgRI0xaWpqpqakxxhhzww03mKFDhzYZMwAA4Ya3lwMAwlJZWZkkKSEhodG6cePGKTU11X4899xzjcrMmzev2Tb+/ve/y+12+5SNjIzUggULWhTrww8/rB07dmjHjh3atGmTsrOz9Yc//MF+G7YlNjbW/n9paamKi4s1duxY/ec//1FpaakkKSUlRdLZV/lra2sDtrd582YlJyfrZz/7mYqLi+3HyJEjlZCQoNzcXEnS22+/rZqaGi1YsEAul8ve/p577glpv1JSUlRZWakdO3aE2hU2q+177rnH5zP3t99+u5KSkvTWW2/5lHe73Zo7d6793OPxaO7cuTp27Jj27dtnx/PNN99oz549LY4HAICOQtINAAhLiYmJkqSKiopG61atWqUdO3bo5ZdfDrit2+1Wz549m23j0KFD6tGjR6PEfuDAgT7PT58+raNHj/o8vA0bNkyZmZnKzMzUtGnT9PLLL2vSpElatGiRjh8/bpfbtWuXMjMzFR8fr5SUFKWmpuqBBx6QJDvpHjt2rG666SYtXrxY3bt31w033KB169bpzJkzdj0HDx5UaWmp0tLSfP74kJqaqoqKCh07dszeP0nKyMjwiTc1NVVdunRptn/uvPNODRgwQNdff7169uypOXPmaPv27c1u5922f196PB7169fPXm9JT09XfHy8z7IBAwZIkv1zZPfdd58SEhJ02WWXKSMjQ/Pnz9euXbtCigcAgI5C0g0ACEvJycnq0aOHPv3000brLr/8cmVmZmrMmDEBt42Ojm72G81bYtOmTerRo4fPoznXXnutqqurtXv3bklnv4n92muvVXFxsf785z/rrbfe0o4dO/Tb3/5WkuyfMnO5XNqyZYs++OAD3XXXXfr22281Z84cjRw50v4DRENDg9LS0uxX1/0fjz32WLvsd1pamvLz87Vt2zZNmTJFubm5uv7661v8mff2MnjwYBUUFGjjxo264oor9Nprr+mKK67QI4880iHxAAAQCr5IDQAQtiZOnKi//vWv2r17ty677LJ2r9/6KbKKigqfV7sLCgp8yk2YMKHFb7Guq6uT9P0r9W+++abOnDmjbdu2qXfv3nY5663g/kaPHq3Ro0dryZIlWr9+vWbMmKGNGzfqtttuU//+/fX2229rzJgxPm9ZD7R/0tlXxvv162cvP378eKNvOQ/G4/Fo8uTJmjx5shoaGnTnnXdq1apVeuihh3TRRRf5vG09UNsFBQU+bdfU1KiwsFCZmZk+5Q8fPmz/tJvliy++kCSfb5yPj4/X9OnTNX36dNXU1OjGG2/UkiVLdP/99ysmJiakfQIA4FzilW4AQNi69957FRcXpzlz5ui7775rtN4Y06b6s7KyVFdX5/OzXfX19Vq2bJlPuR49ethvH7cezcnJyZEkDR8+XNLZz4r7x1xaWqp169b5bFdSUtJov0aMGCFJ9lvMp02bpvr6ev3xj39s1G5dXZ39k1yZmZmKiorSsmXLfOp8+umnm41fkk6cOOHzPCIiQpdccolPLFaS7P8zYJmZ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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 2, + "id": "4fea8a03", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:32:16.169725Z", + "iopub.status.busy": "2025-11-24T18:32:16.169725Z", + "iopub.status.idle": "2025-11-24T18:32:16.492839Z", + "shell.execute_reply": "2025-11-24T18:32:16.492839Z" } - ], + }, + "outputs": [], "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", + "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", + " gridspec_kw={'height_ratios':[10,1]})\n", + "\n", + "gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3')\n", + "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c')\n", + "\n", + "plot_hexagons(stops_gb, ax=ax_map, color='cluster', cmap='Greens', location_id='h3_cell', data_crs='EPSG:3857')\n", + "plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc)\n", + "ax_map.set_axis_off()\n", "\n", "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_gb, ax=ax_barcode, stop_color='green', set_xlim=False, timestamp='unix_ts')\n", - "plt.title(\"Grid-Based stops\")\n", - "plt.tight_layout()\n", + "plot_stops_barcode(stops_gb, ax=ax_barcode, cmap='Greens', set_xlim=False, timestamp='unix_ts')\n", + "\n", + "plt.tight_layout(pad=0.1)\n", "plt.show()" ] } @@ -106,7 +115,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.0" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/examples/grid_based_demo.py b/examples/grid_based_demo.py index 7ec92360..4d667bbd 100644 --- a/examples/grid_based_demo.py +++ b/examples/grid_based_demo.py @@ -25,38 +25,49 @@ # %% # %matplotlib inline +import matplotlib +matplotlib.use('TkAgg') # Non-blocking backend +import matplotlib.pyplot as plt +plt.ion() # Interactive mode # Imports import nomad.io.base as loader import geopandas as gpd from shapely.geometry import box -import pandas as pd -import numpy as np -import matplotlib.pyplot as plt -from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode +from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode, plot_hexagons, plot_pings import nomad.stop_detection.grid_based as GRID_BASED import nomad.filters as filters # Load data -from nomad.city_gen import City -city_obj = City.from_geopackage("garden-city.gpkg") -outer_box = box(0, 0, city_obj.dimensions[0], city_obj.dimensions[1]) +import nomad.data as data_folder +from pathlib import Path +data_dir = Path(data_folder.__file__).parent +city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet') +outer_box = box(*city.total_bounds) filepath_root = 'gc_data_long/' tc = {"user_id": "gc_identifier", "x": "dev_x", "y": "dev_y", "timestamp": "unix_ts"} users = ['admiring_brattain'] -traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','==', '2024-01-01'), traj_cols=tc) +traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc) # Grid-based - data is in Web Mercator (EPSG:3857) projected coordinates traj['h3_cell'] = filters.to_tessellation(traj, index="h3", res=10, traj_cols=tc, data_crs='EPSG:3857') stops_gb = GRID_BASED.grid_based(traj, time_thresh=240, complete_output=True, location_id='h3_cell', traj_cols=tc) # %% -fig, ax_barcode = plt.subplots(figsize=(10,1.5)) +fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5), + gridspec_kw={'height_ratios':[10,1]}) + +gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3') +city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c') + +plot_hexagons(stops_gb, ax=ax_map, color='cluster', cmap='Greens', location_id='h3_cell', data_crs='EPSG:3857') +plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc) +ax_map.set_axis_off() plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True) -plot_stops_barcode(stops_gb, ax=ax_barcode, stop_color='green', set_xlim=False, timestamp='unix_ts') -plt.title("Grid-Based stops") -plt.tight_layout() +plot_stops_barcode(stops_gb, ax=ax_barcode, cmap='Greens', set_xlim=False, timestamp='unix_ts') + +plt.tight_layout(pad=0.1) plt.show() diff --git a/examples/hdbscan_demo.ipynb b/examples/hdbscan_demo.ipynb index 4437d09a..b7c6c4e4 100644 --- a/examples/hdbscan_demo.ipynb +++ b/examples/hdbscan_demo.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "92838936", + "id": "4f16fcbe", "metadata": {}, "source": [ "# HDBSCAN Stop Detection" @@ -10,7 +10,7 @@ }, { "cell_type": "markdown", - "id": "cb276fd9", + "id": "f69ed80f", "metadata": {}, "source": [ "The HDBSCAN algorithm constructs a hierarchy of non-overlapping clusters from different radius values and selects those that maximize stability." @@ -18,73 +18,79 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "19184dee", - "metadata": {}, + "execution_count": 1, + "id": "3561532d", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:32:30.624504Z", + "iopub.status.busy": "2025-11-24T18:32:30.624504Z", + "iopub.status.idle": "2025-11-24T18:32:33.741073Z", + "shell.execute_reply": "2025-11-24T18:32:33.740043Z" + } + }, "outputs": [], "source": [ "%matplotlib inline\n", + "import matplotlib\n", + "matplotlib.use('TkAgg')\n", + "import matplotlib.pyplot as plt\n", + "plt.ion()\n", "\n", "# Imports\n", "import nomad.io.base as loader\n", "import geopandas as gpd\n", "from shapely.geometry import box\n", - "import pandas as pd\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode\n", + "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode, plot_stops, plot_pings\n", "import nomad.stop_detection.hdbscan as HDBSCAN\n", - "import nomad.stop_detection.postprocessing as post\n", "\n", "# Load data\n", - "city = gpd.read_file(\"garden_city.geojson\").to_crs('EPSG:3857')\n", - "outer_box = box(*city.total_bounds).buffer(15, join_style='mitre')\n", + "import nomad.data as data_folder\n", + "from pathlib import Path\n", + "data_dir = Path(data_folder.__file__).parent\n", + "city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet')\n", + "outer_box = box(*city.total_bounds)\n", "\n", "filepath_root = 'gc_data_long/'\n", - "tc = {\n", - " \"user_id\": \"gc_identifier\",\n", - " \"timestamp\": \"unix_ts\",\n", - " \"x\": \"dev_x\",\n", - " \"y\": \"dev_y\",\n", - " \"ha\":\"ha\",\n", - " \"date\":\"date\"}\n", + "tc = {\"user_id\": \"gc_identifier\", \"x\": \"dev_x\", \"y\": \"dev_y\", \"timestamp\": \"unix_ts\"}\n", "\n", "users = ['admiring_brattain']\n", - "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','==', '2024-01-01'), traj_cols=tc)\n", + "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc)\n", "\n", - "user_data_hdb = traj.assign(cluster=HDBSCAN.hdbscan_labels(traj, time_thresh=240, min_pts=3, min_cluster_size=2, traj_cols=tc))\n", "stops_hdb = HDBSCAN.st_hdbscan(traj,\n", " time_thresh=720,\n", - " dist_thresh=15,\n", " min_pts=3,\n", " complete_output=True,\n", - " traj_cols=tc)\n", - "stops_hdb[\"cluster\"] = post.remove_overlaps(user_data_hdb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) " + " traj_cols=tc) " ] }, { "cell_type": "code", - "execution_count": 30, - "id": "fa70719e", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxoAAACuCAYAAACx83usAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAm8klEQVR4nO3deXgV9b3H8c8hZCMbEAgQE8ImBCKLhYKCNMQAslMBQ0B2awsUBaUqyhYoiiC3LsXYp/ey+RhZvKKyKJWYsJXlgkChorgQCCk7GghgzPa7f9BzyuGc5CRhJBDer+fJI+d3fjPzm5nvjPlk5syxGWOMAAAAAMBCVSp6AAAAAAAqH4IGAAAAAMsRNAAAAABYjqABAAAAwHIEDQAAAACWI2gAAAAAsBxBAwAAAIDlCBoAAAAALEfQAAAAAGA5ggZwi1q6dKlsNpv27Nnj9v0+ffqoQYMGTm0NGjSQzWaTzWZTlSpVFBISoubNm2vEiBH69NNP3c7H3t/+ExAQoObNm2vWrFm6fPmyU9/Lly9r3rx5at26tYKDgxUUFKTGjRsrISFBmzdvdpn3kSNHNGHCBDVt2lT+/v6qVq2aYmJiNG3aNP3rX/9yO54BAwbIZrNpwoQJbt/ftGmTY6w7duxweX/UqFEKDAx0O21ZJScna+nSpZbM61bTpUsXdenSxfH6ypUrSkpK0qZNm1z6JiUlyWaz6dy5czdvgDfoxIkTSkpK0v79+yt6KLcN+7HlrgYAoDyqVvQAAFirU6dOWrBggSTp0qVLOnz4sFasWKGHHnpIAwcO1PLly+Xt7e00zaBBgzR58mTHNJs3b9bs2bN14MABvf/++5KkwsJCde/eXQcPHtQzzzyj9u3bS5K++eYbrV27Vlu3blVsbKxjnuvWrVNiYqJq1aqlCRMm6N5775XNZtPBgwe1ePFirV+/Xvv27XMax5kzZ7Ru3TpJUkpKihYsWCA/P79i1/XZZ5/V1q1bb3CLFS85OVm1atXSqFGjfrZlVJTk5GSn11euXNGsWbMkySmA3K5OnDihWbNmqUGDBmrTpk1FD+e28Itf/EI7duxQixYtKnooACoJggZQyVSvXl333Xef43XXrl31+9//XklJSZo1a5amTZumefPmOU1Tp04dl2mOHTumlJQU5ebmys/PT1u2bNH27du1ePFijR492tH3oYce0oQJE1RUVORoy8jIUGJiopo2bar09HSFhIQ43nvwwQf15JNP6oMPPnAZ+9tvv638/Hz17t1b69ev1+rVqzV06FC369mjRw9t2LBBa9euVd++fcu+oe5w/DJpvR9//FH+/v4VPYxyCw4OdjoPAMCN4tYp4A6RlJSkmJgYLVy4ULm5uR77h4SEyGazycvLS5J0/vx5SVK9evXc9q9S5T+nkz/96U+6fPmykpOTnUKGnc1m04ABA1zaFy9erDp16mjZsmXy9/fX4sWLix3fqFGj1KJFCz3//PMqLCz0uD7XO3LkiBITExUeHi5fX1/VqVNH8fHxjlttGjRooC+++EKbN2923Kp17a1qmZmZGjZsmMLCwuTr66vmzZvrv/7rv5wC19GjR2Wz2TR//ny9+OKLql+/vvz8/NSuXTt99tlnTuM5e/asfvvb3yoyMlK+vr6qXbu2OnXqpNTU1GLX4YsvvpDNZtN7773naPv8889ls9kUExPj1Ldfv35q27at4/W1t04dPXpUtWvXliTNmjXLsb7XX8k5ffq0hgwZopCQENWpU0djxozRhQsXPG7rLl266J577tHWrVt13333yd/fX3fddZemT5/usu++//57jR8/XnfddZd8fHzUqFEjTZ06VT/99JNTv/fee08dOnRQSEiIqlWrpkaNGmnMmDGSrt4C9Mtf/lKSNHr0aMf6JCUllThO+y1i+/bt04ABAxQcHKyQkBANGzZMZ8+ederboEED9enTR6tXr9a9994rPz8/xxWhf/7zn+rfv79q1KghPz8/tWnTRsuWLXNZXnZ2tiZPnqxGjRrJ19dXYWFh6tWrl7766itHn7y8PM2ZM0fR0dGOuhg9erTLeNLS0tSlSxeFhobK399f9evX18CBA3XlyhVHn7feekutW7dWYGCggoKCFB0drRdeeMHxvrtbp+y3In777bfq1auXAgMDFRkZqcmTJ7vsk6ysLA0aNEhBQUGqXr26Hn30Ue3evVs2m63S3oIIoGRc0QBucYWFhSooKHBpN8aUeV59+/bVyy+/rD179uiBBx5wmpd9GfZbp5YtW6bExETHbVbt2rWTt7e3Jk6cqBkzZujBBx8sNnR8+umnLldJPNm+fbu+/PJLPfPMMwoNDdXAgQOVkpKijIwMNWzY0KW/l5eX5s6dq/79+2vZsmWOXzJLq1evXiosLNT8+fNVv359nTt3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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 2, + "id": "ca45c6c3", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:32:33.744689Z", + "iopub.status.busy": "2025-11-24T18:32:33.743614Z", + "iopub.status.idle": "2025-11-24T18:32:34.044704Z", + "shell.execute_reply": "2025-11-24T18:32:34.044704Z" } - ], + }, + "outputs": [], "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", + "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", + " gridspec_kw={'height_ratios':[10,1]})\n", + "\n", + "gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3')\n", + "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c')\n", + "\n", + "plot_stops(stops_hdb, ax=ax_map, cmap='Blues')\n", + "plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc)\n", + "ax_map.set_axis_off()\n", "\n", "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_hdb, ax=ax_barcode, stop_color='blue', set_xlim=False, timestamp='unix_ts')\n", - "fig.suptitle(\"HDBSCAN stops with post-processing\")\n", + "plot_stops_barcode(stops_hdb, ax=ax_barcode, cmap='Blues', set_xlim=False, timestamp='unix_ts')\n", + "\n", + "plt.tight_layout(pad=0.1)\n", "plt.show()" ] } @@ -105,7 +111,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/examples/hdbscan_demo.py b/examples/hdbscan_demo.py index 6eb922a1..fcef411f 100644 --- a/examples/hdbscan_demo.py +++ b/examples/hdbscan_demo.py @@ -20,41 +20,50 @@ # %% # %matplotlib inline +import matplotlib +matplotlib.use('TkAgg') +import matplotlib.pyplot as plt +plt.ion() # Imports import nomad.io.base as loader import geopandas as gpd from shapely.geometry import box -import pandas as pd -import numpy as np -import matplotlib.pyplot as plt -from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode +from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode, plot_stops, plot_pings import nomad.stop_detection.hdbscan as HDBSCAN -import nomad.stop_detection.postprocessing as post # Load data -from nomad.city_gen import City -city_obj = City.from_geopackage("garden-city.gpkg") -outer_box = box(0, 0, city_obj.dimensions[0], city_obj.dimensions[1]) +import nomad.data as data_folder +from pathlib import Path +data_dir = Path(data_folder.__file__).parent +city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet') +outer_box = box(*city.total_bounds) filepath_root = 'gc_data_long/' tc = {"user_id": "gc_identifier", "x": "dev_x", "y": "dev_y", "timestamp": "unix_ts"} users = ['admiring_brattain'] -traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','==', '2024-01-01'), traj_cols=tc) +traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc) -user_data_hdb = traj.assign(cluster=HDBSCAN.hdbscan_labels(traj, time_thresh=240, min_pts=3, min_cluster_size=2, traj_cols=tc)) stops_hdb = HDBSCAN.st_hdbscan(traj, time_thresh=720, min_pts=3, complete_output=True, - traj_cols=tc) -stops_hdb["cluster"] = post.remove_overlaps(user_data_hdb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) + traj_cols=tc) # %% -fig, ax_barcode = plt.subplots(figsize=(10,1.5)) +fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5), + gridspec_kw={'height_ratios':[10,1]}) + +gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3') +city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c') + +plot_stops(stops_hdb, ax=ax_map, cmap='Blues') +plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc) +ax_map.set_axis_off() plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True) -plot_stops_barcode(stops_hdb, ax=ax_barcode, stop_color='blue', set_xlim=False, timestamp='unix_ts') -fig.suptitle("HDBSCAN stops with post-processing") +plot_stops_barcode(stops_hdb, ax=ax_barcode, cmap='Blues', set_xlim=False, timestamp='unix_ts') + +plt.tight_layout(pad=0.1) plt.show() diff --git a/examples/ingesting-data.ipynb b/examples/ingesting-data.ipynb new file mode 100644 index 00000000..ecee86c5 --- /dev/null +++ b/examples/ingesting-data.ipynb @@ -0,0 +1,139 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "18b18c53", + "metadata": { + "id": "460ff464-7812-41fb-bc5b-bc4f24e16499" + }, + "source": [ + "# Loading Trajectory Data\n", + "\n", + "Mobility data comes in many formats: timestamps as unix integers or ISO strings (with timezones), \n", + "coordinates in lat/lon or projected, files as single CSVs or partitioned directories.\n", + "\n", + "`nomad.io.from_file` handles these cases with a single function call." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e3245095", + "metadata": {}, + "outputs": [], + "source": [ + "import glob\n", + "import pandas as pd\n", + "import nomad.io.base as loader\n", + "import nomad.data as data_folder\n", + "from pathlib import Path\n", + "\n", + "data_dir = Path(data_folder.__file__).parent" + ] + }, + { + "cell_type": "markdown", + "id": "a48b37e7", + "metadata": {}, + "source": [ + "## Pandas vs nomad.io for partitioned data\n", + "\n", + "Partitioned directories (e.g., `date=2024-01-01/`, `date=2024-01-02/`, ...) require a loop with pandas:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a3a2a1b", + "metadata": {}, + "outputs": [], + "source": [ + "csv_files = glob.glob(str(data_dir / \"partitioned_csv\" / \"*\" / \"*.csv\"))\n", + "df_list = []\n", + "for f in csv_files:\n", + " df_list.append(pd.read_csv(f))\n", + "df_pandas = pd.concat(df_list, ignore_index=True)\n", + "\n", + "print(f\"Pandas: {len(df_pandas)} rows\")\n", + "print(df_pandas.dtypes)\n", + "print(\"\\nFirst few rows:\")\n", + "print(df_pandas.head(3))" + ] + }, + { + "cell_type": "markdown", + "id": "71eb4656", + "metadata": {}, + "source": [ + "`nomad.io.from_file` handles partitioned directories in one line, plus automatic type casting and column mapping:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d48bf128", + "metadata": {}, + "outputs": [], + "source": [ + "traj_cols = {\"user_id\": \"user_id\",\n", + " \"latitude\": \"dev_lat\",\n", + " \"longitude\": \"dev_lon\",\n", + " \"datetime\": \"local_datetime\"}\n", + "\n", + "df = loader.from_file(data_dir / \"partitioned_csv\", format=\"csv\", traj_cols=traj_cols, parse_dates=True)\n", + "print(f\"nomad.io: {len(df)} rows\")\n", + "print(df.dtypes)\n", + "print(\"\\nFirst few rows:\")\n", + "print(df.head(3))\n", + "print(\"\\nNote: 'local_datetime' is now datetime64[ns], not object!\")" + ] + }, + { + "cell_type": "markdown", + "id": "20c187a2", + "metadata": {}, + "source": [ + "The same pattern works for Parquet files, with the type casting and processing relying on passing to the functions which columns correspond to the default \"typical\" spatio-temporal column names" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ca035ae", + "metadata": {}, + "outputs": [], + "source": [ + "traj_cols = {\"user_id\": \"uid\", \"timestamp\": \"timestamp\", \n", + " \"latitude\": \"latitude\", \"longitude\": \"longitude\", \"date\": \"date\"}\n", + "\n", + "df = loader.from_file(data_dir / \"partitioned_parquet\", format=\"parquet\", traj_cols=traj_cols, parse_dates=True)\n", + "print(f\"Loaded {len(df)} rows\")\n", + "print(df.dtypes)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e2f4479", + "metadata": {}, + "outputs": [], + "source": [ + "# These are the default canonical columnn names\n", + "from nomad.constants import DEFAULT_SCHEMA\n", + "print(DEFAULT_SCHEMA.keys())" + ] + } + ], + "metadata": { + "jupytext": { + "formats": "ipynb,py:percent" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/ingesting-data.py b/examples/ingesting-data.py new file mode 100644 index 00000000..4cc46fd9 --- /dev/null +++ b/examples/ingesting-data.py @@ -0,0 +1,80 @@ +# --- +# jupyter: +# jupytext: +# formats: ipynb,py:percent +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: Python 3 (ipykernel) +# language: python +# name: python3 +# --- + +# %% [markdown] id="460ff464-7812-41fb-bc5b-bc4f24e16499" +# # Loading Trajectory Data +# +# Mobility data comes in many formats: timestamps as unix integers or ISO strings (with timezones), +# coordinates in lat/lon or projected, files as single CSVs or partitioned directories. +# +# `nomad.io.from_file` handles these cases with a single function call. + +# %% +import glob +import pandas as pd +import nomad.io.base as loader +import nomad.data as data_folder +from pathlib import Path + +data_dir = Path(data_folder.__file__).parent + +# %% [markdown] +# ## Pandas vs nomad.io for partitioned data +# +# Partitioned directories (e.g., `date=2024-01-01/`, `date=2024-01-02/`, ...) require a loop with pandas: + +# %% +csv_files = glob.glob(str(data_dir / "partitioned_csv" / "*" / "*.csv")) +df_list = [] +for f in csv_files: + df_list.append(pd.read_csv(f)) +df_pandas = pd.concat(df_list, ignore_index=True) + +print(f"Pandas: {len(df_pandas)} rows") +print(df_pandas.dtypes) +print("\nFirst few rows:") +print(df_pandas.head(3)) + +# %% [markdown] +# `nomad.io.from_file` handles partitioned directories in one line, plus automatic type casting and column mapping: + +# %% +traj_cols = {"user_id": "user_id", + "latitude": "dev_lat", + "longitude": "dev_lon", + "datetime": "local_datetime"} + +df = loader.from_file(data_dir / "partitioned_csv", format="csv", traj_cols=traj_cols, parse_dates=True) +print(f"nomad.io: {len(df)} rows") +print(df.dtypes) +print("\nFirst few rows:") +print(df.head(3)) +print("\nNote: 'local_datetime' is now datetime64[ns], not object!") + +# %% [markdown] +# The same pattern works for Parquet files, with the type casting and processing relying on passing to the functions which columns correspond to the default "typical" spatio-temporal column names + +# %% +traj_cols = {"user_id": "uid", "timestamp": "timestamp", + "latitude": "latitude", "longitude": "longitude", "date": "date"} + +df = loader.from_file(data_dir / "partitioned_parquet", format="parquet", traj_cols=traj_cols, parse_dates=True) +print(f"Loaded {len(df)} rows") +print(df.dtypes) + +# %% +# These are the default canonical columnn names +from nomad.constants import DEFAULT_SCHEMA +print(DEFAULT_SCHEMA.keys()) diff --git a/examples/lachesis_demo.ipynb b/examples/lachesis_demo.ipynb index a46d838b..cb9a8992 100644 --- a/examples/lachesis_demo.ipynb +++ b/examples/lachesis_demo.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "92838936", + "id": "05c3afed", "metadata": {}, "source": [ "# Lachesis Stop Detection" @@ -10,7 +10,7 @@ }, { "cell_type": "markdown", - "id": "cb276fd9", + "id": "bca8605a", "metadata": {}, "source": [ "The first stop detection algorithm implemented in ```nomad``` is a sequential algorithm insipired by the one in _Project Lachesis: Parsing and Modeling Location Histories_ (Hariharan & Toyama). This algorithm for extracting stays is dependent on two parameters: the roaming distance and the stay duration. \n", @@ -28,32 +28,43 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "19184dee", - "metadata": {}, + "execution_count": 1, + "id": "7f0b2bb1", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:32:46.921799Z", + "iopub.status.busy": "2025-11-24T18:32:46.921799Z", + "iopub.status.idle": "2025-11-24T18:32:51.000857Z", + "shell.execute_reply": "2025-11-24T18:32:51.000857Z" + } + }, "outputs": [], "source": [ "%matplotlib inline\n", + "import matplotlib\n", + "matplotlib.use('TkAgg')\n", + "import matplotlib.pyplot as plt\n", + "plt.ion()\n", "\n", "# Imports\n", "import nomad.io.base as loader\n", + "import geopandas as gpd\n", "from shapely.geometry import box\n", - "import matplotlib.pyplot as plt\n", - "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode\n", + "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode, plot_stops, plot_pings\n", "import nomad.stop_detection.lachesis as LACHESIS\n", + "import nomad.data as data_folder\n", + "from pathlib import Path\n", "\n", "# Load data\n", + "data_dir = Path(data_folder.__file__).parent\n", + "city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet')\n", + "outer_box = box(*city.total_bounds)\n", + "\n", "filepath_root = 'gc_data_long/'\n", - "tc = {\n", - " \"user_id\": \"gc_identifier\",\n", - " \"timestamp\": \"unix_ts\",\n", - " \"x\": \"dev_x\",\n", - " \"y\": \"dev_y\",\n", - " \"ha\":\"ha\",\n", - " \"date\":\"date\"}\n", + "tc = {\"user_id\": \"gc_identifier\", \"x\": \"dev_x\", \"y\": \"dev_y\", \"timestamp\": \"unix_ts\"}\n", "\n", "users = ['admiring_brattain']\n", - "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','==', '2024-01-01'), traj_cols=tc)\n", + "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc)\n", "\n", "# Lachesis (sequential stop detection)\n", "stops = LACHESIS.lachesis(traj, delta_roam=20, dt_max = 60, dur_min=5, complete_output=True, keep_col_names=True, traj_cols=tc)" @@ -61,28 +72,32 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "570b6103", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 2, + "id": "86273598", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:32:51.005525Z", + "iopub.status.busy": "2025-11-24T18:32:51.000857Z", + "iopub.status.idle": "2025-11-24T18:32:51.355886Z", + "shell.execute_reply": "2025-11-24T18:32:51.355274Z" } - ], + }, + "outputs": [], "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", + "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", + " gridspec_kw={'height_ratios':[10,1]})\n", + "\n", + "gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3')\n", + "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c')\n", + "\n", + "plot_stops(stops, ax=ax_map, cmap='Blues')\n", + "plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc)\n", + "ax_map.set_axis_off()\n", + "\n", + "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", + "plot_stops_barcode(stops, ax=ax_barcode, cmap='Blues', set_xlim=False, timestamp='unix_ts')\n", "\n", - "plot_time_barcode(traj[tc['timestamp']], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops, ax=ax_barcode, stop_color='blue', set_xlim=False, timestamp='unix_ts')\n", - "fig.suptitle(\"Lachesis stops\")\n", - "plt.tight_layout()\n", + "plt.tight_layout(pad=0.1)\n", "plt.show()" ] } @@ -103,7 +118,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/examples/lachesis_demo.py b/examples/lachesis_demo.py index 6598fb9e..fc916225 100644 --- a/examples/lachesis_demo.py +++ b/examples/lachesis_demo.py @@ -30,29 +30,47 @@ # %% # %matplotlib inline +import matplotlib +matplotlib.use('TkAgg') +import matplotlib.pyplot as plt +plt.ion() # Imports import nomad.io.base as loader +import geopandas as gpd from shapely.geometry import box -import matplotlib.pyplot as plt -from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode +from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode, plot_stops, plot_pings import nomad.stop_detection.lachesis as LACHESIS +import nomad.data as data_folder +from pathlib import Path # Load data +data_dir = Path(data_folder.__file__).parent +city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet') +outer_box = box(*city.total_bounds) + filepath_root = 'gc_data_long/' tc = {"user_id": "gc_identifier", "x": "dev_x", "y": "dev_y", "timestamp": "unix_ts"} users = ['admiring_brattain'] -traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','==', '2024-01-01'), traj_cols=tc) +traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc) # Lachesis (sequential stop detection) stops = LACHESIS.lachesis(traj, delta_roam=20, dt_max = 60, dur_min=5, complete_output=True, keep_col_names=True, traj_cols=tc) # %% -fig, ax_barcode = plt.subplots(figsize=(10,1.5)) +fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5), + gridspec_kw={'height_ratios':[10,1]}) + +gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3') +city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c') + +plot_stops(stops, ax=ax_map, cmap='Blues') +plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc) +ax_map.set_axis_off() plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True) -plot_stops_barcode(stops, ax=ax_barcode, stop_color='blue', set_xlim=False, timestamp='unix_ts') -fig.suptitle("Lachesis stops") -plt.tight_layout() +plot_stops_barcode(stops, ax=ax_barcode, cmap='Blues', set_xlim=False, timestamp='unix_ts') + +plt.tight_layout(pad=0.1) plt.show() diff --git a/examples/tadbscan_demo.ipynb b/examples/tadbscan_demo.ipynb index 6e28d6b4..c4f5747b 100644 --- a/examples/tadbscan_demo.ipynb +++ b/examples/tadbscan_demo.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "92838936", + "id": "7c76d1d8", "metadata": {}, "source": [ "# TADBSCAN Stop Detection" @@ -10,7 +10,7 @@ }, { "cell_type": "markdown", - "id": "cb276fd9", + "id": "f29a96ce", "metadata": {}, "source": [ "The second stop detection algorithm implemented in ```nomad``` is an adaptation of DBSCAN. Unlike in plain DBSCAN, we also incorporate the time dimension to determine if two pings are \"neighbors\". This implementation relies on 3 parameters\n", @@ -24,79 +24,81 @@ }, { "cell_type": "code", - "execution_count": 4, - "id": "19184dee", - "metadata": {}, + "execution_count": 1, + "id": "1e62c25a", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:03.240035Z", + "iopub.status.busy": "2025-11-24T18:33:03.240035Z", + "iopub.status.idle": "2025-11-24T18:33:05.816985Z", + "shell.execute_reply": "2025-11-24T18:33:05.816985Z" + } + }, "outputs": [], "source": [ "%matplotlib inline\n", + "import matplotlib\n", + "matplotlib.use('TkAgg')\n", + "import matplotlib.pyplot as plt\n", + "plt.ion()\n", "\n", "# Imports\n", "import nomad.io.base as loader\n", "import geopandas as gpd\n", "from shapely.geometry import box\n", - "import pandas as pd\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode\n", + "from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode, plot_stops, plot_pings\n", "import nomad.stop_detection.dbscan as DBSCAN\n", - "import nomad.filters as filters \n", - "import nomad.stop_detection.postprocessing as post\n", "\n", "# Load data\n", - "city = gpd.read_file(\"garden_city.geojson\").to_crs('EPSG:3857')\n", - "outer_box = box(*city.total_bounds).buffer(15, join_style='mitre')\n", + "import nomad.data as data_folder\n", + "from pathlib import Path\n", + "data_dir = Path(data_folder.__file__).parent\n", + "city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet')\n", + "outer_box = box(*city.total_bounds)\n", "\n", "filepath_root = 'gc_data_long/'\n", - "tc = {\n", - " \"user_id\": \"gc_identifier\",\n", - " \"timestamp\": \"unix_ts\",\n", - " \"x\": \"dev_x\",\n", - " \"y\": \"dev_y\",\n", - " \"ha\":\"ha\",\n", - " \"date\":\"date\"}\n", + "tc = {\"user_id\": \"gc_identifier\", \"x\": \"dev_x\", \"y\": \"dev_y\", \"timestamp\": \"unix_ts\"}\n", "\n", "# Density based stop detection (Temporal DBSCAN)\n", - "users = ['confident_aryabhata']\n", - "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','<=', '2024-01-03'), traj_cols=tc)\n", - "traj[['longitude','latitude']] = np.column_stack(\n", - " filters.to_projection(traj, x='dev_x', y='dev_y', data_crs='EPSG:3857', crs_to='EPSG:4326')\n", - ")\n", + "users = ['admiring_brattain']\n", + "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc)\n", "\n", - "user_data_tadb = traj.assign(cluster=DBSCAN.ta_dbscan_labels(traj, time_thresh=240, dist_thresh=15, min_pts=3, traj_cols=tc))\n", "stops_tadb = DBSCAN.ta_dbscan(traj,\n", " time_thresh=720,\n", " dist_thresh=15,\n", " min_pts=3,\n", " complete_output=True,\n", - " traj_cols=tc)\n", - "stops_tadb[\"cluster\"] = post.remove_overlaps(user_data_tadb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) " + " traj_cols=tc) " ] }, { "cell_type": "code", - "execution_count": 5, - "id": "2159107b", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA9oAAACMCAYAAACDKNEwAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAp9ElEQVR4nO3deXxU1d3H8e9kyGQhCxACJRC2qGwxWFbBUnaRTQXUAsoiqPUpYkFUtC4Ea4uIIlgU9DEsigZEAQUECgjiEhAr2AcBl0oEhCgGSSIQkOQ8f9C5zJpMwmQI8nm/XvOCOXPuOeeee+Y385u5c2MzxhgBAAAAAICgCDvfAwAAAAAA4NeERBsAAAAAgCAi0QYAAAAAIIhItAEAAAAACCISbQAAAAAAgohEGwAAAACAICLRBgAAAAAgiEi0AQAAAAAIIhJtAAAAAACCiEQbAMrAZrMFdNu0aZO1zbPPPiubzabU1NQy9bVp0ya3Nh0OhxITE3XVVVfpoYce0rfffuu1zfz5873GkpiYqC5dumjlypVe9Xfv3q1hw4apcePGioyMVM2aNdWqVSvdddddys/P96q/YsUK9e/fX7Vr15bD4VCNGjXUvXt3vfrqq/rll1+86v/444+KiIiQzWbTJ5984nM/R44cKZvNphYtWqioqMjrcZvNprvuuiuQKSvRwYMHlZ6erh07dpxzW5VNdna2bDab5s+fb5V99NFHSk9P19GjR73qN2zYUP369QvdAIPgnXfeUXp6+vkexgVl5MiRatiw4fkeBgBclEi0AaAMsrKy3G59+vRRVFSUV3mrVq2sbebOnStJ+vzzz7V169Yy9/n3v/9dWVlZ2rhxozIyMtSlSxfNnTtXzZo106uvvupzm3nz5ikrK0sfffSRXnzxRdntdvXv318rVqyw6mzfvl2tW7fWrl279Oijj2rNmjWaM2eO+vbtq7Vr1+rIkSNWXWOMbr31Vl177bUqLi7W9OnTtX79ei1YsEAtW7bUn/70Jz3//PNe43jllVd06tQpSVJGRkaJ+7lr1y63RDHYDh48qMmTJ/8qE+06deooKytLffv2tco++ugjTZ482WeifSF65513NHny5PM9jAvKI488omXLlp3vYQDARanK+R4AAFxIrrzySrf7iYmJCgsL8yp3+uSTT/TZZ5+pb9++WrVqlTIyMtS+ffsy9XnppZe6tX/ttddqwoQJ6tGjh0aOHKm0tDRdfvnlbtukpqaqTZs21v1rrrlG1atXV2Zmpvr37y9JmjFjhsLCwrRp0ybFxsZadW+44Qb99a9/lTHGKps2bZrmz5+vyZMn69FHH3Xrq3///rr//vv19ddfe4197ty5qlWrlho0aKDMzExNnz5dUVFRXvWqVq2qVq1aadKkSRo6dKjPOvAvIiLC7xpE+fzyyy+y2WyqUuXCfauUkpJyvocAABctvtEGgArk/Bb3iSeeUMeOHbVo0SIdP378nNutUaOGXnjhBZ0+fVrPPPNMqfUjIyPlcDgUHh5uleXm5iouLk4xMTE+t7HZbJLOJBxTp05V06ZN9cgjj/is+5vf/Ea/+93v3Mq2bt2qnTt3atiwYbr99tuVl5enN9980+8Yp06dqu+++04zZ84sdX98WbJkidq3b6/4+HhFR0ercePGGjVqlKQzp+G3bdtWknTrrbdap9W7nor89ttvq0OHDoqOjlZsbKx69uyprKwstz7S09Nls9m0fft2DRw4UHFxcYqPj9ctt9yiw4cPu9V999131aVLFyUkJCgqKkr169fXoEGDSjz+9913n+Lj491OoR87dqxsNpumTZtmleXm5iosLEz/+Mc/JHmfOp6enq777rtPktSoUSOfP2mQpDVr1qhVq1aKiopS06ZNrbMvSuLs68knn9Tf/vY31a9fX5GRkWrTpo02bNjgVf+DDz5Q9+7dFRsbq+joaHXs2FGrVq1yq3P8+HHde++9atSokSIjI1WjRg21adNGmZmZks6cAv3cc89Jcv/5RnZ2doljdZ4iv2zZMqWlpSkyMlKNGzfWs88+61bP+TONV155RRMmTFDdunUVERFhfXg0d+5ctWzZ0hrbgAEDtHv3bq/+tm7dqv79+yshIUGRkZFKSUnRuHHj3Op89dVXGjp0qGrVqqWIiAg1a9bM2jen4uJiPf7442rSpImioqJUrVo1paWluT03Dh8+rDvuuEPJycmKiIiwflayfv16q46vU8edP8V45ZVX1KxZM0VHR6tly5Y+f1ry1ltvKS0tTREREWrcuLFmzpxpPQcAACUj0QaACnLixAllZmaqbdu2Sk1N1ahRo1RQUKAlS5YEpf22bduqTp062rx5s9djRUVFOn36tH755RcdOHBA48aN07FjxzR06FCrTocOHXTo0CHdfPPNeu+993TixAmf/XzyySc6cuSIrrvuujK9wXZ+yDBq1CgNHjxY0dHRJZ4+3qFDBw0YMEBTp051O209EFlZWfrDH/6gxo0ba9GiRVq1apUeffRRnT59WpLUqlUrzZs3T5L08MMPW6f433bbbZKk1157Tdddd53i4uKUmZmpjIwM/fTTT+rSpYs++OADr/4GDBigSy65RG+88YbS09O1fPly9erVy/qdenZ2tvr27SuHw6G5c+dqzZo1euKJJ1S1alXrVHpfevToofz8fH388cdW2fr16xUVFaV169ZZZRs2bJAxRj169PDZzm233aaxY8dKkpYuXerzJw2fffaZJkyYoPHjx1sJ1ejRo32uJ19mzZqlNWvWaMaMGVq4cKHCwsLUu3dvtw8n3nvvPXXr1k15eXnKyMhQZmamYmNj1b9/fy1evNiqd88992j27Nm6++67tWbNGr3yyiu68cYblZubK+nMKdA33HCDJPefb9SpU6fUce7YsUPjxo3T+PHjtWzZMnXs2FF//vOf9dRTT3nVffDBB7Vv3z7NmTNHK1asUK1atTRlyhSNHj1aLVq00NKlSzVz5kz9+9//VocOHfTVV19Z265du1adOnXSvn37NH36dK1evVoPP/ywvv/+e6vOrl271LZtW+3cuVNPP/20Vq5cqb59++ruu+92Oy3+ySefVHp6uoYMGaJVq1Zp8eLFGj16tNvPAIYNG6bly5fr0Ucf1T//+U+99NJL6tGjhzVnJVm1apVmzZqlxx57TG+++ab14cE333xj1VmzZo0GDhyohIQELV68WE8++aQyMzO1YMGCUtsHAEgyAIByGzFihKlatarPx15++WUjycyZM8cYY0xBQYGJiYkxnTp1CqjtjRs3GklmyZIlfuu0b9/eREVFWffnzZtnJHndIiIizPPPP++2bWFhobn++uutOna73fz2t781Dz30kPnhhx+seosWLXLbj0AcO3bMxMXFmSuvvNIqGzFihLHZbObrr792q+s6h3v27DF2u91MmDDBelySGTNmTIn9PfXUU0aSOXr0qN8627ZtM5LMvHnz3MqLiopMUlKSufzyy01RUZFVXlBQYGrVqmU6duxolU2aNMlIMuPHj3dr49VXXzWSzMKFC40xxrzxxhtGktmxY0eJ4/Z07Ngx43A4zGOPPWaMMebAgQNGkpk4caKJiooyhYWFxhhjbr/9dpOUlGRtt3fvXq99mzZtmpFk9u7d69VPgwYNTGRkpPn222+tshMnTpgaNWqYP/7xjyWO0dlXUlKSOXHihFWen59vatSoYXr06GGVXXnllaZWrVqmoKDAKjt9+rRJTU019erVM8XFxcYYY1JTU831119fYr9jxowxZX3b0qBBA2Oz2byOQ8+ePU1cXJw5duyYMebsc+33v/+9W72ffvrJREVFmT59+riV79u3z0RERJihQ4daZSkpKSYlJcVtTjz16tXL1KtXz+Tl5bmV33XXXSYyMtIcOXLEGGNMv379zBVXXFHivsXExJhx48aVWGfEiBGmQYMGbmWSTO3atU1+fr5VlpOTY8LCwsyUKVOssrZt25rk5GRz8uRJq6ygoMAkJCSU+TgAwMWIb7QBoIJkZGQoKipKgwcPliTFxMToxhtv1Pvvv+/2Tdjp06fdbsblt9Gl8Vf35Zdf1rZt27Rt2zatXr1aI0aM0JgxYzRr1iyrTkREhJYtW6Zdu3bpmWee0eDBg3X48GH97W9/U7NmzfTFF1+Uc8+l119/Xfn5+dap29KZb7aNMdY3y740adJEo0eP1qxZs7Rv376A+3OeFn7TTTfp9ddf13fffRfwtl988YUOHjyoYcOGKSzs7MtiTEyMBg0apC1btnid7n3zzTe73b/ppptUpUoVbdy4UZJ0xRVXyOFw6I477tCCBQvcviksSXR0tDp06GCd/rtu3TpVq1ZN9913n06dOmV9u75+/Xq/32YH6oorrlD9+vWt+5GRkbrssst8Xs3el4EDByoyMtK67/ymevPmzSoqKtKxY8e0detW3XDDDW4/T7Db7Ro2bJgOHDhgrbF27dpp9erVeuCBB7Rp0ya/Z1f4Ulxc7Pb88bxyfYsWLdSyZUu3sqFDhyo/P1+ffvqpW/mgQYPc7mdlZenEiRMaOXKkW3lycrK6detmnSr/5Zdf6j//+Y9Gjx7tNieuCgsLtWHDBg0YMEDR0dFuY+7Tp48KCwu1ZcsWaz4+++wz/elPf9LatWt9/gWAdu3aaf78+Xr88ce1ZcsWn1f996dr165u12WoXbu2atWqZR37Y8eO6ZNPPtH1118vh8Nh1YuJibGu8QAAKBmJNgBUgK+//lqbN29W3759ZYzR0aNHdfToUev0V+dvYbOzsxUeHu52e++99wLuZ9++fUpKSvIqb9asmdq0aaM2bdrommuu0QsvvKCrr75a999/v9dVqJs1a6Zx48Zp4cKF1mmvubm51u+xncnY3r17Ax5XRkaGIiMjdc0111j7npaWpoYNG2r+/Pk+/4yXU3p6uux2u9/fg/vy+9//XsuXL9fp06c1fPhw1atXT6mpqdZvfEviPNXW12nISUlJKi4u1k8//eRW/pvf/MbtfpUqVZSQkGC1lZKSovXr16tWrVoaM2aMUlJSlJKSEtDvz3v06KEtW7bo2LFjWr9+vbp166aEhAS1bt1a69ev1969e7V3795zTrQTEhK8yiIiIgJOcj3nwFl26tQp/fzzz/rpp59kjPE7r9LZuX/22Wc1ceJELV++XF27dlWNGjV0/fXXu30g5c+oUaPcnj/du3cPaJyu/Tt5jrW0teF83Pn7/Hr16vkdZ25urk6fPq1//OMfXs/5Pn36SDrz5/CkM6ewP/XUU9qyZYt69+6thIQEde/e3e1P5C1evFgjRozQSy+9pA4dOqhGjRoaPny4cnJy/I7BqbRj7zx2tWvX9qrnqwwA4I1EGwAqwNy5c2WM0RtvvKHq1atbN+efX1qwYIGKioqUlJRkffPsvLVu3TqgPj7++GPl5OSoS5cuAdVPS0vTiRMn9OWXX/qtY7PZNH78eFWrVk07d+6UJLVp00Y1atTQW2+9FdC37V9++aU++OADFRYWqn79+m77n52dre+++05r1671u32dOnWsxP/f//53QPsmSdddd502bNigvLw8bdq0SfXq1dPQoUO9LmjmyZl0HDp0yOuxgwcPKiwsTNWrV3cr90xmTp8+rdzcXLcEplOnTlqxYoXy8vK0ZcsWdejQQePGjdOiRYtKHE/37t116tQpbd68WRs2bFDPnj2t8nXr1lm/1fZMKEPNV0KXk5Mjh8OhmJgYVa9eXWFhYX7nVZJq1qwp6cxV5ydPnqw9e/YoJydHs2fP1pYtWwL69jQ9Pd3t+fPCCy8ENE7JO+H0vAZBaWvDOf7ExERJ0oEDB/yOs3r16rLb7Ro5cqTXc955cybcVapU0T333KNPP/1UR44cUWZmpvbv369evXpZZ1fUrFlTM2bMUHZ2tr799ltNmTJFS5cu9fr2vTyqV68um83m9vtyp0ASeQAAiTYABF1RUZEWLFiglJQUbdy40es2YcIEHTp0SKtXr5bD4bC+eXbeXE/p9OfIkSO68847FR4ervHjxwc0Luffj3YmBb6SB+lMApGfn2996xgeHq6JEydqz549+utf/+pzmx9++EEffvihpLMXQfvf//1fr31/5513FB4eXurVrSdOnKgaNWrogQceCGjfXEVERKhz586aOnWqpDN/L9xZLsnrG9smTZqobt26eu2119w+SDh27JjefPNN60rkrjz/fvnrr7+u06dP+/zQw263q3379taVpT1PV/bUrl07xcXFacaMGcrJybES7R49emj79u16/fXX1bx5c59nMnjOg6/9DZalS5eqsLDQul9QUKAVK1aoU6dOstvtqlq1qtq3b6+lS5e6jaG4uFgLFy5UvXr1dNlll3m1W7t2bY0cOVJDhgzRF198YSWW/vanYcOGbs+fJk2auD3++eef67PPPnMre+211xQbG+t2cThfOnTooKioKC1cuNCt/MCBA3r33XetDzsuu+wypaSkaO7cuTp58qTPtqKjo9W1a1dt375daWlpXs/7Nm3a+PymuVq1arrhhhs0ZswYHTlyxOeV1uvXr6+77rpLPXv2LHV9BaJq1apq06aNli9f7nbxvp9//tnn1ckBAN4u3D8OCQCV1OrVq3Xw4EFNnTrVZ+KVmpqqWbNmKSMjQ/369Su1va+++kpbtmxRcXGxcnNztXXrVmVkZCg/P18vv/yyWrRo4bXNzp07rStu5+bmaunSpVq3bp0GDBigRo0aSZLuuOMOHT16VIMGDVJqaqrsdrv27NmjZ555RmFhYZo4caLV3n333afdu3dr0qRJ+vjjjzV06FAlJycrLy9Pmzdv1osvvqjJkyerffv2evnll9WsWTPrit6e+vfvr7fffluHDx+2kn5PcXFxeuihhwL+EOHRRx/VgQMH1L17d9WrV09Hjx7VzJkzFR4ers6dO0s6czp3VFSUXn31VTVr1kwxMTFKSkpSUlKSnnzySd18883q16+f/vjHP+rkyZOaNm2ajh49qieeeMKrv6VLl6pKlSrq2bOnPv/8cz3yyCNq2bKlbrrpJknSnDlz9O6776pv376qX7++CgsLrQ8XSjvl2263q3PnzlqxYoUaNWpk/S3kq666ShEREdqwYYPuvvvuUufE+bfVZ86cqREjRig8PFxNmjQJ6IOcQNjtdvXs2VP33HOPiouLNXXqVOXn57tdPXvKlCnq2bOnunbtqnvvvVcOh0PPP/+8du7cqczMTOsb5Pbt26tfv35KS0tT9erVtXv3br3yyituH3I492fq1Knq3bu37Ha70tLS3H5D7EtSUpKuvfZapaenq06dOlq4cKHWrVunqVOnen2A4qlatWp65JFH9Je//EXDhw/XkCFDlJubq8mTJysyMlKTJk2y6j733HPq37+/rrzySo0fP17169fXvn37tHbtWuuDmZkzZ+p3v/udOnXqpP/5n/9Rw4YNVVBQoK+//lorVqzQu+++K+nMcyQ1NVVt2rRRYmKivv32W82YMUMNGjTQpZdeqry8PHXt2lVDhw5V06ZNFRsbq23btllXCg+Gxx57TH379lWvXr305z//WUVFRZo2bZpiYmLK/FcBAOCidN4uwwYAvwK+rjp+/fXXG4fD4Xblbk+DBw82VapUMTk5OX7rOK+E7LxVqVLFJCQkmA4dOpi//OUvJjs722sbX1cdj4+PN1dccYWZPn26ddVqY4xZu3atGTVqlGnevLmJj483VapUMXXq1DEDBw40WVlZPsf01ltvmb59+5rExERTpUoVU716ddO1a1czZ84cc/LkSbN8+XIjycyYMcPvfq1Zs8ZIMk8//bTfOTTGmJMnT5pGjRoFdNXxlStXmt69e5u6desah8NhatWqZfr06WPef/99t3qZmZmmadOmJjw83EgykyZNsh5bvny5ad++vYmMjDRVq1Y13bt3Nx9++KHb9s6rjv/rX/8y/fv3NzExMSY2NtYMGTLEfP/991a9rKwsM2DAANOgQQMTERFhEhISTOfOnc3bb79d4n44zZw500gyt99+u1t5z549jSSvdnxdddwYYx588EGTlJRkwsLCjCSzceNGY8yZq3H37dvXq9/OnTubzp07lzg2Z19Tp041kydPNvXq1TMOh8P89re/NWvXrvWq//7775tu3bqZqlWrmqioKHPllVeaFStWuNV54IEHTJs2bUz16tVNRESEady4sRk/frz58ccfrTonT540t912m0lMTDQ2m83vFdVdOffzjTfeMC1atDAOh8M0bNjQTJ8+3a1eaVf4f+mll0xaWppxOBwmPj7eXHfddebzzz/3qpeVlWV69+5t4uPjTUREhElJSfG6Qv3evXvNqFGjTN26dU14eLhJTEw0HTt2NI8//rhV5+mnnzYdO3Y0NWvWNA6Hw9SvX9+MHj3aes4XFhaaO++806SlpZm4uDgTFRVl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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 2, + "id": "df942a2c", + "metadata": { + "execution": { + "iopub.execute_input": "2025-11-24T18:33:05.816985Z", + "iopub.status.busy": "2025-11-24T18:33:05.816985Z", + "iopub.status.idle": "2025-11-24T18:33:06.001663Z", + "shell.execute_reply": "2025-11-24T18:33:06.001511Z" } - ], + }, + "outputs": [], "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", + "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", + " gridspec_kw={'height_ratios':[10,1]})\n", + "\n", + "gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3')\n", + "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c')\n", + "\n", + "plot_stops(stops_tadb, ax=ax_map, cmap='Reds')\n", + "plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc)\n", + "ax_map.set_axis_off()\n", "\n", "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_tadb, ax=ax_barcode, stop_color='red', set_xlim=False, timestamp='unix_ts')\n", - "plt.title(\"TA-DBSCAN stops with post-processing\")\n", - "plt.tight_layout()\n", + "plot_stops_barcode(stops_tadb, ax=ax_barcode, cmap='Reds', set_xlim=False, timestamp='unix_ts')\n", + "\n", + "plt.tight_layout(pad=0.1)\n", "plt.show()" ] } @@ -117,7 +119,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/examples/tadbscan_demo.py b/examples/tadbscan_demo.py index 9e63e671..7b6eb0d3 100644 --- a/examples/tadbscan_demo.py +++ b/examples/tadbscan_demo.py @@ -26,46 +26,52 @@ # %% # %matplotlib inline +import matplotlib +matplotlib.use('TkAgg') +import matplotlib.pyplot as plt +plt.ion() # Imports import nomad.io.base as loader import geopandas as gpd from shapely.geometry import box -import pandas as pd -import numpy as np -import matplotlib.pyplot as plt -from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode +from nomad.stop_detection.viz import plot_stops_barcode, plot_time_barcode, plot_stops, plot_pings import nomad.stop_detection.dbscan as DBSCAN -import nomad.filters as filters -import nomad.stop_detection.postprocessing as post # Load data -from nomad.city_gen import City -city_obj = City.from_geopackage("garden-city.gpkg") -# Create a simple bounds box for visualization -outer_box = box(0, 0, city_obj.dimensions[0], city_obj.dimensions[1]) +import nomad.data as data_folder +from pathlib import Path +data_dir = Path(data_folder.__file__).parent +city = gpd.read_parquet(data_dir / 'garden-city-buildings-mercator.parquet') +outer_box = box(*city.total_bounds) filepath_root = 'gc_data_long/' tc = {"user_id": "gc_identifier", "x": "dev_x", "y": "dev_y", "timestamp": "unix_ts"} # Density based stop detection (Temporal DBSCAN) -users = ['confident_aryabhata'] -traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','<=', '2024-01-03'), traj_cols=tc) +users = ['admiring_brattain'] +traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters=('date','==', '2024-01-01'), traj_cols=tc) -user_data_tadb = traj.assign(cluster=DBSCAN.ta_dbscan_labels(traj, time_thresh=240, dist_thresh=15, min_pts=3, traj_cols=tc)) stops_tadb = DBSCAN.ta_dbscan(traj, time_thresh=720, dist_thresh=15, min_pts=3, complete_output=True, - traj_cols=tc) -stops_tadb["cluster"] = post.remove_overlaps(user_data_tadb, time_thresh=240, method='cluster', traj_cols=tc, min_pts=3, dur_min=5, min_cluster_size=3) + traj_cols=tc) # %% -fig, ax_barcode = plt.subplots(figsize=(10,1.5)) +fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5), + gridspec_kw={'height_ratios':[10,1]}) + +gpd.GeoDataFrame(geometry=[outer_box], crs='EPSG:3857').plot(ax=ax_map, color='#d3d3d3') +city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#8c8c8c') + +plot_stops(stops_tadb, ax=ax_map, cmap='Reds') +plot_pings(traj, ax=ax_map, s=6, color='black', alpha=0.5, traj_cols=tc) +ax_map.set_axis_off() plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True) -plot_stops_barcode(stops_tadb, ax=ax_barcode, stop_color='red', set_xlim=False, timestamp='unix_ts') -plt.title("TA-DBSCAN stops with post-processing") -plt.tight_layout() +plot_stops_barcode(stops_tadb, ax=ax_barcode, cmap='Reds', set_xlim=False, timestamp='unix_ts') + +plt.tight_layout(pad=0.1) plt.show() diff --git a/nomad/data/garden-city-buildings-mercator.parquet b/nomad/data/garden-city-buildings-mercator.parquet new file mode 100644 index 00000000..aceac951 Binary files /dev/null and b/nomad/data/garden-city-buildings-mercator.parquet differ diff --git a/nomad/data/garden-city-buildings.geojson b/nomad/data/garden-city-buildings.geojson new file mode 100644 index 00000000..41979fd3 --- /dev/null +++ b/nomad/data/garden-city-buildings.geojson @@ -0,0 +1,113 @@ +{ +"type": "FeatureCollection", +"name": "garden-city-buildings", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, +"features": [ +{ "type": "Feature", "properties": { "index": "p-x12-y11", "id": "p-x12-y11", "building_type": "park", "door_cell_x": 13, "door_cell_y": 11, "size": 16, "door_point_x": 13.0, "door_point_y": 11.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317674376729549, 36.669298771960719 ], [ -38.317674376729549, 36.669731091674187 ], [ -38.318213365900021, 36.669731091674187 ], [ -38.318213365900021, 36.669298771960719 ], [ -38.317674376729549, 36.669298771960719 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x7-y8", "id": "h-x7-y8", "building_type": "home", "door_cell_x": 8, "door_cell_y": 8, "size": 2, "door_point_x": 8.0, "door_point_y": 8.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318482860485254, 36.669082611193211 ], [ -38.318348113192641, 36.669082611193211 ], [ -38.318348113192641, 36.669298771960719 ], [ -38.318482860485254, 36.669298771960719 ], [ -38.318482860485254, 36.669082611193211 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x9-y7", "id": "h-x9-y7", "building_type": "home", "door_cell_x": 9, "door_cell_y": 8, "size": 2, "door_point_x": 9.5, "door_point_y": 8.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318348113192641, 36.669082611193211 ], [ -38.318078618607409, 36.669082611193211 ], [ -38.318078618607409, 36.669190691652865 ], [ -38.318348113192641, 36.669190691652865 ], [ -38.318348113192641, 36.669082611193211 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x10-y7", "id": "h-x10-y7", "building_type": "home", "door_cell_x": 10, "door_cell_y": 8, "size": 1, "door_point_x": 10.5, "door_point_y": 8.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317943871314782, 36.669082611193211 ], [ -38.317943871314782, 36.669190691652865 ], [ -38.318078618607409, 36.669190691652865 ], [ -38.318078618607409, 36.669082611193211 ], [ -38.317943871314782, 36.669082611193211 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x11-y7", "id": "h-x11-y7", "building_type": "home", "door_cell_x": 11, "door_cell_y": 8, "size": 1, "door_point_x": 11.5, "door_point_y": 8.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317809124022169, 36.669082611193211 ], [ -38.317809124022169, 36.669190691652865 ], [ -38.317943871314782, 36.669190691652865 ], [ -38.317943871314782, 36.669082611193211 ], [ -38.317809124022169, 36.669082611193211 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x13-y7", "id": "h-x13-y7", "building_type": "home", "door_cell_x": 13, "door_cell_y": 6, "size": 1, "door_point_x": 13.5, "door_point_y": 7.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317539629436936, 36.669082611193211 ], [ -38.317539629436936, 36.669190691652865 ], [ -38.317674376729549, 36.669190691652865 ], [ -38.317674376729549, 36.669082611193211 ], [ -38.317539629436936, 36.669082611193211 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x14-y7", "id": "h-x14-y7", "building_type": "home", "door_cell_x": 14, "door_cell_y": 6, "size": 1, "door_point_x": 14.5, "door_point_y": 7.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317404882144309, 36.669082611193211 ], [ -38.317404882144309, 36.669190691652865 ], [ -38.317539629436936, 36.669190691652865 ], [ -38.317539629436936, 36.669082611193211 ], [ -38.317404882144309, 36.669082611193211 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x14-y8", "id": "h-x14-y8", "building_type": "home", "door_cell_x": 13, "door_cell_y": 8, "size": 1, "door_point_x": 14.0, "door_point_y": 8.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317404882144309, 36.669190691652865 ], [ -38.317404882144309, 36.669298771960719 ], [ -38.317539629436936, 36.669298771960719 ], [ -38.317539629436936, 36.669190691652865 ], [ -38.317404882144309, 36.669190691652865 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x14-y9", "id": "h-x14-y9", "building_type": "home", "door_cell_x": 13, "door_cell_y": 9, "size": 1, "door_point_x": 14.0, "door_point_y": 9.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317404882144309, 36.669298771960719 ], [ -38.317404882144309, 36.669406852116779 ], [ -38.317539629436936, 36.669406852116779 ], [ -38.317539629436936, 36.669298771960719 ], [ -38.317404882144309, 36.669298771960719 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x14-y11", "id": "h-x14-y11", "building_type": "home", "door_cell_x": 13, "door_cell_y": 11, "size": 1, "door_point_x": 14.0, "door_point_y": 11.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317404882144309, 36.669514932121047 ], [ -38.317404882144309, 36.669623011973513 ], [ -38.317539629436936, 36.669623011973513 ], [ -38.317539629436936, 36.669514932121047 ], [ -38.317404882144309, 36.669514932121047 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x14-y12", "id": "h-x14-y12", "building_type": "home", "door_cell_x": 13, "door_cell_y": 12, "size": 1, "door_point_x": 14.0, "door_point_y": 12.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317404882144309, 36.669623011973513 ], [ -38.317404882144309, 36.669731091674187 ], [ -38.317539629436936, 36.669731091674187 ], [ -38.317539629436936, 36.669623011973513 ], [ -38.317404882144309, 36.669623011973513 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x14-y13", "id": "h-x14-y13", "building_type": "home", "door_cell_x": 15, "door_cell_y": 13, "size": 1, "door_point_x": 15.0, "door_point_y": 13.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317404882144309, 36.669731091674187 ], [ -38.317404882144309, 36.669839171223067 ], [ -38.317539629436936, 36.669839171223067 ], [ -38.317539629436936, 36.669731091674187 ], [ -38.317404882144309, 36.669731091674187 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x13-y14", "id": "h-x13-y14", "building_type": "home", "door_cell_x": 13, "door_cell_y": 13, "size": 2, "door_point_x": 13.5, "door_point_y": 14.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317674376729549, 36.669839171223067 ], [ -38.317404882144309, 36.669839171223067 ], [ -38.317404882144309, 36.66994725062014 ], [ -38.317674376729549, 36.66994725062014 ], [ -38.317674376729549, 36.669839171223067 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x12-y14", "id": "h-x12-y14", "building_type": "home", "door_cell_x": 12, "door_cell_y": 13, "size": 1, "door_point_x": 12.5, "door_point_y": 14.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317674376729549, 36.669839171223067 ], [ -38.317674376729549, 36.66994725062014 ], [ -38.317809124022169, 36.66994725062014 ], [ -38.317809124022169, 36.669839171223067 ], [ -38.317674376729549, 36.669839171223067 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x11-y14", "id": "h-x11-y14", "building_type": "home", "door_cell_x": 11, "door_cell_y": 13, "size": 1, "door_point_x": 11.5, "door_point_y": 14.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317809124022169, 36.669839171223067 ], [ -38.317809124022169, 36.66994725062014 ], [ -38.317943871314782, 36.66994725062014 ], [ -38.317943871314782, 36.669839171223067 ], [ -38.317809124022169, 36.669839171223067 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x9-y14", "id": "h-x9-y14", "building_type": "home", "door_cell_x": 9, "door_cell_y": 13, "size": 1, "door_point_x": 9.5, "door_point_y": 14.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318078618607409, 36.669839171223067 ], [ -38.318078618607409, 36.66994725062014 ], [ -38.318213365900021, 36.66994725062014 ], [ -38.318213365900021, 36.669839171223067 ], [ -38.318078618607409, 36.669839171223067 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x8-y14", "id": "h-x8-y14", "building_type": "home", "door_cell_x": 8, "door_cell_y": 13, "size": 1, "door_point_x": 8.5, "door_point_y": 14.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318213365900021, 36.669839171223067 ], [ -38.318213365900021, 36.66994725062014 ], [ -38.318348113192641, 36.66994725062014 ], [ -38.318348113192641, 36.669839171223067 ], [ -38.318213365900021, 36.669839171223067 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x7-y14", "id": "h-x7-y14", "building_type": "home", "door_cell_x": 7, "door_cell_y": 15, "size": 1, "door_point_x": 7.5, "door_point_y": 15.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318348113192641, 36.669839171223067 ], [ -38.318348113192641, 36.66994725062014 ], [ -38.318482860485254, 36.66994725062014 ], [ -38.318482860485254, 36.669839171223067 ], [ -38.318348113192641, 36.669839171223067 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x7-y13", "id": "h-x7-y13", "building_type": "home", "door_cell_x": 6, "door_cell_y": 13, "size": 1, "door_point_x": 7.0, "door_point_y": 13.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318348113192641, 36.669731091674187 ], [ -38.318348113192641, 36.669839171223067 ], [ -38.318482860485254, 36.669839171223067 ], [ -38.318482860485254, 36.669731091674187 ], [ -38.318348113192641, 36.669731091674187 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x7-y12", "id": "h-x7-y12", "building_type": "home", "door_cell_x": 8, "door_cell_y": 12, "size": 1, "door_point_x": 8.0, "door_point_y": 12.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318348113192641, 36.669623011973513 ], [ -38.318348113192641, 36.669731091674187 ], [ -38.318482860485254, 36.669731091674187 ], [ -38.318482860485254, 36.669623011973513 ], [ -38.318348113192641, 36.669623011973513 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "h-x7-y10", "id": "h-x7-y10", "building_type": "home", "door_cell_x": 8, "door_cell_y": 10, "size": 2, "door_point_x": 8.0, "door_point_y": 10.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318482860485254, 36.669298771960719 ], [ -38.318348113192641, 36.669298771960719 ], [ -38.318348113192641, 36.669514932121047 ], [ -38.318482860485254, 36.669514932121047 ], [ -38.318482860485254, 36.669298771960719 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x4-y4", "id": "w-x4-y4", "building_type": "workplace", "door_cell_x": 3, "door_cell_y": 4, "size": 2, "door_point_x": 4.0, "door_point_y": 4.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318887102363114, 36.668758368903482 ], [ -38.318752355070494, 36.668758368903482 ], [ -38.318752355070494, 36.668974530581764 ], [ -38.318887102363114, 36.668974530581764 ], [ -38.318887102363114, 36.668758368903482 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x5-y4", "id": "w-x5-y4", "building_type": "workplace", "door_cell_x": 5, "door_cell_y": 3, "size": 2, "door_point_x": 5.5, "door_point_y": 4.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318752355070494, 36.668758368903482 ], [ -38.318617607777881, 36.668758368903482 ], [ -38.318617607777881, 36.668974530581764 ], [ -38.318752355070494, 36.668974530581764 ], [ -38.318752355070494, 36.668758368903482 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x6-y5", "id": "w-x6-y5", "building_type": "workplace", "door_cell_x": 6, "door_cell_y": 6, "size": 4, "door_point_x": 6.5, "door_point_y": 6.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318348113192641, 36.668758368903482 ], [ -38.318348113192641, 36.668974530581764 ], [ -38.318617607777881, 36.668974530581764 ], [ -38.318617607777881, 36.668758368903482 ], [ -38.318348113192641, 36.668758368903482 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x8-y5", "id": "w-x8-y5", "building_type": "workplace", "door_cell_x": 8, "door_cell_y": 6, "size": 4, "door_point_x": 8.5, "door_point_y": 6.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318078618607409, 36.668758368903482 ], [ -38.318078618607409, 36.668974530581764 ], [ -38.318348113192641, 36.668974530581764 ], [ -38.318348113192641, 36.668758368903482 ], [ -38.318078618607409, 36.668758368903482 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x12-y5", "id": "w-x12-y5", "building_type": "workplace", "door_cell_x": 12, "door_cell_y": 6, "size": 3, "door_point_x": 12.5, "door_point_y": 6.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317539629436936, 36.668866449818516 ], [ -38.317539629436936, 36.668974530581764 ], [ -38.317943871314782, 36.668974530581764 ], [ -38.317943871314782, 36.668866449818516 ], [ -38.317539629436936, 36.668866449818516 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x12-y4", "id": "w-x12-y4", "building_type": "workplace", "door_cell_x": 12, "door_cell_y": 3, "size": 3, "door_point_x": 12.5, "door_point_y": 4.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317539629436936, 36.668758368903482 ], [ -38.317539629436936, 36.668866449818516 ], [ -38.317943871314782, 36.668866449818516 ], [ -38.317943871314782, 36.668758368903482 ], [ -38.317539629436936, 36.668758368903482 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x15-y4", "id": "w-x15-y4", "building_type": "workplace", "door_cell_x": 15, "door_cell_y": 3, "size": 6, "door_point_x": 15.5, "door_point_y": 4.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317135387559077, 36.668758368903482 ], [ -38.317135387559077, 36.668974530581764 ], [ -38.317539629436936, 36.668974530581764 ], [ -38.317539629436936, 36.668758368903482 ], [ -38.317135387559077, 36.668758368903482 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x17-y4", "id": "w-x17-y4", "building_type": "workplace", "door_cell_x": 18, "door_cell_y": 4, "size": 2, "door_point_x": 18.0, "door_point_y": 4.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317000640266464, 36.668758368903482 ], [ -38.317000640266464, 36.668974530581764 ], [ -38.317135387559077, 36.668974530581764 ], [ -38.317135387559077, 36.668758368903482 ], [ -38.317000640266464, 36.668758368903482 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x17-y6", "id": "w-x17-y6", "building_type": "workplace", "door_cell_x": 18, "door_cell_y": 6, "size": 4, "door_point_x": 18.0, "door_point_y": 6.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317000640266464, 36.668974530581764 ], [ -38.317000640266464, 36.669190691652865 ], [ -38.317270134851697, 36.669190691652865 ], [ -38.317270134851697, 36.668974530581764 ], [ -38.317000640266464, 36.668974530581764 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x16-y9", "id": "w-x16-y9", "building_type": "workplace", "door_cell_x": 15, "door_cell_y": 9, "size": 2, "door_point_x": 16.0, "door_point_y": 9.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317135387559077, 36.669190691652865 ], [ -38.317135387559077, 36.669406852116779 ], [ -38.317270134851697, 36.669406852116779 ], [ -38.317270134851697, 36.669190691652865 ], [ -38.317135387559077, 36.669190691652865 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x17-y8", "id": "w-x17-y8", "building_type": "workplace", "door_cell_x": 18, "door_cell_y": 8, "size": 2, "door_point_x": 18.0, "door_point_y": 8.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317000640266464, 36.669190691652865 ], [ -38.317000640266464, 36.669406852116779 ], [ -38.317135387559077, 36.669406852116779 ], [ -38.317135387559077, 36.669190691652865 ], [ -38.317000640266464, 36.669190691652865 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x17-y10", "id": "w-x17-y10", "building_type": "workplace", "door_cell_x": 18, "door_cell_y": 10, "size": 4, "door_point_x": 18.0, "door_point_y": 10.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317000640266464, 36.669406852116779 ], [ -38.317000640266464, 36.669623011973513 ], [ -38.317270134851697, 36.669623011973513 ], [ -38.317270134851697, 36.669406852116779 ], [ -38.317000640266464, 36.669406852116779 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x17-y13", "id": "w-x17-y13", "building_type": "workplace", "door_cell_x": 18, "door_cell_y": 13, "size": 4, "door_point_x": 18.0, "door_point_y": 13.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317000640266464, 36.669731091674187 ], [ -38.317000640266464, 36.66994725062014 ], [ -38.317270134851697, 36.66994725062014 ], [ -38.317270134851697, 36.669731091674187 ], [ -38.317000640266464, 36.669731091674187 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x17-y15", "id": "w-x17-y15", "building_type": "workplace", "door_cell_x": 18, "door_cell_y": 15, "size": 2, "door_point_x": 18.0, "door_point_y": 15.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317000640266464, 36.66994725062014 ], [ -38.317000640266464, 36.670055329865427 ], [ -38.317270134851697, 36.670055329865427 ], [ -38.317270134851697, 36.66994725062014 ], [ -38.317000640266464, 36.66994725062014 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x15-y16", "id": "w-x15-y16", "building_type": "workplace", "door_cell_x": 15, "door_cell_y": 15, "size": 3, "door_point_x": 15.5, "door_point_y": 16.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317000640266464, 36.670055329865427 ], [ -38.317000640266464, 36.670163408958906 ], [ -38.317404882144309, 36.670163408958906 ], [ -38.317404882144309, 36.670055329865427 ], [ -38.317000640266464, 36.670055329865427 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x14-y16", "id": "w-x14-y16", "building_type": "workplace", "door_cell_x": 14, "door_cell_y": 15, "size": 1, "door_point_x": 14.5, "door_point_y": 16.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317404882144309, 36.670055329865427 ], [ -38.317404882144309, 36.670163408958906 ], [ -38.317539629436936, 36.670163408958906 ], [ -38.317539629436936, 36.670055329865427 ], [ -38.317404882144309, 36.670055329865427 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x16-y17", "id": "w-x16-y17", "building_type": "workplace", "door_cell_x": 16, "door_cell_y": 18, "size": 2, "door_point_x": 16.5, "door_point_y": 18.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317000640266464, 36.670163408958906 ], [ -38.317000640266464, 36.670271487900592 ], [ -38.317270134851697, 36.670271487900592 ], [ -38.317270134851697, 36.670163408958906 ], [ -38.317000640266464, 36.670163408958906 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x15-y17", "id": "w-x15-y17", "building_type": "workplace", "door_cell_x": 15, "door_cell_y": 18, "size": 2, "door_point_x": 15.5, "door_point_y": 18.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317270134851697, 36.670163408958906 ], [ -38.317270134851697, 36.670271487900592 ], [ -38.317539629436936, 36.670271487900592 ], [ -38.317539629436936, 36.670163408958906 ], [ -38.317270134851697, 36.670163408958906 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x13-y17", "id": "w-x13-y17", "building_type": "workplace", "door_cell_x": 13, "door_cell_y": 18, "size": 4, "door_point_x": 13.5, "door_point_y": 18.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317539629436936, 36.670055329865427 ], [ -38.317539629436936, 36.670271487900592 ], [ -38.317809124022169, 36.670271487900592 ], [ -38.317809124022169, 36.670055329865427 ], [ -38.317539629436936, 36.670055329865427 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x11-y17", "id": "w-x11-y17", "building_type": "workplace", "door_cell_x": 11, "door_cell_y": 18, "size": 2, "door_point_x": 11.5, "door_point_y": 18.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317809124022169, 36.670163408958906 ], [ -38.317809124022169, 36.670271487900592 ], [ -38.318078618607409, 36.670271487900592 ], [ -38.318078618607409, 36.670163408958906 ], [ -38.317809124022169, 36.670163408958906 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x11-y16", "id": "w-x11-y16", "building_type": "workplace", "door_cell_x": 11, "door_cell_y": 15, "size": 2, "door_point_x": 11.5, "door_point_y": 16.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317809124022169, 36.670055329865427 ], [ -38.317809124022169, 36.670163408958906 ], [ -38.318078618607409, 36.670163408958906 ], [ -38.318078618607409, 36.670055329865427 ], [ -38.317809124022169, 36.670055329865427 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x8-y17", "id": "w-x8-y17", "building_type": "workplace", "door_cell_x": 8, "door_cell_y": 18, "size": 4, "door_point_x": 8.5, "door_point_y": 18.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318213365900021, 36.670055329865427 ], [ -38.318213365900021, 36.670271487900592 ], [ -38.318482860485254, 36.670271487900592 ], [ -38.318482860485254, 36.670055329865427 ], [ -38.318213365900021, 36.670055329865427 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x6-y17", "id": "w-x6-y17", "building_type": "workplace", "door_cell_x": 6, "door_cell_y": 18, "size": 2, "door_point_x": 6.5, "door_point_y": 18.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318482860485254, 36.670163408958906 ], [ -38.318482860485254, 36.670271487900592 ], [ -38.318752355070494, 36.670271487900592 ], [ -38.318752355070494, 36.670163408958906 ], [ -38.318482860485254, 36.670163408958906 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x6-y16", "id": "w-x6-y16", "building_type": "workplace", "door_cell_x": 6, "door_cell_y": 15, "size": 2, "door_point_x": 6.5, "door_point_y": 16.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318482860485254, 36.670055329865427 ], [ -38.318482860485254, 36.670163408958906 ], [ -38.318752355070494, 36.670163408958906 ], [ -38.318752355070494, 36.670055329865427 ], [ -38.318482860485254, 36.670055329865427 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x4-y16", "id": "w-x4-y16", "building_type": "workplace", "door_cell_x": 3, "door_cell_y": 16, "size": 2, "door_point_x": 4.0, "door_point_y": 16.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318887102363114, 36.670055329865427 ], [ -38.318752355070494, 36.670055329865427 ], [ -38.318752355070494, 36.670271487900592 ], [ -38.318887102363114, 36.670271487900592 ], [ -38.318887102363114, 36.670055329865427 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x4-y13", "id": "w-x4-y13", "building_type": "workplace", "door_cell_x": 3, "door_cell_y": 13, "size": 6, "door_point_x": 4.0, "door_point_y": 13.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318617607777881, 36.669731091674187 ], [ -38.318617607777881, 36.670055329865427 ], [ -38.318887102363114, 36.670055329865427 ], [ -38.318887102363114, 36.669731091674187 ], [ -38.318617607777881, 36.669731091674187 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x5-y12", "id": "w-x5-y12", "building_type": "workplace", "door_cell_x": 6, "door_cell_y": 12, "size": 2, "door_point_x": 6.0, "door_point_y": 12.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318617607777881, 36.669623011973513 ], [ -38.318617607777881, 36.669731091674187 ], [ -38.318887102363114, 36.669731091674187 ], [ -38.318887102363114, 36.669623011973513 ], [ -38.318617607777881, 36.669623011973513 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x4-y10", "id": "w-x4-y10", "building_type": "workplace", "door_cell_x": 3, "door_cell_y": 10, "size": 2, "door_point_x": 4.0, "door_point_y": 10.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318887102363114, 36.669298771960719 ], [ -38.318752355070494, 36.669298771960719 ], [ -38.318752355070494, 36.669514932121047 ], [ -38.318887102363114, 36.669514932121047 ], [ -38.318887102363114, 36.669298771960719 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x5-y9", "id": "w-x5-y9", "building_type": "workplace", "door_cell_x": 6, "door_cell_y": 9, "size": 2, "door_point_x": 6.0, "door_point_y": 9.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318752355070494, 36.669298771960719 ], [ -38.318617607777881, 36.669298771960719 ], [ -38.318617607777881, 36.669514932121047 ], [ -38.318752355070494, 36.669514932121047 ], [ -38.318752355070494, 36.669298771960719 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x5-y8", "id": "w-x5-y8", "building_type": "workplace", "door_cell_x": 6, "door_cell_y": 8, "size": 2, "door_point_x": 6.0, "door_point_y": 8.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318887102363114, 36.669190691652865 ], [ -38.318617607777881, 36.669190691652865 ], [ -38.318617607777881, 36.669298771960719 ], [ -38.318887102363114, 36.669298771960719 ], [ -38.318887102363114, 36.669190691652865 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "w-x4-y6", "id": "w-x4-y6", "building_type": "workplace", "door_cell_x": 3, "door_cell_y": 6, "size": 4, "door_point_x": 4.0, "door_point_y": 6.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318617607777881, 36.668974530581764 ], [ -38.318617607777881, 36.669190691652865 ], [ -38.318887102363114, 36.669190691652865 ], [ -38.318887102363114, 36.668974530581764 ], [ -38.318617607777881, 36.668974530581764 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x1-y1", "id": "r-x1-y1", "building_type": "retail", "door_cell_x": 0, "door_cell_y": 1, "size": 4, "door_point_x": 1.0, "door_point_y": 1.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.6684341252476 ], [ -38.319021849655726, 36.668650287836641 ], [ -38.319291344240966, 36.668650287836641 ], [ -38.319291344240966, 36.6684341252476 ], [ -38.319021849655726, 36.6684341252476 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x3-y1", "id": "r-x3-y1", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 0, "size": 4, "door_point_x": 3.5, "door_point_y": 1.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318752355070494, 36.6684341252476 ], [ -38.318752355070494, 36.668650287836641 ], [ -38.319021849655726, 36.668650287836641 ], [ -38.319021849655726, 36.6684341252476 ], [ -38.318752355070494, 36.6684341252476 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x5-y1", "id": "r-x5-y1", "building_type": "retail", "door_cell_x": 5, "door_cell_y": 0, "size": 1, "door_point_x": 5.5, "door_point_y": 1.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318617607777881, 36.6684341252476 ], [ -38.318617607777881, 36.668542206618028 ], [ -38.318752355070494, 36.668542206618028 ], [ -38.318752355070494, 36.6684341252476 ], [ -38.318617607777881, 36.6684341252476 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x5-y2", "id": "r-x5-y2", "building_type": "retail", "door_cell_x": 5, "door_cell_y": 3, "size": 1, "door_point_x": 5.5, "door_point_y": 3.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318617607777881, 36.668542206618028 ], [ -38.318617607777881, 36.668650287836641 ], [ -38.318752355070494, 36.668650287836641 ], [ -38.318752355070494, 36.668542206618028 ], [ -38.318617607777881, 36.668542206618028 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x6-y1", "id": "r-x6-y1", "building_type": "retail", "door_cell_x": 6, "door_cell_y": 0, "size": 2, "door_point_x": 6.5, "door_point_y": 1.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318348113192641, 36.6684341252476 ], [ -38.318348113192641, 36.668542206618028 ], [ -38.318617607777881, 36.668542206618028 ], [ -38.318617607777881, 36.6684341252476 ], [ -38.318348113192641, 36.6684341252476 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x6-y2", "id": "r-x6-y2", "building_type": "retail", "door_cell_x": 6, "door_cell_y": 3, "size": 2, "door_point_x": 6.5, "door_point_y": 3.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318348113192641, 36.668542206618028 ], [ -38.318348113192641, 36.668650287836641 ], [ -38.318617607777881, 36.668650287836641 ], [ -38.318617607777881, 36.668542206618028 ], [ -38.318348113192641, 36.668542206618028 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x9-y2", "id": "r-x9-y2", "building_type": "retail", "door_cell_x": 9, "door_cell_y": 3, "size": 2, "door_point_x": 9.5, "door_point_y": 3.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318078618607409, 36.6684341252476 ], [ -38.318078618607409, 36.668650287836641 ], [ -38.318213365900021, 36.668650287836641 ], [ -38.318213365900021, 36.6684341252476 ], [ -38.318078618607409, 36.6684341252476 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x12-y2", "id": "r-x12-y2", "building_type": "retail", "door_cell_x": 12, "door_cell_y": 3, "size": 6, "door_point_x": 12.5, "door_point_y": 3.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317674376729549, 36.6684341252476 ], [ -38.317674376729549, 36.668650287836641 ], [ -38.318078618607409, 36.668650287836641 ], [ -38.318078618607409, 36.6684341252476 ], [ -38.317674376729549, 36.6684341252476 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x14-y2", "id": "r-x14-y2", "building_type": "retail", "door_cell_x": 14, "door_cell_y": 3, "size": 4, "door_point_x": 14.5, "door_point_y": 3.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317404882144309, 36.6684341252476 ], [ -38.317404882144309, 36.668650287836641 ], [ -38.317674376729549, 36.668650287836641 ], [ -38.317674376729549, 36.6684341252476 ], [ -38.317404882144309, 36.6684341252476 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x15-y2", "id": "r-x15-y2", "building_type": "retail", "door_cell_x": 15, "door_cell_y": 3, "size": 1, "door_point_x": 15.5, "door_point_y": 3.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317270134851697, 36.668542206618028 ], [ -38.317270134851697, 36.668650287836641 ], [ -38.317404882144309, 36.668650287836641 ], [ -38.317404882144309, 36.668542206618028 ], [ -38.317270134851697, 36.668542206618028 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x16-y2", "id": "r-x16-y2", "building_type": "retail", "door_cell_x": 16, "door_cell_y": 3, "size": 1, "door_point_x": 16.5, "door_point_y": 3.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317135387559077, 36.668542206618028 ], [ -38.317135387559077, 36.668650287836641 ], [ -38.317270134851697, 36.668650287836641 ], [ -38.317270134851697, 36.668542206618028 ], [ -38.317135387559077, 36.668542206618028 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x15-y1", "id": "r-x15-y1", "building_type": "retail", "door_cell_x": 15, "door_cell_y": 0, "size": 1, "door_point_x": 15.5, "door_point_y": 1.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317270134851697, 36.6684341252476 ], [ -38.317270134851697, 36.668542206618028 ], [ -38.317404882144309, 36.668542206618028 ], [ -38.317404882144309, 36.6684341252476 ], [ -38.317270134851697, 36.6684341252476 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x16-y1", "id": "r-x16-y1", "building_type": "retail", "door_cell_x": 16, "door_cell_y": 0, "size": 1, "door_point_x": 16.5, "door_point_y": 1.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317135387559077, 36.6684341252476 ], [ -38.317135387559077, 36.668542206618028 ], [ -38.317270134851697, 36.668542206618028 ], [ -38.317270134851697, 36.6684341252476 ], [ -38.317135387559077, 36.6684341252476 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x17-y2", "id": "r-x17-y2", "building_type": "retail", "door_cell_x": 17, "door_cell_y": 3, "size": 2, "door_point_x": 17.5, "door_point_y": 3.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316865892973837, 36.668542206618028 ], [ -38.316865892973837, 36.668650287836641 ], [ -38.317135387559077, 36.668650287836641 ], [ -38.317135387559077, 36.668542206618028 ], [ -38.316865892973837, 36.668542206618028 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x18-y1", "id": "r-x18-y1", "building_type": "retail", "door_cell_x": 18, "door_cell_y": 0, "size": 2, "door_point_x": 18.5, "door_point_y": 1.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316865892973837, 36.6684341252476 ], [ -38.316865892973837, 36.668542206618028 ], [ -38.317135387559077, 36.668542206618028 ], [ -38.317135387559077, 36.6684341252476 ], [ -38.316865892973837, 36.6684341252476 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x19-y1", "id": "r-x19-y1", "building_type": "retail", "door_cell_x": 19, "door_cell_y": 0, "size": 2, "door_point_x": 19.5, "door_point_y": 1.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316596398388604, 36.6684341252476 ], [ -38.316596398388604, 36.668542206618028 ], [ -38.316865892973837, 36.668542206618028 ], [ -38.316865892973837, 36.6684341252476 ], [ -38.316596398388604, 36.6684341252476 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x19-y3", "id": "r-x19-y3", "building_type": "retail", "door_cell_x": 18, "door_cell_y": 3, "size": 4, "door_point_x": 19.0, "door_point_y": 3.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316596398388604, 36.668542206618028 ], [ -38.316596398388604, 36.668758368903482 ], [ -38.316865892973837, 36.668758368903482 ], [ -38.316865892973837, 36.668542206618028 ], [ -38.316596398388604, 36.668542206618028 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x19-y5", "id": "r-x19-y5", "building_type": "retail", "door_cell_x": 18, "door_cell_y": 5, "size": 4, "door_point_x": 19.0, "door_point_y": 5.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316596398388604, 36.668758368903482 ], [ -38.316596398388604, 36.668974530581764 ], [ -38.316865892973837, 36.668974530581764 ], [ -38.316865892973837, 36.668758368903482 ], [ -38.316596398388604, 36.668758368903482 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x19-y7", "id": "r-x19-y7", "building_type": "retail", "door_cell_x": 18, "door_cell_y": 7, "size": 2, "door_point_x": 19.0, "door_point_y": 7.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316731145681224, 36.668974530581764 ], [ -38.316731145681224, 36.669190691652865 ], [ -38.316865892973837, 36.669190691652865 ], [ -38.316865892973837, 36.668974530581764 ], [ -38.316731145681224, 36.668974530581764 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x20-y7", "id": "r-x20-y7", "building_type": "retail", "door_cell_x": 21, "door_cell_y": 7, "size": 2, "door_point_x": 21.0, "door_point_y": 7.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316596398388604, 36.668974530581764 ], [ -38.316596398388604, 36.669190691652865 ], [ -38.316731145681224, 36.669190691652865 ], [ -38.316731145681224, 36.668974530581764 ], [ -38.316596398388604, 36.668974530581764 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x19-y10", "id": "r-x19-y10", "building_type": "retail", "door_cell_x": 18, "door_cell_y": 10, "size": 4, "door_point_x": 19.0, "door_point_y": 10.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316596398388604, 36.669298771960719 ], [ -38.316596398388604, 36.669514932121047 ], [ -38.316865892973837, 36.669514932121047 ], [ -38.316865892973837, 36.669298771960719 ], [ -38.316596398388604, 36.669298771960719 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x19-y11", "id": "r-x19-y11", "building_type": "retail", "door_cell_x": 18, "door_cell_y": 11, "size": 4, "door_point_x": 19.0, "door_point_y": 11.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316596398388604, 36.669514932121047 ], [ -38.316596398388604, 36.669731091674187 ], [ -38.316865892973837, 36.669731091674187 ], [ -38.316865892973837, 36.669514932121047 ], [ -38.316596398388604, 36.669514932121047 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x19-y13", "id": "r-x19-y13", "building_type": "retail", "door_cell_x": 18, "door_cell_y": 13, "size": 2, "door_point_x": 19.0, "door_point_y": 13.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316731145681224, 36.669731091674187 ], [ -38.316731145681224, 36.66994725062014 ], [ -38.316865892973837, 36.66994725062014 ], [ -38.316865892973837, 36.669731091674187 ], [ -38.316731145681224, 36.669731091674187 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x20-y13", "id": "r-x20-y13", "building_type": "retail", "door_cell_x": 21, "door_cell_y": 13, "size": 2, "door_point_x": 21.0, "door_point_y": 13.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316596398388604, 36.669731091674187 ], [ -38.316596398388604, 36.66994725062014 ], [ -38.316731145681224, 36.66994725062014 ], [ -38.316731145681224, 36.669731091674187 ], [ -38.316596398388604, 36.669731091674187 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x20-y16", "id": "r-x20-y16", "building_type": "retail", "door_cell_x": 21, "door_cell_y": 16, "size": 4, "door_point_x": 21.0, "door_point_y": 16.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316596398388604, 36.66994725062014 ], [ -38.316596398388604, 36.670163408958906 ], [ -38.316865892973837, 36.670163408958906 ], [ -38.316865892973837, 36.66994725062014 ], [ -38.316596398388604, 36.66994725062014 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x20-y18", "id": "r-x20-y18", "building_type": "retail", "door_cell_x": 21, "door_cell_y": 18, "size": 4, "door_point_x": 21.0, "door_point_y": 18.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316596398388604, 36.670163408958906 ], [ -38.316596398388604, 36.670379566690485 ], [ -38.316865892973837, 36.670379566690485 ], [ -38.316865892973837, 36.670163408958906 ], [ -38.316596398388604, 36.670163408958906 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x20-y19", "id": "r-x20-y19", "building_type": "retail", "door_cell_x": 21, "door_cell_y": 19, "size": 2, "door_point_x": 21.0, "door_point_y": 19.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316596398388604, 36.670379566690485 ], [ -38.316596398388604, 36.670487645328578 ], [ -38.316865892973837, 36.670487645328578 ], [ -38.316865892973837, 36.670379566690485 ], [ -38.316596398388604, 36.670379566690485 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x20-y20", "id": "r-x20-y20", "building_type": "retail", "door_cell_x": 20, "door_cell_y": 21, "size": 2, "door_point_x": 20.5, "door_point_y": 21.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.316596398388604, 36.670487645328578 ], [ -38.316596398388604, 36.67059572381487 ], [ -38.316865892973837, 36.67059572381487 ], [ -38.316865892973837, 36.670487645328578 ], [ -38.316596398388604, 36.670487645328578 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x17-y19", "id": "r-x17-y19", "building_type": "retail", "door_cell_x": 17, "door_cell_y": 18, "size": 2, "door_point_x": 17.5, "door_point_y": 19.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317000640266464, 36.670379566690485 ], [ -38.317000640266464, 36.67059572381487 ], [ -38.317135387559077, 36.67059572381487 ], [ -38.317135387559077, 36.670379566690485 ], [ -38.317000640266464, 36.670379566690485 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x16-y19", "id": "r-x16-y19", "building_type": "retail", "door_cell_x": 16, "door_cell_y": 18, "size": 2, "door_point_x": 16.5, "door_point_y": 19.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317135387559077, 36.670379566690485 ], [ -38.317135387559077, 36.67059572381487 ], [ -38.317270134851697, 36.67059572381487 ], [ -38.317270134851697, 36.670379566690485 ], [ -38.317135387559077, 36.670379566690485 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x14-y19", "id": "r-x14-y19", "building_type": "retail", "door_cell_x": 14, "door_cell_y": 18, "size": 3, "door_point_x": 14.5, "door_point_y": 19.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317270134851697, 36.670379566690485 ], [ -38.317270134851697, 36.670487645328578 ], [ -38.317674376729549, 36.670487645328578 ], [ -38.317674376729549, 36.670379566690485 ], [ -38.317270134851697, 36.670379566690485 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x15-y20", "id": "r-x15-y20", "building_type": "retail", "door_cell_x": 15, "door_cell_y": 21, "size": 2, "door_point_x": 15.5, "door_point_y": 21.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317270134851697, 36.670487645328578 ], [ -38.317270134851697, 36.67059572381487 ], [ -38.317539629436936, 36.67059572381487 ], [ -38.317539629436936, 36.670487645328578 ], [ -38.317270134851697, 36.670487645328578 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x13-y20", "id": "r-x13-y20", "building_type": "retail", "door_cell_x": 13, "door_cell_y": 21, "size": 2, "door_point_x": 13.5, "door_point_y": 21.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317539629436936, 36.670487645328578 ], [ -38.317539629436936, 36.67059572381487 ], [ -38.317809124022169, 36.67059572381487 ], [ -38.317809124022169, 36.670487645328578 ], [ -38.317539629436936, 36.670487645328578 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x12-y19", "id": "r-x12-y19", "building_type": "retail", "door_cell_x": 12, "door_cell_y": 18, "size": 1, "door_point_x": 12.5, "door_point_y": 19.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317674376729549, 36.670379566690485 ], [ -38.317674376729549, 36.670487645328578 ], [ -38.317809124022169, 36.670487645328578 ], [ -38.317809124022169, 36.670379566690485 ], [ -38.317674376729549, 36.670379566690485 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x11-y19", "id": "r-x11-y19", "building_type": "retail", "door_cell_x": 11, "door_cell_y": 18, "size": 4, "door_point_x": 11.5, "door_point_y": 19.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.317809124022169, 36.670379566690485 ], [ -38.317809124022169, 36.67059572381487 ], [ -38.318078618607409, 36.67059572381487 ], [ -38.318078618607409, 36.670379566690485 ], [ -38.317809124022169, 36.670379566690485 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x9-y19", "id": "r-x9-y19", "building_type": "retail", "door_cell_x": 9, "door_cell_y": 18, "size": 2, "door_point_x": 9.5, "door_point_y": 19.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318078618607409, 36.670379566690485 ], [ -38.318078618607409, 36.670487645328578 ], [ -38.318348113192641, 36.670487645328578 ], [ -38.318348113192641, 36.670379566690485 ], [ -38.318078618607409, 36.670379566690485 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x9-y20", "id": "r-x9-y20", "building_type": "retail", "door_cell_x": 9, "door_cell_y": 21, "size": 2, "door_point_x": 9.5, "door_point_y": 21.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318078618607409, 36.670487645328578 ], [ -38.318078618607409, 36.67059572381487 ], [ -38.318348113192641, 36.67059572381487 ], [ -38.318348113192641, 36.670487645328578 ], [ -38.318078618607409, 36.670487645328578 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x6-y20", "id": "r-x6-y20", "building_type": "retail", "door_cell_x": 6, "door_cell_y": 21, "size": 4, "door_point_x": 6.5, "door_point_y": 21.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318482860485254, 36.670379566690485 ], [ -38.318482860485254, 36.67059572381487 ], [ -38.318752355070494, 36.67059572381487 ], [ -38.318752355070494, 36.670379566690485 ], [ -38.318482860485254, 36.670379566690485 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x4-y20", "id": "r-x4-y20", "building_type": "retail", "door_cell_x": 4, "door_cell_y": 21, "size": 2, "door_point_x": 4.5, "door_point_y": 21.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318752355070494, 36.670487645328578 ], [ -38.318752355070494, 36.67059572381487 ], [ -38.319021849655726, 36.67059572381487 ], [ -38.319021849655726, 36.670487645328578 ], [ -38.318752355070494, 36.670487645328578 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x4-y19", "id": "r-x4-y19", "building_type": "retail", "door_cell_x": 4, "door_cell_y": 18, "size": 2, "door_point_x": 4.5, "door_point_y": 19.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.318752355070494, 36.670379566690485 ], [ -38.318752355070494, 36.670487645328578 ], [ -38.319021849655726, 36.670487645328578 ], [ -38.319021849655726, 36.670379566690485 ], [ -38.318752355070494, 36.670379566690485 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y19", "id": "r-x2-y19", "building_type": "retail", "door_cell_x": 2, "door_cell_y": 18, "size": 2, "door_point_x": 2.5, "door_point_y": 19.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.670379566690485 ], [ -38.319021849655726, 36.67059572381487 ], [ -38.319156596948346, 36.67059572381487 ], [ -38.319156596948346, 36.670379566690485 ], [ -38.319021849655726, 36.670379566690485 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x1-y19", "id": "r-x1-y19", "building_type": "retail", "door_cell_x": 1, "door_cell_y": 18, "size": 2, "door_point_x": 1.5, "door_point_y": 19.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319156596948346, 36.670379566690485 ], [ -38.319156596948346, 36.67059572381487 ], [ -38.319291344240966, 36.67059572381487 ], [ -38.319291344240966, 36.670379566690485 ], [ -38.319156596948346, 36.670379566690485 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y17", "id": "r-x2-y17", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 17, "size": 4, "door_point_x": 3.0, "door_point_y": 17.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.670055329865427 ], [ -38.319021849655726, 36.670271487900592 ], [ -38.319291344240966, 36.670271487900592 ], [ -38.319291344240966, 36.670055329865427 ], [ -38.319021849655726, 36.670055329865427 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y15", "id": "r-x2-y15", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 15, "size": 2, "door_point_x": 3.0, "door_point_y": 15.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.66994725062014 ], [ -38.319021849655726, 36.670055329865427 ], [ -38.319291344240966, 36.670055329865427 ], [ -38.319291344240966, 36.66994725062014 ], [ -38.319021849655726, 36.66994725062014 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y14", "id": "r-x2-y14", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 14, "size": 2, "door_point_x": 3.0, "door_point_y": 14.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.669839171223067 ], [ -38.319021849655726, 36.66994725062014 ], [ -38.319291344240966, 36.66994725062014 ], [ -38.319291344240966, 36.669839171223067 ], [ -38.319021849655726, 36.669839171223067 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y12", "id": "r-x2-y12", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 12, "size": 4, "door_point_x": 3.0, "door_point_y": 12.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.669623011973513 ], [ -38.319021849655726, 36.669839171223067 ], [ -38.319291344240966, 36.669839171223067 ], [ -38.319291344240966, 36.669623011973513 ], [ -38.319021849655726, 36.669623011973513 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y11", "id": "r-x2-y11", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 11, "size": 2, "door_point_x": 3.0, "door_point_y": 11.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.669514932121047 ], [ -38.319021849655726, 36.669623011973513 ], [ -38.319291344240966, 36.669623011973513 ], [ -38.319291344240966, 36.669514932121047 ], [ -38.319021849655726, 36.669514932121047 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y10", "id": "r-x2-y10", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 10, "size": 2, "door_point_x": 3.0, "door_point_y": 10.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.669406852116779 ], [ -38.319021849655726, 36.669514932121047 ], [ -38.319291344240966, 36.669514932121047 ], [ -38.319291344240966, 36.669406852116779 ], [ -38.319021849655726, 36.669406852116779 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y8", "id": "r-x2-y8", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 8, "size": 4, "door_point_x": 3.0, "door_point_y": 8.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.669190691652865 ], [ -38.319021849655726, 36.669406852116779 ], [ -38.319291344240966, 36.669406852116779 ], [ -38.319291344240966, 36.669190691652865 ], [ -38.319021849655726, 36.669190691652865 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y7", "id": "r-x2-y7", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 7, "size": 2, "door_point_x": 3.0, "door_point_y": 7.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.669082611193211 ], [ -38.319021849655726, 36.669190691652865 ], [ -38.319291344240966, 36.669190691652865 ], [ -38.319291344240966, 36.669082611193211 ], [ -38.319021849655726, 36.669082611193211 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x1-y5", "id": "r-x1-y5", "building_type": "retail", "door_cell_x": 0, "door_cell_y": 5, "size": 3, "door_point_x": 1.0, "door_point_y": 5.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319156596948346, 36.668758368903482 ], [ -38.319156596948346, 36.669082611193211 ], [ -38.319291344240966, 36.669082611193211 ], [ -38.319291344240966, 36.668758368903482 ], [ -38.319156596948346, 36.668758368903482 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y6", "id": "r-x2-y6", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 6, "size": 1, "door_point_x": 3.0, "door_point_y": 6.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.668974530581764 ], [ -38.319021849655726, 36.669082611193211 ], [ -38.319156596948346, 36.669082611193211 ], [ -38.319156596948346, 36.668974530581764 ], [ -38.319021849655726, 36.668974530581764 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y5", "id": "r-x2-y5", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 5, "size": 1, "door_point_x": 3.0, "door_point_y": 5.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.668866449818516 ], [ -38.319021849655726, 36.668974530581764 ], [ -38.319156596948346, 36.668974530581764 ], [ -38.319156596948346, 36.668866449818516 ], [ -38.319021849655726, 36.668866449818516 ] ] ] } }, +{ "type": "Feature", "properties": { "index": "r-x2-y4", "id": "r-x2-y4", "building_type": "retail", "door_cell_x": 3, "door_cell_y": 4, "size": 1, "door_point_x": 3.0, "door_point_y": 4.5 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -38.319021849655726, 36.668758368903482 ], [ -38.319021849655726, 36.668866449818516 ], [ -38.319156596948346, 36.668866449818516 ], [ -38.319156596948346, 36.668758368903482 ], [ -38.319021849655726, 36.668758368903482 ] ] ] } } +] +} diff --git a/nomad/home_attribution.py b/nomad/home_attribution.py index 90534184..074502d9 100644 --- a/nomad/home_attribution.py +++ b/nomad/home_attribution.py @@ -1,156 +1,156 @@ -import pandas as pd -import nomad.io.base as loader -from datetime import datetime, time, timedelta - - -def nocturnal_stops( - stops_table, - dusk_hour=19, - dawn_hour=6, - start_datetime="start_datetime", - end_datetime="end_datetime", -): - """Clip each stop to the nocturnal window between *dusk_hour* and *dawn_hour*. - - This helper assumes the caller already provides proper datetime columns. It - merely slices the stop to the relevant night portion and recomputes the - duration, dropping rows that do not intersect the night at all. - """ - - df = stops_table.copy() - - # Build candidate night windows for every stop - df["_night_start"] = df.apply( - lambda r: [ - pd.Timestamp(datetime.combine(d, time(dusk_hour)), tz=r[start_datetime].tzinfo) - for d in pd.date_range( - (r[start_datetime] - timedelta(days=1)).date(), - r[end_datetime].date(), - freq="D", - ) - ], - axis=1, - ) - - df = df.explode("_night_start", ignore_index=True) - df["_night_end"] = df["_night_start"] + timedelta(hours=(24 - dusk_hour + dawn_hour)) - - # Clip the stop to the nightly interval - df[start_datetime] = df[[start_datetime, "_night_start"]].max(axis=1) - df[end_datetime] = df[[end_datetime, "_night_end"]].min(axis=1) - - df["duration"] = ( - (df[end_datetime] - df[start_datetime]).dt.total_seconds() // 60 - ).astype(int) - - return df[df["duration"] > 0].drop(columns=["_night_start", "_night_end"]) - - -def compute_candidate_homes( - stops_table, - dusk_hour=19, - dawn_hour=6, - traj_cols=None, - **kwargs, -): - """Aggregate nightly presence statistics for home inference. - - Column names are resolved through *traj_cols* or keyword overrides and no - type coercion beyond what is strictly necessary for the calculation is - performed. - """ - - stops = stops_table.copy() - - # Resolve column names - traj_cols = loader._parse_traj_cols(stops.columns, traj_cols, kwargs) - loader._has_time_cols(stops.columns, traj_cols) - - t_key, use_datetime = loader._fallback_time_cols_dt(stops.columns, traj_cols, kwargs) - end_t_key = "end_datetime" if use_datetime else "end_timestamp" - - # Ensure we can compute an end time - end_col_present = loader._has_end_cols(stops.columns, traj_cols) - duration_col_present = loader._has_duration_cols(stops.columns, traj_cols) - if not (end_col_present or duration_col_present): - raise ValueError("stops_table must provide either an end time or a duration.") - - if not end_col_present: - dur_col = traj_cols["duration"] - if use_datetime: - stops[end_t_key] = stops[traj_cols[t_key]] + pd.to_timedelta(stops[dur_col], unit="m") - else: - stops[end_t_key] = stops[traj_cols[t_key]] + stops[dur_col] * 60 - - # Nocturnal clipping - stops_night = nocturnal_stops( - stops, - dusk_hour=dusk_hour, - dawn_hour=dawn_hour, - start_datetime=traj_cols[t_key], - end_datetime=end_t_key, - ) - - # Dates and ISO weeks (convert timestamps if needed) - if use_datetime: - dt = stops_night[traj_cols[t_key]] - else: - dt = pd.to_datetime(stops_night[traj_cols[t_key]], unit="s", utc=True) - - stops_night["_date"] = dt.dt.date - stops_night["_iso_week"] = dt.dt.isocalendar().week - - out = ( - stops_night.groupby([traj_cols["user_id"], traj_cols["location_id"]], as_index=False) - .agg( - num_nights=("_date", "nunique"), - num_weeks=("_iso_week", "nunique"), - total_duration=(traj_cols["duration"], "sum"), - ) - ) - - return out - - - -def select_home( - candidate_homes, - stops_table, - min_days, - min_weeks, - traj_cols=None, - **kwargs, -): - """Select a single home location per user.""" - - traj_cols = loader._parse_traj_cols(candidate_homes.columns, traj_cols, kwargs) - - # Last observation date - t_key, use_datetime = loader._fallback_time_cols_dt(stops_table.columns, traj_cols, kwargs) - dt_series = ( - stops_table[traj_cols[t_key]] - if use_datetime - else pd.to_datetime(stops_table[traj_cols[t_key]], unit="s", utc=True) - ) - last_date = dt_series.dt.date.max() - - # Filter and rank - filtered = ( - candidate_homes.loc[ - (candidate_homes["num_nights"] >= min_days) - & (candidate_homes["num_weeks"] >= min_weeks) - ] - .sort_values( - [traj_cols["user_id"], "num_nights", "total_duration"], - ascending=[True, False, False], - ) - ) - - best = ( - filtered.drop_duplicates(traj_cols["user_id"], keep="first") - .assign(home_date=last_date) - .reset_index(drop=True) - ) - - return best[[traj_cols["user_id"], traj_cols["location_id"], "home_date"]] - +import pandas as pd +import nomad.io.base as loader +from datetime import datetime, time, timedelta + + +def nocturnal_stops( + stops_table, + dusk_hour=19, + dawn_hour=6, + start_datetime="start_datetime", + end_datetime="end_datetime", +): + """Clip each stop to the nocturnal window between *dusk_hour* and *dawn_hour*. + + This helper assumes the caller already provides proper datetime columns. It + merely slices the stop to the relevant night portion and recomputes the + duration, dropping rows that do not intersect the night at all. + """ + + df = stops_table.copy() + + # Build candidate night windows for every stop + df["_night_start"] = df.apply( + lambda r: [ + pd.Timestamp(datetime.combine(d, time(dusk_hour)), tz=r[start_datetime].tzinfo) + for d in pd.date_range( + (r[start_datetime] - timedelta(days=1)).date(), + r[end_datetime].date(), + freq="D", + ) + ], + axis=1, + ) + + df = df.explode("_night_start", ignore_index=True) + df["_night_end"] = df["_night_start"] + timedelta(hours=(24 - dusk_hour + dawn_hour)) + + # Clip the stop to the nightly interval + df[start_datetime] = df[[start_datetime, "_night_start"]].max(axis=1) + df[end_datetime] = df[[end_datetime, "_night_end"]].min(axis=1) + + df["duration"] = ( + (df[end_datetime] - df[start_datetime]).dt.total_seconds() // 60 + ).astype(int) + + return df[df["duration"] > 0].drop(columns=["_night_start", "_night_end"]) + + +def compute_candidate_homes( + stops_table, + dusk_hour=19, + dawn_hour=6, + traj_cols=None, + **kwargs, +): + """Aggregate nightly presence statistics for home inference. + + Column names are resolved through *traj_cols* or keyword overrides and no + type coercion beyond what is strictly necessary for the calculation is + performed. + """ + + stops = stops_table.copy() + + # Resolve column names + traj_cols = loader._parse_traj_cols(stops.columns, traj_cols, kwargs) + loader._has_time_cols(stops.columns, traj_cols) + + t_key, use_datetime = loader._fallback_time_cols_dt(stops.columns, traj_cols, kwargs) + end_t_key = "end_datetime" if use_datetime else "end_timestamp" + + # Ensure we can compute an end time + end_col_present = loader._has_end_cols(stops.columns, traj_cols) + duration_col_present = loader._has_duration_cols(stops.columns, traj_cols) + if not (end_col_present or duration_col_present): + raise ValueError("stops_table must provide either an end time or a duration.") + + if not end_col_present: + dur_col = traj_cols["duration"] + if use_datetime: + stops[end_t_key] = stops[traj_cols[t_key]] + pd.to_timedelta(stops[dur_col], unit="m") + else: + stops[end_t_key] = stops[traj_cols[t_key]] + stops[dur_col] * 60 + + # Nocturnal clipping + stops_night = nocturnal_stops( + stops, + dusk_hour=dusk_hour, + dawn_hour=dawn_hour, + start_datetime=traj_cols[t_key], + end_datetime=end_t_key, + ) + + # Dates and ISO weeks (convert timestamps if needed) + if use_datetime: + dt = stops_night[traj_cols[t_key]] + else: + dt = pd.to_datetime(stops_night[traj_cols[t_key]], unit="s", utc=True) + + stops_night["_date"] = dt.dt.date + stops_night["_iso_week"] = dt.dt.isocalendar().week + + out = ( + stops_night.groupby([traj_cols["user_id"], traj_cols["location_id"]], as_index=False) + .agg( + num_nights=("_date", "nunique"), + num_weeks=("_iso_week", "nunique"), + total_duration=(traj_cols["duration"], "sum"), + ) + ) + + return out + + + +def select_home( + candidate_homes, + stops_table, + min_days, + min_weeks, + traj_cols=None, + **kwargs, +): + """Select a single home location per user.""" + + traj_cols = loader._parse_traj_cols(candidate_homes.columns, traj_cols, kwargs) + + # Last observation date + t_key, use_datetime = loader._fallback_time_cols_dt(stops_table.columns, traj_cols, kwargs) + dt_series = ( + stops_table[traj_cols[t_key]] + if use_datetime + else pd.to_datetime(stops_table[traj_cols[t_key]], unit="s", utc=True) + ) + last_date = dt_series.dt.date.max() + + # Filter and rank + filtered = ( + candidate_homes.loc[ + (candidate_homes["num_nights"] >= min_days) + & (candidate_homes["num_weeks"] >= min_weeks) + ] + .sort_values( + [traj_cols["user_id"], "num_nights", "total_duration"], + ascending=[True, False, False], + ) + ) + + best = ( + filtered.drop_duplicates(traj_cols["user_id"], keep="first") + .assign(home_date=last_date) + .reset_index(drop=True) + ) + + return best[[traj_cols["user_id"], traj_cols["location_id"], "home_date"]] + diff --git a/nomad/visit_attribution/visit_attribution.py b/nomad/visit_attribution/visit_attribution.py index 0daf845f..26c1ba5a 100644 --- a/nomad/visit_attribution/visit_attribution.py +++ b/nomad/visit_attribution/visit_attribution.py @@ -1,441 +1,441 @@ -import geopandas as gpd -import nomad.io.base as loader -import nomad.constants as constants -import warnings -import pandas as pd -import nomad.io.base as loader -import pyproj -import pdb - -# TO DO: change to stops_to_poi -def point_in_polygon(data, poi_table, method='centroid', data_crs=None, max_distance=0, - cluster_label=None, location_id=None, traj_cols=None, **kwargs): - """ - Assign each stop or cluster of pings in `data` to a polygon in `poi_table`, - either by the cluster’s centroid location or by the most frequent polygon hit. - - Parameters - ---------- - data : pd.DataFrame or gpd.GeoDataFrame - A table of pings (with optional stop/duration columns) or stops, - indexed by observation or cluster. - poi_table : gpd.GeoDataFrame - Polygons to match against, with CRS set and optional ID column. - method : {'centroid', 'majority'}, default 'centroid' - ‘centroid’ uses each cluster’s mean point; ‘majority’ picks the polygon - most often visited within each cluster (only for ping data). - data_crs : str or pyproj.CRS, optional - CRS for `data` when it is a plain DataFrame; ignored if `data` is a GeoDataFrame. - max_distance : float, default 0 - Search radius for nearest‐neighbor fall-back; zero triggers strict - point-in-polygon matching. - cluster_label : str, optional - Column name holding cluster IDs in ping data; inferred from `data` if absent. - location_id : str, optional - Column in `poi_table` containing the output ID; uses the GeoDataFrame index if None. - traj_cols : list of str, optional - Names of the coordinate columns in `data` when it is a DataFrame. - **kwargs - Passed through to `poi_map` or the trajectory-column parser. - - Returns - ------- - pd.Series - Indexed like `data`, giving the matched polygon ID for each stop or ping. - Points or clusters that fall outside every polygon or beyond `max_distance` - are set to NaN. - """ - # check if it is stop table - traj_cols_w_deflts = loader._parse_traj_cols(data.columns, traj_cols, kwargs) - end_col_present = loader._has_end_cols(data.columns, traj_cols_w_deflts) - duration_col_present = loader._has_duration_cols(data.columns, traj_cols_w_deflts) - is_stop_table = (end_col_present or duration_col_present) - - if is_stop_table: - # is stop table - if method=='majority': - raise TypeError("Method `majority' requires ping data with cluster labels,\ - but a stop table was provided") - elif method=='centroid': - stop_table = data.copy() - location = poi_map( - data=stop_table, - poi_table=poi_table, - max_distance=max_distance, - data_crs=data_crs, - location_id=location_id, - traj_cols=traj_cols, - **kwargs) - - return location - - else: - raise ValueError(f"Method {method} not among implemented methods: `centroid' and `majority'") - - else: - # is labeled pings - if not cluster_label: #try defaults and raise - if 'cluster_label' in data.columns: - cluster_label = 'cluster_label' - elif 'cluster' in data.columns: - cluster_label = 'cluster' - else: - raise ValueError(f"Argument `cluster_label` is required for visit attribution of labeled pings.") - - clustered_pings = data.loc[data[cluster_label] != -1].copy() - if method=='majority': - location = poi_map( - data=clustered_pings, - poi_table=poi_table, - max_distance=max_distance, - data_crs=data_crs, - location_id=location_id, - traj_cols=traj_cols, - **kwargs - ) - loc_col = location.name - clustered_pings = clustered_pings.join(location) - - location = clustered_pings.groupby(cluster_label)[loc_col].agg( - lambda x: x.mode().iloc[0] if not x.mode().empty else None) - - return data[[cluster_label]].join(location, on=cluster_label)[loc_col] - - elif method=='centroid': # should be medoid? - loader._has_spatial_cols(data.columns, traj_cols, exclusive=True) - use_lon_lat = ('latitude' in traj_cols and 'longitude' in traj_cols) - if use_lon_lat: - warnings.warn("Spherical ('longitude', 'latitude') coordinates were passed. Centroids will not agree with geodetic distances") - centr_data = clustered_pings.groupby(cluster_label).agg({traj_cols['longitude']:'mean', traj_cols['latitude']:'mean'}) - else: - centr_data = clustered_pings.groupby(cluster_label).agg({traj_cols['x']:'mean', traj_cols['y']:'mean'}) - - location = poi_map( - data=centr_data, - poi_table=poi_table, - max_distance=max_distance, - data_crs=data_crs, - location_id=location_id, - traj_cols=traj_cols, - **kwargs) - loc_col = location.name - - return data[[cluster_label]].join(location, on=cluster_label)[loc_col] - - else: - raise ValueError(f"Method {method} not among implemented methods: `centroid' and `majority'") - - return None - -# change to point_in_polygon, move to filters.py -def poi_map(data, poi_table, max_distance=0, data_crs=None, location_id=None, traj_cols=None, **kwargs): - """ - Assign each point in `data` to a polygon in `poi_table`, using containment when - `max_distance==0` or the nearest neighbor within `max_distance` otherwise. - - Parameters - ---------- - data : pd.DataFrame or gpd.GeoDataFrame - Input points, either as a DataFrame with coordinate columns or a GeoDataFrame. - poi_table : gpd.GeoDataFrame - Polygons to match against, indexed or with `location_id` column. - traj_cols : list of str, optional - Names of the coordinate columns in `data` when it is a DataFrame. - max_distance : float, default 0 - Maximum search radius for nearest‐neighbor matching; zero invokes a point‐in‐polygon test. - data_crs : str or pyproj.CRS, optional - CRS for `data` if it is a DataFrame; ignored for GeoDataFrames. - location_id : str, optional - Name of the geometry ID column in `poi_table`; uses the GeoDataFrame index if not provided. - **kwargs - Passed to trajectory‐column parsing helper. - - Returns - ------- - pd.Series - Indexed like `data`, with each entry set to the matching polygon’s ID (from - `location_id` or `poi_table.index`). Points not contained or beyond `max_distance` - yield NaN. When multiple polygons overlap a point, only the first match is kept. - """ - # column name handling - traj_cols = loader._parse_traj_cols(data.columns, traj_cols, kwargs, defaults={}) - - if poi_table.crs is None: - raise ValueError(f"poi_table must have crs attribute for spatial join.") - - # Determine which geometry to use - if isinstance(data, gpd.GeoDataFrame): - pings_gdf = data.geometry - # if geodataframe, data_crs is ignored but we Raise if conflicting crs because it is suspect - if data_crs and not pyproj.CRS(pings_gdf.crs).equals(pyproj.CRS(data_crs)): - raise ValueError(f"Provided CRS {data_crs} conflicts with traj CRS {data.crs}.") - - if isinstance(data, pd.DataFrame): - # Parse traj_cols with kwargs to get spatial column mappings (using empty defaults to avoid conflicts) - traj_cols_w_deflts = loader._parse_traj_cols(data.columns, traj_cols, kwargs, defaults={}, warn=False) - # check that user specified x,y or lat, lon but not both - loader._has_spatial_cols(data.columns, traj_cols_w_deflts, exclusive=True) - - use_lon_lat = ('latitude' in traj_cols_w_deflts and 'longitude' in traj_cols_w_deflts) - - if use_lon_lat: - if data_crs: - data_crs = pyproj.CRS(data_crs) - if data_crs.is_projected: - warnings.warn(f"Provided CRS {data_crs.name} is a projected coordinate system, but " - "spherical ('longitude', 'latitude') coordinates were passed. Did you mean to pass data_crs='EPSG:4326'?" - ) - else: # we assume EPSG:4326 - warnings.warn("Argument `data_crs` not provided, assuming EPSG:4326 for ('longitude', 'latitude') coordinates") - data_crs = pyproj.CRS("EPSG:4326") - - pings_gdf= gpd.points_from_xy( - data[traj_cols_w_deflts['longitude']], - data[traj_cols_w_deflts['latitude']], - crs=data_crs) # order matters: lon first - else: - if not data_crs: - raise ValueError(f"data_crs must be provided when using projected coordinates.") - data_crs = pyproj.CRS(data_crs) - if data_crs.is_geographic: - warnings.warn(f"Provided CRS {data_crs.name} is a geographic coordinate system. " - "This will lead to errors if passed coordinates ('x', 'y') are projected." - f"Did you mean to use {poi_table.crs}?" - ) - pings_gdf= gpd.points_from_xy( - data[traj_cols_w_deflts['x']], - data[traj_cols_w_deflts['y']], - crs=data_crs) - else: - raise TypeError("`data` must be a pandas DataFrame or a GeoDataFrame.") - - if not data_crs.equals(pyproj.CRS(poi_table.crs)): - poi_table = poi_table.to_crs(data_crs) - warnings.warn("CRS for `poi_table` does not match crs for `data`. Reprojecting...") - - use_poi_idx = True - if location_id is not None: - loc_col = location_id - if location_id in poi_table: - use_poi_idx=False - else: - warnings.warn(f"{location_id} column not found in {poi_table.columns}, defaulting to poi_table.index for spatial join.") - else: - loc_col = 'location_id' - warnings.warn(f"location_id column not provided, defaulting to poi_table.index for spatial join.") - - - if max_distance>0: - if data_crs.is_geographic: - warnings.warn(f"Provided CRS {data_crs.name} is a geographic coordinate system. " - "This will lead to errors when computing euclidean distances." - f"Did you mean to use `max_distance=0'?" - ) - - p_idx, idx = poi_table.sindex.nearest(pings_gdf, max_distance=max_distance, return_all=False) - if use_poi_idx: - s = pd.Series(poi_table.iloc[idx].index, index=data.index[p_idx]) - s.name = loc_col - else: - s = pd.Series(poi_table.iloc[idx][loc_col].values, index=data.index[p_idx]) - s.name = loc_col - - return s.reindex(data.index) - - else: # default max_distance = 0 - p_idx, idx = poi_table.sindex.query(pings_gdf, predicate="within") # boundary counts; use "contains" to exclude it - if use_poi_idx: - s = pd.Series(poi_table.iloc[idx].index, index=data.index[p_idx]) # might have duplicates - s = s.loc[~s.index.duplicated()] - s.name = loc_col - else: - s = pd.Series(poi_table.iloc[idx][loc_col].values, index=data.index[p_idx]) - s = s.loc[~s.index.duplicated()] - s.name = loc_col - return s.reindex(data.index) - -def oracle_map(data, true_visits, traj_cols=None, **kwargs): - """ - Map elements in traj to ground truth location based solely on time. - - Parameters - ---------- - data : pd.DataFrame - The trajectory DataFrame containing x and y coordinates. - true_visits : pd.DataFrame - A visitation table containing location IDs, start times, and durations/end times. - traj_cols : list - The columns in the trajectory DataFrame to be used for mapping. - **kwargs : dict - Additional keyword arguments. - - Returns - ------- - pd.Series - A Series containing the location IDs corresponding to the pings in the trajectory. - """ - true_visits = true_visits.copy() - data = data.copy() - - # determine temporal columns to use - t_key_l, use_datetime_l = loader._fallback_time_cols_dt(data.columns, traj_cols, kwargs) - t_key_r, use_datetime_r = loader._fallback_time_cols_dt(true_visits.columns, traj_cols, kwargs) - - - traj_cols = loader._parse_traj_cols(true_visits.columns, traj_cols, kwargs) #load defaults - if use_datetime_l != use_datetime_r: - raise ValueError(f"Mismatch in temporal columns {traj_cols[t_key_l]} vs {traj_cols[t_key_r]}.") - - # check is diary table - end_col_present = loader._has_end_cols(true_visits.columns, traj_cols) - duration_col_present = loader._has_duration_cols(true_visits.columns, traj_cols) - if not (end_col_present or duration_col_present): - raise ValueError("Missing required (end or duration) temporal columns for true_visits dataframe.") - - if traj_cols['location_id'] not in true_visits.columns: - raise ValueError(f"Missing {traj_cols[location_id]} column in {true_visits.columns}." - "pass `location_id` as keyword argument or in traj_cols." - ) - - end_t_key = 'end_datetime' if use_datetime_r else 'end_timestamp' - if not end_col_present: - if use_datetime_r: - true_visits[end_t_key] = true_visits[traj_cols[t_key_r]] + pd.to_timedelta(true_visits[traj_cols['duration']]*60, unit='s') - else: - true_visits[end_t_key] = true_visits[traj_cols[t_key_r]] + true_visits[traj_cols['duration']]*60 - - - # t_key_l and t_key_r match in type, and end_t_key exists - data[traj_cols['location_id']] = pd.NA - for idx, row in true_visits.loc[~true_visits[traj_cols['location_id']].isna()].iterrows(): - start, end, loc = row[traj_cols[t_key_r]], row[traj_cols[end_t_key]], row[traj_cols['location_id']] - data.loc[(data[traj_cols[t_key_l]]>=start)&(data[traj_cols[t_key_l]]= 0: - day_parts = [(start.time(), time.max), (time.min, end.time())] - else: - full_days = 0 - day_parts = [(start.time(), end.time()), (start.time(), start.time())] - return full_days, day_parts - -def duration_at_night_fast(start, end, dawn_hour = 6, dusk_hour = 19): - full_days, (part1, part2) = slice_datetimes_interval_fast(start, end) - total_dawn_time = dawn_time(part1, dawn_hour)+dawn_time(part2, dawn_hour) - total_dusk_time = dusk_time(part1, dusk_hour)+dusk_time(part2, dusk_hour) - return int(total_dawn_time + total_dusk_time + full_days*(dawn_hour + (24-dusk_hour))*60) - -def clip_stays_date(traj, dates, dawn_hour = 6, dusk_hour = 19): - start = pd.to_datetime(traj['start_datetime']) - duration = traj['duration'] - - # Ensure timezone-aware clipping bounds - tz = start.dt.tz - date_0 = pd.Timestamp(parse(dates[0]), tz=tz) - date_1 = pd.Timestamp(parse(dates[1]), tz=tz) - - end = start + pd.to_timedelta(duration, unit='m') - - # Clip to date range - start_clipped = start.clip(lower=date_0, upper=date_1) - end_clipped = end.clip(lower=date_0, upper=date_1) - - # Recompute durations - duration_clipped = ((end_clipped - start_clipped).dt.total_seconds() // 60).astype(int) - duration_night = [duration_at_night_fast(s, e, dawn_hour, dusk_hour) for s, e in zip(start_clipped, end_clipped)] - - return pd.DataFrame({ - 'id': traj['id'].values, - 'start': start_clipped, - 'duration': duration_clipped, - 'duration_night': duration_night, - 'location': traj['location'] - }) - -def count_nights(usr_polygon, dawn_hour = 6, dusk_hour = 19, min_dwell = 10): - nights = set() - weeks = set() - - for _, row in usr_polygon.iterrows(): - d = row['start'] - d = pd.to_datetime(d) - full_days, (part1, part2) = slice_datetimes_interval_fast(d, d + pd.to_timedelta(row['duration'], unit='m')) - - dawn1 = dawn_time(part1, dawn_hour) - dusk1 = dusk_time(part1, dusk_hour) - dawn2 = dawn_time(part2, dawn_hour) - dusk2 = dusk_time(part2, dusk_hour) - - if full_days == 0: - if dawn1 >= min_dwell: - night = d - timedelta(days=1) - nights.add(night.date()) - weeks.add((night - timedelta(days=night.weekday())).date()) - - if (dusk1 + dawn2) >= min_dwell: - night = d - nights.add(night.date()) - weeks.add((night - timedelta(days=night.weekday())).date()) - - if dusk2 >= min_dwell: - night = d + timedelta(days=1) - nights.add(night.date()) - weeks.add((night - timedelta(days=night.weekday())).date()) - else: - if dawn1 >= min_dwell: - night = d - timedelta(days=1) - nights.add(night.date()) - weeks.add((night - timedelta(days=night.weekday())).date()) - - for t in range(full_days + 1): - night = d + timedelta(days=t) - nights.add(night.date()) - weeks.add((night - timedelta(days=night.weekday())).date()) - - if dusk2 >= min_dwell: - night = d + timedelta(days=full_days + 1) - nights.add(night.date()) - weeks.add((night - timedelta(days=night.weekday())).date()) - - identifier = usr_polygon['id'].iloc[0] - location = usr_polygon['location'].iloc[0] - - return pd.DataFrame([{ - 'id': identifier, - 'location': location, - 'night_count': len(nights), - 'week_count': len(weeks) - }]) - - -def night_stops(stop_table, user='user', dawn_hour = 6, dusk_hour = 19, min_dwell = 10): - # Date range - start_date = str(stop_table['start_datetime'].min().date()) - weeks = stop_table['start_datetime'].dt.strftime('%Y-%U') - num_weeks = weeks.nunique() - - # turn dates to datetime - stop_table['start_datetime'] = pd.to_datetime(stop_table['start_datetime']) - - if 'id' not in stop_table.columns: - stop_table['id'] = user - - end_date = (parse(start_date) + timedelta(weeks=num_weeks)).date().isoformat() - dates = (start_date, end_date) - df_clipped = clip_stays_date(stop_table, dates, dawn_hour, dusk_hour) - df_clipped = df_clipped[(df_clipped['duration'] > 0) & (df_clipped['duration_night'] >= 15)] - - return df_clipped.groupby(['id', 'location'], group_keys=False).apply(count_nights(dawn_hour, dusk_hour, min_dwell)).reset_index(drop=True) - +import geopandas as gpd +import nomad.io.base as loader +import nomad.constants as constants +import warnings +import pandas as pd +import nomad.io.base as loader +import pyproj +import pdb + +# TO DO: change to stops_to_poi +def point_in_polygon(data, poi_table, method='centroid', data_crs=None, max_distance=0, + cluster_label=None, location_id=None, traj_cols=None, **kwargs): + """ + Assign each stop or cluster of pings in `data` to a polygon in `poi_table`, + either by the cluster’s centroid location or by the most frequent polygon hit. + + Parameters + ---------- + data : pd.DataFrame or gpd.GeoDataFrame + A table of pings (with optional stop/duration columns) or stops, + indexed by observation or cluster. + poi_table : gpd.GeoDataFrame + Polygons to match against, with CRS set and optional ID column. + method : {'centroid', 'majority'}, default 'centroid' + ‘centroid’ uses each cluster’s mean point; ‘majority’ picks the polygon + most often visited within each cluster (only for ping data). + data_crs : str or pyproj.CRS, optional + CRS for `data` when it is a plain DataFrame; ignored if `data` is a GeoDataFrame. + max_distance : float, default 0 + Search radius for nearest‐neighbor fall-back; zero triggers strict + point-in-polygon matching. + cluster_label : str, optional + Column name holding cluster IDs in ping data; inferred from `data` if absent. + location_id : str, optional + Column in `poi_table` containing the output ID; uses the GeoDataFrame index if None. + traj_cols : list of str, optional + Names of the coordinate columns in `data` when it is a DataFrame. + **kwargs + Passed through to `poi_map` or the trajectory-column parser. + + Returns + ------- + pd.Series + Indexed like `data`, giving the matched polygon ID for each stop or ping. + Points or clusters that fall outside every polygon or beyond `max_distance` + are set to NaN. + """ + # check if it is stop table + traj_cols_w_deflts = loader._parse_traj_cols(data.columns, traj_cols, kwargs) + end_col_present = loader._has_end_cols(data.columns, traj_cols_w_deflts) + duration_col_present = loader._has_duration_cols(data.columns, traj_cols_w_deflts) + is_stop_table = (end_col_present or duration_col_present) + + if is_stop_table: + # is stop table + if method=='majority': + raise TypeError("Method `majority' requires ping data with cluster labels,\ + but a stop table was provided") + elif method=='centroid': + stop_table = data.copy() + location = poi_map( + data=stop_table, + poi_table=poi_table, + max_distance=max_distance, + data_crs=data_crs, + location_id=location_id, + traj_cols=traj_cols, + **kwargs) + + return location + + else: + raise ValueError(f"Method {method} not among implemented methods: `centroid' and `majority'") + + else: + # is labeled pings + if not cluster_label: #try defaults and raise + if 'cluster_label' in data.columns: + cluster_label = 'cluster_label' + elif 'cluster' in data.columns: + cluster_label = 'cluster' + else: + raise ValueError(f"Argument `cluster_label` is required for visit attribution of labeled pings.") + + clustered_pings = data.loc[data[cluster_label] != -1].copy() + if method=='majority': + location = poi_map( + data=clustered_pings, + poi_table=poi_table, + max_distance=max_distance, + data_crs=data_crs, + location_id=location_id, + traj_cols=traj_cols, + **kwargs + ) + loc_col = location.name + clustered_pings = clustered_pings.join(location) + + location = clustered_pings.groupby(cluster_label)[loc_col].agg( + lambda x: x.mode().iloc[0] if not x.mode().empty else None) + + return data[[cluster_label]].join(location, on=cluster_label)[loc_col] + + elif method=='centroid': # should be medoid? + loader._has_spatial_cols(data.columns, traj_cols, exclusive=True) + use_lon_lat = ('latitude' in traj_cols and 'longitude' in traj_cols) + if use_lon_lat: + warnings.warn("Spherical ('longitude', 'latitude') coordinates were passed. Centroids will not agree with geodetic distances") + centr_data = clustered_pings.groupby(cluster_label).agg({traj_cols['longitude']:'mean', traj_cols['latitude']:'mean'}) + else: + centr_data = clustered_pings.groupby(cluster_label).agg({traj_cols['x']:'mean', traj_cols['y']:'mean'}) + + location = poi_map( + data=centr_data, + poi_table=poi_table, + max_distance=max_distance, + data_crs=data_crs, + location_id=location_id, + traj_cols=traj_cols, + **kwargs) + loc_col = location.name + + return data[[cluster_label]].join(location, on=cluster_label)[loc_col] + + else: + raise ValueError(f"Method {method} not among implemented methods: `centroid' and `majority'") + + return None + +# change to point_in_polygon, move to filters.py +def poi_map(data, poi_table, max_distance=0, data_crs=None, location_id=None, traj_cols=None, **kwargs): + """ + Assign each point in `data` to a polygon in `poi_table`, using containment when + `max_distance==0` or the nearest neighbor within `max_distance` otherwise. + + Parameters + ---------- + data : pd.DataFrame or gpd.GeoDataFrame + Input points, either as a DataFrame with coordinate columns or a GeoDataFrame. + poi_table : gpd.GeoDataFrame + Polygons to match against, indexed or with `location_id` column. + traj_cols : list of str, optional + Names of the coordinate columns in `data` when it is a DataFrame. + max_distance : float, default 0 + Maximum search radius for nearest‐neighbor matching; zero invokes a point‐in‐polygon test. + data_crs : str or pyproj.CRS, optional + CRS for `data` if it is a DataFrame; ignored for GeoDataFrames. + location_id : str, optional + Name of the geometry ID column in `poi_table`; uses the GeoDataFrame index if not provided. + **kwargs + Passed to trajectory‐column parsing helper. + + Returns + ------- + pd.Series + Indexed like `data`, with each entry set to the matching polygon’s ID (from + `location_id` or `poi_table.index`). Points not contained or beyond `max_distance` + yield NaN. When multiple polygons overlap a point, only the first match is kept. + """ + # column name handling + traj_cols = loader._parse_traj_cols(data.columns, traj_cols, kwargs, defaults={}) + + if poi_table.crs is None: + raise ValueError(f"poi_table must have crs attribute for spatial join.") + + # Determine which geometry to use + if isinstance(data, gpd.GeoDataFrame): + pings_gdf = data.geometry + # if geodataframe, data_crs is ignored but we Raise if conflicting crs because it is suspect + if data_crs and not pyproj.CRS(pings_gdf.crs).equals(pyproj.CRS(data_crs)): + raise ValueError(f"Provided CRS {data_crs} conflicts with traj CRS {data.crs}.") + + if isinstance(data, pd.DataFrame): + # Parse traj_cols with kwargs to get spatial column mappings (using empty defaults to avoid conflicts) + traj_cols_w_deflts = loader._parse_traj_cols(data.columns, traj_cols, kwargs, defaults={}, warn=False) + # check that user specified x,y or lat, lon but not both + loader._has_spatial_cols(data.columns, traj_cols_w_deflts, exclusive=True) + + use_lon_lat = ('latitude' in traj_cols_w_deflts and 'longitude' in traj_cols_w_deflts) + + if use_lon_lat: + if data_crs: + data_crs = pyproj.CRS(data_crs) + if data_crs.is_projected: + warnings.warn(f"Provided CRS {data_crs.name} is a projected coordinate system, but " + "spherical ('longitude', 'latitude') coordinates were passed. Did you mean to pass data_crs='EPSG:4326'?" + ) + else: # we assume EPSG:4326 + warnings.warn("Argument `data_crs` not provided, assuming EPSG:4326 for ('longitude', 'latitude') coordinates") + data_crs = pyproj.CRS("EPSG:4326") + + pings_gdf= gpd.points_from_xy( + data[traj_cols_w_deflts['longitude']], + data[traj_cols_w_deflts['latitude']], + crs=data_crs) # order matters: lon first + else: + if not data_crs: + raise ValueError(f"data_crs must be provided when using projected coordinates.") + data_crs = pyproj.CRS(data_crs) + if data_crs.is_geographic: + warnings.warn(f"Provided CRS {data_crs.name} is a geographic coordinate system. " + "This will lead to errors if passed coordinates ('x', 'y') are projected." + f"Did you mean to use {poi_table.crs}?" + ) + pings_gdf= gpd.points_from_xy( + data[traj_cols_w_deflts['x']], + data[traj_cols_w_deflts['y']], + crs=data_crs) + else: + raise TypeError("`data` must be a pandas DataFrame or a GeoDataFrame.") + + if not data_crs.equals(pyproj.CRS(poi_table.crs)): + poi_table = poi_table.to_crs(data_crs) + warnings.warn("CRS for `poi_table` does not match crs for `data`. Reprojecting...") + + use_poi_idx = True + if location_id is not None: + loc_col = location_id + if location_id in poi_table: + use_poi_idx=False + else: + warnings.warn(f"{location_id} column not found in {poi_table.columns}, defaulting to poi_table.index for spatial join.") + else: + loc_col = 'location_id' + warnings.warn(f"location_id column not provided, defaulting to poi_table.index for spatial join.") + + + if max_distance>0: + if data_crs.is_geographic: + warnings.warn(f"Provided CRS {data_crs.name} is a geographic coordinate system. " + "This will lead to errors when computing euclidean distances." + f"Did you mean to use `max_distance=0'?" + ) + + p_idx, idx = poi_table.sindex.nearest(pings_gdf, max_distance=max_distance, return_all=False) + if use_poi_idx: + s = pd.Series(poi_table.iloc[idx].index, index=data.index[p_idx]) + s.name = loc_col + else: + s = pd.Series(poi_table.iloc[idx][loc_col].values, index=data.index[p_idx]) + s.name = loc_col + + return s.reindex(data.index) + + else: # default max_distance = 0 + p_idx, idx = poi_table.sindex.query(pings_gdf, predicate="within") # boundary counts; use "contains" to exclude it + if use_poi_idx: + s = pd.Series(poi_table.iloc[idx].index, index=data.index[p_idx]) # might have duplicates + s = s.loc[~s.index.duplicated()] + s.name = loc_col + else: + s = pd.Series(poi_table.iloc[idx][loc_col].values, index=data.index[p_idx]) + s = s.loc[~s.index.duplicated()] + s.name = loc_col + return s.reindex(data.index) + +def oracle_map(data, true_visits, traj_cols=None, **kwargs): + """ + Map elements in traj to ground truth location based solely on time. + + Parameters + ---------- + data : pd.DataFrame + The trajectory DataFrame containing x and y coordinates. + true_visits : pd.DataFrame + A visitation table containing location IDs, start times, and durations/end times. + traj_cols : list + The columns in the trajectory DataFrame to be used for mapping. + **kwargs : dict + Additional keyword arguments. + + Returns + ------- + pd.Series + A Series containing the location IDs corresponding to the pings in the trajectory. + """ + true_visits = true_visits.copy() + data = data.copy() + + # determine temporal columns to use + t_key_l, use_datetime_l = loader._fallback_time_cols_dt(data.columns, traj_cols, kwargs) + t_key_r, use_datetime_r = loader._fallback_time_cols_dt(true_visits.columns, traj_cols, kwargs) + + + traj_cols = loader._parse_traj_cols(true_visits.columns, traj_cols, kwargs) #load defaults + if use_datetime_l != use_datetime_r: + raise ValueError(f"Mismatch in temporal columns {traj_cols[t_key_l]} vs {traj_cols[t_key_r]}.") + + # check is diary table + end_col_present = loader._has_end_cols(true_visits.columns, traj_cols) + duration_col_present = loader._has_duration_cols(true_visits.columns, traj_cols) + if not (end_col_present or duration_col_present): + raise ValueError("Missing required (end or duration) temporal columns for true_visits dataframe.") + + if traj_cols['location_id'] not in true_visits.columns: + raise ValueError(f"Missing {traj_cols[location_id]} column in {true_visits.columns}." + "pass `location_id` as keyword argument or in traj_cols." + ) + + end_t_key = 'end_datetime' if use_datetime_r else 'end_timestamp' + if not end_col_present: + if use_datetime_r: + true_visits[end_t_key] = true_visits[traj_cols[t_key_r]] + pd.to_timedelta(true_visits[traj_cols['duration']]*60, unit='s') + else: + true_visits[end_t_key] = true_visits[traj_cols[t_key_r]] + true_visits[traj_cols['duration']]*60 + + + # t_key_l and t_key_r match in type, and end_t_key exists + data[traj_cols['location_id']] = pd.NA + for idx, row in true_visits.loc[~true_visits[traj_cols['location_id']].isna()].iterrows(): + start, end, loc = row[traj_cols[t_key_r]], row[traj_cols[end_t_key]], row[traj_cols['location_id']] + data.loc[(data[traj_cols[t_key_l]]>=start)&(data[traj_cols[t_key_l]]= 0: + day_parts = [(start.time(), time.max), (time.min, end.time())] + else: + full_days = 0 + day_parts = [(start.time(), end.time()), (start.time(), start.time())] + return full_days, day_parts + +def duration_at_night_fast(start, end, dawn_hour = 6, dusk_hour = 19): + full_days, (part1, part2) = slice_datetimes_interval_fast(start, end) + total_dawn_time = dawn_time(part1, dawn_hour)+dawn_time(part2, dawn_hour) + total_dusk_time = dusk_time(part1, dusk_hour)+dusk_time(part2, dusk_hour) + return int(total_dawn_time + total_dusk_time + full_days*(dawn_hour + (24-dusk_hour))*60) + +def clip_stays_date(traj, dates, dawn_hour = 6, dusk_hour = 19): + start = pd.to_datetime(traj['start_datetime']) + duration = traj['duration'] + + # Ensure timezone-aware clipping bounds + tz = start.dt.tz + date_0 = pd.Timestamp(parse(dates[0]), tz=tz) + date_1 = pd.Timestamp(parse(dates[1]), tz=tz) + + end = start + pd.to_timedelta(duration, unit='m') + + # Clip to date range + start_clipped = start.clip(lower=date_0, upper=date_1) + end_clipped = end.clip(lower=date_0, upper=date_1) + + # Recompute durations + duration_clipped = ((end_clipped - start_clipped).dt.total_seconds() // 60).astype(int) + duration_night = [duration_at_night_fast(s, e, dawn_hour, dusk_hour) for s, e in zip(start_clipped, end_clipped)] + + return pd.DataFrame({ + 'id': traj['id'].values, + 'start': start_clipped, + 'duration': duration_clipped, + 'duration_night': duration_night, + 'location': traj['location'] + }) + +def count_nights(usr_polygon, dawn_hour = 6, dusk_hour = 19, min_dwell = 10): + nights = set() + weeks = set() + + for _, row in usr_polygon.iterrows(): + d = row['start'] + d = pd.to_datetime(d) + full_days, (part1, part2) = slice_datetimes_interval_fast(d, d + pd.to_timedelta(row['duration'], unit='m')) + + dawn1 = dawn_time(part1, dawn_hour) + dusk1 = dusk_time(part1, dusk_hour) + dawn2 = dawn_time(part2, dawn_hour) + dusk2 = dusk_time(part2, dusk_hour) + + if full_days == 0: + if dawn1 >= min_dwell: + night = d - timedelta(days=1) + nights.add(night.date()) + weeks.add((night - timedelta(days=night.weekday())).date()) + + if (dusk1 + dawn2) >= min_dwell: + night = d + nights.add(night.date()) + weeks.add((night - timedelta(days=night.weekday())).date()) + + if dusk2 >= min_dwell: + night = d + timedelta(days=1) + nights.add(night.date()) + weeks.add((night - timedelta(days=night.weekday())).date()) + else: + if dawn1 >= min_dwell: + night = d - timedelta(days=1) + nights.add(night.date()) + weeks.add((night - timedelta(days=night.weekday())).date()) + + for t in range(full_days + 1): + night = d + timedelta(days=t) + nights.add(night.date()) + weeks.add((night - timedelta(days=night.weekday())).date()) + + if dusk2 >= min_dwell: + night = d + timedelta(days=full_days + 1) + nights.add(night.date()) + weeks.add((night - timedelta(days=night.weekday())).date()) + + identifier = usr_polygon['id'].iloc[0] + location = usr_polygon['location'].iloc[0] + + return pd.DataFrame([{ + 'id': identifier, + 'location': location, + 'night_count': len(nights), + 'week_count': len(weeks) + }]) + + +def night_stops(stop_table, user='user', dawn_hour = 6, dusk_hour = 19, min_dwell = 10): + # Date range + start_date = str(stop_table['start_datetime'].min().date()) + weeks = stop_table['start_datetime'].dt.strftime('%Y-%U') + num_weeks = weeks.nunique() + + # turn dates to datetime + stop_table['start_datetime'] = pd.to_datetime(stop_table['start_datetime']) + + if 'id' not in stop_table.columns: + stop_table['id'] = user + + end_date = (parse(start_date) + timedelta(weeks=num_weeks)).date().isoformat() + dates = (start_date, end_date) + df_clipped = clip_stays_date(stop_table, dates, dawn_hour, dusk_hour) + df_clipped = df_clipped[(df_clipped['duration'] > 0) & (df_clipped['duration_night'] >= 15)] + + return df_clipped.groupby(['id', 'location'], group_keys=False).apply(count_nights(dawn_hour, dusk_hour, min_dwell)).reset_index(drop=True) +