diff --git a/docs/html/.buildinfo b/docs/html/.buildinfo deleted file mode 100644 index 85cc1077..00000000 --- a/docs/html/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 7dce5705cc44c0e39c18e0a6ae81cab0 -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/html/.doctrees/environment.pickle b/docs/html/.doctrees/environment.pickle deleted file mode 100644 index 1b499d44..00000000 Binary files a/docs/html/.doctrees/environment.pickle and /dev/null differ diff --git a/docs/html/.doctrees/index.doctree b/docs/html/.doctrees/index.doctree deleted file mode 100644 index b615ed22..00000000 Binary files a/docs/html/.doctrees/index.doctree and /dev/null differ diff --git a/docs/html/.doctrees/sections/Code_overview.doctree b/docs/html/.doctrees/sections/Code_overview.doctree deleted file mode 100644 index c0f433fd..00000000 Binary files a/docs/html/.doctrees/sections/Code_overview.doctree and /dev/null differ diff --git a/docs/html/.doctrees/sections/Command_line_interface.doctree b/docs/html/.doctrees/sections/Command_line_interface.doctree deleted file mode 100644 index c817301e..00000000 Binary files a/docs/html/.doctrees/sections/Command_line_interface.doctree and /dev/null differ diff --git a/docs/html/.doctrees/sections/Configuration/Configuration_overview.doctree b/docs/html/.doctrees/sections/Configuration/Configuration_overview.doctree deleted file mode 100644 index a913f66b..00000000 Binary files a/docs/html/.doctrees/sections/Configuration/Configuration_overview.doctree and /dev/null differ diff --git a/docs/html/.doctrees/sections/Configuration/Route_planning_config.doctree b/docs/html/.doctrees/sections/Configuration/Route_planning_config.doctree deleted file mode 100644 index f5892c54..00000000 Binary files a/docs/html/.doctrees/sections/Configuration/Route_planning_config.doctree and /dev/null differ diff --git a/docs/html/.doctrees/sections/Configuration/Vessel_performance_config.doctree b/docs/html/.doctrees/sections/Configuration/Vessel_performance_config.doctree deleted file mode 100644 index 7737276c..00000000 Binary files a/docs/html/.doctrees/sections/Configuration/Vessel_performance_config.doctree and /dev/null differ diff --git a/docs/html/.doctrees/sections/Examples.doctree b/docs/html/.doctrees/sections/Examples.doctree deleted file mode 100644 index eed9aa30..00000000 Binary files a/docs/html/.doctrees/sections/Examples.doctree and /dev/null differ diff --git a/docs/html/.doctrees/sections/Installation.doctree b/docs/html/.doctrees/sections/Installation.doctree deleted file mode 100644 index 5816cf8b..00000000 Binary files a/docs/html/.doctrees/sections/Installation.doctree and /dev/null differ diff --git a/docs/html/.doctrees/sections/Outputs.doctree b/docs/html/.doctrees/sections/Outputs.doctree deleted file mode 100644 index 0b6a85cd..00000000 Binary files a/docs/html/.doctrees/sections/Outputs.doctree and /dev/null differ diff --git a/docs/html/.doctrees/sections/Route_calculation.doctree b/docs/html/.doctrees/sections/Route_calculation.doctree deleted file mode 100644 index e89a712e..00000000 Binary files a/docs/html/.doctrees/sections/Route_calculation.doctree and /dev/null differ diff --git a/docs/html/.doctrees/sections/Route_optimisation.doctree b/docs/html/.doctrees/sections/Route_optimisation.doctree deleted file mode 100644 index 5fdde543..00000000 Binary files a/docs/html/.doctrees/sections/Route_optimisation.doctree and /dev/null differ diff --git a/docs/html/.doctrees/sections/Vehicle_specifics.doctree b/docs/html/.doctrees/sections/Vehicle_specifics.doctree deleted file mode 100644 index 14d471f9..00000000 Binary files a/docs/html/.doctrees/sections/Vehicle_specifics.doctree and /dev/null differ diff --git a/docs/html/_images/FlowDiagram_Overview.png b/docs/html/_images/FlowDiagram_Overview.png deleted file mode 100644 index e8ab909a..00000000 Binary files a/docs/html/_images/FlowDiagram_Overview.png and /dev/null differ diff --git a/docs/html/_images/Mesh_Fuel_Speed.jpg b/docs/html/_images/Mesh_Fuel_Speed.jpg deleted file mode 100644 index 6733675e..00000000 Binary files a/docs/html/_images/Mesh_Fuel_Speed.jpg and /dev/null differ diff --git a/docs/html/_images/PolarRoute_CLI.png b/docs/html/_images/PolarRoute_CLI.png deleted file mode 100644 index 21271425..00000000 Binary files a/docs/html/_images/PolarRoute_CLI.png and /dev/null differ diff --git a/docs/html/_images/PolarRoute_CodeFlowDiagram.png b/docs/html/_images/PolarRoute_CodeFlowDiagram.png deleted file mode 100644 index 675e5534..00000000 Binary files a/docs/html/_images/PolarRoute_CodeFlowDiagram.png and /dev/null differ diff --git a/docs/html/_images/VesselUML.png b/docs/html/_images/VesselUML.png deleted file mode 100644 index 96037c25..00000000 Binary files a/docs/html/_images/VesselUML.png and /dev/null differ diff --git a/docs/html/_sources/index.rst.txt b/docs/html/_sources/index.rst.txt deleted file mode 100644 index 1b9ae488..00000000 --- a/docs/html/_sources/index.rst.txt +++ /dev/null @@ -1,34 +0,0 @@ -Welcome to the PolarRoute Manual Pages -====================================== - -PolarRoute is a tool for the optimisation of routes for maritime vehicles travelling in polar waters. -It depends on `MeshiPhi `_; a package designed to -discretise the world from heterogeneous data sources (follow link for more details and docs). -This software package has been developed by the **British Antarctic Survey** (BAS). It was originally designed primarily for the -optimisation of polar routes for the BAS research vessel RRS Sir David Attenborough, however it is applicable -to any vessel (e.g. AUVs). The software is written in Python and is open source. - -Plotting functionality for the outputs of PolarRoute is provided by the `GeoPlot `_ package, also developed at BAS. - -For more information on the project, please visit the `PolarRoute website `_ -and follow our `GitHub repository `_. - - -.. note:: The development of this codebase is ongoing and not yet complete. - Please contact the developers for more information. - -Contents: - -.. toctree:: - :maxdepth: 2 - :numbered: - - ./sections/Installation - ./sections/Examples - ./sections/Command_line_interface - ./sections/Configuration/Configuration_overview - ./sections/Outputs - ./sections/Code_overview - ./sections/Vehicle_specifics - ./sections/Route_calculation - ./sections/Route_optimisation diff --git a/docs/html/_sources/sections/Code_overview.rst.txt b/docs/html/_sources/sections/Code_overview.rst.txt deleted file mode 100644 index f6e18048..00000000 --- a/docs/html/_sources/sections/Code_overview.rst.txt +++ /dev/null @@ -1,37 +0,0 @@ -********** -Background -********** - -Code Overview -############# - -The PolarRoute package provides an automated route-planning method for use by an ice-strengthened vessel operating in the polar regions. The long-distance route planning approach builds on the method developed for underwater vehicles reported in Fox et al (2021). We start with the same grid-based route construction approach to obtain routes that satisfy constraints on the performance of the ship in ice. We then apply a novel route-smoothing method to the resulting routes, shortening the grid-based routes and ensure that routes follow great circle arcs where possible. This two-stage process efficiently generates routes that follow standard navigation solutions in open water and optimise vessel performance in and around areas dominated by sea ice. While we have focussed on navigation in and around polar ice, our methods are also applicable to shipping in a broader context (e.g.: commercial shipping) where route-planning must be responsive to changing local and weather conditions. - - -Code Structure -############## -The aim of this manual is to provide the user with all the tools that they need to run the software for a set of examples. We also hope that the background information supplied for each section allows the user to understand the methods used throughout this software package. - -The PolarRoute package can be separated into the four main sections shown in the Figure below: - -.. figure:: ./Figures/FlowDiagram_Overview.png - :align: center - :width: 700 - - *Overview figure outlining the stages in planning a route with PolarRoute. Generation of the initial environmental mesh is performed by the MeshiPhi package.* - -The separate stages can be broken down into: - -1. :ref:`Vessel Performance ` - Application of vehicle specific features applied to the discrete mesh. In this section we will supply the user with the knowledge of how vehicle specific features are applied to the discrete mesh or with variables applied to the computational graph of the mesh. -2. :ref:`Route Planner` - Generating grid-based dijkstra paths and data constrained path smoothing from the gridded solutions - In this section we will give the user the background to constructing paths between user defined waypoints that minimise a specific objective function (e.g. travel time, fuel). Once the gridded Dijkstra paths are formulated we outline a smoothing based procedure that uses the data information to generate non-gridded improved route paths. - -.. figure:: ./Figures/PolarRoute_CodeFlowDiagram.png - :align: center - :width: 700 - - *Overview figure outlining the Input/Output of all sections of the Route Planning pipeline* - -Each stage of this pipeline makes use of a configuration file, found in the :ref:`Configuration Overview` section of the documentation -and produces an output file, the form of which can be found in the :ref:`outputs` section of this document. - -In addition to the main section of the codebase we have also developed a series of plotting classes that allows the user to generate interactive maps and static figures for the Mesh Features and Route Paths. These can be found in the `Plotting` section later in the manual. \ No newline at end of file diff --git a/docs/html/_sources/sections/Command_line_interface.rst.txt b/docs/html/_sources/sections/Command_line_interface.rst.txt deleted file mode 100644 index cc00d197..00000000 --- a/docs/html/_sources/sections/Command_line_interface.rst.txt +++ /dev/null @@ -1,232 +0,0 @@ -.. _cli: - -############################### -Command Line Interface -############################### - -The PolarRoute package provides multiple CLI entry points, intended to be used in succession to plan a route through a digital environment. - -^^^^^^^^^^^ -create_mesh -^^^^^^^^^^^ -The *create_mesh* entry point builds a digital environment file from a collection of source data, which can then be used -by the vessel performance modeller and route planner. - -:: - - create_mesh - -positional arguments: - -:: - - config : A configuration file detailing how to build the digital environment. JSON parsable - -The format of the required *config.json* file can be found in the `"Configuration - Mesh Construction" `_ section of the `MeshiPhi documentation `_ . -There are also example configuration files available in the directory :code:`examples/environment_config/grf_example.config.json` - -optional arguments: - -:: - - -v (verbose logging) - -o (set output location for mesh) - - -^^^^^^^^^^^ -add_vehicle -^^^^^^^^^^^ -The *add_vehicle* command allows vehicle specific simulations to be performed on the digital environment. This vehicle specific -information is then encoded into the digital environment file. - -:: - - add_vehicle - -positional arguments: - -:: - - vessel_config : A configuration file giving details of the vessel to be simulated. - mesh : A digital environment file. - -The format for the required *vessel.json* file can be found in the :ref:`configuration - vessel performance modeller` section of the documentation. -The required *mesh.json* file can be created using the *create_mesh* command from the `MeshiPhi `_ package. - -optional arguments are - -:: - - -v (verbose logging) - -o (set output location for mesh) - -The format of the return Vessel_Mesh.json file is explained in :ref:`the vessel_mesh.json file` section of the documentation. - -^^^^^^^^^^^^^^^^^^ -resimulate_vehicle -^^^^^^^^^^^^^^^^^^ - -The *resimulate_vehicle* command allows vehicle specific simulations to be performed again on an existing vessel mesh. -This allows new models to be easily run using the pre-existing config parameters. - -:: - - resimulate_vehicle - -positional arguments: - -:: - - vessel_mesh : A digital environment file with added vessel specific simulations. - -optional arguments are - -:: - - -v (verbose logging) - -o (set output location for mesh) - -^^^^^^^^^^^^^^^ -optimise_routes -^^^^^^^^^^^^^^^ -Optimal routes through a mesh can be calculated using the command: - -:: - - optimise_routes - -positional parameters: - -:: - - vessel_mesh : A digital environment file with added vessel specific simulations. - route_config : A configuration file detailing optimisation parameters to be used when route planning. - waypoints: A .csv file containing waypoints to be travelled between. - - -The format for the required *route_config.json* file can be found in the :ref:`configuration - route planning` section of the documentation. -The required *vessel_mesh.json* file can be generated using the :ref:`add_vehicle` command shown above. -The format for the required *waypoints.csv* file is as follows: - -As a table: - -+------------------+---------------+---------------+---------+---------------+ -| Name | Lat | Long | Source | Destination | -+==================+===============+===============+=========+===============+ -| Halley | -75.267 | -27.216 | | X | -+------------------+---------------+---------------+---------+---------------+ -| Rothera | -67.617 | -68.047 | | | -+------------------+---------------+---------------+---------+---------------+ -| South Georgia | -54.141 | -36.094 | X | | -+------------------+---------------+---------------+---------+---------------+ -| Falklands | -51.953 | -57.969 | | | -+------------------+---------------+---------------+---------+---------------+ -| Elephant Island | -60.977 | -55.078 | | | -+------------------+---------------+---------------+---------+---------------+ - -In .csv format: - -:: - - Name,Lat,Long,Source,Destination - Halley,-75.267,-27.216,,X - Rothera,-67.617,-68.047 ,, - South Georgia,-54.141,-36.094,X, - Falklands,-51.953,-57.969,, - Elephant Island,-60.977,-55.078,, - -Additional waypoints may be added by extending the *waypoints.csv* file. Which waypoints are navigated between is determined by -adding an **X** in either the *Source* or *Destination* columns. When processed, the route planner will create routes from all -waypoints marked with an **X** in the *Source* column to all waypoints marked with a **X** in the *Destination* column. - -optional arguments are - -:: - - -v (verbose logging) - -o (set output location for mesh) - -p (output only the calculated path, not the entire mesh) - -d (output Dijkstra path as well as smoothed path) - - -The format of the returned *route.json* file is explained in :ref:`the route.json file` section of this documentation. - -^^^^^^^^^^^^^^^ -calculate_route -^^^^^^^^^^^^^^^ -The cost of a user-defined route through a pre-generated mesh containing vehicle information can be calculated using the command: - -:: - - calculate_route - -positional parameters: - -:: - - vessel_mesh : A digital environment file with added vessel specific simulations. - route : A route file containing waypoints on a user-defined path. - -optional arguments: - -:: - - -v : verbose logging - -o : output location - -Running this command will calculate the cost of a route between a set of waypoints provided in either csv or geojson -format. The route is assumed to travel from waypoint to waypoint in the order they are given, following a rhumb line. -The format of the output *route.json* file is identical to that from the :ref:`optimise_routes` command. -This is explained in :ref:`the route.json file` section of the documentation. The time and fuel cost of the route will -also be logged out once the route file has been generated. If the user-defined route crosses a cell in the mesh that is -considered inaccessible to the vessel then a warning will be displayed and no route will be saved. - -^^^^^^^^^^^^^^ -extract_routes -^^^^^^^^^^^^^^ - -This command allows individual routes to be extracted from a larger file containing multiple routes. It automatically -determines the output format from the output filename given. Supported output types are json, geojson, csv, kml and gpx. - -:: - - extract_routes - -positional parameters: - -:: - - route_file.json : A file containing multiple geojson formatted routes. - -optional arguments: - -:: - - -v : verbose logging - -o : output location - -^^^^^^^^ -Plotting -^^^^^^^^ -Meshes produced at any stage in the route planning process can be visualised using the :code:`plot_mesh` cli command from the `GeoPlot `_ -library. Meshes and routes can also be plotted in other GIS software such as QGIS by exporting the mesh to a commonly used format such -as .geojson or .tif using the export_mesh command described in the `MeshiPhi `_ docs. - -:: - - plot_mesh - -optional arguments: - -:: - - -v : verbose logging - -o : output location - -a : add directional arrows to routes - -r : plot an additional route from a file - -.. figure:: ./Figures/PolarRoute_CLI.png - :align: center - :width: 700 - - *Overview figure of the Command Line Interface entry points of PolarRoute* diff --git a/docs/html/_sources/sections/Configuration/Configuration_overview.rst.txt b/docs/html/_sources/sections/Configuration/Configuration_overview.rst.txt deleted file mode 100644 index f96b5c31..00000000 --- a/docs/html/_sources/sections/Configuration/Configuration_overview.rst.txt +++ /dev/null @@ -1,27 +0,0 @@ -###################################### -Configuration Overview -###################################### - -In this section we will outline the standard structure for a configuration file used in -all portions of the PolarRoute software package. - -Each stage of the route-planning process is configured by a separate configuration file. -The configuration files are written in JSON, and are passed to each stage of the -route-planning process as command-line arguments or through a Python script. - -Example configuration files are provided in the `config` directory. - -The format of the config used to generate an environmental mesh can be found in the `"Configuration - Mesh Construction" `_ section of the `MeshiPhi documentation `_ . - -Descriptions of the configuration options for the Vessel Performance Modelling can -be found in the :ref:`Configuration - Vessel Performance Modeller` section of the -documentation. - -Descriptions of the configuration options for Route Planning can be found in the -:ref:`Configuration - Route Planning` section of the documentation. - -.. toctree:: - :maxdepth: 1 - - ./Vessel_performance_config - ./Route_planning_config diff --git a/docs/html/_sources/sections/Configuration/Route_planning_config.rst.txt b/docs/html/_sources/sections/Configuration/Route_planning_config.rst.txt deleted file mode 100644 index e1a152fb..00000000 --- a/docs/html/_sources/sections/Configuration/Route_planning_config.rst.txt +++ /dev/null @@ -1,54 +0,0 @@ -.. _route config: - -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Configuration - Route Planning -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -:: - - { - "objective_function": "traveltime", - "path_variables": [ - "fuel", - "traveltime" - ], - "vector_names": ["uC","vC"] - } - -above is the minimal set of configuration parameters necessary to run the route planner where the variables are as follows: - -* **objective_function** *(string)* : Defining the objective function to minimise for the route construction. This variable can either be defined as 'traveltime', 'battery' or 'fuel', depending on the vessel type . -* **path_variables** *(list<(string)>)* : A list of strings of the route variables to return in the output geojson. -* **vector_names** *(list<(string)>)* : The definition of the horizontal and vertical components of the vector acting on the ship in each cell. Data for these names must be within the cells of the input mesh. - -:: - - { - "objective_function": "traveltime", - "path_variables": [ - "fuel", - "traveltime" - ], - "vector_names": ["uC","vC"], - "time_unit": "days", - "adjust_waypoints": true, - "zero_currents": false, - "fixed_speed": false, - "waypoint_splitting": false, - "smoothing_max_iterations": 2000, - "smoothing_blocked_sic": 10.0, - "smoothing_merge_separation": 1e-3, - "smoothing_converged_separation": 1e-3 - } - -above is the full set of configuration parameters that can be used for route planning, where the additional variables are as follows: - -* **time_unit** *(string)* : The time unit to use in the route output . Currently only takes 'days', but will support 'hrs' in future releases. -* **adjust_waypoints** *(bool)* : Used to enable or disable the process that moves waypoints placed in inaccessible regions to the nearest accessible location. -* **zero_currents** *(bool)* : For development use only. Removes the effect of currents acting on the ship, setting all current vectors to zero. -* **fixed_speed** *(bool)* : For development use only. Removes the effect of variable speed acting on the ship, ship speed set to max speed defined in the vessel config. -* **waypoint_splitting** *(bool)* : Used to enable or disable splitting around the input waypoints. If enabled, all cells containing waypoints will be split to the maximum split depth given in the mesh config. -* **smoothing_max_iterations** *(int)* : For development use only. Maximum number of iterations in the path smoothing. For most paths convergence is met 100x earlier than this value. -* **smoothing_blocked_sic** *(float)* : For development use only. The maximum difference in sea ice concentration allowed before a cell is blocked for the smoothing. -* **smoothing_merge_sep** *(float)* : For development use only. Minimum difference between two path smoothing iterations before a merge is triggered. -* **smoothing_converged_sep** *(float)* : For development use only. Minimum difference between two path smoothing iterations before convergence is triggered. - diff --git a/docs/html/_sources/sections/Configuration/Vessel_performance_config.rst.txt b/docs/html/_sources/sections/Configuration/Vessel_performance_config.rst.txt deleted file mode 100644 index 187842d1..00000000 --- a/docs/html/_sources/sections/Configuration/Vessel_performance_config.rst.txt +++ /dev/null @@ -1,38 +0,0 @@ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Configuration - Vessel Performance Modeller -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The Vessel configuration file provides all the necessary information about the vessel that will execute -the routes such that performance parameters (e.g. speed or fuel consumption) can be calculated by the -`VesselPerformanceModeller` class. A file of this structure is also used as a command line argument for -the 'add_vehicle' entry point. - -:: - - { - "vessel_type": "SDA", - "max_speed": 26.5, - "unit": "km/hr", - "beam": 24.0, - "hull_type": "slender", - "force_limit": 96634.5, - "max_ice_conc": 80, - "min_depth": 10, - "max_wave": 3, - "excluded_zones": ["exclusion_zone"], - "neighbour_splitting": true - } - -Above are a typical set of configuration parameters used for a vessel where the variables are as follows: - -* **vessel_type** *(string)* : The specific vessel class to use for performance modelling. -* **max_speed** *(float)* : The maximum speed of the vessel in open water. -* **unit** *(string)* : The units of measurement for the speed of the vessel (currently only "km/hr" is supported). -* **beam** *(float)* : The beam (width) of the ship in metres. -* **hull_type** *(string)* : The hull profile of the ship (should be one of either "slender" or "blunt"). -* **force_limit** *(float)* : The maximum allowed resistance force, specified in Newtons. -* **max_ice_conc** *(float)* : The maximum Sea Ice Concentration the vessel is able to travel through given as a percentage. -* **min_depth** *(float)* : The minimum depth of water the vessel is able to travel through in metres. -* **max_wave** *(float)* : The maximum significant wave height the vessel is able to travel through in metres. -* **excluded_zones** *(float)* : A list of of strings that name different boolean properties of a cell. Any cell with a value of True for any of the entered keys will be marked as unnavigable. -* **neighbour_splitting** *(bool)* : Used to enable or disable a feature that splits all accessible cells neighbouring inaccessible cells. This improves routing performance but can be disabled to speed up the vessel performance modelling. diff --git a/docs/html/_sources/sections/Examples.rst.txt b/docs/html/_sources/sections/Examples.rst.txt deleted file mode 100644 index ee57eb60..00000000 --- a/docs/html/_sources/sections/Examples.rst.txt +++ /dev/null @@ -1,226 +0,0 @@ -############################### -Command Line Interface Examples -############################### - -The CLI provides multiple entry-points through which the PolarRoute package can be used. Each command is described in the -:ref:`Command Line Interface ` section of these docs. - -To summarise, the basic process is to create an environment mesh, add vehicle performance characteristics to each -cell in that mesh and then find an optimal route between waypoints located within that mesh. At any stage, `GeoPlot `_ -can be used to visualise the outputs. - -:: - - # From MeshiPhi - create_mesh -o - - # From PolarRoute - add_vehicle -o - optimise_routes -o - - # From GeoPlot - plot_mesh -o - - -Above are the commands to run in order to fulfill this process. If you have successfully installed PolarRoute and would -like to try it out, :download:`here` -is some example data which you can use. Simply extract the configs out of the zip archive, and run the commands on the -appropriate files. To map the commands to the files in the zip archive: - -* :code:`` is called :code:`grf_example.config.json` -* :code:`` is called :code:`ship.config.json` -* :code:`` is called :code:`traveltime.config.json` -* :code:`` is called :code:`waypoints_example.csv` - -.. note:: - By default the :code:`plot_mesh` command will plot a basemap showing the location of your mesh on Earth. - When working with entirely synthetic data, e.g. when running any of the GRF examples below, the spatial coordinates - used do not correspond to a real location and we recommend running :code:`plot_mesh` with the :code:`-b` option to - disable this basemap. - -Several notebooks have been created that will guide you through each stage in using PolarRoute, from mesh creation -through to route planning. These notebooks are available via Google Colab and use the CLI entry-points to show how -someone would typically interact with PolarRoute through the terminal. - -^^^^^^^^^^^^^^^^^^ -Empty Mesh Example -^^^^^^^^^^^^^^^^^^ -Here we provide two examples of empty meshes that are simple to process to get you started. Since these are empty meshes, -we expect the optimal calculated route to be a straight line between two waypoints. Over long distances this is seen as -a great circle arc on the mercator projection that GeoPlot uses to display the mesh. - -* :download:`Uniform Mesh` -* :download:`Non-Uniform Mesh` -* `See on Google Colab `_ - -^^^^^^^^^^^^^^^^^^^^^^ -Synthetic Data Example -^^^^^^^^^^^^^^^^^^^^^^ -In this example, we provide synthetic data in the form of Gaussian Random Fields (GRFs), which provide a random, yet somewhat -realistic representation of real-world features such as bathymetry. Here we walk through every step involved in PolarRoute, -from creating the mesh through to optimising a route within it. - -* :download:`Gaussian Random Field data` -* `Synthetic Data Example `_ - -^^^^^^^^^^^^^^^^^ -Real Data Example -^^^^^^^^^^^^^^^^^ -Real world data has been used to generate these meshes around the coast of Antarctica. This data is publicly available, -however is not included here to avoid violating data sharing policies. Instead, we provide a mesh file after the 'create_mesh' stage -since that is a derived product. The data files used to construct the mesh can be seen in the :code:`data_sources` field of -the config contained within the provided mesh. See `Dataloaders `_ -in the MeshiPhi docs for more info on each source of data that PolarRoute currently supports. - -* :download:`Real-world data 1` -* :download:`Real-world data 2` -* `Real Data Example `_ - -############### -Python Examples -############### - -Route planning may also be done in a python interpreter. In this case, the CLI is not required but the steps required for route planning -follow the same format - create a digital environment; simulated a vessel against it; optimise a route plan through the digital environment. -To perform the steps detailed in this section, a mesh must first be generated using `MeshiPhi `_. - -The files used in the following example are those used in the synthetic example from the notebook section above. Download them -:download:`here`. - -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Creating the digital environment. -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -A configuration file is needed to initialise the **EnvironmentMesh** object which forms the digital environment. This configuration file -is of the same format used in the :ref:`create_mesh` CLI entry-point, and may either be loaded from a *json* file or constructed -within a python interpreter. - -Loading configuration from *json* file: -:: - - import json - # Read in config file - with open('/path/to/grf_example.config.json', 'r') as f: - config = json.load(f) - - -The **EnvironmentMesh** object can then be initialised. This mesh object will be constructed using the parameters in its -configuration file. This mesh object can then be manipulated further, such as increasing its resolution through further -splitting, adding additional data sources or altering its configuration parameters. See the relevant section of the `MeshiPhi docs `_ -for a more in-depth explanation. The **EnvironmentMesh** object can then be cast to a json object and saved to a file. -:: - - from meshiphi.mesh_generation.mesh_builder import MeshBuilder - - # Create mesh from config - cg = MeshBuilder(config).build_environmental_mesh() - mesh = cg.to_json() - - # Save output file - with open('/path/to/grf_example.mesh.json', 'w+') as f: - config = json.dump(mesh, f, indent=4) - -.. note:: - We are saving the file after each stage, but if you are running the code snippets - back to back, there is no need to save the json output and then load it in again. - Just pass the dictionary created from the :code:`to_json()` call into the next function - - -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Simulating a Vessel in a Digital Environment -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Once a digital environment **EnvironmentMesh** object has been created with `MeshiPhi `_, -a vessel's performance when travelling within it may be simulated. The **VesselPerformanceModeller** object requires a -digital environment in *json* format and vessel specific configuration parameters, also in *json* format. These may either -be loaded from a file, or created within any python interpreter. - -Loading mesh and vessel from *json* files: -:: - - # Loading digital environment from file - with open('/path/to/grf_example.mesh.json', 'r') as f: - mesh = json.load(f) - - # Loading vessel configuration parameters from file - with open('/path/to/ship.json', 'r') as f: - vessel = json.load(f) - -The **VesselPerformanceModeller** object can then be initialised. This can be used to simulate the performance of the -vessel and encode this information into the digital environment. -:: - - from polar_route.vessel_performance.vessel_performance_modeller import VesselPerformanceModeller - vp = VesselPerformance(mesh, vessel) - vp.model_accessibility() # Method to determine any inaccessible areas, e.g. land - vp.model_performance() # Method to determine the performance of the vessel in accessible regions, e.g speed or fuel consumption - -The **VesselPerformanceModeller** object can then be cast to a json object and saved to a file. This *vessel_mesh.json* file can then -be used by the CLI entry-point :ref:`optimise_routes`, or the json object can be passed to the **RoutePlanner** object in a python -console. -:: - - vessel_mesh = vp.to_json() - # Save to output file - with open('/path/to/grf_example.vessel.json', 'w+') as f: - json.dump(vessel_mesh, f, indent=4) - -^^^^^^^^^^^^^^^^^^ -Route Optimisation -^^^^^^^^^^^^^^^^^^ -Now that the vessel dependent environmental mesh is defined, and represented in the **VesselPerformanceModeller** object, -we can construct routes, with parameters defined by the user in the :ref:`route config file `. - -Waypoints are passed as an input file path, `waypoints.csv`, an example file is give in the :ref:`Command Line Interface ` -section of this documentation. The route construction is performed in two stages: construction of the mesh optimal dijkstra -routes, using the `.compute_routes()` method, and the smoothing of the dijkstra routes to further optimise the solution -and reduce mesh dependency, using `.compute_smooth_routes()`. During the execution of `.compute_routes()` the routes are -stored as an attribute of the **RoutePlanner** object under `routes_dijkstra`. These are then complemented by the smoothed -routes under `routes_smoothed` after running `.compute_smooth_routes()`. An additional entry `waypoints` is generated to -store the waypoints information used in route construction. For further details about the structure of the outputs of the -route planner please see the :ref:`Outputs` section of this documentation. - -:: - - from polar_route.route_planner.route_planner import RoutePlanner - rp = RoutePlanner('/path/to/grf_example.vessel.json', - '/path/to/traveltime.config.json') - # Calculate optimal dijkstra path between waypoints - rp.compute_routes('/path/to/waypoints_example.csv') - # Smooth the dijkstra routes - rp.compute_smoothed_routes() - - route_mesh = rp.to_json() - # Save to output file - with open('/path/to/grf_example.route.json', 'w+') as f: - json.dump(route_mesh, f, indent=4) - - -^^^^^^^^^^^^^^^^^^^ -Visualising Outputs -^^^^^^^^^^^^^^^^^^^ - -The **EnvironmentMesh** object can be visualised using the GeoPlot package, also developed by BAS. This package is not -included in the distribution of PolarRoute, but can be installed using the following command: - -:: - - pip install bas_geoplot - -GeoPlot can then be used to visualise the **EnvironmentMesh** object using the following code in an iPython notebook or -any python interpreter: - -:: - - from bas_geoplot.interactive import Map - - mesh = pd.DataFrame(mesh_json['cellboxes']) - mp = Map(title="GRF Example") - - mp.Maps(mesh, 'MeshGrid', predefined='cx') - mp.Maps(mesh, 'SIC', predefined='SIC') - mp.Maps(mesh, 'Elevation', predefined='Elev', show=False) - mp.Vectors(mesh,'Currents', show=False, predefined='Currents') - mp.Vectors(mesh, 'Winds', predefined='Winds', show=False) - - mp.show() diff --git a/docs/html/_sources/sections/Installation.rst.txt b/docs/html/_sources/sections/Installation.rst.txt deleted file mode 100644 index 0c3528b6..00000000 --- a/docs/html/_sources/sections/Installation.rst.txt +++ /dev/null @@ -1,95 +0,0 @@ -************ -Installation -************ - -In this section we will outline the necessary steps for installing the PolarRoute software package. Any installation of PolarRoute will also include `MeshiPhi `_; a package designed to -discretise the world from heterogeneous data sources. PolarRoute requires a pre-existing installation of Python 3.8 or higher. - -Installing PolarRoute -##################### - -PolarRoute is available from PyPI and can be installed by running: -:: - - pip install polar-route - -For development purposes you can install PolarRoute by downloading the source code from GitHub: -:: - - git clone https://github.com/Antarctica/PolarRoute - pip install -e ./PolarRoute - -Use of :code:`-e` is optional, depending on whether you want to be able to edit the installed copy of the package. - -In order to run the test suite you will also need to include the `[test]` flag to install the optional test dependencies: -:: - - pip install -e ./PolarRoute[test] - -Installing GeoPlot -##################### - -Plotting functionality for the outputs of PolarRoute is provided by the `GeoPlot `_ package, also developed at BAS. - -Geoplot is available from PyPI and can be installed by running: -:: - - pip install bas-geoplot - - -Installing GDAL (Optional) -########################## - -The PolarRoute software has GDAL as an optional requirement. It is only used when exporting TIFF images, -so if this is not useful to you, you can skip this step. It is not always trivial and is a common source of problems. -With that said, below are instructions for various operating systems. - -Windows -******* - -.. note:: - We assume a version of Windows 10 or higher, with a working version of Python 3.9 including pip installed. - We recommend installing PolarRoute into a virtual environment. - -Windows: - -:: - - pip install pipwin # pipwin is a package that allows for easy installation of windows binaries - pipwin install gdal - pipwin install fiona - - -Linux/MacOS -*********** - -Ubuntu/Debian: - -:: - - sudo add-apt-repository ppa:ubuntugis/ppa - sudo apt update - sudo apt install gdal-bin libgdal-dev - export CPLUS_INCLUDE_PATH=/usr/include/gdal - export C_INCLUDE_PATH=/usr/include/gdal - pip install GDAL==$(gdal-config --version) - - -Fedora: - -:: - - sudo dnf update - sudo dnf install gdal gdal-devel - export CPLUS_INCLUDE_PATH=/usr/include/gdal - export C_INCLUDE_PATH=/usr/include/gdal - pip install GDAL==$(gdal-config --version) - - -MacOS (with HomeBrew): - -:: - - brew install gdal --HEAD - brew install gdal - pip install GDAL==$(gdal-config --version) diff --git a/docs/html/_sources/sections/Outputs.rst.txt b/docs/html/_sources/sections/Outputs.rst.txt deleted file mode 100644 index 821d6ac7..00000000 --- a/docs/html/_sources/sections/Outputs.rst.txt +++ /dev/null @@ -1,171 +0,0 @@ -.. _outputs: - -******************** -Outputs - Data Types -******************** - -######################### -The Vessel_mesh.json file -######################### - -The vessel performance mesh is an extension to an environmental mesh created by the -`MeshiPhi `_ library. -Once a discrete mesh environment is created, it is then passed to the vessel performance modeller -which applies transformations which are specific to a given vehicle. These vehicle specific values -are then encoded into the mesh json object and passed downstream to the route planner. - -:: - - import json - from polar_route.vessel_performance.vessel_performance_modeller import VesselPerformanceModeller - - with open('vessel_config.json', 'r') as f: - vessel_config = json.load(f) - - vpm = VesselPerformanceModeller(mesh_json, vessel_config) - - vpm.model_accessibility() - vpm.model_performance() - - vessel_mesh_json = vpm.to_json() - -.. note:: - To make use of the full range of vessel performance transformations, a Mesh should be constructed with - the following attributes: - - * elevation (available via dataloaders: *gebco*, *bsose_depth*) - * SIC (available via dataloaders: *amsr*, *bsose_sic*, *baltic_sic*, *icenet*, *modis*, *visual_iced*) - * thickness (available via dataloaders: *thickness*) - * density (available via dataloaders: *density*) - * u10, v10 (available via dataloaders: *era5_wind*) - - see section **Dataloader Overview** in the `MeshiPhi docs `_ for more information on dataloaders - - The vessel performance modeller will still run without these attributes but will assign default values from the - configuration file where any data is missing. - - -As an example, after running the vessel performance modeller with the SDA class and all relevant data each cellbox will -have a set of new attributes as follows: - -* **speed** *(list)* : The speed of the vessel in that cell when travelling to each of its neighbours. -* **fuel** *(list)* : The rate of fuel consumption in that cell when travelling to each of its neighbours. -* **inaccessible** *(boolean)* : Whether the cell is considered inaccessible to the vessel for any reason. -* **land** *(boolean)* : Whether the cell is shallow enough to be considered land by the vessel. -* **ext_ice** *(boolean)* : Whether the cell has enough ice to be inaccessible to the vessel. -* **resistance** *(list)* : The total resistance force the vessel will encounter in that cell when travelling to each of its neighbours. -* **ice resistance** *(float)* : The resistance force due to ice. -* **wind resistance** *(list)* : The resistance force due to wind. -* **relative wind speed** *(list)* : The apparent wind speed acting on the vessel. -* **relative wind angle** *(list)* : The angle of the apparent wind acting on the vessel. - - -################### -The Route.json file -################### - -During the route planning stage of the pipline information on the routes and the waypoints used are saved -as outputs to the processing stage. Descriptions of the structure of the two outputs are given below: - -========= -waypoints -========= - -An entry in the json including all the information about the waypoints defined by the user in the `waypoints.csv` -file. It may be the case that not all waypoints would have been used in the route construction, but all waypoints -that are defined can be found in this entry. The structure of the entry is as follows: - -:: - - {\n - "Name":{\n - '0':"Falklands",\n - '1':"Rothera",\n - ...\n - },\n - "Lat":{\n - '0':-52.6347222222, - '1':-75.26722,\n - ...\n - },\n - "Long":{\n - ...\n - },\n - "index":{\n - ...\n - }\n - } - -where each of the values represent the following: - -* **** : The waypoint name defined by the user - * **0** : The name of waypoint for index row '0' - * **1** : The name of waypoint for index row '1' etc -* **** : The latitude of the waypoints in WGS84 - * **0** : The latitude of waypoint for index row '0' - * **1** : The latitude of waypoint for index row '1' etc -* **** : The longitude of the waypoints in WGS84 - * **0** : The longitude of waypoint for index row '0' - * **1** : The longitude of waypoint for index row '1' etc -* **** : The index of the cellbox containing the waypoint - * **0** : The cellbox index of waypoint for index row '0' - * **1** : The cellbox index of waypoint for index row '1' etc -* **<...>** : Any additional column names defined in the original .csv that was loaded - -This output can be converted to a pandas dataframe by running:: -waypoints_dataframe = pd.DataFrame(waypoints) - - -===== -paths -===== -An entry in the json, in geojson format, including all the routes constructed between the user defined waypoints. The structure of this entry is as follows: - -:: - - {\n - 'types':'FeatureCollection',\n - "features":{[\n - 'type':'feature',\n - 'geometry':{\n - 'type': 'LineString', - - 'coordinates': [[-27.21694, -75.26722],\n - [-27.5, -75.07960297382266],\n - [-27.619238882768894, -75.0],\n - ...]\n - }, - 'properties':{\n - 'from': 'Halley',\n - 'to': 'Rothera',\n - 'traveltime': [0.0,\n - 0.03531938671648596,\n - 0.050310986633880575,\n - ...],\n - 'fuel': [0.0,\n - 0.9648858923588642,\n - 1.3745886107069096,\n - ...],\n - 'times': ['2017-01-01 00:00:00', - '2017-01-01 00:50:51.595036800', - '2017-01-01 01:12:26.869276800', - ...]\n - }\n - ]}\n - }\n - - -where the output takes a GeoJSON standard form (more info at https://geojson.org) given by: - - -* **** : A list of the features representing each of the separate routes constructed - * **geometry** : The positioning of the route locations - * **coordinates** : A list of the Lat,Long position of all the route points - * **** : A list of meta-information about the route - * **from** : Start waypoint of route - * **to** : End waypoint of route - * **traveltime** : A list of float values representing the cumulative travel time along the route. This entry was originally defined as an output in the configuration file by the `path_variables` definition. - * **fuel** : A list of float values representing the cumulative fuel along the route. This entry was originally defined as an output in the configuration file by the `path_variables` definition. - * **times** : A list of strings representing UTC Datetimes of the route points, given that the route started from `start_time` given in the configuration file. - - diff --git a/docs/html/_sources/sections/Route_calculation.rst.txt b/docs/html/_sources/sections/Route_calculation.rst.txt deleted file mode 100644 index 9715af55..00000000 --- a/docs/html/_sources/sections/Route_calculation.rst.txt +++ /dev/null @@ -1,17 +0,0 @@ -*************************** -Methods - Route Calculation -*************************** - -Route Calculation Overview -########################### - -This section describes the code used for the evaluation of user-defined routes through a mesh constructed by the methods -described in previous sections. This mesh should include all the relevant vessel performance parameters needed to -calculate the fuel and time cost of the route. - - -Route Calculation -################# - -.. automodule:: polar_route.route_calc - :members: diff --git a/docs/html/_sources/sections/Route_optimisation.rst.txt b/docs/html/_sources/sections/Route_optimisation.rst.txt deleted file mode 100644 index 5f9a8853..00000000 --- a/docs/html/_sources/sections/Route_optimisation.rst.txt +++ /dev/null @@ -1,36 +0,0 @@ -*********************** -Methods - Route Planner -*********************** - -Route Optimisation Overview -########################### - -In this section we outline the code used to generate optimal routes through a mesh constructed by the methods described -in previous sections. This mesh should include the vessel performance parameters with respect to which objective -functions can be defined for optimisation. - -Route Optimisation Modules -########################## - -Route Planner -************* - -.. automodule:: polar_route.route_planner.route_planner - :members: - :private-members: - - -Crossing Points -*************** - -.. automodule:: polar_route.route_planner.crossing - :members: - :private-members: - -Crossing Point Smoothing -************************ - -.. automodule:: polar_route.route_planner.crossing_smoothing - :members: - :private-members: - diff --git a/docs/html/_sources/sections/Vehicle_specifics.rst.txt b/docs/html/_sources/sections/Vehicle_specifics.rst.txt deleted file mode 100644 index 797c558c..00000000 --- a/docs/html/_sources/sections/Vehicle_specifics.rst.txt +++ /dev/null @@ -1,96 +0,0 @@ -.. _vessel-performance: - -**************************** -Methods - Vessel Performance -**************************** - -Vessel Overview -############### - -All of the functionality that relates to the specific vehicle traversing our meshed environment model is contained within the vessel_performance directory. -This directory contains a `VesselPerformanceModeller` class that initialises one of the vessel classes in `vessels` and uses this to determine which cells -in a given mesh are inaccessible for that particular vessel and what its performance will be in each of the accessible cells. - -.. figure:: ./Figures/Mesh_Fuel_Speed.jpg - :align: center - :width: 700 - - Maps of the sea ice concentration (a), speed (b) and fuel consumption (c) for the SDA across the Weddell Sea. - The latter two quantities are derived from the former. - -.. figure:: ./Figures/VesselUML.png - :align: center - :width: 700 - - *UML Diagram detailing the vessel performance subsystem* - - - -Vessel Performance Modeller -########################### - -.. automodule:: polar_route.vessel_performance.vessel_performance_modeller - -.. autoclass:: polar_route.vessel_performance.vessel_performance_modeller.VesselPerformanceModeller - :special-members: __init__ - :members: model_performance, model_accessibility, to_json - - -Vessel Factory -############## - -.. automodule:: polar_route.vessel_performance.vessel_factory - -.. autoclass:: polar_route.vessel_performance.vessel_factory.VesselFactory - :members: get_vessel - - -Abstract Vessel -############### - -.. automodule:: polar_route.vessel_performance.abstract_vessel - -.. autoclass:: polar_route.vessel_performance.abstract_vessel.AbstractVessel - :special-members: __init__ - :members: model_performance, model_accessibility - - -Abstract Ship -############# - -.. automodule:: polar_route.vessel_performance.vessels.abstract_ship - -.. autoclass:: polar_route.vessel_performance.vessels.abstract_ship.AbstractShip - :special-members: __init__ - :members: model_performance, model_accessibility, land, extreme_ice - - -SDA -### - -.. automodule:: polar_route.vessel_performance.vessels.SDA - -.. autoclass:: polar_route.vessel_performance.vessels.SDA.SDA - :special-members: __init__ - :members: model_speed, model_fuel, model_resistance, invert_resistance - - -Abstract Glider -############### - -.. automodule:: polar_route.vessel_performance.vessels.abstract_glider - -.. autoclass:: polar_route.vessel_performance.vessels.abstract_glider.AbstractGlider - :special-members: __init__ - :members: model_performance, model_accessibility, land, shallow, extreme_ice - - -Slocum Glider -############# - -.. automodule:: polar_route.vessel_performance.vessels.slocum - -.. autoclass:: polar_route.vessel_performance.vessels.slocum.SlocumGlider - :special-members: __init__ - :members: model_speed, model_battery - diff --git a/docs/html/_static/_sphinx_javascript_frameworks_compat.js b/docs/html/_static/_sphinx_javascript_frameworks_compat.js deleted file mode 100644 index 81415803..00000000 --- a/docs/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/html/_static/basic.css b/docs/html/_static/basic.css deleted file mode 100644 index cfc60b86..00000000 --- a/docs/html/_static/basic.css +++ /dev/null @@ -1,921 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- 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: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -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; -} - -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/html/_static/css/badge_only.css b/docs/html/_static/css/badge_only.css deleted file mode 100644 index c718cee4..00000000 --- a/docs/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-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}} \ No newline at end of file diff --git a/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff deleted file mode 100644 index 6cb60000..00000000 Binary files a/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff2 deleted file mode 100644 index 7059e231..00000000 Binary files a/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff deleted file mode 100644 index f815f63f..00000000 Binary files a/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff2 deleted file mode 100644 index f2c76e5b..00000000 Binary files a/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/fontawesome-webfont.eot b/docs/html/_static/css/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca9..00000000 Binary files a/docs/html/_static/css/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/docs/html/_static/css/fonts/fontawesome-webfont.svg b/docs/html/_static/css/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845e..00000000 --- a/docs/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/html/_static/css/fonts/fontawesome-webfont.ttf b/docs/html/_static/css/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2f..00000000 Binary files a/docs/html/_static/css/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/docs/html/_static/css/fonts/fontawesome-webfont.woff b/docs/html/_static/css/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a4..00000000 Binary files a/docs/html/_static/css/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/fontawesome-webfont.woff2 b/docs/html/_static/css/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc60..00000000 Binary files a/docs/html/_static/css/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-bold-italic.woff b/docs/html/_static/css/fonts/lato-bold-italic.woff deleted file mode 100644 index 88ad05b9..00000000 Binary files a/docs/html/_static/css/fonts/lato-bold-italic.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-bold-italic.woff2 b/docs/html/_static/css/fonts/lato-bold-italic.woff2 deleted file mode 100644 index c4e3d804..00000000 Binary files a/docs/html/_static/css/fonts/lato-bold-italic.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-bold.woff b/docs/html/_static/css/fonts/lato-bold.woff deleted file mode 100644 index c6dff51f..00000000 Binary files a/docs/html/_static/css/fonts/lato-bold.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-bold.woff2 b/docs/html/_static/css/fonts/lato-bold.woff2 deleted file mode 100644 index bb195043..00000000 Binary files a/docs/html/_static/css/fonts/lato-bold.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-normal-italic.woff b/docs/html/_static/css/fonts/lato-normal-italic.woff deleted file mode 100644 index 76114bc0..00000000 Binary files a/docs/html/_static/css/fonts/lato-normal-italic.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-normal-italic.woff2 b/docs/html/_static/css/fonts/lato-normal-italic.woff2 deleted file mode 100644 index 3404f37e..00000000 Binary files a/docs/html/_static/css/fonts/lato-normal-italic.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-normal.woff b/docs/html/_static/css/fonts/lato-normal.woff deleted file mode 100644 index ae1307ff..00000000 Binary files a/docs/html/_static/css/fonts/lato-normal.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-normal.woff2 b/docs/html/_static/css/fonts/lato-normal.woff2 deleted file mode 100644 index 3bf98433..00000000 Binary files a/docs/html/_static/css/fonts/lato-normal.woff2 and /dev/null differ diff --git a/docs/html/_static/css/theme.css b/docs/html/_static/css/theme.css deleted file mode 100644 index 19a446a0..00000000 --- a/docs/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>a:hover{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 img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.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-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}}.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/html/_static/doctools.js b/docs/html/_static/doctools.js deleted file mode 100644 index d06a71d7..00000000 --- a/docs/html/_static/doctools.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ -"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/html/_static/documentation_options.js b/docs/html/_static/documentation_options.js deleted file mode 100644 index 995f333f..00000000 --- a/docs/html/_static/documentation_options.js +++ /dev/null @@ -1,14 +0,0 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '1.0.0', - 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/html/_static/file.png b/docs/html/_static/file.png deleted file mode 100644 index a858a410..00000000 Binary files a/docs/html/_static/file.png and /dev/null differ diff --git a/docs/html/_static/jquery.js b/docs/html/_static/jquery.js deleted file mode 100644 index c4c6022f..00000000 --- a/docs/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/html/_static/js/html5shiv.min.js b/docs/html/_static/js/html5shiv.min.js deleted file mode 100644 index cd1c674f..00000000 --- a/docs/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/html/_static/js/theme.js b/docs/html/_static/js/theme.js deleted file mode 100644 index 1fddb6ee..00000000 --- a/docs/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;t0 - 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/html/_static/minus.png b/docs/html/_static/minus.png deleted file mode 100644 index d96755fd..00000000 Binary files a/docs/html/_static/minus.png and /dev/null differ diff --git a/docs/html/_static/plus.png b/docs/html/_static/plus.png deleted file mode 100644 index 7107cec9..00000000 Binary files a/docs/html/_static/plus.png and /dev/null differ diff --git a/docs/html/_static/pygments.css b/docs/html/_static/pygments.css deleted file mode 100644 index 08bec689..00000000 --- a/docs/html/_static/pygments.css +++ /dev/null @@ -1,74 +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 .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/html/_static/searchtools.js b/docs/html/_static/searchtools.js deleted file mode 100644 index 97d56a74..00000000 --- a/docs/html/_static/searchtools.js +++ /dev/null @@ -1,566 +0,0 @@ -/* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * - * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ -"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] = 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, - }; -} - -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) => { - const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; - const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; - const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; - const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; - - const [docName, title, anchor, descr, score, _filename] = item; - - let listItem = document.createElement("li"); - 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 = docUrlRoot + dirname; - linkUrl = requestUrl; - } else { - // normal html builders - requestUrl = docUrlRoot + 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 + ")"; - else if (showSearchSummary) - fetch(requestUrl) - .then((responseData) => responseData.text()) - .then((data) => { - if (data) - listItem.appendChild( - Search.makeSearchSummary(data, searchTerms) - ); - }); - 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 = _( - `Search finished, found ${resultCount} page(s) matching the search query.` - ); -}; -const _displayNextItem = ( - results, - resultCount, - searchTerms -) => { - // 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); - setTimeout( - () => _displayNextItem(results, resultCount, searchTerms), - 5 - ); - } - // search finished, update title and status message - else _finishSearch(resultCount); -}; - -/** - * 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) => { - const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); - const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent !== undefined) return docContent.textContent; - console.warn( - "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you 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.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); - }, - - /** - * execute search (requires search index to be loaded) - */ - query: (query) => { - 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; - - // 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]); - - // array of [docname, title, anchor, descr, score, filename] - let results = []; - _removeChildren(document.getElementById("search-progress")); - - const queryLower = query.toLowerCase(); - for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { - for (const [file, id] of foundTitles) { - let score = Math.round(100 * queryLower.length / title.length) - results.push([ - docNames[file], - titles[file] !== title ? `${titles[file]} > ${title}` : title, - id !== null ? "#" + id : "", - null, - score, - filenames[file], - ]); - } - } - } - - // 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] of foundEntries) { - let score = Math.round(100 * queryLower.length / entry.length) - results.push([ - docNames[file], - titles[file], - id ? "#" + id : "", - null, - score, - filenames[file], - ]); - } - } - } - - // lookup as object - objectTerms.forEach((term) => - results.push(...Search.performObjectSearch(term, objectTerms)) - ); - - // lookup as search terms in fulltext - results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); - - // let the scorer override scores with a custom scoring function - if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); - - // now sort the results by score (in opposite order of appearance, since the - // display function below uses pop() to retrieve items) and then - // alphabetically - results.sort((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; - }); - - // 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; - }, []); - - results = results.reverse(); - - // for debugging - //Search.lastresults = results.slice(); // a copy - // console.info("search results:", Search.lastresults); - - // print the results - _displayNextItem(results, results.length, searchTerms); - }, - - /** - * 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]], - ]); - }; - 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); - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord) && !terms[word]) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord) && !titleTerms[word]) - arr.push({ files: titleTerms[word], 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.get(file).indexOf(word) === -1) - fileMap.get(file).push(word); - else fileMap.set(file, [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], - ]); - } - 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) => { - const text = Search.htmlToText(htmlText); - 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/html/_static/sphinx_highlight.js b/docs/html/_static/sphinx_highlight.js deleted file mode 100644 index aae669d7..00000000 --- a/docs/html/_static/sphinx_highlight.js +++ /dev/null @@ -1,144 +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))); - parent.insertBefore( - span, - parent.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling - ) - ); - node.nodeValue = val.substr(0, pos); - - 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(SphinxHighlight.highlightSearchWords); -_ready(SphinxHighlight.initEscapeListener); diff --git a/docs/html/genindex.html b/docs/html/genindex.html deleted file mode 100644 index 9a492923..00000000 --- a/docs/html/genindex.html +++ /dev/null @@ -1,528 +0,0 @@ - - - - - - Index — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - -

Index

- -
- _ - | A - | C - | D - | E - | F - | G - | I - | L - | M - | O - | P - | R - | S - | T - | U - | V - -
-

_

- - - -
- -

A

- - - -
- -

C

- - - -
- -

D

- - -
- -

E

- - - -
- -

F

- - - -
- -

G

- - -
- -

I

- - - -
- -

L

- - - -
- -

M

- - - -
- -

O

- - - -
- -

P

- - - -
    -
  • - polar_route.vessel_performance.vessel_factory - -
  • -
  • - polar_route.vessel_performance.vessel_performance_modeller - -
  • -
  • - polar_route.vessel_performance.vessels.abstract_glider - -
  • -
  • - polar_route.vessel_performance.vessels.abstract_ship - -
  • -
  • - polar_route.vessel_performance.vessels.SDA - -
  • -
  • - polar_route.vessel_performance.vessels.slocum - -
  • -
- -

R

- - - -
- -

S

- - - -
- -

T

- - - -
- -

U

- - - -
- -

V

- - - -
- - - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/index.html b/docs/html/index.html deleted file mode 100644 index 40b92b40..00000000 --- a/docs/html/index.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - Welcome to the PolarRoute Manual Pages — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Welcome to the PolarRoute Manual Pages

-

PolarRoute is a tool for the optimisation of routes for maritime vehicles travelling in polar waters. -It depends on MeshiPhi; a package designed to -discretise the world from heterogeneous data sources (follow link for more details and docs). -This software package has been developed by the British Antarctic Survey (BAS). It was originally designed primarily for the -optimisation of polar routes for the BAS research vessel RRS Sir David Attenborough, however it is applicable -to any vessel (e.g. AUVs). The software is written in Python and is open source.

-

Plotting functionality for the outputs of PolarRoute is provided by the GeoPlot package, also developed at BAS.

-

For more information on the project, please visit the PolarRoute website -and follow our GitHub repository.

-
-

Note

-

The development of this codebase is ongoing and not yet complete. -Please contact the developers for more information.

-
-

Contents:

- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/objects.inv b/docs/html/objects.inv deleted file mode 100644 index 2d1f7192..00000000 Binary files a/docs/html/objects.inv and /dev/null differ diff --git a/docs/html/py-modindex.html b/docs/html/py-modindex.html deleted file mode 100644 index b2743040..00000000 --- a/docs/html/py-modindex.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - Python Module Index — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/html/search.html b/docs/html/search.html deleted file mode 100644 index 0f0878e7..00000000 --- a/docs/html/search.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - Search — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - - - -
- -
- -
-
- -
-
-
-
- - - - - - - - - \ No newline at end of file diff --git a/docs/html/searchindex.js b/docs/html/searchindex.js deleted file mode 100644 index d424ace5..00000000 --- a/docs/html/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({"docnames": ["index", "sections/Code_overview", "sections/Command_line_interface", "sections/Configuration/Configuration_overview", "sections/Configuration/Route_planning_config", "sections/Configuration/Vessel_performance_config", "sections/Examples", "sections/Installation", "sections/Outputs", "sections/Route_calculation", "sections/Route_optimisation", "sections/Vehicle_specifics"], "filenames": ["index.rst", "sections/Code_overview.rst", "sections/Command_line_interface.rst", "sections/Configuration/Configuration_overview.rst", "sections/Configuration/Route_planning_config.rst", "sections/Configuration/Vessel_performance_config.rst", "sections/Examples.rst", "sections/Installation.rst", "sections/Outputs.rst", "sections/Route_calculation.rst", "sections/Route_optimisation.rst", "sections/Vehicle_specifics.rst"], "titles": ["Welcome to the PolarRoute Manual Pages", "7. Background", "4. Command Line Interface", "5. Configuration Overview", "5.2. Configuration - Route Planning", "5.1. Configuration - Vessel Performance Modeller", "2. Command Line Interface Examples", "1. Installation", "6. Outputs - Data Types", "9. Methods - Route Calculation", "10. Methods - Route Planner", "8. Methods - Vessel Performance"], "terms": {"i": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11], "tool": [0, 1], "optimis": [0, 1, 2], "rout": [0, 1, 2, 3, 5, 7], "maritim": 0, "vehicl": [0, 1, 2, 6, 8, 9, 10, 11], "travel": [0, 1, 2, 5, 6, 8, 9, 10], "polar": [0, 1, 7], "water": [0, 1, 5, 11], "It": [0, 2, 7, 8], "depend": [0, 4, 6, 7], "meshiphi": [0, 1, 2, 3, 6, 7, 8, 10], "packag": [0, 1, 2, 3, 6, 7, 10], "design": [0, 7, 11], "discretis": [0, 7], "world": [0, 6, 7], "from": [0, 1, 2, 6, 7, 8, 9, 10, 11], "heterogen": [0, 7], "data": [0, 1, 2, 4, 7], "sourc": [0, 2, 6, 7, 10], "follow": [0, 1, 2, 4, 5, 6, 8, 11], "link": 0, "more": [0, 6, 8, 10], "detail": [0, 2, 6, 11], "doc": [0, 2, 6, 8], "thi": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], "softwar": [0, 1, 2, 3, 7], "ha": [0, 2, 6, 7, 8], "been": [0, 2, 6, 8], "develop": [0, 1, 4, 6, 7], "british": [0, 11], "antarct": [0, 11], "survei": [0, 11], "ba": [0, 6, 7], "wa": [0, 8], "origin": [0, 8], "primarili": 0, "research": [0, 11], "vessel": [0, 1, 2, 3, 4, 8, 9, 10], "rr": [0, 11], "sir": [0, 11], "david": [0, 11], "attenborough": [0, 11], "howev": [0, 6], "applic": [0, 1], "ani": [0, 2, 5, 6, 7, 8, 10, 11], "e": [0, 1, 5, 6, 7, 10], "g": [0, 1, 5, 6, 10], "auv": 0, "The": [0, 1, 2, 3, 4, 5, 6, 7, 10, 11], "written": [0, 3], "python": [0, 3, 7, 10], "open": [0, 1, 5, 6, 8], "plot": [0, 1, 6, 7], "function": [0, 1, 4, 6, 7, 9, 10, 11], "output": [0, 1, 2, 4, 7, 10], "provid": [0, 1, 2, 3, 5, 6, 7, 10], "geoplot": [0, 2, 6], "also": [0, 1, 2, 5, 6, 7], "For": [0, 4, 6, 7, 10], "inform": [0, 1, 2, 5, 6, 8, 9, 10], "project": [0, 6], "pleas": [0, 6], "visit": 0, "websit": 0, "our": [0, 1, 11], "github": [0, 7], "repositori": [0, 7], "codebas": [0, 1], "ongo": 0, "yet": [0, 6], "complet": 0, "contact": 0, "content": 0, "instal": [0, 6], "gdal": 0, "option": [0, 2, 3, 6, 10], "command": [0, 3, 5], "line": [0, 3, 5, 9, 10], "interfac": [0, 11], "exampl": [0, 1, 2, 3, 8], "empti": 0, "mesh": [0, 1, 2, 3, 4, 8, 9, 10, 11], "synthet": 0, "real": 0, "creat": [0, 2, 8, 10], "digit": [0, 2], "environ": [0, 2, 7, 8, 11], "simul": [0, 2], "visualis": [0, 2], "create_mesh": [0, 6], "add_vehicl": [0, 5, 6], "resimulate_vehicl": 0, "optimise_rout": [0, 6], "calculate_rout": 0, "extract_rout": 0, "configur": [0, 1, 2, 6, 8, 11], "overview": [0, 2, 8], "perform": [0, 1, 2, 3, 6, 8, 9, 10], "model": [0, 2, 3, 8], "plan": [0, 1, 2, 3, 6, 8], "type": [0, 1, 2, 4, 6, 9, 10, 11], "vessel_mesh": [0, 2, 6], "json": [0, 2, 3, 6, 9, 10, 11], "file": [0, 1, 2, 3, 5, 6, 9, 10, 11], "background": 0, "code": [0, 6, 7, 9, 10], "structur": [0, 3, 5, 6, 8], "method": [0, 1, 6], "factori": 0, "abstract": 0, "ship": [0, 1, 4, 5, 6], "sda": [0, 5, 8], "glider": 0, "slocum": 0, "calcul": [0, 2, 5, 6, 10, 11], "planner": [0, 1, 2, 4, 6, 8], "modul": 0, "polarrout": [1, 2, 3, 6, 10], "an": [1, 2, 3, 6, 7, 8, 10, 11], "autom": 1, "us": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], "ic": [1, 4, 5, 8, 11], "strengthen": 1, "oper": [1, 7], "region": [1, 4, 6], "long": [1, 2, 6, 8, 10], "distanc": [1, 6, 9, 10], "approach": 1, "build": [1, 2], "underwat": [1, 11], "report": 1, "fox": 1, "et": 1, "al": 1, "2021": 1, "we": [1, 3, 6, 7, 10], "start": [1, 6, 8, 9, 10], "same": [1, 6, 10], "grid": 1, "base": [1, 10, 11], "construct": [1, 2, 3, 4, 6, 8, 9, 10], "obtain": 1, "satisfi": 1, "constraint": 1, "appli": [1, 8, 10], "novel": 1, "smooth": [1, 2, 4, 6], "result": 1, "shorten": 1, "ensur": 1, "great": [1, 6], "circl": [1, 6], "arc": [1, 6], "where": [1, 4, 5, 8, 10], "possibl": 1, "two": [1, 4, 6, 8, 9, 10, 11], "stage": [1, 2, 3, 6, 8, 10], "process": [1, 2, 3, 4, 6, 8, 10], "effici": 1, "gener": [1, 2, 3, 6, 10], "standard": [1, 3, 8, 10], "navig": [1, 2], "solut": [1, 6], "around": [1, 4, 6, 10], "area": [1, 6], "domin": 1, "sea": [1, 4, 5, 11], "while": 1, "have": [1, 6, 8], "focuss": 1, "ar": [1, 2, 3, 4, 5, 6, 7, 8, 10, 11], "broader": 1, "context": 1, "commerci": 1, "must": [1, 4, 6], "respons": 1, "chang": 1, "local": 1, "weather": 1, "condit": 1, "aim": 1, "manual": 1, "user": [1, 2, 6, 8, 9, 10], "all": [1, 2, 3, 4, 5, 8, 9, 10, 11], "thei": [1, 2], "need": [1, 6, 7, 9], "run": [1, 2, 4, 6, 7, 8, 10], "set": [1, 2, 4, 5, 8], "hope": 1, "suppli": [1, 11], "each": [1, 3, 4, 6, 8, 10, 11], "section": [1, 2, 3, 6, 7, 8, 9, 10], "allow": [1, 2, 4, 5, 7, 10], "understand": 1, "throughout": 1, "can": [1, 2, 3, 4, 5, 6, 7, 8, 10, 11], "separ": [1, 3, 8], "four": 1, "main": 1, "shown": [1, 2], "figur": [1, 2], "below": [1, 6, 7, 8, 10, 11], "outlin": [1, 3, 7, 10], "initi": [1, 10], "environment": [1, 3, 6, 8, 10, 11], "broken": 1, "down": 1, "specif": [1, 2, 5, 6, 8, 11], "featur": [1, 5, 6, 8, 10], "discret": [1, 8], "In": [1, 2, 3, 6, 7, 10], "knowledg": 1, "how": [1, 2, 6, 10], "variabl": [1, 4, 5, 9, 10], "comput": [1, 10], "graph": [1, 10, 11], "dijkstra": [1, 2, 6, 10], "path": [1, 2, 4, 6, 9, 10], "constrain": 1, "give": [1, 2, 6, 9], "between": [1, 2, 4, 6, 8, 9, 10], "defin": [1, 2, 4, 6, 8, 9, 10, 11], "waypoint": [1, 2, 4, 6, 9, 10], "minimis": [1, 4], "object": [1, 4, 6, 8, 10, 11], "time": [1, 2, 4, 8, 9, 10], "fuel": [1, 2, 4, 5, 6, 8, 9, 10, 11], "onc": [1, 2, 6, 8], "formul": 1, "procedur": 1, "non": [1, 6], "improv": [1, 5], "input": [1, 4, 6, 10, 11], "pipelin": 1, "make": [1, 8], "found": [1, 2, 3, 8], "document": [1, 2, 3, 6], "produc": [1, 2, 11], "form": [1, 6, 8, 10], "which": [1, 2, 6, 8, 10, 11], "addit": [1, 2, 4, 6, 8], "seri": [1, 10], "class": [1, 5, 8, 10, 11], "interact": [1, 6], "map": [1, 6, 11], "static": 1, "These": [1, 6, 8], "later": 1, "multipl": [2, 6], "cli": [2, 6], "entri": [2, 5, 6, 8, 10], "point": [2, 5, 6, 8, 9], "intend": 2, "success": 2, "through": [2, 3, 5, 6, 9, 10], "collect": 2, "config": [2, 3, 4, 6, 7, 10, 11], "posit": [2, 8], "argument": [2, 3, 5], "A": [2, 4, 5, 6, 8, 10], "parsabl": 2, "format": [2, 3, 6, 8, 9, 10, 11], "requir": [2, 6, 7, 10, 11], "There": 2, "avail": [2, 6, 7, 8], "directori": [2, 3, 11], "environment_config": 2, "grf_exampl": [2, 6], "v": [2, 10], "verbos": 2, "log": 2, "o": [2, 6], "locat": [2, 4, 6, 8, 10], "encod": [2, 6, 8], "vessel_config": [2, 8, 11], "return": [2, 4, 9, 10, 11], "explain": 2, "again": [2, 6], "exist": [2, 7], "new": [2, 8, 10], "easili": 2, "pre": [2, 7], "paramet": [2, 4, 5, 6, 9, 10, 11], "ad": [2, 6], "optim": [2, 6, 10], "route_config": 2, "csv": [2, 6, 8, 9, 10], "when": [2, 6, 7, 8, 10], "contain": [2, 4, 6, 8, 9, 10, 11], "abov": [2, 4, 5, 6], "As": [2, 8], "tabl": 2, "name": [2, 4, 5, 8, 9], "lat": [2, 8, 10], "destin": [2, 10], "hallei": [2, 8], "75": [2, 8], "267": 2, "27": [2, 8], "216": 2, "x": [2, 9], "rothera": [2, 8], "67": 2, "617": 2, "68": 2, "047": 2, "south": 2, "georgia": 2, "54": 2, "141": 2, "36": 2, "094": 2, "falkland": [2, 8], "51": [2, 8], "953": 2, "57": 2, "969": 2, "eleph": 2, "island": 2, "60": 2, "977": 2, "55": 2, "078": 2, "mai": [2, 6, 8], "extend": 2, "determin": [2, 6, 9, 10, 11], "either": [2, 4, 5, 6], "column": [2, 8], "mark": [2, 5], "p": 2, "onli": [2, 4, 5, 7], "entir": [2, 6], "d": 2, "well": 2, "cost": [2, 9, 10], "geojson": [2, 4, 8, 9, 10], "assum": [2, 7], "order": [2, 6, 7, 9], "given": [2, 4, 5, 8, 9, 10, 11], "rhumb": [2, 10], "ident": 2, "out": [2, 6], "If": [2, 4, 6, 10], "cross": [2, 9], "cell": [2, 4, 5, 6, 8, 9, 10, 11], "consid": [2, 8], "inaccess": [2, 4, 5, 6, 8, 11], "warn": 2, "displai": [2, 6], "save": [2, 6, 8], "individu": 2, "extract": [2, 6], "larger": 2, "automat": 2, "filenam": 2, "support": [2, 4, 5, 6], "kml": 2, "gpx": [2, 9], "route_fil": [2, 9], "plot_mesh": [2, 6], "librari": [2, 8], "other": [2, 11], "gi": 2, "qgi": 2, "export": [2, 7], "commonli": 2, "tif": 2, "export_mesh": 2, "describ": [2, 6, 9, 10], "add": [2, 6, 7], "direct": [2, 9, 10], "arrow": 2, "r": [2, 6, 8], "portion": 3, "pass": [3, 6, 8], "script": 3, "descript": [3, 8], "objective_funct": [4, 10], "traveltim": [4, 6, 8, 9, 10], "path_vari": [4, 8], "vector_nam": 4, "uc": [4, 9], "vc": [4, 9], "minim": 4, "necessari": [4, 5, 7], "string": [4, 5, 8, 10], "batteri": [4, 11], "list": [4, 5, 8, 9, 10], "definit": [4, 8], "horizont": 4, "vertic": 4, "compon": [4, 9, 10], "vector": [4, 6, 9, 10], "act": [4, 8, 11], "within": [4, 6, 9, 10, 11], "time_unit": 4, "dai": [4, 10, 11], "adjust_waypoint": 4, "true": [4, 5, 10, 11], "zero_curr": 4, "fals": [4, 6, 10], "fixed_spe": 4, "waypoint_split": 4, "smoothing_max_iter": 4, "2000": 4, "smoothing_blocked_s": 4, "10": [4, 5, 7], "0": [4, 5, 8, 9], "smoothing_merge_separ": 4, "1e": 4, "3": [4, 5, 7, 11], "smoothing_converged_separ": 4, "full": [4, 8, 10], "unit": [4, 5], "current": [4, 5, 6, 10], "take": [4, 8, 11], "hr": [4, 5, 10], "futur": 4, "releas": 4, "bool": [4, 5, 10, 11], "enabl": [4, 5], "disabl": [4, 5, 6], "move": [4, 10], "place": 4, "nearest": 4, "access": [4, 5, 6, 10, 11], "remov": [4, 10, 11], "effect": 4, "zero": [4, 10], "speed": [4, 5, 6, 8, 9, 10, 11], "max": [4, 10, 11], "split": [4, 5, 6, 10], "maximum": [4, 5, 11], "depth": [4, 5, 6, 11], "int": [4, 9, 10], "number": 4, "iter": 4, "most": 4, "converg": 4, "met": 4, "100x": 4, "earlier": 4, "than": 4, "valu": [4, 5, 8, 9, 10, 11], "float": [4, 5, 8, 9, 10, 11], "differ": [4, 5], "concentr": [4, 5, 11], "befor": 4, "block": 4, "smoothing_merge_sep": 4, "minimum": [4, 5, 11], "merg": 4, "trigger": 4, "smoothing_converged_sep": 4, "about": [5, 6, 8, 10], "execut": [5, 6], "consumpt": [5, 6, 8, 11], "vesselperformancemodel": [5, 6, 8, 11], "vessel_typ": 5, "max_spe": 5, "26": [5, 8], "5": [5, 8, 10], "km": [5, 10, 11], "beam": 5, "24": 5, "hull_typ": 5, "slender": 5, "force_limit": 5, "96634": 5, "max_ice_conc": 5, "80": 5, "min_depth": 5, "max_wav": 5, "excluded_zon": 5, "exclusion_zon": 5, "neighbour_split": 5, "typic": [5, 6], "measur": 5, "width": 5, "metr": 5, "hull": 5, "profil": 5, "should": [5, 8, 9, 10, 11], "one": [5, 10, 11], "blunt": 5, "resist": [5, 8, 11], "forc": [5, 8, 10, 11], "specifi": [5, 10], "newton": 5, "abl": [5, 7], "percentag": [5, 11], "signific": 5, "wave": 5, "height": 5, "boolean": [5, 8, 11], "properti": [5, 8], "enter": 5, "kei": [5, 9, 10], "unnavig": 5, "neighbour": [5, 8, 10, 11], "up": [5, 10], "To": [6, 8], "summaris": 6, "basic": 6, "characterist": [6, 10, 11], "find": [6, 9, 10, 11], "At": 6, "mesh_config_fil": 6, "mesh_output_fil": 6, "vessel_config_fil": 6, "vessel_output_fil": 6, "route_config_fil": 6, "waypoints_fil": 6, "route_output_fil": 6, "any_output_fil": 6, "output_fil": 6, "fulfil": 6, "you": [6, 7], "successfulli": 6, "would": [6, 8], "like": 6, "try": 6, "here": 6, "some": 6, "simpli": 6, "zip": 6, "archiv": 6, "appropri": 6, "call": 6, "waypoints_exampl": 6, "By": 6, "default": [6, 8, 10], "basemap": 6, "show": 6, "your": 6, "earth": 6, "work": [6, 7, 11], "grf": 6, "spatial": 6, "coordin": [6, 8, 9], "do": 6, "correspond": 6, "recommend": [6, 7], "b": [6, 11], "sever": 6, "notebook": 6, "guid": 6, "creation": [6, 10], "via": [6, 8], "googl": 6, "colab": 6, "someon": 6, "termin": 6, "simpl": 6, "get": [6, 10], "sinc": 6, "expect": 6, "straight": 6, "over": 6, "seen": 6, "mercat": 6, "uniform": 6, "see": [6, 8, 10], "gaussian": 6, "random": 6, "field": 6, "somewhat": 6, "realist": 6, "represent": [6, 11], "bathymetri": 6, "walk": 6, "everi": 6, "step": [6, 7], "involv": 6, "coast": 6, "antarctica": [6, 7], "publicli": 6, "includ": [6, 7, 8, 9, 10, 11], "avoid": 6, "violat": 6, "share": 6, "polici": 6, "instead": 6, "after": [6, 8, 10], "deriv": [6, 11], "product": 6, "data_sourc": 6, "dataload": [6, 8], "info": [6, 8, 9, 10], "1": [6, 8], "2": 6, "done": 6, "interpret": 6, "case": [6, 8, 9, 10], "against": 6, "first": 6, "those": [6, 10], "download": [6, 7], "them": [6, 10], "initialis": [6, 10, 11], "environmentmesh": [6, 10], "load": [6, 8, 9, 10, 11], "import": [6, 8], "read": 6, "f": [6, 8], "its": [6, 8, 11], "manipul": 6, "further": 6, "increas": 6, "resolut": 6, "alter": 6, "relev": [6, 8, 9, 10, 11], "explan": 6, "cast": 6, "mesh_gener": 6, "mesh_build": 6, "meshbuild": 6, "cg": 6, "build_environmental_mesh": 6, "to_json": [6, 8, 10, 11], "w": 6, "dump": 6, "indent": 6, "4": 6, "snippet": 6, "back": 6, "just": [6, 10], "dictionari": [6, 9, 10, 11], "next": 6, "": [6, 10], "polar_rout": [6, 8, 9, 10, 11], "vessel_perform": [6, 8, 11], "vessel_performance_model": [6, 8, 11], "vp": 6, "vesselperform": 6, "model_access": [6, 8, 11], "land": [6, 8, 11], "model_perform": [6, 8, 11], "routeplann": [6, 10], "consol": 6, "now": 6, "repres": [6, 8], "compute_rout": [6, 10], "reduc": 6, "compute_smooth_rout": 6, "dure": [6, 8], "store": 6, "attribut": [6, 8, 10, 11], "under": 6, "routes_dijkstra": 6, "complement": 6, "routes_smooth": 6, "route_plann": [6, 10], "rp": 6, "compute_smoothed_rout": [6, 10], "route_mesh": 6, "distribut": 6, "pip": [6, 7], "bas_geoplot": 6, "ipython": 6, "pd": [6, 8, 10], "datafram": [6, 8, 9, 10], "mesh_json": [6, 8], "cellbox": [6, 8, 9, 10, 11], "mp": 6, "titl": 6, "meshgrid": 6, "predefin": 6, "cx": 6, "sic": [6, 8, 10, 11], "elev": [6, 8], "wind": [6, 8], "8": 7, "higher": 7, "pypi": 7, "purpos": 7, "git": 7, "clone": 7, "http": [7, 8], "com": 7, "whether": [7, 8], "want": 7, "edit": 7, "copi": 7, "tiff": 7, "imag": 7, "so": 7, "skip": 7, "alwai": 7, "trivial": 7, "common": [7, 11], "problem": 7, "With": 7, "said": 7, "instruct": 7, "variou": 7, "system": 7, "version": 7, "9": 7, "virtual": 7, "pipwin": 7, "easi": 7, "binari": 7, "fiona": 7, "ubuntu": 7, "debian": 7, "sudo": 7, "apt": 7, "ppa": 7, "ubuntugi": 7, "updat": [7, 10, 11], "bin": 7, "libgdal": 7, "dev": 7, "cplus_include_path": 7, "usr": 7, "c_include_path": 7, "fedora": 7, "dnf": 7, "devel": 7, "homebrew": 7, "brew": 7, "head": 7, "extens": 8, "transform": 8, "downstream": 8, "vpm": 8, "vessel_mesh_json": 8, "rang": 8, "gebco": 8, "bsose_depth": 8, "amsr": 8, "bsose_s": 8, "baltic_s": 8, "icenet": 8, "modi": 8, "visual_": 8, "thick": [8, 11], "densiti": [8, 11], "u10": 8, "v10": 8, "era5_wind": 8, "still": 8, "without": 8, "assign": 8, "miss": 8, "rate": [8, 11], "reason": 8, "shallow": [8, 11], "enough": 8, "ext_ic": [8, 11], "total": [8, 10], "encount": 8, "due": [8, 11], "rel": 8, "appar": 8, "angl": 8, "piplin": 8, "n": 8, "52": 8, "6347222222": 8, "26722": 8, "index": [8, 9], "row": 8, "etc": 8, "latitud": [8, 10], "wgs84": 8, "longitud": [8, 10], "convert": 8, "panda": [8, 10], "waypoints_datafram": 8, "featurecollect": 8, "geometri": 8, "linestr": 8, "21694": 8, "07960297382266": 8, "619238882768894": 8, "03531938671648596": 8, "050310986633880575": 8, "9648858923588642": 8, "3745886107069096": 8, "2017": 8, "01": 8, "00": 8, "50": 8, "595036800": 8, "12": 8, "869276800": 8, "org": 8, "meta": 8, "end": [8, 9, 10], "cumul": 8, "along": [8, 9, 10], "utc": 8, "datetim": [8, 10], "start_tim": 8, "evalu": 9, "previou": [9, 10], "route_calc": 9, "case_from_angl": 9, "associ": 9, "ndarrai": 9, "select": 9, "find_intersect": 9, "df": 9, "param": [9, 11], "geodatafram": 9, "id": [9, 10], "track_point": 9, "dict": [9, 10, 11], "load_mesh": 9, "mesh_fil": [9, 10], "str": [9, 10], "load_rout": 9, "from_wp": 9, "to_wp": 9, "order_track": 9, "track": 9, "user_track": 9, "user_path": 9, "traveltime_dist": 9, "wp": [9, 10], "cp": [9, 10], "vector_x": 9, "vector_i": 9, "segment": [9, 10], "y": 9, "arrai": [9, 10], "respect": 10, "config_fil": 10, "cost_func": 10, "newtoniandist": 10, "independ": 10, "env_mesh": 10, "func": 10, "src_wp": 10, "reus": 10, "sourcewaypoint": 10, "_dijkstra": 10, "end_wp": 10, "algorithm": 10, "across": [10, 11], "whole": 10, "domain": 10, "_dijkstra_rout": 10, "start_waypoint": 10, "end_waypoint": 10, "hidden": 10, "intern": 10, "_fixed_spe": 10, "correct": 10, "_neighbour_cost": 10, "node_id": 10, "neighbour_id": 10, "transit": 10, "leg": 10, "neighbour_seg": 10, "_splitting_around_waypoint": 10, "waypoints_df": 10, "inplac": 10, "term": 10, "self": 10, "_validate_wp": 10, "valid": 10, "both": [10, 11], "lie": 10, "bound": 10, "env": 10, "encapsul": 10, "invalid": 10, "_zero_curr": 10, "blocked_metr": 10, "previous": 10, "initialise_dijkstra_graph": 10, "neighbour_graph": 10, "path_index": 10, "pathindex": 10, "compris": 10, "dijkstra_graph_dict": 10, "output_json": 10, "_adjust_waypoint": 10, "max_dist": 10, "closest": 10, "isn": 10, "t": 10, "alreadi": 10, "degre": 10, "flexibl": 10, "_load_waypoint": 10, "dest_wp": 10, "_mesh_boundary_polygon": 10, "polygon": 10, "boundari": 10, "flatten_cas": 10, "cell_id": 10, "identifi": 10, "neighbour_indx": 10, "indic": 10, "neighbour_cas": 10, "initialise_dijkstra_rout": 10, "dijkstra_graph": 10, "dijkstra_rout": 10, "adjac": 10, "pair": 10, "findedg": 10, "find_edg": 10, "ap": 10, "implement": 10, "go": 10, "traveltime_in_cel": 10, "xdist": 10, "ydist": 10, "u": 10, "tt_dist": 10, "none": 10, "otherwis": 10, "dist": 10, "crossing_smooth": 10, "cell_a": 10, "cell_b": 10, "edg": 10, "connect": 10, "tupl": 10, "_find_edg": 10, "pathvalu": 10, "path_var": 10, "intersect": 10, "path_requested_vari": 10, "cumsum": 10, "cell_index": 10, "unit_shipspe": 10, "knot": 10, "unit_tim": 10, "min": 10, "_waypoint_correct": 10, "source_graph": 10, "usag": [10, 11], "request": 10, "segment_valu": 10, "adjacent_pair": 10, "relat": [10, 11], "waypoint_correct": 10, "dist_around_glob": 10, "start_point": 10, "crossing_point": 10, "globe": 10, "rhumb_line_dist": 10, "travers": 11, "particular": 11, "what": 11, "c": 11, "weddel": 11, "latter": 11, "quantiti": 11, "former": 11, "uml": 11, "diagram": 11, "subsystem": 11, "env_mesh_json": 11, "modifi": 11, "__init__": 11, "accordingli": 11, "j_mesh": 11, "vessel_factori": 11, "vesselfactori": 11, "classmethod": 11, "get_vessel": 11, "instanc": 11, "abstract_vessel": 11, "abstractvessel": 11, "aggregatedcellbox": 11, "being": 11, "access_valu": 11, "performance_valu": 11, "abstract_ship": 11, "abstractship": 11, "extreme_ic": 11, "criteria": 11, "invert_resist": 11, "keep": 11, "threshold": 11, "averag": 11, "m": 11, "kg": 11, "safe": 11, "h": 11, "new_spe": 11, "model_fuel": 11, "model_resist": 11, "model_spe": 11, "abstract_glid": 11, "abstractglid": 11, "level": 11, "too": 11, "slocumglid": 11, "g2": 11, "model_batteri": 11, "ah": 11, "test": 7, "suit": 7, "flag": 7}, "objects": {"polar_route": [[9, 0, 0, "-", "route_calc"]], "polar_route.route_calc": [[9, 1, 1, "", "case_from_angle"], [9, 1, 1, "", "find_intersections"], [9, 1, 1, "", "load_mesh"], [9, 1, 1, "", "load_route"], [9, 1, 1, "", "order_track"], [9, 1, 1, "", "route_calc"], [9, 1, 1, "", "traveltime_distance"]], "polar_route.route_planner": [[10, 0, 0, "-", "crossing"], [10, 0, 0, "-", "crossing_smoothing"], [10, 0, 0, "-", "route_planner"]], "polar_route.route_planner.crossing": [[10, 1, 1, "", "traveltime_in_cell"]], "polar_route.route_planner.crossing_smoothing": [[10, 2, 1, "", "FindEdge"], [10, 2, 1, "", "PathValues"], [10, 1, 1, "", "dist_around_globe"], [10, 1, 1, "", "rhumb_line_distance"]], "polar_route.route_planner.crossing_smoothing.FindEdge": [[10, 3, 1, "", "_find_edge"]], "polar_route.route_planner.crossing_smoothing.PathValues": [[10, 3, 1, "", "_waypoint_correction"], [10, 3, 1, "", "objective_function"], [10, 4, 1, "", "path_requested_variables"], [10, 4, 1, "", "unit_shipspeed"], [10, 4, 1, "", "unit_time"]], "polar_route.route_planner.route_planner": [[10, 2, 1, "", "RoutePlanner"], [10, 1, 1, "", "_adjust_waypoints"], [10, 1, 1, "", "_load_waypoints"], [10, 1, 1, "", "_mesh_boundary_polygon"], [10, 1, 1, "", "flatten_cases"], [10, 1, 1, "", "initialise_dijkstra_route"]], "polar_route.route_planner.route_planner.RoutePlanner": [[10, 3, 1, "", "_dijkstra"], [10, 3, 1, "", "_dijkstra_routes"], [10, 3, 1, "", "_fixed_speed"], [10, 3, 1, "", "_neighbour_cost"], [10, 3, 1, "", "_splitting_around_waypoints"], [10, 3, 1, "", "_validate_wps"], [10, 3, 1, "", "_zero_currents"], [10, 3, 1, "", "compute_routes"], [10, 3, 1, "", "compute_smoothed_routes"], [10, 4, 1, "", "config"], [10, 4, 1, "", "cost_func"], [10, 4, 1, "", "env_mesh"], [10, 3, 1, "", "initialise_dijkstra_graph"], [10, 4, 1, "", "src_wps"], [10, 3, 1, "", "to_json"]], "polar_route.vessel_performance": [[11, 0, 0, "-", "abstract_vessel"], [11, 0, 0, "-", "vessel_factory"], [11, 0, 0, "-", "vessel_performance_modeller"]], "polar_route.vessel_performance.abstract_vessel": [[11, 2, 1, "", "AbstractVessel"]], "polar_route.vessel_performance.abstract_vessel.AbstractVessel": [[11, 3, 1, "", "__init__"], [11, 3, 1, "", "model_accessibility"], [11, 3, 1, "", "model_performance"]], "polar_route.vessel_performance.vessel_factory": [[11, 2, 1, "", "VesselFactory"]], "polar_route.vessel_performance.vessel_factory.VesselFactory": [[11, 3, 1, "", "get_vessel"]], "polar_route.vessel_performance.vessel_performance_modeller": [[11, 2, 1, "", "VesselPerformanceModeller"]], "polar_route.vessel_performance.vessel_performance_modeller.VesselPerformanceModeller": [[11, 3, 1, "", "__init__"], [11, 3, 1, "", "model_accessibility"], [11, 3, 1, "", "model_performance"], [11, 3, 1, "", "to_json"]], "polar_route.vessel_performance.vessels": [[11, 0, 0, "-", "SDA"], [11, 0, 0, "-", "abstract_glider"], [11, 0, 0, "-", "abstract_ship"], [11, 0, 0, "-", "slocum"]], "polar_route.vessel_performance.vessels.SDA": [[11, 2, 1, "", "SDA"]], "polar_route.vessel_performance.vessels.SDA.SDA": [[11, 3, 1, "", "__init__"], [11, 3, 1, "", "invert_resistance"], [11, 3, 1, "", "model_fuel"], [11, 3, 1, "", "model_resistance"], [11, 3, 1, "", "model_speed"]], "polar_route.vessel_performance.vessels.abstract_glider": [[11, 2, 1, "", "AbstractGlider"]], "polar_route.vessel_performance.vessels.abstract_glider.AbstractGlider": [[11, 3, 1, "", "__init__"], [11, 3, 1, "", "extreme_ice"], [11, 3, 1, "", "land"], [11, 3, 1, "", "model_accessibility"], [11, 3, 1, "", "model_performance"], [11, 3, 1, "", "shallow"]], "polar_route.vessel_performance.vessels.abstract_ship": [[11, 2, 1, "", "AbstractShip"]], "polar_route.vessel_performance.vessels.abstract_ship.AbstractShip": [[11, 3, 1, "", "__init__"], [11, 3, 1, "", "extreme_ice"], [11, 3, 1, "", "land"], [11, 3, 1, "", "model_accessibility"], [11, 3, 1, "", "model_performance"]], "polar_route.vessel_performance.vessels.slocum": [[11, 2, 1, "", "SlocumGlider"]], "polar_route.vessel_performance.vessels.slocum.SlocumGlider": [[11, 3, 1, "", "__init__"], [11, 3, 1, "", "model_battery"], [11, 3, 1, "", "model_speed"]]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:method", "4": "py:attribute"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "method", "Python method"], "4": ["py", "attribute", "Python attribute"]}, "titleterms": {"welcom": 0, "polarrout": [0, 7], "manual": 0, "page": 0, "background": 1, "code": 1, "overview": [1, 3, 9, 10, 11], "structur": 1, "command": [2, 6], "line": [2, 6], "interfac": [2, 6], "create_mesh": 2, "add_vehicl": 2, "resimulate_vehicl": 2, "optimise_rout": 2, "calculate_rout": 2, "extract_rout": 2, "plot": 2, "configur": [3, 4, 5], "rout": [4, 6, 8, 9, 10], "plan": 4, "vessel": [5, 6, 11], "perform": [5, 11], "model": [5, 11], "exampl": 6, "empti": 6, "mesh": 6, "synthet": 6, "data": [6, 8], "real": 6, "python": 6, "creat": 6, "digit": 6, "environ": 6, "simul": 6, "optimis": [6, 10], "visualis": 6, "output": [6, 8], "instal": 7, "geoplot": 7, "gdal": 7, "option": 7, "window": 7, "linux": 7, "maco": 7, "type": 8, "The": 8, "vessel_mesh": 8, "json": 8, "file": 8, "waypoint": 8, "path": 8, "method": [9, 10, 11], "calcul": 9, "planner": 10, "modul": 10, "cross": 10, "point": 10, "smooth": 10, "factori": 11, "abstract": 11, "ship": 11, "sda": 11, "glider": 11, "slocum": 11}, "envversion": {"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": 58}, "alltitles": {"Welcome to the PolarRoute Manual Pages": [[0, "welcome-to-the-polarroute-manual-pages"]], "Background": [[1, "background"]], "Code Overview": [[1, "code-overview"]], "Code Structure": [[1, "code-structure"]], "Command Line Interface": [[2, "command-line-interface"]], "create_mesh": [[2, "create-mesh"]], "add_vehicle": [[2, "add-vehicle"]], "resimulate_vehicle": [[2, "resimulate-vehicle"]], "optimise_routes": [[2, "optimise-routes"]], "calculate_route": [[2, "calculate-route"]], "extract_routes": [[2, "extract-routes"]], "Plotting": [[2, "plotting"]], "Configuration Overview": [[3, "configuration-overview"]], "Configuration - Route Planning": [[4, "configuration-route-planning"]], "Configuration - Vessel Performance Modeller": [[5, "configuration-vessel-performance-modeller"]], "Command Line Interface Examples": [[6, "command-line-interface-examples"]], "Empty Mesh Example": [[6, "empty-mesh-example"]], "Synthetic Data Example": [[6, "synthetic-data-example"]], "Real Data Example": [[6, "real-data-example"]], "Python Examples": [[6, "python-examples"]], "Creating the digital environment.": [[6, "creating-the-digital-environment"]], "Simulating a Vessel in a Digital Environment": [[6, "simulating-a-vessel-in-a-digital-environment"]], "Route Optimisation": [[6, "route-optimisation"]], "Visualising Outputs": [[6, "visualising-outputs"]], "Outputs - Data Types": [[8, "outputs-data-types"]], "The Vessel_mesh.json file": [[8, "the-vessel-mesh-json-file"]], "The Route.json file": [[8, "the-route-json-file"]], "waypoints": [[8, "waypoints"]], "paths": [[8, "paths"]], "Methods - Route Calculation": [[9, "methods-route-calculation"]], "Route Calculation Overview": [[9, "route-calculation-overview"]], "Route Calculation": [[9, "module-polar_route.route_calc"]], "Methods - Route Planner": [[10, "methods-route-planner"]], "Route Optimisation Overview": [[10, "route-optimisation-overview"]], "Route Optimisation Modules": [[10, "route-optimisation-modules"]], "Route Planner": [[10, "module-polar_route.route_planner.route_planner"]], "Crossing Points": [[10, "module-polar_route.route_planner.crossing"]], "Crossing Point Smoothing": [[10, "module-polar_route.route_planner.crossing_smoothing"]], "Methods - Vessel Performance": [[11, "methods-vessel-performance"]], "Vessel Overview": [[11, "vessel-overview"]], "Vessel Performance Modeller": [[11, "module-polar_route.vessel_performance.vessel_performance_modeller"]], "Vessel Factory": [[11, "module-polar_route.vessel_performance.vessel_factory"]], "Abstract Vessel": [[11, "module-polar_route.vessel_performance.abstract_vessel"]], "Abstract Ship": [[11, "module-polar_route.vessel_performance.vessels.abstract_ship"]], "SDA": [[11, "module-polar_route.vessel_performance.vessels.SDA"]], "Abstract Glider": [[11, "module-polar_route.vessel_performance.vessels.abstract_glider"]], "Slocum Glider": [[11, "module-polar_route.vessel_performance.vessels.slocum"]], "Installation": [[7, "installation"]], "Installing PolarRoute": [[7, "installing-polarroute"]], "Installing GeoPlot": [[7, "installing-geoplot"]], "Installing GDAL (Optional)": [[7, "installing-gdal-optional"]], "Windows": [[7, "windows"]], "Linux/MacOS": [[7, "linux-macos"]]}, "indexentries": {}}) \ No newline at end of file diff --git a/docs/html/sections/Code_overview.html b/docs/html/sections/Code_overview.html deleted file mode 100644 index 347bc26d..00000000 --- a/docs/html/sections/Code_overview.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - 7. Background — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

7. Background

-
-

7.1. Code Overview

-

The PolarRoute package provides an automated route-planning method for use by an ice-strengthened vessel operating in the polar regions. The long-distance route planning approach builds on the method developed for underwater vehicles reported in Fox et al (2021). We start with the same grid-based route construction approach to obtain routes that satisfy constraints on the performance of the ship in ice. We then apply a novel route-smoothing method to the resulting routes, shortening the grid-based routes and ensure that routes follow great circle arcs where possible. This two-stage process efficiently generates routes that follow standard navigation solutions in open water and optimise vessel performance in and around areas dominated by sea ice. While we have focussed on navigation in and around polar ice, our methods are also applicable to shipping in a broader context (e.g.: commercial shipping) where route-planning must be responsive to changing local and weather conditions.

-
-
-

7.2. Code Structure

-

The aim of this manual is to provide the user with all the tools that they need to run the software for a set of examples. We also hope that the background information supplied for each section allows the user to understand the methods used throughout this software package.

-

The PolarRoute package can be separated into the four main sections shown in the Figure below:

-
-../_images/FlowDiagram_Overview.png -
-

Overview figure outlining the stages in planning a route with PolarRoute. Generation of the initial environmental mesh is performed by the MeshiPhi package.

-
-
-

The separate stages can be broken down into:

-
    -
  1. Vessel Performance - Application of vehicle specific features applied to the discrete mesh. In this section we will supply the user with the knowledge of how vehicle specific features are applied to the discrete mesh or with variables applied to the computational graph of the mesh.

  2. -
  3. Route Planner - Generating grid-based dijkstra paths and data constrained path smoothing from the gridded solutions - In this section we will give the user the background to constructing paths between user defined waypoints that minimise a specific objective function (e.g. travel time, fuel). Once the gridded Dijkstra paths are formulated we outline a smoothing based procedure that uses the data information to generate non-gridded improved route paths.

  4. -
-
-../_images/PolarRoute_CodeFlowDiagram.png -
-

Overview figure outlining the Input/Output of all sections of the Route Planning pipeline

-
-
-

Each stage of this pipeline makes use of a configuration file, found in the Configuration Overview section of the documentation -and produces an output file, the form of which can be found in the Outputs - Data Types section of this document.

-

In addition to the main section of the codebase we have also developed a series of plotting classes that allows the user to generate interactive maps and static figures for the Mesh Features and Route Paths. These can be found in the Plotting section later in the manual.

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/sections/Command_line_interface.html b/docs/html/sections/Command_line_interface.html deleted file mode 100644 index b64377ed..00000000 --- a/docs/html/sections/Command_line_interface.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - - - - 4. Command Line Interface — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

4. Command Line Interface

-

The PolarRoute package provides multiple CLI entry points, intended to be used in succession to plan a route through a digital environment.

-
-

4.1. create_mesh

-

The create_mesh entry point builds a digital environment file from a collection of source data, which can then be used -by the vessel performance modeller and route planner.

-
create_mesh <config.json>
-
-
-

positional arguments:

-
config : A configuration file detailing how to build the digital environment. JSON parsable
-
-
-

The format of the required config.json file can be found in the “Configuration - Mesh Construction” section of the MeshiPhi documentation . -There are also example configuration files available in the directory examples/environment_config/grf_example.config.json

-

optional arguments:

-
-v (verbose logging)
--o <output location> (set output location for mesh)
-
-
-
-
-

4.2. add_vehicle

-

The add_vehicle command allows vehicle specific simulations to be performed on the digital environment. This vehicle specific -information is then encoded into the digital environment file.

-
add_vehicle <vessel_config.json> <mesh.json>
-
-
-

positional arguments:

-
vessel_config : A configuration file giving details of the vessel to be simulated.
-mesh : A digital environment file.
-
-
-

The format for the required vessel.json file can be found in the Configuration - Vessel Performance Modeller section of the documentation. -The required mesh.json file can be created using the create_mesh command from the MeshiPhi package.

-

optional arguments are

-
-v (verbose logging)
--o <output location> (set output location for mesh)
-
-
-

The format of the return Vessel_Mesh.json file is explained in The Vessel_mesh.json file section of the documentation.

-
-
-

4.3. resimulate_vehicle

-

The resimulate_vehicle command allows vehicle specific simulations to be performed again on an existing vessel mesh. -This allows new models to be easily run using the pre-existing config parameters.

-
resimulate_vehicle <vessel_mesh.json>
-
-
-

positional arguments:

-
vessel_mesh : A digital environment file with added vessel specific simulations.
-
-
-

optional arguments are

-
-v (verbose logging)
--o <output location> (set output location for mesh)
-
-
-
-
-

4.4. optimise_routes

-

Optimal routes through a mesh can be calculated using the command:

-
optimise_routes <route_config.json> <vessel_mesh.json> <waypoints.csv>
-
-
-

positional parameters:

-
vessel_mesh : A digital environment file with added vessel specific simulations.
-route_config : A configuration file detailing optimisation parameters to be used when route planning.
-waypoints: A .csv file containing waypoints to be travelled between.
-
-
-

The format for the required route_config.json file can be found in the Configuration - Route Planning section of the documentation. -The required vessel_mesh.json file can be generated using the add_vehicle command shown above. -The format for the required waypoints.csv file is as follows:

-

As a table:

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

Name

Lat

Long

Source

Destination

Halley

-75.267

-27.216

X

Rothera

-67.617

-68.047

South Georgia

-54.141

-36.094

X

Falklands

-51.953

-57.969

Elephant Island

-60.977

-55.078

-

In .csv format:

-
Name,Lat,Long,Source,Destination
-Halley,-75.267,-27.216,,X
-Rothera,-67.617,-68.047 ,,
-South Georgia,-54.141,-36.094,X,
-Falklands,-51.953,-57.969,,
-Elephant Island,-60.977,-55.078,,
-
-
-

Additional waypoints may be added by extending the waypoints.csv file. Which waypoints are navigated between is determined by -adding an X in either the Source or Destination columns. When processed, the route planner will create routes from all -waypoints marked with an X in the Source column to all waypoints marked with a X in the Destination column.

-

optional arguments are

-
-v (verbose logging)
--o <output location> (set output location for mesh)
--p (output only the calculated path, not the entire mesh)
--d (output Dijkstra path as well as smoothed path)
-
-
-

The format of the returned route.json file is explained in The Route.json file section of this documentation.

-
-
-

4.5. calculate_route

-

The cost of a user-defined route through a pre-generated mesh containing vehicle information can be calculated using the command:

-
calculate_route <vessel_mesh.json> <route>
-
-
-

positional parameters:

-
vessel_mesh : A digital environment file with added vessel specific simulations.
-route : A route file containing waypoints on a user-defined path.
-
-
-

optional arguments:

-
-v : verbose logging
--o : output location
-
-
-

Running this command will calculate the cost of a route between a set of waypoints provided in either csv or geojson -format. The route is assumed to travel from waypoint to waypoint in the order they are given, following a rhumb line. -The format of the output route.json file is identical to that from the optimise_routes command. -This is explained in The Route.json file section of the documentation. The time and fuel cost of the route will -also be logged out once the route file has been generated. If the user-defined route crosses a cell in the mesh that is -considered inaccessible to the vessel then a warning will be displayed and no route will be saved.

-
-
-

4.6. extract_routes

-

This command allows individual routes to be extracted from a larger file containing multiple routes. It automatically -determines the output format from the output filename given. Supported output types are json, geojson, csv, kml and gpx.

-
extract_routes <route_file.json>
-
-
-

positional parameters:

-
route_file.json : A file containing multiple geojson formatted routes.
-
-
-

optional arguments:

-
-v : verbose logging
--o : output location
-
-
-
-
-

4.7. Plotting

-

Meshes produced at any stage in the route planning process can be visualised using the plot_mesh cli command from the GeoPlot -library. Meshes and routes can also be plotted in other GIS software such as QGIS by exporting the mesh to a commonly used format such -as .geojson or .tif using the export_mesh command described in the MeshiPhi docs.

-
plot_mesh <mesh.json>
-
-
-

optional arguments:

-
-v : verbose logging
--o : output location
--a : add directional arrows to routes
--r : plot an additional route from a file
-
-
-
-../_images/PolarRoute_CLI.png -
-

Overview figure of the Command Line Interface entry points of PolarRoute

-
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/sections/Configuration/Configuration_overview.html b/docs/html/sections/Configuration/Configuration_overview.html deleted file mode 100644 index 03223fbe..00000000 --- a/docs/html/sections/Configuration/Configuration_overview.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - 5. Configuration Overview — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

5. Configuration Overview

-

In this section we will outline the standard structure for a configuration file used in -all portions of the PolarRoute software package.

-

Each stage of the route-planning process is configured by a separate configuration file. -The configuration files are written in JSON, and are passed to each stage of the -route-planning process as command-line arguments or through a Python script.

-

Example configuration files are provided in the config directory.

-

The format of the config used to generate an environmental mesh can be found in the “Configuration - Mesh Construction” section of the MeshiPhi documentation .

-

Descriptions of the configuration options for the Vessel Performance Modelling can -be found in the Configuration - Vessel Performance Modeller section of the -documentation.

-

Descriptions of the configuration options for Route Planning can be found in the -Configuration - Route Planning section of the documentation.

- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/sections/Configuration/Route_planning_config.html b/docs/html/sections/Configuration/Route_planning_config.html deleted file mode 100644 index b8880733..00000000 --- a/docs/html/sections/Configuration/Route_planning_config.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - - 5.2. Configuration - Route Planning — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

5.2. Configuration - Route Planning

-
{
- "objective_function": "traveltime",
- "path_variables": [
-   "fuel",
-   "traveltime"
- ],
- "vector_names": ["uC","vC"]
-}
-
-
-

above is the minimal set of configuration parameters necessary to run the route planner where the variables are as follows:

-
    -
  • objective_function (string) : Defining the objective function to minimise for the route construction. This variable can either be defined as ‘traveltime’, ‘battery’ or ‘fuel’, depending on the vessel type .

  • -
  • path_variables (list<(string)>) : A list of strings of the route variables to return in the output geojson.

  • -
  • vector_names (list<(string)>) : The definition of the horizontal and vertical components of the vector acting on the ship in each cell. Data for these names must be within the cells of the input mesh.

  • -
-
{
- "objective_function": "traveltime",
-  "path_variables": [
-    "fuel",
-    "traveltime"
-  ],
-  "vector_names": ["uC","vC"],
-  "time_unit": "days",
-  "adjust_waypoints": true,
-  "zero_currents": false,
-  "fixed_speed": false,
-  "waypoint_splitting": false,
-  "smoothing_max_iterations": 2000,
-  "smoothing_blocked_sic": 10.0,
-  "smoothing_merge_separation": 1e-3,
-  "smoothing_converged_separation": 1e-3
- }
-
-
-

above is the full set of configuration parameters that can be used for route planning, where the additional variables are as follows:

-
    -
  • time_unit (string) : The time unit to use in the route output . Currently only takes ‘days’, but will support ‘hrs’ in future releases.

  • -
  • adjust_waypoints (bool) : Used to enable or disable the process that moves waypoints placed in inaccessible regions to the nearest accessible location.

  • -
  • zero_currents (bool) : For development use only. Removes the effect of currents acting on the ship, setting all current vectors to zero.

  • -
  • fixed_speed (bool) : For development use only. Removes the effect of variable speed acting on the ship, ship speed set to max speed defined in the vessel config.

  • -
  • waypoint_splitting (bool) : Used to enable or disable splitting around the input waypoints. If enabled, all cells containing waypoints will be split to the maximum split depth given in the mesh config.

  • -
  • smoothing_max_iterations (int) : For development use only. Maximum number of iterations in the path smoothing. For most paths convergence is met 100x earlier than this value.

  • -
  • smoothing_blocked_sic (float) : For development use only. The maximum difference in sea ice concentration allowed before a cell is blocked for the smoothing.

  • -
  • smoothing_merge_sep (float) : For development use only. Minimum difference between two path smoothing iterations before a merge is triggered.

  • -
  • smoothing_converged_sep (float) : For development use only. Minimum difference between two path smoothing iterations before convergence is triggered.

  • -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/sections/Configuration/Vessel_performance_config.html b/docs/html/sections/Configuration/Vessel_performance_config.html deleted file mode 100644 index 28f11b55..00000000 --- a/docs/html/sections/Configuration/Vessel_performance_config.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - 5.1. Configuration - Vessel Performance Modeller — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

5.1. Configuration - Vessel Performance Modeller

-

The Vessel configuration file provides all the necessary information about the vessel that will execute -the routes such that performance parameters (e.g. speed or fuel consumption) can be calculated by the -VesselPerformanceModeller class. A file of this structure is also used as a command line argument for -the ‘add_vehicle’ entry point.

-
{
-  "vessel_type": "SDA",
-  "max_speed": 26.5,
-  "unit": "km/hr",
-  "beam": 24.0,
-  "hull_type": "slender",
-  "force_limit": 96634.5,
-  "max_ice_conc": 80,
-  "min_depth": 10,
-  "max_wave": 3,
-  "excluded_zones": ["exclusion_zone"],
-  "neighbour_splitting": true
-}
-
-
-

Above are a typical set of configuration parameters used for a vessel where the variables are as follows:

-
    -
  • vessel_type (string) : The specific vessel class to use for performance modelling.

  • -
  • max_speed (float) : The maximum speed of the vessel in open water.

  • -
  • unit (string) : The units of measurement for the speed of the vessel (currently only “km/hr” is supported).

  • -
  • beam (float) : The beam (width) of the ship in metres.

  • -
  • hull_type (string) : The hull profile of the ship (should be one of either “slender” or “blunt”).

  • -
  • force_limit (float) : The maximum allowed resistance force, specified in Newtons.

  • -
  • max_ice_conc (float) : The maximum Sea Ice Concentration the vessel is able to travel through given as a percentage.

  • -
  • min_depth (float) : The minimum depth of water the vessel is able to travel through in metres.

  • -
  • max_wave (float) : The maximum significant wave height the vessel is able to travel through in metres.

  • -
  • excluded_zones (float) : A list of of strings that name different boolean properties of a cell. Any cell with a value of True for any of the entered keys will be marked as unnavigable.

  • -
  • neighbour_splitting (bool) : Used to enable or disable a feature that splits all accessible cells neighbouring inaccessible cells. This improves routing performance but can be disabled to speed up the vessel performance modelling.

  • -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/sections/Examples.html b/docs/html/sections/Examples.html deleted file mode 100644 index adbac0dd..00000000 --- a/docs/html/sections/Examples.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - - - - 2. Command Line Interface Examples — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

2. Command Line Interface Examples

-

The CLI provides multiple entry-points through which the PolarRoute package can be used. Each command is described in the -Command Line Interface section of these docs.

-

To summarise, the basic process is to create an environment mesh, add vehicle performance characteristics to each -cell in that mesh and then find an optimal route between waypoints located within that mesh. At any stage, GeoPlot -can be used to visualise the outputs.

-
# From MeshiPhi
-create_mesh <mesh_config_file> -o <mesh_output_file>
-
-# From PolarRoute
-add_vehicle <vessel_config_file> <mesh_output_file> -o <vessel_output_file>
-optimise_routes <route_config_file> <vessel_output_file> <waypoints_file> -o <route_output_file>
-
-# From GeoPlot
-plot_mesh <any_output_file> -o <output_file>
-
-
-

Above are the commands to run in order to fulfill this process. If you have successfully installed PolarRoute and would -like to try it out, here -is some example data which you can use. Simply extract the configs out of the zip archive, and run the commands on the -appropriate files. To map the commands to the files in the zip archive:

-
    -
  • <mesh_config_file> is called grf_example.config.json

  • -
  • <vessel_config_file> is called ship.config.json

  • -
  • <route_config_file> is called traveltime.config.json

  • -
  • <waypoints_file> is called waypoints_example.csv

  • -
-
-

Note

-

By default the plot_mesh command will plot a basemap showing the location of your mesh on Earth. -When working with entirely synthetic data, e.g. when running any of the GRF examples below, the spatial coordinates -used do not correspond to a real location and we recommend running plot_mesh with the -b option to -disable this basemap.

-
-

Several notebooks have been created that will guide you through each stage in using PolarRoute, from mesh creation -through to route planning. These notebooks are available via Google Colab and use the CLI entry-points to show how -someone would typically interact with PolarRoute through the terminal.

-
-

2.1. Empty Mesh Example

-

Here we provide two examples of empty meshes that are simple to process to get you started. Since these are empty meshes, -we expect the optimal calculated route to be a straight line between two waypoints. Over long distances this is seen as -a great circle arc on the mercator projection that GeoPlot uses to display the mesh.

- -
-
-

2.2. Synthetic Data Example

-

In this example, we provide synthetic data in the form of Gaussian Random Fields (GRFs), which provide a random, yet somewhat -realistic representation of real-world features such as bathymetry. Here we walk through every step involved in PolarRoute, -from creating the mesh through to optimising a route within it.

- -
-
-

2.3. Real Data Example

-

Real world data has been used to generate these meshes around the coast of Antarctica. This data is publicly available, -however is not included here to avoid violating data sharing policies. Instead, we provide a mesh file after the ‘create_mesh’ stage -since that is a derived product. The data files used to construct the mesh can be seen in the data_sources field of -the config contained within the provided mesh. See Dataloaders -in the MeshiPhi docs for more info on each source of data that PolarRoute currently supports.

- -
-
-
-

3. Python Examples

-

Route planning may also be done in a python interpreter. In this case, the CLI is not required but the steps required for route planning -follow the same format - create a digital environment; simulated a vessel against it; optimise a route plan through the digital environment. -To perform the steps detailed in this section, a mesh must first be generated using MeshiPhi.

-

The files used in the following example are those used in the synthetic example from the notebook section above. Download them -here.

-
-

3.1. Creating the digital environment.

-

A configuration file is needed to initialise the EnvironmentMesh object which forms the digital environment. This configuration file -is of the same format used in the create_mesh CLI entry-point, and may either be loaded from a json file or constructed -within a python interpreter.

-

Loading configuration from json file:

-
import json
-# Read in config file
-with open('/path/to/grf_example.config.json', 'r') as f:
-    config = json.load(f)
-
-
-

The EnvironmentMesh object can then be initialised. This mesh object will be constructed using the parameters in its -configuration file. This mesh object can then be manipulated further, such as increasing its resolution through further -splitting, adding additional data sources or altering its configuration parameters. See the relevant section of the MeshiPhi docs -for a more in-depth explanation. The EnvironmentMesh object can then be cast to a json object and saved to a file.

-
from meshiphi.mesh_generation.mesh_builder import MeshBuilder
-
-# Create mesh from config
-cg = MeshBuilder(config).build_environmental_mesh()
-mesh = cg.to_json()
-
-# Save output file
-with open('/path/to/grf_example.mesh.json', 'w+') as f:
-    config = json.dump(mesh, f, indent=4)
-
-
-
-

Note

-

We are saving the file after each stage, but if you are running the code snippets -back to back, there is no need to save the json output and then load it in again. -Just pass the dictionary created from the to_json() call into the next function

-
-
-
-

3.2. Simulating a Vessel in a Digital Environment

-

Once a digital environment EnvironmentMesh object has been created with MeshiPhi, -a vessel’s performance when travelling within it may be simulated. The VesselPerformanceModeller object requires a -digital environment in json format and vessel specific configuration parameters, also in json format. These may either -be loaded from a file, or created within any python interpreter.

-

Loading mesh and vessel from json files:

-
# Loading digital environment from file
-with open('/path/to/grf_example.mesh.json', 'r') as f:
-    mesh = json.load(f)
-
-# Loading vessel configuration parameters from file
-with open('/path/to/ship.json', 'r') as f:
-    vessel = json.load(f)
-
-
-

The VesselPerformanceModeller object can then be initialised. This can be used to simulate the performance of the -vessel and encode this information into the digital environment.

-
from polar_route.vessel_performance.vessel_performance_modeller import VesselPerformanceModeller
-vp = VesselPerformance(mesh, vessel)
-vp.model_accessibility() # Method to determine any inaccessible areas, e.g. land
-vp.model_performance() # Method to determine the performance of the vessel in accessible regions, e.g speed or fuel consumption
-
-
-

The VesselPerformanceModeller object can then be cast to a json object and saved to a file. This vessel_mesh.json file can then -be used by the CLI entry-point optimise_routes, or the json object can be passed to the RoutePlanner object in a python -console.

-
vessel_mesh = vp.to_json()
-# Save to output file
-with open('/path/to/grf_example.vessel.json', 'w+') as f:
-    json.dump(vessel_mesh, f, indent=4)
-
-
-
-
-

3.3. Route Optimisation

-

Now that the vessel dependent environmental mesh is defined, and represented in the VesselPerformanceModeller object, -we can construct routes, with parameters defined by the user in the route config file.

-

Waypoints are passed as an input file path, waypoints.csv, an example file is give in the Command Line Interface -section of this documentation. The route construction is performed in two stages: construction of the mesh optimal dijkstra -routes, using the .compute_routes() method, and the smoothing of the dijkstra routes to further optimise the solution -and reduce mesh dependency, using .compute_smooth_routes(). During the execution of .compute_routes() the routes are -stored as an attribute of the RoutePlanner object under routes_dijkstra. These are then complemented by the smoothed -routes under routes_smoothed after running .compute_smooth_routes(). An additional entry waypoints is generated to -store the waypoints information used in route construction. For further details about the structure of the outputs of the -route planner please see the Outputs - Data Types section of this documentation.

-
from polar_route.route_planner.route_planner import RoutePlanner
-rp = RoutePlanner('/path/to/grf_example.vessel.json',
-                  '/path/to/traveltime.config.json')
-# Calculate optimal dijkstra path between waypoints
-rp.compute_routes('/path/to/waypoints_example.csv')
-# Smooth the dijkstra routes
-rp.compute_smoothed_routes()
-
-route_mesh = rp.to_json()
-# Save to output file
-with open('/path/to/grf_example.route.json', 'w+') as f:
-    json.dump(route_mesh, f, indent=4)
-
-
-
-
-

3.4. Visualising Outputs

-

The EnvironmentMesh object can be visualised using the GeoPlot package, also developed by BAS. This package is not -included in the distribution of PolarRoute, but can be installed using the following command:

-
pip install bas_geoplot
-
-
-

GeoPlot can then be used to visualise the EnvironmentMesh object using the following code in an iPython notebook or -any python interpreter:

-
from bas_geoplot.interactive import Map
-
-mesh = pd.DataFrame(mesh_json['cellboxes'])
-mp = Map(title="GRF Example")
-
-mp.Maps(mesh, 'MeshGrid', predefined='cx')
-mp.Maps(mesh, 'SIC', predefined='SIC')
-mp.Maps(mesh, 'Elevation', predefined='Elev', show=False)
-mp.Vectors(mesh,'Currents', show=False, predefined='Currents')
-mp.Vectors(mesh, 'Winds', predefined='Winds', show=False)
-
-mp.show()
-
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/sections/Installation.html b/docs/html/sections/Installation.html deleted file mode 100644 index ff0d9a4a..00000000 --- a/docs/html/sections/Installation.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - 1. Installation — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

1. Installation

-

In this section we will outline the necessary steps for installing the PolarRoute software package. Any installation of PolarRoute will also include MeshiPhi; a package designed to -discretise the world from heterogeneous data sources. PolarRoute requires a pre-existing installation of Python 3.8 or higher.

-
-

1.1. Installing PolarRoute

-

PolarRoute is available from PyPI and can be installed by running:

-
pip install polar-route
-
-
-

For development purposes you can install PolarRoute by downloading the source code from GitHub:

-
git clone https://github.com/Antarctica/PolarRoute
-pip install -e ./PolarRoute
-
-
-

Use of -e is optional, depending on whether you want to be able to edit the installed copy of the package.

-

In order to run the test suite you will also need to include the [test] flag to install the optional test dependencies:

-
pip install -e ./PolarRoute[test]
-
-
-
-
-

1.2. Installing GeoPlot

-

Plotting functionality for the outputs of PolarRoute is provided by the GeoPlot package, also developed at BAS.

-

Geoplot is available from PyPI and can be installed by running:

-
pip install bas-geoplot
-
-
-
-
-

1.3. Installing GDAL (Optional)

-

The PolarRoute software has GDAL as an optional requirement. It is only used when exporting TIFF images, -so if this is not useful to you, you can skip this step. It is not always trivial and is a common source of problems. -With that said, below are instructions for various operating systems.

-
-

1.3.1. Windows

-
-

Note

-

We assume a version of Windows 10 or higher, with a working version of Python 3.9 including pip installed. -We recommend installing PolarRoute into a virtual environment.

-
-

Windows:

-
pip install pipwin # pipwin is a package that allows for easy installation of windows binaries
-pipwin install gdal
-pipwin install fiona
-
-
-
-
-

1.3.2. Linux/MacOS

-

Ubuntu/Debian:

-
sudo add-apt-repository ppa:ubuntugis/ppa
-sudo apt update
-sudo apt install gdal-bin libgdal-dev
-export CPLUS_INCLUDE_PATH=/usr/include/gdal
-export C_INCLUDE_PATH=/usr/include/gdal
-pip install GDAL==$(gdal-config --version)
-
-
-

Fedora:

-
sudo dnf update
-sudo dnf install gdal gdal-devel
-export CPLUS_INCLUDE_PATH=/usr/include/gdal
-export C_INCLUDE_PATH=/usr/include/gdal
-pip install GDAL==$(gdal-config --version)
-
-
-

MacOS (with HomeBrew):

-
brew install gdal --HEAD
-brew install gdal
-pip install GDAL==$(gdal-config --version)
-
-
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/sections/Outputs.html b/docs/html/sections/Outputs.html deleted file mode 100644 index 22b76bee..00000000 --- a/docs/html/sections/Outputs.html +++ /dev/null @@ -1,310 +0,0 @@ - - - - - - - 6. Outputs - Data Types — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

6. Outputs - Data Types

-
-

6.1. The Vessel_mesh.json file

-

The vessel performance mesh is an extension to an environmental mesh created by the -MeshiPhi library. -Once a discrete mesh environment is created, it is then passed to the vessel performance modeller -which applies transformations which are specific to a given vehicle. These vehicle specific values -are then encoded into the mesh json object and passed downstream to the route planner.

-
import json
-from polar_route.vessel_performance.vessel_performance_modeller import VesselPerformanceModeller
-
-with open('vessel_config.json', 'r') as f:
-    vessel_config = json.load(f)
-
-vpm = VesselPerformanceModeller(mesh_json, vessel_config)
-
-vpm.model_accessibility()
-vpm.model_performance()
-
-vessel_mesh_json = vpm.to_json()
-
-
-
-

Note

-

To make use of the full range of vessel performance transformations, a Mesh should be constructed with -the following attributes:

-
    -
  • elevation (available via dataloaders: gebco, bsose_depth)

  • -
  • SIC (available via dataloaders: amsr, bsose_sic, baltic_sic, icenet, modis, visual_iced)

  • -
  • thickness (available via dataloaders: thickness)

  • -
  • density (available via dataloaders: density)

  • -
  • u10, v10 (available via dataloaders: era5_wind)

  • -
-

see section Dataloader Overview in the MeshiPhi docs for more information on dataloaders

-

The vessel performance modeller will still run without these attributes but will assign default values from the -configuration file where any data is missing.

-
-

As an example, after running the vessel performance modeller with the SDA class and all relevant data each cellbox will -have a set of new attributes as follows:

-
    -
  • speed (list) : The speed of the vessel in that cell when travelling to each of its neighbours.

  • -
  • fuel (list) : The rate of fuel consumption in that cell when travelling to each of its neighbours.

  • -
  • inaccessible (boolean) : Whether the cell is considered inaccessible to the vessel for any reason.

  • -
  • land (boolean) : Whether the cell is shallow enough to be considered land by the vessel.

  • -
  • ext_ice (boolean) : Whether the cell has enough ice to be inaccessible to the vessel.

  • -
  • resistance (list) : The total resistance force the vessel will encounter in that cell when travelling to each of its neighbours.

  • -
  • ice resistance (float) : The resistance force due to ice.

  • -
  • wind resistance (list) : The resistance force due to wind.

  • -
  • relative wind speed (list) : The apparent wind speed acting on the vessel.

  • -
  • relative wind angle (list) : The angle of the apparent wind acting on the vessel.

  • -
-
-
-

6.2. The Route.json file

-

During the route planning stage of the pipline information on the routes and the waypoints used are saved -as outputs to the processing stage. Descriptions of the structure of the two outputs are given below:

-
-

6.2.1. waypoints

-

An entry in the json including all the information about the waypoints defined by the user in the waypoints.csv -file. It may be the case that not all waypoints would have been used in the route construction, but all waypoints -that are defined can be found in this entry. The structure of the entry is as follows:

-
{\n
-    "Name":{\n
-        '0':"Falklands",\n
-        '1':"Rothera",\n
-        ...\n
-    },\n
-    "Lat":{\n
-        '0':-52.6347222222,
-        '1':-75.26722,\n
-        ...\n
-    },\n
-    "Long":{\n
-        ...\n
-    },\n
-    "index":{\n
-        ...\n
-    }\n
-}
-
-
-

where each of the values represent the following:

-
    -
  • -
    <Name>The waypoint name defined by the user
      -
    • 0 : The name of waypoint for index row ‘0’

    • -
    • 1 : The name of waypoint for index row ‘1’ etc

    • -
    -
    -
    -
  • -
  • -
    <Lat>The latitude of the waypoints in WGS84
      -
    • 0 : The latitude of waypoint for index row ‘0’

    • -
    • 1 : The latitude of waypoint for index row ‘1’ etc

    • -
    -
    -
    -
  • -
  • -
    <Long>The longitude of the waypoints in WGS84
      -
    • 0 : The longitude of waypoint for index row ‘0’

    • -
    • 1 : The longitude of waypoint for index row ‘1’ etc

    • -
    -
    -
    -
  • -
  • -
    <index>The index of the cellbox containing the waypoint
      -
    • 0 : The cellbox index of waypoint for index row ‘0’

    • -
    • 1 : The cellbox index of waypoint for index row ‘1’ etc

    • -
    -
    -
    -
  • -
  • <…> : Any additional column names defined in the original .csv that was loaded

  • -
-

This output can be converted to a pandas dataframe by running:: -waypoints_dataframe = pd.DataFrame(waypoints)

-
-
-

6.2.2. paths

-

An entry in the json, in geojson format, including all the routes constructed between the user defined waypoints. The structure of this entry is as follows:

-
{\n
-    'types':'FeatureCollection',\n
-    "features":{[\n
-        'type':'feature',\n
-        'geometry':{\n
-            'type': 'LineString',
-
-            'coordinates': [[-27.21694, -75.26722],\n
-                            [-27.5, -75.07960297382266],\n
-                            [-27.619238882768894, -75.0],\n
-                            ...]\n
-        },
-        'properties':{\n
-            'from': 'Halley',\n
-            'to': 'Rothera',\n
-            'traveltime': [0.0,\n
-                           0.03531938671648596,\n
-                           0.050310986633880575,\n
-                           ...],\n
-            'fuel': [0.0,\n
-                     0.9648858923588642,\n
-                     1.3745886107069096,\n
-                     ...],\n
-            'times': ['2017-01-01 00:00:00',
-                      '2017-01-01 00:50:51.595036800',
-                      '2017-01-01 01:12:26.869276800',
-                      ...]\n
-        }\n
-    ]}\n
-}\n
-
-
-

where the output takes a GeoJSON standard form (more info at https://geojson.org) given by:

-
    -
  • -
    <features>A list of the features representing each of the separate routes constructed
      -
    • -
      geometryThe positioning of the route locations
        -
      • coordinates : A list of the Lat,Long position of all the route points

      • -
      -
      -
      -
    • -
    • -
      <properties>A list of meta-information about the route
        -
      • from : Start waypoint of route

      • -
      • to : End waypoint of route

      • -
      • traveltime : A list of float values representing the cumulative travel time along the route. This entry was originally defined as an output in the configuration file by the path_variables definition.

      • -
      • fuel : A list of float values representing the cumulative fuel along the route. This entry was originally defined as an output in the configuration file by the path_variables definition.

      • -
      • times : A list of strings representing UTC Datetimes of the route points, given that the route started from start_time given in the configuration file.

      • -
      -
      -
      -
    • -
    -
    -
    -
  • -
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/sections/Route_calculation.html b/docs/html/sections/Route_calculation.html deleted file mode 100644 index 3d21be80..00000000 --- a/docs/html/sections/Route_calculation.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - - 9. Methods - Route Calculation — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

9. Methods - Route Calculation

-
-

9.1. Route Calculation Overview

-

This section describes the code used for the evaluation of user-defined routes through a mesh constructed by the methods -described in previous sections. This mesh should include all the relevant vessel performance parameters needed to -calculate the fuel and time cost of the route.

-
-
-

9.2. Route Calculation

-
-
-polar_route.route_calc.case_from_angle(start, end)
-

Determine the direction of travel between two points and return the associated case

-
-
Parameters:
-
    -
  • start (ndarray) – the coordinates of the start point within the cell

  • -
  • end (ndarray) – the coordinates of the end point within the cell

  • -
-
-
Returns:
-

the case to use to select variable values from a list

-
-
Return type:
-

case (int)

-
-
-
- -
-
-polar_route.route_calc.find_intersections(df, mesh)
-

Find crossing points of the route and the cells in the mesh -:param df: Route info in dataframe format -:type df: DataFrame -:param mesh: Mesh in GeoDataFrame format -:type mesh: GeoDataFrame

-
-
Returns:
-

Dictionary of crossing points and cell ids

-
-
Return type:
-

track_points (dict)

-
-
-
- -
-
-polar_route.route_calc.load_mesh(mesh_file)
-

Load mesh from file into GeoDataFrame -:param mesh_file: Path to mesh with vehicle information -:type mesh_file: str

-
-
Returns:
-

Mesh in GeoDataFrame format

-
-
Return type:
-

mesh (GeoDataFrame)

-
-
-
- -
-
-polar_route.route_calc.load_route(route_file)
-

Load route information from file

-
-
Parameters:
-

route_file (str) – Path to user defined route in json, csv or gpx format

-
-
Returns:
-

Dataframe with route info -from_wp (str): Name of start waypoint -to_wp (str) Name of end waypoint

-
-
Return type:
-

df (Dataframe)

-
-
-
- -
-
-polar_route.route_calc.order_track(df, track_points)
-

Order crossing points into a track along the route -:param df: Route info in dataframe format -:type df: DataFrame -:param track_points: Dictionary of crossing points and cell ids -:type track_points: dict

-
-
Returns:
-

DataFrame of ordered crossing points and cell ids

-
-
Return type:
-

user_track (DataFrame)

-
-
-
- -
-
-polar_route.route_calc.route_calc(route_file, mesh_file)
-

Function to calculate the fuel/time cost of a user defined route in a given mesh

-
-
Parameters:
-
    -
  • route_file (str) – Path to user defined route

  • -
  • mesh_file (str) – Path to mesh with vehicle information

  • -
-
-
Returns:
-

User defined route in geojson format with calculated cost information

-
-
Return type:
-

user_path (dict)

-
-
-
- -
-
-polar_route.route_calc.traveltime_distance(cellbox, wp, cp, speed='speed', vector_x='uC', vector_y='vC', case=0)
-

Calculate travel time and distance for two points.

-
-
Parameters:
-
    -
  • cellbox (dict) – the cell containing the line segment

  • -
  • wp (ndarray) – the start point

  • -
  • cp (ndarray) – the end point

  • -
  • speed (str) – the key for speed

  • -
  • vector_x (str) – the key for the x vector component

  • -
  • vector_y (str) – the key for the y vector component

  • -
  • case (int) – case giving the index of the speed array

  • -
-
-
Returns:
-

the time to travel the line segment -distance (float): the distance along the line segment

-
-
Return type:
-

traveltime (float)

-
-
-
- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/sections/Route_optimisation.html b/docs/html/sections/Route_optimisation.html deleted file mode 100644 index 826b5259..00000000 --- a/docs/html/sections/Route_optimisation.html +++ /dev/null @@ -1,673 +0,0 @@ - - - - - - - 10. Methods - Route Planner — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

10. Methods - Route Planner

-
-

10.1. Route Optimisation Overview

-

In this section we outline the code used to generate optimal routes through a mesh constructed by the methods described -in previous sections. This mesh should include the vessel performance parameters with respect to which objective -functions can be defined for optimisation.

-
-
-

10.2. Route Optimisation Modules

-
-

10.2.1. Route Planner

-

This module is used for construction of routes within an -environmental mesh between a series of user defined waypoints

-
-
-class polar_route.route_planner.route_planner.RoutePlanner(mesh_file, config_file, cost_func=<class 'polar_route.route_planner.crossing.NewtonianDistance'>)
-

RoutePlanner finds the optimal route between a series of waypoints. -The routes are constructed in a two stage process:

-

compute_routes: Uses a mesh based Dijkstra method to determine the optimal routes between a series of waypoints.

-

compute_smoothed_routes: Smooths the output of compute_routes using information from the environmental mesh to determine mesh independent optimal routes.

-

-
-
-env_mesh
-

mesh object that contains the mesh’s cellboxes information and neighbour graph

-
-
Type:
-

EnvironmentMesh

-
-
-
- -
-
-cost_func
-

Crossing point cost function for Dijkstra Route creation

-
-
Type:
-

func

-
-
-
- -
-
-config
-

JSON object that contains the attributes required for the route construction.

-
-
Type:
-

Json

-
-
-
- -
-
-src_wps
-

a list of the source waypoints that contains all of the dijkstra routing -information to reuse this information for routes with the same source -waypoint

-
-
Type:
-

list<SourceWaypoint>

-
-
-
- -

-
-
-_dijkstra(wp, end_wps)
-

Runs Dijkstra’s algorithm across the whole of the domain

-
-
Parameters:
-
    -
  • wp (SourceWaypoint) – object contains the lat, long information of the source waypoint

  • -
  • end_wps (List(Waypoint)) – a list of the end waypoints

  • -
-
-
-
- -
-
-_dijkstra_routes(start_waypoints, end_waypoints)
-

Hidden function. Given internal variables and start and end waypoints this function -returns a list of routes

-
-
Parameters:
-
    -
  • start_waypoints (list<Waypoint>) – list of start waypoints

  • -
  • end_waypoints (list<Waypoint>) – list of end waypoints

  • -
-
-
Returns:
-

list of the constructed routes

-
-
Return type:
-

routes (list<Route>)

-
-
-
- -
-
-_fixed_speed(mesh)
-

Applying max speed for all cellboxes that are accessible

-
-
Parameters:
-

mesh (JSON) – MeshiPhi Mesh input

-
-
Returns:
-

MeshiPhi Mesh Corrected

-
-
Return type:
-

mesh (JSON)

-
-
-
- -
-
-_neighbour_cost(node_id, neighbour_id, case)
-

Determines the neighbour cost when travelling from the cell at node_id to the cell at neighbour_id given the -specified case.

-
-
Parameters:
-
    -
  • node_id (str) – the id of the initial cellbox

  • -
  • neighbour_id (str) – the id of the neighbouring cellbox

  • -
  • case (int) – the case of the transition from node_id to neighbour_id

  • -
-
-
Returns:
-

a list of segments that form the legs of the route from node_id to neighbour_id

-
-
Return type:
-

neighbour_segments (list<Segment>)

-
-
-
- -
-
-_splitting_around_waypoints(waypoints_df)
-

Applying splitting around waypoints if this is defined in config. This is applied -inplace.

-
-
Parameters:
-

waypoints_df (pd.DataFrame) – Pandas DataFrame of Waypoint locations

-
-
-
-
Applied to terms:

self.config (dict): PolarRoute config file

-

self.env_mesh (EnvironmentMesh): The EnvironmentMesh object for the relevant mesh

-
-
-
- -
-
-_validate_wps(wps)
-

Determines if the provided waypoint list contains valid waypoints (i.e. both lie within the bounds of -the env mesh).

-
-
Parameters:
-

wps (list<Waypoint>) – list of waypoint objects that encapsulates lat and long information

-
-
Returns:
-

list of waypoint objects that encapsulates lat and long information after -removing any invalid waypoints

-
-
Return type:
-

Wps (list<Waypoint>)

-
-
-
- -
-
-_zero_currents(mesh)
-

Applying zero currents to mesh

-
-
Parameters:
-

mesh (JSON) – MeshiPhi Mesh input

-
-
Returns:
-

MeshiPhi Mesh Corrected

-
-
Return type:
-

mesh (JSON)

-
-
-
- -
-
-compute_routes(waypoints)
-

Computes the Dijkstra routes between waypoints.

-
-
Parameters:
-

waypoints (String/Dataframe) – DataFrame that contains source and destination waypoints info or a string -pointing to the path of a csv file that contains this info

-
-
Returns:
-

a list of the computed routes

-
-
Return type:
-

routes (List<Route>)

-
-
-
- -
-
-compute_smoothed_routes(blocked_metric='SIC')
-

Uses the previously constructed Dijkstra routes and smooths them to remove mesh features -paths will be updated in the output JSON

-
- -
-
-initialise_dijkstra_graph(cellboxes, neighbour_graph, route, path_index=False)
-

Initialising dijkstra graph information in a standard form used for the smoothing

-
-
Parameters:
-
    -
  • cellboxes (list) – List of cells with environmental and vessel performance info

  • -
  • neighbour_graph (dict) – Neighbour graph for the mesh

  • -
  • route (Route) – Route object for the route to be smoothed

  • -
  • path_index (bool) – Option to generate the pathIndex array that can be used to generate new dijkstra routes

  • -
-
-
Returns:
-

-
Dictionary comprising dijkstra graph with keys based on cellbox id.

Each entry is a dictionary of the cellbox environmental and dijkstra information.

-
-
-

-
-
Return type:
-

dijkstra_graph_dict (dict)

-
-
-
- -
-
-to_json()
-

Output all information from the RoutePlanner object in json format

-
-
Returns:
-

the full mesh and route information in json format

-
-
Return type:
-

output_json (dict)

-
-
-
- -
- -
-
-polar_route.route_planner.route_planner._adjust_waypoints(point, cellboxes, max_distance=5)
-

Moves waypoint to the closest accessible cellbox if it isn’t already in one. Allows up to 5 degrees flexibility -by default.

-
- -
-
-polar_route.route_planner.route_planner._load_waypoints(waypoints)
-

Load source and destination waypoints from dict or file

-
-
Parameters:
-

waypoints (dict or str) – waypoints dict or path to file to load waypoints from

-
-
Returns:
-

list of source waypoints -dest_wps (list): list of destination waypoints

-
-
Return type:
-

src_wps (list)

-
-
-
- -
-
-polar_route.route_planner.route_planner._mesh_boundary_polygon(mesh)
-

Creates a polygon from the mesh boundary

-
- -
-
-polar_route.route_planner.route_planner.flatten_cases(cell_id, neighbour_graph)
-

Identifies the cases with neighbours around a given cell and gets the ids of those neighbouring cells.

-
-
Parameters:
-
    -
  • cell_id (str) – The id of the cell to find the neighbours for

  • -
  • neighbour_graph (dict) – The neighbour graph of the mesh

  • -
-
-
Returns:
-

A list of neighbouring case directions -neighbour_indx (list): A list of neighbouring cell indices

-
-
Return type:
-

neighbour_case (list)

-
-
-
- -
-
-polar_route.route_planner.route_planner.initialise_dijkstra_route(dijkstra_graph, dijkstra_route)
-

Initialising dijkstra route into a standard form used for smoothing

-
-
Parameters:
-
    -
  • dijkstra_graph (dict) – Dictionary comprising dijkstra graph with keys based on cellbox id. -Each entry is a dictionary of the cellbox environmental and dijkstra information.

  • -
  • dijkstra_route (dict) – Dictionary of a GeoJSON entry for the dijkstra route

  • -
-
-
Returns:
-

-
A list of adjacent cell pairs where each entry is of type FindEdge including

information on .crossing, .case, .start, and .end (see ‘find_edge’ for more information)

-
-
-

-
-
Return type:
-

aps (list<FindEdge>)

-
-
-
- -
-
-

10.2.2. Crossing Points

-

The python package crossing implements the optimisation of the crossing point for the Dijkstra path -construction using the NewtonianDistance class. -In the section below we will go through, stage by stage, how the crossing point is determined and the methods -used within the class.

-
-
-polar_route.route_planner.crossing.traveltime_in_cell(xdist, ydist, u, v, s, tt_dist=None)
-

Determine the traveltime within a cell

-
-
Parameters:
-
    -
  • xdist (float) – Longitude distance between two points in km

  • -
  • ydist (float) – Latitude distance between two points in km

  • -
  • u (float) – U-Component for the forcing vector

  • -
  • v (float) – V-Component for the forcing vector

  • -
  • s (float) – Speed of the vehicle

  • -
  • tt_dist (bool) – Returns traveltime and distance if true, otherwise just traveltime

  • -
-
-
Returns:
-

the travel time within the cell -dist (float): the distance within the cell

-
-
Return type:
-

traveltime (float)

-
-
-
- -
-
-

10.2.3. Crossing Point Smoothing

-
-
-class polar_route.route_planner.crossing_smoothing.FindEdge(cell_a, cell_b, case)
-

Class to return characteristics information about the edge connecting two -cells. This information includes:

-

crossing (tuple) - Crossing point (long,lat) -case (int) - Case type connecting the two cells -start (dict) - Dictionary containing the environmental parameters of the start cell -end (dict) - Dictionary containing the environmental parameters of the end cell

-
-
-_find_edge(cell_a, cell_b, case)
-

Function that returns the edge connecting to cells, cell_a and cell_b. If there is no edge -connecting the two then it returns None

-
-
Parameters:
-
    -
  • cell_a (dict) – Dictionary of cell_a information

  • -
  • cell_b (dict) – Dictionary of cell_b information

  • -
-
-
-
-
Returns

crossing (tuple) - Crossing point (long,lat) connecting the two cells -case (int) - Case type connecting the two cells -start (dict) - Dictionary containing the environmental parameters of the start cell -end (dict) - Dictionary containing the environmental parameters of the end cell

-
-
-
- -
- -
-
-class polar_route.route_planner.crossing_smoothing.PathValues(path_vars)
-

A class that returns attributes along a given path intersecting the environmental/vessel mesh.

-
-
-path_requested_variables
-
-
e.g.{‘distance’:{‘processing’:’cumsum’},

‘traveltime’:{‘processing’:’cumsum’}, -‘datetime’:{‘processing’:’cumsum’}, -‘cell_index’:{‘processing’:None}, -‘fuel’:{‘processing’:’cumsum’}}

-
-
-
-
Type:
-

dict

-
-
-
- -
-
-unit_shipspeed
-

‘km/hr’,’knots’

-
-
Type:
-

string

-
-
-
- -
-
-unit_time
-

‘days’,’hr’,’min’,’s

-
-
Type:
-

string

-
-
-
- -
-
Functions:

objective_function - For a list of adjacent cell pairs, start and end waypoints compute path attributes

-
-
-
-
-_waypoint_correction(path_requested_variables, source_graph, Wp, Cp)
-

Applies an in-cell correction to a path segments to determine ‘path_requested_variables’ -defined by the use (e.g. total distance, total traveltime, total fuel usage)

-
-
Input:

path_requested_variable (dict) - A dictionary of the path requested variables -source_graph (dict) - Dictionary of the cell in which the vessel is transiting -Wp (tuple) - Start Waypoint location (long,lat) -Cp (tuple) - End Waypoint location (long,lat)

-
-
-
-
Returns:
-

segment_values (dict) - Dictionary of the segment value information -case (int) - Adjacency case type connecting the two points

-
-
-
- -
-
-objective_function(adjacent_pairs, start_waypoint, end_waypoint)
-

Given a list of adjacent pairs determine the path related information -apply waypoint_correction to get path related information along the path

-
-
Inputs:

adjacent_pairs (list of type find_edge) - A list of the adjacent cell pairs in the form of find_edge -start_waypoint (tuple) - Start waypoint (long,lat) -end_waypoint (tuple) - End waypoint (long,lat)

-
-
-
- -
- -
-
-polar_route.route_planner.crossing_smoothing.dist_around_globe(start_point, crossing_point)
-

Determining the longitude distance around the globe between two points

-
-
Parameters:
-
    -
  • start_point (tuple) – Start Waypoint (long,lat)

  • -
  • crossing_point (tuple) – End Waypoint (long,lat)

  • -
-
-
Returns:
-

longitude distance between the two points in degrees

-
-
Return type:
-

a (float)

-
-
-
- -
-
-polar_route.route_planner.crossing_smoothing.rhumb_line_distance(start_waypoint, end_waypoint)
-

Defining the rhumb line distance from a given waypoint start and end point

-
-
Parameters:
-
    -
  • start_waypoint (list([Long,lat])) – Start Waypoint location with long lat

  • -
  • end_waypoint (list([Long,lat])) – End Waypoint location with long lat

  • -
-
-
Returns:
-

Calculated rhumb line distance

-
-
Return type:
-

distance (float)

-
-
-
- -
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/html/sections/Vehicle_specifics.html b/docs/html/sections/Vehicle_specifics.html deleted file mode 100644 index 3c052244..00000000 --- a/docs/html/sections/Vehicle_specifics.html +++ /dev/null @@ -1,674 +0,0 @@ - - - - - - - 8. Methods - Vessel Performance — polar_route 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

8. Methods - Vessel Performance

-
-

8.1. Vessel Overview

-

All of the functionality that relates to the specific vehicle traversing our meshed environment model is contained within the vessel_performance directory. -This directory contains a VesselPerformanceModeller class that initialises one of the vessel classes in vessels and uses this to determine which cells -in a given mesh are inaccessible for that particular vessel and what its performance will be in each of the accessible cells.

-
-../_images/Mesh_Fuel_Speed.jpg -
-

Maps of the sea ice concentration (a), speed (b) and fuel consumption (c) for the SDA across the Weddell Sea. -The latter two quantities are derived from the former.

-
-
-
-../_images/VesselUML.png -
-

UML Diagram detailing the vessel performance subsystem

-
-
-
-
-

8.2. Vessel Performance Modeller

-
-
-class polar_route.vessel_performance.vessel_performance_modeller.VesselPerformanceModeller(env_mesh_json, vessel_config)
-

Class for modelling the vessel performance. -Takes both an environmental mesh and vessel config as input in json format and modifies the input mesh to -include vessel specifics.

-
-
-__init__(env_mesh_json, vessel_config)
-
-
Parameters:
-
    -
  • env_mesh_json (dict) – a dictionary loaded from an environmental mesh json file

  • -
  • vessel_config (dict) – a dictionary loaded from a vessel config json file

  • -
-
-
-
- -
-
-model_accessibility()
-

Method to determine the accessibility of cells in the environmental mesh and remove inaccessible cells from the -neighbour graph.

-
- -
-
-model_performance()
-

Method to calculate the relevant vessel performance values for each cell in the environmental mesh and update -the mesh accordingly.

-
- -
-
-to_json()
-

Method to return the modified mesh in json format.

-
-
Returns:
-

a dictionary representation of the modified mesh.

-
-
Return type:
-

j_mesh (dict)

-
-
-
- -
- -
-
-

8.3. Vessel Factory

-
-
-class polar_route.vessel_performance.vessel_factory.VesselFactory
-

Factory class to produce initialised vessel objects.

-
-
-classmethod get_vessel(config)
-

Method to return an initialised instance of a vessel class designed for performance modelling

-
-
Parameters:
-

config (dict) – a vessel config dictionary

-
-
Returns:
-

an instance of a vessel class designed for performance modelling

-
-
Return type:
-

vessel

-
-
-
- -
- -
-
-

8.4. Abstract Vessel

-
-
-class polar_route.vessel_performance.abstract_vessel.AbstractVessel(params: dict)
-

Interface to define the abstract methods required for any vessel class to work within the VesselPerformanceModeller.

-
-
-abstract __init__(params: dict)
-

Initialise the vessel object with parameters from the config.

-
-
Parameters:
-

params (dict) – vessel parameters from the vessel config file

-
-
-
- -
-
-abstract model_accessibility(cellbox: AggregatedCellBox)
-

Determine accessibility of the input cell for the given vessel.

-
-
Parameters:
-

cellbox (AggregatedCellBox) – cell in which accessibility is being determined

-
-
Returns:
-

values for the accessibility and other related booleans

-
-
Return type:
-

access_values (dict)

-
-
-
- -
-
-abstract model_performance(cellbox: AggregatedCellBox)
-

Calculate performance parameters for the given vessel.

-
-
Parameters:
-

cellbox (AggregatedCellBox) – cell in which performance is being modelled

-
-
Returns:
-

values for relevant performance parameters

-
-
Return type:
-

performance_values (dict)

-
-
-
- -
- -
-
-

8.5. Abstract Ship

-
-
-class polar_route.vessel_performance.vessels.abstract_ship.AbstractShip(params)
-

Abstract class to define the methods and attributes common to any vessel that is a ship

-
-
-__init__(params)
-
-
Parameters:
-

params (dict) – vessel parameters from the vessel config file

-
-
-
- -
-
-extreme_ice(cellbox)
-

Method to determine if a cell is inaccessible based on configured max ice concentration

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

boolean that is True if the cell is inaccessible due to ice

-
-
Return type:
-

ext_ice (bool)

-
-
-
- -
-
-land(cellbox)
-

Method to determine if a cell is land based on configured minimum depth

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

boolean that is True if the cell is inaccessible due to land

-
-
Return type:
-

land (bool)

-
-
-
- -
-
-model_accessibility(cellbox)
-

Method to determine if a given cell is accessible to the ship

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

boolean values for the modelled accessibility criteria

-
-
Return type:
-

access_values (dict)

-
-
-
- -
-
-model_performance(cellbox)
-

Method to determine the performance characteristics for the ship

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

the value of the modelled performance characteristics for the ship

-
-
Return type:
-

performance_values (dict)

-
-
-
- -
- -
-
-

8.6. SDA

-
-
-class polar_route.vessel_performance.vessels.SDA.SDA(params)
-

Vessel class with methods specifically designed to model the performance of the British Antarctic Survey -research and supply ship, the RRS Sir David Attenborough (SDA)

-
-
-__init__(params)
-
-
Parameters:
-

params (dict) – vessel parameters from the vessel config file

-
-
-
- -
-
-invert_resistance(cellbox)
-

Method to find the vessel speed that keeps the ice resistance force below a given threshold in a given cell

-

The input cellbox should contain the following values

-
-

sic (float) - The average sea ice concentration in the cell as a percentage

-

thickness (float) - The average ice thickness in the cell in m

-

density (float) - The average ice density in the cell in kg/m^3

-
-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

Safe vessel speed in km/h

-
-
Return type:
-

new_speed (float)

-
-
-
- -
-
-model_fuel(cellbox)
-

Method to determine the fuel consumption rate of the SDA in a given cell

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

updated cell with fuel consumption values

-
-
Return type:
-

cellbox (AggregatedCellBox)

-
-
-
- -
-
-model_resistance(cellbox)
-

Method to determine the resistance force acting on the SDA in a given cell

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

updated cell with resistance values

-
-
Return type:
-

cellbox (AggregatedCellBox)

-
-
-
- -
-
-model_speed(cellbox)
-

Method to determine the maximum speed that the SDA can traverse the given cell

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

updated cell with speed values

-
-
Return type:
-

cellbox (AggregatedCellBox)

-
-
-
- -
- -
-
-

8.7. Abstract Glider

-
-
-class polar_route.vessel_performance.vessels.abstract_glider.AbstractGlider(params)
-

Abstract class to model the performance of an underwater glider

-
-
-__init__(params)
-
-
Parameters:
-

params (dict) – vessel parameters from the vessel config file

-
-
-
- -
-
-extreme_ice(cellbox)
-

Method to determine if a cell is inaccessible based on configured max ice concentration

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

boolean that is True if the cell is inaccessible due to ice

-
-
Return type:
-

ext_ice (bool)

-
-
-
- -
-
-land(cellbox)
-

Method to determine if a cell is land based on sea level

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

boolean that is True if the cell is inaccessible due to land

-
-
Return type:
-

land (bool)

-
-
-
- -
-
-model_accessibility(cellbox)
-

Method to determine if a given cell is accessible to the underwater glider

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

boolean values for the modelled accessibility criteria

-
-
Return type:
-

access_values (dict)

-
-
-
- -
-
-model_performance(cellbox)
-

Method to determine the performance characteristics for the underwater glider

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
-
- -
-
-shallow(cellbox)
-

Method to determine if the water in a cell is too shallow for a glider based on configured minimum depth

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

boolean that is True if the cell is too shallow for a glider

-
-
Return type:
-

shallow (bool)

-
-
-
- -
- -
-
-

8.8. Slocum Glider

-
-
-class polar_route.vessel_performance.vessels.slocum.SlocumGlider(params)
-

Vessel class with methods specifically designed to model the performance of the Slocum G2 Glider

-
-
-__init__(params)
-
-
Parameters:
-

params (dict) – vessel parameters from the vessel config file

-
-
-
- -
-
-model_battery(cellbox)
-

Method to determine the rate of battery usage in a given cell in Ah/day

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

updated cell with battery consumption values

-
-
Return type:
-

cellbox (AggregatedCellBox)

-
-
-
- -
-
-model_speed(cellbox)
-

Method to determine the maximum speed that the glider can traverse the given cell

-
-
Parameters:
-

cellbox (AggregatedCellBox) – input cell from environmental mesh

-
-
Returns:
-

updated cell with speed values

-
-
Return type:
-

cellbox (AggregatedCellBox)

-
-
-
- -
- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 545c4b9e..00000000 --- a/docs/index.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - PolarRoute Documentation - - - - - - - - - - - - - - - - -
- If not automatically redirected, click here to load the docs. -
- - - - - \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 1b9ae488..47da3ed4 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -2,16 +2,16 @@ Welcome to the PolarRoute Manual Pages ====================================== PolarRoute is a tool for the optimisation of routes for maritime vehicles travelling in polar waters. -It depends on `MeshiPhi `_; a package designed to +It depends on `MeshiPhi `_; a package designed to discretise the world from heterogeneous data sources (follow link for more details and docs). This software package has been developed by the **British Antarctic Survey** (BAS). It was originally designed primarily for the optimisation of polar routes for the BAS research vessel RRS Sir David Attenborough, however it is applicable to any vessel (e.g. AUVs). The software is written in Python and is open source. -Plotting functionality for the outputs of PolarRoute is provided by the `GeoPlot `_ package, also developed at BAS. +Plotting functionality for the outputs of PolarRoute is provided by the `GeoPlot `_ package, also developed at BAS. For more information on the project, please visit the `PolarRoute website `_ -and follow our `GitHub repository `_. +and follow our `GitHub repository `_. .. note:: The development of this codebase is ongoing and not yet complete. @@ -32,3 +32,4 @@ Contents: ./sections/Vehicle_specifics ./sections/Route_calculation ./sections/Route_optimisation + ./sections/Developer diff --git a/docs/source/sections/Command_line_interface.rst b/docs/source/sections/Command_line_interface.rst index cc00d197..d89d26bd 100644 --- a/docs/source/sections/Command_line_interface.rst +++ b/docs/source/sections/Command_line_interface.rst @@ -22,7 +22,7 @@ positional arguments: config : A configuration file detailing how to build the digital environment. JSON parsable -The format of the required *config.json* file can be found in the `"Configuration - Mesh Construction" `_ section of the `MeshiPhi documentation `_ . +The format of the required *config.json* file can be found in the `"Configuration - Mesh Construction" `_ section of the `MeshiPhi documentation `_ . There are also example configuration files available in the directory :code:`examples/environment_config/grf_example.config.json` optional arguments: @@ -51,7 +51,7 @@ positional arguments: mesh : A digital environment file. The format for the required *vessel.json* file can be found in the :ref:`configuration - vessel performance modeller` section of the documentation. -The required *mesh.json* file can be created using the *create_mesh* command from the `MeshiPhi `_ package. +The required *mesh.json* file can be created using the *create_mesh* command from the `MeshiPhi `_ package. optional arguments are @@ -208,9 +208,9 @@ optional arguments: ^^^^^^^^ Plotting ^^^^^^^^ -Meshes produced at any stage in the route planning process can be visualised using the :code:`plot_mesh` cli command from the `GeoPlot `_ +Meshes produced at any stage in the route planning process can be visualised using the :code:`plot_mesh` cli command from the `GeoPlot `_ library. Meshes and routes can also be plotted in other GIS software such as QGIS by exporting the mesh to a commonly used format such -as .geojson or .tif using the export_mesh command described in the `MeshiPhi `_ docs. +as .geojson or .tif using the export_mesh command described in the `MeshiPhi `_ docs. :: diff --git a/docs/source/sections/Configuration/Configuration_overview.rst b/docs/source/sections/Configuration/Configuration_overview.rst index f96b5c31..a3d6316e 100644 --- a/docs/source/sections/Configuration/Configuration_overview.rst +++ b/docs/source/sections/Configuration/Configuration_overview.rst @@ -11,7 +11,7 @@ route-planning process as command-line arguments or through a Python script. Example configuration files are provided in the `config` directory. -The format of the config used to generate an environmental mesh can be found in the `"Configuration - Mesh Construction" `_ section of the `MeshiPhi documentation `_ . +The format of the config used to generate an environmental mesh can be found in the `"Configuration - Mesh Construction" `_ section of the `MeshiPhi documentation `_ . Descriptions of the configuration options for the Vessel Performance Modelling can be found in the :ref:`Configuration - Vessel Performance Modeller` section of the diff --git a/tests/testing_strategy.md b/docs/source/sections/Developer.rst similarity index 65% rename from tests/testing_strategy.md rename to docs/source/sections/Developer.rst index 74df0252..181df99a 100644 --- a/tests/testing_strategy.md +++ b/docs/source/sections/Developer.rst @@ -1,9 +1,14 @@ -# Testing Strategy -When updating any files within the PolarRoute repository, tests must be run to ensure that the core functionality of the software remains unchanged. To allow for validation of changes, a suite of regression tests have been provided in the folder `tests/regression_tests/...`. These tests attempt to rebuild existing test cases using the changed code and compares these rebuilt outputs to the reference test files. If any differences are found, the tests will fail. +************ +Installation +************ -Evidence that all the required regression tests have passed needs to be submitted as part of a pull request. This should be in the form of a `pytest_output.txt` attached to the pull request. +######################### +Testing +######################### +When updating any files within the PolarRoute repository, tests must be run to ensure that the core functionality of the software remains unchanged. + +To allow for validation of changes, a suite of regression tests have been provided in the folder `tests/regression_tests/...`. These tests attempt to rebuild existing test cases using the changed code and compares these rebuilt outputs to the reference test files. -Pull requests will not be accepted unless all required regression tests pass. ## Vessel Performance Modelling | **Files altered** | **Tests** | @@ -27,13 +32,26 @@ Pull requests will not be accepted unless all required regression tests pass. | `route_planner.py` | | | | | +The smoothing routes tests are slow (>20 mins). For faster testing during development, +you can use the following: -## Testing files -Some updates to PolarRoute may result in changes to meshes calculated in our tests suite (*such as adding additional attributes to the cellbox object*). These changes will cause the test suite to fail, though the mode of failure should be predictable. +``` +# Run only fast tests (skip smoothed routes) +pytest tests/regression_tests/ -m "not slow" + +# Run only slow tests (when needed) +pytest tests/regression_tests/ -m "slow" +``` +We can also run tests in parallel using `pytest-xdist` (see `tests/requirements.txt`). -Details of these failed tests should be submitted as part of the pull request in the form of a `pytest_failures.txt` file, as well as reasoning for a cause of the failures. +For example: -If the changes made are valid, the test files should be updated so-as the tests pass again, and evidence of the updated tests passing also submitted with the pull request. +``` +pytest -n auto tests/regression_tests/ -m "not slow" +``` + +## Testing files +Some updates to PolarRoute may result in changes to meshes calculated in our tests suite (*such as adding additional attributes to the cellbox object*). These changes will cause the test suite to fail, though the mode of failure should be predictable. ### Files diff --git a/docs/source/sections/Examples.rst b/docs/source/sections/Examples.rst index ee57eb60..04b0b7af 100644 --- a/docs/source/sections/Examples.rst +++ b/docs/source/sections/Examples.rst @@ -6,7 +6,7 @@ The CLI provides multiple entry-points through which the PolarRoute package can :ref:`Command Line Interface ` section of these docs. To summarise, the basic process is to create an environment mesh, add vehicle performance characteristics to each -cell in that mesh and then find an optimal route between waypoints located within that mesh. At any stage, `GeoPlot `_ +cell in that mesh and then find an optimal route between waypoints located within that mesh. At any stage, `GeoPlot `_ can be used to visualise the outputs. :: @@ -23,7 +23,7 @@ can be used to visualise the outputs. Above are the commands to run in order to fulfill this process. If you have successfully installed PolarRoute and would -like to try it out, :download:`here` +like to try it out, :download:`here` is some example data which you can use. Simply extract the configs out of the zip archive, and run the commands on the appropriate files. To map the commands to the files in the zip archive: @@ -49,8 +49,8 @@ Here we provide two examples of empty meshes that are simple to process to get y we expect the optimal calculated route to be a straight line between two waypoints. Over long distances this is seen as a great circle arc on the mercator projection that GeoPlot uses to display the mesh. -* :download:`Uniform Mesh` -* :download:`Non-Uniform Mesh` +* :download:`Uniform Mesh` +* :download:`Non-Uniform Mesh` * `See on Google Colab `_ ^^^^^^^^^^^^^^^^^^^^^^ @@ -60,20 +60,20 @@ In this example, we provide synthetic data in the form of Gaussian Random Fields realistic representation of real-world features such as bathymetry. Here we walk through every step involved in PolarRoute, from creating the mesh through to optimising a route within it. -* :download:`Gaussian Random Field data` +* :download:`Gaussian Random Field data` * `Synthetic Data Example `_ ^^^^^^^^^^^^^^^^^ Real Data Example ^^^^^^^^^^^^^^^^^ -Real world data has been used to generate these meshes around the coast of Antarctica. This data is publicly available, +Real world data has been used to generate these meshes around the coast of bas-amop. This data is publicly available, however is not included here to avoid violating data sharing policies. Instead, we provide a mesh file after the 'create_mesh' stage since that is a derived product. The data files used to construct the mesh can be seen in the :code:`data_sources` field of -the config contained within the provided mesh. See `Dataloaders `_ +the config contained within the provided mesh. See `Dataloaders `_ in the MeshiPhi docs for more info on each source of data that PolarRoute currently supports. -* :download:`Real-world data 1` -* :download:`Real-world data 2` +* :download:`Real-world data 1` +* :download:`Real-world data 2` * `Real Data Example `_ ############### @@ -82,10 +82,10 @@ Python Examples Route planning may also be done in a python interpreter. In this case, the CLI is not required but the steps required for route planning follow the same format - create a digital environment; simulated a vessel against it; optimise a route plan through the digital environment. -To perform the steps detailed in this section, a mesh must first be generated using `MeshiPhi `_. +To perform the steps detailed in this section, a mesh must first be generated using `MeshiPhi `_. The files used in the following example are those used in the synthetic example from the notebook section above. Download them -:download:`here`. +:download:`here`. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Creating the digital environment. @@ -106,7 +106,7 @@ Loading configuration from *json* file: The **EnvironmentMesh** object can then be initialised. This mesh object will be constructed using the parameters in its configuration file. This mesh object can then be manipulated further, such as increasing its resolution through further -splitting, adding additional data sources or altering its configuration parameters. See the relevant section of the `MeshiPhi docs `_ +splitting, adding additional data sources or altering its configuration parameters. See the relevant section of the `MeshiPhi docs `_ for a more in-depth explanation. The **EnvironmentMesh** object can then be cast to a json object and saved to a file. :: @@ -130,7 +130,7 @@ for a more in-depth explanation. The **EnvironmentMesh** object can then be cast Simulating a Vessel in a Digital Environment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Once a digital environment **EnvironmentMesh** object has been created with `MeshiPhi `_, +Once a digital environment **EnvironmentMesh** object has been created with `MeshiPhi `_, a vessel's performance when travelling within it may be simulated. The **VesselPerformanceModeller** object requires a digital environment in *json* format and vessel specific configuration parameters, also in *json* format. These may either be loaded from a file, or created within any python interpreter. diff --git a/docs/source/sections/Installation.rst b/docs/source/sections/Installation.rst index 0c3528b6..0787e996 100644 --- a/docs/source/sections/Installation.rst +++ b/docs/source/sections/Installation.rst @@ -2,7 +2,7 @@ Installation ************ -In this section we will outline the necessary steps for installing the PolarRoute software package. Any installation of PolarRoute will also include `MeshiPhi `_; a package designed to +In this section we will outline the necessary steps for installing the PolarRoute software package. Any installation of PolarRoute will also include `MeshiPhi `_; a package designed to discretise the world from heterogeneous data sources. PolarRoute requires a pre-existing installation of Python 3.8 or higher. Installing PolarRoute @@ -16,7 +16,7 @@ PolarRoute is available from PyPI and can be installed by running: For development purposes you can install PolarRoute by downloading the source code from GitHub: :: - git clone https://github.com/Antarctica/PolarRoute + git clone https://github.com/bas-amop/PolarRoute pip install -e ./PolarRoute Use of :code:`-e` is optional, depending on whether you want to be able to edit the installed copy of the package. @@ -29,7 +29,7 @@ In order to run the test suite you will also need to include the `[test]` flag t Installing GeoPlot ##################### -Plotting functionality for the outputs of PolarRoute is provided by the `GeoPlot `_ package, also developed at BAS. +Plotting functionality for the outputs of PolarRoute is provided by the `GeoPlot `_ package, also developed at BAS. Geoplot is available from PyPI and can be installed by running: :: diff --git a/docs/source/sections/Outputs.rst b/docs/source/sections/Outputs.rst index 821d6ac7..231db6b2 100644 --- a/docs/source/sections/Outputs.rst +++ b/docs/source/sections/Outputs.rst @@ -9,7 +9,7 @@ The Vessel_mesh.json file ######################### The vessel performance mesh is an extension to an environmental mesh created by the -`MeshiPhi `_ library. +`MeshiPhi `_ library. Once a discrete mesh environment is created, it is then passed to the vessel performance modeller which applies transformations which are specific to a given vehicle. These vehicle specific values are then encoded into the mesh json object and passed downstream to the route planner. @@ -39,7 +39,7 @@ are then encoded into the mesh json object and passed downstream to the route pl * density (available via dataloaders: *density*) * u10, v10 (available via dataloaders: *era5_wind*) - see section **Dataloader Overview** in the `MeshiPhi docs `_ for more information on dataloaders + see section **Dataloader Overview** in the `MeshiPhi docs `_ for more information on dataloaders The vessel performance modeller will still run without these attributes but will assign default values from the configuration file where any data is missing. diff --git a/examples/README.md b/examples/README.md index a6270500..7c73f65c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,6 @@ # Examples This directory contains example configs that can be used with PolarRoute. Please make sure you have PolarRoute properly installed before attempting to run these examples. -You will also need GeoPlot installed to run the `plot_mesh` command, however it is not necessary if all you need is the ability to generate meshes. See `https://github.com/antarctica/geoplot` for more instructions. +You will also need GeoPlot installed to run the `plot_mesh` command, however it is not necessary if all you need is the ability to generate meshes. See `https://github.com/bas-amop/geoplot` for more instructions. To run PolarRoute, choose one config out of each directory and execute commands in this order: diff --git a/examples/vessel_config/BoatyMcBoatFace.config.json b/examples/vessel_config/BoatyMcBoatFace.config.json new file mode 100644 index 00000000..977c9c69 --- /dev/null +++ b/examples/vessel_config/BoatyMcBoatFace.config.json @@ -0,0 +1,7 @@ +{ + "vessel_type": "BoatyMcBoatFace", + "max_speed": 3.6, + "unit": "km/hr", + "max_ice_conc": 10, + "min_depth": 10 +} \ No newline at end of file diff --git a/examples/vessel_config/Slocum.config.json b/examples/vessel_config/Slocum.config.json new file mode 100644 index 00000000..6a17d1bc --- /dev/null +++ b/examples/vessel_config/Slocum.config.json @@ -0,0 +1,7 @@ +{ + "vessel_type": "Slocum", + "max_speed": 1.25, + "unit": "km/hr", + "max_ice_conc": 10, + "min_depth": 10 +} \ No newline at end of file diff --git a/polar_route/__init__.py b/polar_route/__init__.py index 0d1f54ba..3338ffaa 100644 --- a/polar_route/__init__.py +++ b/polar_route/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.1.8" +__version__ = "1.1.9" __description__ = "PolarRoute: Long-distance maritime polar route planning taking into account complex changing environmental conditions" __license__ = "MIT" __author__ = "Autonomous Marine Operations Planning (AMOP) Team, AI Lab, British Antarctic Survey" diff --git a/polar_route/cli.py b/polar_route/cli.py index d5fcb8c1..01c2d90f 100644 --- a/polar_route/cli.py +++ b/polar_route/cli.py @@ -6,6 +6,9 @@ import pandas as pd import geopandas as gpd +# Module logger +logger = logging.getLogger(__name__) + from meshiphi.mesh_generation.mesh_builder import MeshBuilder from polar_route import __version__ as version @@ -98,7 +101,7 @@ def resimulate_vehicle_cli(): default_output = "resimulate_vehicle_output.vessel.json" args = get_args(default_output, mesh_arg=True, config_arg=False) - logging.info("{} {}".format(inspect.stack()[0][3][:-4], version)) + logger.info("{} {}".format(inspect.stack()[0][3][:-4], version)) mesh_json = json.load(args.mesh) mesh_config = mesh_json['config']['mesh_info'] @@ -115,7 +118,7 @@ def resimulate_vehicle_cli(): rebuilt_mesh_json = vp.to_json() # Saving output - logging.info(f"Saving mesh to {args.output}") + logger.info(f"Saving mesh to {args.output}") with open(args.output, 'w+') as fp: json.dump(rebuilt_mesh_json, fp, indent=4) @@ -128,7 +131,7 @@ def add_vehicle_cli(): default_output = "add_vehicle_output.vessel.json" args = get_args(default_output, config_arg=True, mesh_arg=True) - logging.info("{} {}".format(inspect.stack()[0][3][:-4], version)) + logger.info("{} {}".format(inspect.stack()[0][3][:-4], version)) mesh_json = json.load(args.mesh) vessel_config = json.load(args.config) @@ -138,7 +141,7 @@ def add_vehicle_cli(): vp.model_performance() info = vp.to_json() - logging.info(f"Saving vp mesh to {args.output}") + logger.info(f"Saving vp mesh to {args.output}") with open(args.output, 'w+') as fp: json.dump(info, fp, indent=4) @@ -150,9 +153,9 @@ def optimise_routes_cli(): """ args = get_args("optimise_routes_output.route.json", config_arg=True, mesh_arg=True ,waypoints_arg= True) - logging.info("{} {}".format(inspect.stack()[0][3][:-4], version)) + logger.info("{} {}".format(inspect.stack()[0][3][:-4], version)) - logging.info("Initialising Route Planner") + logger.info("Initialising Route Planner") # Initialise the route planner rp = RoutePlanner(args.mesh.name, args.config.name) @@ -166,7 +169,7 @@ def optimise_routes_cli(): waypoints_df = pd.read_csv(args.waypoints.name) mesh_json['waypoints'] = waypoints_df.to_dict() - logging.info("Calculating Dijkstra routes") + logger.info("Calculating Dijkstra routes") dijkstra_routes = rp.compute_routes(args.waypoints.name) # Update mesh if splitting around waypoints @@ -185,7 +188,7 @@ def optimise_routes_cli(): dijkstra_output_file_strs[-2] += '_dijkstra' dijkstra_output_file = '.'.join(dijkstra_output_file_strs) - logging.info(f"\tOutputting dijkstra path to {dijkstra_output_file}") + logger.info(f"\tOutputting dijkstra path to {dijkstra_output_file}") with open(dijkstra_output_file, 'w+') as fp: json.dump(info_dijkstra, fp, indent=4) @@ -193,18 +196,18 @@ def optimise_routes_cli(): if args.path_geojson: dijkstra_output_file_strs[-1] = 'geojson' dijkstra_output_file = '.'.join(dijkstra_output_file_strs) - logging.info(f"\tExtracting standalone dijkstra path GeoJSON to {dijkstra_output_file}") + logger.info(f"\tExtracting standalone dijkstra path GeoJSON to {dijkstra_output_file}") with open(dijkstra_output_file, 'w+') as fp: json.dump(info_dijkstra['paths'], fp, indent=4) - logging.info("Calculating smoothed routes") + logger.info("Calculating smoothed routes") smoothed_routes = rp.compute_smoothed_routes() info = mesh_json info['paths'] = smoothed_routes - logging.info(f"\tOutputting smoothed route(s) to {output_file}") + logger.info(f"\tOutputting smoothed route(s) to {output_file}") with open(output_file, 'w+') as fp: json.dump(info, fp, indent=4) @@ -213,30 +216,30 @@ def optimise_routes_cli(): # Create GeoJSON filename output_file_strs[-1] = 'geojson' output_file = '.'.join(output_file_strs) - logging.info(f"\tExtracting standalone path GeoJSON to {output_file}") + logger.info(f"\tExtracting standalone path GeoJSON to {output_file}") with open(output_file, 'w+') as fp: json.dump(info['paths'], fp, indent=4) # Optional output of smoothed route(s) to standalone KML file(s) if args.path_kml: - logging.info(f"\tExtracting standalone path(s) to KML file(s)") + logger.info(f"\tExtracting standalone path(s) to KML file(s)") for route in smoothed_routes['features']: from_wp = route["properties"]["from"].replace(" ", "_") to_wp = route["properties"]["to"].replace(" ", "_") route_output_str = '.'.join(output_file_strs[:-1]) + "_" + from_wp + to_wp + ".kml" gdf = gpd.GeoDataFrame.from_features([route]) - logging.info(f"Saving route to {route_output_str}") + logger.info(f"Saving route to {route_output_str}") gdf['geometry'].to_file(route_output_str, "KML") # Optional output of smoothed route(s) to standalone GPX file(s) if args.path_gpx: - logging.info(f"\tExtracting standalone path(s) to GPX file(s)") + logger.info(f"\tExtracting standalone path(s) to GPX file(s)") for route in smoothed_routes['features']: from_wp = route["properties"]["from"].replace(" ", "_") to_wp = route["properties"]["to"].replace(" ", "_") route_output_str = '.'.join(output_file_strs[:-1]) + "_" + from_wp + to_wp + ".gpx" gdf = gpd.GeoDataFrame.from_features([route]) - logging.info(f"Saving route to {route_output_str}") + logger.info(f"Saving route to {route_output_str}") gdf['geometry'].to_file(route_output_str, "GPX") # Optional output of Chart Track formatted csv @@ -250,7 +253,7 @@ def optimise_routes_cli(): for i, csv_str in enumerate(csv_strs): output_file_strs[1] = f'r{i}' output_file = '.'.join(output_file_strs) - logging.info(f"\tOutputting ChartTracker CSV to {output_file}") + logger.info(f"\tOutputting ChartTracker CSV to {output_file}") with open(output_file, 'w+') as fp: fp.write(csv_str) @@ -265,7 +268,7 @@ def extract_routes_cli(): output_file_strs = output_file.split('.') route_file = json.load(args.mesh) - logging.info(f"Extracting routes from: {args.mesh.name} with base output: {args.output}") + logger.info(f"Extracting routes from: {args.mesh.name} with base output: {args.output}") # Check if input is just a route file or if the routes are nested within a mesh if route_file.get("type") == "FeatureCollection": @@ -276,7 +279,7 @@ def extract_routes_cli(): else: routes = [] - logging.info(f"{len(routes)} routes found in mesh") + logger.info(f"{len(routes)} routes found in mesh") if output_file_strs[-1] in ["json", "geojson"]: geojson_outputs = extract_geojson_routes(route_file) @@ -288,42 +291,42 @@ def extract_routes_cli(): to_wp = route["properties"]["to"].replace(" ", "_") route_output_str = '.'.join(output_file_strs[:-1]) + "_" + from_wp + to_wp + "." + output_file_strs[-1] - logging.info(f"Saving route to {route_output_str}") + logger.info(f"Saving route to {route_output_str}") with open(route_output_str, "w") as f: json.dump(geojson_output, f, indent=4) elif output_file_strs[-1] == "gpx": - logging.info("Extracting routes in gpx format") + logger.info("Extracting routes in gpx format") for route in routes: from_wp = route["properties"]["from"].replace(" ", "_") to_wp = route["properties"]["to"].replace(" ", "_") route_output_str = '.'.join(output_file_strs[:-1]) + "_" + from_wp + to_wp + ".gpx" - gdf = gpd.GeoDataFrame.from_features([route]) - logging.info(f"Saving route to {route_output_str}") + gdf = gpd.GeoDataFrame.from_features([geojson_output]) + logger.info(f"Saving route to {route_output_str}") gdf['geometry'].to_file(route_output_str, "GPX") elif output_file_strs[-1] == "kml": - logging.info("Extracting routes in kml format") + logger.info("Extracting routes in kml format") for route in routes: from_wp = route["properties"]["from"].replace(" ", "_") to_wp = route["properties"]["to"].replace(" ", "_") route_output_str = '.'.join(output_file_strs[:-1]) + "_" + from_wp + to_wp + ".kml" - gdf = gpd.GeoDataFrame.from_features([route]) - logging.info(f"Saving route to {route_output_str}") + gdf = gpd.GeoDataFrame.from_features([geojson_output]) + logger.info(f"Saving route to {route_output_str}") gdf['geometry'].to_file(route_output_str, "KML") elif output_file_strs[-1] == "csv": - logging.info("Extracting routes in ChartTrack csv format") + logger.info("Extracting routes in ChartTrack csv format") for route in routes: from_wp = route["properties"]["from"].replace(" ", "_") to_wp = route["properties"]["to"].replace(" ", "_") route_output_str = '.'.join(output_file_strs[:-1]) + "_" + from_wp + to_wp + ".csv" csv_route = to_chart_track_csv(route) - logging.info(f"Saving route to {route_output_str}") + logger.info(f"Saving route to {route_output_str}") with open(route_output_str, "w") as f: f.write(csv_route) else: - logging.warning("Unrecognised output type! No routes have been extracted!") + logger.warning("Unrecognised output type! No routes have been extracted!") @timed_call @@ -333,9 +336,9 @@ def calculate_route_cli(): """ args = get_args("calculated_route.json", config_arg = False, mesh_arg = True, waypoints_arg = True) - logging.info("{} {}".format(inspect.stack()[0][3][:-4], version)) + logger.info("{} {}".format(inspect.stack()[0][3][:-4], version)) - logging.info(f"Calculating the cost of route {args.waypoints.name} from mesh {args.mesh.name}") + logger.info(f"Calculating the cost of route {args.waypoints.name} from mesh {args.mesh.name}") df, from_wp, to_wp, route_type = load_route(route_file = args.waypoints.name) @@ -348,8 +351,8 @@ def calculate_route_cli(): max_time = convert_decimal_days(calc_route["features"][0]["properties"]["traveltime"][-1]) max_fuel = round(calc_route["features"][0]["properties"]["fuel"][-1],2) - logging.info(f"Calculated route has travel time: {max_time} and fuel cost: {max_fuel} tons") + logger.info(f"Calculated route has travel time: {max_time} and fuel cost: {max_fuel} tons") - logging.info(f"Saving calculated route to {args.output}") + logger.info(f"Saving calculated route to {args.output}") with open(args.output, 'w+') as f: json.dump(calc_route, f, indent=4) \ No newline at end of file diff --git a/polar_route/route_calc.py b/polar_route/route_calc.py index e1e6815f..c56b81fd 100644 --- a/polar_route/route_calc.py +++ b/polar_route/route_calc.py @@ -3,6 +3,9 @@ import numpy as np import pandas as pd import geopandas as gpd + +# Module logger +logger = logging.getLogger(__name__) from shapely import wkt, distance from shapely.geometry import Point, LineString, MultiLineString, Polygon from polar_route.utils import gpx_route_import @@ -117,7 +120,7 @@ def load_route(route_file): route_type(str):Type of route, either 'smoothed' or 'dijkstra' """ - logging.info(f"Loading route from: {route_file}") + logger.info(f"Loading route from: {route_file}") # Loading route from csv file if route_file[-3:] == "csv": df = pd.read_csv(route_file) @@ -164,12 +167,12 @@ def load_route(route_file): df['Lat'] = lats route_type = "smoothed" else: - logging.warning("Invalid route input! Please supply either a csv, gpx or geojson file with the route waypoints.") + logger.warning("Invalid route input! Please supply either a csv, gpx or geojson file with the route waypoints.") return None - logging.info(f"Route start waypoint: {from_wp}") - logging.info(f"Route end waypoint: {to_wp}") - logging.debug(f"Route has {len(df)} waypoints") + logger.info(f"Route start waypoint: {from_wp}") + logger.info(f"Route end waypoint: {to_wp}") + logger.debug(f"Route has {len(df)} waypoints") df['id'] = 1 df['order'] = np.arange(len(df)) return df, from_wp, to_wp, route_type @@ -184,14 +187,14 @@ def load_mesh(mesh_file): Returns: mesh (GeoDataFrame): Mesh in GeoDataFrame format """ - logging.info(f"Loading mesh from: {mesh_file}") + logger.info(f"Loading mesh from: {mesh_file}") # Loading mesh information with open(mesh_file, 'r') as fp: info = json.load(fp) mesh = pd.DataFrame(info['cellboxes']) if (not any('uC' in cb for cb in mesh)) or (not any('vC' in cb for cb in mesh)): - logging.info("No data for currents in mesh, setting default value to zero!") + logger.info("No data for currents in mesh, setting default value to zero!") mesh['geometry'] = mesh['geometry'].apply(wkt.loads) mesh = gpd.GeoDataFrame(mesh, crs='EPSG:4326', geometry='geometry') @@ -328,7 +331,7 @@ def route_calc(df, from_wp, to_wp, mesh, route_type): if region_poly.contains(Point((df.iloc[idx]['Long'],df.iloc[idx]['Lat']))): continue else: - logging.warning(f"Mesh does not contain waypoint located at Lat: {df.iloc[idx]['Lat']} " + logger.warning(f"Mesh does not contain waypoint located at Lat: {df.iloc[idx]['Lat']} " f"Long: {df.iloc[idx]['Long']} !") return None @@ -337,7 +340,7 @@ def route_calc(df, from_wp, to_wp, mesh, route_type): # Loop through crossing points to order them into a track along the route user_track = order_track(df, track_points) - logging.debug(f"Route has {len(user_track)} crossing points") + logger.debug(f"Route has {len(user_track)} crossing points") # Initialise segment costs with zero values at start point of path traveltimes = [0.0] @@ -348,9 +351,9 @@ def route_calc(df, from_wp, to_wp, mesh, route_type): # Calculate cost of each segment in the path # Putting logging here so it only triggers once per route if dijkstra_route: - logging.info('Calculating traveltime and distance using dijkstra metric') + logger.info('Calculating traveltime and distance using dijkstra metric') else: - logging.info('Calculating traveltime and distance using smoothed metric') + logger.info('Calculating traveltime and distance using smoothed metric') for idx in range(len(user_track)-1): start_point = np.array((user_track['Point'].iloc[idx].xy[0][0], user_track['Point'].iloc[idx].xy[1][0])) @@ -359,9 +362,9 @@ def route_calc(df, from_wp, to_wp, mesh, route_type): case = case_from_angle(start_point, end_point) # Check for inaccessible cells on user defined route if cell_box['inaccessible']: - logging.warning(f"This route crosses an inaccessible cell! Cell located at Lat: {cell_box['cy']} " + logger.warning(f"This route crosses an inaccessible cell! Cell located at Lat: {cell_box['cy']} " f"Long: {cell_box['cx']}") - logging.info("Trying with speed and fuel from previous cells, reroute for more accurate results") + logger.info("Trying with speed and fuel from previous cells, reroute for more accurate results") i = 0 # Go back along path to find previous accessible cell while cell_box['inaccessible']: @@ -377,7 +380,7 @@ def route_calc(df, from_wp, to_wp, mesh, route_type): cases.append(case) - logging.debug(f"Route crosses {len(set([c['id'] for c in cellboxes]))} different cellboxes") + logger.debug(f"Route crosses {len(set([c['id'] for c in cellboxes]))} different cellboxes") # Find cumulative values along path path_points = user_track['Point'] path_traveltimes = np.cumsum(traveltimes) diff --git a/polar_route/route_planner/crossing.py b/polar_route/route_planner/crossing.py index b2913850..3caf7471 100644 --- a/polar_route/route_planner/crossing.py +++ b/polar_route/route_planner/crossing.py @@ -6,6 +6,9 @@ """ import numpy as np import logging + +# Module logger +logger = logging.getLogger(__name__) from polar_route.utils import unit_time, unit_speed np.seterr(divide='ignore', invalid='ignore') @@ -434,12 +437,12 @@ def value(self): elif abs(self.case)==1 or abs(self.case)==3: travel_time, cross_points, cell_points = self._corner() else: - logging.debug('---> Issue with cell (Xsc,Ysc)={:.2f};{:.2f}'.\ + logger.debug('---> Issue with cell (Xsc,Ysc)={:.2f};{:.2f}' + \ format(self.source_cellbox.get_bounds().getcx(),self.source_cellbox.get_bounds().getcy())) travel_time = [np.inf, np.inf] cross_points = [np.nan, np.nan] cell_points = [np.nan, np.nan] - logging.debug(f"NewtonianDistance.value >> TravelTime >> {travel_time}" ) + logger.debug(f"NewtonianDistance.value >> TravelTime >> {travel_time}" ) return travel_time, cross_points, cell_points, self.case diff --git a/polar_route/route_planner/crossing_smoothing.py b/polar_route/route_planner/crossing_smoothing.py index 7c8fb576..08f60e94 100644 --- a/polar_route/route_planner/crossing_smoothing.py +++ b/polar_route/route_planner/crossing_smoothing.py @@ -1,6 +1,9 @@ import numpy as np import pyproj import logging + +# Module logger +logger = logging.getLogger(__name__) import shapely from polar_route.utils import unit_time, unit_speed, case_from_angle from polar_route.exceptions import RouteSmoothingError @@ -1237,7 +1240,7 @@ def previous_vs(self, edge_a, edge_b, midpoint_prime): # Prevents crashing is edge_b is empty. if edge_b.start is None: - logging.debug('Edge_b is empty') + logger.debug('Edge_b is empty') return True edge_a_start_index = edge_a.start['id'] @@ -1540,7 +1543,7 @@ def forward(self): # Introduction of a U-shape if len(add_indices) == 2: - logging.debug('--- Adding in U-shape ---') + logger.debug('--- Adding in U-shape ---') target_a = add_indices[0] target_b = add_indices[1] case_a = add_cases[0] diff --git a/polar_route/route_planner/route.py b/polar_route/route_planner/route.py index 77a5bca0..39512ff1 100644 --- a/polar_route/route_planner/route.py +++ b/polar_route/route_planner/route.py @@ -2,6 +2,9 @@ import json import numpy as np import geopandas as gpd + +# Module logger +logger = logging.getLogger(__name__) from polar_route.route_planner.crossing import traveltime_in_cell from polar_route.utils import unit_time, unit_speed, case_from_angle from meshiphi.utils import longitude_domain @@ -133,7 +136,7 @@ def save(self, file_path): """ Saves the constructed route to the given file location in the given format """ - logging.info(f"Saving route to {file_path}") + logger.info(f"Saving route to {file_path}") file_path_strs = file_path.split('.') if file_path_strs[-1] in ["json", "geojson"]: @@ -157,8 +160,8 @@ def waypoint_correction(self, cellbox, wp, cp, indx): indx (int): the index of the segment along the route """ direction = [1, 2, 3, 4, -1, -2, -3, -4] - logging.debug(f"WP_correction >> wp >> {wp.to_point()}") - logging.debug(f"WP_correction >> cp >> {cp.to_point()}") + logger.debug(f"WP_correction >> wp >> {wp.to_point()}") + logger.debug(f"WP_correction >> cp >> {cp.to_point()}") m_long = 111.321*1000 m_lat = 111.386*1000 x = (cp.get_longitude() - wp.get_longitude()) * m_long * np.cos(wp.get_latitude() * (np.pi / 180)) @@ -172,9 +175,9 @@ def waypoint_correction(self, cellbox, wp, cp, indx): sv = cellbox.agg_data['vC'] ssp = unit_speed(cellbox.agg_data['speed'][direction.index(case)], self.conf['unit_shipspeed']) traveltime, distance = traveltime_in_cell(x, y, su, sv, ssp, tt_dist=True) - logging.debug(f"WP_correction >> tt >> {traveltime}") - logging.debug(f"WP_correction >> distance >> {distance}") - logging.debug(f"WP_correction >> case >> {case}") + logger.debug(f"WP_correction >> tt >> {traveltime}") + logger.debug(f"WP_correction >> distance >> {distance}") + logger.debug(f"WP_correction >> case >> {case}") traveltime = unit_time(traveltime, self.conf['time_unit']) # update segment and its metrics @@ -183,10 +186,10 @@ def waypoint_correction(self, cellbox, wp, cp, indx): self.segments[indx].set_distance(distance) if 'fuel' in self.conf['path_variables']: self.segments[indx].set_fuel(cellbox.agg_data['fuel'][direction.index(case)] * traveltime) - logging.debug(f"WP_correction >> fuel >> {cellbox.agg_data['fuel'][direction.index(case)] * traveltime}") + logger.debug(f"WP_correction >> fuel >> {cellbox.agg_data['fuel'][direction.index(case)] * traveltime}") if 'battery' in self.conf['path_variables']: self.segments[indx].set_battery(cellbox.agg_data['battery'][direction.index(case)] * traveltime) - logging.debug(f"WP_correction >> battery >> {cellbox.agg_data['battery'][direction.index(case)] * traveltime}") + logger.debug(f"WP_correction >> battery >> {cellbox.agg_data['battery'][direction.index(case)] * traveltime}") def get_points(self): """ diff --git a/polar_route/route_planner/route_planner.py b/polar_route/route_planner/route_planner.py index dd206ecc..11d7885f 100644 --- a/polar_route/route_planner/route_planner.py +++ b/polar_route/route_planner/route_planner.py @@ -12,6 +12,9 @@ import copy import math +# Module logger +logger = logging.getLogger(__name__) + from polar_route.route_planner.route import Route from polar_route.route_planner.source_waypoint import SourceWaypoint from polar_route.route_planner.waypoint import Waypoint @@ -70,10 +73,10 @@ def _adjust_waypoints(point, cellboxes, max_distance=5): cb_polygon = wkt.loads(nearest_cb['geometry']) if point.within(cb_polygon): - logging.debug(f'({point.y},{point.x}) in accessible cellbox') + logger.debug(f'({point.y},{point.x}) in accessible cellbox') return point else: - logging.debug(f'({point.y},{point.x}) not in accessible cellbox') + logger.debug(f'({point.y},{point.x}) not in accessible cellbox') # Create a line between CB centre and point cb_centre = Point([nearest_cb['cx'], nearest_cb['cy']]) connecting_line = LineString([point, cb_centre]) @@ -83,7 +86,7 @@ def _adjust_waypoints(point, cellboxes, max_distance=5): # Put limit on how far it's allowed to adjust waypoint distance_away = connecting_line.length - intersecting_line.length if distance_away > max_distance: - logging.info(f'Waypoint too far from accessible cellbox!') + logger.info(f'Waypoint too far from accessible cellbox!') return point # Find where it meets the cellbox boundary @@ -94,8 +97,8 @@ def _adjust_waypoints(point, cellboxes, max_distance=5): adjusted_point = buffered_point.exterior.intersection(intersecting_line) # Interior point is now a point inside the cellbox # that is not on the boundary - logging.info(f'({point.y},{point.x}) not accessible cellbox') - logging.info(f'Adjusted to ({adjusted_point.y},{adjusted_point.x})') + logger.info(f'({point.y},{point.x}) not accessible cellbox') + logger.info(f'Adjusted to ({adjusted_point.y},{adjusted_point.x})') return adjusted_point @@ -280,7 +283,7 @@ def __init__(self, mesh_file, config_file, cost_func=NewtonianDistance): f'route planner!') # Check for SIC data, used in smoothed route construction if not any('SIC' in cb.agg_data for cb in self.cellboxes_lookup.values()): - logging.debug('The environment mesh does not have SIC data') + logger.debug('The environment mesh does not have SIC data') # Check if speed is defined in the environment mesh if not any('speed' in cb.agg_data for cb in self.cellboxes_lookup.values()): @@ -316,7 +319,7 @@ def _splitting_around_waypoints(self, waypoints_df): """ if ('waypoint_splitting' in self.config) and (self.config['waypoint_splitting']): - logging.info(' Splitting around waypoints !') + logger.info(' Splitting around waypoints !') prior_cells = [cellbox.get_id() for cellbox in self.env_mesh.agg_cellboxes if not cellbox.agg_data['inaccessible']] wps_points = [(entry['Lat'], entry['Long']) for _, entry in waypoints_df.iterrows()] self.env_mesh.split_points(wps_points) @@ -340,7 +343,7 @@ def _zero_currents(self, mesh): # Zeroing currents if both vectors are defined and zeroed if ('zero_currents' in self.config) and ("vector_names" in self.config): if self.config['zero_currents']: - logging.info('Zero currents set in config for this mesh!') + logger.info('Zero currents set in config for this mesh!') for idx, cell in enumerate(mesh['cellboxes']): cell[self.config['vector_names'][0]] = 0.0 cell[self.config['vector_names'][1]] = 0.0 @@ -349,7 +352,7 @@ def _zero_currents(self, mesh): # If no vectors are defined then add zero currents to mesh if 'vector_names' not in self.config: self.config['vector_names'] = ['Vector_x', 'Vector_y'] - logging.info('No vector_names defined in config. Zeroing currents in mesh !') + logger.info('No vector_names defined in config. Zeroing currents in mesh !') for idx, cell in enumerate(mesh['cellboxes']): cell[self.config['vector_names'][0]] = 0.0 cell[self.config['vector_names'][1]] = 0.0 @@ -370,7 +373,7 @@ def _fixed_speed(self, mesh): # Setting speed to a fixed value if specified in the config if 'fixed_speed' in self.config: if self.config['fixed_speed']: - logging.info('Setting all speeds to max speed for this mesh!') + logger.info('Setting all speeds to max speed for this mesh!') max_speed = mesh['config']['vessel_info']['max_speed'] for idx, cell in enumerate(mesh['cellboxes']): if 'speed' in cell.keys(): @@ -410,7 +413,7 @@ def _dijkstra_routes(self, start_waypoints, end_waypoints): if s_wp.equals(e_wp): # No need to log this for the "route" from a waypoint to itself if s_wp.get_name() != e_wp.get_name(): - logging.info(f"Route from {s_wp.get_name()} to {e_wp.get_name()} not calculated, these waypoints" + logger.info(f"Route from {s_wp.get_name()} to {e_wp.get_name()} not calculated, these waypoints" f" are identical") continue route_segments = [] @@ -424,12 +427,11 @@ def _dijkstra_routes(self, start_waypoints, end_waypoints): route.source_waypoint = s_wp else: while s_wp.get_cellbox_indx() != e_wp_indx: - # logging.debug(">>> s_wp_indx >>>", s_wp) - # logging.debug(">>> e_wp_indx >>>", e_wp_indx) + routing_info = s_wp.get_routing_info(e_wp_indx) # If no route found break out of loop and skip this case if routing_info.get_node_index() == -1: - logging.warning(f'{s_wp.get_name()} to {e_wp.get_name()} - Failed to construct Dijkstra route') + logger.warning(f'{s_wp.get_name()} to {e_wp.get_name()} - Failed to construct Dijkstra route') no_route_found = True break # Insert segments at the front of the list as we are moving from e_wp to s_wp @@ -446,19 +448,19 @@ def _dijkstra_routes(self, start_waypoints, end_waypoints): for x in range(2): cases.insert(0, neighbour_case) e_wp_indx = routing_info.get_node_index() - logging.debug(f"route segments >> {route_segments[0][0].to_str()}") + logger.debug(f"route segments >> {route_segments[0][0].to_str()}") route_segments = list(itertools.chain.from_iterable(route_segments)) route = Route(route_segments, s_wp.get_name(), e_wp.get_name(), self.config) route.source_waypoint = s_wp route.set_cases(cases) for s in route_segments: - logging.debug(f">>>|S|>>>> {s.to_str()}") + logger.debug(f">>>|S|>>>> {s.to_str()}") if no_route_found: continue - logging.debug(route.segments[0].get_start_wp().get_cellbox_indx()) + logger.debug(route.segments[0].get_start_wp().get_cellbox_indx()) # Correct the first and last segment of the route route.waypoint_correction(self.cellboxes_lookup[route.segments[0].get_start_wp().get_cellbox_indx()], s_wp, route.segments[0].get_end_wp(), 0) @@ -468,7 +470,7 @@ def _dijkstra_routes(self, start_waypoints, end_waypoints): route.waypoint_correction(self.cellboxes_lookup[route.segments[-1].get_end_wp().get_cellbox_indx()], e_wp, route.segments[-1].get_start_wp(), -1) routes.append(route) - logging.debug(route.to_json(route_type='dijkstra')) + logger.debug(route.to_json(route_type='dijkstra')) return routes @@ -524,7 +526,7 @@ def consider_neighbours(source_wp, _id): while not run_all: # Determine the index of the cell with the minimum objective function cost that has not yet been visited min_obj_indx = find_min_objective(wp) - logging.debug(f"min_obj >>> {min_obj_indx}") + logger.debug(f"min_obj >>> {min_obj_indx}") # If min_obj_indx is -1 then no route possible, and we stop search for this waypoint if min_obj_indx == -1: break @@ -534,7 +536,7 @@ def consider_neighbours(source_wp, _id): while not (wp.is_all_visited() and wp.is_all_cells_visited(self._required_nodes)): # Determine the index of the cell with the minimum objective function cost that has not yet been visited min_obj_indx = find_min_objective(wp) - logging.debug(f"min_obj >>> {min_obj_indx}") + logger.debug(f"min_obj >>> {min_obj_indx}") # If min_obj_indx is -1 then no route possible, and we stop search for this waypoint if min_obj_indx == -1: break @@ -566,7 +568,7 @@ def _neighbour_cost(self, node_id, neighbour_id, case): not math.isnan(traveltime[1]): self.neighbour_legs[node_id+"to"+neighbour_id] = (traveltime, crossing_points) else: - logging.debug(f"Travel time is NaN for {node_id} to {neighbour_id}") + logger.debug(f"Travel time is NaN for {node_id} to {neighbour_id}") # Create segments and set their travel time based on the returned 3 points and the remaining obj accordingly (travel_time * node speed/fuel) s1 = Segment(Waypoint.load_from_cellbox(self.cellboxes_lookup[node_id]), Waypoint(crossing_points[1], @@ -623,19 +625,19 @@ def compute_routes(self, waypoints): point = Point([row['Long'], row['Lat']]) # Only allow waypoints that lie within the bounds of the env_mesh if not point.within(mesh_boundary): - logging.info(f"Waypoint {row['Name']} at Lat: {row['Lat']}, Long {row['Long']} is outside the mesh and " + logger.info(f"Waypoint {row['Name']} at Lat: {row['Lat']}, Long {row['Long']} is outside the mesh and " f"will be disregarded!") waypoints_df.drop(idx) continue # Move waypoint to the closest accessible cellbox, if it isn't in one already if self.config['adjust_waypoints']: - logging.debug("Adjusting waypoints in inaccessible cells to nearest accessible location") + logger.debug("Adjusting waypoints in inaccessible cells to nearest accessible location") adjusted_point = _adjust_waypoints(point, self.env_mesh.to_json()['cellboxes']) waypoints_df.loc[idx, 'Long'] = adjusted_point.x waypoints_df.loc[idx, 'Lat'] = adjusted_point.y else: - logging.debug("Skipping waypoint adjustment") + logger.debug("Skipping waypoint adjustment") # Split around waypoints if specified in the config self._splitting_around_waypoints(waypoints_df) @@ -654,15 +656,15 @@ def compute_routes(self, waypoints): if not end_wps: raise NoRouteFoundError('Invalid waypoints. No accessible destination waypoints specified') - logging.info('============= Dijkstra Route Creation ============') - logging.info(f" - Objective = {self.config['objective_function']}") + logger.info('============= Dijkstra Route Creation ============') + logger.info(f" - Objective = {self.config['objective_function']}") for wp in src_wps: - logging.info('--- Processing Source Waypoint = {}'.format(wp.get_name())) + logger.info('--- Processing Source Waypoint = {}'.format(wp.get_name())) self._dijkstra(wp, end_wps) # Using Dijkstra graph compute route and meta information to all end_waypoints routes = self._dijkstra_routes(src_wps, end_wps) - logging.info("Dijkstra routing complete...") + logger.info("Dijkstra routing complete...") self.routes_dijkstra = routes # Returning the constructed routes return routes @@ -689,10 +691,10 @@ def compute_smoothed_routes(self): converged_sep = self.config.get('smoothing_converged_sep', 1e-3) objective_function = self.config.get('objective_function', 'traveltime') - logging.debug(f"Blocking metric: {blocked_metric}") - logging.debug(f"Blocking threshold: {blocked_percentage}") + logger.debug(f"Blocking metric: {blocked_metric}") + logger.debug(f"Blocking threshold: {blocked_percentage}") - logging.info('========= Determining Smoothed Routes ===========') + logger.info('========= Determining Smoothed Routes ===========') geojson = {} smoothed_routes = [] @@ -705,7 +707,7 @@ def compute_smoothed_routes(self): # Handle straight line route within same cell if len(route_json['properties']['CellIndices']) == 1: - logging.info(f"--- Skipping smoothing for {route_json['properties']['name']}, direct route within a" + logger.info(f"--- Skipping smoothing for {route_json['properties']['name']}, direct route within a" f" single cell") # Set and remove some additional info for final output to match smoothed routes start_location = route_json['geometry']['coordinates'][0] @@ -726,7 +728,7 @@ def compute_smoothed_routes(self): smoothed_routes += [route_json] continue - logging.info(f"--- Smoothing {route_json['properties']['name']}") + logger.info(f"--- Smoothing {route_json['properties']['name']}") initialised_dijkstra_graph = self.initialise_dijkstra_graph(cellboxes, neighbour_graph, route) adjacent_pairs, source_wp, end_wp = initialise_dijkstra_route(initialised_dijkstra_graph, route_json) @@ -775,7 +777,7 @@ def compute_smoothed_routes(self): smoothed_routes += [smoothed_route] - logging.info('Smoothing complete in {} iterations'.format(sf.jj)) + logger.info('Smoothing complete in {} iterations'.format(sf.jj)) geojson['type'] = "FeatureCollection" geojson['features'] = smoothed_routes @@ -846,7 +848,7 @@ def select_cellbox(ids): Returns: selected (int): the id of the selected cellbox """ - logging.debug(">>> selecting cellbox for waypoint on boundary...") + logger.debug(">>> selecting cellbox for waypoint on boundary...") if (self.env_mesh.neighbour_graph.get_neighbour_case(self.cellboxes_lookup[ids[0]], self.cellboxes_lookup[ids[1]]) in [Direction.east, Direction.north_east, Direction.north]): @@ -862,7 +864,7 @@ def select_cellbox(ids): wp_id.append(self.env_mesh.agg_cellboxes[indx].get_id()) wp.set_cellbox_indx(str(self.env_mesh.agg_cellboxes[indx].get_id())) if not wp_id: - logging.warning(f'{wp.get_name()} is not an accessible waypoint') + logger.warning(f'{wp.get_name()} is not an accessible waypoint') valid_wps.remove(wp) if len(wp_id) > 1: # the source wp is on the border of 2 cellboxes diff --git a/polar_route/route_planner/source_waypoint.py b/polar_route/route_planner/source_waypoint.py index 9ae5886a..6134fadc 100644 --- a/polar_route/route_planner/source_waypoint.py +++ b/polar_route/route_planner/source_waypoint.py @@ -3,6 +3,9 @@ import numpy as np import logging +# Module logger +logger = logging.getLogger(__name__) + class SourceWaypoint(Waypoint): """ @@ -109,17 +112,17 @@ def get_path_nodes(self, _id): return path_index def log_routing_table(self): - logging.debug(f'Routing table of {self.cellbox_indx} source waypoint:') + logger.debug(f'Routing table of {self.cellbox_indx} source waypoint:') for x in self.routing_table.keys(): - logging.debug(f"To {x}, through node_idx: {self.routing_table[x].get_node_index()}") + logger.debug(f"To {x}, through node_idx: {self.routing_table[x].get_node_index()}") def log_detailed_routing_info(self): - logging.debug(f'Routing table of {self.cellbox_indx} source waypoint:') + logger.debug(f'Routing table of {self.cellbox_indx} source waypoint:') for x in self.routing_table.keys(): - logging.debug(f"To {x}, through node_idx: {self.routing_table[x].get_node_index()}") - logging.debug("using segments >> ") + logger.debug(f"To {x}, through node_idx: {self.routing_table[x].get_node_index()}") + logger.debug("using segments >> ") for s in self.routing_table[x].get_path(): - logging.debug(s.to_str()) + logger.debug(s.to_str()) def get_obj(self, node_indx, obj): """ diff --git a/polar_route/utils.py b/polar_route/utils.py index 680d4f61..cef4c665 100644 --- a/polar_route/utils.py +++ b/polar_route/utils.py @@ -10,6 +10,9 @@ import pandas as pd import geopandas as gpd +# Module logger +logger = logging.getLogger(__name__) + from datetime import datetime, timedelta from functools import wraps from calendar import monthrange @@ -224,9 +227,9 @@ def wrapper(*args, **kwargs): top_stats = snapshot.statistics('traceback') stat = top_stats[0] - logging.info("{} memory blocks: {.1f} KiB". + logger.info("{} memory blocks: {.1f} KiB". format(stat.count, stat.size / 1024)) - logging.info("\n".join(stat.traceback.format())) + logger.info("\n".join(stat.traceback.format())) return res return wrapper @@ -237,7 +240,7 @@ def wrapper(*args, **kwargs): start = time.perf_counter() res = func(*args, **kwargs) end = time.perf_counter() - logging.info("Timed call to {} took {:02f} seconds". + logger.info("Timed call to {} took {:02f} seconds". format(func.__name__, end - start)) return res return wrapper @@ -506,7 +509,7 @@ def extract_geojson_routes(mesh): empty list """ - logging.info("Extracting routes in geojson format") + logger.info("Extracting routes in geojson format") # Extract the computed routes from the mesh if "paths" in mesh.keys(): diff --git a/polar_route/vessel_performance/vessel_performance_modeller.py b/polar_route/vessel_performance/vessel_performance_modeller.py index cbe14137..ee36bef9 100644 --- a/polar_route/vessel_performance/vessel_performance_modeller.py +++ b/polar_route/vessel_performance/vessel_performance_modeller.py @@ -1,6 +1,9 @@ import numpy as np import logging +# Module logger +logger = logging.getLogger(__name__) + from meshiphi.mesh_generation.environment_mesh import EnvironmentMesh from meshiphi.mesh_generation.direction import Direction from polar_route.vessel_performance.vessel_factory import VesselFactory @@ -21,7 +24,7 @@ def __init__(self, env_mesh_json, vessel_config, custom_vessel=None): vessel_config (dict): a dictionary loaded from a vessel config json file custom_vessel (class): a custom vessel object that specifies a set of performance and accessibility models """ - logging.info("Initialising Vessel Performance Modeller") + logger.info("Initialising Vessel Performance Modeller") validate_vessel_config(vessel_config) self.env_mesh = EnvironmentMesh.load_from_json(env_mesh_json) @@ -32,7 +35,7 @@ def __init__(self, env_mesh_json, vessel_config, custom_vessel=None): self.config['neighbour_splitting'] = True if custom_vessel is not None: - logging.info(f"Loading custom vessel class: {type(custom_vessel).__name__}") + logger.info(f"Loading custom vessel class: {type(custom_vessel).__name__}") self.vessel = custom_vessel(vessel_config) else: self.vessel = VesselFactory.get_vessel(vessel_config) @@ -51,7 +54,7 @@ def model_accessibility(self): access_values = self.vessel.model_accessibility(cellbox) self.env_mesh.update_cellbox(i, access_values) inaccessible_nodes = [c.id for c in self.env_mesh.agg_cellboxes if c.agg_data['inaccessible']] - logging.info(f"Found {len(inaccessible_nodes)} inaccessible cells in the mesh") + logger.info(f"Found {len(inaccessible_nodes)} inaccessible cells in the mesh") if self.config['neighbour_splitting']: # Split any cells that neighbour inaccessible cells to match their size self.split_neighbouring_cells(inaccessible_nodes) @@ -102,7 +105,7 @@ def split_neighbouring_cells(self, inaccessible_nodes): inaccessible_nodes (list): List of inaccessible nodes to split around """ - logging.info("Splitting around inaccessible cells") + logger.info("Splitting around inaccessible cells") for in_node in inaccessible_nodes: in_cb = self.env_mesh.get_cellbox(in_node) neighbour_nodes = self.get_all_neighbours(in_node) diff --git a/polar_route/vessel_performance/vessels/SDA.py b/polar_route/vessel_performance/vessels/SDA.py index 1e337023..e04f6d7f 100644 --- a/polar_route/vessel_performance/vessels/SDA.py +++ b/polar_route/vessel_performance/vessels/SDA.py @@ -3,6 +3,9 @@ import numpy as np import logging +# Module logger +logger = logging.getLogger(__name__) + class SDA(AbstractShip): """ Vessel class with methods specifically designed to model the performance of the British Antarctic Survey @@ -30,12 +33,14 @@ def model_speed(self, cellbox): cellbox (AggregatedCellBox): updated cell with speed values """ - logging.debug(f"Calculating new speed for cellbox {cellbox.id} based on SDA resistance models") + logger.debug(f"Calculating new speed for cellbox {cellbox.id} based on SDA resistance models") speed = cellbox.agg_data['speed'] ice_resistance = None - if all(k in cellbox.agg_data for k in ("SIC", "thickness", "density")): - logging.debug("Adjusting speed according to ice resistance model") + if all(k in cellbox.agg_data for k in ("SIC", "thickness", "density")) and all( + cellbox.agg_data[k] is not None for k in ("SIC", "thickness", "density") + ): + logger.debug("Adjusting speed according to ice resistance model") if cellbox.agg_data['SIC'] == 0.0: ice_resistance = 0. speed = self.max_speed @@ -51,9 +56,9 @@ def model_speed(self, cellbox): else: speed = self.max_speed else: - logging.debug("No resistance data available, no speed adjustment necessary") + logger.debug("No resistance data available, no speed adjustment necessary") - logging.debug("Creating speed array") + logger.debug("Creating speed array") cellbox.agg_data['speed'] = [speed for x in range(8)] if ice_resistance is not None: @@ -71,7 +76,7 @@ def model_fuel(self, cellbox): Returns: cellbox (AggregatedCellBox): updated cell with fuel consumption values """ - logging.debug(f"Calculating fuel requirements in cell {cellbox.id}") + logger.debug(f"Calculating fuel requirements in cell {cellbox.id}") cellbox = self.model_resistance(cellbox) @@ -79,7 +84,6 @@ def model_fuel(self, cellbox): for i, r in enumerate(cellbox.agg_data['resistance'])] return cellbox - def model_resistance(self, cellbox): """ Method to determine the resistance force acting on the SDA in a given cell @@ -102,7 +106,7 @@ def model_resistance(self, cellbox): cellbox = calc_wind(cellbox) cellbox.agg_data['resistance'] = [cellbox.agg_data['wind resistance'][i] + ice_resistance[i] for i in range(8)] else: - logging.debug("No wind data present, wind resistance will not be calculated") + logger.debug("No wind data present, wind resistance will not be calculated") cellbox.agg_data['resistance'] = ice_resistance return cellbox @@ -296,7 +300,7 @@ def calc_wind(cellbox): cellbox (AggregatedCellBox): updated cell with wind information """ - logging.debug(f"Calculating wind resistance in cellbox {cellbox.id}") + logger.debug(f"Calculating wind resistance in cellbox {cellbox.id}") wind_res = [0, 0, 0, 0, 0, 0, 0, 0] rel_wind_speed = [0, 0, 0, 0, 0, 0, 0, 0] diff --git a/polar_route/vessel_performance/vessels/SDA_wind.py b/polar_route/vessel_performance/vessels/SDA_wind.py index 288c931f..4ebc9cb9 100644 --- a/polar_route/vessel_performance/vessels/SDA_wind.py +++ b/polar_route/vessel_performance/vessels/SDA_wind.py @@ -3,6 +3,9 @@ import numpy as np import logging +# Module logger +logger = logging.getLogger(__name__) + class SDAWind(AbstractShip): """ Vessel class with methods specifically designed to model the performance of the British Antarctic Survey @@ -30,12 +33,14 @@ def model_speed(self, cellbox): cellbox (AggregatedCellBox): updated cell with speed values """ - logging.debug(f"Calculating new speed for cellbox {cellbox.id} based on SDA resistance models") + logger.debug(f"Calculating new speed for cellbox {cellbox.id} based on SDA resistance models") speed = cellbox.agg_data['speed'] ice_resistance = None - if all(k in cellbox.agg_data for k in ("SIC", "thickness", "density")): - logging.debug("Adjusting speed according to ice resistance model") + if all(k in cellbox.agg_data for k in ("SIC", "thickness", "density")) and all( + cellbox.agg_data[k] is not None for k in ("SIC", "thickness", "density") + ): + logger.debug("Adjusting speed according to ice resistance model") if cellbox.agg_data['SIC'] == 0.0: ice_resistance = 0. speed = self.max_speed @@ -51,9 +56,9 @@ def model_speed(self, cellbox): else: speed = self.max_speed else: - logging.debug("Not all ice data available, no ice resistance calculated") + logger.debug("Not all ice data available, no ice resistance calculated") - logging.debug("Creating speed array") + logger.debug("Creating speed array") cellbox.agg_data['speed'] = [speed for x in range(8)] if ice_resistance is not None: @@ -62,7 +67,7 @@ def model_speed(self, cellbox): cellbox = self.model_resistance(cellbox) if 'wind resistance' in cellbox.agg_data: - logging.info("Adjusting speed for wind data") + logger.info("Adjusting speed for wind data") for i in range(8): if cellbox.agg_data['wind resistance'][i] > 0: if cellbox.agg_data['wind resistance'][i] < self.force_limit*0.75: @@ -85,13 +90,12 @@ def model_fuel(self, cellbox): Returns: cellbox (AggregatedCellBox): updated cell with fuel consumption values """ - logging.debug(f"Calculating fuel requirements in cell {cellbox.id}") + logger.debug(f"Calculating fuel requirements in cell {cellbox.id}") cellbox.agg_data['fuel'] = [fuel_eq(cellbox.agg_data['speed'][i], r) for i, r in enumerate(cellbox.agg_data['resistance'])] return cellbox - def model_resistance(self, cellbox): """ Method to determine the resistance force acting on the SDA in a given cell @@ -114,7 +118,7 @@ def model_resistance(self, cellbox): cellbox = calc_wind(cellbox) cellbox.agg_data['resistance'] = [cellbox.agg_data['wind resistance'][i] + ice_resistance[i] for i in range(8)] else: - logging.debug("No wind data present, wind resistance will not be calculated") + logger.debug("No wind data present, wind resistance will not be calculated") cellbox.agg_data['resistance'] = ice_resistance return cellbox @@ -308,7 +312,7 @@ def calc_wind(cellbox): cellbox (AggregatedCellBox): updated cell with wind information """ - logging.debug(f"Calculating wind resistance in cellbox {cellbox.id}") + logger.debug(f"Calculating wind resistance in cellbox {cellbox.id}") wind_res = [0, 0, 0, 0, 0, 0, 0, 0] rel_wind_speed = [0, 0, 0, 0, 0, 0, 0, 0] diff --git a/polar_route/vessel_performance/vessels/abstract_alr.py b/polar_route/vessel_performance/vessels/abstract_alr.py index 4ec694d1..40999b9a 100644 --- a/polar_route/vessel_performance/vessels/abstract_alr.py +++ b/polar_route/vessel_performance/vessels/abstract_alr.py @@ -3,6 +3,9 @@ from abc import abstractmethod import logging +# Module logger +logger = logging.getLogger(__name__) + class AbstractALR(AbstractVessel): """ @@ -14,14 +17,13 @@ def __init__(self, params): params (dict): vessel parameters from the vessel config file """ self.vessel_params = params - logging.info(f"Initialising a vessel object of type: {self.__class__.__name__}") + logger.info(f"Initialising a vessel object of type: {self.__class__.__name__}") self.max_speed = self.vessel_params['max_speed'] self.speed_unit = self.vessel_params['unit'] self.max_elevation = -1 * self.vessel_params['min_depth'] self.max_ice = self.vessel_params['max_ice_conc'] self.excluded_zones = self.vessel_params.get('excluded_zones') - def model_performance(self, cellbox): """ Method to determine the performance characteristics for the ALR @@ -29,7 +31,7 @@ def model_performance(self, cellbox): Args: cellbox (AggregatedCellBox): input cell from environmental mesh """ - logging.debug( + logger.debug( f"Modelling performance in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") perf_cellbox = self.model_speed(cellbox) perf_cellbox = self.model_battery(perf_cellbox) @@ -47,7 +49,7 @@ def model_accessibility(self, cellbox): Returns: access_values (dict): boolean values for the modelled accessibility criteria """ - logging.debug(f"Modelling accessibility in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") + logger.debug(f"Modelling accessibility in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") access_values = dict() # Exclude cells due to land or ice @@ -74,14 +76,13 @@ def land(self, cellbox): land (bool): boolean that is True if the cell is inaccessible due to land """ if 'elevation' not in cellbox.agg_data: - logging.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is land") + logger.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is land") land = False else: land = cellbox.agg_data['elevation'] >= 0.0 return land - def shallow(self, cellbox): """ Method to determine if the water in a cell is too shallow for an ALR based on configured minimum depth @@ -92,7 +93,7 @@ def shallow(self, cellbox): shallow (bool): boolean that is True if the cell is too shallow for an ALR """ if 'elevation' not in cellbox.agg_data: - logging.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is too shallow") + logger.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is too shallow") shallow = False else: condition_shallow1 = 0.0 > cellbox.agg_data['elevation'] > self.max_elevation @@ -100,7 +101,6 @@ def shallow(self, cellbox): return shallow - def extreme_ice(self, cellbox): """ Method to determine if a cell is inaccessible based on configured max ice concentration @@ -111,8 +111,8 @@ def extreme_ice(self, cellbox): Returns: ext_ice (bool): boolean that is True if the cell is inaccessible due to ice """ - if 'SIC' not in cellbox.agg_data: - logging.debug(f"No sea ice concentration data in cell {cellbox.id}") + if "SIC" not in cellbox.agg_data or cellbox.agg_data["SIC"] is None: + logger.debug(f"No sea ice concentration data in cell {cellbox.id}") ext_ice = False else: ext_ice = cellbox.agg_data['SIC'] > self.max_ice @@ -144,5 +144,3 @@ def model_battery(self, cellbox: AggregatedCellBox): cellbox (AggregatedCellBox): updated cell with battery consumption values """ raise NotImplementedError - - diff --git a/polar_route/vessel_performance/vessels/abstract_glider.py b/polar_route/vessel_performance/vessels/abstract_glider.py index ef5f5de2..2da28cdf 100644 --- a/polar_route/vessel_performance/vessels/abstract_glider.py +++ b/polar_route/vessel_performance/vessels/abstract_glider.py @@ -3,6 +3,9 @@ from abc import abstractmethod import logging +# Module logger +logger = logging.getLogger(__name__) + class AbstractGlider(AbstractVessel): """ @@ -14,7 +17,7 @@ def __init__(self, params): params (dict): vessel parameters from the vessel config file """ self.vessel_params = params - logging.info(f"Initialising a vessel object of type: {self.__class__.__name__}") + logger.info(f"Initialising a vessel object of type: {self.__class__.__name__}") self.max_speed = self.vessel_params['max_speed'] self.speed_unit = self.vessel_params['unit'] self.max_elevation = -1 * self.vessel_params['min_depth'] @@ -29,7 +32,7 @@ def model_performance(self, cellbox): Args: cellbox (AggregatedCellBox): input cell from environmental mesh """ - logging.debug( + logger.debug( f"Modelling performance in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") perf_cellbox = self.model_speed(cellbox) perf_cellbox = self.model_battery(perf_cellbox) @@ -47,7 +50,7 @@ def model_accessibility(self, cellbox): Returns: access_values (dict): boolean values for the modelled accessibility criteria """ - logging.debug(f"Modelling accessibility in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") + logger.debug(f"Modelling accessibility in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") access_values = dict() # Exclude cells due to land or ice @@ -74,7 +77,7 @@ def land(self, cellbox): land (bool): boolean that is True if the cell is inaccessible due to land """ if 'elevation' not in cellbox.agg_data: - logging.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is land") + logger.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is land") land = False else: land = cellbox.agg_data['elevation'] >= 0.0 @@ -91,7 +94,7 @@ def shallow(self, cellbox): shallow (bool): boolean that is True if the cell is too shallow for a glider """ if 'elevation' not in cellbox.agg_data: - logging.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is too shallow") + logger.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is too shallow") shallow = False else: shallow = 0.0 > cellbox.agg_data['elevation'] > self.max_elevation @@ -108,8 +111,8 @@ def extreme_ice(self, cellbox): Returns: ext_ice (bool): boolean that is True if the cell is inaccessible due to ice """ - if 'SIC' not in cellbox.agg_data: - logging.debug(f"No sea ice concentration data in cell {cellbox.id}") + if "SIC" not in cellbox.agg_data or cellbox.agg_data["SIC"] is None: + logger.debug(f"No sea ice concentration data in cell {cellbox.id}") ext_ice = False else: ext_ice = cellbox.agg_data['SIC'] > self.max_ice diff --git a/polar_route/vessel_performance/vessels/abstract_plane.py b/polar_route/vessel_performance/vessels/abstract_plane.py index 6125897b..4ff18b11 100644 --- a/polar_route/vessel_performance/vessels/abstract_plane.py +++ b/polar_route/vessel_performance/vessels/abstract_plane.py @@ -3,6 +3,9 @@ from abc import abstractmethod import logging +# Module logger +logger = logging.getLogger(__name__) + class AbstractPlane(AbstractVessel): """ @@ -14,7 +17,7 @@ def __init__(self, params): params (dict): vessel parameters from the vessel config file """ self.vessel_params = params - logging.info(f"Initialising a vessel object of type: {self.__class__.__name__}") + logger.info(f"Initialising a vessel object of type: {self.__class__.__name__}") self.max_speed = self.vessel_params['max_speed'] self.speed_unit = self.vessel_params['unit'] self.max_elevation = self.vessel_params['max_elevation'] @@ -28,7 +31,7 @@ def model_performance(self, cellbox): Args: cellbox (AggregatedCellBox): input cell from environmental mesh """ - logging.debug("Modelling performance in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") + logger.debug("Modelling performance in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") perf_cellbox = self.model_speed(cellbox) perf_cellbox = self.model_fuel(perf_cellbox) @@ -45,7 +48,7 @@ def model_accessibility(self, cellbox): Returns: access_values (dict): boolean values for the modelled accessibility criteria """ - logging.debug(f"Modelling accessibility in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") + logger.debug(f"Modelling accessibility in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") access_values = dict() # Exclude cells due to terrain or other features @@ -72,7 +75,7 @@ def land(self, cellbox): land (bool): boolean that is True if the cell is inaccessible due to land """ if 'elevation' not in cellbox.agg_data: - logging.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is land") + logger.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is land") land = False else: land = cellbox.agg_data['elevation'] >= 0.0 @@ -89,7 +92,7 @@ def elevation_max(self, cellbox): elevation_max (bool): boolean that is True if the elevation in a cell is too high for a plane """ if 'elevation' not in cellbox.agg_data: - logging.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is too high") + logger.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is too high") elevation_max = False else: elevation_max = cellbox.agg_data['elevation'] > self.max_elevation diff --git a/polar_route/vessel_performance/vessels/abstract_ship.py b/polar_route/vessel_performance/vessels/abstract_ship.py index 278c3e6b..8614938a 100644 --- a/polar_route/vessel_performance/vessels/abstract_ship.py +++ b/polar_route/vessel_performance/vessels/abstract_ship.py @@ -3,6 +3,9 @@ from abc import abstractmethod import logging +# Module logger +logger = logging.getLogger(__name__) + class AbstractShip(AbstractVessel): """ Abstract class to define the methods and attributes common to any vessel that is a ship @@ -13,7 +16,7 @@ def __init__(self, params): params (dict): vessel parameters from the vessel config file """ self.vessel_params = params - logging.info(f"Initialising a vessel object of type: {self.__class__.__name__}") + logger.info(f"Initialising a vessel object of type: {self.__class__.__name__}") self.max_speed = self.vessel_params['max_speed'] self.speed_unit = self.vessel_params['unit'] @@ -32,10 +35,10 @@ def model_performance(self, cellbox): Returns: performance_values (dict): the value of the modelled performance characteristics for the ship """ - logging.debug(f"Modelling performance in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") + logger.debug(f"Modelling performance in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") # Check if the speed is defined in the input cellbox if 'speed' not in cellbox.agg_data: - logging.debug(f'No speed in cell, assigning default value of {self.max_speed} ' + logger.debug(f'No speed in cell, assigning default value of {self.max_speed} ' f'{self.speed_unit} from config') cellbox.agg_data['speed'] = self.max_speed @@ -56,7 +59,7 @@ def model_accessibility(self, cellbox): Returns: access_values (dict): boolean values for the modelled accessibility criteria """ - logging.debug(f"Modelling accessibility in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") + logger.debug(f"Modelling accessibility in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") access_values = dict() # Make land and extreme ice cells inaccessible @@ -65,7 +68,7 @@ def model_accessibility(self, cellbox): # Make cells above wave height threshold inaccessible if self.max_wave is not None: - logging.debug(f"Excluding areas with wave height above {self.max_wave}m") + logger.debug(f"Excluding areas with wave height above {self.max_wave}m") access_values['ext_waves'] = self.extreme_waves(cellbox) # Exclude any other cells specified in config @@ -74,7 +77,7 @@ def model_accessibility(self, cellbox): try: access_values[zone] = cellbox.agg_data[zone] except KeyError: - logging.debug(f'{zone} not found in agg cellbox!') + logger.debug(f'{zone} not found in agg cellbox!') access_values['inaccessible'] = any(access_values.values()) @@ -116,7 +119,7 @@ def land(self, cellbox): land (bool): boolean that is True if the cell is inaccessible due to land """ if 'elevation' not in cellbox.agg_data: - logging.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is land") + logger.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is land") land = False else: land = cellbox.agg_data['elevation'] > self.max_elevation @@ -132,8 +135,8 @@ def extreme_ice(self, cellbox): Returns: ext_ice (bool): boolean that is True if the cell is inaccessible due to ice """ - if 'SIC' not in cellbox.agg_data: - logging.debug(f"No sea ice concentration data in cell {cellbox.id}") + if 'SIC' not in cellbox.agg_data or cellbox.agg_data['SIC'] is None: + logger.debug(f"No sea ice concentration data in cell {cellbox.id}") ext_ice = False else: ext_ice = cellbox.agg_data['SIC'] > self.max_ice @@ -149,7 +152,7 @@ def extreme_waves(self, cellbox): ext_wave (bool): boolean that is True if the cell is inaccessible due to waves """ if 'swh' not in cellbox.agg_data: - logging.debug(f"No wave height data in cell {cellbox.id}") + logger.debug(f"No wave height data in cell {cellbox.id}") ext_wave = False else: ext_wave = cellbox.agg_data['swh'] > self.max_wave diff --git a/polar_route/vessel_performance/vessels/abstract_uav.py b/polar_route/vessel_performance/vessels/abstract_uav.py index 3baf5cec..77a09402 100644 --- a/polar_route/vessel_performance/vessels/abstract_uav.py +++ b/polar_route/vessel_performance/vessels/abstract_uav.py @@ -3,6 +3,9 @@ from abc import abstractmethod import logging +# Module logger +logger = logging.getLogger(__name__) + class AbstractUAV(AbstractVessel): """ @@ -14,7 +17,7 @@ def __init__(self, params): params (dict): vessel parameters from the vessel config file """ self.vessel_params = params - logging.info(f"Initialising a vessel object of type: {self.__class__.__name__}") + logger.info(f"Initialising a vessel object of type: {self.__class__.__name__}") self.max_speed = self.vessel_params['max_speed'] self.speed_unit = self.vessel_params['unit'] self.max_elevation = self.vessel_params['max_elevation'] @@ -29,7 +32,7 @@ def model_performance(self, cellbox): Args: cellbox (AggregatedCellBox): input cell from environmental mesh """ - logging.debug( + logger.debug( f"Modelling performance in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") perf_cellbox = self.model_speed(cellbox) perf_cellbox = self.model_battery(perf_cellbox) @@ -47,7 +50,7 @@ def model_accessibility(self, cellbox): Returns: access_values (dict): boolean values for the modelled accessibility criteria """ - logging.debug(f"Modelling accessibility in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") + logger.debug(f"Modelling accessibility in cell {cellbox.id} for a vessel of type: {self.__class__.__name__}") access_values = dict() # Exclude cells due to land or ice @@ -76,7 +79,7 @@ def land(self, cellbox): land (bool): boolean that is True if the cell is inaccessible due to land """ if 'elevation' not in cellbox.agg_data: - logging.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is land") + logger.warning(f"No elevation data in cell {cellbox.id}, cannot determine if it is land") land = False else: land = cellbox.agg_data['elevation'] >= 0.0 diff --git a/polar_route/vessel_performance/vessels/example_ship.py b/polar_route/vessel_performance/vessels/example_ship.py index fb9cb311..77f7cf36 100644 --- a/polar_route/vessel_performance/vessels/example_ship.py +++ b/polar_route/vessel_performance/vessels/example_ship.py @@ -3,6 +3,9 @@ import numpy as np import logging +# Module logger +logger = logging.getLogger(__name__) + class ExampleShip(AbstractShip): """ Vessel class with methods designed to model a ship. @@ -29,12 +32,14 @@ def model_speed(self, cellbox): cellbox (AggregatedCellBox): updated cell with speed values """ - logging.debug(f"Calculating new speed for cellbox {cellbox.id} based on resistance modelling") + logger.debug(f"Calculating new speed for cellbox {cellbox.id} based on resistance modelling") speed = cellbox.agg_data['speed'] ice_resistance = None - if all(k in cellbox.agg_data for k in ("SIC", "thickness", "density")): - logging.debug("Adjusting speed according to ice resistance model") + if all(k in cellbox.agg_data for k in ("SIC", "thickness", "density")) and all( + cellbox.agg_data[k] is not None for k in ("SIC", "thickness", "density") + ): + logger.debug("Adjusting speed according to ice resistance model") if cellbox.agg_data['SIC'] == 0.0: ice_resistance = 0. speed = self.max_speed @@ -50,9 +55,9 @@ def model_speed(self, cellbox): else: speed = self.max_speed else: - logging.debug("No resistance data available, no speed adjustment necessary") + logger.debug("No resistance data available, no speed adjustment necessary") - logging.debug("Creating speed array") + logger.debug("Creating speed array") cellbox.agg_data['speed'] = [speed for x in range(8)] if ice_resistance is not None: @@ -70,7 +75,7 @@ def model_fuel(self, cellbox): Returns: cellbox (AggregatedCellBox): updated cell with fuel consumption values """ - logging.debug(f"Calculating fuel requirements in cell {cellbox.id}") + logger.debug(f"Calculating fuel requirements in cell {cellbox.id}") cellbox = self.model_resistance(cellbox) @@ -78,7 +83,6 @@ def model_fuel(self, cellbox): for i, r in enumerate(cellbox.agg_data['resistance'])] return cellbox - def model_resistance(self, cellbox): """ Method to determine the resistance force acting on the ship in a given cell @@ -101,7 +105,7 @@ def model_resistance(self, cellbox): cellbox = calc_wind(cellbox) cellbox.agg_data['resistance'] = [cellbox.agg_data['wind resistance'][i] + ice_resistance[i] for i in range(8)] else: - logging.debug("No wind data present, wind resistance will not be calculated") + logger.debug("No wind data present, wind resistance will not be calculated") cellbox.agg_data['resistance'] = ice_resistance return cellbox @@ -295,7 +299,7 @@ def calc_wind(cellbox): cellbox (AggregatedCellBox): updated cell with wind information """ - logging.debug(f"Calculating wind resistance in cellbox {cellbox.id}") + logger.debug(f"Calculating wind resistance in cellbox {cellbox.id}") wind_res = [0, 0, 0, 0, 0, 0, 0, 0] rel_wind_speed = [0, 0, 0, 0, 0, 0, 0, 0] diff --git a/pyproject.toml b/pyproject.toml index 2e031715..5567e1f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,3 +52,10 @@ optimise_routes = "polar_route.cli:optimise_routes_cli" calculate_route = "polar_route.cli:calculate_route_cli" resimulate_vehicle = "polar_route.cli:resimulate_vehicle_cli" extract_routes = "polar_route.cli:extract_routes_cli" + +[tool.pytest.ini_options] +log_cli = true +log_format = "[%(asctime)s] %(levelname)s : %(message)s" +markers = [ + "slow: marks tests as slow (deselect with '-m \"not slow\"')" +] diff --git a/requirements.txt b/requirements.txt index dbb131e8..dfaf19d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -meshiphi==2.1.15 +meshiphi>=2.1.15 dask geopandas jsonschema diff --git a/tests/regression_tests/pytest.ini b/tests/regression_tests/pytest.ini deleted file mode 100644 index 3394300c..00000000 --- a/tests/regression_tests/pytest.ini +++ /dev/null @@ -1,6 +0,0 @@ -# pytest.ini -[pytest] -log_cli = true -log_format= [%(asctime)s] %(levelname)s : %(message)s - - diff --git a/tests/regression_tests/route_test_functions.py b/tests/regression_tests/route_test_functions.py deleted file mode 100644 index 1ad62e18..00000000 --- a/tests/regression_tests/route_test_functions.py +++ /dev/null @@ -1,281 +0,0 @@ -import numpy as np -import pandas as pd - -from polar_route.utils import round_to_sigfig - -SIG_FIG_TOLERANCE = 4 - -# Testing route outputs -def test_route_coordinates(route_pair): - compare_route_coordinates(route_pair[0], route_pair[1]) - -def test_waypoint_names(route_pair): - compare_waypoint_names(route_pair[0], route_pair[1]) - -def test_time(route_pair): - compare_time(route_pair[0], route_pair[1]) - -def test_fuel_battery(route_pair): - path_variables = extract_route_info(route_pair[0])['path_variables'] - if 'fuel' in path_variables: - compare_fuel(route_pair[0], route_pair[1]) - if 'battery' in path_variables: - compare_battery(route_pair[0], route_pair[1]) - -def test_cell_indices(route_pair): - compare_cell_indices(route_pair[0], route_pair[1]) - -def test_cases(route_pair): - compare_cases(route_pair[0], route_pair[1]) - -def test_distance(route_pair): - compare_distance(route_pair[0], route_pair[1]) - -def test_speed(route_pair): - compare_speed(route_pair[0], route_pair[1]) - - -# Comparison between old and new -def compare_route_coordinates(route_a, route_b): - """ - Tests if coordinates of each node are the same between both routes. - - Args: - route_a (json) - route_b (json) - - Raises: - AssertionError: - Fails if length of coord arrays is different, - which implies different nodes along route - AssertionError: - Fails if coords of each node are different at any point - (beyond sig fig limit) - """ - coords_a = extract_path(route_a)['geometry']['coordinates'] - coords_b = extract_path(route_b)['geometry']['coordinates'] - - len_a = len(coords_a) - len_b = len(coords_b) - - assert(len_a == len_b), \ - f"Number of nodes different! Expected {len_a}, got {len_b}" - - rounded_x_a = round_to_sigfig(coords_a[:][0], sigfig=SIG_FIG_TOLERANCE) - rounded_x_b = round_to_sigfig(coords_b[:][0], sigfig=SIG_FIG_TOLERANCE) - - rounded_y_a = round_to_sigfig(coords_a[:][1], sigfig=SIG_FIG_TOLERANCE) - rounded_y_b = round_to_sigfig(coords_b[:][1], sigfig=SIG_FIG_TOLERANCE) - - np.testing.assert_array_equal(rounded_x_a, rounded_x_b, - err_msg='Difference in "route_coordinates"') - np.testing.assert_array_equal(rounded_y_a, rounded_y_b, - err_msg='Difference in "route_coordinates"') - -def compare_waypoint_names(route_a, route_b): - """ - Tests if waypoint names are the same between both routes. - - Args: - route_a (json) - route_b (json) - - Raises: - AssertionError: - Fails if source or destination names don't match - """ - from_a = extract_path(route_a)['properties']['from'] - from_b = extract_path(route_b)['properties']['from'] - assert(from_a == from_b), \ - f"Waypoint source names don't match! Expected {from_a}, got {from_b}" - - to_a = extract_path(route_a)['properties']['to'] - to_b = extract_path(route_b)['properties']['to'] - assert(to_a == to_b), \ - f"Waypoint destination names don't match! Expected {to_a}, got {to_b}" - -def compare_time(route_a, route_b): - """ - Tests if times to each node are the same between both routes. - - Args: - route_a (json) - route_b (json) - - Raises: - AssertionError: - Fails if time to each node is different at any point - (beyond sig fig limit) - """ - times_a = extract_path(route_a)['properties']['traveltime'] - times_b = extract_path(route_b)['properties']['traveltime'] - - rounded_a = round_to_sigfig(times_a, sigfig=SIG_FIG_TOLERANCE) - rounded_b = round_to_sigfig(times_b, sigfig=SIG_FIG_TOLERANCE) - - np.testing.assert_array_equal(rounded_a, rounded_b, - err_msg='Difference in "traveltime"') - -def compare_fuel(route_a, route_b): - """ - Tests if fuel to each node are the same between both routes. - - Args: - route_a (json) - route_b (json) - - Raises: - AssertionError: - Fails if fuel to each node is different at any point - (beyond sig fig limit) - """ - fuel_a = extract_path(route_a)['properties']['fuel'] - fuel_b = extract_path(route_b)['properties']['fuel'] - - rounded_a = round_to_sigfig(fuel_a, sigfig=SIG_FIG_TOLERANCE) - rounded_b = round_to_sigfig(fuel_b, sigfig=SIG_FIG_TOLERANCE) - - np.testing.assert_array_equal(rounded_a, rounded_b, - err_msg='Difference in "fuel"') - -def compare_battery(route_a, route_b): - """ - Tests if battery consumption is the same between both routes. - - Args: - route_a (json) - route_b (json) - - Raises: - AssertionError: - Fails if battery consumption to each node is different at any point - (beyond sig fig limit) - """ - battery_a = extract_path(route_a)['properties']['battery'] - battery_b = extract_path(route_b)['properties']['battery'] - - rounded_a = round_to_sigfig(battery_a, sigfig=SIG_FIG_TOLERANCE) - rounded_b = round_to_sigfig(battery_b, sigfig=SIG_FIG_TOLERANCE) - - np.testing.assert_array_equal(rounded_a, rounded_b, - err_msg='Difference in "battery"') - -def compare_cell_indices(route_a, route_b): - """ - Tests if cell indices are the same between both routes. - - Args: - route_a (json) - route_b (json) - - Raises: - AssertionError: - Fails if cell indices are different at any point - """ - cid_a = extract_path(route_a)['properties']['CellIndices'] - cid_b = extract_path(route_b)['properties']['CellIndices'] - - cid_a = [int(a) for a in cid_a] - cid_b = [int(b) for b in cid_b] - - np.testing.assert_array_equal(cid_a, cid_b, - err_msg='Difference in "cell_indices"') - -def compare_cases(route_a, route_b): - """ - Tests if dijkstra route direction cases are the same between both routes. - - Args: - route_a (json) - route_b (json) - - Raises: - AssertionError: - Fails if dijkstra route direction cases are different at any point - """ - cases_a = extract_path(route_a)['properties']['cases'] - cases_b = extract_path(route_b)['properties']['cases'] - - np.testing.assert_array_equal(cases_a, cases_b, - err_msg='Difference in "cases"') - -def compare_distance(route_a, route_b): - """ - Tests if distance between cells are the same between both routes. - - Args: - route_a (json) - route_b (json) - - Raises: - AssertionError: - Fails if distances are different at any point - """ - distance_a = extract_path(route_a)['properties']['distance'] - distance_b = extract_path(route_b)['properties']['distance'] - - rounded_a = round_to_sigfig(distance_a, sigfig=SIG_FIG_TOLERANCE) - rounded_b = round_to_sigfig(distance_b, sigfig=SIG_FIG_TOLERANCE) - - np.testing.assert_array_equal(rounded_a, rounded_b, - err_msg='Difference in "distance"') - -def compare_speed(route_a, route_b): - """ - Tests if speed between cells are the same between both routes. - - Args: - route_a (json) - route_b (json) - - Raises: - AssertionError: - Fails if speeds are different at any point - """ - speed_a = extract_path(route_a)['properties']['speed'] - speed_b = extract_path(route_b)['properties']['speed'] - - rounded_a = round_to_sigfig(speed_a, sigfig=SIG_FIG_TOLERANCE) - rounded_b = round_to_sigfig(speed_b, sigfig=SIG_FIG_TOLERANCE) - - np.testing.assert_array_equal(rounded_a, rounded_b, - err_msg='Difference in "speed"') - -# Utility functions -def extract_waypoints(mesh): - """ - Extracts waypoints from mesh and returns as a pd.Dataframe for - routeplanner to work with - - Args: - mesh (json): JSON object with waypoints key - - Returns: - pd.DataFrame: Dataframe constructed from waypoints - """ - return pd.DataFrame.from_dict(mesh['waypoints']) - -def extract_path(mesh): - """ - Extracts path information from mesh and returns as a json for - tests to work with - - Args: - mesh (json): JSON object with paths key - - Returns: - json: Dictionary with path information - """ - return mesh['paths']['features'][0] - -def extract_route_info(mesh): - """ - Extracts route info from mesh and returns as JSON to read - - Args: - mesh (json): JSON object with route_info key - - Returns: - json: Dictionary with route information - """ - return mesh['config']['route_info'] \ No newline at end of file diff --git a/tests/regression_tests/test_config_validation.py b/tests/regression_tests/test_config_validation.py index 689eb304..2ab0d546 100644 --- a/tests/regression_tests/test_config_validation.py +++ b/tests/regression_tests/test_config_validation.py @@ -15,19 +15,15 @@ WAYPOINTS_COLUMNS = ["Name", "Lat", "Long", "Source", "Destination"] -def load_json_example(path): - """Load a JSON file or skip the test if it doesn't exist.""" +def load_example(path, loader_func): + """Load an example file or skip the test if it doesn't exist.""" if not os.path.exists(path): - pytest.skip(f"Example JSON not found: {path}") - with open(path) as f: - return json.load(f) + pytest.skip(f"Example file not found: {path}") + return loader_func(path) -def load_csv_example(path): - """Load a CSV file or skip the test if it doesn't exist.""" - if not os.path.exists(path): - pytest.skip(f"Example CSV not found: {path}") - return pd.read_csv(path) +load_json_example = lambda path: load_example(path, lambda p: json.load(open(p))) +load_csv_example = lambda path: load_example(path, pd.read_csv) @pytest.fixture @@ -50,12 +46,11 @@ def valid_route_config(): @pytest.fixture def valid_waypoints_df(): """Fixture providing a minimal valid waypoints DataFrame.""" - csv = """ -Name,Lat,Long,Source,Destination -WP1,60.0,-45.0,X, -WP2,61.0,-44.0,,X -""" - return pd.read_csv(StringIO(csv)) + return pd.read_csv(StringIO( + "Name,Lat,Long,Source,Destination\n" + "WP1,60.0,-45.0,X,\n" + "WP2,61.0,-44.0,,X" + )) # Vessel config validation @@ -75,6 +70,7 @@ def test_validate_vessel_config_file(): ({"vessel_type": None, "max_speed": 26.5, "unit": "km/hr"}, "vessel_type"), ({"vessel_type": "SDA", "max_speed": None, "unit": "km/hr"}, "max_speed"), ({"vessel_type": "SDA", "max_speed": 26.5, "unit": None}, "unit"), + ({}, ""), # Empty dict ], ) def test_validate_vessel_config_invalid(config, match): @@ -96,12 +92,6 @@ def test_validate_vessel_config_extra_keys(valid_vessel_config): validate_vessel_config(config) -def test_validate_vessel_config_empty_dict(): - """Test that an empty vessel config dictionary raises ValidationError.""" - with pytest.raises(ValidationError): - validate_vessel_config({}) - - # Route config validation testing def test_validate_route_config_file(): """Test that the example route config file validates successfully.""" @@ -132,6 +122,7 @@ def test_validate_route_config_file(): }, "weeks", ), + ({}, ""), # Empty dict ], ) def test_validate_route_config_invalid(config, match): @@ -153,12 +144,6 @@ def test_validate_route_config_extra_keys(valid_route_config): validate_route_config(config) -def test_validate_route_config_empty_dict(): - """Test that an empty route config dictionary raises ValidationError.""" - with pytest.raises(ValidationError): - validate_route_config({}) - - # Waypoint config validation def test_validate_waypoints_file(): """Test that the example waypoints CSV validates successfully.""" @@ -169,35 +154,11 @@ def test_validate_waypoints_file(): @pytest.mark.parametrize( "csv_content, match", [ - ( - """ -Index,Name,Lat,Source -0,WP1,60.0,X -""", - "Expected the following columns", - ), - ( - """ -Index,Name,Lat,Long,Source,Destination -0,WP1,60.0,-45.0,,X -""", - "No source waypoint defined!", - ), - ( - """ -Index,Name,Lat,Long,Source,Destination -0,WP1,60.0,-45.0,X, -""", - "No destination waypoint defined!", - ), - ( - """ -Index,Name,Lat,Long,Source,Destination -0,WP1,sixty,-45.0,X, -1,WP2,61.0,not_a_number,,X -""", - 'Non-numeric value in "Lat" column', - ), + ("Index,Name,Lat,Source\n0,WP1,60.0,X", "Expected the following columns"), + ("Index,Name,Lat,Long,Source,Destination\n0,WP1,60.0,-45.0,,X", "No source waypoint defined!"), + ("Index,Name,Lat,Long,Source,Destination\n0,WP1,60.0,-45.0,X,", "No destination waypoint defined!"), + ("Index,Name,Lat,Long,Source,Destination\n0,WP1,sixty,-45.0,X,\n1,WP2,61.0,not_a_number,,X", 'Non-numeric value in "Lat" column'), + ("Name,Lat,Long,Source,Destination", "No source waypoint defined!"), # Empty with columns ], ) def test_validate_waypoints_invalid(csv_content, match): @@ -219,29 +180,6 @@ def test_validate_waypoints_empty_df(): validate_waypoints(pd.DataFrame()) -def test_validate_waypoints_missing_source_and_destination(): - """Test that missing both source and destination raises an error.""" - df = pd.read_csv( - StringIO( - """ -Name,Lat,Long,Source,Destination -WP1,60.0,-45.0,, -""" - ) - ) - with pytest.raises(AssertionError) as excinfo: - validate_waypoints(df) - error = str(excinfo.value).lower() - assert "source" in error or "destination" in error - - -def test_validate_waypoints_empty_with_columns(): - """Test that empty DataFrame with correct columns raises error.""" - df = pd.DataFrame(columns=WAYPOINTS_COLUMNS) - with pytest.raises(AssertionError, match="No source waypoint defined!"): - validate_waypoints(df) - - def test_validate_waypoints_extra_columns(valid_waypoints_df): """Test that waypoints validation ignores extra columns as expected.""" df = valid_waypoints_df.copy() diff --git a/tests/regression_tests/test_routes_dijkstra.py b/tests/regression_tests/test_routes_dijkstra.py index 103e616e..97eb7de3 100644 --- a/tests/regression_tests/test_routes_dijkstra.py +++ b/tests/regression_tests/test_routes_dijkstra.py @@ -1,166 +1,53 @@ import json import pytest -import time -from polar_route import __version__ as pr_version -from polar_route.route_planner.route_planner import RoutePlanner - -from .route_test_functions import extract_waypoints -from .route_test_functions import extract_route_info - -# Import tests, which are automatically run -from .route_test_functions import test_route_coordinates -from .route_test_functions import test_waypoint_names -from .route_test_functions import test_time -from .route_test_functions import test_fuel_battery -from .route_test_functions import test_cell_indices -from .route_test_functions import test_cases +from .utils import ( + get_route_test_files, + calculate_dijkstra_route, + compare_route_coordinates, + compare_waypoint_names, + compare_time, + compare_fuel, + compare_battery, + compare_cell_indices, + compare_cases +) import logging LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.INFO) -# location of test files to be recalculated for regression testing -TEST_ROUTES = [ - './example_routes/dijkstra/fuel/gaussian_random_field.json', - './example_routes/dijkstra/fuel/gaussian_random_field_waypointsplitting.json', - './example_routes/dijkstra/fuel/checkerboard.json', - './example_routes/dijkstra/fuel/great_circle_forward.json', - './example_routes/dijkstra/fuel/great_circle_reverse.json', - './example_routes/dijkstra/time/gaussian_random_field.json', - './example_routes/dijkstra/time/gaussian_random_field_waypointsplitting.json', - './example_routes/dijkstra/time/checkerboard.json', - './example_routes/dijkstra/time/great_circle_forward.json', - './example_routes/dijkstra/time/great_circle_reverse.json', - './example_routes/dijkstra/time/multi_waypoint_blocked.json', - './example_routes/dijkstra/time/single_cell.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_20lat_s.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_20lat_n.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_40lat_s.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_40lat_n.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_60lat_s.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_60lat_n.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_80lat_s.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_80lat_n.json', - './example_routes/dijkstra/crossing_point/diagonal/diagonal_0lat.json', - './example_routes/dijkstra/crossing_point/diagonal/diagonal_0lat_split.json', - './example_routes/dijkstra/crossing_point/diagonal/diagonal_0lat_split4.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_20lat_s.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_20lat_n.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_40lat_s.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_40lat_n.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_60lat_s.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_60lat_n.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_80lat_s.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_80lat_n.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_split.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_split.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_scalar.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_scalar_split.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_scalar.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_scalar_split.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_offset_source.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_offset_destination.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_scalar_offset_source.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_scalar_offset_destination.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_offset_source.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_offset_destination.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_scalar_offset_source.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_scalar_offset_destination.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_split4.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_split4.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_vector_same_dir.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_vector_opp_dir.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_vector_same_dir.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_vector_opp_dir.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_antimeridian.json', - './example_routes/dijkstra/crossing_point/diagonal/diagonal_0lat_scalar.json', - './example_routes/dijkstra/crossing_point/diagonal/diagonal_0lat_split_scalar.json', - './example_routes/dijkstra/crossing_point/diagonal/diagonal_0lat_vector_opp_dir.json', - './example_routes/dijkstra/crossing_point/diagonal/diagonal_0lat_vector_same_dir.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_wind_opp_dir.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_wind_opp_dir_offset.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_wind_opp_dir.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_wind_opp_dir_offset.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_80latn_reverse.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_80lats_reverse.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_antimeridian_reverse.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_reverse.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_reverse.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_80latn_reverse.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_80lats_reverse.json', - './example_routes/dijkstra/crossing_point/diagonal/diagonal_0lat_reverse.json', - './example_routes/dijkstra/crossing_point/diagonal/diagonal_0lat_vector_reverse.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_scalar_reverse.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_vector_reverse.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_scalar_reverse.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_vector_reverse.json', - './example_routes/dijkstra/crossing_point/diagonal/diagonal_0lat_scalar_reverse.json', - './example_routes/dijkstra/crossing_point/horizontal/horizontal_0lat_wind_same_dir.json', - './example_routes/dijkstra/crossing_point/vertical/vertical_0lat_wind_same_dir.json', - './example_routes/dijkstra/fuel/twin_otter_f_route_dijkstra.json', - './example_routes/dijkstra/battery/slocum_b_route_dijkstra.json', - './example_routes/dijkstra/battery/alr_b_route_dijkstra.json', - './example_routes/dijkstra/time/slocum_tt_route_dijkstra.json', - './example_routes/dijkstra/time/alr_tt_route_dijkstra.json', - './example_routes/dijkstra/time/twin_otter_tt_route_dijkstra.json' -] - -def setup_module(): - LOGGER.info(f'PolarRoute version: {pr_version}') +# Dynamically discover test files +TEST_ROUTES = get_route_test_files('dijkstra') # Pairing old and new outputs -@pytest.fixture(scope='session', autouse=False, params=TEST_ROUTES) +@pytest.fixture(scope='session', params=TEST_ROUTES) def route_pair(request): - """ - Creates a pair of JSON objects, one newly generated, one as old reference - Args: - request (fixture): - fixture object including list of jsons of optimised routes - - Returns: - list: [reference json, new json] - """ - + """Creates pair of routes: reference JSON and newly generated.""" LOGGER.info(f'Test File: {request.param}') - - # Load reference JSON + with open(request.param, 'r') as fp: old_route = json.load(fp) - route_info = extract_route_info(old_route) - # Create new json (cast old to dict to create copy to avoid modifying) - new_route = calculate_dijkstra_route(route_info, dict(old_route)) - - return [old_route, new_route] - -# Generating new outputs -def calculate_dijkstra_route(config, mesh): - """ - Calculates the optimised route, with dijkstra but no smoothing - - Args: - config (dict): the route config - mesh (dict): the reference mesh (including routes and waypoints) - - Returns: - new_route (dict): new route output - """ - start = time.perf_counter() - - # Initial set up - waypoints = extract_waypoints(mesh) - - # Calculate dijkstra route - rp = RoutePlanner(mesh, config) - routes = rp.compute_routes(waypoints) - # Generate json to compare to old output - new_route = mesh - new_route['paths'] = {"type": "FeatureCollection", "features": []} - new_route['paths']['features'] = [r.to_json() for r in routes] - - end = time.perf_counter() - LOGGER.info(f'Route calculated in {end - start} seconds') + new_route = calculate_dijkstra_route(old_route['config']['route_info'], dict(old_route)) + return [old_route, new_route] - return new_route \ No newline at end of file +# Test functions that use the route_pair fixture +@pytest.mark.parametrize('compare_func', [ + compare_route_coordinates, + compare_waypoint_names, + compare_time, + compare_cell_indices, + compare_cases +], ids=['coordinates', 'waypoint_names', 'time', 'cell_indices', 'cases']) +def test_route_property(route_pair, compare_func): + """Test route property matches between old and new""" + compare_func(*route_pair) + +def test_fuel_battery(route_pair): + """Test fuel/battery consumption matches between old and new""" + path_variables = route_pair[0]['config']['route_info']['path_variables'] + if 'fuel' in path_variables: + compare_fuel(*route_pair) + if 'battery' in path_variables: + compare_battery(*route_pair) \ No newline at end of file diff --git a/tests/regression_tests/test_routes_smoothed.py b/tests/regression_tests/test_routes_smoothed.py index 7d8aeae8..3da3b699 100644 --- a/tests/regression_tests/test_routes_smoothed.py +++ b/tests/regression_tests/test_routes_smoothed.py @@ -1,103 +1,55 @@ import json import pytest -import time - -from polar_route import __version__ as pr_version -from polar_route.route_planner.route_planner import RoutePlanner -from .route_test_functions import extract_waypoints -from .route_test_functions import extract_route_info - -# Import tests, which are automatically run -from .route_test_functions import test_route_coordinates -from .route_test_functions import test_waypoint_names -from .route_test_functions import test_time -from .route_test_functions import test_fuel_battery -from .route_test_functions import test_distance -from .route_test_functions import test_speed +from .utils import ( + get_route_test_files, + calculate_smoothed_route, + compare_route_coordinates, + compare_waypoint_names, + compare_time, + compare_fuel, + compare_battery, + compare_cell_indices, + compare_cases +) import logging LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.INFO) -TEST_ROUTES = [ - './example_routes/smoothed/fuel/gaussian_random_field.json', - './example_routes/smoothed/fuel/checkerboard.json', - './example_routes/smoothed/fuel/great_circle_forward.json', - './example_routes/smoothed/fuel/great_circle_reverse.json', - './example_routes/smoothed/time/gaussian_random_field.json', - './example_routes/smoothed/time/checkerboard.json', - './example_routes/smoothed/time/great_circle_forward.json', - './example_routes/smoothed/time/great_circle_reverse.json', - './example_routes/smoothed/time/multi_waypoint_blocked.json', - './example_routes/smoothed/time/single_cell.json', - './example_routes/smoothed/crossing_point/horizontal/horizontal_0lat_smooth.json', - './example_routes/smoothed/crossing_point/horizontal/horizontal_80latn_smooth.json', - './example_routes/smoothed/crossing_point/horizontal/horizontal_80lats_smooth.json', - './example_routes/smoothed/crossing_point/horizontal/horizontal_0lat_boundary_smooth.json', - './example_routes/smoothed/crossing_point/horizontal/horizontal_0lat_corner_smooth.json', - './example_routes/smoothed/crossing_point/vertical/vertical_0lat_smooth.json', - './example_routes/smoothed/crossing_point/vertical/vertical_80latn_smooth.json', - './example_routes/smoothed/crossing_point/vertical/vertical_80lats_smooth.json', - './example_routes/smoothed/crossing_point/vertical/vertical_0lat_boundary_smooth.json', - './example_routes/smoothed/crossing_point/vertical/vertical_0lat_corner_smooth.json', - './example_routes/smoothed/fuel/twin_otter_f_route.json', - './example_routes/smoothed/battery/slocum_b_route.json', - './example_routes/smoothed/battery/alr_b_route.json', - './example_routes/smoothed/time/slocum_tt_route.json', - './example_routes/smoothed/time/alr_tt_route.json', - './example_routes/smoothed/time/twin_otter_tt_route.json' -] +# Dynamically discover test files +TEST_ROUTES = get_route_test_files('smoothed') # Pairing old and new outputs -@pytest.fixture(scope='session', autouse=False, params=TEST_ROUTES) +@pytest.fixture(scope='session', params=TEST_ROUTES) def route_pair(request): - """ - Creates a pair of JSON objects, one newly generated, one as old reference - Args: - request (fixture): - fixture object including list of jsons of fuel optimised routes - - Returns: - list: [reference json, new json] - """ + """Creates pair of routes: reference JSON and newly generated.""" LOGGER.info(f'Test File: {request.param}') - - # Load reference JSON + with open(request.param, 'r') as fp: old_route = json.load(fp) - route_info = extract_route_info(old_route) - # Create new json (cast old to dict to create copy to avoid modifying) - new_route = calculate_smoothed_route(route_info, dict(old_route)) - - return [old_route, new_route] - -# Generating new outputs -def calculate_smoothed_route(config, mesh): - """ - Calculates the fuel-optimised route, with dijkstra but no smoothing - - Args: - route_filename (str): Filename of regression test route - - Returns: - json: New route output - """ - start = time.perf_counter() - - # Initial set up - waypoints = extract_waypoints(mesh) - - # Calculate smoothed route - rp = RoutePlanner(mesh, config) - dijkstra_route = rp.compute_routes(waypoints) - smoothed_route = rp.compute_smoothed_routes() - # Generate json to compare to old output - new_route = mesh - new_route['paths'] = smoothed_route - - end = time.perf_counter() - LOGGER.info(f'Route smoothed in {end - start} seconds') + new_route = calculate_smoothed_route(old_route['config']['route_info'], dict(old_route)) + return [old_route, new_route] - return new_route \ No newline at end of file +# Test functions that use the route_pair fixture +@pytest.mark.parametrize('compare_func', [ + compare_route_coordinates, + compare_waypoint_names, + compare_time, + compare_cell_indices, + compare_cases +], ids=['coordinates', 'waypoint_names', 'time', 'cell_indices', 'cases']) +@pytest.mark.slow +def test_route_property(route_pair, compare_func): + """Test route property matches between old and new""" + compare_func(*route_pair) + +@pytest.mark.slow +def test_fuel_battery(route_pair): + """Test fuel/battery consumption matches between old and new""" + path_variables = route_pair[0]['config']['route_info']['path_variables'] + if 'fuel' in path_variables: + compare_fuel(*route_pair) + if 'battery' in path_variables: + compare_battery(*route_pair) \ No newline at end of file diff --git a/tests/regression_tests/test_vessel.py b/tests/regression_tests/test_vessel.py index 1a347052..291b731c 100644 --- a/tests/regression_tests/test_vessel.py +++ b/tests/regression_tests/test_vessel.py @@ -5,92 +5,67 @@ import json import pytest -import time - -from polar_route import __version__ as pr_version -from polar_route import VesselPerformanceModeller - -# Import tests, which are automatically run -from .vessel_test_functions import test_mesh_cellbox_attributes -from .vessel_test_functions import test_mesh_cellbox_count -from .vessel_test_functions import test_mesh_cellbox_ids -from .vessel_test_functions import test_mesh_cellbox_values -from .vessel_test_functions import test_mesh_neighbour_graph_count -from .vessel_test_functions import test_mesh_neighbour_graph_ids -from .vessel_test_functions import test_mesh_neighbour_graph_values +from pathlib import Path import logging LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.INFO) -# File locations of all vessel performance meshes to be recalculated for regression testing. -INPUT_MESHES = [ - './example_meshes/env_meshes/grf_normal.json', - './example_meshes/env_meshes/grf_downsample.json', - './example_meshes/env_meshes/grf_reprojection.json', - './example_meshes/env_meshes/grf_sparse.json', - './example_meshes/env_meshes/slocum_test_mesh.json', - './example_meshes/env_meshes/alr_test_mesh.json', - './example_meshes/env_meshes/twin_otter_test_mesh.json' -] - -OUTPUT_MESHES = [ - './example_meshes/vessel_meshes/grf_normal.json', - './example_meshes/vessel_meshes/grf_downsample.json', - './example_meshes/vessel_meshes/grf_reprojection.json', - './example_meshes/vessel_meshes/grf_sparse.json', - './example_meshes/vessel_meshes/slocum_test_vessel.json', - './example_meshes/vessel_meshes/alr_test_vessel.json', - './example_meshes/vessel_meshes/twin_otter_test_vessel.json' -] - -@pytest.fixture(scope='session', autouse=False, params=zip(INPUT_MESHES, OUTPUT_MESHES)) +# Import test utilities +from .utils import ( + get_mesh_test_files, + compare_cellbox_count, + compare_cellbox_ids, + compare_cellbox_values, + compare_cellbox_attributes, + compare_neighbour_graph_count, + compare_neighbour_graph_ids, + compare_neighbour_graph_values, + calculate_vessel_mesh +) + +# Dynamically discover test files +INPUT_MESHES, OUTPUT_MESHES = get_mesh_test_files() + +# Create descriptive IDs from mesh file names +def make_mesh_id(mesh_pair): + """Create descriptive test ID from mesh file paths""" + input_mesh, output_mesh = mesh_pair + output_name = Path(output_mesh).stem + return output_name + +MESH_PAIRS = list(zip(INPUT_MESHES, OUTPUT_MESHES)) +MESH_IDS = [make_mesh_id(pair) for pair in MESH_PAIRS] + +@pytest.fixture(scope='session', params=MESH_PAIRS, ids=MESH_IDS) def mesh_pair(request): - """ - Creates a pair of JSON objects, one newly generated, one as old reference - Args: - request (fixture): - fixture object including list of meshes to regenerate - - Returns: - list: old and new mesh jsons for comparison - """ - LOGGER.info(f'Test File: {request.param[1]}') - - input_mesh_file = request.param[0] - output_mesh_file = request.param[1] - # Open vessel mesh for reference + """Creates pair of meshes: reference and newly generated.""" + input_mesh_file, output_mesh_file = request.param + LOGGER.info(f'Test File: {output_mesh_file}') + with open(output_mesh_file, 'r') as fp: old_mesh = json.load(fp) - # Open env mesh to generate new vessel mesh with open(input_mesh_file, 'r') as fp: input_mesh = json.load(fp) - # Extract out vessel config from reference mesh - vessel_config = old_mesh['config']['vessel_info'] - new_mesh = calculate_vessel_mesh(input_mesh, vessel_config) + # Merge vessel config from reference into input mesh + config = input_mesh.copy() + config.setdefault('config', {})['vessel_info'] = old_mesh['config']['vessel_info'] + + new_mesh = calculate_vessel_mesh(config) return [old_mesh, new_mesh] -def calculate_vessel_mesh(mesh_json, vessel_config): - """ - Creates a new, pruned and updated mesh from the environmental mesh - - Args: - mesh_json (json): Environmental mesh to modify with vessel parameters - vessel_config (json): Vessel information to prune the env mesh with - - Returns: - json: Newly regenerated mesh - """ - start = time.perf_counter() - - new_mesh = VesselPerformanceModeller(mesh_json, vessel_config) - new_mesh.model_accessibility() - new_mesh.model_performance() - - end = time.perf_counter() - - cellbox_count = len(new_mesh.env_mesh.agg_cellboxes) - LOGGER.info(f'Vessel simulated against {cellbox_count} cellboxes in {end - start} seconds') - - return new_mesh.to_json() \ No newline at end of file +# Test functions that use the mesh_pair fixture +@pytest.mark.parametrize('compare_func', [ + compare_cellbox_count, + compare_cellbox_ids, + compare_cellbox_values, + compare_cellbox_attributes, + compare_neighbour_graph_count, + compare_neighbour_graph_ids, + compare_neighbour_graph_values +], ids=['cellbox_count', 'cellbox_ids', 'cellbox_values', 'cellbox_attributes', + 'graph_count', 'graph_ids', 'graph_values']) +def test_vessel_mesh(mesh_pair, compare_func): + """Test vessel mesh property matches between old and new""" + compare_func(*mesh_pair) \ No newline at end of file diff --git a/tests/regression_tests/utils.py b/tests/regression_tests/utils.py new file mode 100644 index 00000000..74e4edeb --- /dev/null +++ b/tests/regression_tests/utils.py @@ -0,0 +1,319 @@ +""" +Shared utilities and helper functions for regression tests. + +This module provides utilities for: +- Test file discovery (routes, meshes) +- Data extraction from JSON structures +- Route comparison functions +- Vessel/mesh comparison functions +""" + +import logging +import time +from pathlib import Path +from typing import Dict, List, Tuple + +import numpy as np +import pandas as pd + +from polar_route.utils import round_to_sigfig +from polar_route.route_planner.route_planner import RoutePlanner +from polar_route.vessel_performance.vessel_performance_modeller import VesselPerformanceModeller + +# Configure logging +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.INFO) + +SIG_FIG_TOLERANCE = 4 + +# Test File Discovery +def get_test_data_path(relative_path: str) -> str: + """Convert relative path to absolute path based on test file location""" + test_dir = Path(__file__).parent + return str(test_dir / relative_path.lstrip('./').lstrip('/')) + +def discover_test_files(pattern: str) -> List[str]: + """Discover test files matching a glob pattern""" + test_dir = Path(__file__).parent + return [str(p) for p in test_dir.glob(pattern)] + +def get_route_test_files(route_type: str) -> List[str]: + """Get all test route files for a specific route type (dijkstra or smoothed)""" + pattern = f"example_routes/{route_type}/**/*.json" + return discover_test_files(pattern) + +def get_mesh_test_files() -> Tuple[List[str], List[str]]: + """Get input and output mesh files for vessel tests""" + input_pattern = "example_meshes/env_meshes/*.json" + output_pattern = "example_meshes/vessel_meshes/*.json" + + input_meshes = discover_test_files(input_pattern) + output_meshes = discover_test_files(output_pattern) + + # Sort to ensure consistent pairing + input_meshes.sort() + output_meshes.sort() + + return input_meshes, output_meshes + +# Route and Mesh Calculation Functions +def calculate_dijkstra_route(config: Dict, mesh: Dict) -> Dict: + """ + Calculate optimized route using Dijkstra algorithm. + + Args: + config: Route configuration dictionary + mesh: Reference mesh including routes and waypoints + + Returns: + Updated mesh with calculated Dijkstra paths + """ + start = time.perf_counter() + + waypoints = pd.DataFrame(mesh['waypoints']) + rp = RoutePlanner(mesh, config) + routes = rp.compute_routes(waypoints) + + new_route = mesh + new_route['paths'] = {"type": "FeatureCollection", "features": []} + new_route['paths']['features'] = [r.to_json() for r in routes] + + end = time.perf_counter() + LOGGER.info(f'Route calculated in {end - start:.2f} seconds') + + return new_route + +def calculate_smoothed_route(config: Dict, mesh: Dict) -> Dict: + """ + Calculate smoothed route using Dijkstra followed by smoothing. + + Args: + config: Route configuration dictionary + mesh: Reference mesh including routes and waypoints + + Returns: + Updated mesh with smoothed paths + """ + start = time.perf_counter() + + waypoints = pd.DataFrame(mesh['waypoints']) + rp = RoutePlanner(mesh, config) + rp.compute_routes(waypoints) + smoothed_route = rp.compute_smoothed_routes() + + new_route = mesh + new_route['paths'] = smoothed_route + + end = time.perf_counter() + LOGGER.info(f'Route smoothed in {end - start:.2f} seconds') + + return new_route + +def calculate_vessel_mesh(config: Dict) -> Dict: + """ + Create vessel performance mesh from environmental mesh and vessel config. + + Args: + config: Combined configuration including environmental mesh and vessel_info + + Returns: + Newly generated vessel mesh + """ + start = time.perf_counter() + + # Extract vessel config from combined config + vessel_config = config['config']['vessel_info'] + + vessel_modeller = VesselPerformanceModeller(config, vessel_config) + vessel_modeller.model_accessibility() + vessel_modeller.model_performance() + + end = time.perf_counter() + + cellbox_count = len(vessel_modeller.env_mesh.agg_cellboxes) + LOGGER.info(f'Vessel simulated against {cellbox_count} cellboxes in {end - start:.2f} seconds') + + return vessel_modeller.to_json() + +# Route Comparison Helper Functions +def _compare_property_arrays(route_a: Dict, route_b: Dict, property_name: str, + should_round: bool = True) -> None: + """ + Generic function to compare numeric arrays in route properties. + + Args: + route_a: First route + route_b: Second route + property_name: Name of the property to compare + should_round: Whether to round values to significant figures + + Raises: + AssertionError: If arrays differ beyond tolerance + """ + path_a = route_a['paths']['features'][0]['properties'] + path_b = route_b['paths']['features'][0]['properties'] + + values_a = path_a[property_name] + values_b = path_b[property_name] + + if should_round: + values_a = round_to_sigfig(values_a, sigfig=SIG_FIG_TOLERANCE) + values_b = round_to_sigfig(values_b, sigfig=SIG_FIG_TOLERANCE) + + np.testing.assert_array_equal(values_a, values_b, + err_msg=f'Difference in "{property_name}"') + +def _compare_optional_property(route_a: Dict, route_b: Dict, property_name: str) -> None: + """ + Compare property that may not exist in all route types. + + Args: + route_a: First route + route_b: Second route + property_name: Name of the property to compare + """ + path_a = route_a['paths']['features'][0]['properties'] + path_b = route_b['paths']['features'][0]['properties'] + + # Skip if property not present in either route + if property_name not in path_a or property_name not in path_b: + return + + values_a = path_a[property_name] + values_b = path_b[property_name] + + # Convert to int if CellIndices + if property_name == 'CellIndices': + values_a = [int(x) for x in values_a] + values_b = [int(x) for x in values_b] + + np.testing.assert_array_equal(values_a, values_b, + err_msg=f'Difference in "{property_name}"') + +def compare_route_coordinates(route_a: Dict, route_b: Dict) -> None: + """Compare route node coordinates.""" + coords_a = route_a['paths']['features'][0]['geometry']['coordinates'] + coords_b = route_b['paths']['features'][0]['geometry']['coordinates'] + + assert len(coords_a) == len(coords_b), \ + f"Number of nodes different! Expected {len(coords_a)}, got {len(coords_b)}" + + for axis in [0, 1]: # x and y coordinates + rounded_a = round_to_sigfig(coords_a[:][axis], sigfig=SIG_FIG_TOLERANCE) + rounded_b = round_to_sigfig(coords_b[:][axis], sigfig=SIG_FIG_TOLERANCE) + np.testing.assert_array_equal(rounded_a, rounded_b, + err_msg='Difference in "route_coordinates"') + +def compare_waypoint_names(route_a: Dict, route_b: Dict) -> None: + """Compare source and destination waypoint names.""" + path_a = route_a['paths']['features'][0]['properties'] + path_b = route_b['paths']['features'][0]['properties'] + + assert path_a['from'] == path_b['from'], \ + f"Waypoint source names don't match! Expected {path_a['from']}, got {path_b['from']}" + assert path_a['to'] == path_b['to'], \ + f"Waypoint destination names don't match! Expected {path_a['to']}, got {path_b['to']}" + +# Simple property comparison wrappers +compare_time = lambda route_a, route_b: _compare_property_arrays(route_a, route_b, 'traveltime') +compare_fuel = lambda route_a, route_b: _compare_property_arrays(route_a, route_b, 'fuel') +compare_battery = lambda route_a, route_b: _compare_property_arrays(route_a, route_b, 'battery') +compare_distance = lambda route_a, route_b: _compare_property_arrays(route_a, route_b, 'distance') +compare_speed = lambda route_a, route_b: _compare_property_arrays(route_a, route_b, 'speed') +compare_cell_indices = lambda route_a, route_b: _compare_optional_property(route_a, route_b, 'CellIndices') +compare_cases = lambda route_a, route_b: _compare_optional_property(route_a, route_b, 'cases') + +# Vessel/Mesh Comparison Helper Functions +def _compare_set_difference(set_a: set, set_b: set) -> Tuple[List, List]: + """Helper to compute and return set differences for error messages.""" + missing_from_a = list(set_b - set_a) + missing_from_b = list(set_a - set_b) + return missing_from_a, missing_from_b + +def compare_cellbox_count(mesh_a: Dict, mesh_b: Dict) -> None: + """Compare number of cellboxes between meshes.""" + assert len(mesh_a['cellboxes']) == len(mesh_b['cellboxes']), \ + f"Incorrect number of cellboxes. Expected: {len(mesh_a['cellboxes'])}, got: {len(mesh_b['cellboxes'])}" + +def compare_cellbox_ids(mesh_a: Dict, mesh_b: Dict) -> None: + """Compare cellbox IDs between meshes.""" + ids_a = {cb['id'] for cb in mesh_a['cellboxes']} + ids_b = {cb['id'] for cb in mesh_b['cellboxes']} + + missing_from_a, missing_from_b = _compare_set_difference(ids_a, ids_b) + + assert ids_a == ids_b, \ + f"Mismatch in cellbox IDs. New IDs: {missing_from_a}, Missing IDs: {missing_from_b}" + +def compare_cellbox_values(mesh_a: Dict, mesh_b: Dict) -> None: + """Compare cellbox attribute values between meshes.""" + df_a = pd.DataFrame(mesh_a['cellboxes']).set_index('geometry') + df_b = pd.DataFrame(mesh_b['cellboxes']).set_index('geometry') + + # Extract only common boundaries, drop ID as it may differ + bounds_a = [cb['geometry'] for cb in mesh_a['cellboxes']] + bounds_b = [cb['geometry'] for cb in mesh_b['cellboxes']] + common_bounds = [geom for geom in bounds_a if geom in bounds_b] + df_a = df_a.loc[common_bounds].drop(columns=['id']) + df_b = df_b.loc[common_bounds].drop(columns=['id']) + + # Round float values and floats in lists + for df in [df_a, df_b]: + # Round float columns + for col in df.select_dtypes(include=float).columns: + df[col] = round_to_sigfig(df[col].to_numpy(), sigfig=SIG_FIG_TOLERANCE) + + # Round floats within list columns + for col in df.select_dtypes(include=list).columns: + df[col] = df[col].apply(lambda val: + round_to_sigfig(val, sigfig=SIG_FIG_TOLERANCE) + if isinstance(val, list) and all(isinstance(x, float) for x in val) + else val + ) + + diff = df_a.compare(df_b).rename({'self': 'old', 'other': 'new'}) + assert len(diff) == 0, \ + f'Mismatch in common cellbox values:\n{diff.to_string(max_colwidth=10)}' + +def compare_cellbox_attributes(mesh_a: Dict, mesh_b: Dict) -> None: + """Compare cellbox attribute names between meshes.""" + attrs_a = set(mesh_a['cellboxes'][0].keys()) + attrs_b = set(mesh_b['cellboxes'][0].keys()) + + missing_from_a, missing_from_b = _compare_set_difference(attrs_a, attrs_b) + + assert attrs_a == attrs_b, \ + f"Mismatch in cellbox attributes. New: {missing_from_a}, Missing: {missing_from_b}" + +def compare_neighbour_graph_count(mesh_a: Dict, mesh_b: Dict) -> None: + """Compare number of nodes in neighbour graphs.""" + assert len(mesh_a['neighbour_graph']) == len(mesh_b['neighbour_graph']), \ + f"Incorrect node count. Expected: {len(mesh_a['neighbour_graph'])}, got: {len(mesh_b['neighbour_graph'])}" + +def compare_neighbour_graph_ids(mesh_a: Dict, mesh_b: Dict) -> None: + """Compare node IDs in neighbour graphs.""" + ids_a = set(mesh_a['neighbour_graph'].keys()) + ids_b = set(mesh_b['neighbour_graph'].keys()) + + missing_from_a, missing_from_b = _compare_set_difference(ids_a, ids_b) + + assert ids_a == ids_b, \ + f"Mismatch in graph nodes. New: {len(missing_from_a)}, Missing: {len(missing_from_b)}" + +def compare_neighbour_graph_values(mesh_a: Dict, mesh_b: Dict) -> None: + """Compare neighbour relationships in graphs.""" + graph_a = mesh_a['neighbour_graph'] + graph_b = mesh_b['neighbour_graph'] + + mismatches = {} + for node in graph_a.keys(): + if node in graph_b: # Node existence checked by compare_neighbour_graph_ids + # Sort neighbours as order doesn't matter + sorted_a = {k: sorted(v) for k, v in graph_a[node].items()} + sorted_b = {k: sorted(v) for k, v in graph_b[node].items()} + + if sorted_a != sorted_b: + mismatches[node] = sorted_b + + assert len(mismatches) == 0, \ + f"Mismatch in neighbour relationships. {len(mismatches)} nodes changed." diff --git a/tests/regression_tests/vessel_test_functions.py b/tests/regression_tests/vessel_test_functions.py deleted file mode 100644 index d1156e7e..00000000 --- a/tests/regression_tests/vessel_test_functions.py +++ /dev/null @@ -1,290 +0,0 @@ -import pandas as pd - -from polar_route.utils import round_to_sigfig - -SIG_FIG_TOLERANCE = 4 - -# Testing mesh outputs -def test_mesh_cellbox_count(mesh_pair): - compare_cellbox_count(mesh_pair[0], mesh_pair[1]) - -def test_mesh_cellbox_ids(mesh_pair): - compare_cellbox_ids(mesh_pair[0], mesh_pair[1]) - -def test_mesh_cellbox_values(mesh_pair): - compare_cellbox_values(mesh_pair[0], mesh_pair[1]) - -def test_mesh_cellbox_attributes(mesh_pair): - compare_cellbox_attributes(mesh_pair[0], mesh_pair[1]) - -def test_mesh_neighbour_graph_count(mesh_pair): - compare_neighbour_graph_count(mesh_pair[0], mesh_pair[1]) - -def test_mesh_neighbour_graph_ids(mesh_pair): - compare_neighbour_graph_ids(mesh_pair[0], mesh_pair[1]) - -def test_mesh_neighbour_graph_values(mesh_pair): - compare_neighbour_graph_values(mesh_pair[0], mesh_pair[1]) - - -# Comparison between old and new -def compare_cellbox_count(mesh_a, mesh_b): - """ - Test if two provided meshes contain the same number of cellboxes - - Args: - mesh_a (json) - mesh_b (json) - - Throws: - Fails if the number of cellboxes in regression_mesh and new_mesh are - not equal - """ - regression_mesh = extract_cellboxes(mesh_a) - new_mesh = extract_cellboxes(mesh_b) - - cellbox_count_a = len(regression_mesh) - cellbox_count_b = len(new_mesh) - - assert(cellbox_count_a == cellbox_count_b), \ - f"Incorrect number of cellboxes in new mesh. "\ - f"Expected :{cellbox_count_a}, got: {cellbox_count_b}" - -def compare_cellbox_ids(mesh_a, mesh_b): - """ - Test if two provided meshes contain cellboxes with the same IDs - - Args: - mesh_a (json) - mesh_b (json) - - Throws: - Fails if any cellbox exists in regression_mesh that or not in new_mesh, - or any cellbox exists in new_mesh that is not in regression_mesh - """ - regression_mesh = extract_cellboxes(mesh_a) - new_mesh = extract_cellboxes(mesh_b) - - indxed_a = dict() - for cellbox in regression_mesh: - indxed_a[cellbox['id']] = cellbox - - indxed_b = dict() - for cellbox in new_mesh: - indxed_b[cellbox['id']] = cellbox - - regression_mesh_ids = set(indxed_a.keys()) - new_mesh_ids = set(indxed_b.keys()) - - missing_a_ids = list(new_mesh_ids - regression_mesh_ids) - missing_b_ids = list(regression_mesh_ids - new_mesh_ids) - - assert(indxed_a.keys() == indxed_b.keys()), \ - f"Mismatch in cellbox IDs. ID's {missing_a_ids} have appeared in the "\ - f"new mesh. ID's {missing_b_ids} are missing from the new mesh" - -def compare_cellbox_values(mesh_a, mesh_b): - """ - Tests if the values in of all attributes in each cellbox and the - same in both provided meshes. - - Args: - mesh_a (json) - mesh_b (json) - - Throws: - Fails if any values of any attributes differ between regression_mesh - and new_mesh - """ - # Retrieve cellboxes from meshes - df_a = pd.DataFrame(extract_cellboxes(mesh_a)).set_index('geometry') - df_b = pd.DataFrame(extract_cellboxes(mesh_b)).set_index('geometry') - # Extract only cellboxes with same boundaries - # Drop ID as that may be different despite same boundary - df_a = df_a.loc[extract_common_boundaries(mesh_a, mesh_b)].drop(columns=['id']) - df_b = df_b.loc[extract_common_boundaries(mesh_a, mesh_b)].drop(columns=['id']) - # Ignore cellboxes with different boundaries, that will be picked up in other tests - - # For each mesh - for df in [df_a, df_b]: - # Round to sig figs if column contains floats - float_cols = df.select_dtypes(include=float).columns - for col in float_cols: - df[col] = round_to_sigfig(df[col].to_numpy(), - sigfig=SIG_FIG_TOLERANCE) - # Round to sig figs if column contains list, which may contain floats - list_cols = df.select_dtypes(include=list).columns - # Loop through columns and round any values within lists of floats - for col in list_cols: - round_col = list() - for val in df[col]: - if type(val) == list and all([type(x) == float for x in val]): - round_col.append(round_to_sigfig(val, sigfig=SIG_FIG_TOLERANCE)) - else: - round_col.append(val) - - df[col] = round_col - - - # Find difference between the two - diff = df_a.compare(df_b).rename({'self': 'old', 'other':'new'}) - - assert(len(diff) == 0), \ - f'Mismatch between values in common cellboxes:\n'\ - f'{diff.to_string(max_colwidth=10)}' - -def compare_cellbox_attributes(mesh_a, mesh_b): - """ - Tests if the attributes of cellboxes in regression_mesh and the same as - attributes of cellboxes in new_mesh - - Note: - This assumes that every cellbox in mesh has the same amount - of attributes, so only compares the attributes of the first - two cellboxes in the mesh - - Args: - mesh_a (json) - mesh_b (json) - - Throws: - Fails if the cellboxes in the provided meshes contain different - attributes - """ - regression_mesh = extract_cellboxes(mesh_a) - new_mesh = extract_cellboxes(mesh_b) - - regression_mesh_attributes = set(regression_mesh[0].keys()) - new_mesh_attributes = set(new_mesh[0].keys()) - - missing_a_attributes = list(new_mesh_attributes - regression_mesh_attributes) - missing_b_attributes = list(regression_mesh_attributes - new_mesh_attributes) - - assert(regression_mesh_attributes == new_mesh_attributes), \ - f"Mismatch in cellbox attributes. Attributes {missing_a_attributes} "\ - f"have appeared in the new mesh. Attributes {missing_b_attributes} "\ - f"are missing in the new mesh" - -def compare_neighbour_graph_count(mesh_a, mesh_b): - """ - Tests that the neighbour_graph in the regression mesh and the newly calculated mesh have the - same number of nodes. - - Args: - mesh_a (json) - mesh_b (json) - - """ - regression_graph = extract_neighbour_graph(mesh_a) - new_graph = extract_neighbour_graph(mesh_b) - - regression_graph_count = len(regression_graph.keys()) - new_graph_count = len(new_graph.keys()) - - assert(regression_graph_count == new_graph_count), \ - f"Incorrect number of nodes in neighbour graph. "\ - f"Expected: <{regression_graph_count}> nodes, "\ - f"got: <{new_graph_count}> nodes." - -def compare_neighbour_graph_ids(mesh_a, mesh_b): - """ - Tests that the neighbour_graph in the regression mesh and the newly calculated mesh contain - all the same node IDs. - - Args: - mesh_a (json) - mesh_b (json) - """ - regression_graph = extract_neighbour_graph(mesh_a) - new_graph = extract_neighbour_graph(mesh_b) - - regression_graph_ids = set(regression_graph.keys()) - new_graph_ids = set(new_graph.keys()) - - missing_a_keys = list(new_graph_ids - regression_graph_ids) - missing_b_keys = list(regression_graph_ids - new_graph_ids) - - assert(regression_graph_ids == new_graph_ids) , \ - f"Mismatch in neighbour graph nodes. <{len(missing_a_keys)}> nodes "\ - f"have appeared in the new neighbour graph. <{len(missing_b_keys)}> "\ - f"nodes are missing from the new neighbour graph." - -def compare_neighbour_graph_values(mesh_a, mesh_b): - """ - Tests that each node in the neighbour_graph of the regression mesh and the newly calculated - mesh have the same neighbours. - - Args: - mesh_a (json) - mesh_b (json) - - """ - regression_graph = extract_neighbour_graph(mesh_a) - new_graph = extract_neighbour_graph(mesh_b) - - mismatch_neighbours = dict() - - for node in regression_graph.keys(): - # Prevent crashing if node not found. - # This will be detected by 'test_neighbour_graph_ids'. - if node in new_graph.keys(): - neighbours_a = regression_graph[node] - neighbours_b = new_graph[node] - - # Sort the lists of neighbours as ordering is not important - sorted_neighbours_a = {k:sorted(neighbours_a[k]) - for k in neighbours_a.keys()} - sorted_neighbours_b = {k: sorted(neighbours_b[k]) - for k in neighbours_b.keys()} - - if sorted_neighbours_b != sorted_neighbours_a: - mismatch_neighbours[node] = sorted_neighbours_b - - assert(len(mismatch_neighbours) == 0), \ - f"Mismatch in neighbour graph neighbours. "\ - f"<{len(mismatch_neighbours.keys())}> nodes have changed in the new mesh." - - - -# Utility functions -def extract_neighbour_graph(mesh): - """ - Extracts out the neighbour graph from a mesh - - Args: - mesh (json): Complete mesh output - - Returns: - dict: Neighbour graph for each cellbox - """ - return mesh['neighbour_graph'] - -def extract_cellboxes(mesh): - """ - Extracts out the cellboxes and aggregated info from a mesh - - Args: - mesh (json): Complete mesh output - - Returns: - list: Each cellbox as a dict/json object, in a list - """ - return mesh['cellboxes'] - -def extract_common_boundaries(mesh_a, mesh_b): - """ - Creates a list of common boundaries between two mesh jsons - - Args: - mesh_a (json): First mesh json to extract boundaries from - mesh_b (json): Second mesh json to extract boundaries from - - Returns: - list: List of common cellbox boundaries (as strings) - """ - bounds_a = [cb['geometry'] for cb in extract_cellboxes(mesh_a)] - bounds_b = [cb['geometry'] for cb in extract_cellboxes(mesh_b)] - - common_bounds = [geom for geom in bounds_a if geom in bounds_b] - - return common_bounds \ No newline at end of file diff --git a/tests/requirements.txt b/tests/requirements.txt index e079f8a6..9cda381d 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1 +1,2 @@ pytest +pytest-xdist diff --git a/tests/unit_tests/test_route_calc.py b/tests/unit_tests/test_route_calc.py index 510e4e67..4146b2ef 100644 --- a/tests/unit_tests/test_route_calc.py +++ b/tests/unit_tests/test_route_calc.py @@ -1,45 +1,17 @@ -import unittest +import pytest import numpy as np from polar_route.route_planner.crossing import traveltime_in_cell -class TestRouteCalc(unittest.TestCase): - - def test_traveltime_in_cell(self): - bx = 30 - by = 15 - cx = 2 - cy = -4 - s = np.sqrt(65) - - tt = traveltime_in_cell(bx, by, cx, cy, s) - - expected_tt = 5.0 - - self.assertAlmostEqual(tt, expected_tt, places=5) - - def test_traveltime_in_cell_reverse_current(self): - bx = 30 - by = 15 - cx = -2 - cy = -4 - s = np.sqrt(65) - - tt = traveltime_in_cell(bx, by, cx, cy, s) - - expected_tt = 8.33333 - - self.assertAlmostEqual(tt, expected_tt, places=5) - - def test_traveltime_in_cell_slow_vessel(self): - bx = 30 - by = 15 - cx = 2 - cy = -4 - s = 5 - - tt = traveltime_in_cell(bx, by, cx, cy, s) - - expected_tt = 15.0 - - self.assertAlmostEqual(tt, expected_tt, places=5) +@pytest.mark.parametrize( + "bx, by, cx, cy, speed, expected_tt, test_id", + [ + (30, 15, 2, -4, np.sqrt(65), 5.0, "normal_current"), + (30, 15, -2, -4, np.sqrt(65), 8.33333, "reverse_current"), + (30, 15, 2, -4, 5, 15.0, "slow_vessel"), + ], +) +def test_traveltime_in_cell(bx, by, cx, cy, speed, expected_tt, test_id): + """Test travel time calculation in cell with various conditions.""" + tt = traveltime_in_cell(bx, by, cx, cy, speed) + assert tt == pytest.approx(expected_tt, abs=1e-5) diff --git a/tests/unit_tests/test_vessel_performance.py b/tests/unit_tests/test_vessel_performance.py index 01f1e844..dbc54237 100644 --- a/tests/unit_tests/test_vessel_performance.py +++ b/tests/unit_tests/test_vessel_performance.py @@ -1,4 +1,4 @@ -import unittest +import pytest from copy import copy import numpy as np from polar_route.vessel_performance.vessels.SDA import SDA, wind_resistance, wind_mag_dir, c_wind, calc_wind, fuel_eq @@ -6,407 +6,248 @@ from meshiphi.mesh_generation.boundary import Boundary -class TestSDA(unittest.TestCase): - def setUp(self): - config = { - "vessel_type": "SDA", - "max_speed": 26.5, - "unit": "km/hr", - "beam": 24.0, - "hull_type": "slender", - "force_limit": 96634.5, - "max_ice_conc": 80, - "min_depth": -10 - } - boundary = Boundary([-85, -84.9], [-135, -134.9], ['1970-01-01', '2021-12-31']) - - self.cellbox = AggregatedCellBox(boundary, {}, '0') - self.SDA = SDA(config) - - def test_land(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'elevation': -100. - } - - actual = self.SDA.land(cellbox) - expected = False - self.assertEqual(actual, expected) - - def test_extreme_ice(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'SIC': 100. - } - - actual = self.SDA.extreme_ice(cellbox) - expected = True - self.assertEqual(actual, expected) - - def test_model_speed_open_water(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': 26.5, - 'SIC': 0., - 'thickness': 0., - 'density': 0. - } - - actual = self.SDA.model_speed(cellbox).agg_data - - expected = { - 'speed': [26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5], - 'SIC': 0., - 'thickness': 0., - 'density': 0., - 'ice resistance': 0. - } - - self.assertEqual(actual, expected) - - def test_model_speed_ice(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': 26.5, - 'SIC': 60., - 'thickness': 1., - 'density': 980. - } - - actual = self.SDA.model_speed(cellbox).agg_data - - expected = { - 'speed': [7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, - 7.842665122593933, 7.842665122593933, 7.842665122593933], - 'SIC': 60., - 'thickness': 1., - 'density': 980., - 'ice resistance': 96634.5, - } - - self.assertEqual(actual, expected) - - def test_model_resistance_open_water(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': [26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5], - 'SIC': 0., - 'thickness': 0., - 'density': 0., - 'ice resistance': 0. - } - - actual =self.SDA.model_resistance(cellbox).agg_data - - expected = { - 'speed': [26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5], - 'SIC': 0., - 'thickness': 0., - 'density': 0., - 'ice resistance': 0., - 'resistance': [0., 0., 0., 0., 0., 0., 0., 0.] - } - - self.assertEqual(actual, expected) - - def test_model_resistance_ice(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': [7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, - 7.842665122593933, 7.842665122593933, 7.842665122593933], - 'SIC': 60., - 'thickness': 1., - 'density': 980., - 'ice resistance': 96634.5, - } - - actual =self.SDA.model_resistance(cellbox).agg_data - - expected = { - 'speed': [7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, - 7.842665122593933, 7.842665122593933, 7.842665122593933], - 'SIC': 60., - 'thickness': 1., - 'density': 980., - 'ice resistance': 96634.5, - 'resistance': [96634.5, 96634.5, 96634.5, 96634.5, 96634.5, 96634.5, 96634.5, 96634.5] - } - - self.assertEqual(actual, expected) - - def test_model_fuel_open_water(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': [26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5], - 'SIC': 0., - 'thickness': 0., - 'density': 0., - 'ice resistance': 0., - 'resistance': [0., 0., 0., 0., 0., 0., 0., 0.] - } - - actual =self.SDA.model_fuel(cellbox).agg_data - - expected = { - 'speed': [26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5], - 'SIC': 0., - 'thickness': 0., - 'density': 0., - 'ice resistance': 0., - 'resistance': [0., 0., 0., 0., 0., 0., 0., 0.], - 'fuel': [27.3186897, 27.3186897, 27.3186897, 27.3186897, 27.3186897, 27.3186897, 27.3186897, 27.3186897] - } - - self.assertEqual(actual, expected) - - def test_model_fuel_ice(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': [7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, - 7.842665122593933, 7.842665122593933, 7.842665122593933], - 'SIC': 60., - 'thickness': 1., - 'density': 980., - 'ice resistance': 96634.5, - 'resistance': [96634.5, 96634.5, 96634.5, 96634.5, 96634.5, 96634.5, 96634.5, 96634.5] - } - - actual =self.SDA.model_fuel(cellbox).agg_data - - expected = { - 'speed': [7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, - 7.842665122593933, 7.842665122593933, 7.842665122593933], - 'SIC': 60.0, - 'thickness': 1.0, - 'density': 980.0, - 'ice resistance': 96634.5, - 'resistance': [96634.5, 96634.5, 96634.5, 96634.5, 96634.5, 96634.5, 96634.5, 96634.5], - 'fuel': [39.94376930737089, 39.94376930737089, 39.94376930737089, 39.94376930737089, 39.94376930737089, - 39.94376930737089, 39.94376930737089, 39.94376930737089] - } - - - self.assertEqual(actual, expected) - - def test_ice_resistance_zero(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': 0., - 'SIC': 0., - 'thickness': 0., - 'density':0. - } - - actual = self.SDA.ice_resistance(cellbox) - expected = 0. - self.assertEqual(actual, expected) - - def test_ice_resistance_pos(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': 5.56, - 'SIC': 60., - 'thickness': 1., - 'density': 980. - } - - actual = self.SDA.ice_resistance(cellbox) - expected = 64543.75549708632 - self.assertAlmostEqual(actual, expected, places=5) - - def test_invert_resistance_zero(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': 26.5, - 'SIC': 0., - 'thickness': 0., - 'density': 0. - } - - actual = self.SDA.invert_resistance(cellbox) - expected = 26.5 - self.assertAlmostEqual(actual, expected, places=5) - - def test_invert_resistance_pos(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': 26.5, - 'SIC': 60., - 'thickness': 1., - 'density': 980. - } - - actual = self.SDA.invert_resistance(cellbox) - expected = 7.842665122593933 - self.assertAlmostEqual(actual, expected, places=5) - - def test_fuel_eq_hotel(self): - actual = fuel_eq(0., 0.) - expected = 6.06970392 - self.assertAlmostEqual(actual, expected, places=5) - - def test_fuel_eq_open_water(self): - actual = fuel_eq(26.5, 0) - expected = 27.3186897 - self.assertAlmostEqual(actual, expected, places=5) - - def test_fuel_eq_ice_breaking(self): - actual = fuel_eq(5.56, 64543.76) - expected = 24.48333122037351 - self.assertAlmostEqual(actual, expected, places=5) - - def test_wind_coeff_zero(self): - actual = c_wind(0.) - expected = 0.94 - self.assertEqual(actual, expected) - - def test_wind_coeff_interp(self): - actual = c_wind(np.pi/4.) - expected = 0.595 - self.assertEqual(actual, expected) - - def test_wind_resistance_zero(self): - actual = wind_resistance(0.,0.,0.) - expected = 0. - self.assertEqual(actual, expected) - - def test_wind_resistance_equal_opposite(self): - actual = wind_resistance(10., 10., 0.) - expected = 0. - self.assertEqual(actual, expected) - - def test_wind_resistance_pos(self): - actual = wind_resistance(10., 20., 0.) - expected = 129543.75 - self.assertAlmostEqual(actual, expected, places=5) - - def test_wind_resistance_neg(self): - actual = wind_resistance(10., 20., np.pi) - expected = -105656.25 - self.assertAlmostEqual(actual, expected, places=5) - - def test_wind_mag_dir_zero(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': [0., 0., 0., 0., 0., 0., 0., 0.], - 'u10': 0., - 'v10': 0. - } - actual = wind_mag_dir(cellbox, 0.) - expected = (0., 0.) - self.assertEqual(actual, expected) - - def test_wind_mag_dir_north(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': [10., 10., 10., 10., 10., 10., 10., 10.], - 'u10': 0., - 'v10': 10. - } - actual = wind_mag_dir(cellbox, 0.) - expected = (7.22222, np.pi) - self.assertAlmostEqual(actual[0], expected[0], places=5) - self.assertAlmostEqual(actual[1], expected[1], places=5) - - def test_wind_mag_dir_east(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': [10., 10., 10., 10., 10., 10., 10., 10.], - 'u10': 10., - 'v10': 0. - } - actual = wind_mag_dir(cellbox, 0.) - expected = (10.378634, 1.299849) - self.assertAlmostEqual(actual[0], expected[0], places=5) - self.assertAlmostEqual(actual[1], expected[1], places=5) - - def test_calc_wind_zero(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': [0., 0., 0., 0., 0., 0., 0., 0.], - 'u10': 0., - 'v10': 0. - } - actual = calc_wind(cellbox).agg_data - - expected = { - 'speed': [0., 0., 0., 0., 0., 0., 0., 0.], - 'u10': 0., - 'v10': 0., - 'wind resistance': [0, 0, 0, 0, 0, 0, 0, 0], - 'relative wind speed': [0, 0, 0, 0, 0, 0, 0, 0], - 'relative wind angle': [0, 0, 0, 0, 0, 0, 0, 0] - } - self.assertEqual(actual, expected) - - def test_calc_wind_north(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': [10., 10., 10., 10., 10., 10., 10., 10.], - 'u10': 0., - 'v10': 10. - } - actual = calc_wind(cellbox).agg_data - - expected = { - 'speed': [10., 10., 10., 10., 10., 10., 10., 10.], - 'u10': 0.0, - 'v10': 10.0, +@pytest.fixture +def sda_vessel(): + """Fixture providing SDA vessel instance.""" + config = { + "vessel_type": "SDA", + "max_speed": 26.5, + "unit": "km/hr", + "beam": 24.0, + "hull_type": "slender", + "force_limit": 96634.5, + "max_ice_conc": 80, + "min_depth": -10 + } + return SDA(config) + + +@pytest.fixture +def base_cellbox(): + """Fixture providing base cellbox instance.""" + boundary = Boundary([-85, -84.9], [-135, -134.9], ['1970-01-01', '2021-12-31']) + return AggregatedCellBox(boundary, {}, '0') + + +def test_land(sda_vessel, base_cellbox): + """Test land detection returns False for navigable depth.""" + base_cellbox.agg_data = {'elevation': -100.} + assert sda_vessel.land(base_cellbox) == False + + +def test_extreme_ice(sda_vessel, base_cellbox): + """Test extreme ice detection for 100% concentration.""" + base_cellbox.agg_data = {'SIC': 100.} + assert sda_vessel.extreme_ice(base_cellbox) == True + +def test_extreme_ice(sda_vessel, base_cellbox): + """Test extreme ice detection for 100% concentration.""" + base_cellbox.agg_data = {'SIC': 100.} + assert sda_vessel.extreme_ice(base_cellbox) == True + + +@pytest.mark.parametrize( + "agg_data, expected", + [ + # Open water case + ({'speed': 26.5, 'SIC': 0., 'thickness': 0., 'density': 0.}, + {'speed': [26.5]*8, 'SIC': 0., 'thickness': 0., 'density': 0., 'ice resistance': 0.}), + # Ice case + ({'speed': 26.5, 'SIC': 60., 'thickness': 1., 'density': 980.}, + {'speed': [7.842665122593933]*8, 'SIC': 60., 'thickness': 1., 'density': 980., 'ice resistance': 96634.5}), + ], + ids=['open_water', 'ice'] +) +def test_model_speed(sda_vessel, base_cellbox, agg_data, expected): + """Test speed modeling in open water and ice conditions.""" + base_cellbox.agg_data = agg_data + result = sda_vessel.model_speed(base_cellbox).agg_data + assert result == expected + + +@pytest.mark.parametrize( + "agg_data, expected", + [ + # Open water + ({'speed': [26.5]*8, 'SIC': 0., 'thickness': 0., 'density': 0., 'ice resistance': 0.}, + {'speed': [26.5]*8, 'SIC': 0., 'thickness': 0., 'density': 0., 'ice resistance': 0., 'resistance': [0.]*8}), + # Ice + ({'speed': [7.842665122593933]*8, 'SIC': 60., 'thickness': 1., 'density': 980., 'ice resistance': 96634.5}, + {'speed': [7.842665122593933]*8, 'SIC': 60., 'thickness': 1., 'density': 980., 'ice resistance': 96634.5, 'resistance': [96634.5]*8}), + ], + ids=['open_water', 'ice'] +) +def test_model_resistance(sda_vessel, base_cellbox, agg_data, expected): + """Test resistance modeling in open water and ice conditions.""" + base_cellbox.agg_data = agg_data + result = sda_vessel.model_resistance(base_cellbox).agg_data + assert result == expected + + +@pytest.mark.parametrize( + "agg_data, expected_fuel", + [ + # Open water + ({'speed': [26.5]*8, 'SIC': 0., 'thickness': 0., 'density': 0., 'ice resistance': 0., 'resistance': [0.]*8}, + [27.3186897]*8), + # Ice + ({'speed': [7.842665122593933]*8, 'SIC': 60., 'thickness': 1., 'density': 980., 'ice resistance': 96634.5, 'resistance': [96634.5]*8}, + [39.94376930737089]*8), + ], + ids=['open_water', 'ice'] +) +def test_model_fuel(sda_vessel, base_cellbox, agg_data, expected_fuel): + """Test fuel modeling in open water and ice conditions.""" + base_cellbox.agg_data = agg_data + result = sda_vessel.model_fuel(base_cellbox).agg_data + assert result['fuel'] == expected_fuel + + +@pytest.mark.parametrize( + "speed, sic, thickness, density, expected", + [ + (0., 0., 0., 0., 0.), + (5.56, 60., 1., 980., 64543.75549708632), + ], + ids=['zero', 'positive'] +) +def test_ice_resistance(sda_vessel, base_cellbox, speed, sic, thickness, density, expected): + """Test ice resistance calculation.""" + base_cellbox.agg_data = {'speed': speed, 'SIC': sic, 'thickness': thickness, 'density': density} + result = sda_vessel.ice_resistance(base_cellbox) + assert result == pytest.approx(expected, abs=1e-5) + + +@pytest.mark.parametrize( + "speed, sic, thickness, density, expected", + [ + (26.5, 0., 0., 0., 26.5), + (26.5, 60., 1., 980., 7.842665122593933), + ], + ids=['zero_resistance', 'positive_resistance'] +) +def test_invert_resistance(sda_vessel, base_cellbox, speed, sic, thickness, density, expected): + """Test resistance inversion to calculate achievable speed.""" + base_cellbox.agg_data = {'speed': speed, 'SIC': sic, 'thickness': thickness, 'density': density} + result = sda_vessel.invert_resistance(base_cellbox) + assert result == pytest.approx(expected, abs=1e-5) + + +@pytest.mark.parametrize( + "speed, resistance, expected", + [ + (0., 0., 6.06970392), # Hotel load + (26.5, 0., 27.3186897), # Open water + (5.56, 64543.76, 24.48333122037351), # Ice breaking + ], + ids=['hotel', 'open_water', 'ice_breaking'] +) +def test_fuel_eq(speed, resistance, expected): + """Test fuel equation calculation.""" + result = fuel_eq(speed, resistance) + assert result == pytest.approx(expected, abs=1e-5) + + +@pytest.mark.parametrize( + "angle, expected", + [(0., 0.94), (np.pi/4., 0.595)], + ids=['zero', 'interpolated'] +) +def test_wind_coeff(angle, expected): + """Test wind coefficient calculation.""" + assert c_wind(angle) == expected + + +@pytest.mark.parametrize( + "v_vessel, v_wind, wind_dir, expected", + [ + (0., 0., 0., 0.), + (10., 10., 0., 0.), # Equal opposite + (10., 20., 0., 129543.75), + (10., 20., np.pi, -105656.25), + ], + ids=['zero', 'equal_opposite', 'positive', 'negative'] +) +def test_wind_resistance(v_vessel, v_wind, wind_dir, expected): + """Test wind resistance calculation.""" + result = wind_resistance(v_vessel, v_wind, wind_dir) + assert result == pytest.approx(expected, abs=1e-5) + + +@pytest.mark.parametrize( + "speed, u10, v10, expected", + [ + ([0.]*8, 0., 0., (0., 0.)), + ([10.]*8, 0., 10., (7.22222, np.pi)), + ([10.]*8, 10., 0., (10.378634, 1.299849)), + ], + ids=['zero', 'north', 'east'] +) +def test_wind_mag_dir(base_cellbox, speed, u10, v10, expected): + """Test wind magnitude and direction calculation.""" + base_cellbox.agg_data = {'speed': speed, 'u10': u10, 'v10': v10} + result = wind_mag_dir(base_cellbox, 0.) + assert result[0] == pytest.approx(expected[0], abs=1e-5) + assert result[1] == pytest.approx(expected[1], abs=1e-5) + + +@pytest.mark.parametrize( + "speed, u10, v10, expected", + [ + # Zero wind + ([0.]*8, 0., 0., { + 'speed': [0.]*8, 'u10': 0., 'v10': 0., + 'wind resistance': [0]*8, + 'relative wind speed': [0]*8, + 'relative wind angle': [0]*8 + }), + # Northerly wind + ([10.]*8, 0., 10., { + 'speed': [10.]*8, 'u10': 0.0, 'v10': 10.0, 'wind resistance': [1389.4514464984172, 18883.168370819058, 44192.363791332944, 67170.852525, 44192.36379133295, 18883.168370819058, 1389.45144649843, -11478.704021299203], 'relative wind speed': [8.272382984093076, 10.378634868247365, 12.124347537962088, 12.77778, 12.124347537962088, 10.378634868247365, 8.272382984093076, 7.22222], 'relative wind angle': [2.11646579563727, 1.299849270152763, 0.6226774988830187, 0.0, 0.6226774988830185, 1.2998492701527629, 2.1164657956372697, 3.141592653589793] - } - - self.assertEqual(actual, expected) - - def test_calc_wind_east(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': [10., 10., 10., 10., 10., 10., 10., 10.], - 'u10': 10., - 'v10': 0. - } - actual = calc_wind(cellbox).agg_data - - expected = { - 'speed': [10., 10., 10., 10., 10., 10., 10., 10.], - 'u10': 10.0, - 'v10': 0.0, + }), + # Easterly wind + ([10.]*8, 10., 0., { + 'speed': [10.]*8, 'u10': 10.0, 'v10': 0.0, 'wind resistance': [1389.45144649843, -11478.704021299203, 1389.4514464984172, 18883.168370819058, 44192.363791332944, 67170.852525, 44192.363791332944, 18883.168370819058], 'relative wind speed': [8.272382984093076, 7.22222, 8.272382984093076, 10.378634868247365, 12.124347537962088, 12.77778, 12.124347537962088, 10.378634868247365], 'relative wind angle': [2.1164657956372697, 3.141592653589793, 2.11646579563727, 1.299849270152763, 0.6226774988830187, 0.0, 0.6226774988830187, 1.2998492701527629] - } - - self.assertEqual(actual, expected) - - def test_model_resistance_ice_wind_north(self): - cellbox = copy(self.cellbox) - cellbox.agg_data = { - 'speed': [7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, - 7.842665122593933, 7.842665122593933, 7.842665122593933], - 'SIC': 60., - 'thickness': 1., - 'density': 980., - 'ice resistance': 96634.5, - 'u10': 0., - 'v10': 10. - } - - actual = self.SDA.model_resistance(cellbox).agg_data - - expected = { - 'speed': [7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, 7.842665122593933, - 7.842665122593933, 7.842665122593933, 7.842665122593933], - 'SIC': 60.0, - 'thickness': 1.0, - 'density': 980.0, - 'ice resistance': 96634.5, - 'u10': 0.0, - 'v10': 10.0, - 'wind resistance': [1234.4775504276085, 19864.391781102568, 40525.12205230855, 61995.4919027709, 40525.1220523086, 19864.391781102568, 1234.4775504276222, -11604.216485701227], - 'relative wind speed': [8.598664182949458, 10.234546822417895, 11.64280342483676, 12.178519832423898, 11.642803424836762, 10.234546822417895, 8.598664182949458, 7.8214801675761025], - 'relative wind angle': [2.176072621553403, 1.356295795185576, 0.652700196811307, 0.0, 0.6527001968113064, 1.3562957951855759, 2.1760726215534025, 3.141592653589793], - 'resistance': [97868.9775504276, 116498.89178110257, 137159.62205230855, 158629.99190277088, 137159.6220523086, 116498.89178110257, 97868.97755042762, 85030.28351429878] - } - - self.assertEqual(actual, expected) + }), + ], + ids=['zero', 'north', 'east'] +) +def test_calc_wind(base_cellbox, speed, u10, v10, expected): + """Test complete wind calculation with different wind directions.""" + base_cellbox.agg_data = {'speed': speed, 'u10': u10, 'v10': v10} + result = calc_wind(base_cellbox).agg_data + assert result == expected + + +def test_model_resistance_ice_wind_north(sda_vessel, base_cellbox): + """Test combined ice and wind resistance modeling.""" + base_cellbox.agg_data = { + 'speed': [7.842665122593933]*8, + 'SIC': 60., + 'thickness': 1., + 'density': 980., + 'ice resistance': 96634.5, + 'u10': 0., + 'v10': 10. + } + result = sda_vessel.model_resistance(base_cellbox).agg_data + expected = { + 'speed': [7.842665122593933]*8, + 'SIC': 60.0, + 'thickness': 1.0, + 'density': 980.0, + 'ice resistance': 96634.5, + 'u10': 0.0, + 'v10': 10.0, + 'wind resistance': [1234.4775504276085, 19864.391781102568, 40525.12205230855, 61995.4919027709, 40525.1220523086, 19864.391781102568, 1234.4775504276222, -11604.216485701227], + 'relative wind speed': [8.598664182949458, 10.234546822417895, 11.64280342483676, 12.178519832423898, 11.642803424836762, 10.234546822417895, 8.598664182949458, 7.8214801675761025], + 'relative wind angle': [2.176072621553403, 1.356295795185576, 0.652700196811307, 0.0, 0.6527001968113064, 1.3562957951855759, 2.1760726215534025, 3.141592653589793], + 'resistance': [97868.9775504276, 116498.89178110257, 137159.62205230855, 158629.99190277088, 137159.6220523086, 116498.89178110257, 97868.97755042762, 85030.28351429878] + } + assert result == expected